@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.
- package/README.md +88 -2
- package/dist/cli/daemon.js +10 -9
- package/dist/cli/doctor.js +1 -1
- package/dist/cli/entities.d.ts +8 -0
- package/dist/cli/entities.js +222 -0
- package/dist/cli/init.js +6 -2
- package/dist/cli/kraftwerk.js +10 -0
- package/dist/cli/temp.d.ts +2 -0
- package/dist/cli/temp.js +62 -0
- package/dist/cli/ui.js +2 -1
- package/dist/client/routes.d.ts +18 -0
- package/dist/client/routes.js +18 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.js +17 -6
- package/dist/core/chat/acp.js +2 -1
- package/dist/core/chat/backend.d.ts +2 -0
- package/dist/core/chat/context.js +15 -7
- package/dist/core/chat/pi.js +2 -1
- package/dist/core/chat/seats.js +3 -0
- package/dist/core/chat/sessions.js +3 -0
- package/dist/core/entities.d.ts +171 -0
- package/dist/core/entities.js +441 -0
- package/dist/core/files.js +1 -0
- package/dist/core/git.js +3 -1
- package/dist/core/journal.d.ts +12 -2
- package/dist/core/journal.js +50 -5
- package/dist/core/log.d.ts +18 -0
- package/dist/core/log.js +23 -0
- package/dist/core/projects.d.ts +22 -33
- package/dist/core/projects.js +41 -135
- package/dist/core/records.d.ts +28 -0
- package/dist/core/records.js +71 -0
- package/dist/core/refs.d.ts +74 -0
- package/dist/core/refs.js +143 -0
- package/dist/core/search.d.ts +10 -0
- package/dist/core/search.js +12 -0
- package/dist/core/settings.d.ts +4 -0
- package/dist/core/settings.js +24 -3
- package/dist/core/slug.d.ts +9 -0
- package/dist/core/slug.js +18 -0
- package/dist/core/temp.d.ts +73 -0
- package/dist/core/temp.js +154 -0
- package/dist/core/trash.d.ts +1 -1
- package/dist/harness.d.ts +2 -0
- package/dist/harnesses/acp.js +1 -1
- package/dist/harnesses/claude.js +1 -0
- package/dist/harnesses/codex.js +1 -0
- package/dist/harnesses/pi.js +1 -0
- package/dist/run.d.ts +7 -0
- package/dist/run.js +13 -1
- package/dist/server/api/devices.js +2 -1
- package/dist/server/api/entities.d.ts +74 -0
- package/dist/server/api/entities.js +60 -0
- package/dist/server/api/files.d.ts +9 -1
- package/dist/server/api/files.js +13 -0
- package/dist/server/api/index.d.ts +75 -5
- package/dist/server/api/index.js +5 -0
- package/dist/server/api/projects.d.ts +3 -0
- package/dist/server/api/projects.js +3 -0
- package/dist/server/api/workspace.d.ts +15 -0
- package/dist/server/api/workspace.js +6 -0
- package/dist/server/cloud.js +3 -2
- package/dist/server/relay.d.ts +3 -2
- package/dist/server/relay.js +7 -7
- package/dist/server/server.js +3 -2
- package/dist/yaml.js +1 -0
- package/package.json +1 -1
- package/schema/kraftwerk.schema.json +54 -0
- package/web/dist/assets/{dist-cNijUfNl.js → dist-8IOFzZx-.js} +1 -1
- package/web/dist/assets/{dist-E48sO9Gc.js → dist-AhnSw4hy.js} +1 -1
- package/web/dist/assets/{dist-BTD8k55t.js → dist-BK0VhAPQ.js} +1 -1
- package/web/dist/assets/{dist-A0LN_JdM.js → dist-Ba6WSVOB.js} +1 -1
- package/web/dist/assets/{dist-DgP31lcC.js → dist-BiU4F98O.js} +1 -1
- package/web/dist/assets/{dist-wtIUmbnn.js → dist-Dx3aajX1.js} +1 -1
- package/web/dist/assets/{dist-DdOsafun.js → dist-Dy1SvhKC.js} +1 -1
- package/web/dist/assets/{dist-D93lSM0a.js → dist-ENZYo2jZ.js} +1 -1
- package/web/dist/assets/{dist-BasQkRXA.js → dist-FfKYtQxT.js} +1 -1
- package/web/dist/assets/{dist-xLZN2eGO.js → dist-cewmjxFi.js} +1 -1
- package/web/dist/assets/dist-lgUK2Fjt.js +1 -0
- package/web/dist/assets/{editor-BQlHN7qT.js → editor-CaRa1NMC.js} +3 -3
- package/web/dist/assets/index-Dpzux3OC.js +231 -0
- package/web/dist/assets/index-Dvt-vE1h.css +2 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/dist-B04mMuvH.js +0 -1
- package/web/dist/assets/index-BKDJocDd.css +0 -2
- package/web/dist/assets/index-C7ddpWNC.js +0 -231
package/README.md
CHANGED
|
@@ -130,6 +130,9 @@ set up the rest. From there:
|
|
|
130
130
|
Open one and chat with it; its sessions continue where they ended.
|
|
131
131
|
- **Projects** gather a goal, a brief and links; every chat in a project
|
|
132
132
|
starts with that context.
|
|
133
|
+
- **Contacts, places and events** are the workspace's records of who, where
|
|
134
|
+
and when — one Markdown file each, referenced anywhere as `contact:<slug>`,
|
|
135
|
+
`place:<slug>`, `event:<slug>`.
|
|
133
136
|
- **Workflows** are repeatable processes with checked results; run one from
|
|
134
137
|
its page or with `kraftwerk run <workflow> "<request>"`.
|
|
135
138
|
- **Knowledge** is the shared, human-verified context agents read.
|
|
@@ -256,6 +259,8 @@ kraftwerk workspaces # every workspace on this machine; works
|
|
|
256
259
|
kraftwerk devices # paired devices (a phone, another computer); devices pair|revoke
|
|
257
260
|
kraftwerk remote on|off|pair # reach this machine from anywhere through the relay, end to end encrypted
|
|
258
261
|
kraftwerk projects # goal-scoped project folders; projects create|show|link|log|remove
|
|
262
|
+
kraftwerk contacts|places|events # who, where and when, one Markdown file each; … show|create|set|remove, events --from/--to
|
|
263
|
+
kraftwerk ref <kind:slug> # resolve a reference (contact:anna-meier, project:relaunch, …); --file <md> resolves every one in a file
|
|
259
264
|
kraftwerk trash # what was deleted; trash restore|purge <id>, trash empty
|
|
260
265
|
kraftwerk journal <agent> ["<entry>"] # an agent's memory across sessions: print it, add a line (--kind)
|
|
261
266
|
kraftwerk doctor # preflight: harness CLIs, docker, workflows, declared env vars, legacy leftovers
|
|
@@ -295,7 +300,9 @@ Optional, at the project root, and also the root marker for the walk-up. All
|
|
|
295
300
|
fields are optional. `workflows:` sets the workflows root, `output:` the
|
|
296
301
|
run-artifact directory (default `output/`), `knowledge:` the OKF bundle root
|
|
297
302
|
(default `knowledge/`), and `agents:` the agent-definition root (default
|
|
298
|
-
`agents/`). `repos:` turns on the [repositories](#repositories) folder
|
|
303
|
+
`agents/`). `repos:` turns on the [repositories](#repositories) folder,
|
|
304
|
+
`projects:` the [projects](#projects), and `contacts:`, `places:` and
|
|
305
|
+
`events:` each one of the [contacts, places and events](#contacts-places-and-events).
|
|
299
306
|
`cloud: { enabled: false }` leaves the workspace out of this machine's
|
|
300
307
|
record in the [kraftwerk cloud](#this-machine-in-the-kraftwerk-cloud).
|
|
301
308
|
|
|
@@ -337,7 +344,8 @@ projects (all but archived ones) and the channels of every workspace on this
|
|
|
337
344
|
machine, grouped by workspace and running or not (a stopped one is started
|
|
338
345
|
on the way), read from the workspace registry under `~/.kraftwerk/workspaces`,
|
|
339
346
|
which every inspector keeps current with its roster, project and channel
|
|
340
|
-
lists
|
|
347
|
+
lists — and this workspace's contacts, places and events, by title, role,
|
|
348
|
+
organisation, address or date.
|
|
341
349
|
|
|
342
350
|
### Triggering from CI, cron, or webhooks
|
|
343
351
|
|
|
@@ -626,6 +634,84 @@ kraftwerk projects remove relaunch-netnode-ch
|
|
|
626
634
|
The registry of workspaces on this machine, which answered to
|
|
627
635
|
`kraftwerk projects` until 0.48, is `kraftwerk workspaces` now.
|
|
628
636
|
|
|
637
|
+
## Contacts, places and events
|
|
638
|
+
|
|
639
|
+
Who, where and when, kept by the workspace itself so people and agents can
|
|
640
|
+
point at them reliably. Three features with one shape — turn each on in
|
|
641
|
+
`kraftwerk.yml` (or its checkbox in settings):
|
|
642
|
+
|
|
643
|
+
```yaml
|
|
644
|
+
contacts: # people and organisations
|
|
645
|
+
places: # addresses and points of interest
|
|
646
|
+
events: # dated one-off entries
|
|
647
|
+
root: kraftwerk-data/events # default kraftwerk-data/<kind>; part of the workspace, synced like agents and knowledge
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
One Markdown file per entry: the typed fields in YAML frontmatter, free
|
|
651
|
+
notes in the body. Readable and editable by hand, by agents and by the UI;
|
|
652
|
+
versioned with the workspace, deleted into the trash.
|
|
653
|
+
|
|
654
|
+
```
|
|
655
|
+
kraftwerk-data/contacts/anna-meier.md # title, role, org, email, phone, place, url, tags, records + notes
|
|
656
|
+
kraftwerk-data/places/netnode-office.md # title, address, lat, lng, url, tags, records + notes
|
|
657
|
+
kraftwerk-data/events/2026-11-03-kickoff.md # title, start, end, place, contacts, project, url, tags, records + notes
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
```markdown
|
|
661
|
+
---
|
|
662
|
+
title: Kickoff
|
|
663
|
+
start: 2026-11-03T14:00 # ISO date or date-time; events are one-off (recurrence lives in the calendar entry)
|
|
664
|
+
end: 2026-11-03T16:00
|
|
665
|
+
place: netnode-office # a place's slug
|
|
666
|
+
contacts: [anna-meier] # contact slugs
|
|
667
|
+
project: relaunch # a project's slug
|
|
668
|
+
records:
|
|
669
|
+
- kind: calendar
|
|
670
|
+
url: https://calendar.google.com/event?eid=…
|
|
671
|
+
---
|
|
672
|
+
Agenda: scope, timeline, who does what.
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
**References.** Fields that point at other entries hold slugs, and anything
|
|
676
|
+
in the workspace is referenced in text as `kind:slug` — `contact:anna-meier`,
|
|
677
|
+
`place:netnode-office`, `event:2026-11-03-kickoff`, and likewise `project:`,
|
|
678
|
+
`agent:`, `workflow:`, `repo:`, `vibeable:`, `knowledge:`. The same resolver
|
|
679
|
+
serves a project's links, an event's place and contacts, a token in notes or
|
|
680
|
+
in a chat reply, the ⌘K palette and the context an agent starts with: a
|
|
681
|
+
target that does not exist is reported ("not found"), never an error, and
|
|
682
|
+
only these tokens match, so `https:` never does. Projects link to contacts,
|
|
683
|
+
places and events like to anything else (`kraftwerk projects link <slug>
|
|
684
|
+
contacts anna-meier`), and a project chat starts with them as one-liners.
|
|
685
|
+
|
|
686
|
+
**Systems of record.** As on a project: where the truth lives outside
|
|
687
|
+
kraftwerk — a contact's CRM record and newsletter subscription (`crm`,
|
|
688
|
+
`newsletter`), an event's calendar entry (`calendar`), a place on Google
|
|
689
|
+
Maps (`google-maps`). A place without a maps record gets a Google Maps link
|
|
690
|
+
derived from its coordinates or its address.
|
|
691
|
+
|
|
692
|
+
**Agents** get a "Contacts, places and events" block in every chat: what
|
|
693
|
+
exists, the next events, the token syntax and the CLI — they read and write
|
|
694
|
+
the files through it, never by hand:
|
|
695
|
+
|
|
696
|
+
```bash
|
|
697
|
+
kraftwerk contacts # list (--json); places, events the same
|
|
698
|
+
kraftwerk contacts create "Anna Meier" --org ACME --email anna@acme.ch --place netnode-office
|
|
699
|
+
kraftwerk contacts show anna-meier # fields, records, references with their state, notes
|
|
700
|
+
kraftwerk contacts set anna-meier --role CTO --email "" # "" clears a field; --notes-file replaces the notes
|
|
701
|
+
kraftwerk places create "NETNODE Office" --address "Zentralstrasse 1, 6003 Luzern" --lat 47.05 --lng 8.3
|
|
702
|
+
kraftwerk places show netnode-office # prints the Google Maps link
|
|
703
|
+
kraftwerk events create "Kickoff" --start 2026-11-03T14:00 --place netnode-office --contacts anna-meier --project relaunch
|
|
704
|
+
kraftwerk events --from 2026-11-01 --to 2026-11-30 # the month's events
|
|
705
|
+
kraftwerk ref contact:anna-meier # ✔ with its label, or ? not found (exit 1); --file notes.md checks a whole file
|
|
706
|
+
kraftwerk contacts remove anna-meier # to the trash
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
Over the API each kind is its own route family — `contacts.list|get|create|save|delete`,
|
|
710
|
+
`places.*`, `events.*` (`GET /api/events?from=&to=`) — and `refs.resolve`
|
|
711
|
+
resolves tokens or every reference found in a text. In the inspector each
|
|
712
|
+
kind is a page of the workspace (expert mode) with the list beside the open
|
|
713
|
+
entry; a project's context column shows its contacts, places and events.
|
|
714
|
+
|
|
629
715
|
## Files
|
|
630
716
|
|
|
631
717
|
Files are the material of the work, in any format: briefings, contracts,
|
package/dist/cli/daemon.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { DAEMON_PORT, daemonState, lastOpenRoots, setDaemonVersion, writeDaemonFile } from "../core/daemon.js";
|
|
3
3
|
import { tildify } from "../core/instances.js";
|
|
4
|
+
import { log } from "../core/log.js";
|
|
4
5
|
import { networkUrls } from "../server/bind.js";
|
|
5
6
|
import { startMachineCloud } from "../server/cloud.js";
|
|
6
7
|
import { startPushWatcher } from "../server/push.js";
|
|
@@ -44,31 +45,31 @@ export async function runDaemon(opts) {
|
|
|
44
45
|
await hub.open(root);
|
|
45
46
|
}
|
|
46
47
|
catch (err) {
|
|
47
|
-
|
|
48
|
+
log("warn", "workspace", `not reopened: ${tildify(root)} — ${err.message}`);
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
|
-
|
|
51
|
+
log("info", "daemon", `listening on http://localhost:${hub.port} (pid ${process.pid}, v${await getPkgVersion()})`);
|
|
51
52
|
for (const o of hub.list())
|
|
52
|
-
|
|
53
|
+
log("info", "workspace", `open: ${o.url} (${o.name})${o.aliasPort ? ` · also :${o.aliasPort}` : ""}`);
|
|
53
54
|
if (!hub.list().length)
|
|
54
|
-
|
|
55
|
+
log("info", "daemon", "no workspace open yet — `kraftwerk ui` in a workspace folder opens it here");
|
|
55
56
|
// The machine in the kraftwerk cloud: registered once, the open workspaces as its children (`kraftwerk cloud`, `kraftwerk login`).
|
|
56
57
|
const cloud = await startMachineCloud({ version: await getPkgVersion(), workspaces: () => hub.list(), owner: hub });
|
|
57
58
|
if (cloud.state === "connected") {
|
|
58
|
-
|
|
59
|
+
log("info", "cloud", `registered at ${cloud.url} ${cloud.account ? `(claimed by ${cloud.account})` : "(unclaimed — `kraftwerk login` claims this machine)"}`);
|
|
59
60
|
}
|
|
60
61
|
else if (cloud.state === "error") {
|
|
61
|
-
|
|
62
|
+
log("warn", "cloud", `${cloud.url} unreachable — ${cloud.error ?? "no answer"} (retrying; nothing else depends on it)`);
|
|
62
63
|
}
|
|
63
64
|
// The relay (`kraftwerk remote on`): devices reach this machine from anywhere, end to end encrypted.
|
|
64
|
-
const relay = await applyRelay(hub.port, await getPkgVersion(), (line) =>
|
|
65
|
+
const relay = await applyRelay(hub.port, await getPkgVersion(), (level, line) => log(level, "relay", line));
|
|
65
66
|
// What needs you, pushed to the phones that asked (through the relay, sealed per device).
|
|
66
67
|
startPushWatcher(hub, relayPush);
|
|
67
68
|
if (relay.state !== "off")
|
|
68
|
-
|
|
69
|
+
log("info", "relay", `reachable from anywhere through ${relay.url ?? ""} (\`kraftwerk remote pair\` pairs a device)`);
|
|
69
70
|
const lan = networkUrls(hub.port);
|
|
70
71
|
if (lan.length)
|
|
71
|
-
|
|
72
|
+
log("info", "daemon", `on your network: ${lan.join(", ")} (add /w/<slug>/; devices pair first)`);
|
|
72
73
|
}
|
|
73
74
|
export function registerDaemonCommand(program) {
|
|
74
75
|
const daemon = program
|
package/dist/cli/doctor.js
CHANGED
|
@@ -95,7 +95,7 @@ export async function runDoctor(cwd, opts = {}) {
|
|
|
95
95
|
else {
|
|
96
96
|
const cfg = project.config;
|
|
97
97
|
const keys = Object.keys(cfg);
|
|
98
|
-
// Blocks (git, repos, vibeables, projects, cloud) print as their keys, not [object Object].
|
|
98
|
+
// Blocks (git, repos, vibeables, projects, contacts, places, events, cloud) print as their keys, not [object Object].
|
|
99
99
|
const show = (v) => typeof v === "object" && v !== null
|
|
100
100
|
? Array.isArray(v)
|
|
101
101
|
? `[${v.length}]`
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { type EntityKindDef } from "../core/entities.js";
|
|
3
|
+
export declare function registerEntityCommands(program: Command, def: EntityKindDef<any>): void;
|
|
4
|
+
export declare const registerContactCommands: (program: Command) => void;
|
|
5
|
+
export declare const registerPlaceCommands: (program: Command) => void;
|
|
6
|
+
export declare const registerEventCommands: (program: Command) => void;
|
|
7
|
+
/** `kraftwerk ref` — resolve one reference, or every reference in a file. */
|
|
8
|
+
export declare function registerRefCommand(program: Command): void;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import Table from "cli-table3";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { CONTACTS, createEntity, deleteEntity, EVENTS, getEntity, listEntities, PLACES, saveEntity } from "../core/entities.js";
|
|
5
|
+
import { recordLine } from "../core/records.js";
|
|
6
|
+
import { findRefs, resolveRef, resolveTokens } from "../core/refs.js";
|
|
7
|
+
import { initContext as prepare } from "./routines.js";
|
|
8
|
+
import { fmtAgo } from "./workspaces.js";
|
|
9
|
+
/**
|
|
10
|
+
* `kraftwerk contacts|places|events` — the workspace's records of who,
|
|
11
|
+
* where and when, one Markdown file each (core/entities.ts). One command
|
|
12
|
+
* group per kind, built from the kind's definition so the options are its
|
|
13
|
+
* fields; the same module the inspector uses, so an entry created here
|
|
14
|
+
* shows up in the UI at once, and an agent uses these to keep them current:
|
|
15
|
+
*
|
|
16
|
+
* kraftwerk contacts list (--json)
|
|
17
|
+
* kraftwerk contacts show <slug> fields, records, links (found / missing), notes
|
|
18
|
+
* kraftwerk contacts create <title> [--org ..] [--email ..] [--place <slug>] [--tags a,b] [--notes ..]
|
|
19
|
+
* kraftwerk contacts set <slug> [--role ..] [--notes-file <md>] change fields; "" clears one
|
|
20
|
+
* kraftwerk contacts remove <slug> move the file to the trash
|
|
21
|
+
* kraftwerk places ... --address, --lat, --lng, --url; show prints the Google Maps link
|
|
22
|
+
* kraftwerk events [--from <date>] [--to <date>] --start (required), --end, --place, --contacts a,b, --project
|
|
23
|
+
* kraftwerk ref <kind:slug> | --file <md> resolve references (exit 1 when a target is missing)
|
|
24
|
+
*/
|
|
25
|
+
const die = (msg) => {
|
|
26
|
+
console.error(chalk.red(msg));
|
|
27
|
+
process.exit(1);
|
|
28
|
+
};
|
|
29
|
+
const mark = (found) => (found ? chalk.green("✔") : chalk.yellow("?"));
|
|
30
|
+
function printDetail(def, e) {
|
|
31
|
+
const rec = e;
|
|
32
|
+
console.log(`${chalk.bold(e.title)} ${chalk.dim(`(${def.singular}:${e.slug})`)}`);
|
|
33
|
+
console.log(chalk.dim(e.path));
|
|
34
|
+
if (e.configError)
|
|
35
|
+
console.log(chalk.red(`frontmatter: ${e.configError}`));
|
|
36
|
+
for (const f of def.fields) {
|
|
37
|
+
const v = rec[f.name];
|
|
38
|
+
if (v === undefined || (Array.isArray(v) && v.length === 0))
|
|
39
|
+
continue;
|
|
40
|
+
const links = e.links[f.name];
|
|
41
|
+
const shown = links ? links.map((l) => `${mark(l.found)} ${l.slug}${l.label ? chalk.dim(` ${l.label}`) : l.found ? "" : chalk.dim(" (not found)")}`).join(", ") : Array.isArray(v) ? v.join(", ") : String(v);
|
|
42
|
+
console.log(`${chalk.dim(f.label.padEnd(12))} ${shown}`);
|
|
43
|
+
}
|
|
44
|
+
if (def.kind === "places" && rec.mapsUrl)
|
|
45
|
+
console.log(`${chalk.dim("map".padEnd(12))} ${rec.mapsUrl}`);
|
|
46
|
+
if (e.tags.length)
|
|
47
|
+
console.log(`${chalk.dim("tags".padEnd(12))} ${e.tags.join(", ")}`);
|
|
48
|
+
for (const [k, v] of Object.entries(e.extra ?? {}))
|
|
49
|
+
console.log(`${chalk.dim(k.padEnd(12))} ${typeof v === "string" ? v : JSON.stringify(v)}`);
|
|
50
|
+
if (e.records.length) {
|
|
51
|
+
console.log(`\n${chalk.bold("systems of record")}`);
|
|
52
|
+
for (const r of e.records)
|
|
53
|
+
console.log(recordLine(r));
|
|
54
|
+
}
|
|
55
|
+
if (e.notes.trim())
|
|
56
|
+
console.log(`\n${chalk.bold("notes")}\n${e.notes.trim()}`);
|
|
57
|
+
}
|
|
58
|
+
/** The fields given on the command line, as the store takes them (comma lists split there; "" clears). */
|
|
59
|
+
function fieldsOf(def, opts) {
|
|
60
|
+
const out = {};
|
|
61
|
+
for (const f of def.fields)
|
|
62
|
+
if (opts[f.name] !== undefined)
|
|
63
|
+
out[f.name] = opts[f.name];
|
|
64
|
+
if (opts.tags !== undefined)
|
|
65
|
+
out.tags = opts.tags;
|
|
66
|
+
if (opts.title !== undefined)
|
|
67
|
+
out.title = opts.title;
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
export function registerEntityCommands(program, def) {
|
|
71
|
+
const what = { contacts: "people and organisations", places: "addresses and points of interest", events: "dated one-off entries" }[def.kind];
|
|
72
|
+
const group = program.command(def.kind).description(`${def.kind[0].toUpperCase()}${def.kind.slice(1)}: ${what}, one Markdown file each — list, show, create, set, remove`);
|
|
73
|
+
const withFields = (cmd) => {
|
|
74
|
+
for (const f of def.fields)
|
|
75
|
+
cmd.option(`--${f.name} <value>`, `${f.hint}${f.required ? " (required)" : ""}`);
|
|
76
|
+
cmd.option("--tags <list>", "Tags, comma-separated");
|
|
77
|
+
return cmd;
|
|
78
|
+
};
|
|
79
|
+
const list = group
|
|
80
|
+
.command("list", { isDefault: true })
|
|
81
|
+
.description(`List the ${def.kind} under the ${def.kind} root`)
|
|
82
|
+
.option("--json", "Machine-readable output");
|
|
83
|
+
if (def.span)
|
|
84
|
+
list.option("--from <date>", "Only entries still running on or after this date (ISO)").option("--to <date>", "Only entries that have started by this date (ISO)");
|
|
85
|
+
list.action(async (opts) => {
|
|
86
|
+
await prepare();
|
|
87
|
+
const view = await listEntities(def, { from: opts.from, to: opts.to });
|
|
88
|
+
if (opts.json) {
|
|
89
|
+
console.log(JSON.stringify(view, null, 2));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!view.enabled)
|
|
93
|
+
die(view.error ?? `${def.kind} are off`);
|
|
94
|
+
if (view.items.length === 0) {
|
|
95
|
+
console.log(chalk.dim(`No ${def.kind} under ${view.root} yet — \`kraftwerk ${def.kind} create "<title>"\` adds one.`));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const table = new Table({ head: [def.singular, "details", "changed"].map((h) => chalk.bold(h)), wordWrap: true, colWidths: [30, 56, 14] });
|
|
99
|
+
for (const e of view.items) {
|
|
100
|
+
table.push([`${chalk.cyan(e.title)}\n${chalk.dim(e.slug)}`, e.configError ? chalk.red(e.configError) : def.detail(e) || chalk.dim("—"), chalk.dim(fmtAgo(e.updatedAt))]);
|
|
101
|
+
}
|
|
102
|
+
console.log(table.toString());
|
|
103
|
+
console.log(chalk.dim(view.root ?? ""));
|
|
104
|
+
});
|
|
105
|
+
group
|
|
106
|
+
.command("show")
|
|
107
|
+
.description(`Show one ${def.singular}: fields, records, links with their state, notes`)
|
|
108
|
+
.argument("<slug>", "File name under the root, without .md")
|
|
109
|
+
.option("--json", "Machine-readable output")
|
|
110
|
+
.action(async (slug, opts) => {
|
|
111
|
+
await prepare();
|
|
112
|
+
try {
|
|
113
|
+
const e = await getEntity(def, slug);
|
|
114
|
+
if (!e)
|
|
115
|
+
die(`no ${def.singular} "${slug}"`);
|
|
116
|
+
if (opts.json)
|
|
117
|
+
console.log(JSON.stringify(e, null, 2));
|
|
118
|
+
else
|
|
119
|
+
printDetail(def, e);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
die(err.message);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
withFields(group
|
|
126
|
+
.command("create")
|
|
127
|
+
.description(`Create a ${def.singular} file from its title and fields`)
|
|
128
|
+
.argument("<title>", "Display title; the file name derives from it unless --slug is given")
|
|
129
|
+
.option("--slug <slug>", "File name under the root (lowercase, digits, dashes)")
|
|
130
|
+
.option("--notes <text>", "The Markdown body")
|
|
131
|
+
.option("--notes-file <path>", "Read the body from a file")
|
|
132
|
+
.option("--json", `Print the new ${def.singular} as JSON`)).action(async (title, opts) => {
|
|
133
|
+
await prepare();
|
|
134
|
+
try {
|
|
135
|
+
const notes = opts.notesFile ? await readFile(opts.notesFile, "utf8") : opts.notes;
|
|
136
|
+
const e = await createEntity(def, { ...fieldsOf(def, opts), title, slug: opts.slug, ...(notes !== undefined ? { notes } : {}) });
|
|
137
|
+
if (opts.json)
|
|
138
|
+
console.log(JSON.stringify(e, null, 2));
|
|
139
|
+
else {
|
|
140
|
+
const missing = Object.values(e.links).flat().filter((l) => !l.found);
|
|
141
|
+
console.log(`${chalk.green("✔")} ${chalk.cyan(`${def.singular}:${e.slug}`)} → ${e.path}${missing.length ? chalk.yellow(` (not found in this workspace yet: ${missing.map((l) => l.slug).join(", ")})`) : ""}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
die(err.message);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
withFields(group
|
|
149
|
+
.command("set")
|
|
150
|
+
.description(`Change a ${def.singular}'s fields ("" clears one) or notes`)
|
|
151
|
+
.argument("<slug>", "File name under the root")
|
|
152
|
+
.option("--title <text>", "New title")
|
|
153
|
+
.option("--notes <text>", "Replace the Markdown body")
|
|
154
|
+
.option("--notes-file <path>", "Replace the body with a file's content")
|
|
155
|
+
.option("--json", `Print the ${def.singular} as JSON`)).action(async (slug, opts) => {
|
|
156
|
+
await prepare();
|
|
157
|
+
try {
|
|
158
|
+
const notes = opts.notesFile ? await readFile(opts.notesFile, "utf8") : opts.notes;
|
|
159
|
+
const input = { ...fieldsOf(def, opts), ...(notes !== undefined ? { notes } : {}) };
|
|
160
|
+
if (Object.keys(input).length === 0)
|
|
161
|
+
die(`nothing to set — pass --${def.fields.map((f) => f.name).join(", --")}, --tags, --title or --notes`);
|
|
162
|
+
const e = await saveEntity(def, slug, input);
|
|
163
|
+
if (opts.json)
|
|
164
|
+
console.log(JSON.stringify(e, null, 2));
|
|
165
|
+
else
|
|
166
|
+
console.log(`${chalk.green("✔")} ${chalk.cyan(`${def.singular}:${e.slug}`)} — ${def.describe(e)}`);
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
die(err.message);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
group
|
|
173
|
+
.command("remove")
|
|
174
|
+
.description(`Move a ${def.singular} file to the trash (\`kraftwerk trash\` puts it back)`)
|
|
175
|
+
.argument("<slug>", "File name under the root")
|
|
176
|
+
.action(async (slug) => {
|
|
177
|
+
await prepare();
|
|
178
|
+
try {
|
|
179
|
+
await deleteEntity(def, slug);
|
|
180
|
+
console.log(`${chalk.green("✔")} moved ${chalk.cyan(`${def.singular}:${slug}`)} to the trash`);
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
die(err.message);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
export const registerContactCommands = (program) => registerEntityCommands(program, CONTACTS);
|
|
188
|
+
export const registerPlaceCommands = (program) => registerEntityCommands(program, PLACES);
|
|
189
|
+
export const registerEventCommands = (program) => registerEntityCommands(program, EVENTS);
|
|
190
|
+
const refLine = (r) => `${mark(r.found)} ${r.token}${r.label ? chalk.dim(` ${r.label}`) : r.found ? "" : chalk.dim(" (not found)")}`;
|
|
191
|
+
/** `kraftwerk ref` — resolve one reference, or every reference in a file. */
|
|
192
|
+
export function registerRefCommand(program) {
|
|
193
|
+
program
|
|
194
|
+
.command("ref")
|
|
195
|
+
.description("Resolve a reference (contact:anna-meier, place:…, event:…, project:…, agent:…, workflow:…, repo:…, vibeable:…, knowledge:…) or every one in a file")
|
|
196
|
+
.argument("[ref]", "The reference, <kind>:<slug>")
|
|
197
|
+
.option("--file <path>", "Resolve every reference found in this file")
|
|
198
|
+
.option("--json", "Machine-readable output")
|
|
199
|
+
.action(async (ref, opts) => {
|
|
200
|
+
await prepare();
|
|
201
|
+
let refs;
|
|
202
|
+
if (opts.file)
|
|
203
|
+
refs = await resolveTokens(findRefs(await readFile(opts.file, "utf8").catch((e) => die(e.message))));
|
|
204
|
+
else if (ref) {
|
|
205
|
+
const r = await resolveRef(ref);
|
|
206
|
+
if (!r)
|
|
207
|
+
die(`not a reference: "${ref}" (expected <kind>:<slug>, e.g. contact:anna-meier)`);
|
|
208
|
+
refs = [r];
|
|
209
|
+
}
|
|
210
|
+
else
|
|
211
|
+
die("pass a reference like contact:anna-meier, or --file <path>");
|
|
212
|
+
if (opts.json)
|
|
213
|
+
console.log(JSON.stringify({ refs: refs }, null, 2));
|
|
214
|
+
else if (refs.length === 0)
|
|
215
|
+
console.log(chalk.dim("no references found"));
|
|
216
|
+
else
|
|
217
|
+
for (const r of refs)
|
|
218
|
+
console.log(refLine(r));
|
|
219
|
+
if (refs.some((r) => !r.found))
|
|
220
|
+
process.exit(1);
|
|
221
|
+
});
|
|
222
|
+
}
|
package/dist/cli/init.js
CHANGED
|
@@ -31,6 +31,9 @@ skills: ${DATA_DIR}/skills # workspace skills (shared instruction packag
|
|
|
31
31
|
# root: ${DATA_DIR}/repos # clones land here (git-ignored); a bare \`repos:\` uses repos/ instead
|
|
32
32
|
# vibeables: # small apps built live in a chat with a preview pane (uncomment to enable; one folder per app under ${DATA_DIR}/vibeables, versioned with the workspace)
|
|
33
33
|
# projects: # goal-scoped folders — brief, systems of record, links to knowledge/vibeables/repos/workflows/agents — that chats work in (uncomment to enable; one folder per project under ${DATA_DIR}/projects)
|
|
34
|
+
# contacts: # people and organisations, one Markdown file each under ${DATA_DIR}/contacts — referenced as contact:<slug> (uncomment to enable)
|
|
35
|
+
# places: # addresses and points of interest, one Markdown file each under ${DATA_DIR}/places — referenced as place:<slug> (uncomment to enable)
|
|
36
|
+
# events: # dated one-off entries with place, contacts and project, one Markdown file each under ${DATA_DIR}/events — referenced as event:<slug> (uncomment to enable)
|
|
34
37
|
# cloud: # the daemon registers this MACHINE with the kraftwerk cloud and lists this workspace (\`kraftwerk login\` claims the machine)
|
|
35
38
|
# enabled: false # uncomment both lines to leave this workspace out (\`kraftwerk cloud off\` turns the cloud off for the whole machine)
|
|
36
39
|
`;
|
|
@@ -136,11 +139,12 @@ export async function runInit(cwd) {
|
|
|
136
139
|
created.push(bundleRel);
|
|
137
140
|
}
|
|
138
141
|
// .gitignore: the output dir, the repos root (clones must never become
|
|
139
|
-
// gitlinks of the workspace repo)
|
|
142
|
+
// gitlinks of the workspace repo), the temp root (session scratch, which
|
|
143
|
+
// also carries a .gitignore of its own) and .env (secrets). A missing file gets
|
|
140
144
|
// all in one write; an existing one only the entries it lacks.
|
|
141
145
|
const gitignorePath = path.join(cwd, ".gitignore");
|
|
142
146
|
const gitignore = (await readFile(gitignorePath, "utf8").catch(() => null)) ?? null;
|
|
143
|
-
const entries = [`${DATA_DIR}/output/`, `${DATA_DIR}/repos/`, DOTENV_FILE];
|
|
147
|
+
const entries = [`${DATA_DIR}/output/`, `${DATA_DIR}/repos/`, `${DATA_DIR}/temp/`, DOTENV_FILE];
|
|
144
148
|
if (gitignore === null) {
|
|
145
149
|
await writeFile(gitignorePath, entries.map((e) => `${e}\n`).join(""));
|
|
146
150
|
created.push(".gitignore");
|
package/dist/cli/kraftwerk.js
CHANGED
|
@@ -13,10 +13,12 @@ import { runDoctor } from "./doctor.js";
|
|
|
13
13
|
import { runInit } from "./init.js";
|
|
14
14
|
import { registerKnowledgeCommands } from "./knowledge.js";
|
|
15
15
|
import { registerProjectCommands } from "./projects.js";
|
|
16
|
+
import { registerContactCommands, registerEventCommands, registerPlaceCommands, registerRefCommand } from "./entities.js";
|
|
16
17
|
import { registerWorkspaceCommands } from "./workspaces.js";
|
|
17
18
|
import { registerRepoCommands } from "./repos.js";
|
|
18
19
|
import { registerVibeableCommands } from "./vibeables.js";
|
|
19
20
|
import { registerTrashCommands } from "./trash.js";
|
|
21
|
+
import { registerTempCommands } from "./temp.js";
|
|
20
22
|
import { registerDevicesCommands } from "./devices.js";
|
|
21
23
|
import { registerDaemonCommand } from "./daemon.js";
|
|
22
24
|
import { registerRemoteCommand } from "./remote.js";
|
|
@@ -42,10 +44,13 @@ import { runUi } from "./ui.js";
|
|
|
42
44
|
* kraftwerk ui start the inspector web UI (localhost:1981)
|
|
43
45
|
* kraftwerk workspaces ... known workspaces on this machine (list/start/stop/forget)
|
|
44
46
|
* kraftwerk projects ... goal-scoped project folders (list/create/show/link/log/remove)
|
|
47
|
+
* kraftwerk contacts|places|events who, where and when: one Markdown file each (list/show/create/set/remove)
|
|
48
|
+
* kraftwerk ref <kind:slug> resolve a reference (contact:anna-meier) or every one in a file
|
|
45
49
|
* kraftwerk vibeables ... small apps built live in a chat (list/create/remove)
|
|
46
50
|
* kraftwerk repos ... repositories the agents work on (list/add/update/remove)
|
|
47
51
|
* kraftwerk journal <agent> [entry] an agent's memory across sessions: print it, add a line
|
|
48
52
|
* kraftwerk trash ... what was deleted: list, restore, purge, empty
|
|
53
|
+
* kraftwerk temp [clean] the sessions' scratch folders (TMPDIR of every agent): list, clean
|
|
49
54
|
* kraftwerk devices ... paired devices: list, pair one, revoke one
|
|
50
55
|
* kraftwerk daemon the machine's kraftwerk: every open workspace at <slug>.localhost
|
|
51
56
|
* kraftwerk cloud [on|off|status] this machine in the kraftwerk cloud (the daemon registers it)
|
|
@@ -299,9 +304,14 @@ registerKnowledgeCommands(program);
|
|
|
299
304
|
registerRoutineCommands(program);
|
|
300
305
|
registerWorkspaceCommands(program);
|
|
301
306
|
registerProjectCommands(program);
|
|
307
|
+
registerContactCommands(program);
|
|
308
|
+
registerPlaceCommands(program);
|
|
309
|
+
registerEventCommands(program);
|
|
310
|
+
registerRefCommand(program);
|
|
302
311
|
registerRepoCommands(program);
|
|
303
312
|
registerVibeableCommands(program);
|
|
304
313
|
registerTrashCommands(program);
|
|
314
|
+
registerTempCommands(program);
|
|
305
315
|
registerDevicesCommands(program);
|
|
306
316
|
registerDaemonCommand(program);
|
|
307
317
|
registerRemoteCommand(program);
|
package/dist/cli/temp.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import Table from "cli-table3";
|
|
3
|
+
import { cleanTemp, listTemp, SWEEP_AFTER_MS } from "../core/temp.js";
|
|
4
|
+
import { fmtAgo } from "./workspaces.js";
|
|
5
|
+
import { initContext as prepare } from "./routines.js";
|
|
6
|
+
/**
|
|
7
|
+
* `kraftwerk temp` — the sessions' scratch folders under kraftwerk-data/temp
|
|
8
|
+
* (one per chat session; TMPDIR of the agent process). Nothing in there is
|
|
9
|
+
* a deliverable: a clean deletes for good, no trash.
|
|
10
|
+
*
|
|
11
|
+
* kraftwerk temp list: session, files, size, last change
|
|
12
|
+
* kraftwerk temp clean remove folders idle for a week
|
|
13
|
+
* kraftwerk temp clean --older-than <d> … idle for <d> days
|
|
14
|
+
* kraftwerk temp clean --all remove every folder
|
|
15
|
+
*/
|
|
16
|
+
const fmtBytes = (n) => n < 1024 ? `${n} B` : n < 1024 * 1024 ? `${(n / 1024).toFixed(1)} KB` : n < 1024 ** 3 ? `${(n / 1024 ** 2).toFixed(1)} MB` : `${(n / 1024 ** 3).toFixed(2)} GB`;
|
|
17
|
+
export function registerTempCommands(program) {
|
|
18
|
+
const temp = program.command("temp").description("The sessions' scratch folders: list, clean");
|
|
19
|
+
temp
|
|
20
|
+
.command("list", { isDefault: true })
|
|
21
|
+
.description("List the scratch folders, newest first")
|
|
22
|
+
.option("--json", "Machine-readable output")
|
|
23
|
+
.action(async (opts) => {
|
|
24
|
+
await prepare();
|
|
25
|
+
const view = await listTemp();
|
|
26
|
+
if (opts.json) {
|
|
27
|
+
console.log(JSON.stringify(view, null, 2));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (view.entries.length === 0) {
|
|
31
|
+
console.log(chalk.dim("No scratch folders."));
|
|
32
|
+
console.log(chalk.dim(view.root));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const table = new Table({ head: ["session", "files", "size", "last change"].map((h) => chalk.bold(h)) });
|
|
36
|
+
for (const e of view.entries)
|
|
37
|
+
table.push([chalk.cyan(e.id), String(e.files), fmtBytes(e.bytes), chalk.dim(fmtAgo(e.modifiedAt))]);
|
|
38
|
+
console.log(table.toString());
|
|
39
|
+
console.log(chalk.dim(`${view.root} — ${fmtBytes(view.bytes)} in ${view.entries.length} folder${view.entries.length === 1 ? "" : "s"}`));
|
|
40
|
+
});
|
|
41
|
+
temp
|
|
42
|
+
.command("clean")
|
|
43
|
+
.description(`Remove scratch folders idle for ${SWEEP_AFTER_MS / 86_400_000} days (for good — nothing there is a deliverable)`)
|
|
44
|
+
.option("--older-than <days>", "Remove folders idle for this many days instead")
|
|
45
|
+
.option("--all", "Remove every folder, whatever its age")
|
|
46
|
+
.option("--json", "Machine-readable output")
|
|
47
|
+
.action(async (opts) => {
|
|
48
|
+
await prepare();
|
|
49
|
+
const days = opts.olderThan === undefined ? undefined : Number(opts.olderThan);
|
|
50
|
+
if (days !== undefined && (!Number.isFinite(days) || days < 0)) {
|
|
51
|
+
console.error(chalk.red(`--older-than wants a number of days, not "${opts.olderThan}"`));
|
|
52
|
+
process.exit(2);
|
|
53
|
+
}
|
|
54
|
+
const gone = await cleanTemp({ all: opts.all, ...(days === undefined ? {} : { olderThanMs: days * 86_400_000 }) });
|
|
55
|
+
const bytes = gone.reduce((n, e) => n + e.bytes, 0);
|
|
56
|
+
if (opts.json) {
|
|
57
|
+
console.log(JSON.stringify({ removed: gone.map((e) => e.id), bytes }, null, 2));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
console.log(`${chalk.green("✔")} removed ${gone.length} folder${gone.length === 1 ? "" : "s"} (${fmtBytes(bytes)})`);
|
|
61
|
+
});
|
|
62
|
+
}
|
package/dist/cli/ui.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
+
import { log } from "../core/log.js";
|
|
2
3
|
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -144,7 +145,7 @@ export async function supervise(argv, what) {
|
|
|
144
145
|
if (ran > 120_000)
|
|
145
146
|
backoff = 1000;
|
|
146
147
|
const how = result.signal ? `signal ${result.signal}` : `exit ${result.code}`;
|
|
147
|
-
|
|
148
|
+
log("warn", "supervise", `${what} stopped unexpectedly (${how}) — starting it again in ${backoff / 1000}s`);
|
|
148
149
|
await new Promise((r) => setTimeout(r, backoff));
|
|
149
150
|
backoff = Math.min(backoff * 2, 60_000);
|
|
150
151
|
continue;
|
package/dist/client/routes.d.ts
CHANGED
|
@@ -50,6 +50,11 @@ export declare const ROUTES: {
|
|
|
50
50
|
readonly "cloud.status": readonly ["GET", "/api/cloud"];
|
|
51
51
|
readonly "cloud.sync": readonly ["POST", "/api/cloud/sync"];
|
|
52
52
|
readonly "cloud.unlink": readonly ["POST", "/api/cloud/unlink"];
|
|
53
|
+
readonly "contacts.create": readonly ["POST", "/api/contacts"];
|
|
54
|
+
readonly "contacts.delete": readonly ["DELETE", "/api/contacts/:slug"];
|
|
55
|
+
readonly "contacts.get": readonly ["GET", "/api/contacts/:slug"];
|
|
56
|
+
readonly "contacts.list": readonly ["GET", "/api/contacts"];
|
|
57
|
+
readonly "contacts.save": readonly ["PUT", "/api/contacts/:slug"];
|
|
53
58
|
readonly "devices.code": readonly ["POST", "/api/devices/code"];
|
|
54
59
|
readonly "devices.dropPush": readonly ["DELETE", "/api/devices/push"];
|
|
55
60
|
readonly "devices.list": readonly ["GET", "/api/devices"];
|
|
@@ -59,6 +64,11 @@ export declare const ROUTES: {
|
|
|
59
64
|
readonly "devices.self": readonly ["GET", "/api/devices/self"];
|
|
60
65
|
readonly "devices.setPush": readonly ["POST", "/api/devices/push"];
|
|
61
66
|
readonly "devices.setRelay": readonly ["POST", "/api/devices/relay"];
|
|
67
|
+
readonly "events.create": readonly ["POST", "/api/events"];
|
|
68
|
+
readonly "events.delete": readonly ["DELETE", "/api/events/:slug"];
|
|
69
|
+
readonly "events.get": readonly ["GET", "/api/events/:slug"];
|
|
70
|
+
readonly "events.list": readonly ["GET", "/api/events"];
|
|
71
|
+
readonly "events.save": readonly ["PUT", "/api/events/:slug"];
|
|
62
72
|
readonly "files.delete": readonly ["DELETE", "/api/files"];
|
|
63
73
|
readonly "files.list": readonly ["GET", "/api/files/list"];
|
|
64
74
|
readonly "files.mkdir": readonly ["POST", "/api/files/folder"];
|
|
@@ -89,6 +99,11 @@ export declare const ROUTES: {
|
|
|
89
99
|
readonly "notifications.diagnose": readonly ["POST", "/api/notifications/:id/diagnose"];
|
|
90
100
|
readonly "notifications.list": readonly ["GET", "/api/notifications"];
|
|
91
101
|
readonly "notifications.read": readonly ["POST", "/api/notifications/read"];
|
|
102
|
+
readonly "places.create": readonly ["POST", "/api/places"];
|
|
103
|
+
readonly "places.delete": readonly ["DELETE", "/api/places/:slug"];
|
|
104
|
+
readonly "places.get": readonly ["GET", "/api/places/:slug"];
|
|
105
|
+
readonly "places.list": readonly ["GET", "/api/places"];
|
|
106
|
+
readonly "places.save": readonly ["PUT", "/api/places/:slug"];
|
|
92
107
|
readonly "projects.create": readonly ["POST", "/api/projects"];
|
|
93
108
|
readonly "projects.delete": readonly ["DELETE", "/api/projects/:slug"];
|
|
94
109
|
readonly "projects.get": readonly ["GET", "/api/projects/:slug"];
|
|
@@ -98,6 +113,7 @@ export declare const ROUTES: {
|
|
|
98
113
|
readonly "projects.save": readonly ["PUT", "/api/projects/:slug"];
|
|
99
114
|
readonly "projects.saveLayout": readonly ["PUT", "/api/projects"];
|
|
100
115
|
readonly "protocol.get": readonly ["GET", "/api/protocol"];
|
|
116
|
+
readonly "refs.resolve": readonly ["POST", "/api/refs/resolve"];
|
|
101
117
|
readonly "repos.add": readonly ["POST", "/api/repos"];
|
|
102
118
|
readonly "repos.commit": readonly ["GET", "/api/repos/:slug/commits/:hash"];
|
|
103
119
|
readonly "repos.diff": readonly ["GET", "/api/repos/:slug/diff"];
|
|
@@ -120,6 +136,8 @@ export declare const ROUTES: {
|
|
|
120
136
|
readonly "settings.save": readonly ["PUT", "/api/settings"];
|
|
121
137
|
readonly "skills.get": readonly ["GET", "/api/skills/:name"];
|
|
122
138
|
readonly "skills.list": readonly ["GET", "/api/skills"];
|
|
139
|
+
readonly "temp.clean": readonly ["DELETE", "/api/temp"];
|
|
140
|
+
readonly "temp.list": readonly ["GET", "/api/temp"];
|
|
123
141
|
readonly "trash.empty": readonly ["DELETE", "/api/trash"];
|
|
124
142
|
readonly "trash.list": readonly ["GET", "/api/trash"];
|
|
125
143
|
readonly "trash.purge": readonly ["POST", "/api/trash/purge"];
|