@netnodeag/kraftwerk 0.79.0 → 0.81.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 (86) hide show
  1. package/README.md +88 -2
  2. package/dist/cli/daemon.js +10 -9
  3. package/dist/cli/doctor.js +1 -1
  4. package/dist/cli/entities.d.ts +8 -0
  5. package/dist/cli/entities.js +222 -0
  6. package/dist/cli/init.js +6 -2
  7. package/dist/cli/kraftwerk.js +10 -0
  8. package/dist/cli/temp.d.ts +2 -0
  9. package/dist/cli/temp.js +62 -0
  10. package/dist/cli/ui.js +2 -1
  11. package/dist/client/routes.d.ts +18 -0
  12. package/dist/client/routes.js +18 -0
  13. package/dist/config.d.ts +25 -0
  14. package/dist/config.js +17 -6
  15. package/dist/core/chat/acp.js +2 -1
  16. package/dist/core/chat/backend.d.ts +2 -0
  17. package/dist/core/chat/context.js +15 -7
  18. package/dist/core/chat/pi.js +2 -1
  19. package/dist/core/chat/seats.js +3 -0
  20. package/dist/core/chat/sessions.js +3 -0
  21. package/dist/core/entities.d.ts +171 -0
  22. package/dist/core/entities.js +441 -0
  23. package/dist/core/files.js +1 -0
  24. package/dist/core/git.js +3 -1
  25. package/dist/core/journal.d.ts +12 -2
  26. package/dist/core/journal.js +50 -5
  27. package/dist/core/log.d.ts +18 -0
  28. package/dist/core/log.js +23 -0
  29. package/dist/core/projects.d.ts +22 -33
  30. package/dist/core/projects.js +41 -135
  31. package/dist/core/records.d.ts +28 -0
  32. package/dist/core/records.js +71 -0
  33. package/dist/core/refs.d.ts +74 -0
  34. package/dist/core/refs.js +143 -0
  35. package/dist/core/search.d.ts +10 -0
  36. package/dist/core/search.js +12 -0
  37. package/dist/core/settings.d.ts +4 -0
  38. package/dist/core/settings.js +24 -3
  39. package/dist/core/slug.d.ts +9 -0
  40. package/dist/core/slug.js +18 -0
  41. package/dist/core/temp.d.ts +73 -0
  42. package/dist/core/temp.js +154 -0
  43. package/dist/core/trash.d.ts +1 -1
  44. package/dist/harness.d.ts +2 -0
  45. package/dist/harnesses/acp.js +1 -1
  46. package/dist/harnesses/claude.js +1 -0
  47. package/dist/harnesses/codex.js +1 -0
  48. package/dist/harnesses/pi.js +1 -0
  49. package/dist/run.d.ts +7 -0
  50. package/dist/run.js +13 -1
  51. package/dist/server/api/devices.js +2 -1
  52. package/dist/server/api/entities.d.ts +74 -0
  53. package/dist/server/api/entities.js +60 -0
  54. package/dist/server/api/files.d.ts +9 -1
  55. package/dist/server/api/files.js +13 -0
  56. package/dist/server/api/index.d.ts +75 -5
  57. package/dist/server/api/index.js +5 -0
  58. package/dist/server/api/projects.d.ts +3 -0
  59. package/dist/server/api/projects.js +3 -0
  60. package/dist/server/api/workspace.d.ts +15 -0
  61. package/dist/server/api/workspace.js +6 -0
  62. package/dist/server/cloud.js +3 -2
  63. package/dist/server/relay.d.ts +3 -2
  64. package/dist/server/relay.js +7 -7
  65. package/dist/server/server.js +3 -2
  66. package/dist/yaml.js +1 -0
  67. package/package.json +1 -1
  68. package/schema/kraftwerk.schema.json +54 -0
  69. package/web/dist/assets/{dist-cNijUfNl.js → dist-8IOFzZx-.js} +1 -1
  70. package/web/dist/assets/{dist-E48sO9Gc.js → dist-AhnSw4hy.js} +1 -1
  71. package/web/dist/assets/{dist-BTD8k55t.js → dist-BK0VhAPQ.js} +1 -1
  72. package/web/dist/assets/{dist-A0LN_JdM.js → dist-Ba6WSVOB.js} +1 -1
  73. package/web/dist/assets/{dist-DgP31lcC.js → dist-BiU4F98O.js} +1 -1
  74. package/web/dist/assets/{dist-wtIUmbnn.js → dist-Dx3aajX1.js} +1 -1
  75. package/web/dist/assets/{dist-DdOsafun.js → dist-Dy1SvhKC.js} +1 -1
  76. package/web/dist/assets/{dist-D93lSM0a.js → dist-ENZYo2jZ.js} +1 -1
  77. package/web/dist/assets/{dist-BasQkRXA.js → dist-FfKYtQxT.js} +1 -1
  78. package/web/dist/assets/{dist-xLZN2eGO.js → dist-cewmjxFi.js} +1 -1
  79. package/web/dist/assets/dist-lgUK2Fjt.js +1 -0
  80. package/web/dist/assets/{editor-BQlHN7qT.js → editor-CaRa1NMC.js} +3 -3
  81. package/web/dist/assets/index-Dpzux3OC.js +231 -0
  82. package/web/dist/assets/index-Dvt-vE1h.css +2 -0
  83. package/web/dist/index.html +2 -2
  84. package/web/dist/assets/dist-B04mMuvH.js +0 -1
  85. package/web/dist/assets/index-BKDJocDd.css +0 -2
  86. package/web/dist/assets/index-C7ddpWNC.js +0 -231
@@ -0,0 +1,74 @@
1
+ import * as z from "zod";
2
+ import type { EntityKind } from "../../config.js";
3
+ import { type EntityBase, type EntityKindDef } from "../../core/entities.js";
4
+ /**
5
+ * Contacts, places and events over the API. One factory, three kinds: the
6
+ * routes are named per kind (`contacts.list`, `places.get`, `events.save`)
7
+ * with the kind's own result type, so the typed client and the UI see
8
+ * three ordinary route families — not one generic entities endpoint whose
9
+ * results would be a union. Adding a kind is one definition in
10
+ * core/entities.ts and one factory call below.
11
+ */
12
+ export declare function entityRoutes<D extends EntityBase, K extends EntityKind>(def: EntityKindDef<D, K>): readonly [import("./router.js").Route<`${K}.list`, import("../../core/entities.js").EntitiesView<D>, `/api/${K}`, undefined>, import("./router.js").Route<`${K}.create`, import("./router.js").Reply<import("../../core/entities.js").EntityDetail<D>>, `/api/${K}`, z.ZodObject<{
13
+ notes: z.ZodOptional<z.ZodString>;
14
+ tags: z.ZodOptional<z.ZodUnknown>;
15
+ records: z.ZodOptional<z.ZodUnknown>;
16
+ title: z.ZodString;
17
+ slug: z.ZodOptional<z.ZodString>;
18
+ }, z.core.$loose>>, import("./router.js").Route<`${K}.get`, import("../../core/entities.js").EntityDetail<D>, `/api/${K}/:slug`, undefined>, import("./router.js").Route<`${K}.save`, import("../../core/entities.js").EntityDetail<D>, `/api/${K}/:slug`, z.ZodObject<{
19
+ title: z.ZodOptional<z.ZodString>;
20
+ notes: z.ZodOptional<z.ZodString>;
21
+ tags: z.ZodOptional<z.ZodUnknown>;
22
+ records: z.ZodOptional<z.ZodUnknown>;
23
+ }, z.core.$loose>>, import("./router.js").Route<`${K}.delete`, {
24
+ ok: boolean;
25
+ }, `/api/${K}/:slug`, undefined>];
26
+ export declare const contactRoutes: readonly [import("./router.js").Route<"contacts.list", import("../../core/entities.js").EntitiesView<import("../../core/entities.js").ContactDef>, "/api/contacts", undefined>, import("./router.js").Route<"contacts.create", import("./router.js").Reply<import("../../core/entities.js").EntityDetail<import("../../core/entities.js").ContactDef>>, "/api/contacts", z.ZodObject<{
27
+ notes: z.ZodOptional<z.ZodString>;
28
+ tags: z.ZodOptional<z.ZodUnknown>;
29
+ records: z.ZodOptional<z.ZodUnknown>;
30
+ title: z.ZodString;
31
+ slug: z.ZodOptional<z.ZodString>;
32
+ }, z.core.$loose>>, import("./router.js").Route<"contacts.get", import("../../core/entities.js").EntityDetail<import("../../core/entities.js").ContactDef>, "/api/contacts/:slug", undefined>, import("./router.js").Route<"contacts.save", import("../../core/entities.js").EntityDetail<import("../../core/entities.js").ContactDef>, "/api/contacts/:slug", z.ZodObject<{
33
+ title: z.ZodOptional<z.ZodString>;
34
+ notes: z.ZodOptional<z.ZodString>;
35
+ tags: z.ZodOptional<z.ZodUnknown>;
36
+ records: z.ZodOptional<z.ZodUnknown>;
37
+ }, z.core.$loose>>, import("./router.js").Route<"contacts.delete", {
38
+ ok: boolean;
39
+ }, "/api/contacts/:slug", undefined>];
40
+ export declare const placeRoutes: readonly [import("./router.js").Route<"places.list", import("../../core/entities.js").EntitiesView<import("../../core/entities.js").PlaceDef>, "/api/places", undefined>, import("./router.js").Route<"places.create", import("./router.js").Reply<import("../../core/entities.js").EntityDetail<import("../../core/entities.js").PlaceDef>>, "/api/places", z.ZodObject<{
41
+ notes: z.ZodOptional<z.ZodString>;
42
+ tags: z.ZodOptional<z.ZodUnknown>;
43
+ records: z.ZodOptional<z.ZodUnknown>;
44
+ title: z.ZodString;
45
+ slug: z.ZodOptional<z.ZodString>;
46
+ }, z.core.$loose>>, import("./router.js").Route<"places.get", import("../../core/entities.js").EntityDetail<import("../../core/entities.js").PlaceDef>, "/api/places/:slug", undefined>, import("./router.js").Route<"places.save", import("../../core/entities.js").EntityDetail<import("../../core/entities.js").PlaceDef>, "/api/places/:slug", z.ZodObject<{
47
+ title: z.ZodOptional<z.ZodString>;
48
+ notes: z.ZodOptional<z.ZodString>;
49
+ tags: z.ZodOptional<z.ZodUnknown>;
50
+ records: z.ZodOptional<z.ZodUnknown>;
51
+ }, z.core.$loose>>, import("./router.js").Route<"places.delete", {
52
+ ok: boolean;
53
+ }, "/api/places/:slug", undefined>];
54
+ export declare const eventRoutes: readonly [import("./router.js").Route<"events.list", import("../../core/entities.js").EntitiesView<import("../../core/entities.js").EventDef>, "/api/events", undefined>, import("./router.js").Route<"events.create", import("./router.js").Reply<import("../../core/entities.js").EntityDetail<import("../../core/entities.js").EventDef>>, "/api/events", z.ZodObject<{
55
+ notes: z.ZodOptional<z.ZodString>;
56
+ tags: z.ZodOptional<z.ZodUnknown>;
57
+ records: z.ZodOptional<z.ZodUnknown>;
58
+ title: z.ZodString;
59
+ slug: z.ZodOptional<z.ZodString>;
60
+ }, z.core.$loose>>, import("./router.js").Route<"events.get", import("../../core/entities.js").EntityDetail<import("../../core/entities.js").EventDef>, "/api/events/:slug", undefined>, import("./router.js").Route<"events.save", import("../../core/entities.js").EntityDetail<import("../../core/entities.js").EventDef>, "/api/events/:slug", z.ZodObject<{
61
+ title: z.ZodOptional<z.ZodString>;
62
+ notes: z.ZodOptional<z.ZodString>;
63
+ tags: z.ZodOptional<z.ZodUnknown>;
64
+ records: z.ZodOptional<z.ZodUnknown>;
65
+ }, z.core.$loose>>, import("./router.js").Route<"events.delete", {
66
+ ok: boolean;
67
+ }, "/api/events/:slug", undefined>];
68
+ /** References: resolve `kind:slug` tokens — given as a list, or found in a text — against the registry. */
69
+ export declare const refRoutes: import("./router.js").Route<"refs.resolve", {
70
+ refs: import("../../core/refs.js").ResolvedRef[];
71
+ }, "/api/refs/resolve", z.ZodObject<{
72
+ tokens: z.ZodDefault<z.ZodArray<z.ZodString>>;
73
+ text: z.ZodDefault<z.ZodString>;
74
+ }, z.core.$strip>>[];
@@ -0,0 +1,60 @@
1
+ import * as z from "zod";
2
+ import { CONTACTS, createEntity, deleteEntity, EVENTS, getEntity, listEntities, openEntities, PLACES, saveEntity } from "../../core/entities.js";
3
+ import { findRefs, parseRef, resolveTokens } from "../../core/refs.js";
4
+ import { fail, reply, route, statusByMessage } from "./router.js";
5
+ /**
6
+ * Contacts, places and events over the API. One factory, three kinds: the
7
+ * routes are named per kind (`contacts.list`, `places.get`, `events.save`)
8
+ * with the kind's own result type, so the typed client and the UI see
9
+ * three ordinary route families — not one generic entities endpoint whose
10
+ * results would be a union. Adding a kind is one definition in
11
+ * core/entities.ts and one factory call below.
12
+ */
13
+ export function entityRoutes(def) {
14
+ const errors = statusByMessage(new RegExp(`^no ${def.singular}`));
15
+ const kind = def.kind;
16
+ /** The editable fields. Loose: the domain checks values and keeps what it knows (unknown keys stay in the frontmatter). */
17
+ const Fields = z.looseObject({
18
+ title: z.string().optional(),
19
+ notes: z.string().optional(),
20
+ tags: z.unknown().optional(),
21
+ records: z.unknown().optional(),
22
+ ...Object.fromEntries(def.fields.map((f) => [f.name, z.unknown().optional()])),
23
+ });
24
+ const Create = Fields.extend({ title: z.string(), slug: z.string().optional() });
25
+ const fieldNames = def.fields.map((f) => f.name).join(", ");
26
+ return [
27
+ route({ name: `${kind}.list`, method: "GET", path: `/api/${kind}`, summary: `every ${def.singular} file under the root${def.span ? " (?from=&to= narrow by date; an entry counts while it runs)" : ""}` }, async (c) => listEntities(def, { from: c.query.get("from") ?? undefined, to: c.query.get("to") ?? undefined })),
28
+ route({ name: `${kind}.create`, method: "POST", path: `/api/${kind}`, summary: `create one {title, slug?, notes?, tags?, records?, ${fieldNames}}`, errors: 400, body: Create }, async (c) => {
29
+ if ((await openEntities(kind)).off)
30
+ fail(409, `${kind} are off`);
31
+ return reply(201, await createEntity(def, (await c.body())));
32
+ }),
33
+ route({ name: `${kind}.get`, method: "GET", path: `/api/${kind}/:slug`, summary: "fields, notes and link states", errors }, async (c) => (await getEntity(def, c.params.slug)) ?? fail(404, `no ${def.singular} "${c.params.slug}"`)),
34
+ route({ name: `${kind}.save`, method: "PUT", path: `/api/${kind}/:slug`, summary: 'save fields and notes ("" clears a field)', errors, body: Fields }, async (c) => saveEntity(def, c.params.slug, (await c.body()))),
35
+ route({ name: `${kind}.delete`, method: "DELETE", path: `/api/${kind}/:slug`, summary: "move the file to the trash", errors }, async (c) => {
36
+ await deleteEntity(def, c.params.slug);
37
+ return { ok: true };
38
+ }),
39
+ ];
40
+ }
41
+ export const contactRoutes = entityRoutes(CONTACTS);
42
+ export const placeRoutes = entityRoutes(PLACES);
43
+ export const eventRoutes = entityRoutes(EVENTS);
44
+ /** References: resolve `kind:slug` tokens — given as a list, or found in a text — against the registry. */
45
+ export const refRoutes = [
46
+ route({
47
+ name: "refs.resolve",
48
+ method: "POST",
49
+ path: "/api/refs/resolve",
50
+ summary: "resolve references {tokens?: [kind:slug], text?} to whether each target exists and where it opens",
51
+ errors: 400,
52
+ body: z.object({ tokens: z.array(z.string()).default([]), text: z.string().default("") }),
53
+ }, async (c) => {
54
+ const { tokens, text } = await c.body();
55
+ const given = tokens.map((t) => parseRef(t) ?? fail(400, `not a reference: "${t}" (expected <kind>:<slug>, e.g. contact:anna-meier)`));
56
+ const found = findRefs(text);
57
+ const all = [...given, ...found].filter((r, i, arr) => arr.findIndex((x) => x.kind === r.kind && x.slug === r.slug) === i);
58
+ return { refs: await resolveTokens(all) };
59
+ }),
60
+ ];
@@ -20,4 +20,12 @@ export declare const fileRoutes: (import("./router.js").Route<"files.delete", {
20
20
  }[];
21
21
  }, "/api/files/recent", undefined> | import("./router.js").Route<"files.scopes", {
22
22
  scopes: import("../../core/files.js").FilesScopeInfo[];
23
- }, "/api/files", undefined> | import("./router.js").Route<"files.upload", import("./router.js").Reply<import("../../core/files.js").FileEntry>, "/api/files/upload", undefined>)[];
23
+ }, "/api/files", undefined> | import("./router.js").Route<"files.upload", import("./router.js").Reply<import("../../core/files.js").FileEntry>, "/api/files/upload", undefined> | import("./router.js").Route<"temp.clean", {
24
+ ok: boolean;
25
+ removed: string[];
26
+ bytes: number;
27
+ }, "/api/temp", undefined> | import("./router.js").Route<"temp.list", {
28
+ entries: import("../../core/temp.js").TempEntry[];
29
+ bytes: number;
30
+ root: string;
31
+ }, "/api/temp", undefined>)[];
@@ -1,5 +1,7 @@
1
1
  import * as z from "zod";
2
2
  import { deleteFile, filesSummary, listFiles, listScopes, makeFolder, moveFile, openFile, saveUpload } from "../../core/files.js";
3
+ import { cleanTemp, listTemp } from "../../core/temp.js";
4
+ import { tildify } from "../../core/instances.js";
3
5
  import { reply, route } from "./router.js";
4
6
  const errors = (message) => (/^no (file|folder|project)/.test(message) ? 404 : 400);
5
7
  /** The scope (workspace, or a project's folder) and the path inside it, from the query. */
@@ -45,4 +47,15 @@ export const fileRoutes = [
45
47
  await deleteFile(...where(c));
46
48
  return { ok: true };
47
49
  }),
50
+ // The temp root: one scratch folder per session (core/temp.ts). Nothing in it is a deliverable, so a clean deletes for good.
51
+ route({ name: "temp.list", method: "GET", path: "/api/temp", summary: "the sessions' scratch folders, newest first", errors }, async () => {
52
+ const view = await listTemp();
53
+ return { ...view, root: tildify(view.root) };
54
+ }),
55
+ route({ name: "temp.clean", method: "DELETE", path: "/api/temp", summary: "remove scratch folders idle a week ?olderThanDays, or every one ?all=1", errors }, async (c) => {
56
+ const raw = c.query.get("olderThanDays");
57
+ const days = raw === null ? NaN : Number(raw);
58
+ const gone = await cleanTemp({ all: c.query.get("all") === "1", ...(Number.isFinite(days) && days >= 0 ? { olderThanMs: days * 24 * 60 * 60 * 1000 } : {}) });
59
+ return { ok: true, removed: gone.map((e) => e.id), bytes: gone.reduce((n, e) => n + e.bytes, 0) };
60
+ }),
48
61
  ];
@@ -367,7 +367,20 @@ export declare const routes: (Route<"agents.addJournal", {
367
367
  claimCode?: string | null;
368
368
  claimUrl?: string | null;
369
369
  workspaces?: number;
370
- }, "/api/cloud/unlink", undefined> | Route<"devices.code", {
370
+ }, "/api/cloud/unlink", undefined> | Route<"contacts.create", import("./router.js").Reply<import("../../core/entities.js").EntityDetail<import("../../core/entities.js").ContactDef>>, "/api/contacts", z.ZodObject<{
371
+ notes: z.ZodOptional<z.ZodString>;
372
+ tags: z.ZodOptional<z.ZodUnknown>;
373
+ records: z.ZodOptional<z.ZodUnknown>;
374
+ title: z.ZodString;
375
+ slug: z.ZodOptional<z.ZodString>;
376
+ }, z.core.$loose>> | Route<"contacts.delete", {
377
+ ok: boolean;
378
+ }, "/api/contacts/:slug", undefined> | Route<"contacts.get", import("../../core/entities.js").EntityDetail<import("../../core/entities.js").ContactDef>, "/api/contacts/:slug", undefined> | Route<"contacts.list", import("../../core/entities.js").EntitiesView<import("../../core/entities.js").ContactDef>, "/api/contacts", undefined> | Route<"contacts.save", import("../../core/entities.js").EntityDetail<import("../../core/entities.js").ContactDef>, "/api/contacts/:slug", z.ZodObject<{
379
+ title: z.ZodOptional<z.ZodString>;
380
+ notes: z.ZodOptional<z.ZodString>;
381
+ tags: z.ZodOptional<z.ZodUnknown>;
382
+ records: z.ZodOptional<z.ZodUnknown>;
383
+ }, z.core.$loose>> | Route<"devices.code", {
371
384
  code: string;
372
385
  expiresAt: string;
373
386
  urls: string[];
@@ -427,7 +440,20 @@ export declare const routes: (Route<"agents.addJournal", {
427
440
  }, "/api/devices/relay", z.ZodObject<{
428
441
  enabled: z.ZodBoolean;
429
442
  url: z.ZodOptional<z.ZodString>;
430
- }, z.core.$strip>> | Route<"files.delete", {
443
+ }, z.core.$strip>> | Route<"events.create", import("./router.js").Reply<import("../../core/entities.js").EntityDetail<import("../../core/entities.js").EventDef>>, "/api/events", z.ZodObject<{
444
+ notes: z.ZodOptional<z.ZodString>;
445
+ tags: z.ZodOptional<z.ZodUnknown>;
446
+ records: z.ZodOptional<z.ZodUnknown>;
447
+ title: z.ZodString;
448
+ slug: z.ZodOptional<z.ZodString>;
449
+ }, z.core.$loose>> | Route<"events.delete", {
450
+ ok: boolean;
451
+ }, "/api/events/:slug", undefined> | Route<"events.get", import("../../core/entities.js").EntityDetail<import("../../core/entities.js").EventDef>, "/api/events/:slug", undefined> | Route<"events.list", import("../../core/entities.js").EntitiesView<import("../../core/entities.js").EventDef>, "/api/events", undefined> | Route<"events.save", import("../../core/entities.js").EntityDetail<import("../../core/entities.js").EventDef>, "/api/events/:slug", z.ZodObject<{
452
+ title: z.ZodOptional<z.ZodString>;
453
+ notes: z.ZodOptional<z.ZodString>;
454
+ tags: z.ZodOptional<z.ZodUnknown>;
455
+ records: z.ZodOptional<z.ZodUnknown>;
456
+ }, z.core.$loose>> | Route<"files.delete", {
431
457
  ok: boolean;
432
458
  }, "/api/files", undefined> | Route<"files.list", import("../../core/files.js").FilesListing, "/api/files/list", undefined> | Route<"files.mkdir", import("./router.js").Reply<import("../../core/files.js").FileEntry>, "/api/files/folder", z.ZodObject<{
433
459
  scope: z.ZodOptional<z.ZodString>;
@@ -520,6 +546,9 @@ export declare const routes: (Route<"agents.addJournal", {
520
546
  repos: boolean;
521
547
  vibeables: boolean;
522
548
  projects: boolean;
549
+ contacts: boolean;
550
+ places: boolean;
551
+ events: boolean;
523
552
  publicUrl: string;
524
553
  cloud: {
525
554
  included: boolean;
@@ -545,7 +574,20 @@ export declare const routes: (Route<"agents.addJournal", {
545
574
  href: string;
546
575
  }, "/api/notifications/:id/diagnose", undefined> | Route<"notifications.list", import("../../core/notifications.js").NotificationsView, "/api/notifications", undefined> | Route<"notifications.read", import("../../core/notifications.js").NotificationsView, "/api/notifications/read", z.ZodObject<{
547
576
  ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
548
- }, z.core.$strip>> | Route<"projects.create", import("./router.js").Reply<import("../../core/projects.js").ProjectDetail>, "/api/projects", z.ZodObject<{
577
+ }, z.core.$strip>> | Route<"places.create", import("./router.js").Reply<import("../../core/entities.js").EntityDetail<import("../../core/entities.js").PlaceDef>>, "/api/places", z.ZodObject<{
578
+ notes: z.ZodOptional<z.ZodString>;
579
+ tags: z.ZodOptional<z.ZodUnknown>;
580
+ records: z.ZodOptional<z.ZodUnknown>;
581
+ title: z.ZodString;
582
+ slug: z.ZodOptional<z.ZodString>;
583
+ }, z.core.$loose>> | Route<"places.delete", {
584
+ ok: boolean;
585
+ }, "/api/places/:slug", undefined> | Route<"places.get", import("../../core/entities.js").EntityDetail<import("../../core/entities.js").PlaceDef>, "/api/places/:slug", undefined> | Route<"places.list", import("../../core/entities.js").EntitiesView<import("../../core/entities.js").PlaceDef>, "/api/places", undefined> | Route<"places.save", import("../../core/entities.js").EntityDetail<import("../../core/entities.js").PlaceDef>, "/api/places/:slug", z.ZodObject<{
586
+ title: z.ZodOptional<z.ZodString>;
587
+ notes: z.ZodOptional<z.ZodString>;
588
+ tags: z.ZodOptional<z.ZodUnknown>;
589
+ records: z.ZodOptional<z.ZodUnknown>;
590
+ }, z.core.$loose>> | Route<"projects.create", import("./router.js").Reply<import("../../core/projects.js").ProjectDetail>, "/api/projects", z.ZodObject<{
549
591
  title: z.ZodString;
550
592
  goal: z.ZodOptional<z.ZodString>;
551
593
  slug: z.ZodOptional<z.ZodString>;
@@ -576,6 +618,9 @@ export declare const routes: (Route<"agents.addJournal", {
576
618
  repos: z.ZodOptional<z.ZodUnknown>;
577
619
  workflows: z.ZodOptional<z.ZodUnknown>;
578
620
  agents: z.ZodOptional<z.ZodUnknown>;
621
+ contacts: z.ZodOptional<z.ZodUnknown>;
622
+ places: z.ZodOptional<z.ZodUnknown>;
623
+ events: z.ZodOptional<z.ZodUnknown>;
579
624
  }, z.core.$loose>> | Route<"projects.saveLayout", {
580
625
  ok: boolean;
581
626
  layout: import("../../core/projects.js").ProjectLayout;
@@ -588,7 +633,12 @@ export declare const routes: (Route<"agents.addJournal", {
588
633
  }, z.core.$strip>> | Route<"protocol.get", {
589
634
  version: number;
590
635
  routes: RouteInfo[];
591
- }, "/api/protocol", undefined> | Route<"repos.add", import("./router.js").Reply<import("../../core/repos.js").RepoInfo>, "/api/repos", z.ZodObject<{
636
+ }, "/api/protocol", undefined> | Route<"refs.resolve", {
637
+ refs: import("../../core/refs.js").ResolvedRef[];
638
+ }, "/api/refs/resolve", z.ZodObject<{
639
+ tokens: z.ZodDefault<z.ZodArray<z.ZodString>>;
640
+ text: z.ZodDefault<z.ZodString>;
641
+ }, z.core.$strip>> | Route<"repos.add", import("./router.js").Reply<import("../../core/repos.js").RepoInfo>, "/api/repos", z.ZodObject<{
592
642
  url: z.ZodDefault<z.ZodString>;
593
643
  name: z.ZodOptional<z.ZodString>;
594
644
  branch: z.ZodOptional<z.ZodString>;
@@ -663,10 +713,30 @@ export declare const routes: (Route<"agents.addJournal", {
663
713
  enabled: z.ZodBoolean;
664
714
  root: z.ZodOptional<z.ZodString>;
665
715
  }, z.core.$strip>>;
716
+ contacts: z.ZodOptional<z.ZodObject<{
717
+ enabled: z.ZodBoolean;
718
+ root: z.ZodOptional<z.ZodString>;
719
+ }, z.core.$strip>>;
720
+ places: z.ZodOptional<z.ZodObject<{
721
+ enabled: z.ZodBoolean;
722
+ root: z.ZodOptional<z.ZodString>;
723
+ }, z.core.$strip>>;
724
+ events: z.ZodOptional<z.ZodObject<{
725
+ enabled: z.ZodBoolean;
726
+ root: z.ZodOptional<z.ZodString>;
727
+ }, z.core.$strip>>;
666
728
  }, z.core.$loose>> | Route<"skills.get", import("../../core/skills.js").SkillDetail, "/api/skills/:name", undefined> | Route<"skills.list", {
667
729
  root: string;
668
730
  skills: import("../../core/skills.js").SkillInfo[];
669
- }, "/api/skills", undefined> | Route<"trash.empty", {
731
+ }, "/api/skills", undefined> | Route<"temp.clean", {
732
+ ok: boolean;
733
+ removed: string[];
734
+ bytes: number;
735
+ }, "/api/temp", undefined> | Route<"temp.list", {
736
+ entries: import("../../core/temp.js").TempEntry[];
737
+ bytes: number;
738
+ root: string;
739
+ }, "/api/temp", undefined> | Route<"trash.empty", {
670
740
  ok: boolean;
671
741
  purged: number;
672
742
  }, "/api/trash", undefined> | Route<"trash.list", {
@@ -5,6 +5,7 @@ import { channelRoutes } from "./channels.js";
5
5
  import { chatRoutes } from "./chats.js";
6
6
  import { cloudRoutes } from "./cloud.js";
7
7
  import { deviceRoutes } from "./devices.js";
8
+ import { contactRoutes, eventRoutes, placeRoutes, refRoutes } from "./entities.js";
8
9
  import { hubRoutes } from "./hub.js";
9
10
  import { fileRoutes } from "./files.js";
10
11
  import { gitRoutes } from "./git.js";
@@ -30,6 +31,10 @@ const domainRoutes = [
30
31
  ...chatRoutes,
31
32
  ...channelRoutes,
32
33
  ...projectRoutes,
34
+ ...contactRoutes,
35
+ ...placeRoutes,
36
+ ...eventRoutes,
37
+ ...refRoutes,
33
38
  ...runRoutes,
34
39
  ...knowledgeRoutes,
35
40
  ...fileRoutes,
@@ -32,6 +32,9 @@ export declare const projectRoutes: (import("./router.js").Route<"projects.creat
32
32
  repos: z.ZodOptional<z.ZodUnknown>;
33
33
  workflows: z.ZodOptional<z.ZodUnknown>;
34
34
  agents: z.ZodOptional<z.ZodUnknown>;
35
+ contacts: z.ZodOptional<z.ZodUnknown>;
36
+ places: z.ZodOptional<z.ZodUnknown>;
37
+ events: z.ZodOptional<z.ZodUnknown>;
35
38
  }, z.core.$loose>> | import("./router.js").Route<"projects.saveLayout", {
36
39
  ok: boolean;
37
40
  layout: import("../../core/projects.js").ProjectLayout;
@@ -23,6 +23,9 @@ const ProjectFields = z.looseObject({
23
23
  repos: z.unknown().optional(),
24
24
  workflows: z.unknown().optional(),
25
25
  agents: z.unknown().optional(),
26
+ contacts: z.unknown().optional(),
27
+ places: z.unknown().optional(),
28
+ events: z.unknown().optional(),
26
29
  });
27
30
  /** Projects: folders with a goal, a brief, a log and links, plus the rail's order. */
28
31
  export const projectRoutes = [
@@ -33,6 +33,9 @@ export declare const workspaceRoutes: (import("./router.js").Route<"identity.get
33
33
  repos: boolean;
34
34
  vibeables: boolean;
35
35
  projects: boolean;
36
+ contacts: boolean;
37
+ places: boolean;
38
+ events: boolean;
36
39
  /** @deprecated Always "": the Cloudflare Tunnel that set it was removed in 0.65. Removed with protocol 2. */
37
40
  publicUrl: string;
38
41
  cloud: {
@@ -86,6 +89,18 @@ export declare const workspaceRoutes: (import("./router.js").Route<"identity.get
86
89
  enabled: z.ZodBoolean;
87
90
  root: z.ZodOptional<z.ZodString>;
88
91
  }, z.core.$strip>>;
92
+ contacts: z.ZodOptional<z.ZodObject<{
93
+ enabled: z.ZodBoolean;
94
+ root: z.ZodOptional<z.ZodString>;
95
+ }, z.core.$strip>>;
96
+ places: z.ZodOptional<z.ZodObject<{
97
+ enabled: z.ZodBoolean;
98
+ root: z.ZodOptional<z.ZodString>;
99
+ }, z.core.$strip>>;
100
+ events: z.ZodOptional<z.ZodObject<{
101
+ enabled: z.ZodBoolean;
102
+ root: z.ZodOptional<z.ZodString>;
103
+ }, z.core.$strip>>;
89
104
  }, z.core.$loose>> | import("./router.js").Route<"update.check", import("./router.js").Reply<{
90
105
  name: string;
91
106
  current: string;
@@ -33,6 +33,9 @@ const SettingsFields = z.looseObject({
33
33
  repos: RootBlock.optional(),
34
34
  vibeables: RootBlock.optional(),
35
35
  projects: RootBlock.optional(),
36
+ contacts: RootBlock.optional(),
37
+ places: RootBlock.optional(),
38
+ events: RootBlock.optional(),
36
39
  });
37
40
  /**
38
41
  * Mark the workspaces the hub answering this request serves — or, a daemon,
@@ -105,6 +108,9 @@ export const workspaceRoutes = [
105
108
  repos: (ws?.config.repos && ws.config.repos.enabled !== false) === true,
106
109
  vibeables: (ws?.config.vibeables && ws.config.vibeables.enabled !== false) === true,
107
110
  projects: (ws?.config.projects && ws.config.projects.enabled !== false) === true,
111
+ contacts: (ws?.config.contacts && ws.config.contacts.enabled !== false) === true,
112
+ places: (ws?.config.places && ws.config.places.enabled !== false) === true,
113
+ events: (ws?.config.events && ws.config.events.enabled !== false) === true,
108
114
  /** @deprecated Always "": the Cloudflare Tunnel that set it was removed in 0.65. Removed with protocol 2. */
109
115
  publicUrl: "",
110
116
  // The machine's cloud record (only the daemon registers one); `included`: this workspace rides along (no cloud.enabled: false veto).
@@ -1,4 +1,5 @@
1
1
  import { promises as fs } from "node:fs";
2
+ import { log } from "../core/log.js";
2
3
  import os from "node:os";
3
4
  import { resolveWorkspace } from "../config.js";
4
5
  import { claimUrlFor, machineCloud, machineGoodbye, machineHeartbeat, machineRegister, machineUnlink, MAX_REPORTED_ROUTINES, retiredCloudDir, unknownMachine, workspaceInvite, } from "../core/cloud.js";
@@ -74,10 +75,10 @@ function warnYml(cloud, ymlUrls) {
74
75
  return;
75
76
  m.warned.add(key);
76
77
  if (cloud.source === "kraftwerk.yml") {
77
- console.warn(`⚠ kraftwerk.yml cloud.url is deprecated — the cloud is the machine's now: \`kraftwerk cloud on --url ${cloud.url}\` (using ${cloud.url} for now)`);
78
+ log("warn", "cloud", `kraftwerk.yml cloud.url is deprecated — the cloud is the machine's now: \`kraftwerk cloud on --url ${cloud.url}\` (using ${cloud.url} for now)`);
78
79
  }
79
80
  else {
80
- console.warn(`⚠ kraftwerk.yml cloud.url (${ymlUrls[0]}) is ignored: the machine's cloud is ${cloud.url} (${cloud.source}); remove it from kraftwerk.yml`);
81
+ log("warn", "cloud", `kraftwerk.yml cloud.url (${ymlUrls[0]}) is ignored: the machine's cloud is ${cloud.url} (${cloud.source}); remove it from kraftwerk.yml`);
81
82
  }
82
83
  }
83
84
  async function register() {
@@ -93,14 +93,15 @@ export interface RelayOptions {
93
93
  connect?: (url: string) => WebSocket;
94
94
  /** Give up on a connection the relay has not accepted (`ok`) within this long, and try again (default 15 s). */
95
95
  handshakeTimeoutMs?: number;
96
- log?: (line: string) => void;
96
+ /** One relay event, leveled — the daemon writes it as a standard log line. */
97
+ log?: (level: "info" | "warn", line: string) => void;
97
98
  }
98
99
  /** Hold the daemon's socket to the relay, reconnecting with backoff, and answer every client behind it. */
99
100
  export declare function startRelay(opts: RelayOptions): Promise<RelayLink>;
100
101
  /** A short fingerprint of the machine key, to compare what a phone shows with what this machine shows. */
101
102
  export declare const keyFingerprint: (key: string) => string;
102
103
  /** Start, restart or stop this daemon's relay link to match ~/.kraftwerk/relay.json. */
103
- export declare function applyRelay(port: number, version: string, log?: (line: string) => void): Promise<RelayStatus>;
104
+ export declare function applyRelay(port: number, version: string, log?: (level: "info" | "warn", line: string) => void): Promise<RelayStatus>;
104
105
  /** Hand push notifications to this daemon's relay link; false without one. */
105
106
  export declare const relayPush: (pushes: PushMessage[]) => boolean;
106
107
  /** The link's state; "off" when this process holds none (not the daemon, or the relay is off). */
@@ -188,7 +188,7 @@ export async function startRelay(opts) {
188
188
  send({ t: "msg", c, d: JSON.stringify(ready) });
189
189
  }
190
190
  catch (err) {
191
- log(`relay: handshake refused — ${err.message}`);
191
+ log("warn", `handshake refused — ${err.message}`);
192
192
  send({ t: "close", c, reason: "handshake failed" });
193
193
  dropConn(c);
194
194
  }
@@ -216,7 +216,7 @@ export async function startRelay(opts) {
216
216
  return;
217
217
  }
218
218
  if (!r.ok) {
219
- log(`relay: member refused — ${r.error}`);
219
+ log("warn", `member refused — ${r.error}`);
220
220
  await reply(c, conn, { t: "auth", ok: false, error: r.error });
221
221
  send({ t: "close", c, reason: r.error.slice(0, 100) });
222
222
  dropConn(c);
@@ -234,7 +234,7 @@ export async function startRelay(opts) {
234
234
  if (!check.ok) {
235
235
  await reply(c, conn, { t: "fail", id: msg.id, error: check.error });
236
236
  if (check.ended) {
237
- log(`relay: member session dropped — ${check.error}`);
237
+ log("warn", `member session dropped — ${check.error}`);
238
238
  send({ t: "close", c, reason: check.error.slice(0, 100) });
239
239
  dropConn(c);
240
240
  }
@@ -282,7 +282,7 @@ export async function startRelay(opts) {
282
282
  catch { }
283
283
  status.state = "error";
284
284
  status.error = "the relay did not answer";
285
- log("relay: no answer from the relay — trying again");
285
+ log("warn", "no answer from the relay — trying again");
286
286
  schedule();
287
287
  }, opts.handshakeTimeoutMs ?? 15_000);
288
288
  handshake.unref?.();
@@ -305,7 +305,7 @@ export async function startRelay(opts) {
305
305
  status.state = "connected";
306
306
  status.error = undefined;
307
307
  status.since = new Date().toISOString();
308
- log(`relay: connected to ${opts.settings.url} as ${id}`);
308
+ log("info", `connected to ${opts.settings.url} as ${id}`);
309
309
  }
310
310
  else if (msg.t === "open" && msg.c) {
311
311
  dropConn(msg.c);
@@ -337,11 +337,11 @@ export async function startRelay(opts) {
337
337
  if (stopped)
338
338
  return;
339
339
  if (status.state === "connected")
340
- log("relay: connection lost — reconnecting");
340
+ log("warn", "connection lost — reconnecting");
341
341
  status.state = "error";
342
342
  status.error = ev.code === 4401 ? `the relay refused this machine: ${ev.reason || "wrong secret"}` : ev.reason || "the relay connection closed";
343
343
  if (ev.code === 4401)
344
- log(`relay: ${status.error}`);
344
+ log("warn", `${status.error}`);
345
345
  schedule();
346
346
  });
347
347
  socket.addEventListener("error", () => { });
@@ -1,4 +1,5 @@
1
1
  import { promises as fs } from "node:fs";
2
+ import { log } from "../core/log.js";
2
3
  import http from "node:http";
3
4
  import net from "node:net";
4
5
  import path from "node:path";
@@ -145,7 +146,7 @@ function installExitHandlers() {
145
146
  process.on("uncaughtException", (err) => {
146
147
  if (closedOnly(err))
147
148
  return;
148
- console.error(`✖ ${new Date().toISOString()} crashed:`, err);
149
+ log("error", "server", `crashed: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`);
149
150
  process.exit(1);
150
151
  });
151
152
  // Chat agent subprocesses must die with the server — signals bypass "exit" handlers, so hook the signals themselves.
@@ -377,7 +378,7 @@ export async function startHub(opts) {
377
378
  await opts.onChange?.(hub);
378
379
  }
379
380
  catch (err) {
380
- console.error(`kraftwerk: could not note the open workspaces: ${err.message}`);
381
+ log("warn", "daemon", `could not note the open workspaces: ${err.message}`);
381
382
  }
382
383
  }
383
384
  async function openNow(root, o) {
package/dist/yaml.js CHANGED
@@ -293,6 +293,7 @@ export async function loadWorkflow(givenPath) {
293
293
  verbose,
294
294
  workspaceContext: [
295
295
  `Working directory (read and create all files here): ${runDir}`,
296
+ `Temporary files (scratch, downloads, intermediate output, one-off scripts) go to ${runDir}/temp — TMPDIR and KRAFTWERK_TEMP point there; nothing in it counts as a result of the run.`,
296
297
  workspace,
297
298
  ]
298
299
  .filter(Boolean)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netnodeag/kraftwerk",
3
- "version": "0.79.0",
3
+ "version": "0.81.0",
4
4
  "description": "Open-source agentic workspace for teams \u2014 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",
@@ -171,6 +171,60 @@
171
171
  }
172
172
  }
173
173
  },
174
+ "contacts": {
175
+ "type": [
176
+ "object",
177
+ "null"
178
+ ],
179
+ "additionalProperties": false,
180
+ "description": "Contacts: people and organisations the workspace works with, one Markdown file (YAML frontmatter + notes) each under the root, part of the workspace. Referenced as contact:<slug> from projects, events and text. Absent = feature off, a bare `contacts:` = on with defaults.",
181
+ "properties": {
182
+ "enabled": {
183
+ "type": "boolean",
184
+ "description": "Turn the feature off while keeping the config. Default: true when this block exists"
185
+ },
186
+ "root": {
187
+ "type": "string",
188
+ "description": "Where the contact files live, relative to the project root. Default: kraftwerk-data/contacts"
189
+ }
190
+ }
191
+ },
192
+ "places": {
193
+ "type": [
194
+ "object",
195
+ "null"
196
+ ],
197
+ "additionalProperties": false,
198
+ "description": "Places: addresses and points of interest, one Markdown file (YAML frontmatter + notes) each under the root, part of the workspace. Referenced as place:<slug>; a Google Maps link is derived from coordinates or the address. Absent = feature off, a bare `places:` = on with defaults.",
199
+ "properties": {
200
+ "enabled": {
201
+ "type": "boolean",
202
+ "description": "Turn the feature off while keeping the config. Default: true when this block exists"
203
+ },
204
+ "root": {
205
+ "type": "string",
206
+ "description": "Where the place files live, relative to the project root. Default: kraftwerk-data/places"
207
+ }
208
+ }
209
+ },
210
+ "events": {
211
+ "type": [
212
+ "object",
213
+ "null"
214
+ ],
215
+ "additionalProperties": false,
216
+ "description": "Events: dated, one-off entries (a meeting, a launch, a deadline) with a place, contacts and a project, one Markdown file (YAML frontmatter + notes) each under the root, part of the workspace. Referenced as event:<slug>. Absent = feature off, a bare `events:` = on with defaults.",
217
+ "properties": {
218
+ "enabled": {
219
+ "type": "boolean",
220
+ "description": "Turn the feature off while keeping the config. Default: true when this block exists"
221
+ },
222
+ "root": {
223
+ "type": "string",
224
+ "description": "Where the event files live, relative to the project root. Default: kraftwerk-data/events"
225
+ }
226
+ }
227
+ },
174
228
  "cloud": {
175
229
  "type": [
176
230
  "object",