@edspencer/paddock 0.58.0 → 0.59.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 +56 -0
- package/packages/server/dist/cli/args.js +159 -0
- package/packages/server/dist/cli/here.d.ts +25 -0
- package/packages/server/dist/cli/here.js +104 -0
- package/packages/server/dist/cli/paddock.d.ts +1 -29
- package/packages/server/dist/cli/paddock.js +93 -127
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/{InstanceConfigPage-BKE6j_Wy.js → InstanceConfigPage-CAwSnyga.js} +1 -1
- package/packages/web/dist/assets/{MarkdownRenderer-B7_FErIN.js → MarkdownRenderer-DpGpkMzG.js} +1 -1
- package/packages/web/dist/assets/{ProjectRedirect-DOaB53YM.js → ProjectRedirect-D1ipv4jr.js} +1 -1
- package/packages/web/dist/assets/{ProjectView-Dz_QUyPG.js → ProjectView-BI1TtrOK.js} +3 -3
- package/packages/web/dist/assets/{ProjectsGrid-CjfDQ37q.js → ProjectsGrid-Dwngb-m1.js} +1 -1
- package/packages/web/dist/assets/{arc-BxAGgBjd.js → arc-BZh6Z9_D.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-CBz2e-c3.js → architectureDiagram-3BPJPVTR-iCtJiW4k.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-ZqClBxB7.js → blockDiagram-GPEHLZMM-BeI9Wi0h.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-CvFOBN1E.js → c4Diagram-AAUBKEIU-D_j2LKZ-.js} +1 -1
- package/packages/web/dist/assets/channel-DszBVZgr.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-C44u7-Xg.js → chunk-2J33WTMH-DRlfPROq.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-DXG9UM3Z.js → chunk-4BX2VUAB-BMY66MLs.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-BLkJFttb.js → chunk-55IACEB6-BAQ92trf.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-DZidMKHZ.js → chunk-727SXJPM-Dxgb_nNN.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-DVowfDYg.js → chunk-AQP2D5EJ-D5cqv3Jv.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-zeHzmwLI.js → chunk-FMBD7UC4-BOEqxzyA.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-Pcd02Wpz.js → chunk-ND2GUHAM-CkTZgv_W.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-yxlVeu8W.js → chunk-QZHKN3VN-DIr8TL7M.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DuGqlnbT.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DuGqlnbT.js +2 -0
- package/packages/web/dist/assets/{core-CSvc61jo.js → core-DyOZ2e1O.js} +1 -1
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-6MJqrbdr.js → cose-bilkent-S5V4N54A-D8Muyf4o.js} +1 -1
- package/packages/web/dist/assets/{dagre-BM42HDAG-B3pDB8_y.js → dagre-BM42HDAG-B_EUbUz6.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-BJA5EERJ.js → diagram-2AECGRRQ-DpAM0K6Q.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-CcyfxhVS.js → diagram-5GNKFQAL-Dhs9RMKk.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-CPNyNU1j.js → diagram-KO2AKTUF-LBeTHReu.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-Q4klJqX8.js → diagram-LMA3HP47-CxboRwue.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-D0ZBTRoA.js → diagram-OG6HWLK6-BK_P-OSi.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-DQrCT17u.js → erDiagram-TEJ5UH35-CPSKRHI5.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-CK7nM7wz.js → flowDiagram-I6XJVG4X-CbUumAP5.js} +1 -1
- package/packages/web/dist/assets/{format-GJRFyZIp.js → format-DDL0Q7H2.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-CFsNGWGG.js → ganttDiagram-6RSMTGT7-l-p25ckA.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-xP0bbv60.js → gitGraphDiagram-PVQCEYII-i8kDIgga.js} +1 -1
- package/packages/web/dist/assets/{index-CDiCRIso.js → index-DKDgTDXa.js} +2 -2
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-BaKZDvMw.js → infoDiagram-5YYISTIA-CbyLnHIe.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-Dr0LK4iy.js → ishikawaDiagram-YF4QCWOH-C1_3z8-9.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-CxusdThR.js → journeyDiagram-JHISSGLW-BzyAtQdy.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-CJYlFeaB.js → kanban-definition-UN3LZRKU-Fp2VVm-b.js} +1 -1
- package/packages/web/dist/assets/{linear-D0XMlvYu.js → linear-6ZKEeWQ5.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-CcPpo_y5.js → mermaid.core-Dorg4Dl2.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-w064Pb6X.js → mindmap-definition-RKZ34NQL-Bk4Ax0bI.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-D_qwlox1.js → pieDiagram-4H26LBE5-DoTn5jHm.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-BuMUhsEZ.js → quadrantDiagram-W4KKPZXB-ClNmIKCj.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-Bp1BqfXY.js → requirementDiagram-4Y6WPE33-DMF7i5KW.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-BQR4HfjD.js → sankeyDiagram-5OEKKPKP-C-ALbVLg.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-DIAHV11i.js → sequenceDiagram-3UESZ5HK-DsUxj8IQ.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-BHMJl8rE.js → stateDiagram-AJRCARHV-OCZiRRsz.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-BwcJspgG.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-CB5sHevP.js → timeline-definition-PNZ67QCA-5ifjxa-s.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-BeS1AXva.js → vennDiagram-CIIHVFJN-Bqvqpot5.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-CJ9C5W6N.js → wardley-L42UT6IY-DwgnVd48.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-Bf75XvJ_.js → wardleyDiagram-YWT4CUSO-F-VCH-oE.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-0Qiya8rY.js → xychartDiagram-2RQKCTM6-L4q6oV_7.js} +1 -1
- package/packages/web/dist/index.html +1 -1
- package/packages/web/dist/sw.js +1 -1
- package/packages/web/dist/assets/channel-BHzPWerJ.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-hNchnAu0.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-hNchnAu0.js +0 -2
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CScn8fuQ.js +0 -2
package/package.json
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure argument parsing and message formatting for the `paddock` CLI.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this is its own module
|
|
5
|
+
*
|
|
6
|
+
* These functions want unit tests, and the entrypoint they used to live in
|
|
7
|
+
* cannot be imported without running. The first attempt kept them in
|
|
8
|
+
* `paddock.ts` behind a run-directly guard:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* if (pathToFileURL(process.argv[1]).href === import.meta.url) main();
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* **That guard shipped a silent no-op to npm.** npm installs a `bin` as a
|
|
15
|
+
* SYMLINK at `node_modules/.bin/paddock`, so `process.argv[1]` is the symlink
|
|
16
|
+
* path while `import.meta.url` is the module's realpath. They never match, the
|
|
17
|
+
* condition is always false, and `npx @edspencer/paddock` printed nothing and
|
|
18
|
+
* exited 0 — in 0.57.0, 0.58.0 and 0.59.0. It went unnoticed because the manual
|
|
19
|
+
* checks invoked `node <file>` directly, which is the one path where the guard
|
|
20
|
+
* does hold.
|
|
21
|
+
*
|
|
22
|
+
* `realpathSync(argv[1])` would fix that instance. Splitting the module removes
|
|
23
|
+
* the need for the guard entirely, so there is no condition left to get wrong on
|
|
24
|
+
* the next shim, platform or package manager. `paddock.ts` now always runs.
|
|
25
|
+
*/
|
|
26
|
+
export interface CliOptions {
|
|
27
|
+
port?: string;
|
|
28
|
+
host?: string;
|
|
29
|
+
dataDir?: string;
|
|
30
|
+
here: boolean;
|
|
31
|
+
open: boolean;
|
|
32
|
+
verbose: boolean;
|
|
33
|
+
help: boolean;
|
|
34
|
+
version: boolean;
|
|
35
|
+
}
|
|
36
|
+
/** A usage error. Thrown rather than exiting, so parsing stays testable. */
|
|
37
|
+
export declare class CliError extends Error {
|
|
38
|
+
}
|
|
39
|
+
export declare const MIN_NODE_MAJOR = 22;
|
|
40
|
+
export declare function parseArgs(argv: string[]): CliOptions;
|
|
41
|
+
/**
|
|
42
|
+
* Why this Node is unusable, or `undefined` if it is fine.
|
|
43
|
+
*
|
|
44
|
+
* `engines` only warns by default, and the failure it eventually produces is an
|
|
45
|
+
* unexplained syntax error deep inside a dependency.
|
|
46
|
+
*/
|
|
47
|
+
export declare function nodeVersionProblem(nodeVersion: string): string | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Translate a listen failure into something a human can act on.
|
|
50
|
+
*
|
|
51
|
+
* `EADDRINUSE` on 4000 is the likeliest first-run failure — a popular port and
|
|
52
|
+
* Paddock's own default — and Node's raw error names neither the port nor the
|
|
53
|
+
* flag that fixes it.
|
|
54
|
+
*/
|
|
55
|
+
export declare function explainListenError(err: unknown, host: string, port: string): string;
|
|
56
|
+
export declare const USAGE = "paddock \u2014 run a Paddock instance locally\n\nUsage\n npx @edspencer/paddock [options]\n\nOptions\n -p, --port <port> HTTP/WS port (default 4000, 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 --here Open the CURRENT directory as the workspace (see below)\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\nOpening a directory (--here)\n Run `paddock --here` inside a project and Paddock opens THAT directory as its\n workspace: Claude works in your files, and any Claude Code sessions you already\n have for the directory are offered for import. It:\n\n \u00B7 creates .paddock/ in the directory for this workspace's own state\n \u00B7 creates .chats/ for transcripts, and adds both to .gitignore\n \u00B7 links ~/.claude/projects/<encoded-dir> at this workspace\n\n Once done, later runs in the same directory resume it \u2014 no flag needed.\n Without --here, Paddock never touches the directory you ran it from.\n\nCredentials\n Paddock drives Claude Code, so it needs Claude credentials in the\n environment: CLAUDE_CODE_OAUTH_TOKEN (Max/Pro) or ANTHROPIC_API_KEY (API\n billing). If you already use Claude Code on this machine, an existing login\n under ~/.claude is picked up automatically. Otherwise run `claude setup-token`.\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";
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure argument parsing and message formatting for the `paddock` CLI.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this is its own module
|
|
5
|
+
*
|
|
6
|
+
* These functions want unit tests, and the entrypoint they used to live in
|
|
7
|
+
* cannot be imported without running. The first attempt kept them in
|
|
8
|
+
* `paddock.ts` behind a run-directly guard:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* if (pathToFileURL(process.argv[1]).href === import.meta.url) main();
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* **That guard shipped a silent no-op to npm.** npm installs a `bin` as a
|
|
15
|
+
* SYMLINK at `node_modules/.bin/paddock`, so `process.argv[1]` is the symlink
|
|
16
|
+
* path while `import.meta.url` is the module's realpath. They never match, the
|
|
17
|
+
* condition is always false, and `npx @edspencer/paddock` printed nothing and
|
|
18
|
+
* exited 0 — in 0.57.0, 0.58.0 and 0.59.0. It went unnoticed because the manual
|
|
19
|
+
* checks invoked `node <file>` directly, which is the one path where the guard
|
|
20
|
+
* does hold.
|
|
21
|
+
*
|
|
22
|
+
* `realpathSync(argv[1])` would fix that instance. Splitting the module removes
|
|
23
|
+
* the need for the guard entirely, so there is no condition left to get wrong on
|
|
24
|
+
* the next shim, platform or package manager. `paddock.ts` now always runs.
|
|
25
|
+
*/
|
|
26
|
+
/** A usage error. Thrown rather than exiting, so parsing stays testable. */
|
|
27
|
+
export class CliError extends Error {
|
|
28
|
+
}
|
|
29
|
+
export const MIN_NODE_MAJOR = 22;
|
|
30
|
+
export function parseArgs(argv) {
|
|
31
|
+
const opts = {
|
|
32
|
+
here: false,
|
|
33
|
+
open: false,
|
|
34
|
+
verbose: false,
|
|
35
|
+
help: false,
|
|
36
|
+
version: false,
|
|
37
|
+
};
|
|
38
|
+
for (let i = 0; i < argv.length; i++) {
|
|
39
|
+
const arg = argv[i];
|
|
40
|
+
const next = () => {
|
|
41
|
+
const v = argv[++i];
|
|
42
|
+
if (v === undefined)
|
|
43
|
+
throw new CliError(`${arg} needs a value`);
|
|
44
|
+
return v;
|
|
45
|
+
};
|
|
46
|
+
switch (arg) {
|
|
47
|
+
case "-p":
|
|
48
|
+
case "--port":
|
|
49
|
+
opts.port = next();
|
|
50
|
+
break;
|
|
51
|
+
case "--host":
|
|
52
|
+
opts.host = next();
|
|
53
|
+
break;
|
|
54
|
+
case "-d":
|
|
55
|
+
case "--data-dir":
|
|
56
|
+
opts.dataDir = next();
|
|
57
|
+
break;
|
|
58
|
+
case "--here":
|
|
59
|
+
opts.here = true;
|
|
60
|
+
break;
|
|
61
|
+
case "-o":
|
|
62
|
+
case "--open":
|
|
63
|
+
opts.open = true;
|
|
64
|
+
break;
|
|
65
|
+
case "--verbose":
|
|
66
|
+
opts.verbose = true;
|
|
67
|
+
break;
|
|
68
|
+
case "-h":
|
|
69
|
+
case "--help":
|
|
70
|
+
opts.help = true;
|
|
71
|
+
break;
|
|
72
|
+
case "-v":
|
|
73
|
+
case "--version":
|
|
74
|
+
opts.version = true;
|
|
75
|
+
break;
|
|
76
|
+
default:
|
|
77
|
+
throw new CliError(`unknown option: ${arg}\nRun \`paddock --help\` for usage.`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return opts;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Why this Node is unusable, or `undefined` if it is fine.
|
|
84
|
+
*
|
|
85
|
+
* `engines` only warns by default, and the failure it eventually produces is an
|
|
86
|
+
* unexplained syntax error deep inside a dependency.
|
|
87
|
+
*/
|
|
88
|
+
export function nodeVersionProblem(nodeVersion) {
|
|
89
|
+
const major = Number(nodeVersion.split(".")[0]);
|
|
90
|
+
if (!Number.isFinite(major) || major >= MIN_NODE_MAJOR)
|
|
91
|
+
return undefined;
|
|
92
|
+
return (`Node ${nodeVersion} is too old — Paddock needs Node ${MIN_NODE_MAJOR}+.\n` +
|
|
93
|
+
`Upgrade Node, then re-run. (nodejs.org, or \`nvm install ${MIN_NODE_MAJOR}\`)`);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Translate a listen failure into something a human can act on.
|
|
97
|
+
*
|
|
98
|
+
* `EADDRINUSE` on 4000 is the likeliest first-run failure — a popular port and
|
|
99
|
+
* Paddock's own default — and Node's raw error names neither the port nor the
|
|
100
|
+
* flag that fixes it.
|
|
101
|
+
*/
|
|
102
|
+
export function explainListenError(err, host, port) {
|
|
103
|
+
const code = err?.code;
|
|
104
|
+
if (code === "EADDRINUSE") {
|
|
105
|
+
return (`port ${port} is already in use.\n` +
|
|
106
|
+
`Something else is listening on ${host}:${port} — possibly another Paddock.\n` +
|
|
107
|
+
`Pick a different port: paddock --port ${Number(port) + 1}`);
|
|
108
|
+
}
|
|
109
|
+
if (code === "EACCES") {
|
|
110
|
+
return (`not allowed to bind ${host}:${port}.\n` +
|
|
111
|
+
`Ports below 1024 need elevated privileges — use a higher one: paddock --port 4000`);
|
|
112
|
+
}
|
|
113
|
+
return String(err?.message ?? err);
|
|
114
|
+
}
|
|
115
|
+
export const USAGE = `paddock — run a Paddock instance locally
|
|
116
|
+
|
|
117
|
+
Usage
|
|
118
|
+
npx @edspencer/paddock [options]
|
|
119
|
+
|
|
120
|
+
Options
|
|
121
|
+
-p, --port <port> HTTP/WS port (default 4000, or $PORT)
|
|
122
|
+
--host <host> Bind address (default 127.0.0.1)
|
|
123
|
+
-d, --data-dir <path> Projects + state (default ~/.paddock, or $PADDOCK_DATA_DIR)
|
|
124
|
+
--here Open the CURRENT directory as the workspace (see below)
|
|
125
|
+
-o, --open Open the app in your browser once it is listening
|
|
126
|
+
--verbose Show the server's own logs (quiet by default)
|
|
127
|
+
-v, --version Print the Paddock version and exit
|
|
128
|
+
-h, --help Show this help
|
|
129
|
+
|
|
130
|
+
Opening a directory (--here)
|
|
131
|
+
Run \`paddock --here\` inside a project and Paddock opens THAT directory as its
|
|
132
|
+
workspace: Claude works in your files, and any Claude Code sessions you already
|
|
133
|
+
have for the directory are offered for import. It:
|
|
134
|
+
|
|
135
|
+
· creates .paddock/ in the directory for this workspace's own state
|
|
136
|
+
· creates .chats/ for transcripts, and adds both to .gitignore
|
|
137
|
+
· links ~/.claude/projects/<encoded-dir> at this workspace
|
|
138
|
+
|
|
139
|
+
Once done, later runs in the same directory resume it — no flag needed.
|
|
140
|
+
Without --here, Paddock never touches the directory you ran it from.
|
|
141
|
+
|
|
142
|
+
Credentials
|
|
143
|
+
Paddock drives Claude Code, so it needs Claude credentials in the
|
|
144
|
+
environment: CLAUDE_CODE_OAUTH_TOKEN (Max/Pro) or ANTHROPIC_API_KEY (API
|
|
145
|
+
billing). If you already use Claude Code on this machine, an existing login
|
|
146
|
+
under ~/.claude is picked up automatically. Otherwise run \`claude setup-token\`.
|
|
147
|
+
|
|
148
|
+
Your data
|
|
149
|
+
Everything lives in one directory — ~/.paddock unless you pass --data-dir.
|
|
150
|
+
Projects, chat transcripts and settings all persist there between runs. Move
|
|
151
|
+
that directory to move your instance; delete it to start over. Nothing is
|
|
152
|
+
stored anywhere else.
|
|
153
|
+
|
|
154
|
+
Notes
|
|
155
|
+
Binds loopback with authentication disabled, which is safe for a laptop. To
|
|
156
|
+
expose it on a network, set PADDOCK_AUTH_MODE first — Paddock refuses to bind
|
|
157
|
+
a routable interface wide open. See AUTH.md.
|
|
158
|
+
|
|
159
|
+
Docs: https://github.com/edspencer/paddock`;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Directory name that both stores `--here` state and marks the workspace. */
|
|
2
|
+
export declare const HERE_MARKER = ".paddock";
|
|
3
|
+
/** Has this directory been opened with `--here` before? */
|
|
4
|
+
export declare function isHereWorkspace(dir: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* How many of `dir`'s existing Claude Code sessions are worth offering.
|
|
7
|
+
*
|
|
8
|
+
* **`encodePathForCli`, not `transcripts.ts`'s `encodeProjectDir`.** The latter
|
|
9
|
+
* omits Claude Code's truncate-and-hash branch for paths over 200 characters,
|
|
10
|
+
* on the explicit assumption that "paddock's project dirs are always short".
|
|
11
|
+
* An arbitrary user directory is exactly where that assumption fails.
|
|
12
|
+
*
|
|
13
|
+
* `MIN_TRANSCRIPT_BYTES` is shared with `AdoptableIndex` so the number quoted
|
|
14
|
+
* here matches what the UI will actually offer to import — a count that
|
|
15
|
+
* disagreed with the import screen would be worse than no count.
|
|
16
|
+
*/
|
|
17
|
+
export declare function countClaudeSessions(claudeHome: string, dir: string): number;
|
|
18
|
+
/**
|
|
19
|
+
* Add `entry` to `dir`'s `.gitignore` if no equivalent line is already there.
|
|
20
|
+
*
|
|
21
|
+
* Mirrors `ProjectStore.ensureRootGitignore`: read, tolerate the equivalent
|
|
22
|
+
* forms, append rather than overwrite. `--here` runs inside somebody's real
|
|
23
|
+
* repository, so clobbering their `.gitignore` is not an option.
|
|
24
|
+
*/
|
|
25
|
+
export declare function ensureGitignored(dir: string, entry: string): void;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--here` support: opening the user's own directory as a Paddock workspace (#640).
|
|
3
|
+
*
|
|
4
|
+
* ## Why this needs no new concept
|
|
5
|
+
*
|
|
6
|
+
* A *project* cannot live outside `projectsRoot` — `dirFor` joins the key onto
|
|
7
|
+
* the root with no branch, deliberately. But the **root workspace** is a
|
|
8
|
+
* different thing: its key is `""`, so `dirFor("")` resolves to `projectsRoot`
|
|
9
|
+
* itself, and `projectsRoot` is user-configurable. Pointing
|
|
10
|
+
* `PADDOCK_PROJECTS_DIR` at a directory therefore makes that directory the root
|
|
11
|
+
* workspace, named after its own basename, with no schema change anywhere.
|
|
12
|
+
*
|
|
13
|
+
* Session adoption then works by construction: `AdoptableIndex`'s notebook
|
|
14
|
+
* branch matches on exact `cwd === project.workingDir` equality, and in this
|
|
15
|
+
* mode the workspace's working directory IS the user's cwd.
|
|
16
|
+
*
|
|
17
|
+
* ## Why the data dir goes inside the directory
|
|
18
|
+
*
|
|
19
|
+
* `--here` puts state in `<dir>/.paddock` rather than the shared `~/.paddock`.
|
|
20
|
+
* Two reasons, and the second is the important one:
|
|
21
|
+
*
|
|
22
|
+
* 1. **Self-containment.** Opening directory A and later directory B must not
|
|
23
|
+
* have them share one job store, herdctl config and sidecar set — that is
|
|
24
|
+
* how A's run history leaks into B's UI.
|
|
25
|
+
* 2. **It gives us an unambiguous marker.** A second, flagless run in the same
|
|
26
|
+
* directory should resume it (the `git` model: `--here` is `git init`,
|
|
27
|
+
* `.paddock/` is `.git`). Keying that off `project.yaml` would be ambiguous
|
|
28
|
+
* — that filename already exists in the wild, including in every project
|
|
29
|
+
* directory of the `edspencer/projects` notes repo — whereas `.paddock/` is
|
|
30
|
+
* ours and its presence means exactly one thing.
|
|
31
|
+
*/
|
|
32
|
+
import fs from "node:fs";
|
|
33
|
+
import path from "node:path";
|
|
34
|
+
import { encodePathForCli } from "@herdctl/core";
|
|
35
|
+
import { MIN_TRANSCRIPT_BYTES } from "../adoptable.js";
|
|
36
|
+
/** Directory name that both stores `--here` state and marks the workspace. */
|
|
37
|
+
export const HERE_MARKER = ".paddock";
|
|
38
|
+
/** Has this directory been opened with `--here` before? */
|
|
39
|
+
export function isHereWorkspace(dir) {
|
|
40
|
+
try {
|
|
41
|
+
return fs.statSync(path.join(dir, HERE_MARKER)).isDirectory();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* How many of `dir`'s existing Claude Code sessions are worth offering.
|
|
49
|
+
*
|
|
50
|
+
* **`encodePathForCli`, not `transcripts.ts`'s `encodeProjectDir`.** The latter
|
|
51
|
+
* omits Claude Code's truncate-and-hash branch for paths over 200 characters,
|
|
52
|
+
* on the explicit assumption that "paddock's project dirs are always short".
|
|
53
|
+
* An arbitrary user directory is exactly where that assumption fails.
|
|
54
|
+
*
|
|
55
|
+
* `MIN_TRANSCRIPT_BYTES` is shared with `AdoptableIndex` so the number quoted
|
|
56
|
+
* here matches what the UI will actually offer to import — a count that
|
|
57
|
+
* disagreed with the import screen would be worse than no count.
|
|
58
|
+
*/
|
|
59
|
+
export function countClaudeSessions(claudeHome, dir) {
|
|
60
|
+
const folder = path.join(claudeHome, "projects", encodePathForCli(dir));
|
|
61
|
+
let entries;
|
|
62
|
+
try {
|
|
63
|
+
entries = fs.readdirSync(folder);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
let n = 0;
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
if (!entry.endsWith(".jsonl"))
|
|
71
|
+
continue;
|
|
72
|
+
try {
|
|
73
|
+
if (fs.statSync(path.join(folder, entry)).size >= MIN_TRANSCRIPT_BYTES)
|
|
74
|
+
n++;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
/* raced or unreadable — not offerable either way */
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return n;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Add `entry` to `dir`'s `.gitignore` if no equivalent line is already there.
|
|
84
|
+
*
|
|
85
|
+
* Mirrors `ProjectStore.ensureRootGitignore`: read, tolerate the equivalent
|
|
86
|
+
* forms, append rather than overwrite. `--here` runs inside somebody's real
|
|
87
|
+
* repository, so clobbering their `.gitignore` is not an option.
|
|
88
|
+
*/
|
|
89
|
+
export function ensureGitignored(dir, entry) {
|
|
90
|
+
const file = path.join(dir, ".gitignore");
|
|
91
|
+
let existing = "";
|
|
92
|
+
try {
|
|
93
|
+
existing = fs.readFileSync(file, "utf8");
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
/* no .gitignore yet */
|
|
97
|
+
}
|
|
98
|
+
const bare = entry.replace(/^\/|\/$/g, "");
|
|
99
|
+
const have = new Set(existing.split("\n").map((l) => l.trim()));
|
|
100
|
+
if (have.has(`/${bare}/`) || have.has(`${bare}/`) || have.has(bare))
|
|
101
|
+
return;
|
|
102
|
+
const body = existing && !existing.endsWith("\n") ? `${existing}\n` : existing;
|
|
103
|
+
fs.writeFileSync(file, `${body}${entry}\n`, "utf8");
|
|
104
|
+
}
|
|
@@ -1,30 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export
|
|
3
|
-
port?: string;
|
|
4
|
-
host?: string;
|
|
5
|
-
dataDir?: string;
|
|
6
|
-
open: boolean;
|
|
7
|
-
verbose: boolean;
|
|
8
|
-
help: boolean;
|
|
9
|
-
version: boolean;
|
|
10
|
-
}
|
|
11
|
-
/** A usage error. Thrown rather than exiting, so `parseArgs` stays testable. */
|
|
12
|
-
export declare class CliError extends Error {
|
|
13
|
-
}
|
|
14
|
-
export declare function parseArgs(argv: string[]): CliOptions;
|
|
15
|
-
/**
|
|
16
|
-
* Why this Node is unusable, or `undefined` if it is fine.
|
|
17
|
-
*
|
|
18
|
-
* Split from the exit so it can be tested. `engines` only warns by default, and
|
|
19
|
-
* the failure it eventually produces is an unexplained syntax error deep inside
|
|
20
|
-
* a dependency — which is nobody's idea of a first-run experience.
|
|
21
|
-
*/
|
|
22
|
-
export declare function nodeVersionProblem(nodeVersion: string): string | undefined;
|
|
23
|
-
/**
|
|
24
|
-
* Translate a listen failure into something a human can act on.
|
|
25
|
-
*
|
|
26
|
-
* `EADDRINUSE` on 4000 is the single most likely first-run failure — it is a
|
|
27
|
-
* popular port and Paddock's own default — and Node's raw error names neither
|
|
28
|
-
* the port nor the flag that fixes it.
|
|
29
|
-
*/
|
|
30
|
-
export declare function explainListenError(err: unknown, host: string, port: string): string;
|
|
2
|
+
export {};
|
|
@@ -22,8 +22,9 @@ import fs from "node:fs";
|
|
|
22
22
|
import os from "node:os";
|
|
23
23
|
import path from "node:path";
|
|
24
24
|
import { spawn } from "node:child_process";
|
|
25
|
-
import { fileURLToPath
|
|
26
|
-
|
|
25
|
+
import { fileURLToPath } from "node:url";
|
|
26
|
+
import { HERE_MARKER, isHereWorkspace, countClaudeSessions, ensureGitignored } from "./here.js";
|
|
27
|
+
import { CliError, USAGE, parseArgs, nodeVersionProblem, explainListenError, } from "./args.js";
|
|
27
28
|
/** This module's own directory: `<…>/packages/server/dist/cli`. */
|
|
28
29
|
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
29
30
|
/**
|
|
@@ -52,52 +53,6 @@ function findUp(rel) {
|
|
|
52
53
|
}
|
|
53
54
|
/** Nearest ancestor holding a `package.json` — `packages/server` in both layouts. */
|
|
54
55
|
const packageRoot = findUp("package.json") ?? path.resolve(moduleDir, "../..");
|
|
55
|
-
/** A usage error. Thrown rather than exiting, so `parseArgs` stays testable. */
|
|
56
|
-
export class CliError extends Error {
|
|
57
|
-
}
|
|
58
|
-
export function parseArgs(argv) {
|
|
59
|
-
const opts = { open: false, verbose: false, help: false, version: false };
|
|
60
|
-
for (let i = 0; i < argv.length; i++) {
|
|
61
|
-
const arg = argv[i];
|
|
62
|
-
const next = () => {
|
|
63
|
-
const v = argv[++i];
|
|
64
|
-
if (v === undefined)
|
|
65
|
-
throw new CliError(`${arg} needs a value`);
|
|
66
|
-
return v;
|
|
67
|
-
};
|
|
68
|
-
switch (arg) {
|
|
69
|
-
case "-p":
|
|
70
|
-
case "--port":
|
|
71
|
-
opts.port = next();
|
|
72
|
-
break;
|
|
73
|
-
case "--host":
|
|
74
|
-
opts.host = next();
|
|
75
|
-
break;
|
|
76
|
-
case "-d":
|
|
77
|
-
case "--data-dir":
|
|
78
|
-
opts.dataDir = next();
|
|
79
|
-
break;
|
|
80
|
-
case "-o":
|
|
81
|
-
case "--open":
|
|
82
|
-
opts.open = true;
|
|
83
|
-
break;
|
|
84
|
-
case "--verbose":
|
|
85
|
-
opts.verbose = true;
|
|
86
|
-
break;
|
|
87
|
-
case "-h":
|
|
88
|
-
case "--help":
|
|
89
|
-
opts.help = true;
|
|
90
|
-
break;
|
|
91
|
-
case "-v":
|
|
92
|
-
case "--version":
|
|
93
|
-
opts.version = true;
|
|
94
|
-
break;
|
|
95
|
-
default:
|
|
96
|
-
throw new CliError(`unknown option: ${arg}\nRun \`paddock --help\` for usage.`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return opts;
|
|
100
|
-
}
|
|
101
56
|
function fail(message) {
|
|
102
57
|
console.error(`paddock: ${message}`);
|
|
103
58
|
process.exit(1);
|
|
@@ -111,52 +66,6 @@ function readVersion() {
|
|
|
111
66
|
return "unknown";
|
|
112
67
|
}
|
|
113
68
|
}
|
|
114
|
-
const USAGE = `paddock — run a Paddock instance locally
|
|
115
|
-
|
|
116
|
-
Usage
|
|
117
|
-
npx @edspencer/paddock [options]
|
|
118
|
-
|
|
119
|
-
Options
|
|
120
|
-
-p, --port <port> HTTP/WS port (default 4000, or $PORT)
|
|
121
|
-
--host <host> Bind address (default 127.0.0.1)
|
|
122
|
-
-d, --data-dir <path> Projects + state (default ~/.paddock, or $PADDOCK_DATA_DIR)
|
|
123
|
-
-o, --open Open the app in your browser once it is listening
|
|
124
|
-
--verbose Show the server's own logs (quiet by default)
|
|
125
|
-
-v, --version Print the Paddock version and exit
|
|
126
|
-
-h, --help Show this help
|
|
127
|
-
|
|
128
|
-
Credentials
|
|
129
|
-
Paddock drives Claude Code, so it needs Claude credentials in the
|
|
130
|
-
environment: CLAUDE_CODE_OAUTH_TOKEN (Max/Pro) or ANTHROPIC_API_KEY (API
|
|
131
|
-
billing). If you already use Claude Code on this machine, an existing login
|
|
132
|
-
under ~/.claude is picked up automatically. Otherwise run \`claude setup-token\`.
|
|
133
|
-
|
|
134
|
-
Your data
|
|
135
|
-
Everything lives in one directory — ~/.paddock unless you pass --data-dir.
|
|
136
|
-
Projects, chat transcripts and settings all persist there between runs. Move
|
|
137
|
-
that directory to move your instance; delete it to start over. Nothing is
|
|
138
|
-
stored anywhere else.
|
|
139
|
-
|
|
140
|
-
Notes
|
|
141
|
-
Binds loopback with authentication disabled, which is safe for a laptop. To
|
|
142
|
-
expose it on a network, set PADDOCK_AUTH_MODE first — Paddock refuses to bind
|
|
143
|
-
a routable interface wide open. See AUTH.md.
|
|
144
|
-
|
|
145
|
-
Docs: https://github.com/edspencer/paddock`;
|
|
146
|
-
/**
|
|
147
|
-
* Why this Node is unusable, or `undefined` if it is fine.
|
|
148
|
-
*
|
|
149
|
-
* Split from the exit so it can be tested. `engines` only warns by default, and
|
|
150
|
-
* the failure it eventually produces is an unexplained syntax error deep inside
|
|
151
|
-
* a dependency — which is nobody's idea of a first-run experience.
|
|
152
|
-
*/
|
|
153
|
-
export function nodeVersionProblem(nodeVersion) {
|
|
154
|
-
const major = Number(nodeVersion.split(".")[0]);
|
|
155
|
-
if (!Number.isFinite(major) || major >= MIN_NODE_MAJOR)
|
|
156
|
-
return undefined;
|
|
157
|
-
return (`Node ${nodeVersion} is too old — Paddock needs Node ${MIN_NODE_MAJOR}+.\n` +
|
|
158
|
-
`Upgrade Node, then re-run. (nodejs.org, or \`nvm install ${MIN_NODE_MAJOR}\`)`);
|
|
159
|
-
}
|
|
160
69
|
/**
|
|
161
70
|
* Put our bundled `claude` on PATH for child processes.
|
|
162
71
|
*
|
|
@@ -225,6 +134,48 @@ function noteFirstRun(dataDir) {
|
|
|
225
134
|
"",
|
|
226
135
|
].join("\n"));
|
|
227
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Say what `--here` is about to do to this directory, on the run that does it.
|
|
139
|
+
*
|
|
140
|
+
* The flag IS the consent — there is no prompt, and no `--yes` to remember.
|
|
141
|
+
* But consent only means something if you can know what you agreed to, so the
|
|
142
|
+
* effects are announced rather than asked about. Printed only on the first
|
|
143
|
+
* `--here` in a directory; resuming an already-opened one says nothing.
|
|
144
|
+
*/
|
|
145
|
+
function announceHereConsent(dir, dataDir) {
|
|
146
|
+
console.log([
|
|
147
|
+
"",
|
|
148
|
+
` Opening ${dir} as a Paddock workspace.`,
|
|
149
|
+
` · ${path.relative(dir, dataDir) || HERE_MARKER}/ and .chats/ created here, and added to .gitignore`,
|
|
150
|
+
" · ~/.claude sessions for this directory are linked at the workspace",
|
|
151
|
+
" Later runs here resume it — no flag needed.",
|
|
152
|
+
"",
|
|
153
|
+
].join("\n"));
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Tell the user their existing Claude history could be opened here.
|
|
157
|
+
*
|
|
158
|
+
* The only way anyone discovers `--here` exists. Read-only: a bare run counts
|
|
159
|
+
* transcripts and prints, and changes nothing on disk.
|
|
160
|
+
*/
|
|
161
|
+
function offerHereIfSessionsExist(dir) {
|
|
162
|
+
const claudeHome = process.env.CLAUDE_HOME ?? path.join(os.homedir(), ".claude");
|
|
163
|
+
let sessions = 0;
|
|
164
|
+
try {
|
|
165
|
+
sessions = countClaudeSessions(claudeHome, dir);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return; // detection is a courtesy; never let it break a boot
|
|
169
|
+
}
|
|
170
|
+
if (sessions === 0)
|
|
171
|
+
return;
|
|
172
|
+
console.log([
|
|
173
|
+
"",
|
|
174
|
+
` This directory has ${sessions} Claude Code session${sessions === 1 ? "" : "s"}.`,
|
|
175
|
+
" Run `paddock --here` to open it as a workspace and import them.",
|
|
176
|
+
"",
|
|
177
|
+
].join("\n"));
|
|
178
|
+
}
|
|
228
179
|
/** Open `url` in the default browser. Best-effort: failing is never fatal. */
|
|
229
180
|
function openBrowser(url) {
|
|
230
181
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
@@ -243,26 +194,6 @@ function openBrowser(url) {
|
|
|
243
194
|
/* as above */
|
|
244
195
|
}
|
|
245
196
|
}
|
|
246
|
-
/**
|
|
247
|
-
* Translate a listen failure into something a human can act on.
|
|
248
|
-
*
|
|
249
|
-
* `EADDRINUSE` on 4000 is the single most likely first-run failure — it is a
|
|
250
|
-
* popular port and Paddock's own default — and Node's raw error names neither
|
|
251
|
-
* the port nor the flag that fixes it.
|
|
252
|
-
*/
|
|
253
|
-
export function explainListenError(err, host, port) {
|
|
254
|
-
const code = err?.code;
|
|
255
|
-
if (code === "EADDRINUSE") {
|
|
256
|
-
return (`port ${port} is already in use.\n` +
|
|
257
|
-
`Something else is listening on ${host}:${port} — possibly another Paddock.\n` +
|
|
258
|
-
`Pick a different port: paddock --port ${Number(port) + 1}`);
|
|
259
|
-
}
|
|
260
|
-
if (code === "EACCES") {
|
|
261
|
-
return (`not allowed to bind ${host}:${port}.\n` +
|
|
262
|
-
`Ports below 1024 need elevated privileges — use a higher one: paddock --port 4000`);
|
|
263
|
-
}
|
|
264
|
-
return String(err?.message ?? err);
|
|
265
|
-
}
|
|
266
197
|
async function main() {
|
|
267
198
|
let opts;
|
|
268
199
|
try {
|
|
@@ -285,12 +216,44 @@ async function main() {
|
|
|
285
216
|
if (problem !== undefined)
|
|
286
217
|
fail(problem);
|
|
287
218
|
addBundledBinsToPath();
|
|
219
|
+
// Canonical, because working directories "MUST be canonical so session
|
|
220
|
+
// discovery (which encodes the real path) can find Claude transcripts"
|
|
221
|
+
// (config.ts). On macOS a /tmp vs /private/tmp divergence would silently
|
|
222
|
+
// break adoption — and macOS is most laptops.
|
|
223
|
+
let cwd = process.cwd();
|
|
224
|
+
try {
|
|
225
|
+
cwd = fs.realpathSync(cwd);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
/* keep the literal path; the server will surface any real problem */
|
|
229
|
+
}
|
|
230
|
+
// `--here` is the consent. A flagless run in a directory ALREADY opened this
|
|
231
|
+
// way resumes it — the git model, where `--here` is `git init` and `.paddock/`
|
|
232
|
+
// is `.git`. A flagless run anywhere else touches nothing.
|
|
233
|
+
const resuming = !opts.here && isHereWorkspace(cwd);
|
|
234
|
+
const hereMode = opts.here || resuming;
|
|
288
235
|
// Apply CLI defaults as env vars. This is the whole integration surface: the
|
|
289
236
|
// server resolves config inside `buildApp()`, so anything set before the
|
|
290
237
|
// dynamic import below is picked up with no special-casing in config.ts.
|
|
291
238
|
// Precedence is explicit flag > existing env > our default.
|
|
292
|
-
const dataDir = path.resolve(opts.dataDir ??
|
|
293
|
-
|
|
239
|
+
const dataDir = path.resolve(opts.dataDir ??
|
|
240
|
+
process.env.PADDOCK_DATA_DIR ??
|
|
241
|
+
(hereMode ? path.join(cwd, HERE_MARKER) : path.join(os.homedir(), ".paddock")));
|
|
242
|
+
if (hereMode) {
|
|
243
|
+
if (!resuming)
|
|
244
|
+
announceHereConsent(cwd, dataDir);
|
|
245
|
+
// The root workspace IS `projectsRoot` (its key is ""), so pointing this at
|
|
246
|
+
// the user's directory makes that directory the workspace. No schema change,
|
|
247
|
+
// no new project type — see cli/here.ts.
|
|
248
|
+
process.env.PADDOCK_PROJECTS_DIR = cwd;
|
|
249
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
250
|
+
ensureGitignored(cwd, `/${HERE_MARKER}/`);
|
|
251
|
+
ensureGitignored(cwd, "/.chats/");
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
noteFirstRun(dataDir);
|
|
255
|
+
offerHereIfSessionsExist(cwd);
|
|
256
|
+
}
|
|
294
257
|
process.env.PADDOCK_DATA_DIR = dataDir;
|
|
295
258
|
if (opts.port)
|
|
296
259
|
process.env.PORT = opts.port;
|
|
@@ -323,17 +286,20 @@ async function main() {
|
|
|
323
286
|
fail(explainListenError(err, host, port));
|
|
324
287
|
}
|
|
325
288
|
const url = `http://${host}:${port}`;
|
|
326
|
-
|
|
289
|
+
// ALWAYS name the workspace, not just in --here mode. Behaviour that varies
|
|
290
|
+
// with cwd is only safe if it is observable: "why is this instance different
|
|
291
|
+
// today" should be answerable by reading the two lines the tool just printed.
|
|
292
|
+
console.log([
|
|
293
|
+
"",
|
|
294
|
+
` Paddock is running at ${url}`,
|
|
295
|
+
` Workspace: ${hereMode ? cwd : dataDir}${resuming ? " (resumed)" : ""}`,
|
|
296
|
+
" Press Ctrl-C to stop.",
|
|
297
|
+
"",
|
|
298
|
+
].join("\n"));
|
|
327
299
|
if (opts.open)
|
|
328
300
|
openBrowser(url);
|
|
329
301
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if (process.argv[1] !== undefined && pathToFileURL(process.argv[1]).href === import.meta.url) {
|
|
335
|
-
main().catch((err) => {
|
|
336
|
-
console.error("fatal:", err);
|
|
337
|
-
process.exit(1);
|
|
338
|
-
});
|
|
339
|
-
}
|
|
302
|
+
main().catch((err) => {
|
|
303
|
+
console.error("fatal:", err);
|
|
304
|
+
process.exit(1);
|
|
305
|
+
});
|