@edspencer/paddock 0.57.0 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/packages/server/dist/cli/here.d.ts +25 -0
- package/packages/server/dist/cli/here.js +104 -0
- package/packages/server/dist/cli/import-chats.js +6 -1
- package/packages/server/dist/cli/paddock.d.ts +30 -1
- package/packages/server/dist/cli/paddock.js +244 -46
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/{InstanceConfigPage-Bypd_quL.js → InstanceConfigPage-Cli_WHsM.js} +1 -1
- package/packages/web/dist/assets/{MarkdownRenderer-Co8w9iuX.js → MarkdownRenderer-BYK9nmr4.js} +1 -1
- package/packages/web/dist/assets/{ProjectRedirect-DXrA24Cy.js → ProjectRedirect-DWcMpdZJ.js} +1 -1
- package/packages/web/dist/assets/{ProjectView-B9opP7Sv.js → ProjectView-CvD9WQsW.js} +3 -3
- package/packages/web/dist/assets/{ProjectsGrid-D2cwb2jY.js → ProjectsGrid-Bf2cx9-Z.js} +1 -1
- package/packages/web/dist/assets/{arc-B--FIRB8.js → arc-CZ9CFd-g.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-B6dRWZZ6.js → architectureDiagram-3BPJPVTR-BzOXUlLu.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-DLxFAvvf.js → blockDiagram-GPEHLZMM-Bnkcx-Fn.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-CNee5ks4.js → c4Diagram-AAUBKEIU-C-nqckPS.js} +1 -1
- package/packages/web/dist/assets/channel-By51cUN7.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-DObw03Xl.js → chunk-2J33WTMH-jVn7J_5N.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-CH2SR6nN.js → chunk-4BX2VUAB-D5eys7Hi.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-WlVpegT3.js → chunk-55IACEB6-CqpdXIoL.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-g8GT9Rgd.js → chunk-727SXJPM-SQikk09l.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-DbN4PsDF.js → chunk-AQP2D5EJ-rfBNpUwD.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-DSN3BjFG.js → chunk-FMBD7UC4-CnL0tA20.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-D9R4le0z.js → chunk-ND2GUHAM-CZGi4swC.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-D4YgRaKH.js → chunk-QZHKN3VN-Du4WKuCj.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-sIDgFl-L.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-sIDgFl-L.js +2 -0
- package/packages/web/dist/assets/{core-CysJ2gLj.js → core-LJ--cHnT.js} +1 -1
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-D1PB0KNu.js → cose-bilkent-S5V4N54A-CEovKrYZ.js} +1 -1
- package/packages/web/dist/assets/{dagre-BM42HDAG-C6Uuq-dq.js → dagre-BM42HDAG-jQhK8147.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-C9JvQ425.js → diagram-2AECGRRQ-BmiauiWQ.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-Cu6pdh2d.js → diagram-5GNKFQAL-VYdTkqGm.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-PmXcUQhY.js → diagram-KO2AKTUF-DcOTKmwo.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-9ww6_9Iq.js → diagram-LMA3HP47-DaehIKcD.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-DWQVjv_n.js → diagram-OG6HWLK6-DTqk6D4N.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-6F7xTuff.js → erDiagram-TEJ5UH35-Dj4rI8M1.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-SCiGu7m6.js → flowDiagram-I6XJVG4X-D0FWSohH.js} +1 -1
- package/packages/web/dist/assets/{format-yf7Vf9kR.js → format-CgkZTf5F.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-BNpPwxkL.js → ganttDiagram-6RSMTGT7-DgaRh12R.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-C0gyAJjW.js → gitGraphDiagram-PVQCEYII-5qiry4ao.js} +1 -1
- package/packages/web/dist/assets/{index-B-YuzkF6.js → index-CcYeBUNk.js} +2 -2
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-CJ2CFPfy.js → infoDiagram-5YYISTIA-CZmDwouw.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-Cs0D32Ry.js → ishikawaDiagram-YF4QCWOH-knkHstDa.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-Bc9fuI2i.js → journeyDiagram-JHISSGLW-ByfdmIVy.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-D_tC7QfC.js → kanban-definition-UN3LZRKU-DSzly2xb.js} +1 -1
- package/packages/web/dist/assets/{linear-BzGHkF9K.js → linear-BX6qYIpB.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-DlbsYzE-.js → mermaid.core-BXPz6_-1.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-Bxeh-ToD.js → mindmap-definition-RKZ34NQL-fzfZWy91.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-CDmilo8S.js → pieDiagram-4H26LBE5-M99j1K9t.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-D14-JyE9.js → quadrantDiagram-W4KKPZXB-B7fKq1ce.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-CJ1PUGu-.js → requirementDiagram-4Y6WPE33-xCBFrXav.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-BkUK73W0.js → sankeyDiagram-5OEKKPKP-Dp9KLMhd.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-sbcvsuLp.js → sequenceDiagram-3UESZ5HK-CNNVWpta.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-DV-7BdHK.js → stateDiagram-AJRCARHV-CzKRXREb.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-LKJwobEd.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-C5su21G_.js → timeline-definition-PNZ67QCA-DfvQTe5J.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-JNkIJAaQ.js → vennDiagram-CIIHVFJN-2PBM01eq.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-qcz6t2vR.js → wardley-L42UT6IY-DC1EIjci.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-CMrOIZ-c.js → wardleyDiagram-YWT4CUSO-4kYoKkfS.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-C9q6Oy9u.js → xychartDiagram-2RQKCTM6-Bin8O1V8.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-Bw0A52CB.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-itf_kt-q.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-itf_kt-q.js +0 -2
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-BlkGdXKW.js +0 -2
package/package.json
CHANGED
|
@@ -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
|
+
}
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
* Applied to the environment BEFORE the config is read.
|
|
38
38
|
* --json Emit the raw result as JSON instead of prose.
|
|
39
39
|
*/
|
|
40
|
+
import { pathToFileURL } from "node:url";
|
|
40
41
|
import { loadPaddockConfig } from "../config.js";
|
|
41
42
|
import { ProjectStore, ROOT_KEY } from "../projects.js";
|
|
42
43
|
import { HerdctlService } from "../herdctl.js";
|
|
@@ -187,7 +188,11 @@ async function main() {
|
|
|
187
188
|
}
|
|
188
189
|
}
|
|
189
190
|
// Only run when invoked directly, so the arg parser stays unit-testable.
|
|
190
|
-
|
|
191
|
+
// `pathToFileURL`, not `"file://" + argv[1]`: the naive concatenation leaves any
|
|
192
|
+
// space or non-ASCII character in the path un-encoded, so the comparison fails
|
|
193
|
+
// and the script silently does nothing when run from such a directory — the same
|
|
194
|
+
// percent-encoding trap as #636.
|
|
195
|
+
if (process.argv[1] !== undefined && pathToFileURL(process.argv[1]).href === import.meta.url) {
|
|
191
196
|
main()
|
|
192
197
|
.then((code) => process.exit(code))
|
|
193
198
|
.catch((err) => {
|
|
@@ -1,2 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export {
|
|
2
|
+
export interface CliOptions {
|
|
3
|
+
port?: string;
|
|
4
|
+
host?: string;
|
|
5
|
+
dataDir?: string;
|
|
6
|
+
here: boolean;
|
|
7
|
+
open: boolean;
|
|
8
|
+
verbose: boolean;
|
|
9
|
+
help: boolean;
|
|
10
|
+
version: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** A usage error. Thrown rather than exiting, so `parseArgs` stays testable. */
|
|
13
|
+
export declare class CliError extends Error {
|
|
14
|
+
}
|
|
15
|
+
export declare function parseArgs(argv: string[]): CliOptions;
|
|
16
|
+
/**
|
|
17
|
+
* Why this Node is unusable, or `undefined` if it is fine.
|
|
18
|
+
*
|
|
19
|
+
* Split from the exit so it can be tested. `engines` only warns by default, and
|
|
20
|
+
* the failure it eventually produces is an unexplained syntax error deep inside
|
|
21
|
+
* a dependency — which is nobody's idea of a first-run experience.
|
|
22
|
+
*/
|
|
23
|
+
export declare function nodeVersionProblem(nodeVersion: string): string | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Translate a listen failure into something a human can act on.
|
|
26
|
+
*
|
|
27
|
+
* `EADDRINUSE` on 4000 is the single most likely first-run failure — it is a
|
|
28
|
+
* popular port and Paddock's own default — and Node's raw error names neither
|
|
29
|
+
* the port nor the flag that fixes it.
|
|
30
|
+
*/
|
|
31
|
+
export declare function explainListenError(err: unknown, host: string, port: string): string;
|
|
@@ -1,34 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* `paddock` — the npx / global-install entrypoint (#
|
|
3
|
+
* `paddock` — the npx / global-install entrypoint (#637, #638).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* uses.
|
|
5
|
+
* The user-facing front door for `npx @edspencer/paddock`: someone who has never
|
|
6
|
+
* seen this repo runs one command and gets a working instance on localhost.
|
|
7
|
+
* Everything here is preflight, defaults and presentation — the server itself is
|
|
8
|
+
* untouched, and `start()` is the same lifecycle `node dist/index.js` uses.
|
|
10
9
|
*
|
|
11
|
-
* The design constraint that shapes this file: the server is already
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* The design constraint that shapes this file: the server is already *tolerant*
|
|
11
|
+
* — every setting has a default, an empty data dir self-initializes, and auth
|
|
12
|
+
* defaults to `none` on a loopback bind. So a bare `start()` would in fact boot.
|
|
13
|
+
* What it would NOT do is tell a first-time user why their chats fail, where
|
|
14
|
+
* their data went, or which of the several hundred lines of JSON that just
|
|
15
|
+
* scrolled past contained the URL.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* relative to cwd and would litter whatever directory you happened to run
|
|
21
|
-
* npx from (and silently give you a different instance next time).
|
|
22
|
-
* 3. `claude` on PATH — prepended from our own node_modules/.bin. Chats do not
|
|
23
|
-
* need it (the SDK runtime resolves its own bundled binary), but the
|
|
24
|
-
* sweeper and triggers shell out to `claude` by name.
|
|
25
|
-
* 4. Credentials — a warning, never a hard failure: we cannot reliably detect
|
|
26
|
-
* a macOS Keychain login, so refusing to start would lock out valid users.
|
|
17
|
+
* Everything below the arg parser is side-effecting by nature, so the pure parts
|
|
18
|
+
* (`parseArgs`, `nodeVersionProblem`, `explainListenError`) are exported and
|
|
19
|
+
* unit-tested; the rest is exercised by running the built binary.
|
|
27
20
|
*/
|
|
28
21
|
import fs from "node:fs";
|
|
29
22
|
import os from "node:os";
|
|
30
23
|
import path from "node:path";
|
|
31
|
-
import {
|
|
24
|
+
import { spawn } from "node:child_process";
|
|
25
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
26
|
+
import { HERE_MARKER, isHereWorkspace, countClaudeSessions, ensureGitignored } from "./here.js";
|
|
32
27
|
const MIN_NODE_MAJOR = 22;
|
|
33
28
|
/** This module's own directory: `<…>/packages/server/dist/cli`. */
|
|
34
29
|
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -43,8 +38,7 @@ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
|
43
38
|
* deps at `<pkg>/node_modules` only — two levels FURTHER up
|
|
44
39
|
*
|
|
45
40
|
* A fixed `../..` hop is right for the repo and wrong for the published tarball,
|
|
46
|
-
* where it lands on `packages/server` and finds no `node_modules` at all.
|
|
47
|
-
* up is correct for both without branching on which one we are in.
|
|
41
|
+
* where it lands on `packages/server` and finds no `node_modules` at all.
|
|
48
42
|
*/
|
|
49
43
|
function findUp(rel) {
|
|
50
44
|
let dir = moduleDir;
|
|
@@ -59,14 +53,23 @@ function findUp(rel) {
|
|
|
59
53
|
}
|
|
60
54
|
/** Nearest ancestor holding a `package.json` — `packages/server` in both layouts. */
|
|
61
55
|
const packageRoot = findUp("package.json") ?? path.resolve(moduleDir, "../..");
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
/** A usage error. Thrown rather than exiting, so `parseArgs` stays testable. */
|
|
57
|
+
export class CliError extends Error {
|
|
58
|
+
}
|
|
59
|
+
export function parseArgs(argv) {
|
|
60
|
+
const opts = {
|
|
61
|
+
here: false,
|
|
62
|
+
open: false,
|
|
63
|
+
verbose: false,
|
|
64
|
+
help: false,
|
|
65
|
+
version: false,
|
|
66
|
+
};
|
|
64
67
|
for (let i = 0; i < argv.length; i++) {
|
|
65
68
|
const arg = argv[i];
|
|
66
69
|
const next = () => {
|
|
67
70
|
const v = argv[++i];
|
|
68
71
|
if (v === undefined)
|
|
69
|
-
|
|
72
|
+
throw new CliError(`${arg} needs a value`);
|
|
70
73
|
return v;
|
|
71
74
|
};
|
|
72
75
|
switch (arg) {
|
|
@@ -81,6 +84,16 @@ function parseArgs(argv) {
|
|
|
81
84
|
case "--data-dir":
|
|
82
85
|
opts.dataDir = next();
|
|
83
86
|
break;
|
|
87
|
+
case "--here":
|
|
88
|
+
opts.here = true;
|
|
89
|
+
break;
|
|
90
|
+
case "-o":
|
|
91
|
+
case "--open":
|
|
92
|
+
opts.open = true;
|
|
93
|
+
break;
|
|
94
|
+
case "--verbose":
|
|
95
|
+
opts.verbose = true;
|
|
96
|
+
break;
|
|
84
97
|
case "-h":
|
|
85
98
|
case "--help":
|
|
86
99
|
opts.help = true;
|
|
@@ -90,7 +103,7 @@ function parseArgs(argv) {
|
|
|
90
103
|
opts.version = true;
|
|
91
104
|
break;
|
|
92
105
|
default:
|
|
93
|
-
|
|
106
|
+
throw new CliError(`unknown option: ${arg}\nRun \`paddock --help\` for usage.`);
|
|
94
107
|
}
|
|
95
108
|
}
|
|
96
109
|
return opts;
|
|
@@ -117,29 +130,55 @@ Options
|
|
|
117
130
|
-p, --port <port> HTTP/WS port (default 4000, or $PORT)
|
|
118
131
|
--host <host> Bind address (default 127.0.0.1)
|
|
119
132
|
-d, --data-dir <path> Projects + state (default ~/.paddock, or $PADDOCK_DATA_DIR)
|
|
133
|
+
--here Open the CURRENT directory as the workspace (see below)
|
|
134
|
+
-o, --open Open the app in your browser once it is listening
|
|
135
|
+
--verbose Show the server's own logs (quiet by default)
|
|
120
136
|
-v, --version Print the Paddock version and exit
|
|
121
137
|
-h, --help Show this help
|
|
122
138
|
|
|
139
|
+
Opening a directory (--here)
|
|
140
|
+
Run \`paddock --here\` inside a project and Paddock opens THAT directory as its
|
|
141
|
+
workspace: Claude works in your files, and any Claude Code sessions you already
|
|
142
|
+
have for the directory are offered for import. It:
|
|
143
|
+
|
|
144
|
+
· creates .paddock/ in the directory for this workspace's own state
|
|
145
|
+
· creates .chats/ for transcripts, and adds both to .gitignore
|
|
146
|
+
· links ~/.claude/projects/<encoded-dir> at this workspace
|
|
147
|
+
|
|
148
|
+
Once done, later runs in the same directory resume it — no flag needed.
|
|
149
|
+
Without --here, Paddock never touches the directory you ran it from.
|
|
150
|
+
|
|
123
151
|
Credentials
|
|
124
152
|
Paddock drives Claude Code, so it needs Claude credentials in the
|
|
125
153
|
environment: CLAUDE_CODE_OAUTH_TOKEN (Max/Pro) or ANTHROPIC_API_KEY (API
|
|
126
154
|
billing). If you already use Claude Code on this machine, an existing login
|
|
127
155
|
under ~/.claude is picked up automatically. Otherwise run \`claude setup-token\`.
|
|
128
156
|
|
|
157
|
+
Your data
|
|
158
|
+
Everything lives in one directory — ~/.paddock unless you pass --data-dir.
|
|
159
|
+
Projects, chat transcripts and settings all persist there between runs. Move
|
|
160
|
+
that directory to move your instance; delete it to start over. Nothing is
|
|
161
|
+
stored anywhere else.
|
|
162
|
+
|
|
129
163
|
Notes
|
|
130
164
|
Binds loopback with authentication disabled, which is safe for a laptop. To
|
|
131
165
|
expose it on a network, set PADDOCK_AUTH_MODE first — Paddock refuses to bind
|
|
132
166
|
a routable interface wide open. See AUTH.md.
|
|
133
167
|
|
|
134
168
|
Docs: https://github.com/edspencer/paddock`;
|
|
135
|
-
/**
|
|
136
|
-
*
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Why this Node is unusable, or `undefined` if it is fine.
|
|
171
|
+
*
|
|
172
|
+
* Split from the exit so it can be tested. `engines` only warns by default, and
|
|
173
|
+
* the failure it eventually produces is an unexplained syntax error deep inside
|
|
174
|
+
* a dependency — which is nobody's idea of a first-run experience.
|
|
175
|
+
*/
|
|
176
|
+
export function nodeVersionProblem(nodeVersion) {
|
|
177
|
+
const major = Number(nodeVersion.split(".")[0]);
|
|
178
|
+
if (!Number.isFinite(major) || major >= MIN_NODE_MAJOR)
|
|
179
|
+
return undefined;
|
|
180
|
+
return (`Node ${nodeVersion} is too old — Paddock needs Node ${MIN_NODE_MAJOR}+.\n` +
|
|
181
|
+
`Upgrade Node, then re-run. (nodejs.org, or \`nvm install ${MIN_NODE_MAJOR}\`)`);
|
|
143
182
|
}
|
|
144
183
|
/**
|
|
145
184
|
* Put our bundled `claude` on PATH for child processes.
|
|
@@ -209,8 +248,96 @@ function noteFirstRun(dataDir) {
|
|
|
209
248
|
"",
|
|
210
249
|
].join("\n"));
|
|
211
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Say what `--here` is about to do to this directory, on the run that does it.
|
|
253
|
+
*
|
|
254
|
+
* The flag IS the consent — there is no prompt, and no `--yes` to remember.
|
|
255
|
+
* But consent only means something if you can know what you agreed to, so the
|
|
256
|
+
* effects are announced rather than asked about. Printed only on the first
|
|
257
|
+
* `--here` in a directory; resuming an already-opened one says nothing.
|
|
258
|
+
*/
|
|
259
|
+
function announceHereConsent(dir, dataDir) {
|
|
260
|
+
console.log([
|
|
261
|
+
"",
|
|
262
|
+
` Opening ${dir} as a Paddock workspace.`,
|
|
263
|
+
` · ${path.relative(dir, dataDir) || HERE_MARKER}/ and .chats/ created here, and added to .gitignore`,
|
|
264
|
+
" · ~/.claude sessions for this directory are linked at the workspace",
|
|
265
|
+
" Later runs here resume it — no flag needed.",
|
|
266
|
+
"",
|
|
267
|
+
].join("\n"));
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Tell the user their existing Claude history could be opened here.
|
|
271
|
+
*
|
|
272
|
+
* The only way anyone discovers `--here` exists. Read-only: a bare run counts
|
|
273
|
+
* transcripts and prints, and changes nothing on disk.
|
|
274
|
+
*/
|
|
275
|
+
function offerHereIfSessionsExist(dir) {
|
|
276
|
+
const claudeHome = process.env.CLAUDE_HOME ?? path.join(os.homedir(), ".claude");
|
|
277
|
+
let sessions = 0;
|
|
278
|
+
try {
|
|
279
|
+
sessions = countClaudeSessions(claudeHome, dir);
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return; // detection is a courtesy; never let it break a boot
|
|
283
|
+
}
|
|
284
|
+
if (sessions === 0)
|
|
285
|
+
return;
|
|
286
|
+
console.log([
|
|
287
|
+
"",
|
|
288
|
+
` This directory has ${sessions} Claude Code session${sessions === 1 ? "" : "s"}.`,
|
|
289
|
+
" Run `paddock --here` to open it as a workspace and import them.",
|
|
290
|
+
"",
|
|
291
|
+
].join("\n"));
|
|
292
|
+
}
|
|
293
|
+
/** Open `url` in the default browser. Best-effort: failing is never fatal. */
|
|
294
|
+
function openBrowser(url) {
|
|
295
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
296
|
+
try {
|
|
297
|
+
const child = spawn(cmd, [url], {
|
|
298
|
+
stdio: "ignore",
|
|
299
|
+
detached: true,
|
|
300
|
+
shell: process.platform === "win32", // `start` is a cmd builtin, not a binary
|
|
301
|
+
});
|
|
302
|
+
// A headless box has no xdg-open. The URL is printed either way, so an
|
|
303
|
+
// unopenable browser is a non-event rather than a failed run.
|
|
304
|
+
child.on("error", () => { });
|
|
305
|
+
child.unref();
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
/* as above */
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Translate a listen failure into something a human can act on.
|
|
313
|
+
*
|
|
314
|
+
* `EADDRINUSE` on 4000 is the single most likely first-run failure — it is a
|
|
315
|
+
* popular port and Paddock's own default — and Node's raw error names neither
|
|
316
|
+
* the port nor the flag that fixes it.
|
|
317
|
+
*/
|
|
318
|
+
export function explainListenError(err, host, port) {
|
|
319
|
+
const code = err?.code;
|
|
320
|
+
if (code === "EADDRINUSE") {
|
|
321
|
+
return (`port ${port} is already in use.\n` +
|
|
322
|
+
`Something else is listening on ${host}:${port} — possibly another Paddock.\n` +
|
|
323
|
+
`Pick a different port: paddock --port ${Number(port) + 1}`);
|
|
324
|
+
}
|
|
325
|
+
if (code === "EACCES") {
|
|
326
|
+
return (`not allowed to bind ${host}:${port}.\n` +
|
|
327
|
+
`Ports below 1024 need elevated privileges — use a higher one: paddock --port 4000`);
|
|
328
|
+
}
|
|
329
|
+
return String(err?.message ?? err);
|
|
330
|
+
}
|
|
212
331
|
async function main() {
|
|
213
|
-
|
|
332
|
+
let opts;
|
|
333
|
+
try {
|
|
334
|
+
opts = parseArgs(process.argv.slice(2));
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
if (err instanceof CliError)
|
|
338
|
+
fail(err.message);
|
|
339
|
+
throw err;
|
|
340
|
+
}
|
|
214
341
|
if (opts.help) {
|
|
215
342
|
console.log(USAGE);
|
|
216
343
|
return;
|
|
@@ -219,29 +346,100 @@ async function main() {
|
|
|
219
346
|
console.log(readVersion());
|
|
220
347
|
return;
|
|
221
348
|
}
|
|
222
|
-
|
|
349
|
+
const problem = nodeVersionProblem(process.versions.node);
|
|
350
|
+
if (problem !== undefined)
|
|
351
|
+
fail(problem);
|
|
223
352
|
addBundledBinsToPath();
|
|
353
|
+
// Canonical, because working directories "MUST be canonical so session
|
|
354
|
+
// discovery (which encodes the real path) can find Claude transcripts"
|
|
355
|
+
// (config.ts). On macOS a /tmp vs /private/tmp divergence would silently
|
|
356
|
+
// break adoption — and macOS is most laptops.
|
|
357
|
+
let cwd = process.cwd();
|
|
358
|
+
try {
|
|
359
|
+
cwd = fs.realpathSync(cwd);
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
/* keep the literal path; the server will surface any real problem */
|
|
363
|
+
}
|
|
364
|
+
// `--here` is the consent. A flagless run in a directory ALREADY opened this
|
|
365
|
+
// way resumes it — the git model, where `--here` is `git init` and `.paddock/`
|
|
366
|
+
// is `.git`. A flagless run anywhere else touches nothing.
|
|
367
|
+
const resuming = !opts.here && isHereWorkspace(cwd);
|
|
368
|
+
const hereMode = opts.here || resuming;
|
|
224
369
|
// Apply CLI defaults as env vars. This is the whole integration surface: the
|
|
225
370
|
// server resolves config inside `buildApp()`, so anything set before the
|
|
226
371
|
// dynamic import below is picked up with no special-casing in config.ts.
|
|
227
372
|
// Precedence is explicit flag > existing env > our default.
|
|
228
|
-
const dataDir = path.resolve(opts.dataDir ??
|
|
229
|
-
|
|
373
|
+
const dataDir = path.resolve(opts.dataDir ??
|
|
374
|
+
process.env.PADDOCK_DATA_DIR ??
|
|
375
|
+
(hereMode ? path.join(cwd, HERE_MARKER) : path.join(os.homedir(), ".paddock")));
|
|
376
|
+
if (hereMode) {
|
|
377
|
+
if (!resuming)
|
|
378
|
+
announceHereConsent(cwd, dataDir);
|
|
379
|
+
// The root workspace IS `projectsRoot` (its key is ""), so pointing this at
|
|
380
|
+
// the user's directory makes that directory the workspace. No schema change,
|
|
381
|
+
// no new project type — see cli/here.ts.
|
|
382
|
+
process.env.PADDOCK_PROJECTS_DIR = cwd;
|
|
383
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
384
|
+
ensureGitignored(cwd, `/${HERE_MARKER}/`);
|
|
385
|
+
ensureGitignored(cwd, "/.chats/");
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
noteFirstRun(dataDir);
|
|
389
|
+
offerHereIfSessionsExist(cwd);
|
|
390
|
+
}
|
|
230
391
|
process.env.PADDOCK_DATA_DIR = dataDir;
|
|
231
392
|
if (opts.port)
|
|
232
393
|
process.env.PORT = opts.port;
|
|
233
394
|
if (opts.host)
|
|
234
395
|
process.env.HOST = opts.host;
|
|
396
|
+
// Quiet by default. The server emits a few hundred lines on the way up, which
|
|
397
|
+
// scrolls the one line the user actually needs — the URL — off the top of the
|
|
398
|
+
// terminal. TWO loggers have to be told, and they are unrelated:
|
|
399
|
+
// LOG_LEVEL Paddock's own pino logger (config.ts).
|
|
400
|
+
// HERDCTL_LOG_LEVEL @herdctl/core's `createLogger`, which writes the
|
|
401
|
+
// `[fleet-manager] …` lines via console.info — pino's
|
|
402
|
+
// level cannot reach those.
|
|
403
|
+
// An explicit value for either is left alone, and `--verbose` skips both.
|
|
404
|
+
if (!opts.verbose) {
|
|
405
|
+
if (process.env.LOG_LEVEL === undefined)
|
|
406
|
+
process.env.LOG_LEVEL = "warn";
|
|
407
|
+
if (process.env.HERDCTL_LOG_LEVEL === undefined)
|
|
408
|
+
process.env.HERDCTL_LOG_LEVEL = "warn";
|
|
409
|
+
}
|
|
235
410
|
warnIfNoCredentials(process.env.CLAUDE_HOME ?? path.join(os.homedir(), ".claude"));
|
|
236
411
|
const port = process.env.PORT ?? "4000";
|
|
237
412
|
const host = process.env.HOST ?? "127.0.0.1";
|
|
238
413
|
// Imported dynamically, AFTER the env above is set: a static import would
|
|
239
414
|
// pull in app.ts -> config.ts and resolve config against the wrong data dir.
|
|
240
415
|
const { start } = await import("../start.js");
|
|
241
|
-
|
|
242
|
-
|
|
416
|
+
try {
|
|
417
|
+
await start();
|
|
418
|
+
}
|
|
419
|
+
catch (err) {
|
|
420
|
+
fail(explainListenError(err, host, port));
|
|
421
|
+
}
|
|
422
|
+
const url = `http://${host}:${port}`;
|
|
423
|
+
// ALWAYS name the workspace, not just in --here mode. Behaviour that varies
|
|
424
|
+
// with cwd is only safe if it is observable: "why is this instance different
|
|
425
|
+
// today" should be answerable by reading the two lines the tool just printed.
|
|
426
|
+
console.log([
|
|
427
|
+
"",
|
|
428
|
+
` Paddock is running at ${url}`,
|
|
429
|
+
` Workspace: ${hereMode ? cwd : dataDir}${resuming ? " (resumed)" : ""}`,
|
|
430
|
+
" Press Ctrl-C to stop.",
|
|
431
|
+
"",
|
|
432
|
+
].join("\n"));
|
|
433
|
+
if (opts.open)
|
|
434
|
+
openBrowser(url);
|
|
435
|
+
}
|
|
436
|
+
// Run only when executed directly, so the unit tests can import the pure parts.
|
|
437
|
+
// `pathToFileURL`, not `"file://" + argv[1]`: the naive concatenation breaks on
|
|
438
|
+
// any path containing a space or non-ASCII character — the same percent-encoding
|
|
439
|
+
// trap as #636.
|
|
440
|
+
if (process.argv[1] !== undefined && pathToFileURL(process.argv[1]).href === import.meta.url) {
|
|
441
|
+
main().catch((err) => {
|
|
442
|
+
console.error("fatal:", err);
|
|
443
|
+
process.exit(1);
|
|
444
|
+
});
|
|
243
445
|
}
|
|
244
|
-
main().catch((err) => {
|
|
245
|
-
console.error("fatal:", err);
|
|
246
|
-
process.exit(1);
|
|
247
|
-
});
|
package/packages/web/dist/assets/{InstanceConfigPage-Bypd_quL.js → InstanceConfigPage-Cli_WHsM.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as o,b as f,j as e,A as b,k as E,l as A,m as O,M as F,n as I}from"./index-
|
|
1
|
+
import{r as o,b as f,j as e,A as b,k as E,l as A,m as O,M as F,n as I}from"./index-CcYeBUNk.js";function $(){const[t,s]=o.useState(null),[l,i]=o.useState(!0),[r,c]=o.useState(null),[d,m]=o.useState({}),[h,k]=o.useState(!1),[j,p]=o.useState(null),[N,u]=o.useState(!1);o.useEffect(()=>{let a=!0;return i(!0),f.getInstanceConfig().then(n=>{a&&(s(n),c(null))}).catch(n=>a&&c(n instanceof Error?n.message:String(n))).finally(()=>a&&i(!1)),()=>{a=!1}},[]);const v=(a,n)=>{u(!1),p(null),m(g=>({...g,[a]:n}))},S=a=>Object.prototype.hasOwnProperty.call(d,a.key)?d[a.key]:a.value,y=o.useMemo(()=>t?t.groups.flatMap(a=>a.fields):[],[t]),x=o.useMemo(()=>y.filter(a=>a.editable&&!a.envOverridden).filter(a=>Object.prototype.hasOwnProperty.call(d,a.key)).filter(a=>!B(d[a.key],a.value)).map(a=>a.key),[y,d]),w=async()=>{if(x.length===0)return;const a={};for(const n of x)a[n]=d[n];k(!0),p(null);try{await f.updateInstanceConfig(a);const n=await f.getInstanceConfig();s(n),m({}),u(!0)}catch(n){p(n instanceof A||n instanceof Error?n.message:String(n))}finally{k(!1)}},C=()=>{m({}),p(null),u(!1)};return e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"min-h-0 flex-1 overflow-y-auto px-4 py-5 sm:px-6",children:e.jsxs("div",{className:"mx-auto max-w-2xl",children:[e.jsx(L,{saved:N,configPath:t==null?void 0:t.configPath}),l&&e.jsx("p",{className:"text-sm text-paddock-500",children:"Loading…"}),r&&e.jsxs("p",{className:"text-sm text-red-600 dark:text-red-400",role:"alert",children:["Failed to load settings: ",r]}),t&&t.groups.map(a=>e.jsx(M,{title:a.label,description:a.description,children:e.jsx("div",{className:"grid grid-cols-1 gap-4",children:a.fields.map(n=>e.jsx(P,{field:n,value:S(n),onChange:g=>v(n.key,g)},n.key))})},a.id))]})}),t&&e.jsxs("footer",{className:"flex items-center gap-3 border-t border-paddock-200 px-4 py-3 dark:border-paddock-800 sm:px-6",children:[j&&e.jsxs("span",{className:"flex items-center gap-1.5 text-[13px] text-red-600 dark:text-red-400",role:"alert",children:[e.jsx(b,{width:14,height:14,className:"shrink-0"}),j]}),e.jsx("span",{className:"ml-auto text-[12px] text-paddock-400",children:x.length>0?`${x.length} unsaved change${x.length===1?"":"s"}`:"No changes"}),e.jsx("button",{type:"button",className:"btn-subtle",onClick:C,disabled:x.length===0||h,children:"Reset"}),e.jsx("button",{type:"button",className:"btn-primary",onClick:w,disabled:x.length===0||h,children:h?"Saving…":"Save changes"})]})]})}function L({saved:t,configPath:s}){return e.jsxs("div",{role:"status",className:`mb-6 flex items-start gap-2 rounded-lg border px-3 py-2.5 text-[13px] leading-snug ${t?"border-emerald-300 bg-emerald-50 text-emerald-800 dark:border-emerald-700/60 dark:bg-emerald-900/20 dark:text-emerald-300":"border-amber-300 bg-amber-50 text-amber-800 dark:border-amber-700/60 dark:bg-amber-900/20 dark:text-amber-300"}`,children:[t?e.jsx(E,{width:15,height:15,className:"mt-0.5 shrink-0"}):e.jsx(b,{width:15,height:15,className:"mt-0.5 shrink-0"}),e.jsx("span",{children:t?e.jsxs(e.Fragment,{children:[e.jsx("strong",{children:"Saved to disk."})," These changes take effect only after the server restarts."]}):e.jsxs(e.Fragment,{children:["Changes here are written to ",e.jsx("code",{className:"font-mono text-[12px]",children:T(s)})," ","and take effect only after the server restarts — the running instance keeps its current config until then."]})})]})}function M({title:t,description:s,children:l}){return e.jsxs("section",{className:"mb-6",children:[e.jsx("h2",{className:"text-sm font-semibold uppercase tracking-wide text-paddock-500",children:t}),s&&e.jsx("p",{className:"mb-3 mt-0.5 text-[13px] text-paddock-500",children:s}),e.jsx("div",{className:`card ${s?"":"mt-2"}`,children:l})]})}function P({field:t,value:s,onChange:l}){const i=!t.editable||t.envOverridden,r=`cfg-${t.key}`;return e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("label",{htmlFor:i?void 0:r,className:"text-sm font-medium",children:[t.label,t.sensitive&&e.jsx("span",{className:"ml-1.5 align-middle text-[10px] font-semibold uppercase tracking-wide text-amber-600 dark:text-amber-400",children:"sensitive"})]}),t.type==="boolean"&&!i&&e.jsx("input",{id:r,type:"checkbox",className:"h-4 w-4 accent-accent",checked:!!s,onChange:c=>l(c.target.checked)})]}),t.help&&e.jsx("p",{className:"mt-0.5 text-[12px] leading-snug text-paddock-500",children:t.help}),i?e.jsx(V,{field:t,value:s}):t.type!=="boolean"&&e.jsx(R,{field:t,value:s,onChange:l,inputId:r}),t.envOverridden&&e.jsxs("p",{className:"mt-1 flex items-start gap-1.5 text-[12px] leading-snug text-amber-600 dark:text-amber-400",children:[e.jsx(b,{width:13,height:13,className:"mt-0.5 shrink-0"}),e.jsxs("span",{children:["Overridden by environment variable ",e.jsx("code",{className:"font-mono",children:t.envVar})," — edit that env var (and restart) to change it."]})]})]})}function R({field:t,value:s,onChange:l,inputId:i}){if(t.type==="enum")return e.jsx("select",{id:i,className:"input mt-1.5",value:String(s??""),onChange:r=>l(r.target.value),children:(t.enumValues??[]).map(r=>e.jsx("option",{value:r,children:r},r))});if(t.type==="number")return e.jsx("input",{id:i,type:"number",className:"input mt-1.5",value:s==null?"":String(s),placeholder:t.default===null?"default":String(t.default),onChange:r=>l(r.target.value===""?null:Number(r.target.value))});if(t.type==="text"){const r=s==null||s===t.default,c=s==null?String(t.default??""):String(s),d=typeof t.default=="string"&&!r;return e.jsxs("div",{className:"mt-1.5",children:[e.jsx("textarea",{id:i,className:"input min-h-[9rem] resize-y font-mono text-[12px] leading-relaxed",rows:10,spellCheck:!1,value:c,onChange:m=>l(m.target.value)}),e.jsxs("div",{className:"mt-1 flex items-center justify-between gap-3 text-[12px] text-paddock-500",children:[e.jsx("span",{children:r?"Using the built-in default.":s===""?"Empty — nothing will be appended.":`${c.length.toLocaleString()} characters.`}),d&&e.jsx("button",{type:"button",className:"shrink-0 underline underline-offset-2 hover:text-paddock-700 dark:hover:text-paddock-300",onClick:()=>l(null),children:"Restore default"})]})]})}if(t.type==="string-list"){const r=Array.isArray(s)?s.join(", "):String(s??"");return e.jsx("input",{id:i,type:"text",className:"input mt-1.5",value:r,onChange:c=>l(c.target.value.split(",").map(d=>d.trim()).filter(Boolean))})}return e.jsx("input",{id:i,type:"text",className:"input mt-1.5",value:s==null?"":String(s),placeholder:t.default===null?"":String(t.default),onChange:r=>l(r.target.value)})}function V({field:t,value:s}){return e.jsx("div",{className:"mt-1.5 rounded-md border border-dashed border-paddock-300 bg-paddock-50 px-2.5 py-1.5 text-[13px] text-paddock-600 dark:border-paddock-700 dark:bg-paddock-900/40 dark:text-paddock-300",children:t.type==="boolean"?e.jsx("span",{className:"font-mono",children:s?"true":"false"}):s==null||s===""?e.jsx("span",{className:"italic text-paddock-400",children:t.type==="text"&&s===""?"(empty — nothing appended)":"(not set)"}):t.type==="text"?e.jsx("span",{className:"block max-h-64 overflow-y-auto whitespace-pre-wrap font-mono text-[12px] leading-relaxed",children:String(s)}):e.jsx("span",{className:"break-all font-mono",children:Array.isArray(s)?s.join(", "):String(s)})})}function B(t,s){return Array.isArray(t)&&Array.isArray(s)?t.length===s.length&&t.every((l,i)=>l===s[i]):t===s}function T(t){if(!t)return"paddock.config.yaml";const s=t.split("/");return s[s.length-1]||t}function z(){const t=O(),s=(t==null?void 0:t.openNav)??(()=>{});return e.jsxs("div",{className:"flex h-full min-h-0 flex-col",children:[e.jsxs("header",{className:"pt-safe flex items-center gap-2 border-b border-paddock-200 px-3 pb-2.5 dark:border-paddock-800 sm:px-6 lg:py-4",children:[e.jsx("button",{type:"button",onClick:s,"aria-label":"Open menu",className:"btn-subtle -ml-1 shrink-0 px-2 py-1.5 lg:hidden",children:e.jsx(F,{width:20,height:20})}),e.jsx(I,{width:18,height:18,className:"shrink-0 text-paddock-400"}),e.jsx("h1",{className:"text-[15px] font-semibold tracking-tight",children:"Config"})]}),e.jsx($,{})]})}export{z as InstanceConfigPage};
|
|
2
2
|
|