@edspencer/paddock 0.79.0 → 0.80.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 +2 -2
- package/packages/server/dist/app.js +18 -14
- package/packages/server/dist/cli/args.d.ts +47 -7
- package/packages/server/dist/cli/args.js +112 -18
- 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/paddock.js +84 -37
- package/packages/server/dist/cli/service/backend.d.ts +48 -4
- package/packages/server/dist/cli/service/index.d.ts +56 -2
- package/packages/server/dist/cli/service/index.js +283 -56
- package/packages/server/dist/cli/service/launchd.d.ts +8 -0
- package/packages/server/dist/cli/service/launchd.js +29 -7
- 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/management-mcp-server.d.ts +21 -3
- package/packages/server/dist/management-mcp-server.js +4 -13
- package/packages/server/dist/routes/meta.js +11 -3
- package/packages/server/dist/service-log.d.ts +29 -0
- package/packages/server/dist/service-log.js +104 -0
- package/packages/server/dist/start.js +11 -0
- package/packages/server/dist/version.d.ts +3 -0
- package/packages/server/dist/version.js +32 -0
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/{DeleteProjectDialog-DE5KhPD_.js → DeleteProjectDialog-DHczLhwQ.js} +1 -1
- package/packages/web/dist/assets/DiscoverPage-BSGSzTEs.js +2 -0
- package/packages/web/dist/assets/{DiscoverView-DCEp5YDh.js → DiscoverView-DtTOpTji.js} +1 -1
- package/packages/web/dist/assets/{InstanceConfigPage-D09mz7nR.js → InstanceConfigPage-BRoBh1C1.js} +1 -1
- package/packages/web/dist/assets/{MarkdownRenderer-CujBOaSt.js → MarkdownRenderer-DJf8IJuk.js} +1 -1
- package/packages/web/dist/assets/{ProjectRedirect-DaOQz7Bx.js → ProjectRedirect-EioguUKW.js} +1 -1
- package/packages/web/dist/assets/{ProjectView-BxwVtKmQ.js → ProjectView-BiKnGfk6.js} +3 -3
- package/packages/web/dist/assets/{ProjectsGrid-BkULnAQF.js → ProjectsGrid-D6kfaIal.js} +1 -1
- package/packages/web/dist/assets/{RootHome-C7PQt4Mc.js → RootHome-DpcyuV6e.js} +1 -1
- package/packages/web/dist/assets/{Surfaces-XlC0TXmU.js → Surfaces-CHM29PhP.js} +1 -1
- package/packages/web/dist/assets/{arc-C1crrky_.js → arc-fiqD05Hu.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR--3BQUIXc.js → architectureDiagram-3BPJPVTR-BuuB2Nkf.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-EisqobqG.js → blockDiagram-GPEHLZMM-DQMBuzfi.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-e6-wiFTa.js → c4Diagram-AAUBKEIU-BKN0UxCH.js} +1 -1
- package/packages/web/dist/assets/channel-B7yh6Su_.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-ToP1Bx9O.js → chunk-2J33WTMH-DW37EupX.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-BD2icQlZ.js → chunk-4BX2VUAB-Dyv7zV_c.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-D803_50z.js → chunk-55IACEB6-DzMR0dOk.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-CiJOKx_U.js → chunk-727SXJPM-BdZ3Sor8.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-BYni6r_j.js → chunk-AQP2D5EJ-B4i7_og0.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-D6QbCpWh.js → chunk-FMBD7UC4-jcTr85it.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-CvJSg6SC.js → chunk-ND2GUHAM-fgMsadqB.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-Cz0BtHbV.js → chunk-QZHKN3VN-bJNHYS1W.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DIlhMPgi.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DIlhMPgi.js +2 -0
- package/packages/web/dist/assets/{core-BaJJFoBn.js → core-Bk4KFrmN.js} +1 -1
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-CzAP4gqt.js → cose-bilkent-S5V4N54A-BIqCSlQy.js} +1 -1
- package/packages/web/dist/assets/{dagre-BM42HDAG-CwIEzm8u.js → dagre-BM42HDAG-6nyhtVQz.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-BEvdaFOg.js → diagram-2AECGRRQ-CjizfipR.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-mk5qWzPB.js → diagram-5GNKFQAL-DlgB5vWb.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-C4lD_Kit.js → diagram-KO2AKTUF-CNC9qanV.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-BmduqHdC.js → diagram-LMA3HP47-DSJ-s9lh.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-CzbvsaAK.js → diagram-OG6HWLK6-C9FoHBDg.js} +1 -1
- package/packages/web/dist/assets/{documentTitle-DvZmF8pw.js → documentTitle-Cj_AY97j.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-B3Nm1nfI.js → erDiagram-TEJ5UH35-z6gku4Yt.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-DTAf1VHv.js → flowDiagram-I6XJVG4X-Dl5c_FgU.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-BOeIF25v.js → ganttDiagram-6RSMTGT7-CIscJ-Dt.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-DR0ETY9X.js → gitGraphDiagram-PVQCEYII-CvttOXM_.js} +1 -1
- package/packages/web/dist/assets/{index-BqXu07yh.js → index-DN82do6Z.js} +2 -2
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-iFsN2k7Z.js → infoDiagram-5YYISTIA-dzWYdDJj.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-D0aKZ4O0.js → ishikawaDiagram-YF4QCWOH-8yX9k4bo.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-Dzt-NowV.js → journeyDiagram-JHISSGLW-BmitzJDR.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-EurYbfWQ.js → kanban-definition-UN3LZRKU-BZmIkfkW.js} +1 -1
- package/packages/web/dist/assets/{linear-DjReSGMv.js → linear-BBLXE4o7.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-CPXZwBWr.js → mermaid.core-mSKff4yu.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-BZKr8aHp.js → mindmap-definition-RKZ34NQL-BWAUa4CP.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-CGIjsXTW.js → pieDiagram-4H26LBE5-VmVdiL4C.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-CMk1ecJ2.js → quadrantDiagram-W4KKPZXB-Dybfv6NU.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-DR2dI24k.js → requirementDiagram-4Y6WPE33-BPnOvB7w.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-BP6UMMTr.js → sankeyDiagram-5OEKKPKP-YXIEmKw7.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-4jrB80y7.js → sequenceDiagram-3UESZ5HK-DintIRUe.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-yAWdzPCD.js → stateDiagram-AJRCARHV-eMsjahWp.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CpAOGzbO.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-MFxU8aFE.js → timeline-definition-PNZ67QCA-as4zRCPn.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-DptEBZ6E.js → vennDiagram-CIIHVFJN-CPzvUkM0.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-Dk-HljGs.js → wardley-L42UT6IY-q6moXj5g.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-DhIz3dBE.js → wardleyDiagram-YWT4CUSO-BsEEb9Gm.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-ytw3T4h8.js → xychartDiagram-2RQKCTM6-DSgZlcZW.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-vaaC-Dji.js +0 -2
- package/packages/web/dist/assets/channel-D4JZjkXm.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-BQVXohi4.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-BQVXohi4.js +0 -2
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-D0dmQsgw.js +0 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edspencer/paddock",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.80.0",
|
|
4
4
|
"description": "Project-first launchpad for Claude Code: server-hosted, persistent, resumable sessions organized by project.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@fastify/swagger-ui": "4.2.0",
|
|
38
38
|
"@fastify/websocket": "10.0.1",
|
|
39
39
|
"@herdctl/chat": "0.8.0",
|
|
40
|
-
"@herdctl/core": "5.34.
|
|
40
|
+
"@herdctl/core": "5.34.1",
|
|
41
41
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
42
42
|
"fastify": "4.28.1",
|
|
43
43
|
"jose": "6.2.3",
|
|
@@ -15,7 +15,6 @@ import fastifyStatic from "@fastify/static";
|
|
|
15
15
|
import fastifyMultipart from "@fastify/multipart";
|
|
16
16
|
import fastifySwagger from "@fastify/swagger";
|
|
17
17
|
import fastifySwaggerUi from "@fastify/swagger-ui";
|
|
18
|
-
import { createRequire } from "node:module";
|
|
19
18
|
import { promises as fs } from "node:fs";
|
|
20
19
|
import path from "node:path";
|
|
21
20
|
import { loadPaddockConfig } from "./config.js";
|
|
@@ -50,17 +49,9 @@ import { TriggerSessionStore } from "./trigger-session.js";
|
|
|
50
49
|
import { PaddockEventBus } from "./event-bus.js";
|
|
51
50
|
import { TriggerService } from "./triggers.js";
|
|
52
51
|
import { buildSwaggerOptions, buildSwaggerUiOptions } from "./openapi.js";
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
const require = createRequire(import.meta.url);
|
|
58
|
-
return require("../package.json").version ?? "0.0.0";
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
return "0.0.0";
|
|
62
|
-
}
|
|
63
|
-
})();
|
|
52
|
+
// One source of truth for "which Paddock is this?" — the OpenAPI document,
|
|
53
|
+
// /api/health and the management MCP handshake all read it (version.ts).
|
|
54
|
+
import { PADDOCK_VERSION } from "./version.js";
|
|
64
55
|
/**
|
|
65
56
|
* True when the (query-stripped) path's last segment carries a file extension —
|
|
66
57
|
* e.g. `/assets/index-ABC.js`, `/favicon.ico`, `/sw.js`. Used by the SPA
|
|
@@ -85,7 +76,20 @@ export async function buildApp(opts = {}) {
|
|
|
85
76
|
// out here. `PADDOCK_QUIET` is set by `cli/paddock.ts` unless `--verbose`.
|
|
86
77
|
installHerdctlLogBridge({ quiet: (process.env.PADDOCK_QUIET ?? "") !== "" });
|
|
87
78
|
const app = Fastify({
|
|
88
|
-
|
|
79
|
+
// **Logs go to stderr, not stdout**, and that is load-bearing rather than
|
|
80
|
+
// tidy. SPEC §3 of the CLI contract requires `serve`'s FIRST stdout line to
|
|
81
|
+
// be `Paddock <version>: <url>` — the line a script reads to find the URL —
|
|
82
|
+
// and boot logging is emitted before the server can know it has bound. With
|
|
83
|
+
// pino on stdout, a machine with no Claude login (every CI runner, every
|
|
84
|
+
// fresh install) put `{"level":40,…,"msg":"no Claude credentials found…"}`
|
|
85
|
+
// there first, so the first line was a JSON log record. The conformance
|
|
86
|
+
// runner caught it; nothing else would have.
|
|
87
|
+
//
|
|
88
|
+
// Nothing loses the logs: Docker and Kubernetes collect both streams, and
|
|
89
|
+
// the service units send stdout and stderr to the same file (SPEC §7). A log
|
|
90
|
+
// shipper configured for stdout ALONE is the one thing that changes, which
|
|
91
|
+
// is why the changeset says so.
|
|
92
|
+
logger: { level: cfg.logLevel, stream: process.stderr },
|
|
89
93
|
});
|
|
90
94
|
// --- safe-by-default bind guard (#435) --------------------------------
|
|
91
95
|
// Fail closed if the resolved bind host is non-loopback AND auth is `none`:
|
|
@@ -313,7 +317,7 @@ export async function buildApp(opts = {}) {
|
|
|
313
317
|
readImage(path.join("icons", "icon-192.png"), "image/png"),
|
|
314
318
|
readImage(path.join("icons", "favicon-32.png"), "image/png"),
|
|
315
319
|
]);
|
|
316
|
-
await app.register(fastifySwagger, buildSwaggerOptions(
|
|
320
|
+
await app.register(fastifySwagger, buildSwaggerOptions(PADDOCK_VERSION, cfg.auth));
|
|
317
321
|
await app.register(fastifySwaggerUi, buildSwaggerUiOptions({
|
|
318
322
|
routePrefix: cfg.openapi.path,
|
|
319
323
|
logo: swaggerLogo,
|
|
@@ -67,9 +67,33 @@ export interface CliOptions {
|
|
|
67
67
|
* supplies. Off by default — see the argument in config.ts.
|
|
68
68
|
*/
|
|
69
69
|
includeEnv?: boolean;
|
|
70
|
+
/** `service logs`: keep printing what gets appended (`-f`). */
|
|
71
|
+
follow?: boolean;
|
|
72
|
+
/** `service logs`: how many lines to print first (`-n`, default 50). */
|
|
73
|
+
lines?: number;
|
|
70
74
|
}
|
|
71
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* A failure with an exit code attached (SPEC §2).
|
|
77
|
+
*
|
|
78
|
+
* Thrown rather than exiting, so parsing stays testable. The code travels with
|
|
79
|
+
* the error because the thing that KNOWS which code applies is whatever threw —
|
|
80
|
+
* `main` cannot tell an unsupported platform (a failure, 1) from a misspelled
|
|
81
|
+
* flag (usage, 2) by looking at a string.
|
|
82
|
+
*/
|
|
72
83
|
export declare class CliError extends Error {
|
|
84
|
+
readonly exitCode: number;
|
|
85
|
+
constructor(message: string, exitCode?: number);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* An unknown command, an unknown flag, or a bad value: exit 2 (SPEC §2).
|
|
89
|
+
*
|
|
90
|
+
* **This is a behaviour change, and a deliberate one.** Every one of these used
|
|
91
|
+
* to exit 1, which is also what a genuine failure exits — so a script could not
|
|
92
|
+
* tell "you typed it wrong" from "it tried and could not". The contract splits
|
|
93
|
+
* them, and a script that tested `$? -eq 1` for a typo now needs `-eq 2`.
|
|
94
|
+
*/
|
|
95
|
+
export declare class UsageError extends CliError {
|
|
96
|
+
constructor(message: string);
|
|
73
97
|
}
|
|
74
98
|
export declare const MIN_NODE_MAJOR = 22;
|
|
75
99
|
/**
|
|
@@ -81,7 +105,7 @@ export declare const MIN_NODE_MAJOR = 22;
|
|
|
81
105
|
* because `parseCommand` dispatches on `argv[0]` alone and only then reads an
|
|
82
106
|
* action, so the two never compete for the same token.
|
|
83
107
|
*/
|
|
84
|
-
export declare const SERVICE_ACTIONS: readonly ["install", "uninstall", "status", "start", "stop", "restart"];
|
|
108
|
+
export declare const SERVICE_ACTIONS: readonly ["install", "uninstall", "status", "start", "stop", "restart", "logs"];
|
|
85
109
|
export type ServiceAction = (typeof SERVICE_ACTIONS)[number];
|
|
86
110
|
/**
|
|
87
111
|
* What `paddock config` can be asked to do (#878).
|
|
@@ -93,8 +117,15 @@ export type ServiceAction = (typeof SERVICE_ACTIONS)[number];
|
|
|
93
117
|
*/
|
|
94
118
|
export declare const CONFIG_ACTIONS: readonly ["show", "eject"];
|
|
95
119
|
export type ConfigAction = (typeof CONFIG_ACTIONS)[number];
|
|
96
|
-
/**
|
|
97
|
-
|
|
120
|
+
/**
|
|
121
|
+
* The leading words {@link parseCommand} recognises. Anything else is a flag.
|
|
122
|
+
*
|
|
123
|
+
* `serve` is the contract's name for running in the foreground (SPEC §1), and
|
|
124
|
+
* `start` is kept as an alias **for good** — the spec says so explicitly for
|
|
125
|
+
* Paddock and Managers, every existing service unit's `ExecStart` names it, and
|
|
126
|
+
* it is in two years of other people's scripts. They parse to the same command.
|
|
127
|
+
*/
|
|
128
|
+
export declare const VERBS: readonly ["serve", "start", "service", "config"];
|
|
98
129
|
/**
|
|
99
130
|
* A parsed invocation: which verb, plus the flags that followed it.
|
|
100
131
|
*
|
|
@@ -103,7 +134,7 @@ export declare const VERBS: readonly ["start", "service", "config"];
|
|
|
103
134
|
* is the whole request. It is `undefined` only when `opts.help` is true.
|
|
104
135
|
*/
|
|
105
136
|
export type Command = {
|
|
106
|
-
verb: "
|
|
137
|
+
verb: "serve";
|
|
107
138
|
opts: CliOptions;
|
|
108
139
|
} | {
|
|
109
140
|
verb: "service";
|
|
@@ -157,6 +188,15 @@ export declare function nodeVersionProblem(nodeVersion: string): string | undefi
|
|
|
157
188
|
* names neither the port nor the flag that fixes it.
|
|
158
189
|
*/
|
|
159
190
|
export declare function explainListenError(err: unknown, host: string, port: string): string;
|
|
160
|
-
|
|
161
|
-
|
|
191
|
+
/**
|
|
192
|
+
* The command list, on its own.
|
|
193
|
+
*
|
|
194
|
+
* Printed after a usage error, because SPEC §2 says the usage follows the
|
|
195
|
+
* message — and because {@link USAGE} is a thousand words of guide that would
|
|
196
|
+
* bury the one line explaining what went wrong. The full text is one
|
|
197
|
+
* `--help` away, and the last line here says so.
|
|
198
|
+
*/
|
|
199
|
+
export declare const USAGE_SYNOPSIS = "Usage\n paddock [serve] [options] run the server in this terminal\n paddock service <action> [options] install|uninstall|status|start|stop|restart|logs\n paddock config <show|eject> [options] what this instance's config resolved to\n paddock -v | --version print the version and exit\n paddock -h | --help the full help";
|
|
200
|
+
export declare const USAGE = "paddock \u2014 run a Paddock instance locally\n\nUsage\n npx @edspencer/paddock [options] run the server (the default)\n paddock serve [options] the same thing, said out loud\n (`paddock start` is a permanent alias)\n paddock service install [options] run it in the background from login\n paddock service uninstall stop it and remove the unit\n paddock service status [--json] installed? running? which version?\n paddock service start start an installed service\n paddock service stop stop it, leave it installed\n paddock service restart stop it and start it again\n paddock service logs [-f] [-n <lines>] the service's log\n (`paddock service --help`)\n paddock config show [--resolved] what this instance's config resolved\n to, and where each value came from\n paddock config eject [--write] freeze that resolution into\n paddock.config.yaml\n (`paddock config --help`)\n\nOptions\n -p, --port <port> HTTP/WS port (default 7233, or $PORT)\n --host <host> Bind address (default 127.0.0.1)\n -d, --data-dir <path> Projects + state (default ~/.paddock, or $PADDOCK_DATA_DIR)\n -o, --open Open the app in your browser once it is listening\n --verbose Show the server's own logs (quiet by default)\n -v, --version Print the Paddock version and exit\n -h, --help Show this help\n\nExit codes\n 0 done; running and answering\n 1 failed\n 2 usage: an unknown command or flag, or a bad value\n 3 not running: installed, but not running or not answering\n 4 not installed: there is no service unit\n\nOpening your own directories\n Where you run this from does not matter \u2014 the instance is decided by its data\n dir alone. Directories are added inside the app, on the Discover screen: it\n reads your Claude Code history, lists the directories you have worked in, and\n links the ones you tick as projects. A new instance opens on it.\n\n Importing a directory does not write into it. No .paddock/, no .chats/, no\n .gitignore edits, no CLAUDE.md \u2014 the project record and the copied transcripts\n both live in the data dir, and the project just points at the path. Your own\n ~/.claude transcripts are copied, never moved or deleted, so your terminal\n `claude` keeps working exactly as before.\n\nCredentials\n Paddock drives Claude Code, so it needs Claude credentials \u2014 and if you\n already use Claude Code on this machine, it uses the login you already have.\n That is a macOS Keychain entry on a Mac, or your ~/.claude/.credentials.json\n elsewhere (symlinked in, never copied). Reading a login writes nothing.\n\n Otherwise: a CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY in the environment,\n or a one-off `CLAUDE_CONFIG_DIR=<data-dir>/claude-home claude login`. With no\n login at all anywhere, run `claude setup-token`. Paddock says at startup when\n it can find none.\n\nPosture profiles\n One key picks how much Paddock shares and how much its agents may do:\n\n profile: balanced # paranoid | balanced | yolo (PADDOCK_PROFILE)\n\n paranoid shares nothing but the login and turns every capability off \u2014 the\n behaviour Paddock had before profiles existed. balanced (the default)\n inherits the CAPABILITIES your CLI already has \u2014 instructions, MCP servers,\n plugins \u2014 and adds the read-only self-management tools, while keeping your\n chat history Paddock's own. yolo turns the rest on: your transcripts, host\n hooks, the write and project tools, schedule mutation, deeper spawning, the\n browser.\n\n A profile only sets defaults \u2014 any single key still overrides it, and an\n individual key in the config file beats PADDOCK_PROFILE in the environment.\n Profiles never touch your port, bind address, auth or model list: yolo does\n NOT open the bind or relax auth, which stay a separate, explicit decision.\n\n What host actually reaches depends on the machine \u2014 a workstation has a real\n ~/.claude, a container usually has none. The profile sets the levers, the\n environment sets the blast radius.\n\n To see what your profile actually expanded to on THIS machine, with the layer\n each value came from: paddock config show --resolved\n\nSharing your Claude Code state\n Apart from that login, Paddock writes nothing outside its data dir by\n default: transcripts go to each project's .chats/, and your ~/.claude is read\n for config only. Each thing it can share is one key in\n <data-dir>/paddock.config.yaml, and each defaults to whatever your profile\n says (see Posture profiles below):\n\n claude:\n transcripts: host # own | host host on yolo only\n credentials: host # own | host host on every profile\n instructions: host # own | host own on paranoid, else host\n hooks: host # own | host host on yolo only\n mcpServers: host # own | host own on paranoid, else host\n\n transcripts: host makes a chat and a `claude --resume` in the same directory\n the same file, live in both directions; deleting such a chat in Paddock\n releases it rather than removing it, because it is your history rather than\n Paddock's copy. credentials: own is the opt-out from sharing the login above.\n\n instructions: host loads your ~/.claude CLAUDE.md, agents/, commands/ and\n plugins/. On from balanced up, so your curated CLAUDE.md does reach Paddock's\n agents; profile: paranoid or instructions: own keeps it out. Each project's\n own CLAUDE.md applies either way.\n\n hooks: host runs the shell commands your ~/.claude/settings.json binds to\n tool use. Off by default \u2014 inheriting someone's hooks is not something to\n discover after the fact. Its other keys (permissions, model, statusline)\n apply either way: under hooks: own Paddock writes its own settings.json\n carrying them with hooks dropped, regenerated at startup.\n\n mcpServers: host attaches the MCP servers declared in your ~/.claude.json \u2014\n the top-level ones, plus a project's own when its directory matches. To give\n this instance a server your machine doesn't have, declare it instead in a\n sibling mcpServers: block of the same file; use env:VAR_NAME anywhere a\n string goes so tokens stay out of it. That keeps a token out of the file, not\n out of `ps`: leave driveMode on its default (session) for a server holding\n one, since batch passes the definition to claude as a command-line argument.\n\nYour data\n Everything lives in one directory \u2014 ~/.paddock unless you pass --data-dir.\n Projects, chat transcripts and settings all persist there between runs. Move\n that directory to move your instance; delete it to start over. Nothing is\n stored anywhere else.\n\nNotes\n Binds loopback with authentication disabled, which is safe for a laptop. To\n expose it on a network, set PADDOCK_AUTH_MODE first \u2014 Paddock refuses to bind\n a routable interface wide open. See AUTH.md.\n\nDocs: https://github.com/edspencer/paddock";
|
|
201
|
+
export declare const SERVICE_USAGE = "paddock service \u2014 keep Paddock running in the background\n\nUsage\n paddock service install [options] register it and start it now\n paddock service uninstall stop it and deregister it\n paddock service status [--json] is it registered, is it running, which\n version, where are the logs\n paddock service start start an installed service that is stopped\n paddock service stop stop it, and leave it installed\n paddock service restart stop it and start it again, re-reading config\n paddock service logs [-f] [-n <n>] the service's log (the last 50 lines)\n\nOptions (logs)\n -f, --follow keep printing what gets appended, until Ctrl-C\n -n, --lines <n> how many lines to print first (default 50)\n\nOptions (install only \u2014 recorded in the generated unit)\n -p, --port <port> HTTP/WS port (default 7233)\n --host <host> Bind address (default 127.0.0.1)\n -d, --data-dir <path> Only if you want an instance SEPARATE from your\n terminal one. Left out of the unit when you omit it\n AND `PADDOCK_DATA_DIR` is unset in this shell, so\n `paddock service` and a bare `paddock` are the same\n ~/.paddock instance reached two ways. If\n `PADDOCK_DATA_DIR` IS set here, that path is recorded\n in the unit \u2014 the service would otherwise point\n somewhere your terminal does not.\n --verbose Record the server's own logs, not just warnings\n\nAt login, not at boot\n This registers a per-USER service \u2014 a launchd LaunchAgent on macOS, a\n `systemd --user` unit on Linux \u2014 so it runs as you, with your own Claude\n login. That is not incidental: on macOS your Claude login is a Keychain item\n that only a logged-in user session can read. A boot-time system daemon has no\n such session and could not use it.\n\n So Paddock starts when you LOG IN, not when the machine boots. After a\n restart that nobody logs into, Paddock is not running. That is the design, not\n a fault.\n\n On Linux, a user service is also stopped when you log out. To keep it up:\n\n loginctl enable-linger $USER\n\nWhere it lives\n macOS ~/Library/LaunchAgents/net.edspencer.paddock.plist\n Linux ~/.config/systemd/user/paddock.service\n\n Both write stdout and stderr to ONE file, <data-dir>/logs/service.log, which\n is what `paddock service logs` reads. Not the journal on Linux: a volatile\n journal has nothing in it after the reboot you are asking about, and one file\n means one answer to \"where are the logs\" on every OS. Paddock rotates it\n itself at 8 MB, keeping three older generations beside it.\n\nExit codes\n 0 it is installed, running and answering\n 3 installed, but not running or not answering\n 4 not installed: there is no unit (status, logs, start, stop, restart)\n 1 something failed\n 2 usage: an unknown action or flag, or a bad value\n\nInstalled from npx?\n `service install` refuses. An npx cache path is hash-keyed and npm may prune\n it, so the unit would work until it silently didn't, at some future login.\n Install properly first:\n\n npm i -g @edspencer/paddock && paddock service install\n\nA note on access\n Paddock binds loopback with authentication off, which is right for a laptop.\n A service is up for as long as you are logged in rather than as long as a\n terminal tab, so that window is longer \u2014 but it is not wider: any local\n process that could reach the port could already read the same Claude login as\n you. Set PADDOCK_AUTH_MODE if you want a credential on it anyway.\n\nDocs: https://github.com/edspencer/paddock";
|
|
162
202
|
export declare const CONFIG_USAGE = "paddock config \u2014 what this instance's configuration actually resolved to\n\nUsage\n paddock config show the decisions: your profile, the keys your\n config file sets, the variables your\n environment sets\n paddock config show --resolved EVERY effective value, and which layer it\n came from\n paddock config show --json the same report as JSON, long values in\n full (sensitive ones still hidden)\n paddock config eject what freezing that resolution into\n paddock.config.yaml would write \u2014 a\n preview; writes nothing\n paddock config eject --write actually write it\n\nOptions\n -d, --data-dir <path> Which instance to inspect (default ~/.paddock, or\n $PADDOCK_DATA_DIR) \u2014 the same rule `paddock start`\n uses, so the two always read the same instance\n --resolved show: every field, not just the ones someone set\n --json show: the whole report as JSON (implies --resolved's\n scope)\n --show-sensitive show: print the values of fields marked sensitive\n --write eject: apply the plan. Without it, eject only prints\n --include-env eject: also freeze values an environment variable\n currently supplies (see below \u2014 off by default)\n -h, --help Show this help\n\nWhere a value can come from\n Config resolves in four layers, and each row of --resolved names the one that\n won:\n\n default Paddock's built-in default.\n profile (<name>) Your posture profile. The twelve levers a profile governs\n have no code default of their own any more \u2014 the profile\n IS their default \u2014 so this is a distinct answer from\n \"default\", and switching profile would change it.\n file A key in paddock.config.yaml.\n env <NAME> An environment variable, which beats the file for the\n same key.\n\n One wrinkle worth knowing, and the command shows it plainly: an individual key\n in the FILE beats PADDOCK_PROFILE in the environment. Specific beats general \u2014\n PADDOCK_PROFILE speaks for the levers you did not mention.\n\nWhy show, and not just write it all into the file\n `show` is computed by the loader the server boots with, so it cannot drift\n from what the process would actually do. A materialized file can: it stops\n inheriting improved defaults, says nothing about the variables your container\n sets on top, and no longer describes the whole surface the day a lever is\n added. That is why the thin file is the default and `show` is the answer to\n \"what am I actually running?\".\n\n `eject` is there for when you want it written down anyway \u2014 frozen, in the\n file, reviewable in git. It is a real tradeoff rather than a better `show`,\n so it prints what it would cost you and writes nothing until you pass --write.\n\nWhat eject writes, and what it leaves alone\n It writes the posture levers your profile expanded to, plus the behavioural\n settings the Config screen already round-trips through this file. It does NOT\n write:\n\n the bindings port, host, data dir, projects root, state dir, web dist.\n These resolve to absolute paths on THIS machine, and the\n data dir resolves to the directory holding the file \u2014 so\n ejecting them makes the file unportable and unmountable.\n sensitive keys transcription.endpoint can carry credentials in its URL.\n Bulk-writing a secret-shaped value to disk should not be\n one keystroke away; write that key by hand.\n env-owned keys by default. An environment variable BEATS the file, so\n writing its value in changes nothing today and everything\n the day the variable goes away \u2014 a deferred, silent change\n of provenance. eject names each one it skipped instead.\n --include-env writes them, which is the right flag if you\n are deliberately migrating from env vars to a file.\n\n It always writes `profile:` as well, even though every key that profile\n governs is now explicit. That line is what a lever added in a FUTURE release\n falls back to: without it, a new capability toggle would resolve against the\n built-in default profile rather than the posture you actually ejected from.\n\n Your comments and any keys Paddock does not manage survive \u2014 the file is\n round-tripped, not regenerated \u2014 and the write is atomic.\n\nReading nothing, writing nothing\n `show` resolves config and prints it. It starts no server, contacts nothing,\n and writes no file \u2014 including the data dir itself, which it will report as\n missing rather than create. `eject` writes exactly one file, and only with\n --write.\n\n Values are printed to a terminal that often ends up pasted into an issue, so\n fields marked sensitive are shown as (hidden). None of them is a secret \u2014\n Paddock keeps secrets out of this surface entirely, so there is no API key or\n JWT signing material here to hide or reveal.\n\nDocs: https://github.com/edspencer/paddock";
|
|
@@ -23,8 +23,34 @@
|
|
|
23
23
|
* the need for the guard entirely, so there is no condition left to get wrong on
|
|
24
24
|
* the next shim, platform or package manager. `paddock.ts` now always runs.
|
|
25
25
|
*/
|
|
26
|
-
|
|
26
|
+
import { EXIT } from "./exit-codes.js";
|
|
27
|
+
/**
|
|
28
|
+
* A failure with an exit code attached (SPEC §2).
|
|
29
|
+
*
|
|
30
|
+
* Thrown rather than exiting, so parsing stays testable. The code travels with
|
|
31
|
+
* the error because the thing that KNOWS which code applies is whatever threw —
|
|
32
|
+
* `main` cannot tell an unsupported platform (a failure, 1) from a misspelled
|
|
33
|
+
* flag (usage, 2) by looking at a string.
|
|
34
|
+
*/
|
|
27
35
|
export class CliError extends Error {
|
|
36
|
+
exitCode;
|
|
37
|
+
constructor(message, exitCode = EXIT.failure) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.exitCode = exitCode;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* An unknown command, an unknown flag, or a bad value: exit 2 (SPEC §2).
|
|
44
|
+
*
|
|
45
|
+
* **This is a behaviour change, and a deliberate one.** Every one of these used
|
|
46
|
+
* to exit 1, which is also what a genuine failure exits — so a script could not
|
|
47
|
+
* tell "you typed it wrong" from "it tried and could not". The contract splits
|
|
48
|
+
* them, and a script that tested `$? -eq 1` for a typo now needs `-eq 2`.
|
|
49
|
+
*/
|
|
50
|
+
export class UsageError extends CliError {
|
|
51
|
+
constructor(message) {
|
|
52
|
+
super(message, EXIT.usage);
|
|
53
|
+
}
|
|
28
54
|
}
|
|
29
55
|
export const MIN_NODE_MAJOR = 22;
|
|
30
56
|
/**
|
|
@@ -43,6 +69,7 @@ export const SERVICE_ACTIONS = [
|
|
|
43
69
|
"start",
|
|
44
70
|
"stop",
|
|
45
71
|
"restart",
|
|
72
|
+
"logs",
|
|
46
73
|
];
|
|
47
74
|
/**
|
|
48
75
|
* What `paddock config` can be asked to do (#878).
|
|
@@ -53,8 +80,15 @@ export const SERVICE_ACTIONS = [
|
|
|
53
80
|
* the command that makes it unnecessary.
|
|
54
81
|
*/
|
|
55
82
|
export const CONFIG_ACTIONS = ["show", "eject"];
|
|
56
|
-
/**
|
|
57
|
-
|
|
83
|
+
/**
|
|
84
|
+
* The leading words {@link parseCommand} recognises. Anything else is a flag.
|
|
85
|
+
*
|
|
86
|
+
* `serve` is the contract's name for running in the foreground (SPEC §1), and
|
|
87
|
+
* `start` is kept as an alias **for good** — the spec says so explicitly for
|
|
88
|
+
* Paddock and Managers, every existing service unit's `ExecStart` names it, and
|
|
89
|
+
* it is in two years of other people's scripts. They parse to the same command.
|
|
90
|
+
*/
|
|
91
|
+
export const VERBS = ["serve", "start", "service", "config"];
|
|
58
92
|
/**
|
|
59
93
|
* Split an action off a verb's argv, then parse what follows as flags.
|
|
60
94
|
*
|
|
@@ -67,7 +101,7 @@ function parseAction(verb, actions, rest) {
|
|
|
67
101
|
let action;
|
|
68
102
|
if (head !== undefined && !head.startsWith("-")) {
|
|
69
103
|
if (!actions.includes(head)) {
|
|
70
|
-
throw new
|
|
104
|
+
throw new UsageError(`unknown ${verb} action: ${head}\n` +
|
|
71
105
|
`Expected one of: ${actions.join(", ")}.\n` +
|
|
72
106
|
`Run \`paddock ${verb} --help\` for usage.`);
|
|
73
107
|
}
|
|
@@ -76,7 +110,7 @@ function parseAction(verb, actions, rest) {
|
|
|
76
110
|
const opts = parseArgs(action === undefined ? rest : rest.slice(1));
|
|
77
111
|
// `--help` wins over a missing action: asking for usage is not a usage error.
|
|
78
112
|
if (action === undefined && !opts.help) {
|
|
79
|
-
throw new
|
|
113
|
+
throw new UsageError(`\`paddock ${verb}\` needs an action: ${actions.join(", ")}.\n` +
|
|
80
114
|
`Run \`paddock ${verb} --help\` for usage.`);
|
|
81
115
|
}
|
|
82
116
|
return { action, opts };
|
|
@@ -115,9 +149,10 @@ export function parseCommand(argv) {
|
|
|
115
149
|
if (first === "config") {
|
|
116
150
|
return { verb: "config", ...parseAction("config", CONFIG_ACTIONS, rest) };
|
|
117
151
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
152
|
+
// `serve` and `start` are one command: see VERBS.
|
|
153
|
+
if (first === "serve" || first === "start")
|
|
154
|
+
return { verb: "serve", opts: parseArgs(rest) };
|
|
155
|
+
return { verb: "serve", opts: parseArgs(argv) };
|
|
121
156
|
}
|
|
122
157
|
export function parseArgs(argv) {
|
|
123
158
|
const opts = {
|
|
@@ -131,7 +166,7 @@ export function parseArgs(argv) {
|
|
|
131
166
|
const next = () => {
|
|
132
167
|
const v = argv[++i];
|
|
133
168
|
if (v === undefined)
|
|
134
|
-
throw new
|
|
169
|
+
throw new UsageError(`${arg} needs a value`);
|
|
135
170
|
return v;
|
|
136
171
|
};
|
|
137
172
|
switch (arg) {
|
|
@@ -148,7 +183,7 @@ export function parseArgs(argv) {
|
|
|
148
183
|
// into a hard error.
|
|
149
184
|
const v = next();
|
|
150
185
|
if (v !== "" && (!/^\d+$/.test(v) || Number(v) < 1 || Number(v) > 65535)) {
|
|
151
|
-
throw new
|
|
186
|
+
throw new UsageError(`${arg} needs a number between 1 and 65535, got: ${v}`);
|
|
152
187
|
}
|
|
153
188
|
opts.port = v;
|
|
154
189
|
break;
|
|
@@ -185,6 +220,21 @@ export function parseArgs(argv) {
|
|
|
185
220
|
case "--include-env":
|
|
186
221
|
opts.includeEnv = true;
|
|
187
222
|
break;
|
|
223
|
+
// `service logs` flags, the names `tail`, `journalctl` and every other
|
|
224
|
+
// log reader uses — this is not the place to be original.
|
|
225
|
+
case "-f":
|
|
226
|
+
case "--follow":
|
|
227
|
+
opts.follow = true;
|
|
228
|
+
break;
|
|
229
|
+
case "-n":
|
|
230
|
+
case "--lines": {
|
|
231
|
+
const v = next();
|
|
232
|
+
if (!/^\d+$/.test(v) || Number(v) < 1) {
|
|
233
|
+
throw new UsageError(`${arg} needs a positive number of lines, got: ${v}`);
|
|
234
|
+
}
|
|
235
|
+
opts.lines = Number(v);
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
188
238
|
case "-h":
|
|
189
239
|
case "--help":
|
|
190
240
|
opts.help = true;
|
|
@@ -194,7 +244,7 @@ export function parseArgs(argv) {
|
|
|
194
244
|
opts.version = true;
|
|
195
245
|
break;
|
|
196
246
|
default:
|
|
197
|
-
throw new
|
|
247
|
+
throw new UsageError(`unknown option: ${arg}`);
|
|
198
248
|
}
|
|
199
249
|
}
|
|
200
250
|
return opts;
|
|
@@ -232,13 +282,33 @@ export function explainListenError(err, host, port) {
|
|
|
232
282
|
}
|
|
233
283
|
return String(err?.message ?? err);
|
|
234
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* The command list, on its own.
|
|
287
|
+
*
|
|
288
|
+
* Printed after a usage error, because SPEC §2 says the usage follows the
|
|
289
|
+
* message — and because {@link USAGE} is a thousand words of guide that would
|
|
290
|
+
* bury the one line explaining what went wrong. The full text is one
|
|
291
|
+
* `--help` away, and the last line here says so.
|
|
292
|
+
*/
|
|
293
|
+
export const USAGE_SYNOPSIS = `Usage
|
|
294
|
+
paddock [serve] [options] run the server in this terminal
|
|
295
|
+
paddock service <action> [options] install|uninstall|status|start|stop|restart|logs
|
|
296
|
+
paddock config <show|eject> [options] what this instance's config resolved to
|
|
297
|
+
paddock -v | --version print the version and exit
|
|
298
|
+
paddock -h | --help the full help`;
|
|
235
299
|
export const USAGE = `paddock — run a Paddock instance locally
|
|
236
300
|
|
|
237
301
|
Usage
|
|
238
|
-
npx @edspencer/paddock [options]
|
|
239
|
-
paddock
|
|
240
|
-
|
|
241
|
-
|
|
302
|
+
npx @edspencer/paddock [options] run the server (the default)
|
|
303
|
+
paddock serve [options] the same thing, said out loud
|
|
304
|
+
(\`paddock start\` is a permanent alias)
|
|
305
|
+
paddock service install [options] run it in the background from login
|
|
306
|
+
paddock service uninstall stop it and remove the unit
|
|
307
|
+
paddock service status [--json] installed? running? which version?
|
|
308
|
+
paddock service start start an installed service
|
|
309
|
+
paddock service stop stop it, leave it installed
|
|
310
|
+
paddock service restart stop it and start it again
|
|
311
|
+
paddock service logs [-f] [-n <lines>] the service's log
|
|
242
312
|
(\`paddock service --help\`)
|
|
243
313
|
paddock config show [--resolved] what this instance's config resolved
|
|
244
314
|
to, and where each value came from
|
|
@@ -255,6 +325,13 @@ Options
|
|
|
255
325
|
-v, --version Print the Paddock version and exit
|
|
256
326
|
-h, --help Show this help
|
|
257
327
|
|
|
328
|
+
Exit codes
|
|
329
|
+
0 done; running and answering
|
|
330
|
+
1 failed
|
|
331
|
+
2 usage: an unknown command or flag, or a bad value
|
|
332
|
+
3 not running: installed, but not running or not answering
|
|
333
|
+
4 not installed: there is no service unit
|
|
334
|
+
|
|
258
335
|
Opening your own directories
|
|
259
336
|
Where you run this from does not matter — the instance is decided by its data
|
|
260
337
|
dir alone. Directories are added inside the app, on the Discover screen: it
|
|
@@ -358,10 +435,16 @@ export const SERVICE_USAGE = `paddock service — keep Paddock running in the ba
|
|
|
358
435
|
Usage
|
|
359
436
|
paddock service install [options] register it and start it now
|
|
360
437
|
paddock service uninstall stop it and deregister it
|
|
361
|
-
paddock service status
|
|
438
|
+
paddock service status [--json] is it registered, is it running, which
|
|
439
|
+
version, where are the logs
|
|
362
440
|
paddock service start start an installed service that is stopped
|
|
363
441
|
paddock service stop stop it, and leave it installed
|
|
364
442
|
paddock service restart stop it and start it again, re-reading config
|
|
443
|
+
paddock service logs [-f] [-n <n>] the service's log (the last 50 lines)
|
|
444
|
+
|
|
445
|
+
Options (logs)
|
|
446
|
+
-f, --follow keep printing what gets appended, until Ctrl-C
|
|
447
|
+
-n, --lines <n> how many lines to print first (default 50)
|
|
365
448
|
|
|
366
449
|
Options (install only — recorded in the generated unit)
|
|
367
450
|
-p, --port <port> HTTP/WS port (default 7233)
|
|
@@ -393,9 +476,20 @@ At login, not at boot
|
|
|
393
476
|
|
|
394
477
|
Where it lives
|
|
395
478
|
macOS ~/Library/LaunchAgents/net.edspencer.paddock.plist
|
|
396
|
-
logs in <data-dir>/service/
|
|
397
479
|
Linux ~/.config/systemd/user/paddock.service
|
|
398
|
-
|
|
480
|
+
|
|
481
|
+
Both write stdout and stderr to ONE file, <data-dir>/logs/service.log, which
|
|
482
|
+
is what \`paddock service logs\` reads. Not the journal on Linux: a volatile
|
|
483
|
+
journal has nothing in it after the reboot you are asking about, and one file
|
|
484
|
+
means one answer to "where are the logs" on every OS. Paddock rotates it
|
|
485
|
+
itself at 8 MB, keeping three older generations beside it.
|
|
486
|
+
|
|
487
|
+
Exit codes
|
|
488
|
+
0 it is installed, running and answering
|
|
489
|
+
3 installed, but not running or not answering
|
|
490
|
+
4 not installed: there is no unit (status, logs, start, stop, restart)
|
|
491
|
+
1 something failed
|
|
492
|
+
2 usage: an unknown action or flag, or a bad value
|
|
399
493
|
|
|
400
494
|
Installed from npx?
|
|
401
495
|
\`service install\` refuses. An npx cache path is hash-keyed and npm may prune
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CLI contract's exit codes (SPEC §2), as constants.
|
|
3
|
+
*
|
|
4
|
+
* From edspencer/cli-contract SPEC.md §2 @ 0.1. Written here rather than copied
|
|
5
|
+
* from `reference/exit-codes.ts` because the reference module did not exist yet
|
|
6
|
+
* when Paddock aligned; wave C4 re-syncs this file to it and adds the usual
|
|
7
|
+
* provenance header. The numbers, and what each means, are the contract.
|
|
8
|
+
*
|
|
9
|
+
* | 0 | done; running and answering; up to date; not busy
|
|
10
|
+
* | 1 | failed
|
|
11
|
+
* | 2 | usage: an unknown command or flag, a bad value
|
|
12
|
+
* | 3 | not running: installed but not running or not answering
|
|
13
|
+
* | 4 | not installed: there is no service unit
|
|
14
|
+
* | 5 | this install cannot update itself (`update`, not yet built)
|
|
15
|
+
* | 6 | the update failed and was rolled back (`update`, not yet built)
|
|
16
|
+
* | 7 | the update failed and could not be rolled back
|
|
17
|
+
* | 75 | busy: refused, and why was printed (`busy`, not yet built)
|
|
18
|
+
*
|
|
19
|
+
* Codes 3 and 4 are what `systemctl status` and LSB init scripts use for "not
|
|
20
|
+
* running" and "no such service", so a script that already branches on them
|
|
21
|
+
* keeps working. 75 is `EX_TEMPFAIL` from `sysexits.h`, which is also the code
|
|
22
|
+
* that makes Watchtower's pre-update hook skip an update.
|
|
23
|
+
*
|
|
24
|
+
* The three `update` codes are declared now and unused until wave C2. They are
|
|
25
|
+
* here because the contract's whole point is that the numbers mean the same
|
|
26
|
+
* thing in every tool: adding them later, from a different file, is how two
|
|
27
|
+
* tools end up disagreeing about what 5 means.
|
|
28
|
+
*/
|
|
29
|
+
export declare const EXIT: {
|
|
30
|
+
/** Done. Running and answering. Up to date. Not busy. */
|
|
31
|
+
readonly ok: 0;
|
|
32
|
+
/** Failed — the catch-all. */
|
|
33
|
+
readonly failure: 1;
|
|
34
|
+
/** Usage: an unknown command or flag, or a bad value. Usage follows the message. */
|
|
35
|
+
readonly usage: 2;
|
|
36
|
+
/** Not running: installed, but not running or not answering. */
|
|
37
|
+
readonly notRunning: 3;
|
|
38
|
+
/** Not installed: there is no service unit. */
|
|
39
|
+
readonly notInstalled: 4;
|
|
40
|
+
/** This install cannot update itself; the right command was printed. */
|
|
41
|
+
readonly cannotSelfUpdate: 5;
|
|
42
|
+
/** The update failed and was rolled back; the old version is running again. */
|
|
43
|
+
readonly rolledBack: 6;
|
|
44
|
+
/** The update failed and could not be rolled back; a person is needed. */
|
|
45
|
+
readonly needsHuman: 7;
|
|
46
|
+
/** Busy: refused, and why was printed. Try later, or use `--force`. */
|
|
47
|
+
readonly busy: 75;
|
|
48
|
+
};
|
|
49
|
+
export type ExitCode = (typeof EXIT)[keyof typeof EXIT];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CLI contract's exit codes (SPEC §2), as constants.
|
|
3
|
+
*
|
|
4
|
+
* From edspencer/cli-contract SPEC.md §2 @ 0.1. Written here rather than copied
|
|
5
|
+
* from `reference/exit-codes.ts` because the reference module did not exist yet
|
|
6
|
+
* when Paddock aligned; wave C4 re-syncs this file to it and adds the usual
|
|
7
|
+
* provenance header. The numbers, and what each means, are the contract.
|
|
8
|
+
*
|
|
9
|
+
* | 0 | done; running and answering; up to date; not busy
|
|
10
|
+
* | 1 | failed
|
|
11
|
+
* | 2 | usage: an unknown command or flag, a bad value
|
|
12
|
+
* | 3 | not running: installed but not running or not answering
|
|
13
|
+
* | 4 | not installed: there is no service unit
|
|
14
|
+
* | 5 | this install cannot update itself (`update`, not yet built)
|
|
15
|
+
* | 6 | the update failed and was rolled back (`update`, not yet built)
|
|
16
|
+
* | 7 | the update failed and could not be rolled back
|
|
17
|
+
* | 75 | busy: refused, and why was printed (`busy`, not yet built)
|
|
18
|
+
*
|
|
19
|
+
* Codes 3 and 4 are what `systemctl status` and LSB init scripts use for "not
|
|
20
|
+
* running" and "no such service", so a script that already branches on them
|
|
21
|
+
* keeps working. 75 is `EX_TEMPFAIL` from `sysexits.h`, which is also the code
|
|
22
|
+
* that makes Watchtower's pre-update hook skip an update.
|
|
23
|
+
*
|
|
24
|
+
* The three `update` codes are declared now and unused until wave C2. They are
|
|
25
|
+
* here because the contract's whole point is that the numbers mean the same
|
|
26
|
+
* thing in every tool: adding them later, from a different file, is how two
|
|
27
|
+
* tools end up disagreeing about what 5 means.
|
|
28
|
+
*/
|
|
29
|
+
export const EXIT = {
|
|
30
|
+
/** Done. Running and answering. Up to date. Not busy. */
|
|
31
|
+
ok: 0,
|
|
32
|
+
/** Failed — the catch-all. */
|
|
33
|
+
failure: 1,
|
|
34
|
+
/** Usage: an unknown command or flag, or a bad value. Usage follows the message. */
|
|
35
|
+
usage: 2,
|
|
36
|
+
/** Not running: installed, but not running or not answering. */
|
|
37
|
+
notRunning: 3,
|
|
38
|
+
/** Not installed: there is no service unit. */
|
|
39
|
+
notInstalled: 4,
|
|
40
|
+
/** This install cannot update itself; the right command was printed. */
|
|
41
|
+
cannotSelfUpdate: 5,
|
|
42
|
+
/** The update failed and was rolled back; the old version is running again. */
|
|
43
|
+
rolledBack: 6,
|
|
44
|
+
/** The update failed and could not be rolled back; a person is needed. */
|
|
45
|
+
needsHuman: 7,
|
|
46
|
+
/** Busy: refused, and why was printed. Try later, or use `--force`. */
|
|
47
|
+
busy: 75,
|
|
48
|
+
};
|