@edspencer/paddock 0.79.1 → 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/package.json +1 -1
- package/packages/server/dist/app.d.ts +7 -0
- package/packages/server/dist/app.js +19 -15
- package/packages/server/dist/auth.d.ts +3 -2
- package/packages/server/dist/auth.js +11 -0
- package/packages/server/dist/background-live.d.ts +17 -0
- package/packages/server/dist/background-live.js +24 -0
- package/packages/server/dist/busy.d.ts +96 -0
- package/packages/server/dist/busy.js +88 -0
- package/packages/server/dist/cli/args.d.ts +63 -7
- package/packages/server/dist/cli/args.js +227 -20
- package/packages/server/dist/cli/busy-command.d.ts +58 -0
- package/packages/server/dist/cli/busy-command.js +108 -0
- package/packages/server/dist/cli/busy.d.ts +69 -0
- package/packages/server/dist/cli/busy.js +150 -0
- package/packages/server/dist/cli/exit-codes.d.ts +49 -0
- package/packages/server/dist/cli/exit-codes.js +48 -0
- package/packages/server/dist/cli/output.d.ts +52 -0
- package/packages/server/dist/cli/output.js +84 -0
- package/packages/server/dist/cli/paddock.js +98 -37
- package/packages/server/dist/cli/service/backend.d.ts +48 -4
- package/packages/server/dist/cli/service/index.d.ts +68 -2
- package/packages/server/dist/cli/service/index.js +321 -60
- package/packages/server/dist/cli/service/launchd.d.ts +23 -1
- package/packages/server/dist/cli/service/launchd.js +145 -25
- package/packages/server/dist/cli/service/logs.d.ts +39 -0
- package/packages/server/dist/cli/service/logs.js +115 -0
- package/packages/server/dist/cli/service/path-env.d.ts +27 -0
- package/packages/server/dist/cli/service/path-env.js +120 -0
- package/packages/server/dist/cli/service/ready.d.ts +22 -0
- package/packages/server/dist/cli/service/ready.js +30 -0
- package/packages/server/dist/cli/service/spec.d.ts +45 -6
- package/packages/server/dist/cli/service/spec.js +34 -7
- package/packages/server/dist/cli/service/systemd.d.ts +47 -4
- package/packages/server/dist/cli/service/systemd.js +127 -22
- package/packages/server/dist/herdctl.d.ts +19 -0
- package/packages/server/dist/herdctl.js +26 -0
- package/packages/server/dist/management-mcp-server.d.ts +21 -3
- package/packages/server/dist/management-mcp-server.js +4 -13
- package/packages/server/dist/routes/meta.js +56 -3
- package/packages/server/dist/runtime-file.d.ts +26 -0
- package/packages/server/dist/runtime-file.js +91 -0
- package/packages/server/dist/service-log.d.ts +29 -0
- package/packages/server/dist/service-log.js +104 -0
- package/packages/server/dist/start.d.ts +33 -0
- package/packages/server/dist/start.js +126 -8
- package/packages/server/dist/version.d.ts +3 -0
- package/packages/server/dist/version.js +32 -0
- package/packages/server/dist/ws-context.d.ts +9 -0
- package/packages/server/dist/ws-turn.js +1 -0
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/{DeleteProjectDialog-BcxJMA1w.js → DeleteProjectDialog-D8oIJ8HC.js} +1 -1
- package/packages/web/dist/assets/DiscoverPage-CQlzxsn4.js +2 -0
- package/packages/web/dist/assets/{DiscoverView-DOmTmiVv.js → DiscoverView-o0KCr3aB.js} +1 -1
- package/packages/web/dist/assets/{InstanceConfigPage-DWLQqHW0.js → InstanceConfigPage-RSJz7oL3.js} +1 -1
- package/packages/web/dist/assets/{MarkdownRenderer-Y25JWlMR.js → MarkdownRenderer-39mafH8b.js} +1 -1
- package/packages/web/dist/assets/{ProjectRedirect-CCI1VWM6.js → ProjectRedirect-Bq7GVguY.js} +1 -1
- package/packages/web/dist/assets/{ProjectView-DzfVhjya.js → ProjectView-BoBIru1g.js} +3 -3
- package/packages/web/dist/assets/{ProjectsGrid-yIyyb2HA.js → ProjectsGrid-mJ4gqbr6.js} +1 -1
- package/packages/web/dist/assets/{RootHome-x0Om9-Fd.js → RootHome-C0kwFyYp.js} +1 -1
- package/packages/web/dist/assets/{Surfaces-wbcMQIsK.js → Surfaces-MkhXlB8z.js} +1 -1
- package/packages/web/dist/assets/{arc-tWN7FYIq.js → arc-DExlwAwO.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-Ccj6Vlyy.js → architectureDiagram-3BPJPVTR-CZly3W79.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-B7iGXwqY.js → blockDiagram-GPEHLZMM-AgES73i3.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-SkLKFSso.js → c4Diagram-AAUBKEIU-N9mD8Pfk.js} +1 -1
- package/packages/web/dist/assets/channel-ExsAG7qT.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-BrS8C-YK.js → chunk-2J33WTMH-Dsx_kfUl.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-BUR6nG8w.js → chunk-4BX2VUAB-BQjqcjPj.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-BRBNJb-u.js → chunk-55IACEB6-CCxpYQpY.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-DKeNPTXz.js → chunk-727SXJPM-DpFRdnUa.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-BOS1K-DX.js → chunk-AQP2D5EJ-D2BAlzoO.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-D3PZL8fo.js → chunk-FMBD7UC4-BFaMRzUF.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-DA9Y77Zj.js → chunk-ND2GUHAM-B9NGJ9xZ.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-wPph-weq.js → chunk-QZHKN3VN-CF1IHyMS.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-si7g9okq.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-si7g9okq.js +2 -0
- package/packages/web/dist/assets/{core-I1PQdGMJ.js → core-XjElsvQ7.js} +1 -1
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-BA7N2vVK.js → cose-bilkent-S5V4N54A-CF972Flx.js} +1 -1
- package/packages/web/dist/assets/{dagre-BM42HDAG-DKpON5p2.js → dagre-BM42HDAG-DsneI8YE.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-Db0I-eEL.js → diagram-2AECGRRQ-BHyrVzIk.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-BEuqOTfn.js → diagram-5GNKFQAL-Dj4q8xEi.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-CnyX_0Xk.js → diagram-KO2AKTUF-BHe3Puv_.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-CYvknzDd.js → diagram-LMA3HP47-wzt6G2FE.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-DlgKxbYz.js → diagram-OG6HWLK6-CZMbMRJq.js} +1 -1
- package/packages/web/dist/assets/{documentTitle-Cvob-HVs.js → documentTitle-BwTIA8rI.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-BkO5JmiM.js → erDiagram-TEJ5UH35-Dtu14BL1.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-DG0o_zc1.js → flowDiagram-I6XJVG4X-T34ql9j0.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-Bs71No2S.js → ganttDiagram-6RSMTGT7-DQLOeCXK.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-CxODhRKd.js → gitGraphDiagram-PVQCEYII-Bk11zea6.js} +1 -1
- package/packages/web/dist/assets/{index-C4tsPSvG.js → index-CvaWErFv.js} +2 -2
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-C412TQaw.js → infoDiagram-5YYISTIA-CsE83lu0.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-DUlcZXQQ.js → ishikawaDiagram-YF4QCWOH-DNVgA93n.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-iL0klVhn.js → journeyDiagram-JHISSGLW-CLpSNjM3.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-CO02Zl_Q.js → kanban-definition-UN3LZRKU-BRzuXcKr.js} +1 -1
- package/packages/web/dist/assets/{linear-DtmLY8AG.js → linear-BxOawONt.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-Dvzxug80.js → mermaid.core-Dvs1QCcz.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-q950AAWL.js → mindmap-definition-RKZ34NQL-Dmt7pYwF.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-BVrv8A-H.js → pieDiagram-4H26LBE5-BHiZaJD-.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-Bp5D2MFq.js → quadrantDiagram-W4KKPZXB-U0A1noRE.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-yZfQU4Ix.js → requirementDiagram-4Y6WPE33-CZLsr6yi.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-5R8_OZir.js → sankeyDiagram-5OEKKPKP-BTlLGILU.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-C3A9rinG.js → sequenceDiagram-3UESZ5HK-DpSS_Gb1.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-Cshrt_XB.js → stateDiagram-AJRCARHV-D9srE-tV.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-AxWqsDh-.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-kNkiddCX.js → timeline-definition-PNZ67QCA-BYQ5CKab.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-BiNJm17g.js → vennDiagram-CIIHVFJN-B4W2CtSV.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-CjExbCuX.js → wardley-L42UT6IY--XUV7GvD.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-CMn3z-G2.js → wardleyDiagram-YWT4CUSO-Bl2VIEGY.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-B-UhnooW.js → xychartDiagram-2RQKCTM6-Bj2H9Rcf.js} +1 -1
- package/packages/web/dist/index.html +1 -1
- package/packages/web/dist/sw.js +1 -1
- package/packages/web/dist/assets/DiscoverPage-CSPRrFVk.js +0 -2
- package/packages/web/dist/assets/channel-B2NSmlBB.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-B3tLFeZC.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-B3tLFeZC.js +0 -2
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-DgiazF78.js +0 -2
|
@@ -22,14 +22,23 @@ export interface ServiceSpec {
|
|
|
22
22
|
* interpreter explicitly removes the lookup entirely.
|
|
23
23
|
*/
|
|
24
24
|
scriptPath: string;
|
|
25
|
-
/** Arguments after the script: always starts with the `
|
|
25
|
+
/** Arguments after the script: always starts with the `serve` verb. */
|
|
26
26
|
args: string[];
|
|
27
27
|
/** A directory the service can sit in that it certainly owns. */
|
|
28
28
|
workingDirectory: string;
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
/**
|
|
30
|
+
* The one file both streams append to, on every OS (SPEC §7).
|
|
31
|
+
*
|
|
32
|
+
* launchd names it twice (`StandardOutPath` and `StandardErrorPath`); systemd
|
|
33
|
+
* names it twice as well (`StandardOutput=append:` / `StandardError=append:`)
|
|
34
|
+
* rather than using the journal. That is what makes `paddock service logs`
|
|
35
|
+
* one Node implementation instead of two, and it is also what makes the
|
|
36
|
+
* Linux logs survive a journal that is volatile or rate-limited — the default
|
|
37
|
+
* on plenty of distributions, where `journalctl --user` after a reboot showed
|
|
38
|
+
* nothing at all.
|
|
39
|
+
*/
|
|
40
|
+
logPath: string;
|
|
41
|
+
/** PATH for the service: Node's bin, the usable inherited entries, the system dirs. */
|
|
33
42
|
pathEnv: string;
|
|
34
43
|
}
|
|
35
44
|
export interface SpecInput {
|
|
@@ -44,6 +53,19 @@ export interface SpecInput {
|
|
|
44
53
|
dataDirArg?: string;
|
|
45
54
|
verbose?: boolean;
|
|
46
55
|
pathEnv?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Injected by tests: "is this an existing directory?", and which system
|
|
58
|
+
* directories to append.
|
|
59
|
+
*
|
|
60
|
+
* Not a knob — a necessity. `servicePath` keeps only directories that EXIST,
|
|
61
|
+
* which is what stops a unit naming a `node` that went away with a terminal
|
|
62
|
+
* (path-env.ts). That makes the rendered unit depend on the filesystem it was
|
|
63
|
+
* rendered on, so a golden plist asserted on this box would differ from the
|
|
64
|
+
* same golden on a macOS runner, for reasons that have nothing to do with the
|
|
65
|
+
* code under test. Pinning both makes the goldens about the renderer again.
|
|
66
|
+
*/
|
|
67
|
+
pathExists?: (p: string) => boolean;
|
|
68
|
+
systemDirs?: readonly string[];
|
|
47
69
|
}
|
|
48
70
|
/**
|
|
49
71
|
* The service's own directory inside the data dir.
|
|
@@ -58,14 +80,31 @@ export interface SpecInput {
|
|
|
58
80
|
* subdirectory is neither.
|
|
59
81
|
*/
|
|
60
82
|
export declare function serviceDir(dataDir: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* Where the service's log lives: `<dataDir>/logs/service.log` (SPEC §7).
|
|
85
|
+
*
|
|
86
|
+
* One file, both streams, every platform — so `paddock service logs` is one
|
|
87
|
+
* implementation and the answer to "where are the logs" is one path. It is NOT
|
|
88
|
+
* under {@link serviceDir}: the logs outlive any one install (`uninstall` keeps
|
|
89
|
+
* them deliberately), and the server rotates this file itself
|
|
90
|
+
* (`service-log.ts`), which means something other than the service CLI has to
|
|
91
|
+
* be able to name it from the data dir alone.
|
|
92
|
+
*/
|
|
93
|
+
export declare function logsDir(dataDir: string): string;
|
|
94
|
+
/** The single log file both streams append to. */
|
|
95
|
+
export declare function serviceLogPath(dataDir: string): string;
|
|
61
96
|
/** The data dir the service will use, resolved the same way `paddock.ts` does. */
|
|
62
97
|
export declare function defaultDataDir(homeDir?: string): string;
|
|
63
98
|
/**
|
|
64
99
|
* Assemble the spec.
|
|
65
100
|
*
|
|
66
|
-
* The argument vector leads with the `
|
|
101
|
+
* The argument vector leads with the `serve` verb even though bare `paddock`
|
|
67
102
|
* would do the same thing. It costs nothing and it is the difference between
|
|
68
103
|
* `launchctl print` showing an argv you can read and one you have to recognise.
|
|
104
|
+
* It was `start` until the CLI contract made `serve` the name for running in
|
|
105
|
+
* the foreground (SPEC §1); `start` is a permanent alias, so a unit written by
|
|
106
|
+
* an older Paddock keeps working untouched and only gets `serve` when something
|
|
107
|
+
* rewrites it.
|
|
69
108
|
*
|
|
70
109
|
* `--data-dir` is forwarded ONLY when the user passed it. The default of
|
|
71
110
|
* setting nothing is load-bearing: with no data dir named anywhere, the service
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import os from "node:os";
|
|
12
12
|
import path from "node:path";
|
|
13
|
+
import { servicePath } from "./path-env.js";
|
|
13
14
|
/**
|
|
14
15
|
* Reverse-DNS on macOS, plain on Linux. Both are stable identifiers a user may
|
|
15
16
|
* end up typing (`launchctl print gui/501/net.edspencer.paddock`,
|
|
@@ -37,6 +38,23 @@ export const DEFAULT_PORT = "7233";
|
|
|
37
38
|
export function serviceDir(dataDir) {
|
|
38
39
|
return path.join(dataDir, "service");
|
|
39
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Where the service's log lives: `<dataDir>/logs/service.log` (SPEC §7).
|
|
43
|
+
*
|
|
44
|
+
* One file, both streams, every platform — so `paddock service logs` is one
|
|
45
|
+
* implementation and the answer to "where are the logs" is one path. It is NOT
|
|
46
|
+
* under {@link serviceDir}: the logs outlive any one install (`uninstall` keeps
|
|
47
|
+
* them deliberately), and the server rotates this file itself
|
|
48
|
+
* (`service-log.ts`), which means something other than the service CLI has to
|
|
49
|
+
* be able to name it from the data dir alone.
|
|
50
|
+
*/
|
|
51
|
+
export function logsDir(dataDir) {
|
|
52
|
+
return path.join(dataDir, "logs");
|
|
53
|
+
}
|
|
54
|
+
/** The single log file both streams append to. */
|
|
55
|
+
export function serviceLogPath(dataDir) {
|
|
56
|
+
return path.join(logsDir(dataDir), "service.log");
|
|
57
|
+
}
|
|
40
58
|
/** The data dir the service will use, resolved the same way `paddock.ts` does. */
|
|
41
59
|
export function defaultDataDir(homeDir = os.homedir()) {
|
|
42
60
|
return path.join(homeDir, ".paddock");
|
|
@@ -44,9 +62,13 @@ export function defaultDataDir(homeDir = os.homedir()) {
|
|
|
44
62
|
/**
|
|
45
63
|
* Assemble the spec.
|
|
46
64
|
*
|
|
47
|
-
* The argument vector leads with the `
|
|
65
|
+
* The argument vector leads with the `serve` verb even though bare `paddock`
|
|
48
66
|
* would do the same thing. It costs nothing and it is the difference between
|
|
49
67
|
* `launchctl print` showing an argv you can read and one you have to recognise.
|
|
68
|
+
* It was `start` until the CLI contract made `serve` the name for running in
|
|
69
|
+
* the foreground (SPEC §1); `start` is a permanent alias, so a unit written by
|
|
70
|
+
* an older Paddock keeps working untouched and only gets `serve` when something
|
|
71
|
+
* rewrites it.
|
|
50
72
|
*
|
|
51
73
|
* `--data-dir` is forwarded ONLY when the user passed it. The default of
|
|
52
74
|
* setting nothing is load-bearing: with no data dir named anywhere, the service
|
|
@@ -58,7 +80,7 @@ export function defaultDataDir(homeDir = os.homedir()) {
|
|
|
58
80
|
* service opening a browser tab at login is not a feature.
|
|
59
81
|
*/
|
|
60
82
|
export function buildSpec(input) {
|
|
61
|
-
const args = ["
|
|
83
|
+
const args = ["serve"];
|
|
62
84
|
if (input.port !== undefined)
|
|
63
85
|
args.push("--port", input.port);
|
|
64
86
|
if (input.host !== undefined)
|
|
@@ -67,15 +89,20 @@ export function buildSpec(input) {
|
|
|
67
89
|
args.push("--data-dir", input.dataDirArg);
|
|
68
90
|
if (input.verbose === true)
|
|
69
91
|
args.push("--verbose");
|
|
70
|
-
const dir = serviceDir(input.dataDir);
|
|
71
92
|
return {
|
|
72
93
|
nodePath: input.nodePath,
|
|
73
94
|
scriptPath: input.scriptPath,
|
|
74
95
|
args,
|
|
75
|
-
workingDirectory:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
96
|
+
workingDirectory: serviceDir(input.dataDir),
|
|
97
|
+
logPath: serviceLogPath(input.dataDir),
|
|
98
|
+
// Node's own bin directory leads, so the interpreter the unit names can
|
|
99
|
+
// find its neighbours; `servicePath` drops the installing shell's
|
|
100
|
+
// per-session entries. See path-env.ts for why inheriting PATH verbatim
|
|
101
|
+
// writes a unit that rots when a terminal closes.
|
|
102
|
+
pathEnv: servicePath(path.dirname(input.nodePath), input.pathEnv, {
|
|
103
|
+
...(input.pathExists !== undefined ? { exists: input.pathExists } : {}),
|
|
104
|
+
...(input.systemDirs !== undefined ? { systemDirs: input.systemDirs } : {}),
|
|
105
|
+
}),
|
|
79
106
|
};
|
|
80
107
|
}
|
|
81
108
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ServiceSpec } from "./spec.js";
|
|
2
|
-
import type { Runner, ServiceBackend } from "./backend.js";
|
|
2
|
+
import type { RunResult, Runner, ServiceBackend } from "./backend.js";
|
|
3
3
|
/**
|
|
4
4
|
* `~/.config/systemd/user/paddock.service`, honouring `XDG_CONFIG_HOME`.
|
|
5
5
|
*
|
|
@@ -28,6 +28,17 @@ import type { Runner, ServiceBackend } from "./backend.js";
|
|
|
28
28
|
* caller with no opinion still gets the right answer.)
|
|
29
29
|
*/
|
|
30
30
|
export declare function unitPath(homeDir?: string, xdgConfigHome?: string): string;
|
|
31
|
+
/** One `ExecStart` word: specifiers and variables escaped, quoted when it has to be. */
|
|
32
|
+
export declare function execWord(a: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* The other way: the words of an `ExecStart` line.
|
|
35
|
+
*
|
|
36
|
+
* Reads both shapes on purpose. A unit written before #796's quoting is a bare
|
|
37
|
+
* whitespace-separated line, and its owner is exactly the person whose `status`
|
|
38
|
+
* has to keep working — so unquoted words pass through unchanged, and quoted
|
|
39
|
+
* ones are unescaped.
|
|
40
|
+
*/
|
|
41
|
+
export declare function execWords(line: string): string[];
|
|
31
42
|
/**
|
|
32
43
|
* Render the unit.
|
|
33
44
|
*
|
|
@@ -47,11 +58,32 @@ export declare function unitPath(homeDir?: string, xdgConfigHome?: string): stri
|
|
|
47
58
|
* "start at login" for a user unit; `multi-user.target` is the system-unit
|
|
48
59
|
* spelling and does nothing here.
|
|
49
60
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
61
|
+
* ## Logs go to a file, not the journal (SPEC §7)
|
|
62
|
+
*
|
|
63
|
+
* `StandardOutput=append:` / `StandardError=append:` name one file, the same
|
|
64
|
+
* one launchd's two keys name. The journal was the obvious choice and the wrong
|
|
65
|
+
* one for three reasons: `journalctl --user` shows nothing at all on a distro
|
|
66
|
+
* whose journal is volatile (the output is gone after a reboot, which is
|
|
67
|
+
* exactly when someone looks); it makes "where are the logs" have a different
|
|
68
|
+
* answer on each platform, so `paddock service logs` would be two
|
|
69
|
+
* implementations; and it rules out Windows, which the contract requires
|
|
70
|
+
* nothing rule out.
|
|
71
|
+
*
|
|
72
|
+
* The directory has to exist first — systemd creates the file, not its parent —
|
|
73
|
+
* which `install` below does.
|
|
74
|
+
*
|
|
75
|
+
* ## Every ExecStart word is quoted
|
|
76
|
+
*
|
|
77
|
+
* `%` starts a specifier anywhere in a unit file and `$` starts a variable in
|
|
78
|
+
* `ExecStart`, so both are escaped; a word containing whitespace, a quote or a
|
|
79
|
+
* backslash is quoted. Paths with a space in them are the common case
|
|
80
|
+
* (`/Users/Ed Spencer/.paddock`), and the old unqualified `join(" ")` wrote a
|
|
81
|
+
* unit systemd then split into the wrong arguments — a service that fails at
|
|
82
|
+
* login having printed nothing.
|
|
53
83
|
*/
|
|
54
84
|
export declare function renderUnit(spec: ServiceSpec): string;
|
|
85
|
+
/** The log file an installed unit appends to, or undefined if it names none. */
|
|
86
|
+
export declare function parseUnitLogPath(text: string): string | undefined;
|
|
55
87
|
/**
|
|
56
88
|
* Does an installed unit carry `Restart=always` (#872)?
|
|
57
89
|
*
|
|
@@ -64,4 +96,15 @@ export declare function renderUnit(spec: ServiceSpec): string;
|
|
|
64
96
|
export declare function unitKeepsAlive(text: string): boolean;
|
|
65
97
|
/** Read `ExecStart=` back out of an installed unit, split into an argv. */
|
|
66
98
|
export declare function parseUnitArgv(text: string): string[];
|
|
99
|
+
/**
|
|
100
|
+
* What a `systemctl` failure means, in the manager's own words where it has any.
|
|
101
|
+
*
|
|
102
|
+
* The `status === null` branch is the one QA found: `spawnSync` reports a
|
|
103
|
+
* missing binary that way, with no output at all, so the message was
|
|
104
|
+
* `systemctl --user enable --now failed (exit null).` and then a blank line —
|
|
105
|
+
* which tells someone on a container with no systemd nothing whatsoever. That
|
|
106
|
+
* is also the single likeliest way this command fails on a machine it was never
|
|
107
|
+
* going to work on.
|
|
108
|
+
*/
|
|
109
|
+
export declare function systemctlFailure(args: string[], r: RunResult): string;
|
|
67
110
|
export declare function createSystemdBackend(run: Runner, homeDir?: string, xdgConfigHome?: string): ServiceBackend;
|
|
@@ -59,6 +59,39 @@ export function unitPath(homeDir, xdgConfigHome) {
|
|
|
59
59
|
: (process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config")));
|
|
60
60
|
return path.join(base, "systemd", "user", SYSTEMD_UNIT);
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Unit-file escaping, ported from Local Review's `server/service/systemd.ts`
|
|
64
|
+
* (`pct`, `cEscape`, `cUnescape`, `execWord`, `execWords`) @ cli-contract spec
|
|
65
|
+
* 0.1; local changes: doc comments only. Wave C4 moves these to
|
|
66
|
+
* `reference/service/` and re-syncs.
|
|
67
|
+
*
|
|
68
|
+
* `%` starts a specifier EVERYWHERE in a unit file, so it is doubled; `$`
|
|
69
|
+
* starts a variable in `ExecStart`, so it is doubled there too.
|
|
70
|
+
*/
|
|
71
|
+
const pct = (s) => s.replace(/%/g, "%%");
|
|
72
|
+
const cEscape = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\t/g, "\\t");
|
|
73
|
+
const cUnescape = (s) => s.replace(/\\(.)/g, (_, c) => (c === "n" ? "\n" : c === "t" ? "\t" : c));
|
|
74
|
+
/** One `ExecStart` word: specifiers and variables escaped, quoted when it has to be. */
|
|
75
|
+
export function execWord(a) {
|
|
76
|
+
const s = pct(a).replace(/\$/g, "$$$$");
|
|
77
|
+
return s === "" || /[\s"'\\;]/.test(s) ? `"${cEscape(s)}"` : s;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The other way: the words of an `ExecStart` line.
|
|
81
|
+
*
|
|
82
|
+
* Reads both shapes on purpose. A unit written before #796's quoting is a bare
|
|
83
|
+
* whitespace-separated line, and its owner is exactly the person whose `status`
|
|
84
|
+
* has to keep working — so unquoted words pass through unchanged, and quoted
|
|
85
|
+
* ones are unescaped.
|
|
86
|
+
*/
|
|
87
|
+
export function execWords(line) {
|
|
88
|
+
const out = [];
|
|
89
|
+
for (const m of line.matchAll(/"((?:[^"\\]|\\.)*)"|(\S+)/g)) {
|
|
90
|
+
const w = m[1] !== undefined ? cUnescape(m[1]) : m[2];
|
|
91
|
+
out.push(w.replace(/\$\$/g, "$").replace(/%%/g, "%"));
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
62
95
|
/**
|
|
63
96
|
* Render the unit.
|
|
64
97
|
*
|
|
@@ -78,21 +111,44 @@ export function unitPath(homeDir, xdgConfigHome) {
|
|
|
78
111
|
* "start at login" for a user unit; `multi-user.target` is the system-unit
|
|
79
112
|
* spelling and does nothing here.
|
|
80
113
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
114
|
+
* ## Logs go to a file, not the journal (SPEC §7)
|
|
115
|
+
*
|
|
116
|
+
* `StandardOutput=append:` / `StandardError=append:` name one file, the same
|
|
117
|
+
* one launchd's two keys name. The journal was the obvious choice and the wrong
|
|
118
|
+
* one for three reasons: `journalctl --user` shows nothing at all on a distro
|
|
119
|
+
* whose journal is volatile (the output is gone after a reboot, which is
|
|
120
|
+
* exactly when someone looks); it makes "where are the logs" have a different
|
|
121
|
+
* answer on each platform, so `paddock service logs` would be two
|
|
122
|
+
* implementations; and it rules out Windows, which the contract requires
|
|
123
|
+
* nothing rule out.
|
|
124
|
+
*
|
|
125
|
+
* The directory has to exist first — systemd creates the file, not its parent —
|
|
126
|
+
* which `install` below does.
|
|
127
|
+
*
|
|
128
|
+
* ## Every ExecStart word is quoted
|
|
129
|
+
*
|
|
130
|
+
* `%` starts a specifier anywhere in a unit file and `$` starts a variable in
|
|
131
|
+
* `ExecStart`, so both are escaped; a word containing whitespace, a quote or a
|
|
132
|
+
* backslash is quoted. Paths with a space in them are the common case
|
|
133
|
+
* (`/Users/Ed Spencer/.paddock`), and the old unqualified `join(" ")` wrote a
|
|
134
|
+
* unit systemd then split into the wrong arguments — a service that fails at
|
|
135
|
+
* login having printed nothing.
|
|
84
136
|
*/
|
|
85
137
|
export function renderUnit(spec) {
|
|
86
|
-
const exec = [spec.nodePath, spec.scriptPath, ...spec.args].join(" ");
|
|
87
|
-
return
|
|
138
|
+
const exec = [spec.nodePath, spec.scriptPath, ...spec.args].map(execWord).join(" ");
|
|
139
|
+
return `# Generated by Paddock — \`paddock service install\` rewrites this file.
|
|
140
|
+
# Node: ${spec.nodePath}
|
|
141
|
+
[Unit]
|
|
88
142
|
Description=Paddock — persistent, resumable Claude Code sessions, by project
|
|
89
143
|
Documentation=https://paddock.edspencer.net
|
|
90
144
|
|
|
91
145
|
[Service]
|
|
92
146
|
Type=simple
|
|
93
147
|
ExecStart=${exec}
|
|
94
|
-
WorkingDirectory=${spec.workingDirectory}
|
|
95
|
-
Environment=PATH=${spec.pathEnv}
|
|
148
|
+
WorkingDirectory=${pct(spec.workingDirectory)}
|
|
149
|
+
Environment="PATH=${cEscape(pct(spec.pathEnv))}"
|
|
150
|
+
StandardOutput=append:${pct(spec.logPath)}
|
|
151
|
+
StandardError=append:${pct(spec.logPath)}
|
|
96
152
|
Restart=always
|
|
97
153
|
RestartSec=10
|
|
98
154
|
|
|
@@ -100,6 +156,11 @@ RestartSec=10
|
|
|
100
156
|
WantedBy=default.target
|
|
101
157
|
`;
|
|
102
158
|
}
|
|
159
|
+
/** The log file an installed unit appends to, or undefined if it names none. */
|
|
160
|
+
export function parseUnitLogPath(text) {
|
|
161
|
+
const m = /^StandardOutput=append:(.*)$/m.exec(text);
|
|
162
|
+
return m === null ? undefined : m[1].trim().replace(/%%/g, "%");
|
|
163
|
+
}
|
|
103
164
|
/**
|
|
104
165
|
* Does an installed unit carry `Restart=always` (#872)?
|
|
105
166
|
*
|
|
@@ -117,15 +178,40 @@ export function parseUnitArgv(text) {
|
|
|
117
178
|
const m = /^ExecStart=(.*)$/m.exec(text);
|
|
118
179
|
if (m === null)
|
|
119
180
|
return [];
|
|
120
|
-
return m[1].trim()
|
|
181
|
+
return execWords(m[1].trim());
|
|
121
182
|
}
|
|
122
|
-
/**
|
|
183
|
+
/**
|
|
184
|
+
* What a `systemctl` failure means, in the manager's own words where it has any.
|
|
185
|
+
*
|
|
186
|
+
* The `status === null` branch is the one QA found: `spawnSync` reports a
|
|
187
|
+
* missing binary that way, with no output at all, so the message was
|
|
188
|
+
* `systemctl --user enable --now failed (exit null).` and then a blank line —
|
|
189
|
+
* which tells someone on a container with no systemd nothing whatsoever. That
|
|
190
|
+
* is also the single likeliest way this command fails on a machine it was never
|
|
191
|
+
* going to work on.
|
|
192
|
+
*/
|
|
193
|
+
export function systemctlFailure(args, r) {
|
|
194
|
+
if (r.status === null || r.status === 127) {
|
|
195
|
+
return ("systemctl is not available, so Paddock cannot install a service here.\n" +
|
|
196
|
+
"It needs systemd's per-user instance (`systemctl --user`) — a container " +
|
|
197
|
+
"or a non-systemd distribution has none.\n" +
|
|
198
|
+
"Run `paddock serve` in a terminal, or keep it up with whatever this " +
|
|
199
|
+
"machine uses.");
|
|
200
|
+
}
|
|
201
|
+
if (/Failed to connect to (user scope )?bus|DBUS_SESSION_BUS_ADDRESS|XDG_RUNTIME_DIR/.test(r.stderr)) {
|
|
202
|
+
return (`cannot reach systemd's per-user instance (${r.stderr.trim()}).\n` +
|
|
203
|
+
"Run this from a login session, and check that $XDG_RUNTIME_DIR is set " +
|
|
204
|
+
"(usually /run/user/$(id -u)).");
|
|
205
|
+
}
|
|
206
|
+
return (`systemctl ${args.join(" ")} failed (exit ${String(r.status)}).\n` +
|
|
207
|
+
`${r.stderr || r.stdout}`.trim());
|
|
208
|
+
}
|
|
209
|
+
/** Run a `systemctl` and turn a non-zero exit into something actionable. */
|
|
123
210
|
function systemctlOrThrow(run, args) {
|
|
124
211
|
const r = run("systemctl", args);
|
|
125
212
|
if (r.status === 0)
|
|
126
213
|
return;
|
|
127
|
-
throw new Error(
|
|
128
|
-
`${r.stderr || r.stdout}`.trim());
|
|
214
|
+
throw new Error(systemctlFailure(args, r));
|
|
129
215
|
}
|
|
130
216
|
export function createSystemdBackend(run, homeDir, xdgConfigHome) {
|
|
131
217
|
// Both forwarded, neither defaulted here: `unitPath` owns the precedence, and
|
|
@@ -140,15 +226,19 @@ export function createSystemdBackend(run, homeDir, xdgConfigHome) {
|
|
|
140
226
|
install(spec) {
|
|
141
227
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
142
228
|
fs.mkdirSync(spec.workingDirectory, { recursive: true });
|
|
229
|
+
// systemd creates the log FILE for `append:` but not its directory, and a
|
|
230
|
+
// missing directory fails the unit at start with the server never having
|
|
231
|
+
// run — no output anywhere, because the thing that would have written it
|
|
232
|
+
// is what failed.
|
|
233
|
+
fs.mkdirSync(path.dirname(spec.logPath), { recursive: true });
|
|
143
234
|
fs.writeFileSync(file, renderUnit(spec), "utf8");
|
|
144
235
|
// Without this the manager keeps serving the previous unit from cache and
|
|
145
236
|
// an "upgrade" silently changes nothing.
|
|
146
237
|
run("systemctl", ["--user", "daemon-reload"]);
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
238
|
+
const args = ["--user", "enable", "--now", SYSTEMD_UNIT];
|
|
239
|
+
const enabled = run("systemctl", args);
|
|
240
|
+
if (enabled.status !== 0)
|
|
241
|
+
throw new Error(systemctlFailure(args, enabled));
|
|
152
242
|
},
|
|
153
243
|
uninstall() {
|
|
154
244
|
const existed = fs.existsSync(file);
|
|
@@ -188,21 +278,36 @@ export function createSystemdBackend(run, homeDir, xdgConfigHome) {
|
|
|
188
278
|
const active = run("systemctl", ["--user", "is-active", SYSTEMD_UNIT]);
|
|
189
279
|
const pid = run("systemctl", ["--user", "show", "-p", "MainPID", "--value", SYSTEMD_UNIT]);
|
|
190
280
|
const mainPid = pid.stdout.trim();
|
|
281
|
+
const logPath = parseUnitLogPath(text);
|
|
191
282
|
return {
|
|
192
283
|
registered: true,
|
|
193
284
|
running: active.stdout.trim() === "active",
|
|
194
285
|
...(mainPid !== "" && mainPid !== "0" ? { pid: mainPid } : {}),
|
|
195
286
|
argv: argv.slice(2),
|
|
287
|
+
...(argv[0] !== undefined ? { nodePath: argv[0] } : {}),
|
|
288
|
+
...(argv[1] !== undefined ? { cliPath: argv[1] } : {}),
|
|
289
|
+
...(logPath !== undefined ? { logPath } : {}),
|
|
196
290
|
keepsAlive: unitKeepsAlive(text),
|
|
197
291
|
};
|
|
198
292
|
},
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
293
|
+
/**
|
|
294
|
+
* `loginctl show-user <user> -p Linger` (SPEC §7).
|
|
295
|
+
*
|
|
296
|
+
* Parsed rather than trusted to exit codes: `loginctl` is absent in plenty
|
|
297
|
+
* of containers and exits non-zero for a user logind has no record of, and
|
|
298
|
+
* neither of those is "linger is off". An unparseable answer is reported as
|
|
299
|
+
* unknown, which prints nothing — the alternative is handing a `sudo`
|
|
300
|
+
* command to someone whose machine has no `loginctl` to run it with.
|
|
301
|
+
*/
|
|
302
|
+
linger() {
|
|
203
303
|
const user = os.userInfo().username;
|
|
204
|
-
|
|
205
|
-
|
|
304
|
+
const r = run("loginctl", ["show-user", user, "-p", "Linger"]);
|
|
305
|
+
const m = /Linger=(yes|no)/i.exec(`${r.stdout}${r.stderr}`);
|
|
306
|
+
return {
|
|
307
|
+
applies: true,
|
|
308
|
+
user,
|
|
309
|
+
...(m !== null ? { enabled: m[1].toLowerCase() === "yes" } : {}),
|
|
310
|
+
};
|
|
206
311
|
},
|
|
207
312
|
};
|
|
208
313
|
}
|
|
@@ -371,6 +371,25 @@ export declare class HerdctlService {
|
|
|
371
371
|
* error, returns `false` — the pre-#397 behaviour.
|
|
372
372
|
*/
|
|
373
373
|
isSdkSessionLive(sessionId: string): boolean;
|
|
374
|
+
/**
|
|
375
|
+
* How many scheduler jobs are running right now, read from memory.
|
|
376
|
+
*
|
|
377
|
+
* This exists for `GET /api/busy` (CLI contract SPEC §4), which must read
|
|
378
|
+
* in-memory state and never walk the disk. The number the contract's ROADMAP
|
|
379
|
+
* names is `fleetStatus().counts.runningJobs` — but {@link fleetStatus} is
|
|
380
|
+
* built from `getAgentInfo()`, which re-reads the fleet state FILE on every
|
|
381
|
+
* call, and that re-parse has been this server's single largest CPU cost.
|
|
382
|
+
*
|
|
383
|
+
* The two are the same number. `counts.runningJobs` is
|
|
384
|
+
* `Σ scheduler.getRunningJobCount(agent.qualifiedName)` over the configured
|
|
385
|
+
* agents, and `getTotalRunningJobCount()` is the size of the same scheduler
|
|
386
|
+
* map keyed `<agent>/<schedule>` — one entry per running agent+schedule pair
|
|
387
|
+
* either way. So this reads the authority directly instead of a derived
|
|
388
|
+
* snapshot of it.
|
|
389
|
+
*
|
|
390
|
+
* Null-safe like {@link isSdkSessionLive}: no fleet or no scheduler means 0.
|
|
391
|
+
*/
|
|
392
|
+
runningJobCount(): number;
|
|
374
393
|
fleetStatus(): Promise<FleetStatus>;
|
|
375
394
|
agents(): Promise<AgentInfo[]>;
|
|
376
395
|
/**
|
|
@@ -528,6 +528,32 @@ export class HerdctlService {
|
|
|
528
528
|
return false;
|
|
529
529
|
}
|
|
530
530
|
}
|
|
531
|
+
/**
|
|
532
|
+
* How many scheduler jobs are running right now, read from memory.
|
|
533
|
+
*
|
|
534
|
+
* This exists for `GET /api/busy` (CLI contract SPEC §4), which must read
|
|
535
|
+
* in-memory state and never walk the disk. The number the contract's ROADMAP
|
|
536
|
+
* names is `fleetStatus().counts.runningJobs` — but {@link fleetStatus} is
|
|
537
|
+
* built from `getAgentInfo()`, which re-reads the fleet state FILE on every
|
|
538
|
+
* call, and that re-parse has been this server's single largest CPU cost.
|
|
539
|
+
*
|
|
540
|
+
* The two are the same number. `counts.runningJobs` is
|
|
541
|
+
* `Σ scheduler.getRunningJobCount(agent.qualifiedName)` over the configured
|
|
542
|
+
* agents, and `getTotalRunningJobCount()` is the size of the same scheduler
|
|
543
|
+
* map keyed `<agent>/<schedule>` — one entry per running agent+schedule pair
|
|
544
|
+
* either way. So this reads the authority directly instead of a derived
|
|
545
|
+
* snapshot of it.
|
|
546
|
+
*
|
|
547
|
+
* Null-safe like {@link isSdkSessionLive}: no fleet or no scheduler means 0.
|
|
548
|
+
*/
|
|
549
|
+
runningJobCount() {
|
|
550
|
+
try {
|
|
551
|
+
return this.fleet?.getScheduler()?.getTotalRunningJobCount() ?? 0;
|
|
552
|
+
}
|
|
553
|
+
catch {
|
|
554
|
+
return 0;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
531
557
|
async fleetStatus() {
|
|
532
558
|
return this.manager.getFleetStatus();
|
|
533
559
|
}
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MCP server for external Management API callers (issue #312 M2).
|
|
3
|
+
*
|
|
4
|
+
* Adapts the SAME tool definitions the in-process keeper gets — an
|
|
5
|
+
* {@link InjectedMcpServerDef} produced by `selfMcpServerDefForOps` — onto the
|
|
6
|
+
* official MCP SDK, so an external client sees exactly the toolset a keeper
|
|
7
|
+
* sees, minus whatever its scope hides. Nothing is redefined here: if a tool is
|
|
8
|
+
* added to the self-MCP, it appears over `/mcp` with no further work, which is
|
|
9
|
+
* the "upgrade the server, every client's toolset upgrades" property the epic
|
|
10
|
+
* calls out.
|
|
11
|
+
*
|
|
12
|
+
* ── Why the LOW-LEVEL `Server`, not `McpServer` ────────────────────────────
|
|
13
|
+
* The high-level `McpServer.registerTool` wants Zod schemas and converts them to
|
|
14
|
+
* JSON Schema for the wire. Our tools already carry hand-written JSON Schema
|
|
15
|
+
* (shared with herdctl's injected-MCP bridge), so going through Zod would mean
|
|
16
|
+
* translating JSON Schema → Zod → JSON Schema and hoping the round trip is
|
|
17
|
+
* faithful. Implementing `tools/list` + `tools/call` directly passes our schemas
|
|
18
|
+
* through verbatim and keeps ONE source of truth for the tool surface.
|
|
19
|
+
*/
|
|
1
20
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
21
|
import type { InjectedMcpServerDef } from "@herdctl/core";
|
|
3
22
|
import { type ManagementPrincipal } from "./management-policy.js";
|
|
@@ -5,9 +24,8 @@ import { type ManagementPrincipal } from "./management-policy.js";
|
|
|
5
24
|
export declare const MANAGEMENT_MCP_SERVER_NAME = "paddock";
|
|
6
25
|
/**
|
|
7
26
|
* Package version, advertised to the client so it can report which Paddock it
|
|
8
|
-
* is talking to.
|
|
9
|
-
*
|
|
10
|
-
* so it tracks releases without a build step.
|
|
27
|
+
* is talking to. One resolution, shared with the OpenAPI document and
|
|
28
|
+
* `/api/health` (version.ts), so the three cannot disagree.
|
|
11
29
|
*/
|
|
12
30
|
export declare const MANAGEMENT_MCP_SERVER_VERSION: string;
|
|
13
31
|
/**
|
|
@@ -17,27 +17,18 @@
|
|
|
17
17
|
* faithful. Implementing `tools/list` + `tools/call` directly passes our schemas
|
|
18
18
|
* through verbatim and keeps ONE source of truth for the tool surface.
|
|
19
19
|
*/
|
|
20
|
-
import { createRequire } from "node:module";
|
|
21
20
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
22
21
|
import { ListToolsRequestSchema, CallToolRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
23
22
|
import { ManagementDeniedError } from "./management-policy.js";
|
|
23
|
+
import { PADDOCK_VERSION } from "./version.js";
|
|
24
24
|
/** Server identity advertised in the MCP `initialize` handshake. */
|
|
25
25
|
export const MANAGEMENT_MCP_SERVER_NAME = "paddock";
|
|
26
26
|
/**
|
|
27
27
|
* Package version, advertised to the client so it can report which Paddock it
|
|
28
|
-
* is talking to.
|
|
29
|
-
*
|
|
30
|
-
* so it tracks releases without a build step.
|
|
28
|
+
* is talking to. One resolution, shared with the OpenAPI document and
|
|
29
|
+
* `/api/health` (version.ts), so the three cannot disagree.
|
|
31
30
|
*/
|
|
32
|
-
export const MANAGEMENT_MCP_SERVER_VERSION =
|
|
33
|
-
try {
|
|
34
|
-
const require = createRequire(import.meta.url);
|
|
35
|
-
return require("../package.json").version ?? "0.0.0";
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
return "0.0.0";
|
|
39
|
-
}
|
|
40
|
-
})();
|
|
31
|
+
export const MANAGEMENT_MCP_SERVER_VERSION = PADDOCK_VERSION;
|
|
41
32
|
/**
|
|
42
33
|
* Build an MCP server exposing `def`'s tools to `principal`.
|
|
43
34
|
*
|