@edspencer/paddock 0.82.1 → 0.83.1
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/cli/args.d.ts +29 -4
- package/packages/server/dist/cli/args.js +143 -5
- package/packages/server/dist/cli/busy.js +1 -1
- package/packages/server/dist/cli/doctor.d.ts +60 -0
- package/packages/server/dist/cli/doctor.js +580 -0
- package/packages/server/dist/cli/exit-codes.js +1 -1
- package/packages/server/dist/cli/output.js +1 -1
- package/packages/server/dist/cli/paddock.js +28 -3
- package/packages/server/dist/cli/service/index.d.ts +34 -1
- package/packages/server/dist/cli/service/index.js +142 -16
- package/packages/server/dist/cli/service/launchd.js +12 -2
- package/packages/server/dist/cli/service/logs.js +1 -1
- package/packages/server/dist/cli/service/manager.js +1 -1
- package/packages/server/dist/cli/service/path-env.js +1 -1
- package/packages/server/dist/cli/service/ready.d.ts +16 -23
- package/packages/server/dist/cli/service/ready.js +42 -0
- package/packages/server/dist/cli/service/systemd.js +16 -3
- package/packages/server/dist/cli/update/health.js +1 -1
- package/packages/server/dist/cli/update/install-method.js +1 -1
- package/packages/server/dist/cli/update/lock.js +1 -1
- package/packages/server/dist/cli/update/package-manager.js +1 -1
- package/packages/server/dist/cli/update/registry.js +1 -1
- package/packages/server/dist/cli/update/semver.js +1 -1
- package/packages/server/dist/cli/update/update-check.js +1 -1
- package/packages/server/dist/cli/update/update.js +1 -1
- package/packages/server/dist/runtime-file.d.ts +1 -1
- package/packages/server/dist/runtime-file.js +9 -13
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/{DeleteProjectDialog-CgHpQctt.js → DeleteProjectDialog-BPwSkY3s.js} +1 -1
- package/packages/web/dist/assets/DiscoverPage-DVoF9Vil.js +2 -0
- package/packages/web/dist/assets/{DiscoverView-BKqI0I1E.js → DiscoverView-Bz4Rxg-V.js} +1 -1
- package/packages/web/dist/assets/{InstanceConfigPage-DL3raCK1.js → InstanceConfigPage-CXHem0Ab.js} +1 -1
- package/packages/web/dist/assets/{MarkdownRenderer-DcjEuf3s.js → MarkdownRenderer-2vrOdDh7.js} +1 -1
- package/packages/web/dist/assets/{ProjectRedirect-DerkA-nE.js → ProjectRedirect-V77rycxm.js} +1 -1
- package/packages/web/dist/assets/{ProjectView-NwfC3_w7.js → ProjectView-C1qr6nlt.js} +3 -3
- package/packages/web/dist/assets/{ProjectsGrid-BeORa0UH.js → ProjectsGrid-Jq1LRFcs.js} +1 -1
- package/packages/web/dist/assets/{RootHome-BYJs8qJ6.js → RootHome-H2k4p0ry.js} +1 -1
- package/packages/web/dist/assets/{Surfaces-w-kEaAYU.js → Surfaces-By65IjL4.js} +1 -1
- package/packages/web/dist/assets/{arc-Bv56hasM.js → arc-BzdIzLZ7.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-BHEknx9i.js → architectureDiagram-3BPJPVTR-DT9cGrnU.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-DtxTMrPX.js → blockDiagram-GPEHLZMM-BjD-1DXr.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-DRpEYOlM.js → c4Diagram-AAUBKEIU-DV5q5E08.js} +1 -1
- package/packages/web/dist/assets/channel-D0zKU6oW.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-S4s7GCg7.js → chunk-2J33WTMH-mzG-wWrp.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-BkWVPLf1.js → chunk-4BX2VUAB-BJijOYsf.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-4-FQp01F.js → chunk-55IACEB6-CSHJ3zhe.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-BeyDWdIb.js → chunk-727SXJPM-BfdGsNJG.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-ZSxKy745.js → chunk-AQP2D5EJ-2OJN-ttV.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-Bq8uV9j-.js → chunk-FMBD7UC4-bvjojDsP.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-BOFsRyHk.js → chunk-ND2GUHAM-DnDJhgvR.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-uZ6oNc4m.js → chunk-QZHKN3VN-Bw6DAHLR.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-bF2OmpN6.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-bF2OmpN6.js +2 -0
- package/packages/web/dist/assets/{core-CYuJAM2U.js → core-BKN_gaN-.js} +1 -1
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-DIzkVSFb.js → cose-bilkent-S5V4N54A-BUfjSTaX.js} +1 -1
- package/packages/web/dist/assets/{dagre-BM42HDAG-CHR6jJBv.js → dagre-BM42HDAG-LwxKA-3H.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-BAFXCH8Q.js → diagram-2AECGRRQ-DHS0Xgf9.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-BJ5t5gbz.js → diagram-5GNKFQAL-CNDLt1m9.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-D9Xt0IVg.js → diagram-KO2AKTUF-D7eVz-kU.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-Bq38u6sy.js → diagram-LMA3HP47-Cc0shYXj.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-CMQVqkpi.js → diagram-OG6HWLK6-CdiThnZF.js} +1 -1
- package/packages/web/dist/assets/{documentTitle-M1lrfXRU.js → documentTitle-BgvYvKAe.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-C63M1g9b.js → erDiagram-TEJ5UH35--YKwrR0M.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-D3j8ACN7.js → flowDiagram-I6XJVG4X-BuWFXtpn.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-B1Fd1A2Y.js → ganttDiagram-6RSMTGT7-F6CLJBJ1.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-CEhZhDdR.js → gitGraphDiagram-PVQCEYII-BXrk9SVF.js} +1 -1
- package/packages/web/dist/assets/{index-1E9yHciS.js → index-2MWb3aF0.js} +2 -2
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-Ctquaew6.js → infoDiagram-5YYISTIA-D9iTq2aK.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-DV4GvA1R.js → ishikawaDiagram-YF4QCWOH-CE2vjaAw.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-C_Cy9waA.js → journeyDiagram-JHISSGLW-CyuASyD1.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-CGr9ctEf.js → kanban-definition-UN3LZRKU-C-JcQkTl.js} +1 -1
- package/packages/web/dist/assets/{linear-D3U8NOkt.js → linear-vd9dAgq3.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-BiArdWKV.js → mermaid.core-C-uiHBIT.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-CZIpzj8Z.js → mindmap-definition-RKZ34NQL-CQQKVCUB.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-1MaeS3rt.js → pieDiagram-4H26LBE5-CB92bTxC.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-B5pgQ54e.js → quadrantDiagram-W4KKPZXB-Cu5Ux2WT.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-BNnxrpGr.js → requirementDiagram-4Y6WPE33-FJxTktFV.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-DsxVH-hM.js → sankeyDiagram-5OEKKPKP-Bkxk5fIB.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-Ke9LcFZI.js → sequenceDiagram-3UESZ5HK-gw8EPzuw.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-UJzmflND.js → stateDiagram-AJRCARHV-CIHq9Q4N.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-Bp8j_o9f.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-BRjNALVI.js → timeline-definition-PNZ67QCA-CI_99nZc.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-ClMAspxz.js → vennDiagram-CIIHVFJN-D5e169vs.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-UPv4JEmz.js → wardley-L42UT6IY-C-EFyR0g.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-BgYJbdEx.js → wardleyDiagram-YWT4CUSO-Cp6Fld6d.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-Bfk5HjGx.js → xychartDiagram-2RQKCTM6-D0iq_qGw.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-CG6cf-0i.js +0 -2
- package/packages/web/dist/assets/channel-CdIiAqY2.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DCCUHba0.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DCCUHba0.js +0 -2
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-DgUc3HkP.js +0 -2
package/package.json
CHANGED
|
@@ -112,6 +112,25 @@ export interface CliOptions {
|
|
|
112
112
|
* `--port 9000` service comes back on 7233.
|
|
113
113
|
*/
|
|
114
114
|
refresh?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* `doctor --offline`: skip the one check that asks the npm registry
|
|
117
|
+
* (SPEC §1). Every other check is local.
|
|
118
|
+
*/
|
|
119
|
+
offline?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* `service install --dry-run`: print the unit it would write, and write
|
|
122
|
+
* nothing — not the unit, not a folder (SPEC §1).
|
|
123
|
+
*/
|
|
124
|
+
dryRun?: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* `service install --from-checkout`: install a unit that runs THIS copy even
|
|
127
|
+
* though it is a git checkout (SPEC §1). Without it, `install` refuses: a unit
|
|
128
|
+
* pointing into a working tree breaks the next time it is rebuilt, switched
|
|
129
|
+
* to another branch or moved.
|
|
130
|
+
*/
|
|
131
|
+
fromCheckout?: boolean;
|
|
132
|
+
/** `serve --quiet` / `-q`: print the `Paddock <version>: <url>` line and nothing else (SPEC §3). */
|
|
133
|
+
quiet?: boolean;
|
|
115
134
|
}
|
|
116
135
|
/**
|
|
117
136
|
* A failure with an exit code attached (SPEC §2).
|
|
@@ -166,7 +185,7 @@ export type ConfigAction = (typeof CONFIG_ACTIONS)[number];
|
|
|
166
185
|
* Paddock and Managers, every existing service unit's `ExecStart` names it, and
|
|
167
186
|
* it is in two years of other people's scripts. They parse to the same command.
|
|
168
187
|
*/
|
|
169
|
-
export declare const VERBS: readonly ["serve", "start", "service", "config", "busy", "update", "upgrade"];
|
|
188
|
+
export declare const VERBS: readonly ["serve", "start", "service", "config", "busy", "update", "upgrade", "doctor"];
|
|
170
189
|
/**
|
|
171
190
|
* A parsed invocation: which verb, plus the flags that followed it.
|
|
172
191
|
*
|
|
@@ -200,6 +219,11 @@ export type Command = {
|
|
|
200
219
|
| {
|
|
201
220
|
verb: "update";
|
|
202
221
|
opts: CliOptions;
|
|
222
|
+
}
|
|
223
|
+
/** `doctor` (SPEC §1): the checks, with a fix for each failure. */
|
|
224
|
+
| {
|
|
225
|
+
verb: "doctor";
|
|
226
|
+
opts: CliOptions;
|
|
203
227
|
};
|
|
204
228
|
export declare function parseCommand(argv: string[]): Command;
|
|
205
229
|
export declare function parseArgs(argv: string[]): CliOptions;
|
|
@@ -226,9 +250,9 @@ export declare function explainListenError(err: unknown, host: string, port: str
|
|
|
226
250
|
* bury the one line explaining what went wrong. The full text is one
|
|
227
251
|
* `--help` away, and the last line here says so.
|
|
228
252
|
*/
|
|
229
|
-
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 busy [--json] is the running instance busy?\n paddock update [--check] [--to <v>] install a newer Paddock\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";
|
|
230
|
-
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 [--force] stop it, leave it installed\n paddock service restart [--force] stop it and start it again\n paddock service logs [-f] [-n <lines>] the service's log\n (`paddock service --help`)\n paddock busy [--json] is it busy? exit 0 no, 75 yes, 3\n nothing answering\n (`paddock busy --help`)\n paddock update [--check] [--to <v>] install a newer Paddock and restart\n the service\n (`paddock update --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; up to date; not busy\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; nothing answers\n 4 not installed: there is no service unit\n 5 this install cannot update itself; the right command was printed (update)\n 6 the update failed and was rolled back; the old version is back (update)\n 7 the update failed and could not be rolled back; a person is needed\n 75 busy: refused, and why was printed. Try later, or use --force\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";
|
|
231
|
-
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 install --refresh re-write the unit, keeping its settings\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 [--force] stop it, and leave it installed\n paddock service restart [--force] 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 (stop, restart)\n --force stop it even though it is busy\n\nOptions (install)\n --refresh Re-render the unit from the settings the EXISTING one\n carries \u2014 its port, host, data dir and verbosity \u2014\n
|
|
253
|
+
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 busy [--json] is the running instance busy?\n paddock update [--check] [--to <v>] install a newer Paddock\n paddock doctor [--json] [--offline] checks, with a fix for each problem\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";
|
|
254
|
+
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 (--dry-run prints the unit instead)\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 [--force] stop it, leave it installed\n paddock service restart [--force] stop it and start it again\n paddock service logs [-f] [-n <lines>] the service's log\n (`paddock service --help`)\n paddock busy [--json] is it busy? exit 0 no, 75 yes, 3\n nothing answering\n (`paddock busy --help`)\n paddock update [--check] [--to <v>] install a newer Paddock and restart\n the service\n (`paddock update --help`)\n paddock doctor [--json] [--offline] check this install, with a fix for\n each problem\n (`paddock doctor --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 -q, --quiet Print only the first line, `Paddock <version>: <url>`\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; up to date; not busy\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; nothing answers\n 4 not installed: there is no service unit\n 5 this install cannot update itself; the right command was printed (update)\n 6 the update failed and was rolled back; the old version is back (update)\n 7 the update failed and could not be rolled back; a person is needed\n 75 busy: refused, and why was printed. Try later, or use --force\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";
|
|
255
|
+
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 install --refresh re-write the unit, keeping its settings\n paddock service install --dry-run print the unit it would write; write nothing\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 [--force] stop it, and leave it installed\n paddock service restart [--force] 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 (stop, restart)\n --force stop it even though it is busy\n\nOptions (install, uninstall, start, stop, restart, status)\n --json One JSON document on stdout: ok, action, and the\n service's status afterwards (the same fields as\n `status --json`), plus error on a failure. All\n prose goes to stderr. The exit code is unchanged\n\nOptions (install)\n --dry-run Print the unit (and where it would go) and write\n nothing \u2014 not the unit, not a folder\n --from-checkout Install from a git checkout anyway. Without it,\n install refuses: a unit that runs a working tree\n breaks when you rebuild, switch branch or move it\n --refresh Re-render the unit from the settings the EXISTING one\n carries \u2014 its port, host, data dir and verbosity \u2014\n and never from PADDOCK_DATA_DIR in this shell.\n This is what `paddock update` runs, through the new\n version's CLI, so the unit is written in the format\n that version writes. Exit 4 with nothing installed:\n there is no unit to refresh, and doing a first\n install with defaults instead is how a --port 9000\n service comes back on 7233.\n\n Running install again is safe: it keeps the port, host, data dir and\n verbosity the installed unit records, unless you give new ones, and\n restarts the running service onto the new unit.\n\nBusy\n `stop` and `restart` ask the running instance whether it is busy first, and\n refuse with exit 75 if it is \u2014 a chat turn in flight, a background task, a\n scheduled job. `paddock busy` asks the same question on its own.\n\n Being refused is the useful case: a restart kills a chat turn mid-sentence,\n and the turn cannot be resumed from where it was. Wait, or --force.\n\n `uninstall` does not ask. Removing the service is an explicit act, and\n someone uninstalling it has already decided.\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 (status), or done\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 75 busy, and --force was not given (stop, restart)\n\nInstalled from npx, or running from a checkout?\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 A checkout is yours to rebuild, switch and move, and the unit would follow\n it. Install properly first:\n\n npm i -g @edspencer/paddock && paddock service install\n\n (--from-checkout installs from the checkout anyway, for working on Paddock.)\n\n It also refuses when something that isn't Paddock holds the port: pick\n another with --port.\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";
|
|
232
256
|
export declare const BUSY_USAGE = "paddock busy \u2014 is the running instance in the middle of something?\n\nUsage\n paddock busy ask, and say so in a sentence\n paddock busy --json the same answer as one JSON document\n paddock busy -d <path> ask a particular instance\n\nExit codes \u2014 this command is FOR its exit code\n 0 not busy. Nothing a restart would cut short.\n 75 busy. What is running was printed.\n 3 nothing answered. No instance is running, so there is nothing to wait for.\n 1 something answered, but not as Paddock's busy check.\n\nOptions\n -d, --data-dir <path> Which instance to ask (default ~/.paddock, or\n $PADDOCK_DATA_DIR) \u2014 the same rule `paddock serve`\n uses, so the two always mean the same instance\n --json One JSON document on stdout: running, busy, url,\n version, counts. Prose goes to stderr\n -h, --help Show this help\n\nWhat counts as busy\n Only work a restart would LOSE or cut short:\n\n chat a chat turn running \u2014 a human turn, a fired trigger, a cron\n fire. A turn is a live claude subprocess writing a transcript,\n and it cannot be resumed from where it was interrupted.\n background a background task a live session is still holding after its\n turn ended: a background shell, a sub-agent, a Monitor.\n job a scheduled job herdctl is running.\n\n Not busy: an idle session (it resumes), an open browser tab (it reconnects),\n a pending curation sweep (it re-runs from its watermark).\n\nWhere to run it from\n From a terminal. A Paddock chat that runs this is itself one of the chat turns\n it counts, so it will always see itself as busy \u2014 and `service stop --force`\n from inside a chat stops the service that is running the chat, which does not\n come back with an explanation. That is why the refusal says so.\n\nFor scripts, containers and Watchtower\n `busy` is there for callers with no service to ask. 75 is EX_TEMPFAIL, which\n is the code Watchtower's pre-update hook reads as \"skip this one\":\n\n labels:\n com.centurylinklabs.watchtower.lifecycle.pre-update: paddock busy\n\n An Ansible task does the same thing with a retry. Nothing here contacts the\n network or the npm registry, and the question is answered from memory \u2014 it is\n cheap enough to ask in a loop.\n\nHow it finds the instance\n `<data-dir>/runtime.json`, written 0600 when the server starts listening and\n removed when it stops. There is no fallback to a default port: guessing one\n would ask whatever else happened to be listening there, and get an answer.\n\n A Paddock from before this feature shipped writes no such file, so it reads as\n not running (exit 3). Upgrade it and restart it once.\n\nDocs: https://github.com/edspencer/paddock";
|
|
233
257
|
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";
|
|
234
258
|
/**
|
|
@@ -241,3 +265,4 @@ export declare const CONFIG_USAGE = "paddock config \u2014 what this instance's
|
|
|
241
265
|
*/
|
|
242
266
|
export declare const UPDATE_SYNOPSIS = "Usage\n paddock update [--yes] install the latest release\n paddock update --check [--json] what a new version would be; changes nothing\n paddock update --to <version|tag> a particular version, or a dist-tag\n paddock update --force skip the busy check, and nothing else\n paddock update --help the full help";
|
|
243
267
|
export declare const UPDATE_USAGE = "paddock update \u2014 install a newer Paddock, and put the service back up\n\nUsage\n paddock update update to the latest release, asking first\n paddock update --check what a new version would be; changes nothing\n paddock update --yes do not ask (required when there is no terminal)\n paddock update --to 0.79.1 a particular version \u2014 the only way to go BACK\n paddock update --to next a dist-tag\n paddock update --json one JSON document on stdout\n\nOptions\n --check Ask the registry, report, and change nothing. Exits 0\n whether or not there is an update \u2014 scripts read\n `updateAvailable` from --json\n --to <version|tag> Install this version or dist-tag instead of latest.\n Required to move to a LOWER version: an update never\n goes backwards by accident\n --yes, -y Skip the question. Required when stdin is not a\n terminal, where a command that would have asked exits\n 2 rather than guessing\n --force Skip the busy check, and nothing else \u2014 the health\n check and the rollback still run\n --json One JSON document on stdout; all prose on stderr\n -h, --help Show this help\n\nWhat it does, in order\n 1 takes <data-dir>/update.lock, so two updates cannot interleave\n 2 works out how this copy was installed (see \"Installs that cannot update\n themselves\" below)\n 3 asks the registry for the target version and its `engines.node`\n 4 compares, and stops here with --check\n 5 checks the global folder is writable, and prints a sudo line if not.\n It never runs sudo itself\n 6 asks the running instance whether it is BUSY \u2014 before anything is stopped\n 7 asks you, unless --yes\n 8 fetches both the new and the current version into the npm cache, while\n the old version is still serving, so a rollback has its package to hand\n 9 stops the service\n 10 installs with the package manager this copy came from\n 11 hands over to the NEW CLI, which re-writes the service unit\n (`service install --refresh`) and starts it\n 12 waits for /api/health to report the version it just installed\n 13 and if any of 10\u201312 fails, puts the old version back\n\nExit codes \u2014 this command is FOR its exit code\n 0 updated, or already up to date, or --check reported\n 1 something failed, and nothing had been stopped yet\n 2 usage \u2014 including \"there is no terminal to ask, so --yes is required\"\n 5 this install cannot update itself. The right command was printed\n 6 the new version did not start; the old one is running again\n 7 the new version did not start and the old one could not be restored.\n The commands to run by hand were printed\n 75 busy: a chat turn, a background task or a scheduled job is running.\n Wait, or --force\n\nInstalls that cannot update themselves (exit 5)\n Paddock reads where it is running from, and five layouts are not ours to\n replace. Each prints the command that WOULD work:\n\n a container image docker compose pull && docker compose up -d\n a managed install whatever PADDOCK_UPDATE_HINT says\n npx / pnpm dlx npx @edspencer/paddock@latest already runs the latest\n a checkout git pull && npm install && npm run build\n a project dependency update it in that project\n a yarn classic global yarn global add @edspencer/paddock\n\n A global npm, pnpm, bun or volta install updates in place.\n\nWhere it runs from\n From a terminal, never from inside the service. An updater the service\n started \u2014 a Paddock chat, say \u2014 is in the service's own process group, so\n stopping the service kills the updater half way through step 9. The busy\n check refuses first and says so; --force from inside a chat would stop the\n service running that chat, which then never gets to report what happened.\n\nAsking the registry\n Only `update`, `update --check` and `doctor` ever contact the npm\n registry, and only when you run them. There is no background check and no\n check at startup; the server never asks. What the last check found is cached\n in <data-dir>/update-check.json, which is what `paddock service status`\n repeats \u2014 it never asks itself.\n\n The registry is `npm_config_registry`, else `npm config get registry`,\n else https://registry.npmjs.org.\n\nDocs: https://github.com/edspencer/paddock";
|
|
268
|
+
export declare const DOCTOR_USAGE = "paddock doctor \u2014 check that Paddock can run here, with a fix for each problem\n\nUsage\n paddock doctor run the checks, one line each\n paddock doctor --json the same checks as one JSON document\n paddock doctor --offline don't ask the npm registry for a newer version\n\nOptions\n --json One JSON document on stdout: ok, version, checks (each\n with an id, a status, a message and, for a warning or\n a failure, a fix). The update notice goes to stderr\n --offline Skip the update check, the only one that uses the\n network\n -d, --data-dir <path> Which instance to check (default: the service's, else\n $PADDOCK_DATA_DIR, else ~/.paddock)\n -h, --help Show this help\n\nWhat it checks\n node-engines this Node satisfies the package's engines\n install-method how Paddock was installed, and whether `paddock update`\n can update it\n unit the service: installed, running and answering, its Node\n and CLI still there, the same install as this CLI\n running-version the running Paddock is the installed version\n port the port is free, or held by Paddock\n linger on Linux, whether the service keeps running after you log out\n data-home the data dir is writable\n config paddock.config.yaml and the environment load, as `serve`\n would load them\n projects the projects folder is writable\n claude-login a Claude login Paddock can use (which kind; never its value)\n claude-cli a `claude` on PATH, which the sweeper always runs, and\n chats and triggers do on driveMode: batch\n update a newer version on the registry (skipped with --offline)\n\n Each line starts \u2713 (fine), ! (a warning), \u2717 (a failure) or - (doesn't apply\n here), and every warning and failure has a fix: line under it.\n\nExit codes\n 0 nothing failed (warnings don't count)\n 1 a check failed\n 2 usage: an unknown flag, or a bad value\n\nAsking the registry\n The update check is the only one that leaves this machine, and only because\n you ran it. Like `paddock update --check`, it writes what it found to\n <data-dir>/update-check.json, which `paddock service status` repeats. It\n writes nothing else, and never creates the data dir.\n\nDocs: https://github.com/edspencer/paddock";
|
|
@@ -88,7 +88,7 @@ export const CONFIG_ACTIONS = ["show", "eject"];
|
|
|
88
88
|
* Paddock and Managers, every existing service unit's `ExecStart` names it, and
|
|
89
89
|
* it is in two years of other people's scripts. They parse to the same command.
|
|
90
90
|
*/
|
|
91
|
-
export const VERBS = ["serve", "start", "service", "config", "busy", "update", "upgrade"];
|
|
91
|
+
export const VERBS = ["serve", "start", "service", "config", "busy", "update", "upgrade", "doctor"];
|
|
92
92
|
/**
|
|
93
93
|
* Split an action off a verb's argv, then parse what follows as flags.
|
|
94
94
|
*
|
|
@@ -181,6 +181,47 @@ const SCOPED_FLAGS = [
|
|
|
181
181
|
where: ["update"],
|
|
182
182
|
why: "`update` is the only command that asks a question.",
|
|
183
183
|
},
|
|
184
|
+
{
|
|
185
|
+
flag: "--json",
|
|
186
|
+
read: (o) => o.json === true,
|
|
187
|
+
where: [
|
|
188
|
+
"service status",
|
|
189
|
+
"service install",
|
|
190
|
+
"service uninstall",
|
|
191
|
+
"service start",
|
|
192
|
+
"service stop",
|
|
193
|
+
"service restart",
|
|
194
|
+
"busy",
|
|
195
|
+
"update",
|
|
196
|
+
"doctor",
|
|
197
|
+
"config show",
|
|
198
|
+
],
|
|
199
|
+
why: "It prints one JSON document on stdout, and those are the commands that have one.",
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
flag: "--dry-run",
|
|
203
|
+
read: (o) => o.dryRun === true,
|
|
204
|
+
where: ["service install"],
|
|
205
|
+
why: "It prints the unit `install` would write, and writes nothing.",
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
flag: "--from-checkout",
|
|
209
|
+
read: (o) => o.fromCheckout === true,
|
|
210
|
+
where: ["service install"],
|
|
211
|
+
why: "It lets `install` write a unit that runs a git checkout.",
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
flag: "--quiet",
|
|
215
|
+
read: (o) => o.quiet === true,
|
|
216
|
+
where: ["serve"],
|
|
217
|
+
why: "It keeps `serve` to its one `Paddock <version>: <url>` line.",
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
flag: "--offline",
|
|
221
|
+
read: (o) => o.offline === true,
|
|
222
|
+
where: ["doctor"],
|
|
223
|
+
why: "It skips doctor's one check that asks the npm registry.",
|
|
224
|
+
},
|
|
184
225
|
{
|
|
185
226
|
flag: "--refresh",
|
|
186
227
|
read: (o) => o.refresh === true,
|
|
@@ -224,6 +265,9 @@ function dispatch(argv) {
|
|
|
224
265
|
// `update`, with `upgrade` accepted and undocumented (SPEC §1).
|
|
225
266
|
if (first === "update" || first === "upgrade")
|
|
226
267
|
return { verb: "update", opts: parseArgs(rest) };
|
|
268
|
+
// `doctor` (SPEC §1).
|
|
269
|
+
if (first === "doctor")
|
|
270
|
+
return { verb: "doctor", opts: parseArgs(rest) };
|
|
227
271
|
// `serve` and `start` are one command: see VERBS.
|
|
228
272
|
if (first === "serve" || first === "start")
|
|
229
273
|
return { verb: "serve", opts: parseArgs(rest) };
|
|
@@ -320,6 +364,22 @@ export function parseArgs(argv) {
|
|
|
320
364
|
case "--refresh":
|
|
321
365
|
opts.refresh = true;
|
|
322
366
|
break;
|
|
367
|
+
// `service install` (SPEC §1).
|
|
368
|
+
case "--dry-run":
|
|
369
|
+
opts.dryRun = true;
|
|
370
|
+
break;
|
|
371
|
+
case "--from-checkout":
|
|
372
|
+
opts.fromCheckout = true;
|
|
373
|
+
break;
|
|
374
|
+
// `serve --quiet` (SPEC §3).
|
|
375
|
+
case "-q":
|
|
376
|
+
case "--quiet":
|
|
377
|
+
opts.quiet = true;
|
|
378
|
+
break;
|
|
379
|
+
// `doctor --offline` (SPEC §1).
|
|
380
|
+
case "--offline":
|
|
381
|
+
opts.offline = true;
|
|
382
|
+
break;
|
|
323
383
|
// `service logs` flags, the names `tail`, `journalctl` and every other
|
|
324
384
|
// log reader uses — this is not the place to be original.
|
|
325
385
|
case "-f":
|
|
@@ -395,6 +455,7 @@ export const USAGE_SYNOPSIS = `Usage
|
|
|
395
455
|
paddock service <action> [options] install|uninstall|status|start|stop|restart|logs
|
|
396
456
|
paddock busy [--json] is the running instance busy?
|
|
397
457
|
paddock update [--check] [--to <v>] install a newer Paddock
|
|
458
|
+
paddock doctor [--json] [--offline] checks, with a fix for each problem
|
|
398
459
|
paddock config <show|eject> [options] what this instance's config resolved to
|
|
399
460
|
paddock -v | --version print the version and exit
|
|
400
461
|
paddock -h | --help the full help`;
|
|
@@ -405,6 +466,7 @@ Usage
|
|
|
405
466
|
paddock serve [options] the same thing, said out loud
|
|
406
467
|
(\`paddock start\` is a permanent alias)
|
|
407
468
|
paddock service install [options] run it in the background from login
|
|
469
|
+
(--dry-run prints the unit instead)
|
|
408
470
|
paddock service uninstall stop it and remove the unit
|
|
409
471
|
paddock service status [--json] installed? running? which version?
|
|
410
472
|
paddock service start start an installed service
|
|
@@ -418,6 +480,9 @@ Usage
|
|
|
418
480
|
paddock update [--check] [--to <v>] install a newer Paddock and restart
|
|
419
481
|
the service
|
|
420
482
|
(\`paddock update --help\`)
|
|
483
|
+
paddock doctor [--json] [--offline] check this install, with a fix for
|
|
484
|
+
each problem
|
|
485
|
+
(\`paddock doctor --help\`)
|
|
421
486
|
paddock config show [--resolved] what this instance's config resolved
|
|
422
487
|
to, and where each value came from
|
|
423
488
|
paddock config eject [--write] freeze that resolution into
|
|
@@ -429,6 +494,7 @@ Options
|
|
|
429
494
|
--host <host> Bind address (default 127.0.0.1)
|
|
430
495
|
-d, --data-dir <path> Projects + state (default ~/.paddock, or $PADDOCK_DATA_DIR)
|
|
431
496
|
-o, --open Open the app in your browser once it is listening
|
|
497
|
+
-q, --quiet Print only the first line, \`Paddock <version>: <url>\`
|
|
432
498
|
--verbose Show the server's own logs (quiet by default)
|
|
433
499
|
-v, --version Print the Paddock version and exit
|
|
434
500
|
-h, --help Show this help
|
|
@@ -547,6 +613,7 @@ export const SERVICE_USAGE = `paddock service — keep Paddock running in the ba
|
|
|
547
613
|
Usage
|
|
548
614
|
paddock service install [options] register it and start it now
|
|
549
615
|
paddock service install --refresh re-write the unit, keeping its settings
|
|
616
|
+
paddock service install --dry-run print the unit it would write; write nothing
|
|
550
617
|
paddock service uninstall stop it and deregister it
|
|
551
618
|
paddock service status [--json] is it registered, is it running, which
|
|
552
619
|
version, where are the logs
|
|
@@ -562,10 +629,21 @@ Options (logs)
|
|
|
562
629
|
Options (stop, restart)
|
|
563
630
|
--force stop it even though it is busy
|
|
564
631
|
|
|
632
|
+
Options (install, uninstall, start, stop, restart, status)
|
|
633
|
+
--json One JSON document on stdout: ok, action, and the
|
|
634
|
+
service's status afterwards (the same fields as
|
|
635
|
+
\`status --json\`), plus error on a failure. All
|
|
636
|
+
prose goes to stderr. The exit code is unchanged
|
|
637
|
+
|
|
565
638
|
Options (install)
|
|
639
|
+
--dry-run Print the unit (and where it would go) and write
|
|
640
|
+
nothing — not the unit, not a folder
|
|
641
|
+
--from-checkout Install from a git checkout anyway. Without it,
|
|
642
|
+
install refuses: a unit that runs a working tree
|
|
643
|
+
breaks when you rebuild, switch branch or move it
|
|
566
644
|
--refresh Re-render the unit from the settings the EXISTING one
|
|
567
645
|
carries — its port, host, data dir and verbosity —
|
|
568
|
-
|
|
646
|
+
and never from PADDOCK_DATA_DIR in this shell.
|
|
569
647
|
This is what \`paddock update\` runs, through the new
|
|
570
648
|
version's CLI, so the unit is written in the format
|
|
571
649
|
that version writes. Exit 4 with nothing installed:
|
|
@@ -573,6 +651,10 @@ Options (install)
|
|
|
573
651
|
install with defaults instead is how a --port 9000
|
|
574
652
|
service comes back on 7233.
|
|
575
653
|
|
|
654
|
+
Running install again is safe: it keeps the port, host, data dir and
|
|
655
|
+
verbosity the installed unit records, unless you give new ones, and
|
|
656
|
+
restarts the running service onto the new unit.
|
|
657
|
+
|
|
576
658
|
Busy
|
|
577
659
|
\`stop\` and \`restart\` ask the running instance whether it is busy first, and
|
|
578
660
|
refuse with exit 75 if it is — a chat turn in flight, a background task, a
|
|
@@ -623,20 +705,26 @@ Where it lives
|
|
|
623
705
|
itself at 8 MB, keeping three older generations beside it.
|
|
624
706
|
|
|
625
707
|
Exit codes
|
|
626
|
-
0 it is installed, running and answering
|
|
708
|
+
0 it is installed, running and answering (status), or done
|
|
627
709
|
3 installed, but not running or not answering
|
|
628
710
|
4 not installed: there is no unit (status, logs, start, stop, restart)
|
|
629
711
|
1 something failed
|
|
630
712
|
2 usage: an unknown action or flag, or a bad value
|
|
631
713
|
75 busy, and --force was not given (stop, restart)
|
|
632
714
|
|
|
633
|
-
Installed from npx?
|
|
715
|
+
Installed from npx, or running from a checkout?
|
|
634
716
|
\`service install\` refuses. An npx cache path is hash-keyed and npm may prune
|
|
635
717
|
it, so the unit would work until it silently didn't, at some future login.
|
|
636
|
-
|
|
718
|
+
A checkout is yours to rebuild, switch and move, and the unit would follow
|
|
719
|
+
it. Install properly first:
|
|
637
720
|
|
|
638
721
|
npm i -g @edspencer/paddock && paddock service install
|
|
639
722
|
|
|
723
|
+
(--from-checkout installs from the checkout anyway, for working on Paddock.)
|
|
724
|
+
|
|
725
|
+
It also refuses when something that isn't Paddock holds the port: pick
|
|
726
|
+
another with --port.
|
|
727
|
+
|
|
640
728
|
A note on access
|
|
641
729
|
Paddock binds loopback with authentication off, which is right for a laptop.
|
|
642
730
|
A service is up for as long as you are logged in rather than as long as a
|
|
@@ -901,3 +989,53 @@ Asking the registry
|
|
|
901
989
|
else https://registry.npmjs.org.
|
|
902
990
|
|
|
903
991
|
Docs: https://github.com/edspencer/paddock`;
|
|
992
|
+
export const DOCTOR_USAGE = `paddock doctor — check that Paddock can run here, with a fix for each problem
|
|
993
|
+
|
|
994
|
+
Usage
|
|
995
|
+
paddock doctor run the checks, one line each
|
|
996
|
+
paddock doctor --json the same checks as one JSON document
|
|
997
|
+
paddock doctor --offline don't ask the npm registry for a newer version
|
|
998
|
+
|
|
999
|
+
Options
|
|
1000
|
+
--json One JSON document on stdout: ok, version, checks (each
|
|
1001
|
+
with an id, a status, a message and, for a warning or
|
|
1002
|
+
a failure, a fix). The update notice goes to stderr
|
|
1003
|
+
--offline Skip the update check, the only one that uses the
|
|
1004
|
+
network
|
|
1005
|
+
-d, --data-dir <path> Which instance to check (default: the service's, else
|
|
1006
|
+
$PADDOCK_DATA_DIR, else ~/.paddock)
|
|
1007
|
+
-h, --help Show this help
|
|
1008
|
+
|
|
1009
|
+
What it checks
|
|
1010
|
+
node-engines this Node satisfies the package's engines
|
|
1011
|
+
install-method how Paddock was installed, and whether \`paddock update\`
|
|
1012
|
+
can update it
|
|
1013
|
+
unit the service: installed, running and answering, its Node
|
|
1014
|
+
and CLI still there, the same install as this CLI
|
|
1015
|
+
running-version the running Paddock is the installed version
|
|
1016
|
+
port the port is free, or held by Paddock
|
|
1017
|
+
linger on Linux, whether the service keeps running after you log out
|
|
1018
|
+
data-home the data dir is writable
|
|
1019
|
+
config paddock.config.yaml and the environment load, as \`serve\`
|
|
1020
|
+
would load them
|
|
1021
|
+
projects the projects folder is writable
|
|
1022
|
+
claude-login a Claude login Paddock can use (which kind; never its value)
|
|
1023
|
+
claude-cli a \`claude\` on PATH, which the sweeper always runs, and
|
|
1024
|
+
chats and triggers do on driveMode: batch
|
|
1025
|
+
update a newer version on the registry (skipped with --offline)
|
|
1026
|
+
|
|
1027
|
+
Each line starts ✓ (fine), ! (a warning), ✗ (a failure) or - (doesn't apply
|
|
1028
|
+
here), and every warning and failure has a fix: line under it.
|
|
1029
|
+
|
|
1030
|
+
Exit codes
|
|
1031
|
+
0 nothing failed (warnings don't count)
|
|
1032
|
+
1 a check failed
|
|
1033
|
+
2 usage: an unknown flag, or a bad value
|
|
1034
|
+
|
|
1035
|
+
Asking the registry
|
|
1036
|
+
The update check is the only one that leaves this machine, and only because
|
|
1037
|
+
you ran it. Like \`paddock update --check\`, it writes what it found to
|
|
1038
|
+
<data-dir>/update-check.json, which \`paddock service status\` repeats. It
|
|
1039
|
+
writes nothing else, and never creates the data dir.
|
|
1040
|
+
|
|
1041
|
+
Docs: https://github.com/edspencer/paddock`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// From edspencer/cli-contract reference/busy-client.ts @ 0
|
|
1
|
+
// From edspencer/cli-contract reference/busy-client.ts @ 1.0; local changes: `.ts` import specifiers → `.js` and
|
|
2
2
|
// `../runtime-file.js` (the writer lives server-side, next to the boot path that writes the file); and `hint` widened
|
|
3
3
|
// from a string to `string | ((result) => string | undefined)`, with {@link runBusyCommand} printing it too — that is
|
|
4
4
|
// what carries Paddock's keeper-chat trap (ROADMAP "Busy, per tool"), where the line to print depends on WHICH counts
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type CliOptions } from "./args.js";
|
|
2
|
+
import { type ServiceContext } from "./service/index.js";
|
|
3
|
+
export type CheckStatus = "pass" | "warn" | "fail" | "skip";
|
|
4
|
+
/** One line of the report, and one entry of `--json`'s `checks` (`schemas/doctor.schema.json`). */
|
|
5
|
+
export interface Check {
|
|
6
|
+
/** Stable, for scripts. */
|
|
7
|
+
id: string;
|
|
8
|
+
status: CheckStatus;
|
|
9
|
+
message: string;
|
|
10
|
+
/** What to do about it. Every `fail` has one, and so does every `warn`. */
|
|
11
|
+
fix?: string;
|
|
12
|
+
}
|
|
13
|
+
/** What `doctor` reads of the instance's resolved config. */
|
|
14
|
+
export interface InstanceConfig {
|
|
15
|
+
projectsRoot: string;
|
|
16
|
+
claudeHome: string;
|
|
17
|
+
credentials: "own" | "host";
|
|
18
|
+
driveMode: "session" | "batch";
|
|
19
|
+
}
|
|
20
|
+
export interface DoctorInput {
|
|
21
|
+
ctx: ServiceContext;
|
|
22
|
+
/** `--data-dir`, when it was given. */
|
|
23
|
+
dataDir?: string;
|
|
24
|
+
offline?: boolean;
|
|
25
|
+
/** `process.argv[1]`, for SPEC §6's walk-up. */
|
|
26
|
+
entry: string;
|
|
27
|
+
env?: NodeJS.ProcessEnv;
|
|
28
|
+
/** Injected for tests. Everything below defaults to the real thing. */
|
|
29
|
+
nodeVersion?: string;
|
|
30
|
+
npmGlobalRoot?: () => string | undefined;
|
|
31
|
+
registry?: string;
|
|
32
|
+
registryFetch?: typeof fetch;
|
|
33
|
+
/** Is anything accepting connections at host:port? */
|
|
34
|
+
isListening?: (host: string, port: number) => Promise<boolean>;
|
|
35
|
+
/** Is what answers at `url` a Paddock, and which version? Undefined if nothing answers HTTP there. */
|
|
36
|
+
identify?: (url: string) => Promise<{
|
|
37
|
+
paddock: boolean;
|
|
38
|
+
version?: string;
|
|
39
|
+
} | undefined>;
|
|
40
|
+
/** Load the instance's config for `dataDir`. Throws what `paddock serve` would fail with. */
|
|
41
|
+
loadConfig?: (dataDir: string) => Promise<InstanceConfig>;
|
|
42
|
+
/** The macOS Keychain probe `claude-home.ts` runs at boot. */
|
|
43
|
+
keychain?: () => Promise<"found" | "unknown">;
|
|
44
|
+
/** `~/.claude`, the machine's own Claude home. */
|
|
45
|
+
userClaudeHome?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface DoctorResult {
|
|
48
|
+
checks: Check[];
|
|
49
|
+
/** SPEC §3's update notice, for stderr. */
|
|
50
|
+
notice?: string;
|
|
51
|
+
}
|
|
52
|
+
export declare function diagnose(i: DoctorInput): Promise<DoctorResult>;
|
|
53
|
+
/** The checklist, for a person. */
|
|
54
|
+
export declare function renderChecks(checks: readonly Check[]): string;
|
|
55
|
+
/** 1 if any check failed (SPEC §1); warnings don't change it. */
|
|
56
|
+
export declare function doctorExit(checks: readonly Check[]): number;
|
|
57
|
+
/** `paddock doctor`: run the checks, print them, and hand back the exit code. */
|
|
58
|
+
export declare function runDoctor(opts: CliOptions, ctx: ServiceContext): Promise<number>;
|
|
59
|
+
/** True if `dir` is writable; "creatable" if it doesn't exist but its nearest existing parent is; else why not. */
|
|
60
|
+
export declare function writableOrCreatable(dir: string): true | "creatable" | string;
|