@timqi/pier 0.0.25 → 0.0.26
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/pi.js +16 -16
- package/dist/channels/attach.js +6 -3
- package/dist/channels/lark-outbound.js +6 -1
- package/dist/channels/lark.js +16 -4
- package/dist/channels/slack-api.js +8 -0
- package/dist/channels/slack-outbound.js +14 -5
- package/dist/channels/slack-tool.js +83 -4
- package/dist/channels/slack.js +156 -8
- package/dist/channels/telegram.js +15 -4
- package/dist/core/inbound-file.js +69 -3
- package/dist/core/reply.js +17 -5
- package/dist/core/router.js +34 -1
- package/dist/web/fs.js +6 -22
- package/dist/web/public/assets/{activity-xRauUtIM.js → activity-BJ7YoxQo.js} +1 -1
- package/dist/web/public/assets/activity-BJ7YoxQo.js.br +0 -0
- package/dist/web/public/assets/activity-BJ7YoxQo.js.gz +0 -0
- package/dist/web/public/assets/{boards-CxXk1JfX.js → boards-7GDMK7s8.js} +1 -1
- package/dist/web/public/assets/boards-7GDMK7s8.js.br +0 -0
- package/dist/web/public/assets/boards-7GDMK7s8.js.gz +0 -0
- package/dist/web/public/assets/{code-pUOkepAa.js → code-8o-S-MIZ.js} +1 -1
- package/dist/web/public/assets/{explorer-C1KiLceA.js → explorer-DBusW0TA.js} +1 -1
- package/dist/web/public/assets/explorer-DBusW0TA.js.br +0 -0
- package/dist/web/public/assets/explorer-DBusW0TA.js.gz +0 -0
- package/dist/web/public/assets/{index-D9euDszY.js → index-CquEUmXo.js} +14 -13
- package/dist/web/public/assets/index-CquEUmXo.js.br +0 -0
- package/dist/web/public/assets/index-CquEUmXo.js.gz +0 -0
- package/dist/web/public/assets/{runs-BuE-mags.js → runs-BjcdVVmW.js} +1 -1
- package/dist/web/public/assets/runs-BjcdVVmW.js.br +0 -0
- package/dist/web/public/assets/runs-BjcdVVmW.js.gz +0 -0
- package/dist/web/public/assets/{settings-5KvCAMBF.js → settings-DH3Nlk9f.js} +1 -1
- package/dist/web/public/assets/settings-DH3Nlk9f.js.br +0 -0
- package/dist/web/public/assets/settings-DH3Nlk9f.js.gz +0 -0
- package/dist/web/public/assets/{task-runs-DIlw1pXt.js → task-runs-DsOxAyRj.js} +1 -1
- package/dist/web/public/assets/task-runs-DsOxAyRj.js.br +0 -0
- package/dist/web/public/assets/task-runs-DsOxAyRj.js.gz +0 -0
- package/dist/web/public/assets/{tasks-CcAxbMR8.js → tasks-CYfdrdF2.js} +1 -1
- package/dist/web/public/assets/tasks-CYfdrdF2.js.br +0 -0
- package/dist/web/public/assets/tasks-CYfdrdF2.js.gz +0 -0
- package/dist/web/public/index.html +1 -1
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/server.js +11 -11
- package/package.json +1 -1
- package/skills/pier-slack/SKILL.md +61 -133
- package/dist/web/public/assets/activity-xRauUtIM.js.br +0 -0
- package/dist/web/public/assets/activity-xRauUtIM.js.gz +0 -0
- package/dist/web/public/assets/boards-CxXk1JfX.js.br +0 -0
- package/dist/web/public/assets/boards-CxXk1JfX.js.gz +0 -0
- package/dist/web/public/assets/explorer-C1KiLceA.js.br +0 -0
- package/dist/web/public/assets/explorer-C1KiLceA.js.gz +0 -0
- package/dist/web/public/assets/index-D9euDszY.js.br +0 -0
- package/dist/web/public/assets/index-D9euDszY.js.gz +0 -0
- package/dist/web/public/assets/runs-BuE-mags.js.br +0 -0
- package/dist/web/public/assets/runs-BuE-mags.js.gz +0 -0
- package/dist/web/public/assets/settings-5KvCAMBF.js.br +0 -0
- package/dist/web/public/assets/settings-5KvCAMBF.js.gz +0 -0
- package/dist/web/public/assets/task-runs-DIlw1pXt.js.br +0 -0
- package/dist/web/public/assets/task-runs-DIlw1pXt.js.gz +0 -0
- package/dist/web/public/assets/tasks-CcAxbMR8.js.br +0 -0
- package/dist/web/public/assets/tasks-CcAxbMR8.js.gz +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
// The
|
|
1
|
+
// The file-link convention, browser-safe: what a user's attachment may be
|
|
2
2
|
// called, the marker line that tells the agent about it, the parser that
|
|
3
|
-
// splits it back out of a message,
|
|
3
|
+
// splits it back out of a message, the size cap both ends enforce, and where a
|
|
4
|
+
// `file://` link does not count as one at all (inside code).
|
|
4
5
|
// Producers are node code (channels/, web/server.ts) but the web composer
|
|
5
6
|
// builds markers and the web chat parses them in the browser, so the grammar
|
|
6
7
|
// lives in a module with no node imports that either side can load. The
|
|
@@ -46,13 +47,78 @@ export const fileMarker = (path) => `[${path.split("/").pop() ?? "file"}](file:/
|
|
|
46
47
|
* could not upload (channels/attach.ts) are the same fact to the reader.
|
|
47
48
|
*/
|
|
48
49
|
export const lostMarker = (name, reason) => `[attachment lost: ${name} — ${reason}]`;
|
|
50
|
+
/** A fenced block's opening or closing line. */
|
|
51
|
+
const FENCE_RE = /^ {0,3}(`{3,}|~{3,})/;
|
|
52
|
+
/**
|
|
53
|
+
* The character ranges of `text` that are code: fenced blocks (fence lines
|
|
54
|
+
* included) and inline spans. Inline spans are paired within a line — one that
|
|
55
|
+
* wraps across a newline is legal markdown and not how anyone writes an
|
|
56
|
+
* example link, and per-line pairing keeps this a scan instead of a parser.
|
|
57
|
+
*/
|
|
58
|
+
function codeRanges(text) {
|
|
59
|
+
const ranges = [];
|
|
60
|
+
let fence;
|
|
61
|
+
let at = 0;
|
|
62
|
+
for (const line of text.split("\n")) {
|
|
63
|
+
const run = FENCE_RE.exec(line)?.[1];
|
|
64
|
+
if (fence !== undefined) {
|
|
65
|
+
ranges.push([at, at + line.length]);
|
|
66
|
+
if (run && run[0] === fence[0] && run.length >= fence.length)
|
|
67
|
+
fence = undefined;
|
|
68
|
+
}
|
|
69
|
+
else if (run) {
|
|
70
|
+
fence = run;
|
|
71
|
+
ranges.push([at, at + line.length]);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// A span closes on the next backtick run of the same length; runs in
|
|
75
|
+
// between are content, so an unpaired opener leaves the rest as prose.
|
|
76
|
+
const runs = [...line.matchAll(/`+/g)];
|
|
77
|
+
for (let i = 0; i < runs.length; i++) {
|
|
78
|
+
const open = runs[i];
|
|
79
|
+
let close = i + 1;
|
|
80
|
+
while (close < runs.length && runs[close][0].length !== open[0].length)
|
|
81
|
+
close++;
|
|
82
|
+
if (close === runs.length)
|
|
83
|
+
break;
|
|
84
|
+
ranges.push([at + open.index, at + runs[close].index + runs[close][0].length]);
|
|
85
|
+
i = close;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
at += line.length + 1; // the newline split() removed
|
|
89
|
+
}
|
|
90
|
+
return ranges;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* `text.replace(pattern, …)` for every match that is not inside code. An agent
|
|
94
|
+
* that documents this convention writes an example link in backticks, and a
|
|
95
|
+
* scanner that cannot tell an example from a link turned that example into a
|
|
96
|
+
* real attachment — a dead card in the chat, a lost-attachment line in Slack.
|
|
97
|
+
* Matches inside code are left byte-identical: the reader asked to see them.
|
|
98
|
+
*/
|
|
99
|
+
export function replaceOutsideCode(text, pattern, replace) {
|
|
100
|
+
const skip = codeRanges(text);
|
|
101
|
+
let out = "";
|
|
102
|
+
let last = 0;
|
|
103
|
+
for (const match of text.matchAll(pattern)) {
|
|
104
|
+
const start = match.index;
|
|
105
|
+
const end = start + match[0].length;
|
|
106
|
+
if (skip.some(([from, to]) => start < to && end > from))
|
|
107
|
+
continue;
|
|
108
|
+
out += text.slice(last, start) + replace(match);
|
|
109
|
+
last = end;
|
|
110
|
+
}
|
|
111
|
+
return out + text.slice(last);
|
|
112
|
+
}
|
|
49
113
|
// A whole line that is one `[name](file:///…)` link — what fileMarker emits.
|
|
50
114
|
const MARKER_RE = /^\[[^\]\n]*\]\(\s*<?file:\/\/(\/[^)>\s]*)>?\s*\)$/;
|
|
51
115
|
/**
|
|
52
116
|
* Split a user message into its typed text and the attached files' paths.
|
|
53
117
|
* Only the contiguous *trailing* block of marker lines is an attachment —
|
|
54
118
|
* that is where every producer puts them — so a `file://` link the user
|
|
55
|
-
* wrote mid-message stays message text.
|
|
119
|
+
* wrote mid-message stays message text. No code scan needed for the same
|
|
120
|
+
* reason: an example in a fence sits under its closing line, which is not a
|
|
121
|
+
* marker line, so the walk stops there before ever reaching it.
|
|
56
122
|
*/
|
|
57
123
|
export function splitInboundFiles(raw) {
|
|
58
124
|
const lines = raw.split("\n");
|
package/dist/core/reply.js
CHANGED
|
@@ -24,11 +24,11 @@ conventions:
|
|
|
24
24
|
for anything destructive.
|
|
25
25
|
- **Attachments** — link a file you produced by absolute \`file://\` URL:
|
|
26
26
|
\`[report.md](file:///abs/path/report.md)\`. Images render as thumbnails,
|
|
27
|
-
other files as a download card
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
other files as a download card, wherever on disk you wrote it. The same
|
|
28
|
+
convention runs inbound: a user message ending in \`[name](file:///…)\`
|
|
29
|
+
lines is carrying files the sender attached, already saved to disk — read
|
|
30
|
+
one only when it matters to the task; every read puts its content in your
|
|
31
|
+
context for good.
|
|
32
32
|
- **Staying silent** — \`<silent>why</silent>\` is stripped, and if nothing else
|
|
33
33
|
remains no message is sent. In a group chat you are handed every message,
|
|
34
34
|
including humans talking to each other: stay silent rather than acknowledge
|
|
@@ -94,6 +94,18 @@ export function originLabel(origin) {
|
|
|
94
94
|
};
|
|
95
95
|
return `from a subagent \u00b7 ${kinds[origin.messageKind] ?? origin.messageKind}`;
|
|
96
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Is a turn coming, once this note is posted?
|
|
99
|
+
*
|
|
100
|
+
* A system input is context for the turn it triggers, so on IM the note is the
|
|
101
|
+
* only message that turn has to wear the 👀 — nobody typed one. An error note
|
|
102
|
+
* is the opposite: it reports a turn that already ended or never began, and a
|
|
103
|
+
* receipt marked on it would hang until the stale sweep.
|
|
104
|
+
*
|
|
105
|
+
* Beside `originLabel` for the same reason it is: three adapters ask this, and
|
|
106
|
+
* three copies of the answer are three chances to drift.
|
|
107
|
+
*/
|
|
108
|
+
export const awaitsTurn = (origin) => origin.kind !== "error";
|
|
97
109
|
/**
|
|
98
110
|
* Quote and bracket characters that may be lifted out of a `**strong**` run.
|
|
99
111
|
* Moving them changes nothing a reader can see — the punctuation is simply no
|
package/dist/core/router.js
CHANGED
|
@@ -15,6 +15,38 @@ const IDLE_TTL_MS = 30 * 60_000;
|
|
|
15
15
|
const SWEEP_MS = 5 * 60_000;
|
|
16
16
|
/** An error goes into a chat window, so it is trimmed to something readable. */
|
|
17
17
|
const truncate = (message) => message.length > 600 ? `${message.slice(0, 600)}…` : message;
|
|
18
|
+
/** What a chat window gets of a system input, and how much it may stand in
|
|
19
|
+
* for. A task callback carries up to 8000 characters of result text
|
|
20
|
+
* (tasks/callbacks.ts). */
|
|
21
|
+
const NOTE_CHARS = 200;
|
|
22
|
+
const NOTE_LINES = 4;
|
|
23
|
+
/**
|
|
24
|
+
* The head of a system input plus a count of what was left out.
|
|
25
|
+
*
|
|
26
|
+
* A note is *context* for the turn it precedes, not the message: pasted whole,
|
|
27
|
+
* a run result buries the conversation it was meant to explain — and on IM
|
|
28
|
+
* that is the one surface with no way to collapse it. The hub, the web
|
|
29
|
+
* timeline and the Pi transcript keep every character.
|
|
30
|
+
*
|
|
31
|
+
* Cut on a line boundary and then on a word, so the head reads as text rather
|
|
32
|
+
* than as a string that ran out.
|
|
33
|
+
*/
|
|
34
|
+
function digest(text) {
|
|
35
|
+
const body = text.trimEnd();
|
|
36
|
+
let head = body.split("\n").slice(0, NOTE_LINES).join("\n");
|
|
37
|
+
if (head.length > NOTE_CHARS) {
|
|
38
|
+
const capped = head.slice(0, NOTE_CHARS);
|
|
39
|
+
// Only a boundary in the second half is worth taking: cutting further back
|
|
40
|
+
// than that loses more than the ragged edge was costing.
|
|
41
|
+
const boundary = Math.max(capped.lastIndexOf("\n"), capped.lastIndexOf(" "));
|
|
42
|
+
head = capped.slice(0, boundary > NOTE_CHARS / 2 ? boundary : NOTE_CHARS);
|
|
43
|
+
}
|
|
44
|
+
const rest = body.slice(head.length).trim();
|
|
45
|
+
if (!rest)
|
|
46
|
+
return body;
|
|
47
|
+
const dropped = rest.split("\n").length;
|
|
48
|
+
return `${head.trimEnd()}\n… +${String(dropped)} more line${dropped === 1 ? "" : "s"}`;
|
|
49
|
+
}
|
|
18
50
|
function keyOf(key) {
|
|
19
51
|
return `${key.channelId}:${key.conversationId}`;
|
|
20
52
|
}
|
|
@@ -225,7 +257,8 @@ export class Router {
|
|
|
225
257
|
// out before the turn it triggers, so the answer has a visible cause.
|
|
226
258
|
if (payload.type === "system-input") {
|
|
227
259
|
const channel = this.channels.get(key.channelId);
|
|
228
|
-
|
|
260
|
+
// A digest, not the input: the hub emit above is what carries it whole.
|
|
261
|
+
channel?.notify(key.conversationId, { text: digest(payload.text), origin: payload.origin })
|
|
229
262
|
.catch((err) => {
|
|
230
263
|
log.error(`notify ${key.channelId} failed`, err);
|
|
231
264
|
this.hub.emit(session.id, {
|
package/dist/web/fs.js
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
// routes and the session attachments server.ts serves all ask the same
|
|
3
3
|
// question, and asked it several different ways until this file existed.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// The boundary is the Console password, not a path: an owner past it picks a
|
|
6
|
+
// session's cwd from anywhere under `$HOME`, so anything this process can read
|
|
7
|
+
// is already reachable — which is why the session attachments route (server.ts)
|
|
8
|
+
// takes any absolute file. What `root` confines is a *listing*, so a browse
|
|
9
|
+
// cannot widen itself past the tree the view asked for: `path` resolves under
|
|
8
10
|
// the `root` it was asked with, realpath on both ends, so neither `..` nor a
|
|
9
|
-
// symlink steps outside
|
|
10
|
-
// writes, and only a name.
|
|
11
|
+
// symlink steps outside. Only mkdir writes, and only a name.
|
|
11
12
|
import { mkdir, open, readdir, realpath, stat } from "node:fs/promises";
|
|
12
13
|
import { homedir } from "node:os";
|
|
13
14
|
import { basename, dirname, extname, isAbsolute, resolve, sep } from "node:path";
|
|
@@ -46,23 +47,6 @@ export async function scoped(root, path = "") {
|
|
|
46
47
|
throw new Error("path escapes root");
|
|
47
48
|
return target;
|
|
48
49
|
}
|
|
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
50
|
/** How bytes leave: the type they may be served as, and whether a browser may
|
|
67
51
|
* render them in place. `download` forces the attachment disposition — the
|
|
68
52
|
* chat's Download button, on a file it would otherwise show inline. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{B as e,C as t,H as n,I as r,K as i,L as a,N as o,k as s,m as c,v as l,z as u}from"./index-
|
|
1
|
+
import{B as e,C as t,H as n,I as r,K as i,L as a,N as o,k as s,m as c,v as l,z as u}from"./index-CquEUmXo.js";var d=(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},f=e=>e===null?`-`:r(Date.now()-e),p={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`}}},m=()=>p[document.documentElement.dataset.theme===`dark`?`dark`:`light`];function h(r,p,h){let g=`sessions`,_=`active`,v={sessions:[],runs:[],messages:[]},y=``,b=e(async()=>{let e=_,t=await n(`/api/activity?scope=${e}`,`Failed to load activity`);if(!t.ok){y=``,r.replaceChildren(a(`p`,`p-4 text-[13px] text-red-600`,t.error));return}if(e!==_)return;let o=JSON.stringify(t.value);if(`${e}:${o}`===y)return;y=`${e}:${o}`;let s=t.value;v={...s,sessions:s.sessions.map(e=>({...e,title:i(e.title)}))},x()});function x(){let e=s(t([[`Sessions`,`sessions`],[`Relationships`,`dependencies`]],g,e=>{g=e,x()}),a(`span`,`ml-auto text-[11.5px] text-neutral-400`,`${v.sessions.length} sessions · ${v.runs.length} runs`),t([[`Current`,`active`],[`Last 24h`,`recent`]],_,e=>{_=e,x(),b()})),n=a(`div`,`min-h-0 flex-1 overflow-auto`);g===`sessions`?S(n):w(n),r.replaceChildren(e,n)}function S(e){let t=document.createElement(`table`);t.className=`w-full table-fixed text-left text-[12.5px]`,t.innerHTML=`<thead class="sticky top-0 bg-neutral-50 text-[10.5px] uppercase tracking-wide text-neutral-400 shadow-[inset_0_-1px_0_var(--color-neutral-200)]"><tr>
|
|
2
2
|
<th class="w-[50%] px-4 py-2 font-semibold md:w-[34%]">Session</th>
|
|
3
3
|
<th class="hidden w-[38%] px-2 py-2 font-semibold md:table-cell">Project</th>
|
|
4
4
|
<th class="px-2 py-2 font-semibold md:w-[14%]">State</th>
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{G as e,H as t,L as n,N as r,O as i,P as a,R as o,V as s,W as c,h as l}from"./index-
|
|
1
|
+
import{G as e,H as t,L as n,N as r,O as i,P as a,R as o,V as s,W as c,h as l}from"./index-CquEUmXo.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 _(t,n){let r=await e(`/api/boards/${t.slug}`,{public:n},`PATCH`);if(r.ok)return t.token=(await r.json().catch(()=>({}))).token??``,x();m=await s(r,`Could not change ${t.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`),r=document.createElement(`a`);r.className=`truncate font-medium text-neutral-800 hover:text-indigo-700`,r.href=u(e),r.target=`_blank`,r.rel=`noreferrer`,r.textContent=e.title;let s=n(`div`,`flex items-center gap-2`,r,n(`span`,`flex-none font-mono text-[11.5px] text-neutral-400`,e.slug)),c=i(``,``,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`,o(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 t(`/api/boards`,`Could not load boards`);if(!e.ok)return m=e.error,x();p=e.value,x(),m=``}let C=r(d,()=>void S());return window.addEventListener(`focus`,()=>{C.visible&&S()}),C}export{d as createBoardsView};
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{L as e,d as t}from"./index-
|
|
1
|
+
import{L as e,d as t}from"./index-CquEUmXo.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};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{F as e,L as t,M as n,N as r,R as i,U as a,a as o,i as s,n as c,s as l,u}from"./index-
|
|
1
|
+
import{F as e,L as t,M as n,N as r,R as i,U as a,a as o,i as s,n as c,s as l,u}from"./index-CquEUmXo.js";import{n as d,t as f}from"./code-8o-S-MIZ.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
2
|
|
|
3
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,n,r){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===n?`✓`:``),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?i(e.at):``}`)),_(a,()=>m(e)),a.onclick=()=>r(e.value),a}function ee(e,n,r,i,a){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)))),o=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,s(),i(c,l)}))),f=t(`div`,`flex-none border-t border-neutral-200`,v({value:a,label:`↺ ${a} ↔ Working tree`,subject:`back to the default: the main line's tip vs the files on disk`},c===a&&l===``?a:``,()=>{c=a,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`)),o,...d===`compare`?[f]:[])}h(),o(e,m)}var te=/\.(png|jpe?g|gif|webp|avif|bmp|svg)$/i,ne={A:`text-emerald-600`,M:`text-amber-600`,D:`text-red-600`},re=30,y=`pier.filesPrefs`,b=50,x=()=>{try{let e=JSON.parse(localStorage.getItem(y)??`{}`);return e&&typeof e==`object`?e:{}}catch{return{}}},S=e=>{let t=e?x()[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=x();delete n[e],n[e]=t;let r=Object.keys(n);for(let e of r.slice(0,Math.max(0,r.length-b)))delete n[e];try{localStorage.setItem(y,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(i,o,s,p){let h=``,g=``,v={branch:null,refs:[],commits:[],worktrees:[]},y=`HEAD`,b=``,x=new Map,w=!0,T=!1,ae=[],E=new Set,D=null,O=null,k=t(`header`,`flex h-10 flex-none items-center gap-2 border-b border-neutral-200 bg-white px-3`),A=t(`div`,`flex-none border-b border-neutral-200`),j=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`,A,j),M=t(`div`,`h-full min-w-0 overflow-auto`),N=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`,M,N);i.append(k,t(`div`,`flex min-h-0 flex-1 max-md:flex-col`,oe,se));let P=(e,t)=>`/api/explorer/${e}?${new URLSearchParams({root:h,...t})}`,F=(e,t)=>`/api/fs/${e}?${new URLSearchParams({root:h,...t})}`,I=(e,n=`text-neutral-400`)=>t(`p`,`px-4 py-3 text-[12.5px] ${n}`,e),L=e=>{let t=0;for(let n of x.keys())n.startsWith(`${e}/`)&&t++;return t};function R(e,t){let n=new Set(t.map(e=>e.name)),r=e?`${e}/`:``;for(let[e,i]of x){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 w&&v.branch&&(i=t.filter(e=>e.dir?L(`${r}${e.name}`)>0:x.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 z(e,t){try{let{entries:n}=await a(F(`ls`,{path:t}),`could not list this folder`);e.replaceChildren(...R(t,n)),e.childElementCount||e.append(I(w?`No changes.`:`Empty.`))}catch(n){let r=R(t,[]);r.length?e.replaceChildren(...r):e.replaceChildren(I(String(n),`text-red-600`))}}function ce(n,r){let i=L(n),a=[t(`span`,`truncate`,r)];i&&a.push(t(`span`,`ml-auto flex-none rounded bg-amber-100/80 px-1 font-mono text-[10px] text-amber-700`,String(i)));let{el:o,summary:s}=e(``,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(I(`…`)),z(c,n))};return o.ontoggle=()=>{E[o.open?`add`:`delete`](n),o.open&&u()},(w&&v.branch&&!T||E.has(n))&&(o.open=!0,u()),o}function B(e){O?.classList.remove(`bg-indigo-50`,`text-indigo-700`),O=e,e.classList.add(`bg-indigo-50`,`text-indigo-700`)}function le(e,n){let r=x.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 ${ne[r]??`text-neutral-500`}`,r)),i.title=e,e===D&&B(i),i.onclick=()=>{B(i),D=e,de(e)},i}let V=()=>void z(j,``),H=(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 U(e,r,i){let a=x.get(e),o=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));a&&(o.append(t(`span`,`flex-none font-semibold ${ne[a.status]??``}`,a.status)),o.append(...H(a.add,a.del)));let s=t(`div`,`ml-auto flex flex-none items-center gap-2`);if(i&&s.append(i),r){let t=document.createElement(`a`);t.className=`flex-none text-neutral-400 hover:text-indigo-700 hover:underline`,t.href=F(`file`,{path:e}),t.download=n(e),t.textContent=`Download`,s.append(t)}return o.append(s),o}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 W=_e();function G(){N.classList.add(`hidden`),N.replaceChildren(),W.set([],[])}let K=0,q=e=>e===K;function de(e){let t=++K;return x.has(e)?ve(e,t):J(e,t)}async function J(e,n){if(!q(n))return;G();let r=F(`file`,{path:e}),i=t(`div`,`min-w-0`);if(M.classList.remove(`flex`,`flex-col`),M.replaceChildren(U(e,!0),i),te.test(e)){let e=t(`img`,`max-w-full p-4`);e.src=r,e.onerror=()=>i.replaceChildren(I(`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,M.replaceChildren(U(e,!0),t),M.classList.add(`flex`,`flex-col`);return}i.append(I(`…`));let a=await fetch(r);if(!q(n))return;if(!a.ok){let{error:e}=await a.json().catch(()=>({}));i.replaceChildren(I(e??`HTTP ${a.status}`,`text-red-600`));return}if(!(a.headers.get(`content-type`)??``).startsWith(`text/plain`)){i.replaceChildren(I(`Binary file — use Download.`));return}let o=(await a.text()).split(`
|
|
4
4
|
`);if(!q(n))return;o.at(-1)===``&&o.pop();let s=await u(e);q(n)&&i.replaceChildren(f(d(o),s))}function fe(e){let t=[],n=0,r=0,i=!1,a=e.split(`
|
|
Binary file
|
|
Binary file
|