@timqi/pier 0.0.16 → 0.0.19
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/dist/agent/events.js +16 -4
- package/dist/agent/listing.js +12 -13
- package/dist/agent/pi.js +28 -2
- package/dist/boards/boards.js +7 -6
- package/dist/channels/attach.js +18 -4
- package/dist/channels/config.js +7 -2
- package/dist/channels/dedup.js +9 -1
- package/dist/channels/lark.js +2 -6
- package/dist/channels/receipts.js +21 -6
- package/dist/channels/slack-tool.js +86 -25
- package/dist/channels/slack.js +2 -10
- package/dist/channels/telegram.js +1 -0
- package/dist/core/hub.js +14 -4
- package/dist/core/inbox.js +1 -1
- package/dist/core/reply.js +64 -0
- package/dist/db.js +105 -0
- package/dist/main.js +1 -0
- package/dist/secrets.js +18 -6
- package/dist/settings.js +2 -11
- package/dist/tasks/agent.js +14 -1
- package/dist/tasks/definitions.js +3 -1
- package/dist/tasks/messages.js +1 -2
- package/dist/tasks/service.js +5 -0
- package/dist/tasks/store.js +44 -12
- package/dist/tools.js +42 -18
- package/dist/web/auth.js +212 -68
- package/dist/web/config.js +61 -0
- package/dist/web/explorer.js +20 -70
- package/dist/web/fs.js +159 -0
- package/dist/web/instance.js +11 -0
- package/dist/web/public/assets/activity-BsoWK6vt.js +5 -0
- package/dist/web/public/assets/activity-BsoWK6vt.js.br +0 -0
- package/dist/web/public/assets/activity-BsoWK6vt.js.gz +0 -0
- package/dist/web/public/assets/boards-DEMQ51m1.js +1 -0
- package/dist/web/public/assets/boards-DEMQ51m1.js.br +0 -0
- package/dist/web/public/assets/boards-DEMQ51m1.js.gz +0 -0
- package/dist/web/public/assets/code-DjWvqL94.js +1 -0
- package/dist/web/public/assets/explorer-BTGQ6Syc.js +5 -0
- package/dist/web/public/assets/explorer-BTGQ6Syc.js.br +0 -0
- package/dist/web/public/assets/explorer-BTGQ6Syc.js.gz +0 -0
- package/dist/web/public/assets/{ghostty-web-C4ivXTBE.js → ghostty-web-D8CLFi_l.js} +1 -1
- package/dist/web/public/assets/ghostty-web-D8CLFi_l.js.br +0 -0
- package/dist/web/public/assets/ghostty-web-D8CLFi_l.js.gz +0 -0
- package/dist/web/public/assets/hljs-tWqyD59G.js +3 -0
- package/dist/web/public/assets/hljs-tWqyD59G.js.br +0 -0
- package/dist/web/public/assets/hljs-tWqyD59G.js.gz +0 -0
- package/dist/web/public/assets/index-BFzmvpGX.js +78 -0
- package/dist/web/public/assets/index-BFzmvpGX.js.br +0 -0
- package/dist/web/public/assets/index-BFzmvpGX.js.gz +0 -0
- package/dist/web/public/assets/{index-2E9_cwpg.css → index-yNocr797.css} +1 -1
- package/dist/web/public/assets/index-yNocr797.css.br +0 -0
- package/dist/web/public/assets/index-yNocr797.css.gz +0 -0
- package/dist/web/public/assets/settings-DSTKXN2o.js +6 -0
- package/dist/web/public/assets/settings-DSTKXN2o.js.br +0 -0
- package/dist/web/public/assets/settings-DSTKXN2o.js.gz +0 -0
- package/dist/web/public/assets/tasks-Dy1SCAE_.js +4 -0
- package/dist/web/public/assets/tasks-Dy1SCAE_.js.br +0 -0
- package/dist/web/public/assets/tasks-Dy1SCAE_.js.gz +0 -0
- package/dist/web/public/assets/terminal-zLyOvGto.js +2 -0
- package/dist/web/public/assets/terminal-zLyOvGto.js.br +0 -0
- package/dist/web/public/assets/terminal-zLyOvGto.js.gz +0 -0
- package/dist/web/public/icon.svg.br +0 -0
- package/dist/web/public/icon.svg.gz +0 -0
- package/dist/web/public/index.html +2 -2
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/manifest.webmanifest.br +0 -0
- package/dist/web/public/manifest.webmanifest.gz +0 -0
- package/dist/web/public/sw.js.br +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/push.js +30 -7
- package/dist/web/route.js +17 -0
- package/dist/web/server.js +104 -23
- package/dist/web/terminal.js +21 -16
- package/docs/deploy.md +12 -3
- package/package.json +1 -1
- package/skills/pier-slack/SKILL.md +4 -16
- package/dist/web/files.js +0 -183
- package/dist/web/public/assets/index-DVUvzNK1.js +0 -93
package/dist/web/fs.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// Every path the Console reaches on disk resolves here: the browse/read/mkdir
|
|
2
|
+
// routes, the session attachments server.ts serves, and the pty's cwd all ask
|
|
3
|
+
// the same question, and asked it four different ways until this file existed.
|
|
4
|
+
//
|
|
5
|
+
// A `root` is any directory the process can read — sessions work in worktrees
|
|
6
|
+
// and siblings of their cwd, and an owner already past the Console password
|
|
7
|
+
// can reach those paths anyway. What is confined is `path`: it resolves under
|
|
8
|
+
// the `root` it was asked with, realpath on both ends, so neither `..` nor a
|
|
9
|
+
// symlink steps outside and a listing can never widen itself. Only mkdir
|
|
10
|
+
// writes, and only a name.
|
|
11
|
+
import { mkdir, open, readdir, realpath, stat } from "node:fs/promises";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { basename, dirname, extname, isAbsolute, resolve, sep } from "node:path";
|
|
14
|
+
import { Readable } from "node:stream";
|
|
15
|
+
import { logger } from "../log.js";
|
|
16
|
+
import { guarded } from "./route.js";
|
|
17
|
+
const log = logger("web");
|
|
18
|
+
/** What a browser is asked to hold at once — the same ceiling for a preview
|
|
19
|
+
* and for an attachment, because it is the reader's patience, not the route's. */
|
|
20
|
+
export const MAX_FILE_BYTES = 32 * 1024 * 1024;
|
|
21
|
+
/** Types a browser may render as themselves. Everything else is sniffed for
|
|
22
|
+
* text, and bytes we can't vouch for download instead of rendering — that is
|
|
23
|
+
* how a file starts executing. */
|
|
24
|
+
const RENDERABLE = {
|
|
25
|
+
".png": "image/png",
|
|
26
|
+
".jpg": "image/jpeg",
|
|
27
|
+
".jpeg": "image/jpeg",
|
|
28
|
+
".gif": "image/gif",
|
|
29
|
+
".webp": "image/webp",
|
|
30
|
+
".avif": "image/avif",
|
|
31
|
+
".bmp": "image/bmp",
|
|
32
|
+
".svg": "image/svg+xml",
|
|
33
|
+
".pdf": "application/pdf",
|
|
34
|
+
};
|
|
35
|
+
/** The containment check, and the only way a path becomes a path this reads:
|
|
36
|
+
* `root` must be an absolute directory, and the returned target is `path`
|
|
37
|
+
* resolved inside it. Throws — the route wrapper turns that into the answer. */
|
|
38
|
+
export async function scoped(root, path = "") {
|
|
39
|
+
if (!root || !isAbsolute(root))
|
|
40
|
+
throw new Error("not a directory this can read");
|
|
41
|
+
const real = await realpath(root);
|
|
42
|
+
if (!(await stat(real)).isDirectory())
|
|
43
|
+
throw new Error("not a directory this can read");
|
|
44
|
+
const target = await realpath(resolve(real, path));
|
|
45
|
+
if (target !== real && !target.startsWith(real + sep))
|
|
46
|
+
throw new Error("path escapes root");
|
|
47
|
+
return target;
|
|
48
|
+
}
|
|
49
|
+
/** The same containment against several roots, for the caller that has more
|
|
50
|
+
* than one: a session's attachments come from its own cwd or from the inbox
|
|
51
|
+
* its inbound files landed in. null rather than a throw, because to that
|
|
52
|
+
* caller "outside every root", "not a file" and "gone" are one answer. */
|
|
53
|
+
export async function scopedFile(roots, path) {
|
|
54
|
+
for (const root of roots) {
|
|
55
|
+
try {
|
|
56
|
+
const file = await scoped(root, path);
|
|
57
|
+
if ((await stat(file)).isFile())
|
|
58
|
+
return file;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
/* outside this root, unreadable, or gone — try the next */
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/** How bytes leave: the type they may be served as, and whether a browser may
|
|
67
|
+
* render them in place. `download` forces the attachment disposition — the
|
|
68
|
+
* chat's Download button, on a file it would otherwise show inline. */
|
|
69
|
+
export function fileHeaders(file, bytes, download = false) {
|
|
70
|
+
const ext = extname(file).toLowerCase();
|
|
71
|
+
const known = RENDERABLE[ext];
|
|
72
|
+
const text = !known && !bytes.subarray(0, 8192).includes(0);
|
|
73
|
+
// An <img> renders its source whatever the disposition says, but a tab
|
|
74
|
+
// navigated straight at an SVG runs the script inside it — same origin, past
|
|
75
|
+
// the password. So an SVG keeps its type and loses only its own tab.
|
|
76
|
+
const inline = !download && (text || (known !== undefined && ext !== ".svg"));
|
|
77
|
+
return {
|
|
78
|
+
"content-type": known ?? (text ? "text/plain; charset=utf-8" : "application/octet-stream"),
|
|
79
|
+
"content-disposition": `${inline ? "inline" : "attachment"}; filename*=UTF-8''${encodeURIComponent(basename(file))}`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export function registerFsRoutes(app) {
|
|
83
|
+
// One listing behind both trees: the Files view walks a project (`root`,
|
|
84
|
+
// which confines it and is the floor `parent` stops at), the cwd pickers
|
|
85
|
+
// walk from anywhere (no root, starting at home). Names and which are
|
|
86
|
+
// directories — never contents, and never `.git`, which is plumbing. What to
|
|
87
|
+
// leave out of the answer is the caller's business: one tree shows folders
|
|
88
|
+
// only, the other shows files too.
|
|
89
|
+
guarded(app, "GET", "/api/fs/ls", 404, async (c) => {
|
|
90
|
+
c.header("cache-control", "no-store");
|
|
91
|
+
const asked = c.req.query("path");
|
|
92
|
+
const root = c.req.query("root");
|
|
93
|
+
const top = root ? await scoped(root) : null;
|
|
94
|
+
const dir = top ? await scoped(top, asked) : await scoped(asked || homedir());
|
|
95
|
+
const up = dirname(dir);
|
|
96
|
+
const entries = (await readdir(dir, { withFileTypes: true }))
|
|
97
|
+
.filter((e) => e.name !== ".git" && (e.isDirectory() || e.isFile()))
|
|
98
|
+
.map((e) => ({ name: e.name, dir: e.isDirectory() }))
|
|
99
|
+
.sort((a, b) => Number(b.dir) - Number(a.dir) || a.name.localeCompare(b.name));
|
|
100
|
+
return c.json({ path: dir, parent: dir === top || up === dir ? null : up, entries });
|
|
101
|
+
});
|
|
102
|
+
// File bytes, read-only: the Files view's previews and whole-file reads.
|
|
103
|
+
guarded(app, "GET", "/api/fs/file", 404, async (c) => {
|
|
104
|
+
const file = await scoped(c.req.query("root"), c.req.query("path"));
|
|
105
|
+
const handle = await open(file);
|
|
106
|
+
let streaming = false;
|
|
107
|
+
try {
|
|
108
|
+
const info = await handle.stat();
|
|
109
|
+
if (!info.isFile())
|
|
110
|
+
throw new Error("not a file");
|
|
111
|
+
if (info.size > MAX_FILE_BYTES)
|
|
112
|
+
return c.json({ error: "file too large" }, 413);
|
|
113
|
+
// Size and mtime, not a digest: the view re-reads a file every time it is
|
|
114
|
+
// clicked again, and a validator the stat above already knows costs
|
|
115
|
+
// nothing to offer. `no-cache` keeps the answer conditional, so an edited
|
|
116
|
+
// file is never shown from a browser cache.
|
|
117
|
+
const tag = `"${info.size.toString(16)}-${info.mtime.getTime().toString(16)}"`;
|
|
118
|
+
const validators = {
|
|
119
|
+
etag: tag,
|
|
120
|
+
"last-modified": info.mtime.toUTCString(),
|
|
121
|
+
"cache-control": "private, no-cache",
|
|
122
|
+
};
|
|
123
|
+
if (c.req.header("if-none-match") === tag)
|
|
124
|
+
return c.body(null, 304, validators);
|
|
125
|
+
// Sniff and stream the same open file, so a replacement cannot bypass
|
|
126
|
+
// the size check or make the headers describe different bytes.
|
|
127
|
+
const head = Buffer.alloc(Math.min(8192, info.size));
|
|
128
|
+
await handle.read(head, 0, head.length, 0);
|
|
129
|
+
const bytes = handle.createReadStream({ start: 0 });
|
|
130
|
+
streaming = true;
|
|
131
|
+
// Past the headers a failure can only truncate the body, so this is the
|
|
132
|
+
// one place it can still be said at all (§5b).
|
|
133
|
+
bytes.on("error", (err) => log.warn(`serving ${file} stopped mid-stream`, err));
|
|
134
|
+
return c.body(Readable.toWeb(bytes), 200, {
|
|
135
|
+
...fileHeaders(file, head),
|
|
136
|
+
...validators,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
if (!streaming)
|
|
141
|
+
await handle.close();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
// Create a folder while picking one — a new project usually needs a new
|
|
145
|
+
// directory. A name, never a path: traversal is rejected, not normalized.
|
|
146
|
+
guarded(app, "POST", "/api/fs/mkdir", 400, async (c) => {
|
|
147
|
+
const body = await c.req.json().catch(() => null);
|
|
148
|
+
const parent = typeof body?.path === "string" ? body.path : "";
|
|
149
|
+
const name = typeof body?.name === "string" ? body.name.trim() : "";
|
|
150
|
+
if (!isAbsolute(parent))
|
|
151
|
+
return c.json({ error: "absolute path required" }, 400);
|
|
152
|
+
if (!name || name.length > 64 || /[/\\]|^\.\.?$/.test(name)) {
|
|
153
|
+
return c.json({ error: "invalid folder name" }, 400);
|
|
154
|
+
}
|
|
155
|
+
const path = resolve(parent, name);
|
|
156
|
+
await mkdir(path); // no recursive: the parent must already exist
|
|
157
|
+
return c.json({ path });
|
|
158
|
+
});
|
|
159
|
+
}
|
package/dist/web/instance.js
CHANGED
|
@@ -302,6 +302,17 @@ export function registerInstanceRoutes(app, deps) {
|
|
|
302
302
|
onUnlocked?.();
|
|
303
303
|
return c.json(secretsStatus());
|
|
304
304
|
});
|
|
305
|
+
// Why vt cannot hand the key over is vt's answer, not Pier's: config
|
|
306
|
+
// sources, routing, whether an agent is listening. Read-only and safe while
|
|
307
|
+
// locked — without it "locked" is one error string and no way to repair it.
|
|
308
|
+
app.get("/api/secrets/doctor", async (c) => {
|
|
309
|
+
try {
|
|
310
|
+
return c.json({ report: await secrets.doctor() });
|
|
311
|
+
}
|
|
312
|
+
catch (err) {
|
|
313
|
+
return c.json({ error: String(err) }, 500);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
305
316
|
app.post("/api/secrets/rotate", async (c) => {
|
|
306
317
|
const body = (await c.req.json().catch(() => ({})));
|
|
307
318
|
if (body.mode !== undefined && body.mode !== "vt" && body.mode !== "file") {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{F as e,I as t,K as n,M as r,R as i,T as a,V as o,z as s}from"./index-BFzmvpGX.js";var c=(e,t={})=>{let n=document.createElementNS(`http://www.w3.org/2000/svg`,e);for(let[e,r]of Object.entries(t))n.setAttribute(e,r);return n},l=t=>t===null?`-`:e(Date.now()-t),u={light:{edge:`#a3a3a3`,callback:`#0891b2`,message:`#d97706`,scheduler:{fill:`#f5f3ff`,stroke:`#a78bfa`,text:`#5b21b6`},console:{fill:`#eff6ff`,stroke:`#93c5fd`,text:`#1d4ed8`},task:{fill:`#fffbeb`,stroke:`#fbbf24`,text:`#92400e`},process:{fill:`#f5f5f5`,stroke:`#a3a3a3`,text:`#525252`,dash:`4 3`},streaming:{fill:`#ffffff`,stroke:`#10b981`,text:`#262626`},idle:{fill:`#fafafa`,stroke:`#d4d4d4`,text:`#737373`}},dark:{edge:`#7a7a7a`,callback:`#3fc3dd`,message:`#e0a13a`,scheduler:{fill:`#2b2440`,stroke:`#7d63c9`,text:`#c9b8f5`},console:{fill:`#1f2a3d`,stroke:`#4a7fbf`,text:`#a8c8f0`},task:{fill:`#3a2f1c`,stroke:`#b8862a`,text:`#f0d49a`},process:{fill:`#262626`,stroke:`#5c5c5c`,text:`#a3a3a3`,dash:`4 3`},streaming:{fill:`#22302a`,stroke:`#10b981`,text:`#e5e5e5`},idle:{fill:`#212121`,stroke:`#4a4a4a`,text:`#9a9a9a`}}},d=()=>u[document.documentElement.dataset.theme===`dark`?`dark`:`light`];function f(e,u,f){let p=`sessions`,m=`active`,h={sessions:[],runs:[],messages:[]},g=``,_=s(async()=>{let r=m,i=await o(`/api/activity?scope=${r}`,`Failed to load activity`);if(!i.ok){g=``,e.replaceChildren(t(`p`,`p-4 text-[13px] text-red-600`,i.error));return}if(r!==m)return;let a=JSON.stringify(i.value);if(a===g)return;g=a;let s=i.value;h={...s,sessions:s.sessions.map(e=>({...e,title:n(e.title)}))},v()});function v(){let n=t(`header`,`flex h-10 flex-none items-center gap-2 border-b border-neutral-200 px-4 max-md:hidden`,t(`span`,`font-medium`,`Activity`)),r=t(`div`,`tabstrip`);r.append(a(`Sessions`,p===`sessions`,()=>{p=`sessions`,v()}),a(`Dependencies`,p===`dependencies`,()=>{p=`dependencies`,v()}),a(`Tasks`,!1,()=>f()));let i=t(`div`,`ml-auto flex gap-1 max-md:ml-0 max-md:w-full`),o=e=>()=>{m=e,v(),_()};i.append(a(`Active`,m===`active`,o(`active`)),a(`Last 24h`,m===`recent`,o(`recent`))),r.append(i);let s=t(`div`,`min-h-0 flex-1 overflow-auto`);p===`sessions`?y(s):b(s),e.replaceChildren(n,r,s)}function y(e){let n=document.createElement(`table`);n.className=`w-full min-w-[38rem] table-fixed text-left text-[12.5px]`,n.innerHTML=`<thead class="bg-neutral-50 text-[10.5px] uppercase text-neutral-400"><tr>
|
|
2
|
+
<th class="w-[34%] px-4 py-2 font-semibold">Session</th>
|
|
3
|
+
<th class="w-[38%] px-2 py-2 font-semibold">Project</th>
|
|
4
|
+
<th class="w-[14%] px-2 py-2 font-semibold">State</th>
|
|
5
|
+
<th class="px-2 py-2 font-semibold">Duration</th></tr></thead>`;let r=document.createElement(`tbody`);for(let e of h.sessions){let n=document.createElement(`tr`);n.className=`cursor-pointer border-b border-neutral-100 hover:bg-neutral-50`,n.onclick=()=>u(e.id),n.append(t(`td`,`px-4 py-2.5`,t(`div`,`truncate font-medium`,e.title??i(e.cwd)),t(`div`,`truncate font-mono text-[11px] text-neutral-400`,e.id)),t(`td`,`truncate px-2 py-2.5 font-mono text-[11.5px]`,e.cwd||`-`),t(`td`,`px-2 py-2.5`,t(`span`,`mr-2 inline-block h-2 w-2 rounded-full ${e.state===`streaming`?`animate-pulse bg-green-500`:`bg-neutral-300`}`),e.state),t(`td`,`px-2 py-2.5 text-neutral-500`,l(e.stateSince))),r.append(n)}n.append(r),e.append(n),h.sessions.length||e.append(t(`p`,`p-4 text-[13px] text-neutral-400`,m===`active`?`No active sessions.`:`No sessions in the last 24 hours.`))}function b(e){let n=new Map,r=[],i=(e,t,r,i)=>{n.has(e)||n.set(e,{id:e,label:t,kind:r,state:i})};for(let e of h.sessions)i(e.id,e.title??e.id.slice(0,10),`session`,e.state);for(let e of h.runs){let t=e.invokedBySessionId??`scheduler`,n=e.targetSessionId??(e.context.definition.action.type===`bash`?`process:${e.id}`:`task:${e.id}`);i(t,t===`scheduler`?`Scheduler`:t.slice(0,10),t===`scheduler`?`scheduler`:`session`),i(n,e.targetSessionId?e.targetSessionId.slice(0,10):e.context.definition.action.type===`bash`?`Bash process`:`Task`,e.targetSessionId?`session`:e.context.definition.action.type===`bash`?`process`:`task`),r.push({from:t,to:n,run:e,kind:`invocation`}),e.callbackSessionId&&(i(e.callbackSessionId,e.callbackSessionId.slice(0,10),`session`),r.push({from:n,to:e.callbackSessionId,run:e,kind:`callback`}))}for(let e of h.messages){if(!e.fromSessionId||!e.toSessionId||e.fromSessionId===e.toSessionId)continue;let t=h.runs.find(t=>t.id===e.runId);if(t){for(let t of[e.fromSessionId,e.toSessionId])i(t,t===`console`?`Console`:t.slice(0,10),t===`console`?`console`:`session`);r.push({from:e.fromSessionId,to:e.toSessionId,run:t,kind:`message`})}}if(!n.size){e.append(t(`p`,`p-4 text-[13px] text-neutral-400`,m===`active`?`No active dependencies.`:`No dependencies in the last 24 hours.`));return}let a=[...n.values()],o=new Set(r.map(e=>e.to)),s=new Map,l=a.filter(e=>!o.has(e.id)).map(e=>e.id);l.length||l.push(a[0].id);for(let e of l)s.set(e,0);for(;l.length;){let e=l.shift();for(let t of r)t.from!==e||s.has(t.to)||(s.set(t.to,s.get(e)+1),l.push(t.to))}for(let e of a)s.has(e.id)||s.set(e.id,0);let p=new Map;for(let e of a){let t=s.get(e.id);p.set(t,[...p.get(t)??[],e])}let g=Math.max(...p.keys())+1,_=Math.max(...[...p.values()].map(e=>e.length)),v=r.some(e=>e.from!==e.to&&s.get(e.from)===s.get(e.to))?40:0,y=56+g*180+(g-1)*110+v,b=56+_*46+(_-1)*30,x=new Map;for(let[e,t]of p)t.forEach((t,n)=>x.set(t.id,{x:28+e*290+90,y:28+n*76+23}));let S=c(`svg`,{width:String(y),height:String(b),viewBox:`0 0 ${y} ${b}`,class:`block`}),C=c(`defs`),w=d();for(let[e,t]of[[`activity-arrow`,w.edge],[`activity-arrow-cb`,w.callback],[`activity-arrow-msg`,w.message]]){let n=c(`marker`,{id:e,viewBox:`0 0 10 10`,refX:`9`,refY:`5`,markerWidth:`6`,markerHeight:`6`,orient:`auto-start-reverse`});n.append(c(`path`,{d:`M 0 0 L 10 5 L 0 10 z`,fill:t})),C.append(n)}S.append(C);let T=new Set;for(let e of r){let t=`${e.from}→${e.to}:${e.kind}`;if(T.has(t))continue;T.add(t);let n=x.get(e.from),r=x.get(e.to),i=c(`path`,{d:n.x===r.x?`M ${n.x+90} ${n.y} C ${n.x+90+46} ${n.y}, ${r.x+90+46} ${r.y}, ${r.x+90} ${r.y}`:(()=>{let e=r.x>n.x,t=n.x+(e?90:-90),i=r.x+(e?-90:90),a=Math.max(36,Math.abs(i-t)*.45)*(e?1:-1);return`M ${t} ${n.y} C ${t+a} ${n.y}, ${i-a} ${r.y}, ${i} ${r.y}`})(),fill:`none`,stroke:e.kind===`callback`?w.callback:e.kind===`message`?w.message:w.edge,"stroke-width":`1.5`,"marker-end":e.kind===`callback`?`url(#activity-arrow-cb)`:e.kind===`message`?`url(#activity-arrow-msg)`:`url(#activity-arrow)`});e.kind===`callback`&&i.setAttribute(`stroke-dasharray`,`6 5`),e.kind===`message`&&i.setAttribute(`stroke-dasharray`,`2 5`),i.classList.add(`cursor-pointer`),i.onclick=()=>f(e.run.taskId),S.append(i)}let E=/[\u1100-\u11FF\u2E80-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFF60\uFFE0-\uFFE6]/,D=(e,t)=>{let n=0;for(let r=0;r<e.length;r++)if(n+=E.test(e[r])?2:1,n>t)return`${e.slice(0,r)}…`;return e};for(let e of a){let t=x.get(e.id),n=e.kind===`session`?e.state===`streaming`?w.streaming:w.idle:w[e.kind],r=c(`g`,{transform:`translate(${t.x},${t.y})`});e.kind===`session`&&r.classList.add(`cursor-pointer`),r.onclick=()=>{e.kind===`session`&&u(e.id)};let i=c(`rect`,{x:`-90`,y:`-23`,width:`180`,height:`46`,rx:`10`,fill:n.fill,stroke:n.stroke,"stroke-width":e.kind===`session`&&e.state===`streaming`?`1.5`:`1`});n.dash&&i.setAttribute(`stroke-dasharray`,n.dash),r.append(i);let a=-78;if(e.kind===`session`){let t=c(`circle`,{cx:`-75`,cy:`0`,r:`3.5`,fill:n.stroke});if(e.state===`streaming`){let e=c(`animate`,{attributeName:`opacity`,values:`1;0.3;1`,dur:`1.6s`,repeatCount:`indefinite`});t.append(e)}r.append(t),a=-64}let o=c(`text`,{x:String(a),"text-anchor":`start`,"dominant-baseline":`middle`,"font-size":`11.5`,"font-weight":e.kind===`session`?`500`:`400`,fill:n.text});o.textContent=D(e.label,e.kind===`session`?22:25);let s=c(`title`);s.textContent=e.label===e.id?e.id:`${e.label}\n${e.id}`,r.append(o,s),S.append(r)}let O=e=>t(`span`,`inline-block h-2 w-2 rounded-full ${e}`);e.append(t(`div`,`w-max p-4`,S),t(`div`,`flex flex-wrap gap-x-5 gap-y-1 border-t border-neutral-200 px-4 py-2 text-[11px] text-neutral-500`,t(`span`,``,`Solid: task invocation`),t(`span`,`text-cyan-700`,`Dashed: callback`),t(`span`,`text-amber-700`,`Dotted: supervisor/control`),t(`span`,`inline-flex items-center gap-1.5`,O(`bg-emerald-500`),`streaming`),t(`span`,`inline-flex items-center gap-1.5`,O(`bg-neutral-300`),`idle`)))}let x=r(e,e=>{e===`dependencies`?p=`dependencies`:e===`sessions`&&(p=`sessions`),_()},()=>{e.replaceChildren(),h={sessions:[],runs:[],messages:[]},g=``});return window.addEventListener(`pier:theme`,()=>{x.visible&&v()}),Object.assign(x,{refresh(){x.visible&&_()}})}export{f as createActivityView};
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{B as e,G as t,I as n,L as r,M as i,N as a,O as o,V as s,W as c,h as l}from"./index-BFzmvpGX.js";var u=e=>e.public&&e.token?`/p/${e.slug}-${e.token}/`:`/boards/${e.slug}/`;function d(d,f){let p=[],m=``,h=n(`header`,`sticky top-0 z-30 flex h-10 items-center gap-3 border-b border-neutral-200 bg-white px-4`,n(`span`,`font-medium max-md:hidden`,`Boards`)),g=n(`div`,`px-4 py-5`);d.append(n(`div`,`min-h-0 flex-1 overflow-y-auto`,h,g));async function _(n,r){let i=await t(`/api/boards/${n.slug}`,{public:r},`PATCH`);if(i.ok)return n.token=(await i.json().catch(()=>({}))).token??``,x();m=await e(i,`Could not change ${n.slug}`),await S()}async function v(e){m=await c(`/api/boards/${e}`,`DELETE`,`Could not delete ${e}`)??``,await S()}function y(e){let t=n(`div`,`group flex flex-col gap-1 border-b border-neutral-200/70 px-1 py-2.5 last:border-b-0`),i=document.createElement(`a`);i.className=`truncate font-medium text-neutral-800 hover:text-indigo-700`,i.href=u(e),i.target=`_blank`,i.rel=`noreferrer`,i.textContent=e.title;let s=n(`div`,`flex items-center gap-2`,i,n(`span`,`flex-none font-mono text-[11.5px] text-neutral-400`,e.slug)),c=o(``,``,e.public,t=>{e.public=t,_(e,t)});c.className=`ml-auto flex flex-none cursor-pointer items-center gap-1.5 text-[11.5px] text-neutral-500`,c.append(n(`span`,``,`Public`));let d=l(`Delete`,`flex-none cursor-pointer text-[11.5px] text-neutral-400 opacity-0 transition-opacity hover:text-red-600 group-hover:opacity-100`);d.title=`Renames the folder on disk; nothing is erased`,d.onclick=()=>void v(e.slug);let p=a(`flex-none cursor-pointer text-[11.5px] text-neutral-400 opacity-0 transition-opacity hover:text-indigo-600 group-hover:opacity-100`,()=>`${location.origin}${u(e)}`);p.title=`Copy the board's link`,s.append(c,p,d),t.append(s);let m=n(`div`,`flex flex-wrap items-center gap-x-3 gap-y-1 text-[11.5px] text-neutral-500`);e.description&&m.append(n(`span`,`min-w-0 truncate`,e.description)),m.append(n(`span`,`flex-none text-neutral-400`,r(Date.parse(e.updatedAt))));for(let t of e.sessions){let e=l(t.slice(0,8),`flex-none cursor-pointer font-mono text-indigo-600 hover:underline`);e.title=`Open session ${t}`,e.onclick=()=>f(t),m.append(e)}if(e.public&&e.token){let t=n(`span`,`flex-none font-mono text-neutral-400`,u(e));t.title=`Public URL — anyone holding this link can read the board, no password`,m.append(t)}return t.append(m),t}let b=[{title:`Public`,hint:`Readable at /p/<slug>-<token>/ by anyone holding the link.`,wanted:!0},{title:`Private`,hint:`Reachable from the Console only.`,wanted:!1}];function x(){let e=n(`div`,`mx-auto flex max-w-3xl flex-col`);p.length===0&&e.append(n(`p`,`rounded-xl border border-dashed border-neutral-300 px-4 py-6 text-center text-[12.5px] text-neutral-500`,`No boards yet. Ask an agent to build one — it writes a folder under ~/.pier/boards.`));for(let{title:t,hint:r,wanted:i}of b){let a=p.filter(e=>e.public===i);if(a.length===0)continue;let o=n(`section`,`mb-6`);o.append(n(`h2`,`text-[13px] font-semibold text-neutral-700`,t)),o.append(n(`p`,`mb-1 text-[11.5px] leading-snug text-neutral-500`,r));let s=n(`div`,`rounded-xl border border-neutral-200 bg-white px-3`);s.append(...a.map(y)),o.append(s),e.append(o)}g.replaceChildren(...m?[n(`p`,`mb-3 text-[13px] text-red-600`,m)]:[],e)}async function S(){let e=await s(`/api/boards`,`Could not load boards`);if(!e.ok)return m=e.error,x();p=e.value,x(),m=``}let C=i(d,()=>void S());return window.addEventListener(`focus`,()=>{C.visible&&S()}),C}export{d as createBoardsView};
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{I as e,u as t}from"./index-BFzmvpGX.js";var n=2e4,r=e=>e.map((e,t)=>({nums:[t+1],text:e,tone:``}));function i(t,r){let i=t.length<=n?r:null,o=Math.max(2,...t.map(e=>e.nums.reduce((e,t)=>Math.max(e,String(t).length),0))),s=e(`div`,`py-2 font-mono text-[12px] leading-[1.5]`);for(let n of t){let t=e(`div`,`flex pl-2 pr-4 ${n.tone===`add`?`bg-emerald-50`:n.tone===`del`?`bg-red-50`:``}`);for(let r of n.nums){let n=e(`span`,`flex-none select-none pr-1.5 text-right text-neutral-300`,r===``?``:String(r));n.style.width=`calc(${o}ch + 0.375rem)`,t.append(n)}t.append(a(n,i)),s.append(t)}return s}function a(n,r){let i=`min-w-0 flex-1 whitespace-pre-wrap [overflow-wrap:anywhere]`;if(!n.mark||n.mark[0]>=n.mark[1]){let e=t(n.text,r);return e.className=i,e}let[a,o]=n.mark,s=t(n.text.slice(a,o),r);return s.className=`rounded-xs ${n.tone===`add`?`bg-emerald-200/80`:`bg-red-200/70`}`,e(`span`,i,t(n.text.slice(0,a),r),s,t(n.text.slice(o),r))}export{r as n,i as t};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{H as e,I as t,L as n,M as r,P as i,a,i as o,j as s,l as c,n as l,s as u}from"./index-BFzmvpGX.js";import{n as d,t as f}from"./code-DjWvqL94.js";var p=e=>{let t=new Date(e),n=e=>String(e).padStart(2,`0`);return`${t.getFullYear()}-${n(t.getMonth()+1)}-${n(t.getDate())} ${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`},m=e=>[e.subject,e.body,[[e.author,e.email?`<${e.email}>`:``].filter(Boolean).join(` `),e.at?p(e.at):``].filter(Boolean).join(` · `)].filter(Boolean).join(`
|
|
2
|
+
|
|
3
|
+
`),h=null;function g(){h&&(h.remove(),h=null,document.removeEventListener(`pointerdown`,g,!0),window.removeEventListener(`scroll`,g,!0))}function _(e,n){e.onmouseenter=()=>{g();let r=n();if(!r)return;h=t(`div`,`pointer-events-none fixed z-50 max-w-96 whitespace-pre-wrap rounded-lg bg-neutral-800/95 px-3 py-2 font-sans text-[11.5px] leading-snug text-neutral-100 shadow-lg`),h.textContent=r,document.body.append(h),document.addEventListener(`pointerdown`,g,!0),window.addEventListener(`scroll`,g,!0);let i=e.getBoundingClientRect();h.style.left=`${Math.max(8,Math.min(i.left,window.innerWidth-h.offsetWidth-8))}px`,h.style.top=i.bottom+6+h.offsetHeight>window.innerHeight?`${i.top-h.offsetHeight-6}px`:`${i.bottom+6}px`},e.onmouseleave=g}function v(e,r,i){let a=t(`button`,`flex w-full cursor-pointer items-center gap-1.5 px-2 py-1 text-left hover:bg-neutral-100`,t(`span`,`w-3 flex-none text-indigo-600`,e.value===r?`✓`:``),t(`span`,`flex-none font-mono text-[11.5px] text-neutral-800`,e.label??e.value));return e.subject&&a.append(t(`span`,`min-w-0 truncate text-[11px] text-neutral-400`,e.subject)),e.author&&a.append(t(`span`,`ml-auto flex-none pl-1.5 text-[10.5px] text-neutral-400`,`${e.author} · ${e.at?n(e.at):``}`)),_(a,()=>m(e)),a.onclick=()=>i(e.value),a}function ee(e,n,r,i,s){let{base:c,head:l}=r,u=()=>l&&c===`${l}~1`?l:``,d=u()?`commit`:`compare`,f=n.commits[0],p=e=>[...e?[{value:``,label:`Working tree`,subject:`the files on disk, uncommitted changes included`}]:[],{...f,value:`HEAD`},...n.refs.map(e=>({value:e.name,subject:e.subject})),...n.commits.map(e=>({...e,value:e.hash}))],m=t(`div`,`flex w-[38rem] max-w-full flex-col`);function h(){let e=(e,n)=>{let r=t(`button`,`cursor-pointer rounded-full px-2.5 py-0.5 text-[11.5px] ${d===n?`bg-indigo-50 font-medium text-indigo-700`:`text-neutral-500 hover:bg-neutral-100`}`,e);return r.onclick=()=>{d=n,h()},r},r=(e,n,r,i)=>t(`div`,`flex min-w-0 flex-1 flex-col`,t(`div`,`flex-none px-2 pb-0.5 text-[10.5px] font-semibold uppercase tracking-wide text-neutral-400`,e),t(`div`,`min-h-0 flex-1 overflow-y-auto`,...n.map(e=>v(e,r,i)))),a=d===`compare`?t(`div`,`flex h-72 min-h-0 divide-x divide-neutral-200 pb-1`,r(`base`,p(!1),c,e=>{c=e,h(),i(c,l)}),r(`to`,p(!0),l,e=>{l=e,h(),i(c,l)})):t(`div`,`flex h-72 min-h-0 flex-col overflow-y-auto pb-1`,...n.commits.map(e=>v({value:e.hash,...e},u(),e=>{c=`${e}~1`,l=e,o(),i(c,l)}))),f=t(`div`,`flex-none border-t border-neutral-200`,v({value:s,label:`↺ ${s} ↔ Working tree`,subject:`back to the default: the main line's tip vs the files on disk`},c===s&&l===``?s:``,()=>{c=s,l=``,h(),i(c,l)}));m.replaceChildren(t(`div`,`flex flex-none items-center gap-1 border-b border-neutral-200 px-2 py-1.5`,e(`Compare two points`,`compare`),e(`One commit`,`commit`)),a,...d===`compare`?[f]:[])}h(),a(e,m)}var te=/\.(png|jpe?g|gif|webp|avif|bmp|svg)$/i,y={A:`text-emerald-600`,M:`text-amber-600`,D:`text-red-600`},ne=30,b=`pier.filesPrefs`,x=50,S=()=>{try{let e=JSON.parse(localStorage.getItem(b)??`{}`);return e&&typeof e==`object`?e:{}}catch{return{}}},re=e=>{let t=e?S()[e]:void 0;return typeof t?.cwd==`string`&&typeof t.base==`string`&&typeof t.head==`string`?{cwd:t.cwd,base:t.base,head:t.head}:null},ie=(e,t)=>{let n=S();delete n[e],n[e]=t;let r=Object.keys(n);for(let e of r.slice(0,Math.max(0,r.length-x)))delete n[e];try{localStorage.setItem(b,JSON.stringify(n))}catch(e){console.warn(`Files: could not remember this folder`,e)}},C=(e,n=`indigo`)=>{let r=t(`button`,`min-w-0 cursor-pointer truncate rounded-md px-2 py-0.5 text-left font-mono text-[11.5px] ${n===`indigo`?`bg-indigo-50 font-medium text-indigo-700 hover:bg-indigo-100`:`bg-neutral-100 text-neutral-600 hover:bg-neutral-200`}`,e);return r.type=`button`,r};function w(n,a,o,p,h){let g=``,v=``,b={branch:null,refs:[],commits:[],worktrees:[]},x=`HEAD`,S=``,w=new Map,T=!0,E=!1,D=[],O=new Set,k=null,A=null,ae=t(`header`,`flex h-10 flex-none items-center gap-2 border-b border-neutral-200 bg-white px-3`),j=t(`div`,`flex-none border-b border-neutral-200`),M=t(`div`,`min-h-0 flex-1 overflow-y-auto py-1`),oe=t(`aside`,`flex w-64 flex-none flex-col border-r border-neutral-200 text-[12.5px] max-md:h-2/5 max-md:w-full max-md:border-b max-md:border-r-0`,j,M),N=t(`div`,`h-full min-w-0 overflow-auto`),P=t(`div`,`pointer-events-none absolute inset-y-0 right-0 z-20 hidden w-2`),se=t(`section`,`relative min-h-0 min-w-0 flex-1`,N,P);n.append(ae,t(`div`,`flex min-h-0 flex-1 max-md:flex-col`,oe,se));let F=(e,t)=>`/api/explorer/${e}?${new URLSearchParams({root:g,...t})}`,I=(e,t)=>`/api/fs/${e}?${new URLSearchParams({root:g,...t})}`,L=(e,n=`text-neutral-400`)=>t(`p`,`px-4 py-3 text-[12.5px] ${n}`,e),R=e=>{let t=0;for(let n of w.keys())n.startsWith(`${e}/`)&&t++;return t};function z(e,t){let n=new Set(t.map(e=>e.name)),r=e?`${e}/`:``;for(let[e,i]of w){if(i.status!==`D`||!e.startsWith(r))continue;let a=e.slice(r.length).split(`/`)[0];n.has(a)||(n.add(a),t.push({name:a,dir:e.slice(r.length).includes(`/`)}))}let i=t;return T&&b.branch&&(i=t.filter(e=>e.dir?R(`${r}${e.name}`)>0:w.has(`${r}${e.name}`))),i.sort((e,t)=>Number(t.dir)-Number(e.dir)||e.name.localeCompare(t.name)),i.map(e=>e.dir?ce(`${r}${e.name}`,e.name):le(`${r}${e.name}`,e.name))}async function B(t,n){try{let{entries:r}=await e(I(`ls`,{path:n}),`could not list this folder`);t.replaceChildren(...z(n,r)),t.childElementCount||t.append(L(T?`No changes.`:`Empty.`))}catch(e){let r=z(n,[]);r.length?t.replaceChildren(...r):t.replaceChildren(L(String(e),`text-red-600`))}}function ce(e,n){let r=R(e),a=[t(`span`,`truncate`,n)];r&&a.push(t(`span`,`ml-auto flex-none rounded bg-amber-100/80 px-1 font-mono text-[10px] text-amber-700`,String(r)));let{el:o,summary:s}=i(``,a);s.className+=` px-2 py-0.5 hover:bg-neutral-100`;let c=t(`div`,`pl-3`);o.append(c);let l=!1,u=()=>{l||(l=!0,c.append(L(`…`)),B(c,e))};return o.ontoggle=()=>{O[o.open?`add`:`delete`](e),o.open&&u()},(T&&b.branch&&!E||O.has(e))&&(o.open=!0,u()),o}function V(e){A?.classList.remove(`bg-indigo-50`,`text-indigo-700`),A=e,e.classList.add(`bg-indigo-50`,`text-indigo-700`)}function le(e,n){let r=w.get(e)?.status,i=t(`button`,`flex w-full cursor-pointer items-center gap-1.5 px-2 py-0.5 pl-6 text-left hover:bg-neutral-100`,t(`span`,`truncate`,n));return r&&i.append(t(`span`,`ml-auto flex-none font-mono text-[10.5px] font-semibold ${y[r]??`text-neutral-500`}`,r)),i.title=e,e===k&&V(i),i.onclick=()=>{V(i),k=e,Y(e)},i}let H=()=>void B(M,``),U=(e,n)=>[...e?[t(`span`,`flex-none font-mono text-[10.5px] font-semibold text-emerald-600`,`+${e}`)]:[],...n?[t(`span`,`flex-none font-mono text-[10.5px] font-semibold text-red-600`,`−${n}`)]:[]];function W(e,n,r){let i=w.get(e),a=t(`div`,`sticky top-0 z-10 flex items-center gap-2 border-b border-neutral-200 bg-white px-4 py-1.5 font-mono text-[11.5px] text-neutral-500`,t(`span`,`truncate`,e));i&&(a.append(t(`span`,`flex-none font-semibold ${y[i.status]??``}`,i.status)),a.append(...U(i.add,i.del)));let o=t(`div`,`ml-auto flex flex-none items-center gap-2`);if(r&&o.append(r),n){let t=document.createElement(`a`);t.className=`flex-none text-neutral-400 hover:text-indigo-700 hover:underline`,t.href=I(`file`,{path:e}),t.download=s(e),t.textContent=`Download`,o.append(t)}return a.append(o),a}function ue(e){let t=0;for(;t<e.length;){if(e[t].tone!==`del`){t++;continue}let n=t;for(;t<e.length&&e[t].tone===`del`;)t++;let r=t;for(;t<e.length&&e[t].tone===`add`;)t++;let i=Math.min(r-n,t-r);for(let t=0;t<i;t++){let i=e[n+t],a=e[r+t],o=Math.min(i.text.length,a.text.length),s=0;for(;s<o&&i.text[s]===a.text[s];)s++;let c=0;for(;c<o-s&&i.text[i.text.length-1-c]===a.text[a.text.length-1-c];)c++;s+c!==0&&(i.mark=[s,i.text.length-c],a.mark=[s,a.text.length-c])}}}let G=_e();function K(){P.classList.add(`hidden`),P.replaceChildren(),G.set([],[])}let q=0,J=e=>e===q;function Y(e){let t=++q;return w.has(e)?ve(e,t):de(e,t)}async function de(e,n){if(!J(n))return;K();let r=I(`file`,{path:e}),i=t(`div`,`min-w-0`);if(N.classList.remove(`flex`,`flex-col`),N.replaceChildren(W(e,!0),i),te.test(e)){let e=t(`img`,`max-w-full p-4`);e.src=r,e.onerror=()=>i.replaceChildren(L(`Could not load image.`,`text-red-600`)),i.append(e);return}if(e.toLowerCase().endsWith(`.pdf`)){let t=document.createElement(`iframe`);t.className=`h-full w-full`,t.src=r,N.replaceChildren(W(e,!0),t),N.classList.add(`flex`,`flex-col`);return}i.append(L(`…`));let a=await fetch(r);if(!J(n))return;if(!a.ok){let{error:e}=await a.json().catch(()=>({}));i.replaceChildren(L(e??`HTTP ${a.status}`,`text-red-600`));return}if(!(a.headers.get(`content-type`)??``).startsWith(`text/plain`)){i.replaceChildren(L(`Binary file — use Download.`));return}let o=(await a.text()).split(`
|
|
4
|
+
`);if(!J(n))return;o.at(-1)===``&&o.pop();let s=await c(e);J(n)&&i.replaceChildren(f(d(o),s))}function fe(e){let t=[],n=0,r=0,i=!1,a=e.split(`
|
|
5
|
+
`);a.at(-1)===``&&a.pop();for(let e of a){let a=/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(e);if(a){i&&t.push({nums:[``,``],text:`⋯`,tone:``}),[n,r]=[Number(a[1]),Number(a[2])],i=!0;continue}!i||e.startsWith(`\\`)||(e.startsWith(`+`)?t.push({nums:[``,r++],text:e.slice(1),tone:`add`}):e.startsWith(`-`)?t.push({nums:[n++,``],text:e.slice(1),tone:`del`}):t.push({nums:[n++,r++],text:e.slice(1),tone:``}))}return t}function pe(e){let t=[];for(let n=0;n<e.length;n++){let r=e[n].tone;if(!r)continue;let i=t.at(-1);i&&i.end===n-1?(i.end=n,(r===`add`&&i.tone===`del`||r===`del`&&i.tone===`add`)&&(i.tone=`mixed`)):t.push({start:n,end:n,tone:r})}return t}let me={add:`bg-emerald-400`,del:`bg-red-400`,mixed:`bg-amber-400`};function he(e,n,r){P.replaceChildren(...e.map(e=>{let i=t(`button`,`pointer-events-auto absolute w-full cursor-pointer ${me[e.tone]}`);return i.style.top=`${e.start/n*100}%`,i.style.height=`${Math.max(.4,(e.end-e.start+1)/n*100)}%`,i.title=`line ${ge(r,e.start)}`,i.onclick=()=>r[e.start].scrollIntoView({block:`center`}),i})),P.classList.remove(`hidden`)}let ge=(e,t)=>e[t]?.querySelector(`span`)?.textContent?.trim()||String(t+1);function _e(){let e=[],r=[],i=-1,a=t(`span`,`flex-none text-[10.5px] text-neutral-400`),o=t=>{e.length&&(i=(i+t+e.length)%e.length,r[e[i].start].scrollIntoView({block:`center`}),a.textContent=`${i+1}/${e.length}`)},s=()=>e.length>0&&!n.classList.contains(`hidden`),c=(e,n,r,i)=>{let a=t(`button`,`icon-btn flex-none`,e);return a.onclick=()=>o(n),u(a,i,r,()=>o(n),s),a};return{el:t(`div`,`flex flex-none items-center gap-1`,a,c(`↑`,-1,`Previous change`,[`p`,`k`]),c(`↓`,1,`Next change`,[`n`,`j`])),set(t,n){e=t,r=n,i=-1,a.textContent=`${t.length} change${t.length===1?``:`s`}`}}}async function ve(t,n){K(),N.classList.remove(`flex`,`flex-col`);let r=w.get(t)?.status!==`D`;N.replaceChildren(W(t,r),L(`…`));try{let{diff:i}=await e(F(`diff`,{base:x,head:S,file:t,context:`99999`}),`could not read this diff`);if(!J(n))return;if(!i||/^Binary files /m.test(i))return de(t,n);let a=fe(i);ue(a);let o=await c(t);if(!J(n))return;let s=f(a,o),l=[...s.children],u=pe(a);G.set(u,l),N.replaceChildren(W(t,r,G.el),s),he(u,a.length,l)}catch(e){J(n)&&N.replaceChildren(W(t,!1),L(String(e),`text-red-600`))}}let X=()=>S&&x===`${S}~1`?S:``,ye=()=>b.commits.find(e=>e.hash===X())?.subject??``,be=()=>X()?`${X()} — ${ye()||`commit`}`:`${x} ↔ ${S||`working tree`}`,xe=(e,n,r,i)=>{let a=t(`button`,`flex h-6 w-6 flex-none cursor-pointer items-center justify-center rounded-md ${r?`bg-indigo-50 text-indigo-700`:`text-neutral-400 hover:bg-neutral-100 hover:text-neutral-700`}`);return a.innerHTML=e,a.title=n,a.onclick=i,a};function Z(){let e=xe(`<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="h-3.5 w-3.5"><path d="M2.5 3h11l-4.25 5v4.5l-2.5 1.25V8L2.5 3z" stroke-linejoin="round"/></svg>`,`Show only files that differ`,T,()=>{T=!T,Z(),H()}),n=xe(`<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="h-3.5 w-3.5"><path d="m4.5 2.5 3.5 3 3.5-3M4.5 13.5l3.5-3 3.5 3" stroke-linecap="round" stroke-linejoin="round"/></svg>`,E?`Restore folders`:`Collapse all folders`,E,()=>{if(E=!E,E)D=[...O],O.clear();else for(let e of D)O.add(e);Z(),H()});if(!b.branch){j.replaceChildren(t(`div`,`flex items-center justify-end gap-1 p-1.5`,n));return}let r=C(be());if(X()){let e=b.commits.find(e=>e.hash===X());_(r,()=>e?m(e):``)}else r.title=`Choose what to diff`;r.onclick=async()=>{await Te(),ee(r,b,{base:x,head:S},(e,t)=>{x=e,S=t,Q()},Ee())};let[i,a]=[0,0];for(let e of w.values())i+=e.add,a+=e.del;j.replaceChildren(t(`div`,`flex flex-col gap-1 p-2`,r,t(`div`,`flex items-center gap-1.5 pl-1`,t(`span`,`text-[11px] text-neutral-400`,`${w.size} changed file${w.size===1?``:`s`}`),...U(i,a),t(`span`,`ml-auto`),e,n)))}let Se=()=>{v&&g&&ie(v,{cwd:g,base:x,head:S})},Ce=e=>{let t=e.replace(/~\d+$/,``);return!t||t===`HEAD`||b.refs.some(e=>e.name===t)||b.commits.some(e=>e.hash===t)};async function we(){if(w=new Map,!b.branch)return;let{files:t}=await e(F(`diff`,{base:x,head:S}),`could not read what changed`);for(let e of t)w.set(e.path,{status:e.status,add:e.add,del:e.del});if(w.size<=ne)for(let e of w.keys()){let t=e.split(`/`);for(let e=1;e<t.length;e++)O.add(t.slice(0,e).join(`/`))}}async function Q(){Se();try{await we()}catch(e){Z(),M.replaceChildren(L(String(e),`text-red-600`));return}Z(),H(),k&&Y(k)}function $(){let e=C(g||`Choose a folder…`,`neutral`);e.className+=` max-w-72`,e.title=`Switch folder`,e.onclick=()=>l(e,b.worktrees.map(e=>({path:e.path,...e.branch?{hint:e.branch}:{}})),g||void 0,o);let n=C(`Terminal`,`neutral`);n.title=`Open a shell in this folder`,n.onclick=()=>p(g);let r=t(`button`,`icon-btn`,`✕`);r.type=`button`,r.title=`Close Files`,r.setAttribute(`aria-label`,`Close Files`),r.onclick=h;let i=g?[n,r]:[r];i[0].classList.add(`ml-auto`),ae.replaceChildren(e,t(`span`,`truncate font-mono text-[11.5px] text-neutral-400`,b.branch?`⎇ ${b.branch}`:`no git`),...i)}async function Te(){if(g)try{b=await e(F(`git`,{}),`could not read this repository`),$()}catch(e){N.replaceChildren(L(String(e),`text-red-600`))}}let Ee=()=>b.refs.some(e=>e.name===`main`)?`main`:b.refs.some(e=>e.name===`master`)?`master`:`HEAD`;async function De(){if($(),k=null,A=null,O.clear(),K(),N.classList.remove(`flex`,`flex-col`),N.replaceChildren(L(`Select a file.`)),!g){j.replaceChildren(),M.replaceChildren(),N.replaceChildren(L(`No folder yet — pick one from the chip above.`));return}try{b=await e(F(`git`,{}),`could not read this repository`)}catch(e){b={branch:null,refs:[],commits:[],worktrees:[]},N.replaceChildren(L(String(e),`text-red-600`))}let t=re(v),n=t&&t.cwd===g&&Ce(t.base)&&Ce(t.head)?t:null;x=n?.base??Ee(),S=n?.head??``,$(),await Q()}return r(n,e=>{let t=a(),n=t?.id??``,r=e?.startsWith(`/`)?e:(n===v?g:``)||re(n)?.cwd||t?.cwd||``;if(r===g&&n===v&&M.childElementCount){$(),Te().then(Q);return}v=n,g=r,De()},()=>{K(),N.classList.remove(`flex`,`flex-col`),N.replaceChildren(L(`Select a file.`))})}export{w as createExplorerView};
|
|
Binary file
|
|
Binary file
|