@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.
Files changed (79) hide show
  1. package/dist/agent/events.js +16 -4
  2. package/dist/agent/listing.js +12 -13
  3. package/dist/agent/pi.js +28 -2
  4. package/dist/boards/boards.js +7 -6
  5. package/dist/channels/attach.js +18 -4
  6. package/dist/channels/config.js +7 -2
  7. package/dist/channels/dedup.js +9 -1
  8. package/dist/channels/lark.js +2 -6
  9. package/dist/channels/receipts.js +21 -6
  10. package/dist/channels/slack-tool.js +86 -25
  11. package/dist/channels/slack.js +2 -10
  12. package/dist/channels/telegram.js +1 -0
  13. package/dist/core/hub.js +14 -4
  14. package/dist/core/inbox.js +1 -1
  15. package/dist/core/reply.js +64 -0
  16. package/dist/db.js +105 -0
  17. package/dist/main.js +1 -0
  18. package/dist/secrets.js +18 -6
  19. package/dist/settings.js +2 -11
  20. package/dist/tasks/agent.js +14 -1
  21. package/dist/tasks/definitions.js +3 -1
  22. package/dist/tasks/messages.js +1 -2
  23. package/dist/tasks/service.js +5 -0
  24. package/dist/tasks/store.js +44 -12
  25. package/dist/tools.js +42 -18
  26. package/dist/web/auth.js +212 -68
  27. package/dist/web/config.js +61 -0
  28. package/dist/web/explorer.js +20 -70
  29. package/dist/web/fs.js +159 -0
  30. package/dist/web/instance.js +11 -0
  31. package/dist/web/public/assets/activity-BsoWK6vt.js +5 -0
  32. package/dist/web/public/assets/activity-BsoWK6vt.js.br +0 -0
  33. package/dist/web/public/assets/activity-BsoWK6vt.js.gz +0 -0
  34. package/dist/web/public/assets/boards-DEMQ51m1.js +1 -0
  35. package/dist/web/public/assets/boards-DEMQ51m1.js.br +0 -0
  36. package/dist/web/public/assets/boards-DEMQ51m1.js.gz +0 -0
  37. package/dist/web/public/assets/code-DjWvqL94.js +1 -0
  38. package/dist/web/public/assets/explorer-BTGQ6Syc.js +5 -0
  39. package/dist/web/public/assets/explorer-BTGQ6Syc.js.br +0 -0
  40. package/dist/web/public/assets/explorer-BTGQ6Syc.js.gz +0 -0
  41. package/dist/web/public/assets/{ghostty-web-C4ivXTBE.js → ghostty-web-D8CLFi_l.js} +1 -1
  42. package/dist/web/public/assets/ghostty-web-D8CLFi_l.js.br +0 -0
  43. package/dist/web/public/assets/ghostty-web-D8CLFi_l.js.gz +0 -0
  44. package/dist/web/public/assets/hljs-tWqyD59G.js +3 -0
  45. package/dist/web/public/assets/hljs-tWqyD59G.js.br +0 -0
  46. package/dist/web/public/assets/hljs-tWqyD59G.js.gz +0 -0
  47. package/dist/web/public/assets/index-BFzmvpGX.js +78 -0
  48. package/dist/web/public/assets/index-BFzmvpGX.js.br +0 -0
  49. package/dist/web/public/assets/index-BFzmvpGX.js.gz +0 -0
  50. package/dist/web/public/assets/{index-2E9_cwpg.css → index-yNocr797.css} +1 -1
  51. package/dist/web/public/assets/index-yNocr797.css.br +0 -0
  52. package/dist/web/public/assets/index-yNocr797.css.gz +0 -0
  53. package/dist/web/public/assets/settings-DSTKXN2o.js +6 -0
  54. package/dist/web/public/assets/settings-DSTKXN2o.js.br +0 -0
  55. package/dist/web/public/assets/settings-DSTKXN2o.js.gz +0 -0
  56. package/dist/web/public/assets/tasks-Dy1SCAE_.js +4 -0
  57. package/dist/web/public/assets/tasks-Dy1SCAE_.js.br +0 -0
  58. package/dist/web/public/assets/tasks-Dy1SCAE_.js.gz +0 -0
  59. package/dist/web/public/assets/terminal-zLyOvGto.js +2 -0
  60. package/dist/web/public/assets/terminal-zLyOvGto.js.br +0 -0
  61. package/dist/web/public/assets/terminal-zLyOvGto.js.gz +0 -0
  62. package/dist/web/public/icon.svg.br +0 -0
  63. package/dist/web/public/icon.svg.gz +0 -0
  64. package/dist/web/public/index.html +2 -2
  65. package/dist/web/public/index.html.br +0 -0
  66. package/dist/web/public/index.html.gz +0 -0
  67. package/dist/web/public/manifest.webmanifest.br +0 -0
  68. package/dist/web/public/manifest.webmanifest.gz +0 -0
  69. package/dist/web/public/sw.js.br +0 -0
  70. package/dist/web/public/sw.js.gz +0 -0
  71. package/dist/web/push.js +30 -7
  72. package/dist/web/route.js +17 -0
  73. package/dist/web/server.js +104 -23
  74. package/dist/web/terminal.js +21 -16
  75. package/docs/deploy.md +12 -3
  76. package/package.json +1 -1
  77. package/skills/pier-slack/SKILL.md +4 -16
  78. package/dist/web/files.js +0 -183
  79. 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
+ }
@@ -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};
@@ -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};
@@ -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};