@edspencer/paddock 0.69.0 → 0.70.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/agent-errors.js +7 -1
- package/packages/server/dist/background-live.d.ts +16 -8
- package/packages/server/dist/background-live.js +41 -1
- package/packages/server/dist/cli/paddock.js +12 -7
- package/packages/server/dist/config.d.ts +7 -5
- package/packages/server/dist/config.js +0 -1
- package/packages/server/dist/git.d.ts +63 -8
- package/packages/server/dist/git.js +100 -20
- package/packages/server/dist/herdctl-jobs.d.ts +30 -4
- package/packages/server/dist/herdctl-jobs.js +35 -8
- package/packages/server/dist/instance-config.js +1 -2
- package/packages/server/dist/project-files.d.ts +18 -7
- package/packages/server/dist/project-files.js +38 -34
- package/packages/server/dist/projects.d.ts +1 -1
- package/packages/server/dist/projects.js +14 -7
- package/packages/server/dist/recovery-config.d.ts +0 -6
- package/packages/server/dist/recovery-config.js +0 -4
- package/packages/server/dist/routes/git.d.ts +2 -2
- package/packages/server/dist/routes/git.js +133 -1
- package/packages/server/dist/ws-protocol.d.ts +11 -1
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/{Chip-B8UU8Zpr.js → Chip-P4n88XYj.js} +1 -1
- package/packages/web/dist/assets/DiscoverPage-B7ap1I1Y.js +2 -0
- package/packages/web/dist/assets/{DiscoverView-CKN93BnT.js → DiscoverView-DxC3mmGp.js} +1 -1
- package/packages/web/dist/assets/{InstanceConfigPage-COc2rsu6.js → InstanceConfigPage-DO87_86Z.js} +1 -1
- package/packages/web/dist/assets/{MarkdownRenderer-BlUYSdHR.js → MarkdownRenderer-DatKu1Pl.js} +1 -1
- package/packages/web/dist/assets/{ProjectMenu-Davm1-P7.js → ProjectMenu-DJB7uCGt.js} +1 -1
- package/packages/web/dist/assets/{ProjectRedirect-COrC7Yvg.js → ProjectRedirect-D5Uxki7M.js} +1 -1
- package/packages/web/dist/assets/ProjectView-D8TqrBW4.js +13 -0
- package/packages/web/dist/assets/{ProjectsGrid-CfDehr5s.js → ProjectsGrid-CqQTeqdE.js} +1 -1
- package/packages/web/dist/assets/{RootHome-DniHfYAw.js → RootHome-B0si7ijq.js} +1 -1
- package/packages/web/dist/assets/{Surfaces-GzOkJJHQ.js → Surfaces-t3XF_my5.js} +1 -1
- package/packages/web/dist/assets/{arc-_I2xwzbW.js → arc-Bvnrokeb.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-GW4T_vCB.js → architectureDiagram-3BPJPVTR-Dc5OXCmK.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-rpMzVG3O.js → blockDiagram-GPEHLZMM-CFZeUqSH.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-CwxpNlW3.js → c4Diagram-AAUBKEIU-BPI5UmpX.js} +1 -1
- package/packages/web/dist/assets/channel-BGIf4GoQ.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-BG9SyFJ-.js → chunk-2J33WTMH-CVpthvPT.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-DJlbnTd4.js → chunk-4BX2VUAB-cTaBnmzz.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-C-VMlU4m.js → chunk-55IACEB6-dkgF9sAS.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-Wigu1170.js → chunk-727SXJPM-UiFZch2W.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-C5CFWtjs.js → chunk-AQP2D5EJ-IKsoQNZy.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-hQaZU9Hi.js → chunk-FMBD7UC4-Tzt_HckZ.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-AMO00QGi.js → chunk-ND2GUHAM-dbNwUFdT.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-T3F7CT-B.js → chunk-QZHKN3VN-eUsG1OkP.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-6kluxBAR.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-6kluxBAR.js +2 -0
- package/packages/web/dist/assets/{core-D1JA8p71.js → core-Duun4AtS.js} +1 -1
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-C_fPZY7X.js → cose-bilkent-S5V4N54A-KF_gNdsd.js} +1 -1
- package/packages/web/dist/assets/{dagre-BM42HDAG-nLjMp3c7.js → dagre-BM42HDAG-CXFDGeP7.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-qYzVcYYg.js → diagram-2AECGRRQ-BiefFjo3.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-DVFKMWv0.js → diagram-5GNKFQAL-B1IpANC8.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-B8T7qnCR.js → diagram-KO2AKTUF-ChdOA3EK.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-9-_-MiG8.js → diagram-LMA3HP47-BTbLDsXf.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-D4eE55Vg.js → diagram-OG6HWLK6-Bqiqt27L.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-adlALhlq.js → erDiagram-TEJ5UH35-KA9WLkV0.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-jhJEXK0D.js → flowDiagram-I6XJVG4X-CZKvoeTz.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-CHGNR8Fk.js → ganttDiagram-6RSMTGT7-o1QDzlgt.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-tBQ_60zD.js → gitGraphDiagram-PVQCEYII-D5GdOppF.js} +1 -1
- package/packages/web/dist/assets/index-4ugpmAz4.css +1 -0
- package/packages/web/dist/assets/{index-Djl4krRr.js → index-B-J2K1w2.js} +13 -13
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-D_LJZit4.js → infoDiagram-5YYISTIA-21Z9hMO-.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-CLsVMtFo.js → ishikawaDiagram-YF4QCWOH-TmbPK40i.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-DKLfsczG.js → journeyDiagram-JHISSGLW-DYAWKu-K.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-CG1dRRin.js → kanban-definition-UN3LZRKU-ZW_ZQ0DQ.js} +1 -1
- package/packages/web/dist/assets/{linear-Bc0TsEWL.js → linear-BDQKnwHn.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-7NBz7_-Q.js → mermaid.core-BHXnmT7N.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-DD-2NsoA.js → mindmap-definition-RKZ34NQL-CHVPYLQs.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-BTYpRP3j.js → pieDiagram-4H26LBE5-paqmu057.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-DScmlT0o.js → quadrantDiagram-W4KKPZXB-diM_4CGS.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-BzLuYCY3.js → requirementDiagram-4Y6WPE33-DCyNbyIz.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-CH9nUHfL.js → sankeyDiagram-5OEKKPKP-DxpEvGOy.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-vQjaE3Ay.js → sequenceDiagram-3UESZ5HK-BH4-7BdR.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-C76FMvr4.js → stateDiagram-AJRCARHV-CVPrwckU.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CMmy9TKm.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-zjhYOHh7.js → timeline-definition-PNZ67QCA-B6_yfyHl.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-BpyreyjG.js → vennDiagram-CIIHVFJN-Ic8OogjJ.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-CugQjZZ8.js → wardley-L42UT6IY-CuhgMTiv.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-CTpxg9iQ.js → wardleyDiagram-YWT4CUSO-Djeda9cE.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-COJLOF01.js → xychartDiagram-2RQKCTM6-D3hl8Mdh.js} +1 -1
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/sw.js +1 -1
- package/packages/web/dist/assets/DiscoverPage-vz1s8GYQ.js +0 -2
- package/packages/web/dist/assets/ProjectView-WIODXM93.js +0 -13
- package/packages/web/dist/assets/channel-nPAjQX8p.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DuI-4XNG.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DuI-4XNG.js +0 -2
- package/packages/web/dist/assets/index-CJT1g4_p.css +0 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CWy9AQvb.js +0 -2
|
@@ -3,31 +3,23 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Extracted from projects.ts (issue #403): the path-traversal guard plus the
|
|
5
5
|
* directory listing + file readers (text / raw bytes / kind-hinted). Pure free
|
|
6
|
-
* functions taking `(
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* functions taking `(dir, …)` — no `ProjectStore` state at all — so
|
|
7
|
+
* `ProjectStore` keeps thin delegate methods over these and the public API is
|
|
8
|
+
* unchanged. `ProjectError` codes are preserved exactly.
|
|
9
|
+
*
|
|
10
|
+
* **`dir` is the project's CONTENT dir, not `projectsRoot/<slug>`** (issue #710).
|
|
11
|
+
* These used to take `(root, slug)` and re-derive the directory by joining, which
|
|
12
|
+
* was right only while a project's content lived under the projects root. A
|
|
13
|
+
* MANAGED project with a `path:` (issue #206) keeps its curated trio out at that
|
|
14
|
+
* path, so the join addressed a directory holding nothing but `project.yaml` and
|
|
15
|
+
* the whole Files tab came back empty. Taking the directory the caller already
|
|
16
|
+
* resolved removes the second copy of that resolution rather than teaching it a
|
|
17
|
+
* new case — see {@link import("./project-paths.js").contentDirFor}.
|
|
9
18
|
*/
|
|
10
19
|
import { promises as fs } from "node:fs";
|
|
11
20
|
import path from "node:path";
|
|
12
21
|
import { ProjectError } from "./project-paths.js";
|
|
13
22
|
import { fileKind, contentTypeFor } from "./project-mime.js";
|
|
14
|
-
/**
|
|
15
|
-
* The directory backing a workspace key — a second copy of
|
|
16
|
-
* `ProjectStore.dirFor`'s resolution, kept in step by being the same one-liner.
|
|
17
|
-
*
|
|
18
|
-
* A key is a path relative to the projects root and the root workspace's key is
|
|
19
|
-
* `""`, so `path.join` handles both with no branch. That matters historically:
|
|
20
|
-
* the previous design DID need a branch here for a reserved slug, this copy was
|
|
21
|
-
* missed, and every root file route 404'd until it was found in live QA. There
|
|
22
|
-
* is no longer a branch to miss.
|
|
23
|
-
*
|
|
24
|
-
* The traversal guard below is unaffected: it compares against whatever this
|
|
25
|
-
* returns, so at the root "inside the workspace dir" means "inside the projects
|
|
26
|
-
* root" — exactly the scope of a keeper whose cwd is that directory.
|
|
27
|
-
*/
|
|
28
|
-
function dirFor(root, key) {
|
|
29
|
-
return path.join(root, key);
|
|
30
|
-
}
|
|
31
23
|
/**
|
|
32
24
|
* Resolve a freeform file name to an absolute path inside the project dir,
|
|
33
25
|
* rejecting path traversal AND traversal *through* a hidden (dot-prefixed)
|
|
@@ -62,13 +54,24 @@ function dirFor(root, key) {
|
|
|
62
54
|
* reachable there either. This is worth closing because "hidden in the listing"
|
|
63
55
|
* should not be the only thing standing between an API and a transcript, not
|
|
64
56
|
* because it grants anything new.
|
|
57
|
+
*
|
|
58
|
+
* **`hiddenSegments: "allow"`** turns the dot-directory half off, leaving only
|
|
59
|
+
* containment (issue #710). It exists for exactly one caller: `/git/file`, which
|
|
60
|
+
* serves a file from the project's WORKING directory and has already checked
|
|
61
|
+
* that `git status` reports that path as untracked. A git-verified allowlist is
|
|
62
|
+
* strictly stronger than this heuristic — git never names `.git` and never names
|
|
63
|
+
* an ignored path — and the heuristic is actively wrong out there, because a new
|
|
64
|
+
* `.github/workflows/ci.yml` is an ordinary Changes-tab row that a dot-directory
|
|
65
|
+
* rule refuses to render. Every other caller keeps the default. Pass it only
|
|
66
|
+
* with an independent allowlist in hand.
|
|
65
67
|
*/
|
|
66
|
-
export function resolveInProject(
|
|
67
|
-
const dir = dirFor(root, slug);
|
|
68
|
+
export function resolveInProject(dir, name, hiddenSegments = "refuse") {
|
|
68
69
|
const resolved = path.resolve(dir, name);
|
|
69
70
|
if (resolved !== dir && !resolved.startsWith(dir + path.sep)) {
|
|
70
71
|
throw new ProjectError("Invalid file path", "invalid");
|
|
71
72
|
}
|
|
73
|
+
if (hiddenSegments === "allow")
|
|
74
|
+
return resolved;
|
|
72
75
|
// Check the RESOLVED path's segments relative to the project dir, not the
|
|
73
76
|
// caller's raw string: `a/./.git/config` and `a/../.git/config` both normalise
|
|
74
77
|
// to a hidden directory segment the raw string doesn't literally contain. The
|
|
@@ -100,11 +103,11 @@ function isHidden(segment) {
|
|
|
100
103
|
* doesn't exist and `ProjectError("not_directory")` when `subpath` is a file —
|
|
101
104
|
* the latter lets the caller fall back to rendering that file.
|
|
102
105
|
*/
|
|
103
|
-
export async function listFiles(
|
|
104
|
-
const target = resolveInProject(
|
|
106
|
+
export async function listFiles(dir, subpath = "") {
|
|
107
|
+
const target = resolveInProject(dir, subpath);
|
|
105
108
|
// A LISTING target is a directory, so unlike a read the leaf gets no pass:
|
|
106
109
|
// `?path=.chats` is exactly how every transcript filename was enumerable.
|
|
107
|
-
const leaf = relSegments(
|
|
110
|
+
const leaf = relSegments(dir, target).at(-1);
|
|
108
111
|
if (leaf && isHidden(leaf)) {
|
|
109
112
|
throw new ProjectError("Invalid file path", "invalid");
|
|
110
113
|
}
|
|
@@ -126,17 +129,17 @@ export async function listFiles(root, slug, subpath = "") {
|
|
|
126
129
|
.sort((a, b) => a.kind === b.kind ? a.name.localeCompare(b.name) : a.kind === "dir" ? -1 : 1);
|
|
127
130
|
}
|
|
128
131
|
/** Read a freeform file's contents as UTF-8 text (path-traversal guarded). */
|
|
129
|
-
export async function readProjectFile(
|
|
130
|
-
return fs.readFile(resolveInProject(
|
|
132
|
+
export async function readProjectFile(dir, name, hiddenSegments = "refuse") {
|
|
133
|
+
return fs.readFile(resolveInProject(dir, name, hiddenSegments), "utf8");
|
|
131
134
|
}
|
|
132
135
|
/**
|
|
133
136
|
* Read a file's raw bytes + its MIME type (issue #61), for the binary/image
|
|
134
137
|
* endpoint. Path-traversal guarded; throws ProjectError("not_found") if the
|
|
135
138
|
* file is missing so the route can 404 cleanly. NOT decoded as text, so binary
|
|
136
|
-
* (image) bytes survive intact.
|
|
139
|
+
* (image) bytes survive intact. See {@link resolveInProject} for `hiddenSegments`.
|
|
137
140
|
*/
|
|
138
|
-
export async function readFileBytes(
|
|
139
|
-
const resolved = resolveInProject(
|
|
141
|
+
export async function readFileBytes(dir, name, hiddenSegments = "refuse") {
|
|
142
|
+
const resolved = resolveInProject(dir, name, hiddenSegments);
|
|
140
143
|
try {
|
|
141
144
|
const bytes = await fs.readFile(resolved);
|
|
142
145
|
return { bytes, mime: contentTypeFor(name) };
|
|
@@ -156,14 +159,15 @@ export async function readFileBytes(root, slug, name) {
|
|
|
156
159
|
* For an IMAGE the raw bytes are NOT returned here (decoding binary as UTF-8
|
|
157
160
|
* would mangle it): `content` is empty and the client fetches the bytes from
|
|
158
161
|
* the raw endpoint. We still stat the file so a missing image 404s. Path-
|
|
159
|
-
* traversal guarded; throws ProjectError("not_found") when missing.
|
|
162
|
+
* traversal guarded; throws ProjectError("not_found") when missing. See
|
|
163
|
+
* {@link resolveInProject} for `hiddenSegments`.
|
|
160
164
|
*/
|
|
161
|
-
export async function readFileWithKind(
|
|
165
|
+
export async function readFileWithKind(dir, name, hiddenSegments = "refuse") {
|
|
162
166
|
const kind = fileKind(name);
|
|
163
167
|
if (kind === "image") {
|
|
164
168
|
// Existence check only — the bytes go over the raw endpoint.
|
|
165
169
|
try {
|
|
166
|
-
await fs.stat(resolveInProject(
|
|
170
|
+
await fs.stat(resolveInProject(dir, name, hiddenSegments));
|
|
167
171
|
}
|
|
168
172
|
catch (err) {
|
|
169
173
|
if (err instanceof ProjectError)
|
|
@@ -177,7 +181,7 @@ export async function readFileWithKind(root, slug, name) {
|
|
|
177
181
|
}
|
|
178
182
|
let content;
|
|
179
183
|
try {
|
|
180
|
-
content = await readProjectFile(
|
|
184
|
+
content = await readProjectFile(dir, name, hiddenSegments);
|
|
181
185
|
}
|
|
182
186
|
catch (err) {
|
|
183
187
|
if (err instanceof ProjectError)
|
|
@@ -311,7 +311,7 @@ export declare class ProjectStore {
|
|
|
311
311
|
*/
|
|
312
312
|
writeChangelog(slug: string, body: string): Promise<void>;
|
|
313
313
|
/**
|
|
314
|
-
* List one level of a project directory (issue #259). See
|
|
314
|
+
* List one level of a project's content directory (issue #259). See
|
|
315
315
|
* {@link import("./project-files.js").listFiles}.
|
|
316
316
|
*/
|
|
317
317
|
listFiles(slug: string, subpath?: string): Promise<FileEntry[]>;
|
|
@@ -1066,32 +1066,39 @@ export class ProjectStore {
|
|
|
1066
1066
|
await fs.writeFile(file, `${title}\n\n${withoutTitle.trim()}\n`, "utf8");
|
|
1067
1067
|
}
|
|
1068
1068
|
// --- freeform file serving (delegated to project-files.ts, issue #403) ---
|
|
1069
|
-
// Thin wrappers over the pure `(
|
|
1070
|
-
//
|
|
1069
|
+
// Thin wrappers over the pure `(dir, …)` helpers so `ProjectStore`'s public
|
|
1070
|
+
// file-read API (and its ProjectError codes) is unchanged.
|
|
1071
|
+
//
|
|
1072
|
+
// Every one of them browses the project's CONTENT dir (issue #710). That is
|
|
1073
|
+
// the metadata dir for a notebook and for any UNMANAGED project — unchanged —
|
|
1074
|
+
// but for a MANAGED project with a `path:` (issue #206) the curated trio lives
|
|
1075
|
+
// out at that path, and joining `root + slug` addressed a directory holding
|
|
1076
|
+
// only `project.yaml`, so the Files tab listed nothing and every note 404'd.
|
|
1077
|
+
// `contentDirOf` is the single resolution both this and the sweeper use.
|
|
1071
1078
|
/**
|
|
1072
|
-
* List one level of a project directory (issue #259). See
|
|
1079
|
+
* List one level of a project's content directory (issue #259). See
|
|
1073
1080
|
* {@link import("./project-files.js").listFiles}.
|
|
1074
1081
|
*/
|
|
1075
1082
|
async listFiles(slug, subpath = "") {
|
|
1076
|
-
return projectFiles.listFiles(this.
|
|
1083
|
+
return projectFiles.listFiles(await this.contentDirOf(slug), subpath);
|
|
1077
1084
|
}
|
|
1078
1085
|
/** Read a freeform file's contents as UTF-8 text (path-traversal guarded). */
|
|
1079
1086
|
async readFile(slug, name) {
|
|
1080
|
-
return projectFiles.readProjectFile(this.
|
|
1087
|
+
return projectFiles.readProjectFile(await this.contentDirOf(slug), name);
|
|
1081
1088
|
}
|
|
1082
1089
|
/**
|
|
1083
1090
|
* Read a file's raw bytes + its MIME type (issue #61), for the binary/image
|
|
1084
1091
|
* endpoint. See {@link import("./project-files.js").readFileBytes}.
|
|
1085
1092
|
*/
|
|
1086
1093
|
async readFileBytes(slug, name) {
|
|
1087
|
-
return projectFiles.readFileBytes(this.
|
|
1094
|
+
return projectFiles.readFileBytes(await this.contentDirOf(slug), name);
|
|
1088
1095
|
}
|
|
1089
1096
|
/**
|
|
1090
1097
|
* Read a file plus a render-kind hint derived from its extension (issues #3 /
|
|
1091
1098
|
* #61). See {@link import("./project-files.js").readFileWithKind}.
|
|
1092
1099
|
*/
|
|
1093
1100
|
async readFileWithKind(slug, name) {
|
|
1094
|
-
return projectFiles.readFileWithKind(this.
|
|
1101
|
+
return projectFiles.readFileWithKind(await this.contentDirOf(slug), name);
|
|
1095
1102
|
}
|
|
1096
1103
|
// --- internals ---------------------------------------------------------
|
|
1097
1104
|
/**
|
|
@@ -67,12 +67,6 @@ export interface RecoveryConfig {
|
|
|
67
67
|
* `PADDOCK_RECOVERY_MAX_RETRIES`.
|
|
68
68
|
*/
|
|
69
69
|
maxRetries: number;
|
|
70
|
-
/**
|
|
71
|
-
* Layer 2 backstop — if a kept-alive keeper session shows no activity for this
|
|
72
|
-
* many ms after a killed-task notification, surface it as stuck. `0` disables
|
|
73
|
-
* the backstop (the default). Env `PADDOCK_RECOVERY_LIMBO_MS`.
|
|
74
|
-
*/
|
|
75
|
-
limboTimeoutMs: number;
|
|
76
70
|
}
|
|
77
71
|
/**
|
|
78
72
|
* A per-project recovery override as stored in `project.yaml` — every field
|
|
@@ -43,7 +43,6 @@ export const DEFAULT_RECOVERY = Object.freeze({
|
|
|
43
43
|
autoReDrive: false,
|
|
44
44
|
debounceMs: 5000,
|
|
45
45
|
maxRetries: 1,
|
|
46
|
-
limboTimeoutMs: 0,
|
|
47
46
|
});
|
|
48
47
|
/** The `<status>` values a killed-at-turn-boundary task notification carries. */
|
|
49
48
|
const TERMINATED_STATUSES = new Set(["killed", "stopped"]);
|
|
@@ -81,8 +80,6 @@ export function sanitizeRecoveryOverride(value) {
|
|
|
81
80
|
out.debounceMs = o.debounceMs;
|
|
82
81
|
if (isNonNegativeInt(o.maxRetries))
|
|
83
82
|
out.maxRetries = o.maxRetries;
|
|
84
|
-
if (isNonNegativeInt(o.limboTimeoutMs))
|
|
85
|
-
out.limboTimeoutMs = o.limboTimeoutMs;
|
|
86
83
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
87
84
|
}
|
|
88
85
|
/**
|
|
@@ -100,6 +97,5 @@ export function resolveRecoveryConfig(override, instanceDefault) {
|
|
|
100
97
|
autoReDrive: clean.autoReDrive ?? instanceDefault.autoReDrive,
|
|
101
98
|
debounceMs: clean.debounceMs ?? instanceDefault.debounceMs,
|
|
102
99
|
maxRetries: clean.maxRetries ?? instanceDefault.maxRetries,
|
|
103
|
-
limboTimeoutMs: clean.limboTimeoutMs ?? instanceDefault.limboTimeoutMs,
|
|
104
100
|
};
|
|
105
101
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Git routes: the fleet-level remote + GitHub device-flow auth, plus the
|
|
3
|
-
* per-project working-tree status / diff / commit
|
|
4
|
-
* separate explicit action from a project commit.
|
|
3
|
+
* per-project working-tree status / diff / commit / remote / push surface
|
|
4
|
+
* (#258, #710). Push is a separate explicit action from a project commit.
|
|
5
5
|
*/
|
|
6
6
|
import type { FastifyInstance } from "fastify";
|
|
7
7
|
import type { RouteCtx } from "../route-context.js";
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { sendProjectError } from "../route-errors.js";
|
|
2
|
+
import { ProjectError } from "../project-paths.js";
|
|
3
|
+
import { readFileWithKind, readFileBytes } from "../project-files.js";
|
|
2
4
|
export function registerGitRoutes(app, ctx) {
|
|
3
5
|
const { git, githubAuth } = ctx;
|
|
4
6
|
// --- git (backing store): fleet-level remote + connection state --------
|
|
@@ -118,7 +120,7 @@ export function registerGitRoutes(app, ctx) {
|
|
|
118
120
|
* and per project) — see `workspace-mount.ts`. Paths are relative to the mount.
|
|
119
121
|
*/
|
|
120
122
|
export function registerGitWorkspaceRoutes(app, ctx) {
|
|
121
|
-
const { git, projects } = ctx;
|
|
123
|
+
const { git, projects, githubAuth } = ctx;
|
|
122
124
|
// --- git (backing-store capability, phase 1: read surface) -------------
|
|
123
125
|
// Uncommitted changes confined to this project's subtree. Returns
|
|
124
126
|
// `{ repo: false }` when the projects dir isn't a git working tree, so the
|
|
@@ -236,4 +238,134 @@ export function registerGitWorkspaceRoutes(app, ctx) {
|
|
|
236
238
|
return sendProjectError(reply, err);
|
|
237
239
|
}
|
|
238
240
|
});
|
|
241
|
+
// This project's own remote + ahead/behind, with the (genuinely fleet-level)
|
|
242
|
+
// GitHub connection state folded in so the Changes header is one fetch (#710).
|
|
243
|
+
// Same payload shape as `GET /api/git`, which now differs only in WHICH repo it
|
|
244
|
+
// describes: that one always speaks for the backing store, this one for the
|
|
245
|
+
// directory the project actually works in.
|
|
246
|
+
app.get("/git/remote", {
|
|
247
|
+
schema: {
|
|
248
|
+
tags: ["Git"],
|
|
249
|
+
summary: "Get a project's git remote and GitHub connection state",
|
|
250
|
+
description: "Returns the remote details for the project's OWN working directory (a linked checkout or worktree has its own origin, branch and ahead/behind — issue #710) merged with a `github` object describing the fleet-level GitHub device-flow connection status.",
|
|
251
|
+
params: {
|
|
252
|
+
type: "object",
|
|
253
|
+
properties: {
|
|
254
|
+
slug: { type: "string", description: "The project slug." },
|
|
255
|
+
},
|
|
256
|
+
required: ["slug"],
|
|
257
|
+
},
|
|
258
|
+
response: {
|
|
259
|
+
200: {
|
|
260
|
+
description: "Object containing the remote configuration fields plus a nested `github` object with connection state.",
|
|
261
|
+
type: "object",
|
|
262
|
+
additionalProperties: true,
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
}, async (req, reply) => {
|
|
267
|
+
try {
|
|
268
|
+
const project = await projects.get(req.params.slug);
|
|
269
|
+
const [remote, github] = await Promise.all([
|
|
270
|
+
git.remote(project.workingDir),
|
|
271
|
+
githubAuth.status(),
|
|
272
|
+
]);
|
|
273
|
+
return { ...remote, github };
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
return sendProjectError(reply, err);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
// Push THIS project's working directory to its origin (#710). The fleet-level
|
|
280
|
+
// `POST /api/git/push` still pushes the backing store; for a notebook project
|
|
281
|
+
// the two are the same repo, and for a linked one they are emphatically not —
|
|
282
|
+
// the Changes header's Push button used to push Paddock's notes repo while
|
|
283
|
+
// showing the user's worktree branch beside it.
|
|
284
|
+
app.post("/git/push", {
|
|
285
|
+
schema: {
|
|
286
|
+
tags: ["Git"],
|
|
287
|
+
summary: "Push a project's working directory to its remote",
|
|
288
|
+
description: "Pushes the current branch of the project's own working directory to its `origin`, setting upstream on first push. Returns an object describing the push result.",
|
|
289
|
+
params: {
|
|
290
|
+
type: "object",
|
|
291
|
+
properties: {
|
|
292
|
+
slug: { type: "string", description: "The project slug." },
|
|
293
|
+
},
|
|
294
|
+
required: ["slug"],
|
|
295
|
+
},
|
|
296
|
+
response: {
|
|
297
|
+
200: {
|
|
298
|
+
description: "Object describing the outcome of the push operation.",
|
|
299
|
+
type: "object",
|
|
300
|
+
additionalProperties: true,
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
}, async (req, reply) => {
|
|
305
|
+
try {
|
|
306
|
+
const project = await projects.get(req.params.slug);
|
|
307
|
+
return await git.push(project.workingDir);
|
|
308
|
+
}
|
|
309
|
+
catch (err) {
|
|
310
|
+
return sendProjectError(reply, err);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
// An UNTRACKED file's content, read from the project's WORKING directory
|
|
314
|
+
// (#710). The Changes tab shows a new file's content in place of a diff, and
|
|
315
|
+
// used to fetch it from `/files/:name` — the metadata-dir surface, which for a
|
|
316
|
+
// repo-backed or linked project is a different directory entirely, so every
|
|
317
|
+
// new file in the pane rendered "File not found".
|
|
318
|
+
//
|
|
319
|
+
// The gate is `isUntrackedFile`, i.e. git's own answer, NOT the dot-segment
|
|
320
|
+
// guard `/files/:name` uses — see {@link GitService.isUntrackedFile} for why a
|
|
321
|
+
// path-shape rule is wrong in both directions once the target is a work tree.
|
|
322
|
+
// A path git does not report as untracked 404s, so the servable set is exactly
|
|
323
|
+
// the set the pane lists.
|
|
324
|
+
app.get("/git/file", {
|
|
325
|
+
schema: {
|
|
326
|
+
tags: ["Git"],
|
|
327
|
+
summary: "Get an untracked file's content from the project's working directory",
|
|
328
|
+
description: "Returns the content of a file git reports as UNTRACKED in the project's working directory — what the Changes tab renders in place of a diff for a brand-new file. `?path=` is the project-relative path; anything git does not currently report as untracked 404s. With `?raw=1` it streams raw bytes with the correct Content-Type (sandbox CSP + nosniff + inline disposition), how the pane loads a new image.",
|
|
329
|
+
params: {
|
|
330
|
+
type: "object",
|
|
331
|
+
properties: {
|
|
332
|
+
slug: { type: "string", description: "The project slug." },
|
|
333
|
+
},
|
|
334
|
+
required: ["slug"],
|
|
335
|
+
},
|
|
336
|
+
querystring: {
|
|
337
|
+
type: "object",
|
|
338
|
+
additionalProperties: true,
|
|
339
|
+
properties: {
|
|
340
|
+
path: { type: "string", description: "Project-relative path of the untracked file." },
|
|
341
|
+
raw: {
|
|
342
|
+
type: "string",
|
|
343
|
+
description: "Set to a truthy value (e.g. `1`) to stream raw bytes instead of JSON.",
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
}, async (req, reply) => {
|
|
349
|
+
try {
|
|
350
|
+
const project = await projects.get(req.params.slug);
|
|
351
|
+
const name = req.query.path ?? "";
|
|
352
|
+
if (!(await git.isUntrackedFile(project.workingDir, name))) {
|
|
353
|
+
throw new ProjectError(`File not found: ${name}`, "not_found");
|
|
354
|
+
}
|
|
355
|
+
if (req.query.raw !== undefined && req.query.raw !== "0" && req.query.raw !== "false") {
|
|
356
|
+
const { bytes, mime } = await readFileBytes(project.workingDir, name, "allow");
|
|
357
|
+
return reply
|
|
358
|
+
.header("content-type", mime)
|
|
359
|
+
.header("content-disposition", "inline")
|
|
360
|
+
.header("x-content-type-options", "nosniff")
|
|
361
|
+
.header("content-security-policy", "sandbox; default-src 'none'")
|
|
362
|
+
.header("cache-control", "private, max-age=60")
|
|
363
|
+
.send(bytes);
|
|
364
|
+
}
|
|
365
|
+
return await readFileWithKind(project.workingDir, name, "allow");
|
|
366
|
+
}
|
|
367
|
+
catch (err) {
|
|
368
|
+
return sendProjectError(reply, err);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
239
371
|
}
|
|
@@ -561,8 +561,18 @@ export interface ChatKilledTaskMessage {
|
|
|
561
561
|
*/
|
|
562
562
|
export interface LiveBackgroundTaskWire {
|
|
563
563
|
id: string;
|
|
564
|
-
/**
|
|
564
|
+
/**
|
|
565
|
+
* The RAW SDK discriminant — `local_bash`, `local_agent`, `monitor_mcp`, … —
|
|
566
|
+
* carried verbatim so an unexpected kind is diagnosable from the wire (#846).
|
|
567
|
+
* Render from {@link role} instead.
|
|
568
|
+
*/
|
|
565
569
|
type: string;
|
|
570
|
+
/**
|
|
571
|
+
* Rendering role derived from {@link type} by the registry: `shell` |
|
|
572
|
+
* `subagent` | `monitor` | `workflow` | `task`, falling back to the raw
|
|
573
|
+
* discriminant for a kind the server does not know about yet.
|
|
574
|
+
*/
|
|
575
|
+
role: string;
|
|
566
576
|
description: string;
|
|
567
577
|
startedAt: number;
|
|
568
578
|
toolUseId?: string;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{j as r,l as t,r as d}from"./index-
|
|
1
|
+
import{j as r,l as t,r as d}from"./index-B-J2K1w2.js";const u=["w-full rounded-lg border border-edge-strong bg-surface-sunken px-3 py-2 text-sm text-fg","outline-none motion-fast transition-[border-color,box-shadow]","focus:border-accent focus:ring-2 focus:ring-accent/25","disabled:cursor-not-allowed disabled:opacity-60","aria-[invalid=true]:border-danger aria-[invalid=true]:focus:ring-danger/25"].join(" ");function p({className:e,...s}){return r.jsx("input",{className:t(u,e),...s})}function m({className:e,...s}){return r.jsx("textarea",{className:t(u,"resize-y",e),...s})}function h({className:e,...s}){return r.jsx("select",{className:t(u,e),...s})}function j({checked:e,onChange:s,label:a,disabled:n,id:o,className:c}){const i=d.useId(),l=o??i;return r.jsxs("label",{htmlFor:l,className:t("relative inline-block shrink-0 cursor-pointer",c),children:[r.jsx("input",{id:l,type:"checkbox","aria-label":a,className:"peer sr-only",checked:e,disabled:n,onChange:x=>s(x.target.checked)}),r.jsx("span",{className:t("block h-[18px] w-8 rounded-full bg-edge-strong","motion-fast transition-colors","after:absolute after:left-0.5 after:top-0.5 after:h-3.5 after:w-3.5 after:rounded-full","after:bg-surface-raised after:shadow-2xs after:transition-transform after:content-['']","peer-checked:bg-accent-solid peer-checked:after:translate-x-3.5","peer-focus-visible:ring-2 peer-focus-visible:ring-accent/50","peer-disabled:opacity-50")})]})}function w({label:e,description:s,indeterminate:a=!1,className:n,...o}){const c=d.useRef(null);d.useEffect(()=>{c.current&&(c.current.indeterminate=a)},[a]);const i=r.jsx("input",{ref:c,type:"checkbox",...a?{"aria-checked":"mixed"}:{},className:t("h-4 w-4 shrink-0 cursor-pointer rounded border-edge-strong accent-[var(--accent-solid)]","disabled:cursor-not-allowed disabled:opacity-50",e?"mt-0.5":"",n),...o});return e?r.jsxs("label",{className:"flex cursor-pointer items-start gap-2 text-sm text-fg",children:[i,r.jsxs("span",{className:"min-w-0",children:[e,s&&r.jsx("span",{className:"block text-xs leading-snug text-fg-muted",children:s})]})]}):i}const f={neutral:"bg-surface-active text-fg-muted",accent:"bg-accent-soft text-accent",success:"bg-success-soft text-success",warn:"bg-warn-soft text-warn",danger:"bg-danger-soft text-danger",info:"bg-info-soft text-info",lineage:"bg-lineage-soft text-lineage"},g={neutral:"bg-fg-subtle",accent:"bg-accent-solid",success:"bg-success-solid",warn:"bg-warn-solid",danger:"bg-danger-solid",info:"bg-info-solid",lineage:"bg-lineage-solid"};function N({tone:e="neutral",shape:s="tag",size:a="md",dot:n=!1,icon:o,title:c,className:i,children:l}){return r.jsxs("span",{title:c,className:t("inline-flex items-center gap-1 font-medium leading-none",s==="pill"?"rounded-full px-2 py-0.5":"rounded-md px-1.5 py-0.5",a==="sm"?"text-3xs":"text-2xs",f[e],i),children:[n&&r.jsx("span",{className:t("h-1.5 w-1.5 shrink-0 rounded-full",g[e])}),o,l]})}function k({tone:e="neutral",pulse:s=!1,className:a}){return r.jsx("span",{className:t("inline-block h-2 w-2 shrink-0 rounded-full",g[e],s&&"animate-pulse",a)})}function y({tone:e="info",icon:s,className:a,children:n}){const o={neutral:"border-edge",accent:"border-accent-edge",success:"border-success-edge",warn:"border-warn-edge",danger:"border-danger-edge",info:"border-info-edge",lineage:"border-lineage-edge"};return r.jsxs("div",{role:e==="danger"?"alert":void 0,className:t("flex items-start gap-2 rounded-lg border px-3 py-2 text-sm",f[e],o[e],a),children:[s&&r.jsx("span",{className:"mt-0.5 shrink-0",children:s}),r.jsx("div",{className:"min-w-0",children:n})]})}export{N as C,p as I,k as S,j as T,y as a,h as b,m as c,w as d};
|
|
2
2
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as ne,u as re,H as ae,r as l,b as P,j as s,M as oe,E as ie,D as Q,J as U,K as ce,l as F,O as de,h as le}from"./index-Djl4krRr.js";import{E as O,B as R,C as he}from"./Surfaces-GzOkJJHQ.js";import{a as W,d as M,C as T,T as K}from"./Chip-B8UU8Zpr.js";function ue(e){const t={};for(const r of e)t[r.path]={checked:!0,expanded:!1,loading:!1,error:null};return t}function pe(e){if(!e.sessions||!e.selected)return e.checked?"on":"off";const t=e.sessions.sessions.length,r=e.selected.size;return t===0||r===0?"off":r===t?"on":"mixed"}function C(e,t){return e.sessions&&e.selected?e.selected.size:e.checked?t.sessionCount:0}function me(e,t){return t.reduce((r,a)=>r+C(e[a.path]??z(),a),0)}function xe(e,t){return t.filter(r=>C(e[r.path]??z(),r)>0)}function fe(e,t,r){const a=e[t];if(!a)return e;const n={...a,checked:r};return a.sessions&&(n.selected=r?new Set(a.sessions.sessions.map(d=>d.sessionId)):new Set),{...e,[t]:n}}function ge(e,t,r){const a=e[t];if(!a||!a.sessions)return e;const n=new Set(a.selected??[]);return n.delete(r)||n.add(r),{...e,[t]:{...a,selected:n,checked:n.size>0}}}function je(e,t,r){const a=e[t];if(!a)return e;const n=a.checked?new Set(r.sessions.map(d=>d.sessionId)):new Set;return{...e,[t]:{...a,sessions:r,selected:n,loading:!1,error:null}}}function D(e,t,r){const a=e[t];return a?{...e,[t]:{...a,...r}}:e}function be(e,t){const r=e??z();return C(r,t)===0?null:r.sessions&&r.selected?{sessionIds:[...r.selected]}:{}}function z(){return{checked:!1,expanded:!1,loading:!1,error:null}}function ye(e){return e.error!==void 0?e.stage==="create"?"danger":"warn":e.adopted>0?"success":"warn"}async function we(e,t,r){let a;try{a=await e.createProject({name:t.name,slug:t.suggestedSlug,path:t.path,managed:!1})}catch(n){return{stage:"create",adopted:0,skipped:[],error:V(n)}}try{const n=await e.adoptChats(a.slug,{sourceCwd:a.workingDir,...r.sessionIds?{sessionIds:r.sessionIds}:{}});return{project:a,adopted:n.adopted.length,skipped:n.skipped}}catch(n){return{stage:"import",project:a,adopted:0,skipped:[],error:V(n)}}}async function Ne(e,t,r){const a=[];for(const n of t){r(n.candidate.path,"running");const d=await we(e,n.candidate,n.plan);a.push(d),r(n.candidate.path,"done",d)}return a}const ve={sidechain:"sub-agent transcript","already-adopted":"already imported","destination-exists":"a chat with that id already exists here","attributed-to-run":"belongs to a Paddock run",unreadable:"transcript unreadable","placement-failed":"copy failed","record-failed":"could not be recorded"};function J(e){if(e.length===0)return"";const t=[...new Set(e.map(r=>ve[r.reason]??r.reason))];return`${e.length} skipped (${t.join(", ")})`}function ke(e,t){var h;if(e.stage==="create")return`Couldn't create the project: ${e.error}`;const r=((h=e.project)==null?void 0:h.slug)??t.suggestedSlug;if(e.stage==="import")return`Project “${r}” was created, but importing its chats failed: ${e.error}. It is there and empty — you can import them from the project itself.`;if(e.adopted===0){const m=J(e.skipped);return t.recordedPath!==void 0?`Project “${r}” was created but no chats came with it. Its transcripts record ${t.recordedPath}, a different spelling of ${t.path}, and an import matches on the exact path.`:m?`Project “${r}” was created but no chats came with it — ${m}.`:`Project “${r}” was created but no chats came with it.`}const a=e.adopted,n=J(e.skipped),d=`Imported ${a} chat${a===1?"":"s"} into “${r}”`;return n?`${d} — ${n}.`:`${d}.`}function V(e){return e instanceof Error?e.message:String(e)}function ze({firstRun:e=!1,onLeave:t}){const r=ne(),{refresh:a}=re(),n=ae(),[d,h]=l.useState(!1),[m,c]=l.useState(!1),[x,$]=l.useState(null),[g,u]=l.useState(null),[v,I]=l.useState(!0),[j,w]=l.useState({}),[k,A]=l.useState(!1),[X,H]=l.useState({}),[E,B]=l.useState({}),[S,Y]=l.useState(!1);l.useEffect(()=>{let o=!0;return I(!0),u(null),P.discover({includeNonGit:d,includeOutsideHome:m}).then(i=>{o&&($(i),w(ue(i.candidates)),I(!1))}).catch(i=>{o&&(u(i instanceof Error?i.message:"Discovery failed"),I(!1))}),()=>{o=!1}},[d,m]);const y=(x==null?void 0:x.candidates)??[],G=l.useMemo(()=>me(j,y),[j,y]),b=l.useMemo(()=>xe(j,y),[j,y]),Z=l.useCallback(o=>{w(p=>{const f=p[o.path];if(!f)return p;const N=D(p,o.path,{expanded:!f.expanded});return f.expanded||f.sessions||f.loading?N:D(N,o.path,{loading:!0,error:null})});const i=j[o.path];!i||i.expanded||i.sessions||i.loading||P.discoverSessions(o.path).then(p=>w(f=>je(f,o.path,p))).catch(p=>w(f=>D(f,o.path,{loading:!1,error:p instanceof Error?p.message:"Could not read this directory's sessions"})))},[j]),L=l.useCallback(async()=>{if(k)return;A(!0),H(Object.fromEntries(b.map(i=>[i.path,"pending"]))),B({});const o=b.map(i=>({candidate:i,plan:be(j[i.path],i)??{}}));await Ne(P,o,(i,p,f)=>{H(N=>({...N,[i]:p})),f&&B(N=>({...N,[i]:f}))}),A(!1),Y(!0),await a()},[b,j,k,a]),ee=l.useCallback(async()=>{await a(),t==null||t(),r("/")},[r,a,t]),se=Object.values(E).reduce((o,i)=>o+i.adopted,0),te=Object.values(E).filter(o=>o.project).length;return s.jsxs("div",{className:"flex h-full min-h-0 flex-col",children:[s.jsxs("header",{className:"pt-safe flex items-center gap-2 border-b border-edge px-3 pb-2.5 sm:px-6 lg:py-4",children:[s.jsx("button",{type:"button",onClick:()=>n==null?void 0:n.openNav(),"aria-label":"Open menu",className:"btn-subtle -ml-1 shrink-0 px-2 py-1.5 lg:hidden",children:s.jsx(oe,{width:20,height:20})}),s.jsx(ie,{width:18,height:18,className:"shrink-0 text-fg-subtle"}),s.jsx("h1",{className:"text-md font-semibold tracking-tight",children:"Discover"})]}),s.jsx("div",{className:"flex-1 overflow-y-auto overscroll-contain",children:s.jsxs("div",{className:"mx-auto max-w-3xl px-6 py-6",children:[s.jsx("p",{className:"mb-5 text-sm text-fg-muted",children:e?"Nothing here yet — but you have probably already been using Claude Code in a terminal. These directories have history Paddock can bring in as projects.":"Directories on this machine with existing Claude Code history. Importing one links it as a project and copies its conversations in; your own history is never moved or deleted."}),S&&s.jsx(Ie,{imported:se,projects:te,onGetStarted:ee}),g!==null&&s.jsx(W,{tone:"danger",icon:s.jsx(Q,{width:16,height:16}),children:g}),v&&s.jsx(Ee,{}),!v&&g===null&&x!==null&&s.jsxs(s.Fragment,{children:[x.scanned===0?s.jsx($e,{claudeHome:x.claudeHome}):y.length===0?s.jsx(O,{variant:"panel",icon:s.jsx(U,{width:22,height:22}),title:"Nothing left to import",body:"Every directory with Claude Code history on this machine is already a project, or was filtered out."}):s.jsx("ul",{className:"space-y-2",children:y.map(o=>s.jsx(Se,{candidate:o,row:j[o.path],status:X[o.path],outcome:E[o.path],locked:k||S,onToggleRow:i=>w(p=>fe(p,o.path,i)),onToggleSession:i=>w(p=>ge(p,o.path,i)),onToggleExpanded:()=>Z(o)},o.path))}),!S&&s.jsx(Ce,{result:x,includeNonGit:d,includeOutsideHome:m,disabled:k,onIncludeNonGit:h,onIncludeOutsideHome:c}),y.length>0&&!S&&s.jsxs("div",{className:"mt-5 flex items-center justify-between gap-3 border-t border-edge pt-4",children:[s.jsxs("span",{className:"text-sm text-fg-muted",children:[G," conversation",G===1?"":"s"," from ",b.length," director",b.length===1?"y":"ies"]}),s.jsxs(R,{variant:"primary",onClick:()=>void L(),disabled:b.length===0,loading:k,loadingLabel:"Importing…",children:["Import ",b.length," project",b.length===1?"":"s"]})]})]})]})})]})}function Se({candidate:e,row:t,status:r,outcome:a,locked:n,onToggleRow:d,onToggleSession:h,onToggleExpanded:m}){const c=t??{checked:!1,expanded:!1,loading:!1,error:null},x=pe(c),$=C(c,e),g=a?ye(a):null;return s.jsx("li",{children:s.jsxs(he,{flush:!0,className:F("overflow-hidden",g==="success"&&"border-success-edge bg-success-soft",g==="warn"&&"border-warn-edge bg-warn-soft",g==="danger"&&"border-danger-edge bg-danger-soft",r==="running"&&"border-accent-edge"),"data-testid":`discover-row-${e.suggestedSlug}`,"data-tone":g??void 0,children:[s.jsxs("div",{className:"flex items-start gap-3 px-3 py-2.5",children:[s.jsx(M,{className:"mt-1",checked:x==="on",indeterminate:x==="mixed",disabled:n,"aria-label":`Import ${e.path}`,onChange:u=>d(u.target.checked)}),s.jsxs("div",{className:"min-w-0 flex-1",children:[s.jsx("code",{className:"block break-all text-sm text-fg",children:e.path}),s.jsxs("div",{className:"mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-fg-muted",children:[s.jsxs("span",{className:"tabular",children:[e.sessionCount," conversation",e.sessionCount===1?"":"s"]}),e.filteredCount>0&&s.jsxs("span",{className:"tabular",title:"Empty, slash-command-only or sweeper transcripts, withheld as noise",children:["· ",e.filteredCount," filtered out"]}),e.lastSessionAt!==void 0&&s.jsxs("span",{className:"tabular",children:["· last ",q(e.lastSessionAt)]}),e.gitRemote!==void 0&&s.jsx(T,{tone:"lineage",size:"sm",children:e.gitRemote}),!e.hasGit&&s.jsx(T,{tone:"neutral",size:"sm",children:"no git"}),!e.insideHome&&s.jsx(T,{tone:"neutral",size:"sm",children:"outside home"})]}),e.recordedPath!==void 0&&s.jsx(W,{tone:"warn",className:"mt-2",icon:s.jsx(Q,{width:14,height:14}),children:s.jsxs("span",{className:"text-xs",children:["These transcripts record"," ",s.jsx("code",{className:"break-all",children:e.recordedPath})," — a different spelling of this path. The import matches on the exact path, so it may bring nothing in."]})}),a&&s.jsx("p",{className:F("mt-2 text-xs",g==="success"&&"text-success",g==="warn"&&"text-warn",g==="danger"&&"text-danger"),children:ke(a,e)}),r==="running"&&!a&&s.jsx("p",{className:"mt-2 text-xs text-fg-muted",children:"Importing…"})]}),s.jsxs("span",{className:"flex shrink-0 items-center gap-1",children:[s.jsx("span",{className:"tabular text-xs text-fg-subtle",children:$}),s.jsx(R,{size:"icon-sm",variant:"subtle","aria-label":c.expanded?"Hide conversations":"Show conversations","aria-expanded":c.expanded,onClick:m,children:c.expanded?s.jsx(de,{width:14,height:14}):s.jsx(le,{width:14,height:14})})]})]}),c.expanded&&s.jsxs("div",{className:"border-t border-edge px-3 py-2",children:[c.loading&&s.jsx("p",{className:"py-2 text-xs text-fg-muted",children:"Reading transcripts…"}),c.error!==null&&s.jsx("p",{className:"py-2 text-xs text-danger",children:c.error}),c.sessions&&c.sessions.sessions.length===0&&s.jsx("p",{className:"py-2 text-xs text-fg-muted",children:"Nothing importable here any more — it may have been imported since the scan."}),c.sessions&&c.sessions.sessions.length>0&&s.jsx("ul",{className:"space-y-1",children:c.sessions.sessions.map(u=>{var v;return s.jsx("li",{children:s.jsxs("label",{className:"flex cursor-pointer items-start gap-2 rounded-lg px-2 py-1.5 transition-colors hover:bg-surface-hover",children:[s.jsx(M,{checked:((v=c.selected)==null?void 0:v.has(u.sessionId))??!1,disabled:n,"aria-label":_(u),onChange:()=>h(u.sessionId)}),s.jsxs("span",{className:"min-w-0 flex-1",children:[s.jsx("span",{className:"block truncate text-xs text-fg",children:_(u)}),s.jsxs("span",{className:"tabular mt-0.5 block text-3xs text-fg-muted",children:[q(u.mtime)," · ",Te(u.sizeBytes)]})]})]})},u.sessionId)})}),c.sessions&&c.sessions.filtered.length>0&&s.jsxs("p",{className:"mt-2 px-2 text-3xs text-fg-subtle",children:[c.sessions.filtered.length," more withheld as noise (",[...new Set(c.sessions.filtered.map(u=>u.reason))].join(", "),")."]})]})]})})}function Ce({result:e,includeNonGit:t,includeOutsideHome:r,disabled:a,onIncludeNonGit:n,onIncludeOutsideHome:d}){const h=e.excluded["no-git"]??0,m=e.excluded["outside-home"]??0,c=h>0||t,x=m>0||r;return e.scanned===0?null:s.jsxs("div",{className:"mt-5 space-y-2 border-t border-edge pt-4",children:[s.jsx("p",{className:"text-xs text-fg-muted",children:Pe(e)}),c&&s.jsxs("div",{className:"flex items-center gap-2 text-xs text-fg-muted",children:[s.jsx(K,{checked:t,disabled:a,onChange:n,label:"Also offer directories without a git repository"}),s.jsxs("span",{children:["Also offer directories without a git repository",h>0&&` (${h} hidden)`]})]}),x&&s.jsxs("div",{className:"flex items-center gap-2 text-xs text-fg-muted",children:[s.jsx(K,{checked:r,disabled:a,onChange:d,label:"Also offer directories outside your home directory"}),s.jsxs("span",{children:["Also offer directories outside ",s.jsx("code",{children:e.homeDir}),m>0&&` (${m} hidden)`]})]})]})}function $e({claudeHome:e}){return s.jsx(O,{variant:"panel",icon:s.jsx(ce,{width:22,height:22}),title:"No Claude Code history on this machine",body:s.jsxs(s.Fragment,{children:["Nothing has been recorded under ",s.jsx("code",{className:"break-all",children:e}),". That is normal for a container, whose Claude home is its own rather than yours — bind-mount your history there and re-open this page, or import it headlessly with"," ",s.jsx("code",{children:"npm run import-chats"}),"."]})})}function Ie({imported:e,projects:t,onGetStarted:r}){return s.jsx("div",{className:"mb-6",children:s.jsx(O,{variant:"panel",icon:s.jsx(U,{width:22,height:22}),title:t===0?"Nothing was imported":`${t} project${t===1?"":"s"}, ${e} conversation${e===1?"":"s"}`,body:t===0?"Every row failed. Each one says why below.":"They are in the sidebar now. Anything that did not land says why below.",action:s.jsx(R,{variant:"primary",onClick:()=>void r(),children:"Get started"})})})}function Ee(){return s.jsx("div",{className:"space-y-2","aria-label":"Scanning",role:"status",children:[0,1,2].map(e=>s.jsx("div",{className:"h-16 animate-pulse rounded-2xl bg-surface-active"},e))})}function Pe(e){const t=[],r=[["temp-root",n=>`${n} in temp directories`],["no-git",n=>`${n} without a git repository`],["outside-home",n=>`${n} outside your home directory`],["already-managed",n=>`${n} already a project`],["no-sessions",n=>`${n} with nothing importable`],["no-recorded-cwd",n=>`${n} with no recorded directory`],["missing",n=>`${n} no longer on disk`],["home-root",n=>`${n} your home directory itself`],["system-path",n=>`${n} system directories`],["paddock-internal",n=>`${n} inside Paddock's own data`]];for(const[n,d]of r){const h=e.excluded[n];h!==void 0&&h>0&&t.push(d(h))}const a=`${e.scanned} transcript folder${e.scanned===1?"":"s"} scanned`;return t.length===0?`${a}.`:`${a}; skipped ${t.join(", ")}.`}function _(e){var a,n;const t=(a=e.autoName)==null?void 0:a.trim();if(t!==void 0&&t!=="")return t;const r=(n=e.preview)==null?void 0:n.trim();return r!==void 0&&r!==""?r:e.sessionId}function q(e){const t=new Date(e);return Number.isNaN(t.getTime())?"unknown date":t.toLocaleDateString(void 0,{year:"numeric",month:"short",day:"numeric"})}function Te(e){return e<1024?`${e} B`:e<1024*1024?`${Math.round(e/1024)} kB`:`${(e/(1024*1024)).toFixed(1)} MB`}export{ze as D};
|
|
1
|
+
import{a as ne,u as re,H as ae,r as l,b as P,j as s,M as oe,E as ie,D as Q,J as U,K as ce,l as F,O as de,h as le}from"./index-B-J2K1w2.js";import{E as O,B as R,C as he}from"./Surfaces-t3XF_my5.js";import{a as W,d as M,C as T,T as K}from"./Chip-P4n88XYj.js";function ue(e){const t={};for(const r of e)t[r.path]={checked:!0,expanded:!1,loading:!1,error:null};return t}function pe(e){if(!e.sessions||!e.selected)return e.checked?"on":"off";const t=e.sessions.sessions.length,r=e.selected.size;return t===0||r===0?"off":r===t?"on":"mixed"}function C(e,t){return e.sessions&&e.selected?e.selected.size:e.checked?t.sessionCount:0}function me(e,t){return t.reduce((r,a)=>r+C(e[a.path]??z(),a),0)}function xe(e,t){return t.filter(r=>C(e[r.path]??z(),r)>0)}function fe(e,t,r){const a=e[t];if(!a)return e;const n={...a,checked:r};return a.sessions&&(n.selected=r?new Set(a.sessions.sessions.map(d=>d.sessionId)):new Set),{...e,[t]:n}}function ge(e,t,r){const a=e[t];if(!a||!a.sessions)return e;const n=new Set(a.selected??[]);return n.delete(r)||n.add(r),{...e,[t]:{...a,selected:n,checked:n.size>0}}}function je(e,t,r){const a=e[t];if(!a)return e;const n=a.checked?new Set(r.sessions.map(d=>d.sessionId)):new Set;return{...e,[t]:{...a,sessions:r,selected:n,loading:!1,error:null}}}function D(e,t,r){const a=e[t];return a?{...e,[t]:{...a,...r}}:e}function be(e,t){const r=e??z();return C(r,t)===0?null:r.sessions&&r.selected?{sessionIds:[...r.selected]}:{}}function z(){return{checked:!1,expanded:!1,loading:!1,error:null}}function ye(e){return e.error!==void 0?e.stage==="create"?"danger":"warn":e.adopted>0?"success":"warn"}async function we(e,t,r){let a;try{a=await e.createProject({name:t.name,slug:t.suggestedSlug,path:t.path,managed:!1})}catch(n){return{stage:"create",adopted:0,skipped:[],error:V(n)}}try{const n=await e.adoptChats(a.slug,{sourceCwd:a.workingDir,...r.sessionIds?{sessionIds:r.sessionIds}:{}});return{project:a,adopted:n.adopted.length,skipped:n.skipped}}catch(n){return{stage:"import",project:a,adopted:0,skipped:[],error:V(n)}}}async function Ne(e,t,r){const a=[];for(const n of t){r(n.candidate.path,"running");const d=await we(e,n.candidate,n.plan);a.push(d),r(n.candidate.path,"done",d)}return a}const ve={sidechain:"sub-agent transcript","already-adopted":"already imported","destination-exists":"a chat with that id already exists here","attributed-to-run":"belongs to a Paddock run",unreadable:"transcript unreadable","placement-failed":"copy failed","record-failed":"could not be recorded"};function J(e){if(e.length===0)return"";const t=[...new Set(e.map(r=>ve[r.reason]??r.reason))];return`${e.length} skipped (${t.join(", ")})`}function ke(e,t){var h;if(e.stage==="create")return`Couldn't create the project: ${e.error}`;const r=((h=e.project)==null?void 0:h.slug)??t.suggestedSlug;if(e.stage==="import")return`Project “${r}” was created, but importing its chats failed: ${e.error}. It is there and empty — you can import them from the project itself.`;if(e.adopted===0){const m=J(e.skipped);return t.recordedPath!==void 0?`Project “${r}” was created but no chats came with it. Its transcripts record ${t.recordedPath}, a different spelling of ${t.path}, and an import matches on the exact path.`:m?`Project “${r}” was created but no chats came with it — ${m}.`:`Project “${r}” was created but no chats came with it.`}const a=e.adopted,n=J(e.skipped),d=`Imported ${a} chat${a===1?"":"s"} into “${r}”`;return n?`${d} — ${n}.`:`${d}.`}function V(e){return e instanceof Error?e.message:String(e)}function ze({firstRun:e=!1,onLeave:t}){const r=ne(),{refresh:a}=re(),n=ae(),[d,h]=l.useState(!1),[m,c]=l.useState(!1),[x,$]=l.useState(null),[g,u]=l.useState(null),[v,I]=l.useState(!0),[j,w]=l.useState({}),[k,A]=l.useState(!1),[X,H]=l.useState({}),[E,B]=l.useState({}),[S,Y]=l.useState(!1);l.useEffect(()=>{let o=!0;return I(!0),u(null),P.discover({includeNonGit:d,includeOutsideHome:m}).then(i=>{o&&($(i),w(ue(i.candidates)),I(!1))}).catch(i=>{o&&(u(i instanceof Error?i.message:"Discovery failed"),I(!1))}),()=>{o=!1}},[d,m]);const y=(x==null?void 0:x.candidates)??[],G=l.useMemo(()=>me(j,y),[j,y]),b=l.useMemo(()=>xe(j,y),[j,y]),Z=l.useCallback(o=>{w(p=>{const f=p[o.path];if(!f)return p;const N=D(p,o.path,{expanded:!f.expanded});return f.expanded||f.sessions||f.loading?N:D(N,o.path,{loading:!0,error:null})});const i=j[o.path];!i||i.expanded||i.sessions||i.loading||P.discoverSessions(o.path).then(p=>w(f=>je(f,o.path,p))).catch(p=>w(f=>D(f,o.path,{loading:!1,error:p instanceof Error?p.message:"Could not read this directory's sessions"})))},[j]),L=l.useCallback(async()=>{if(k)return;A(!0),H(Object.fromEntries(b.map(i=>[i.path,"pending"]))),B({});const o=b.map(i=>({candidate:i,plan:be(j[i.path],i)??{}}));await Ne(P,o,(i,p,f)=>{H(N=>({...N,[i]:p})),f&&B(N=>({...N,[i]:f}))}),A(!1),Y(!0),await a()},[b,j,k,a]),ee=l.useCallback(async()=>{await a(),t==null||t(),r("/")},[r,a,t]),se=Object.values(E).reduce((o,i)=>o+i.adopted,0),te=Object.values(E).filter(o=>o.project).length;return s.jsxs("div",{className:"flex h-full min-h-0 flex-col",children:[s.jsxs("header",{className:"pt-safe flex items-center gap-2 border-b border-edge px-3 pb-2.5 sm:px-6 lg:py-4",children:[s.jsx("button",{type:"button",onClick:()=>n==null?void 0:n.openNav(),"aria-label":"Open menu",className:"btn-subtle -ml-1 shrink-0 px-2 py-1.5 lg:hidden",children:s.jsx(oe,{width:20,height:20})}),s.jsx(ie,{width:18,height:18,className:"shrink-0 text-fg-subtle"}),s.jsx("h1",{className:"text-md font-semibold tracking-tight",children:"Discover"})]}),s.jsx("div",{className:"flex-1 overflow-y-auto overscroll-contain",children:s.jsxs("div",{className:"mx-auto max-w-3xl px-6 py-6",children:[s.jsx("p",{className:"mb-5 text-sm text-fg-muted",children:e?"Nothing here yet — but you have probably already been using Claude Code in a terminal. These directories have history Paddock can bring in as projects.":"Directories on this machine with existing Claude Code history. Importing one links it as a project and copies its conversations in; your own history is never moved or deleted."}),S&&s.jsx(Ie,{imported:se,projects:te,onGetStarted:ee}),g!==null&&s.jsx(W,{tone:"danger",icon:s.jsx(Q,{width:16,height:16}),children:g}),v&&s.jsx(Ee,{}),!v&&g===null&&x!==null&&s.jsxs(s.Fragment,{children:[x.scanned===0?s.jsx($e,{claudeHome:x.claudeHome}):y.length===0?s.jsx(O,{variant:"panel",icon:s.jsx(U,{width:22,height:22}),title:"Nothing left to import",body:"Every directory with Claude Code history on this machine is already a project, or was filtered out."}):s.jsx("ul",{className:"space-y-2",children:y.map(o=>s.jsx(Se,{candidate:o,row:j[o.path],status:X[o.path],outcome:E[o.path],locked:k||S,onToggleRow:i=>w(p=>fe(p,o.path,i)),onToggleSession:i=>w(p=>ge(p,o.path,i)),onToggleExpanded:()=>Z(o)},o.path))}),!S&&s.jsx(Ce,{result:x,includeNonGit:d,includeOutsideHome:m,disabled:k,onIncludeNonGit:h,onIncludeOutsideHome:c}),y.length>0&&!S&&s.jsxs("div",{className:"mt-5 flex items-center justify-between gap-3 border-t border-edge pt-4",children:[s.jsxs("span",{className:"text-sm text-fg-muted",children:[G," conversation",G===1?"":"s"," from ",b.length," director",b.length===1?"y":"ies"]}),s.jsxs(R,{variant:"primary",onClick:()=>void L(),disabled:b.length===0,loading:k,loadingLabel:"Importing…",children:["Import ",b.length," project",b.length===1?"":"s"]})]})]})]})})]})}function Se({candidate:e,row:t,status:r,outcome:a,locked:n,onToggleRow:d,onToggleSession:h,onToggleExpanded:m}){const c=t??{checked:!1,expanded:!1,loading:!1,error:null},x=pe(c),$=C(c,e),g=a?ye(a):null;return s.jsx("li",{children:s.jsxs(he,{flush:!0,className:F("overflow-hidden",g==="success"&&"border-success-edge bg-success-soft",g==="warn"&&"border-warn-edge bg-warn-soft",g==="danger"&&"border-danger-edge bg-danger-soft",r==="running"&&"border-accent-edge"),"data-testid":`discover-row-${e.suggestedSlug}`,"data-tone":g??void 0,children:[s.jsxs("div",{className:"flex items-start gap-3 px-3 py-2.5",children:[s.jsx(M,{className:"mt-1",checked:x==="on",indeterminate:x==="mixed",disabled:n,"aria-label":`Import ${e.path}`,onChange:u=>d(u.target.checked)}),s.jsxs("div",{className:"min-w-0 flex-1",children:[s.jsx("code",{className:"block break-all text-sm text-fg",children:e.path}),s.jsxs("div",{className:"mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-fg-muted",children:[s.jsxs("span",{className:"tabular",children:[e.sessionCount," conversation",e.sessionCount===1?"":"s"]}),e.filteredCount>0&&s.jsxs("span",{className:"tabular",title:"Empty, slash-command-only or sweeper transcripts, withheld as noise",children:["· ",e.filteredCount," filtered out"]}),e.lastSessionAt!==void 0&&s.jsxs("span",{className:"tabular",children:["· last ",q(e.lastSessionAt)]}),e.gitRemote!==void 0&&s.jsx(T,{tone:"lineage",size:"sm",children:e.gitRemote}),!e.hasGit&&s.jsx(T,{tone:"neutral",size:"sm",children:"no git"}),!e.insideHome&&s.jsx(T,{tone:"neutral",size:"sm",children:"outside home"})]}),e.recordedPath!==void 0&&s.jsx(W,{tone:"warn",className:"mt-2",icon:s.jsx(Q,{width:14,height:14}),children:s.jsxs("span",{className:"text-xs",children:["These transcripts record"," ",s.jsx("code",{className:"break-all",children:e.recordedPath})," — a different spelling of this path. The import matches on the exact path, so it may bring nothing in."]})}),a&&s.jsx("p",{className:F("mt-2 text-xs",g==="success"&&"text-success",g==="warn"&&"text-warn",g==="danger"&&"text-danger"),children:ke(a,e)}),r==="running"&&!a&&s.jsx("p",{className:"mt-2 text-xs text-fg-muted",children:"Importing…"})]}),s.jsxs("span",{className:"flex shrink-0 items-center gap-1",children:[s.jsx("span",{className:"tabular text-xs text-fg-subtle",children:$}),s.jsx(R,{size:"icon-sm",variant:"subtle","aria-label":c.expanded?"Hide conversations":"Show conversations","aria-expanded":c.expanded,onClick:m,children:c.expanded?s.jsx(de,{width:14,height:14}):s.jsx(le,{width:14,height:14})})]})]}),c.expanded&&s.jsxs("div",{className:"border-t border-edge px-3 py-2",children:[c.loading&&s.jsx("p",{className:"py-2 text-xs text-fg-muted",children:"Reading transcripts…"}),c.error!==null&&s.jsx("p",{className:"py-2 text-xs text-danger",children:c.error}),c.sessions&&c.sessions.sessions.length===0&&s.jsx("p",{className:"py-2 text-xs text-fg-muted",children:"Nothing importable here any more — it may have been imported since the scan."}),c.sessions&&c.sessions.sessions.length>0&&s.jsx("ul",{className:"space-y-1",children:c.sessions.sessions.map(u=>{var v;return s.jsx("li",{children:s.jsxs("label",{className:"flex cursor-pointer items-start gap-2 rounded-lg px-2 py-1.5 transition-colors hover:bg-surface-hover",children:[s.jsx(M,{checked:((v=c.selected)==null?void 0:v.has(u.sessionId))??!1,disabled:n,"aria-label":_(u),onChange:()=>h(u.sessionId)}),s.jsxs("span",{className:"min-w-0 flex-1",children:[s.jsx("span",{className:"block truncate text-xs text-fg",children:_(u)}),s.jsxs("span",{className:"tabular mt-0.5 block text-3xs text-fg-muted",children:[q(u.mtime)," · ",Te(u.sizeBytes)]})]})]})},u.sessionId)})}),c.sessions&&c.sessions.filtered.length>0&&s.jsxs("p",{className:"mt-2 px-2 text-3xs text-fg-subtle",children:[c.sessions.filtered.length," more withheld as noise (",[...new Set(c.sessions.filtered.map(u=>u.reason))].join(", "),")."]})]})]})})}function Ce({result:e,includeNonGit:t,includeOutsideHome:r,disabled:a,onIncludeNonGit:n,onIncludeOutsideHome:d}){const h=e.excluded["no-git"]??0,m=e.excluded["outside-home"]??0,c=h>0||t,x=m>0||r;return e.scanned===0?null:s.jsxs("div",{className:"mt-5 space-y-2 border-t border-edge pt-4",children:[s.jsx("p",{className:"text-xs text-fg-muted",children:Pe(e)}),c&&s.jsxs("div",{className:"flex items-center gap-2 text-xs text-fg-muted",children:[s.jsx(K,{checked:t,disabled:a,onChange:n,label:"Also offer directories without a git repository"}),s.jsxs("span",{children:["Also offer directories without a git repository",h>0&&` (${h} hidden)`]})]}),x&&s.jsxs("div",{className:"flex items-center gap-2 text-xs text-fg-muted",children:[s.jsx(K,{checked:r,disabled:a,onChange:d,label:"Also offer directories outside your home directory"}),s.jsxs("span",{children:["Also offer directories outside ",s.jsx("code",{children:e.homeDir}),m>0&&` (${m} hidden)`]})]})]})}function $e({claudeHome:e}){return s.jsx(O,{variant:"panel",icon:s.jsx(ce,{width:22,height:22}),title:"No Claude Code history on this machine",body:s.jsxs(s.Fragment,{children:["Nothing has been recorded under ",s.jsx("code",{className:"break-all",children:e}),". That is normal for a container, whose Claude home is its own rather than yours — bind-mount your history there and re-open this page, or import it headlessly with"," ",s.jsx("code",{children:"npm run import-chats"}),"."]})})}function Ie({imported:e,projects:t,onGetStarted:r}){return s.jsx("div",{className:"mb-6",children:s.jsx(O,{variant:"panel",icon:s.jsx(U,{width:22,height:22}),title:t===0?"Nothing was imported":`${t} project${t===1?"":"s"}, ${e} conversation${e===1?"":"s"}`,body:t===0?"Every row failed. Each one says why below.":"They are in the sidebar now. Anything that did not land says why below.",action:s.jsx(R,{variant:"primary",onClick:()=>void r(),children:"Get started"})})})}function Ee(){return s.jsx("div",{className:"space-y-2","aria-label":"Scanning",role:"status",children:[0,1,2].map(e=>s.jsx("div",{className:"h-16 animate-pulse rounded-2xl bg-surface-active"},e))})}function Pe(e){const t=[],r=[["temp-root",n=>`${n} in temp directories`],["no-git",n=>`${n} without a git repository`],["outside-home",n=>`${n} outside your home directory`],["already-managed",n=>`${n} already a project`],["no-sessions",n=>`${n} with nothing importable`],["no-recorded-cwd",n=>`${n} with no recorded directory`],["missing",n=>`${n} no longer on disk`],["home-root",n=>`${n} your home directory itself`],["system-path",n=>`${n} system directories`],["paddock-internal",n=>`${n} inside Paddock's own data`]];for(const[n,d]of r){const h=e.excluded[n];h!==void 0&&h>0&&t.push(d(h))}const a=`${e.scanned} transcript folder${e.scanned===1?"":"s"} scanned`;return t.length===0?`${a}.`:`${a}; skipped ${t.join(", ")}.`}function _(e){var a,n;const t=(a=e.autoName)==null?void 0:a.trim();if(t!==void 0&&t!=="")return t;const r=(n=e.preview)==null?void 0:n.trim();return r!==void 0&&r!==""?r:e.sessionId}function q(e){const t=new Date(e);return Number.isNaN(t.getTime())?"unknown date":t.toLocaleDateString(void 0,{year:"numeric",month:"short",day:"numeric"})}function Te(e){return e<1024?`${e} B`:e<1024*1024?`${Math.round(e/1024)} kB`:`${(e/(1024*1024)).toFixed(1)} MB`}export{ze as D};
|
|
2
2
|
|