@davidbalzan/groundwork-console 0.1.4 → 0.1.6
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 +3 -3
- package/bin/groundwork-console.mjs +19 -2
- package/dist/bus.js +11 -4
- package/dist/runs.js +34 -2
- package/dist/server.js +43 -1
- package/dist/tmux.js +32 -0
- package/dist/uploads.js +31 -1
- package/package.json +1 -1
- package/public/assets/{abnfDiagram-N423BO3Z-DjcI5EtN.js → abnfDiagram-N423BO3Z-CdHjY-Rl.js} +1 -1
- package/public/assets/{arc-CLreMRTi.js → arc-BrWBiZCE.js} +1 -1
- package/public/assets/architecture-TIHT7OUA-nRAC6cKL.js +1 -0
- package/public/assets/{architectureDiagram-T3A2C74G-GVsKASaE.js → architectureDiagram-T3A2C74G-BlL-ALnB.js} +1 -1
- package/public/assets/{blockDiagram-VBNYF7ZC-kUIhKmE4.js → blockDiagram-VBNYF7ZC-DZyulKP3.js} +1 -1
- package/public/assets/{c4Diagram-5PPSVZJV-BQFI1po0.js → c4Diagram-5PPSVZJV-BMMLwghr.js} +1 -1
- package/public/assets/channel-CmRhJjbj.js +1 -0
- package/public/assets/{chunk-2GRJ4B5K-CAC9E6Lc.js → chunk-2GRJ4B5K-BzJtMGzP.js} +1 -1
- package/public/assets/{chunk-4I5QYGJK-Do801ucH.js → chunk-4I5QYGJK-DwFXVxXp.js} +1 -1
- package/public/assets/{chunk-5RXB4S5H-DgGb8Ei0.js → chunk-5RXB4S5H-BohnhuTy.js} +1 -1
- package/public/assets/{chunk-6Q2QTUOP-N3v574-9.js → chunk-6Q2QTUOP-BXxDc17T.js} +1 -1
- package/public/assets/{chunk-7Z6QIM7H-BGVpdvGU.js → chunk-7Z6QIM7H-k5qXgkB4.js} +1 -1
- package/public/assets/{chunk-GF5L2VYU-CyQ84gnX.js → chunk-GF5L2VYU-Cl8z_Pcd.js} +1 -1
- package/public/assets/{chunk-I66GZJ75-Bazokq-r.js → chunk-I66GZJ75-CCxcXl9W.js} +1 -1
- package/public/assets/{chunk-JQJVKLGR-BXG1sCkI.js → chunk-JQJVKLGR-Dah9RJ-p.js} +1 -1
- package/public/assets/{chunk-KBJHAD2P-Bi0agcVN.js → chunk-KBJHAD2P-B1kM7HCm.js} +1 -1
- package/public/assets/{chunk-NSK5VX7P-CRNBZms6.js → chunk-NSK5VX7P-Ddw2Pneq.js} +1 -1
- package/public/assets/{chunk-QR6OTTB3-BVNnO0nC.js → chunk-QR6OTTB3-SR1VT_gf.js} +1 -1
- package/public/assets/{chunk-RYQCIY6F-BDA4j6A-.js → chunk-RYQCIY6F-CsMzInBn.js} +1 -1
- package/public/assets/{chunk-UBXNYLIW-DGeBgwe-.js → chunk-UBXNYLIW-CysKciL_.js} +1 -1
- package/public/assets/{chunk-W5SLKNZC-xcy3l2eH.js → chunk-W5SLKNZC-CueNCRji.js} +1 -1
- package/public/assets/{chunk-WRU74C26-CIO3U59-.js → chunk-WRU74C26-CVxduXc3.js} +1 -1
- package/public/assets/{chunk-XXDRQBXY-lsNR2odJ.js → chunk-XXDRQBXY-DOdFQa-t.js} +1 -1
- package/public/assets/classDiagram-JCYQIIEL-BhHtVWEo.js +1 -0
- package/public/assets/classDiagram-v2-OCEON4UE-BhHtVWEo.js +1 -0
- package/public/assets/{cose-bilkent-JH36ORCC-Dgq33rVP.js → cose-bilkent-JH36ORCC-B2Z9nj2r.js} +1 -1
- package/public/assets/{cynefin-VYW2F7L2-BHP3DZqn.js → cynefin-VYW2F7L2-CmV_z2bb.js} +1 -1
- package/public/assets/{cynefinDiagram-MW4NZA55-BmxpLFSH.js → cynefinDiagram-MW4NZA55-zeWgiEbQ.js} +1 -1
- package/public/assets/{dagre-VZM6K2ZE-3sD0lAzD.js → dagre-VZM6K2ZE-CaMVheN4.js} +1 -1
- package/public/assets/{diagram-7IWD3JNH-CyaD0_jh.js → diagram-7IWD3JNH-BTJeDC0S.js} +1 -1
- package/public/assets/{diagram-B4RE2ZJO-td2N4qE1.js → diagram-B4RE2ZJO-BUtRYMUD.js} +1 -1
- package/public/assets/{diagram-LBJQPF4R-D_wWDJ4F.js → diagram-LBJQPF4R-DM--EdGk.js} +1 -1
- package/public/assets/{diagram-Q27KOJAE-DKPs8Pt1.js → diagram-Q27KOJAE-Dcp9D1k4.js} +1 -1
- package/public/assets/{diagram-UB23O5K3-DMH09z4H.js → diagram-UB23O5K3-DzLpzKu2.js} +1 -1
- package/public/assets/{dist-BFMCu1a5.js → dist-DFkPtM9v.js} +1 -1
- package/public/assets/{ebnfDiagram-BXEA7PRR-Cl2AEyY9.js → ebnfDiagram-BXEA7PRR-C2lFoa24.js} +1 -1
- package/public/assets/{erDiagram-JOGREHBK-CyAJ5sjB.js → erDiagram-JOGREHBK-GvedjkFY.js} +1 -1
- package/public/assets/eventmodeling-45OFAUF4-CVrSQ2LC.js +1 -0
- package/public/assets/flowDiagram-UKHOOZJN-BWxvJEZS.js +1 -0
- package/public/assets/{ganttDiagram-PKOTCBZU-YM9FA8H8.js → ganttDiagram-PKOTCBZU-NzyabXr_.js} +1 -1
- package/public/assets/{gitGraph-TEB2WS4Q-ws9TESN2.js → gitGraph-TEB2WS4Q-CAnBW3hc.js} +1 -1
- package/public/assets/{gitGraphDiagram-DS77QQ5N-CZu8bXTF.js → gitGraphDiagram-DS77QQ5N-ChFOjBWW.js} +1 -1
- package/public/assets/index-CsJm47wv.css +2 -0
- package/public/assets/index-DPLoCbew.js +40 -0
- package/public/assets/{info-DKCQHKI2-RSQw6txJ.js → info-DKCQHKI2-9HGQNL8q.js} +1 -1
- package/public/assets/{infoDiagram-6WML65LV-Bxmxzous.js → infoDiagram-6WML65LV-CSZIpCN6.js} +1 -1
- package/public/assets/{ishikawaDiagram-WSZJBQD7-C80fxFdg.js → ishikawaDiagram-WSZJBQD7-m5LI_l0g.js} +1 -1
- package/public/assets/{journeyDiagram-NVQOT4AX-DQTG0OdM.js → journeyDiagram-NVQOT4AX-DlhpOOGP.js} +1 -1
- package/public/assets/{kanban-definition-27J2QSJJ-B5WxFLvF.js → kanban-definition-27J2QSJJ-CpS1zcAJ.js} +1 -1
- package/public/assets/{line-D-jeoEtL.js → line-DqdLvBGQ.js} +1 -1
- package/public/assets/{linear-7oKWtFDc.js → linear-ZFSYfg_z.js} +1 -1
- package/public/assets/{mermaid-parser.core-DG-hDTtF.js → mermaid-parser.core-B4KSdud0.js} +3 -3
- package/public/assets/{mermaid.core-CxtgV-Z8.js → mermaid.core-CSBAT3ig.js} +4 -4
- package/public/assets/{mindmap-definition-FAOFIHXS-DyT9O-XZ.js → mindmap-definition-FAOFIHXS-DM39eR7l.js} +1 -1
- package/public/assets/{packet-7NZHBO7P-fYfZRQ_r.js → packet-7NZHBO7P-CaKShi1f.js} +1 -1
- package/public/assets/{pegDiagram-VL7TDLO6-BIKwmoZk.js → pegDiagram-VL7TDLO6-CeupMOTZ.js} +1 -1
- package/public/assets/{pie-RZYD4A2V-hHye9M3B.js → pie-RZYD4A2V-CdyK9ryQ.js} +1 -1
- package/public/assets/{pieDiagram-7S7Q4E2Y-B9IF9fZk.js → pieDiagram-7S7Q4E2Y-BTsBpWka.js} +1 -1
- package/public/assets/{quadrantDiagram-CIZ2JOQS-CriT2z0D.js → quadrantDiagram-CIZ2JOQS-UamjAqXX.js} +1 -1
- package/public/assets/{radar-I7S5WNFK-UHbLAuY0.js → radar-I7S5WNFK-BHPrA655.js} +1 -1
- package/public/assets/{railroad-3IZDKUUU-3enNyn3l.js → railroad-3IZDKUUU-BmnKim6H.js} +1 -1
- package/public/assets/railroad-abnf-AHOZXSZD-n6VUqVzW.js +1 -0
- package/public/assets/railroad-ebnf-EBAXGLYW-Buhu71hT.js +1 -0
- package/public/assets/railroad-peg-LSFZ7HO6-DxxRF3eB.js +1 -0
- package/public/assets/{railroadDiagram-AXF67PYL-Bkr9TUm0.js → railroadDiagram-AXF67PYL-bbNt4a4d.js} +1 -1
- package/public/assets/{requirementDiagram-LRYGKXZP-CEp-ppYZ.js → requirementDiagram-LRYGKXZP-BrwBiGll.js} +1 -1
- package/public/assets/{sankeyDiagram-W5VNT64P-CU-4IBMj.js → sankeyDiagram-W5VNT64P-BeKu1QRQ.js} +1 -1
- package/public/assets/{sequenceDiagram-SI44F4Z6-Asi631mZ.js → sequenceDiagram-SI44F4Z6-DiBZBmdo.js} +1 -1
- package/public/assets/{src-CJc45UuW.js → src-B2eptEoc.js} +1 -1
- package/public/assets/{stateDiagram-OKZ733FA-DkDphzLX.js → stateDiagram-OKZ733FA-Tjie08JN.js} +1 -1
- package/public/assets/stateDiagram-v2-UEYNNEHI-Bt5as9x1.js +1 -0
- package/public/assets/{swimlanes-SLNWSIFB-BVnRBmZ_.js → swimlanes-SLNWSIFB-B_W9IMQM.js} +1 -1
- package/public/assets/swimlanesDiagram-ULZ7WXOC-B1GEgwSB.js +8 -0
- package/public/assets/{timeline-definition-Z64GVDOM-B3jYbkKD.js → timeline-definition-Z64GVDOM-B3CqBSPD.js} +1 -1
- package/public/assets/{treeView-QDETBFTQ-FZndVHM1.js → treeView-QDETBFTQ-BUEh9zyF.js} +1 -1
- package/public/assets/{treemap-6X3UGDF4-CKoOe0fa.js → treemap-6X3UGDF4-D_t1bJ-9.js} +1 -1
- package/public/assets/{vennDiagram-T6HMQDX7-B7qlSH-y.js → vennDiagram-T6HMQDX7-B1_XMyC0.js} +1 -1
- package/public/assets/{wardley-OPB4EBWU-DlWXVdBE.js → wardley-OPB4EBWU-BlIYjwGG.js} +1 -1
- package/public/assets/{wardleyDiagram-T6FBY63Y-Dk0uBYWd.js → wardleyDiagram-T6FBY63Y-BUJJz-1q.js} +1 -1
- package/public/assets/{xychartDiagram-ELKLHX3M-BDrlWQ3J.js → xychartDiagram-ELKLHX3M-CgWS9hey.js} +1 -1
- package/public/index.html +2 -2
- package/public/assets/architecture-TIHT7OUA-BxHBbCM_.js +0 -1
- package/public/assets/channel-DiEdto8Z.js +0 -1
- package/public/assets/classDiagram-JCYQIIEL-DQfOF2lx.js +0 -1
- package/public/assets/classDiagram-v2-OCEON4UE-DQfOF2lx.js +0 -1
- package/public/assets/eventmodeling-45OFAUF4-CA3qsYNk.js +0 -1
- package/public/assets/flowDiagram-UKHOOZJN-BmCE-89P.js +0 -1
- package/public/assets/index-C3zhnM_H.css +0 -2
- package/public/assets/index-C84drWhX.js +0 -40
- package/public/assets/railroad-abnf-AHOZXSZD-CxQOPSx1.js +0 -1
- package/public/assets/railroad-ebnf-EBAXGLYW-CQ4P6ont.js +0 -1
- package/public/assets/railroad-peg-LSFZ7HO6-sSkmKuAv.js +0 -1
- package/public/assets/stateDiagram-v2-UEYNNEHI-DUHdaHkv.js +0 -1
- package/public/assets/swimlanesDiagram-ULZ7WXOC-RkUs-XHd.js +0 -8
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npx @davidbalzan/groundwork-console # or: npm
|
|
|
12
12
|
First run creates `~/.groundwork-console/config.json` (add projects from Home → **Add project**) and `~/.groundwork-console/token`, prints the URL and a sign-in link, and opens the browser already signed in. Everything runs on one port (`:8790`).
|
|
13
13
|
|
|
14
14
|
```
|
|
15
|
-
groundwork-console [start] [--port 8790] [--host 127.0.0.1 | --tailscale] [--no-open] [--no-auth] [--config <file>]
|
|
15
|
+
groundwork-console [start] [--port 8790] [--host 127.0.0.1 | --tailscale | --lan[=<ip>]] [--no-open] [--no-auth] [--config <file>]
|
|
16
16
|
groundwork-console doctor # kit requires + which bins/paths were resolved
|
|
17
17
|
groundwork-console token # print the token
|
|
18
18
|
groundwork-console config # print the config path
|
|
@@ -22,7 +22,7 @@ Requires on the host: Node ≥ 22, tmux (pane monitor / spawn), and either the t
|
|
|
22
22
|
|
|
23
23
|
## From another machine
|
|
24
24
|
|
|
25
|
-
`groundwork-console --tailscale` binds this machine's Tailscale IP (found via `tailscale ip -4`) instead of loopback and prints a sign-in link you can open from any device on your tailnet; the token cookie does the rest (plain http — keep it on the tailnet, or put TLS in front).
|
|
25
|
+
`groundwork-console --tailscale` binds this machine's Tailscale IP (found via `tailscale ip -4`) instead of loopback and prints a sign-in link you can open from any device on your tailnet; the token cookie does the rest (plain http — keep it on the tailnet, or put TLS in front). On the same home network without Tailscale: `groundwork-console --lan` binds the primary private LAN IPv4 (or `--lan=<ip>` to pin one) — same sign-in link flow, plain http, trusted networks only. Otherwise: `ssh -N -L 8790:127.0.0.1:8790 <host>` and open `http://localhost:8790`. `--no-auth` is refused together with `--lan`/`--tailscale` (token auth must front any non-loopback bind).
|
|
26
26
|
|
|
27
27
|
## What it does
|
|
28
28
|
|
|
@@ -39,6 +39,6 @@ Writes are behind two one-click header switches (bus writes · seam & docs write
|
|
|
39
39
|
## Config & env
|
|
40
40
|
|
|
41
41
|
`~/.groundwork-console/config.json`: `{ "kitPath": "~/workspace/groundwork-kit", "coordDir": "~/agent-coord", "projects": [{ "name", "path", "room" }] }`.
|
|
42
|
-
Env: `GROUNDWORK_CONSOLE_HOST` (bind; default `127.0.0.1` — put the token auth in front before exposing), `GROUNDWORK_CONSOLE_TOKEN`, `GROUNDWORK_CONSOLE_AUTH=off`, `GROUNDWORK_CONSOLE_CONFIG`, `GROUNDWORK_CONSOLE_RULES`, `AGENT_COORD_DIR`, `PORT`. (`KIT_CONSOLE_*` from the pre-rename days is still honoured.)
|
|
42
|
+
Env: `GROUNDWORK_CONSOLE_HOST` (bind; default `127.0.0.1` — put the token auth in front before exposing), `GROUNDWORK_CONSOLE_TOKEN`, `GROUNDWORK_CONSOLE_UPLOAD_TTL_HOURS` (pasted images are purged after this many hours; default 6), `GROUNDWORK_CONSOLE_AUTH=off`, `GROUNDWORK_CONSOLE_CONFIG`, `GROUNDWORK_CONSOLE_RULES`, `AGENT_COORD_DIR`, `PORT`. (`KIT_CONSOLE_*` from the pre-rename days is still honoured.)
|
|
43
43
|
|
|
44
44
|
Source, issues, design notes: <https://github.com/davidbalzan/groundwork-console>. MIT.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/* groundwork-console — one command: starts the API + serves the built UI on one port, prints the sign-in link, opens the browser.
|
|
3
|
-
Flags: --port 8790 · --host 127.0.0.1 · --tailscale (bind the tailnet IP
|
|
3
|
+
Flags: --port 8790 · --host 127.0.0.1 · --tailscale (bind the tailnet IP) · --lan[=<ip>] (bind the primary private LAN IPv4) · --no-open · --no-auth · --config <file>
|
|
4
|
+
Both remote flags print the remote sign-in link; token auth must front any non-loopback bind (--no-auth + --lan/--tailscale refuses unless GROUNDWORK_CONSOLE_UNSAFE_LAN=1).
|
|
4
5
|
Subcommands: token (print the token) · doctor (kit requires + paths) · config (print config path) */
|
|
5
6
|
import { readFileSync, existsSync } from "node:fs";
|
|
6
7
|
import { execFileSync } from "node:child_process";
|
|
@@ -23,7 +24,23 @@ function tailscaleIp() {
|
|
|
23
24
|
for (const addrs of Object.values(os.networkInterfaces())) for (const a of addrs ?? []) if (a.family === "IPv4" && /^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./.test(a.address)) return a.address;
|
|
24
25
|
return null;
|
|
25
26
|
}
|
|
27
|
+
/** Primary private LAN IPv4: prefer en0/eth0, else the first non-internal 10/8, 172.16/12, 192.168/16 address. */
|
|
28
|
+
function lanIp() {
|
|
29
|
+
const ifs = os.networkInterfaces();
|
|
30
|
+
const priv = (a) => /^(10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/.test(a);
|
|
31
|
+
const pick = (names) => { for (const n of names) for (const a of ifs[n] ?? []) if (a.family === "IPv4" && !a.internal && priv(a.address)) return a.address; return null; };
|
|
32
|
+
return pick(["en0", "eth0", "en1", "wlan0"]) ?? pick(Object.keys(ifs).sort());
|
|
33
|
+
}
|
|
26
34
|
let host = String(flag("--host", process.env.GROUNDWORK_CONSOLE_HOST ?? "127.0.0.1"));
|
|
35
|
+
const lanArg = args.find((a) => a === "--lan" || a.startsWith("--lan="));
|
|
36
|
+
if (lanArg && has("--tailscale")) { console.error("--lan and --tailscale are exclusive — pick one bind"); process.exit(1); }
|
|
37
|
+
if ((lanArg || has("--tailscale")) && has("--no-auth") && process.env.GROUNDWORK_CONSOLE_UNSAFE_LAN !== "1") { console.error("refusing a non-loopback bind with auth off — drop --no-auth (token auth fronts the LAN/tailnet), or set GROUNDWORK_CONSOLE_UNSAFE_LAN=1 if you really mean it"); process.exit(1); }
|
|
38
|
+
if (lanArg) {
|
|
39
|
+
const pinned = lanArg.includes("=") ? lanArg.split("=")[1] : null;
|
|
40
|
+
const ip = pinned ?? lanIp();
|
|
41
|
+
if (!ip) { console.error("--lan: no private LAN IPv4 found (interfaces: " + Object.keys(os.networkInterfaces()).join(", ") + ") — pass --lan=<ip>"); process.exit(1); }
|
|
42
|
+
host = ip; if (!process.env.GROUNDWORK_CONSOLE_PUBLIC_API) process.env.GROUNDWORK_CONSOLE_PUBLIC_API = `http://${ip}:${port}/api`;
|
|
43
|
+
}
|
|
27
44
|
if (has("--tailscale")) { const ip = tailscaleIp(); if (!ip) { console.error("--tailscale: no tailnet address found (is Tailscale running? try `tailscale ip -4`)"); process.exit(1); } host = ip; if (!process.env.GROUNDWORK_CONSOLE_PUBLIC_API) process.env.GROUNDWORK_CONSOLE_PUBLIC_API = `http://${ip}:${port}/api`; }
|
|
28
45
|
if (flag("--config")) process.env.GROUNDWORK_CONSOLE_CONFIG = String(flag("--config"));
|
|
29
46
|
process.env.PORT = port; process.env.GROUNDWORK_CONSOLE_HOST = host;
|
|
@@ -52,5 +69,5 @@ const { CONFIG_FILE } = await import("../dist/config.js");
|
|
|
52
69
|
const base = `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`;
|
|
53
70
|
const token = authEnabled ? loadToken() : null;
|
|
54
71
|
const url = token ? `${base}/api/auth/login?token=${encodeURIComponent(token)}&next=/` : `${base}/`;
|
|
55
|
-
console.log(`\n groundwork-console ${pkg.version}\n UI ${base}/\n config ${CONFIG_FILE}\n token ${authEnabled ? tokenFile : "auth off"}\n sign in ${url}${has("--tailscale") ? `\n remote open the sign-in link above from any device on your tailnet (token cookie, 30 d). Plain http — keep it on the tailnet.` : ""}\n`);
|
|
72
|
+
console.log(`\n groundwork-console ${pkg.version}\n UI ${base}/\n config ${CONFIG_FILE}\n token ${authEnabled ? tokenFile : "auth off"}\n sign in ${url}${has("--tailscale") ? `\n remote open the sign-in link above from any device on your tailnet (token cookie, 30 d). Plain http — keep it on the tailnet.` : lanArg ? `\n remote open the sign-in link above from any device on this LAN (token cookie, 30 d). Plain http on your local network — home/trusted networks only, never a shared or public one.` : ""}\n`);
|
|
56
73
|
if (!has("--no-open") && host !== "0.0.0.0") { try { const { default: open } = await import("open"); await open(url); } catch { /* headless: link is printed */ } }
|
package/dist/bus.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* Read-only views over the agent-coord store (~/agent-coord). Mirrors what the MCP tools return, without going through the MCP. */
|
|
2
2
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { listPanes } from "./tmux.js";
|
|
4
|
+
import { listPanes, contentActivity } from "./tmux.js";
|
|
5
5
|
const readJson = (p, fallback) => { try {
|
|
6
6
|
return JSON.parse(readFileSync(p, "utf8"));
|
|
7
7
|
}
|
|
@@ -21,12 +21,19 @@ const tailJsonl = (p, limit) => {
|
|
|
21
21
|
};
|
|
22
22
|
export async function agentsAsync(root) {
|
|
23
23
|
const panes = await listPanes();
|
|
24
|
-
|
|
24
|
+
const base = agentsSync(root);
|
|
25
|
+
// Content-based activity for live panes only (one capture per pane per poll); window_activity is the fallback.
|
|
26
|
+
const content = new Map();
|
|
27
|
+
await Promise.all(base.map(async (a) => { const pane = a.pane ? panes.find((p) => p.paneId === a.pane || p.target === a.pane) : undefined; if (pane && !pane.dead)
|
|
28
|
+
content.set(a.agentId, await contentActivity(pane.paneId)); }));
|
|
29
|
+
return base.map((a) => {
|
|
25
30
|
const pane = a.pane ? panes.find((p) => p.paneId === a.pane || p.target === a.pane) : undefined;
|
|
26
|
-
const
|
|
31
|
+
const windowActivity = pane?.activityTs ?? null;
|
|
32
|
+
const ct = content.get(a.agentId);
|
|
33
|
+
const paneActivity = ct === undefined || ct === null ? windowActivity : ct || null; // 0 = seeded, no change observed yet → treated as unknown, not as "now"
|
|
27
34
|
const lastSeen = Math.max(a.lastSeen ?? 0, paneActivity ?? 0);
|
|
28
35
|
const age = lastSeen ? Date.now() - lastSeen : Infinity;
|
|
29
|
-
return { ...a, paneActivity, paneCommand: pane?.command ?? null, paneAlive: pane ? !pane.dead : null, lastSeen: lastSeen || null, heartbeatAgeMs: Number.isFinite(age) ? age : null, liveness: age < 15 * 60_000 ? "live" : age < 24 * 3_600_000 ? "idle" : "dead", active: paneActivity ? Date.now() - paneActivity < 60_000 : false };
|
|
36
|
+
return { ...a, paneActivity, windowActivity, paneCommand: pane?.command ?? null, paneAlive: pane ? !pane.dead : null, lastSeen: lastSeen || null, heartbeatAgeMs: Number.isFinite(age) ? age : null, liveness: age < 15 * 60_000 ? "live" : age < 24 * 3_600_000 ? "idle" : "dead", active: paneActivity ? Date.now() - paneActivity < 60_000 : false };
|
|
30
37
|
}).sort((a, b) => (b.lastSeen ?? 0) - (a.lastSeen ?? 0));
|
|
31
38
|
}
|
|
32
39
|
export const agents = agentsSync;
|
package/dist/runs.js
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
|
-
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const RING = 200, LOG_MAX = 400, PERSIST_LOG = 60;
|
|
5
|
+
/* Persistence: ~/.groundwork-console/runs.json (state dir only — never ~/agent-coord). Write-behind (debounced 300 ms) on every change; loaded on construct.
|
|
6
|
+
Runs still "running" at load are marked failed ("api restarted") so nothing looks alive that isn't. Per-run logs are truncated to PERSIST_LOG lines on disk. */
|
|
7
|
+
const RUNS_FILE = path.join(homedir(), ".groundwork-console", "runs.json");
|
|
2
8
|
export class Runs {
|
|
3
9
|
runs = [];
|
|
10
|
+
saveTimer = null;
|
|
11
|
+
constructor() {
|
|
12
|
+
try {
|
|
13
|
+
if (existsSync(RUNS_FILE)) {
|
|
14
|
+
const j = JSON.parse(readFileSync(RUNS_FILE, "utf8"));
|
|
15
|
+
this.runs = (j.runs ?? []).slice(-RING).map((r) => (r.status === "running" || r.status === "queued" ? { ...r, status: "failed", endedAt: r.endedAt ?? Date.now(), error: r.error ?? "api restarted while running" } : r));
|
|
16
|
+
this.seq = j.seq ?? 0;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
this.runs = [];
|
|
21
|
+
} // corrupt file: start empty, next save overwrites
|
|
22
|
+
}
|
|
23
|
+
save() {
|
|
24
|
+
if (this.saveTimer)
|
|
25
|
+
return;
|
|
26
|
+
this.saveTimer = setTimeout(() => {
|
|
27
|
+
this.saveTimer = null;
|
|
28
|
+
try {
|
|
29
|
+
mkdirSync(path.dirname(RUNS_FILE), { recursive: true });
|
|
30
|
+
const slim = this.runs.map((r) => ({ ...r, log: r.log.length > PERSIST_LOG ? [`… ${r.log.length - PERSIST_LOG} earlier lines`, ...r.log.slice(-PERSIST_LOG)] : r.log }));
|
|
31
|
+
writeFileSync(RUNS_FILE, JSON.stringify({ seq: this.seq, savedAt: Date.now(), runs: slim }));
|
|
32
|
+
}
|
|
33
|
+
catch { /* disk unavailable: in-memory ring still works */ }
|
|
34
|
+
}, 300);
|
|
35
|
+
}
|
|
4
36
|
listeners = new Set();
|
|
5
37
|
seq = 0;
|
|
6
38
|
list(limit = 50) { return this.runs.slice(-limit).reverse(); }
|
|
@@ -8,7 +40,7 @@ export class Runs {
|
|
|
8
40
|
running() { return this.runs.filter((r) => r.status === "running" || r.status === "queued"); }
|
|
9
41
|
subscribe(fn) { this.listeners.add(fn); return () => this.listeners.delete(fn); }
|
|
10
42
|
emit(r) { for (const l of this.listeners)
|
|
11
|
-
l(r); }
|
|
43
|
+
l(r); this.save(); }
|
|
12
44
|
create(init) {
|
|
13
45
|
const r = { id: `run-${Date.now()}-${++this.seq}`, status: "queued", startedAt: Date.now(), log: [], ...init };
|
|
14
46
|
this.runs.push(r);
|
package/dist/server.js
CHANGED
|
@@ -26,7 +26,7 @@ import { Runs } from "./runs.js";
|
|
|
26
26
|
import { BusClient, AGENT_ID, busWrites, seamWrites, saveSwitches, SWITCHES_FILE } from "./mcp.js";
|
|
27
27
|
import { addQueueItem, reverifyFact, scopeAllows } from "./seamwrite.js";
|
|
28
28
|
import { fileURLToPath } from "node:url";
|
|
29
|
-
import { saveUpload, uploadPath, UPLOAD_DIR } from "./uploads.js";
|
|
29
|
+
import { saveUpload, uploadPath, purgeUploads, UPLOAD_DIR, UPLOAD_TTL_HOURS } from "./uploads.js";
|
|
30
30
|
import { answered, markAnswered, isAnswered, setCoordDir } from "./answered.js";
|
|
31
31
|
import { requireAuth, login, logout, isAuthed, authEnabled, tokenFile } from "./auth.js";
|
|
32
32
|
import { knowledgeIndex, knowledgeDoc, knowledgePath } from "./knowledge.js";
|
|
@@ -703,6 +703,32 @@ app.post("/actions/phase/:name/:phase/:verb", async (c) => {
|
|
|
703
703
|
const r = await runs.exec({ kind: "bus", label: `${verb} ${phase} → ${target}`, project: p.name, sideEffects: true, args: { to: target, text: text.slice(0, 200) } }, (log) => busDavid.call("send_message", { to: target, text }, log));
|
|
704
704
|
return c.json(r, r.status === "ok" ? 200 : 403);
|
|
705
705
|
});
|
|
706
|
+
/* ---- Per-task "mark done": ask the task's owner (stream owner → bus agent; fallback aide) to run /check-task. The console never ticks PHASE*_TASKS.md itself. ---- */
|
|
707
|
+
app.post("/actions/task/:name/:phase/:line", async (c) => {
|
|
708
|
+
const p = project(c.req.param("name"));
|
|
709
|
+
if (!p)
|
|
710
|
+
return c.json({ error: "unknown project" }, 404);
|
|
711
|
+
const phase = c.req.param("phase");
|
|
712
|
+
const line = Number(c.req.param("line"));
|
|
713
|
+
const b = (await c.req.json().catch(() => ({})));
|
|
714
|
+
const t = phaseTasksOf(path.join(p.path, "docs"), phase);
|
|
715
|
+
if (!t?.file)
|
|
716
|
+
return c.json({ error: "no tasks file for that phase" }, 404);
|
|
717
|
+
const sec = t.sections.find((sc) => sc.tasks.some((x) => x.line === line));
|
|
718
|
+
const task = sec?.tasks.find((x) => x.line === line);
|
|
719
|
+
if (!task)
|
|
720
|
+
return c.json({ error: "no task at that line" }, 404);
|
|
721
|
+
if (task.done)
|
|
722
|
+
return c.json({ error: "already ticked" }, 409);
|
|
723
|
+
const live = agents(cfg.coordDir).filter((a) => a.liveness !== "dead");
|
|
724
|
+
const owner = b.owner && live.some((a) => a.agentId === b.owner) ? b.owner : roleAgent(p.name, p.room, /aide|liaison|curator/i);
|
|
725
|
+
if (!owner)
|
|
726
|
+
return c.json({ error: `no live owner or aide for ${p.name}` }, 409);
|
|
727
|
+
const n = phase.match(/(\d+(?:\.\d+)?)/)?.[1] ?? phase;
|
|
728
|
+
const text = `AGENT_ACTION: mark done in ${p.name} — run /check-task ${n} "${task.text.slice(0, 120)}" (${t.file}:${line}, section "${sec.title}") — tick it in the tasks file and update the board row; reply DONE with the line.${b.note ? " Note: " + b.note : ""}`;
|
|
729
|
+
const r = await runs.exec({ kind: "bus", label: `mark done ${phase}:${line} → ${owner}`, project: p.name, sideEffects: true, args: { to: owner, phase, line, task: task.text.slice(0, 120) } }, (log) => busDavid.call("send_message", { to: owner, text }, log));
|
|
730
|
+
return c.json({ ...r, owner }, r.status === "ok" ? 200 : 403);
|
|
731
|
+
});
|
|
706
732
|
/* ---- Project-level onboarding actions (as david → the aide): create-prd conversation starter, /kickstart ---- */
|
|
707
733
|
app.post("/actions/project/:name/:verb", async (c) => {
|
|
708
734
|
const p = project(c.req.param("name"));
|
|
@@ -867,6 +893,22 @@ app.get("/bus/status", (c) => c.json(statusStream(cfg.coordDir, Number(c.req.que
|
|
|
867
893
|
app.get("/bus/scopes", (c) => c.json(scopes(cfg.coordDir)));
|
|
868
894
|
events.start(15_000);
|
|
869
895
|
notifier.start(15_000);
|
|
896
|
+
/* Uploads TTL purge: at boot + every 30 min (unref'd so it never holds the process). A Run is logged only when something was deleted. */
|
|
897
|
+
const runPurge = () => {
|
|
898
|
+
try {
|
|
899
|
+
const r = purgeUploads();
|
|
900
|
+
if (r.deleted.length)
|
|
901
|
+
void runs.exec({ kind: "cli", label: `uploads purge · ${r.deleted.length} deleted`, sideEffects: true, args: { ttlHours: UPLOAD_TTL_HOURS, deleted: r.deleted.length, kept: r.kept, errors: r.errors } }, async (log) => { log(`ttl ${UPLOAD_TTL_HOURS} h · dir ${UPLOAD_DIR}`); for (const n of r.deleted.slice(0, 50))
|
|
902
|
+
log(`deleted ${n}`); if (r.deleted.length > 50)
|
|
903
|
+
log(`… ${r.deleted.length - 50} more`); if (r.errors)
|
|
904
|
+
log(`${r.errors} file(s) could not be removed`); return r; });
|
|
905
|
+
}
|
|
906
|
+
catch (e) {
|
|
907
|
+
console.warn(`uploads purge failed: ${e.message}`);
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
runPurge();
|
|
911
|
+
setInterval(runPurge, 30 * 60_000).unref();
|
|
870
912
|
/* Packaged mode: the built web UI lives in ../public (copied at prepack) and is served by the same process at / — one port, no Vite. Dev keeps using Vite on :5173 with the /api proxy. */
|
|
871
913
|
root.route("/", app);
|
|
872
914
|
if (existsSync(PUBLIC_DIR)) {
|
package/dist/tmux.js
CHANGED
|
@@ -17,6 +17,38 @@ export async function listPanes(ttlMs = 3_000) {
|
|
|
17
17
|
}
|
|
18
18
|
export async function paneActivity(paneId) { if (!paneId)
|
|
19
19
|
return null; return (await listPanes()).find((p) => p.paneId === paneId || p.target === paneId) ?? null; }
|
|
20
|
+
/* Content activity: `window_activity` ticks on ANY output, including the harness status/timer strip redrawing while idle — that made tiles pulse with nobody working.
|
|
21
|
+
contentTs advances only when the pane content ABOVE the bottom K lines changes (K=3 = the Claude Code status/prompt strip). One capture per pane per poll
|
|
22
|
+
(N=120 lines, ≤ once per CONTENT_TTL, dead panes skipped), hashed and compared per pane. Falls back to window_activity when capture fails. Read-only. */
|
|
23
|
+
const CONTENT_TTL = 3_000, CONTENT_LINES = 120, STRIP_LINES = 3;
|
|
24
|
+
const contentCache = new Map();
|
|
25
|
+
const djb2 = (s) => { let h = 5381; for (let i = 0; i < s.length; i++)
|
|
26
|
+
h = ((h << 5) + h + s.charCodeAt(i)) | 0; return (h >>> 0).toString(36); };
|
|
27
|
+
export async function contentActivity(paneId) {
|
|
28
|
+
const c = contentCache.get(paneId);
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
if (c && now - c.at < CONTENT_TTL)
|
|
31
|
+
return c.ts;
|
|
32
|
+
try {
|
|
33
|
+
const { stdout } = await run("tmux", ["capture-pane", "-p", "-t", paneId, "-S", `-${CONTENT_LINES}`], { timeout: 2_000, maxBuffer: 1 << 20 });
|
|
34
|
+
const lines = stdout.replace(/\s+$/gm, "").split("\n");
|
|
35
|
+
while (lines.length && !lines[lines.length - 1])
|
|
36
|
+
lines.pop();
|
|
37
|
+
const body = lines.slice(0, Math.max(0, lines.length - STRIP_LINES)).join("\n");
|
|
38
|
+
const hash = djb2(body);
|
|
39
|
+
// First sample seeds ts=0 (unknown): we never claim activity we have not observed; the first CHANGE sets it.
|
|
40
|
+
const ts = c ? (c.hash === hash ? c.ts : now) : 0;
|
|
41
|
+
contentCache.set(paneId, { at: now, hash, ts });
|
|
42
|
+
if (contentCache.size > 200)
|
|
43
|
+
for (const [k, v] of contentCache)
|
|
44
|
+
if (now - v.at > 600_000)
|
|
45
|
+
contentCache.delete(k);
|
|
46
|
+
return ts;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
20
52
|
const ANSI = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
|
|
21
53
|
export async function capturePane(paneId, lines = 200, colors = false) {
|
|
22
54
|
const panes = await listPanes(0);
|
package/dist/uploads.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
/* Pasted/dropped images for DMs. Console-owned store: ~/.groundwork-console/uploads/<date>-<sha8>.<ext>.
|
|
@@ -38,3 +38,33 @@ export function uploadPath(name) {
|
|
|
38
38
|
const mime = Object.entries(EXT).find(([, e]) => e === ext || (e === "jpg" && ext === "jpeg"))?.[0] ?? "application/octet-stream";
|
|
39
39
|
return { path: p, mime, bytes: statSync(p).size };
|
|
40
40
|
}
|
|
41
|
+
/* TTL purge (David 2026-08-19): screenshots are transient — the message text keeps the path and the agent has already read it.
|
|
42
|
+
Deletes regular files in UPLOAD_DIR matching the upload name pattern whose mtime is older than GROUNDWORK_CONSOLE_UPLOAD_TTL_HOURS (default 6; 0/invalid → default).
|
|
43
|
+
No subdirs, no symlink follow, per-file errors counted not thrown. Run at boot + every 30 min (see server.ts). */
|
|
44
|
+
export const UPLOAD_TTL_HOURS = (() => { const n = Number(process.env.GROUNDWORK_CONSOLE_UPLOAD_TTL_HOURS); return Number.isFinite(n) && n > 0 ? n : 6; })();
|
|
45
|
+
export function purgeUploads(ttlHours = UPLOAD_TTL_HOURS) {
|
|
46
|
+
const out = { deleted: [], errors: 0, kept: 0 };
|
|
47
|
+
if (!existsSync(UPLOAD_DIR))
|
|
48
|
+
return out;
|
|
49
|
+
const cutoff = Date.now() - ttlHours * 3600_000;
|
|
50
|
+
for (const name of readdirSync(UPLOAD_DIR)) {
|
|
51
|
+
if (!NAME_RE.test(name))
|
|
52
|
+
continue;
|
|
53
|
+
const p = path.join(UPLOAD_DIR, name);
|
|
54
|
+
try {
|
|
55
|
+
const st = lstatSync(p);
|
|
56
|
+
if (!st.isFile())
|
|
57
|
+
continue;
|
|
58
|
+
if (st.mtimeMs < cutoff) {
|
|
59
|
+
unlinkSync(p);
|
|
60
|
+
out.deleted.push(name);
|
|
61
|
+
}
|
|
62
|
+
else
|
|
63
|
+
out.kept++;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
out.errors++;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{m as e,t}from"./mermaid-parser.core-
|
|
1
|
+
import{m as e,t}from"./mermaid-parser.core-B4KSdud0.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-B2eptEoc.js";import"./chunk-I66GZJ75-CCxcXl9W.js";import{n as i,r as a,t as o}from"./chunk-6Q2QTUOP-BXxDc17T.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-CSBAT3ig.js";var c=e().RailroadAbnf.parser.LangiumParser,l=n(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=n(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=n(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=n(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=n(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=n(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=n(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:n(e=>{o.clear(),r.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(e);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new t(n);let i=n.value;r.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),r.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e,t}from"./path-fybaL0A-.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-
|
|
1
|
+
import{n as e,t}from"./path-fybaL0A-.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-DFkPtM9v.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i){if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./chunk-KEIR6QF5-BICK3FdT.js";import{M as e}from"./mermaid-parser.core-B4KSdud0.js";export{e as createArchitectureServices};
|