@timqi/pier 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +97 -0
- package/dist/agent/config.js +133 -0
- package/dist/agent/credentials.js +179 -0
- package/dist/agent/events.js +253 -0
- package/dist/agent/models.js +15 -0
- package/dist/agent/pi.js +296 -0
- package/dist/boards/boards.js +200 -0
- package/dist/boards/pier.css +445 -0
- package/dist/channels/chains.js +67 -0
- package/dist/channels/chunk.js +28 -0
- package/dist/channels/commands.js +28 -0
- package/dist/channels/config.js +172 -0
- package/dist/channels/control.js +71 -0
- package/dist/channels/conversations.js +65 -0
- package/dist/channels/gatekeeper.js +63 -0
- package/dist/channels/panel.js +233 -0
- package/dist/channels/receipts.js +104 -0
- package/dist/channels/routes.js +110 -0
- package/dist/channels/runtime.js +76 -0
- package/dist/channels/slack-api.js +296 -0
- package/dist/channels/slack-directory.js +77 -0
- package/dist/channels/slack-outbound.js +121 -0
- package/dist/channels/slack-panel.js +122 -0
- package/dist/channels/slack-render.js +214 -0
- package/dist/channels/slack-tool.js +334 -0
- package/dist/channels/slack.js +510 -0
- package/dist/channels/telegram-api.js +78 -0
- package/dist/channels/telegram-panel.js +113 -0
- package/dist/channels/telegram-render.js +96 -0
- package/dist/channels/telegram.js +473 -0
- package/dist/channels/types.js +27 -0
- package/dist/cli.js +101 -0
- package/dist/core/hub.js +53 -0
- package/dist/core/identity.js +66 -0
- package/dist/core/queue.js +11 -0
- package/dist/core/reply.js +202 -0
- package/dist/core/router.js +189 -0
- package/dist/core/types.js +7 -0
- package/dist/db.js +268 -0
- package/dist/log.js +55 -0
- package/dist/main.js +183 -0
- package/dist/paths.js +17 -0
- package/dist/secrets.js +191 -0
- package/dist/service.js +134 -0
- package/dist/settings.js +57 -0
- package/dist/tasks/agent.js +197 -0
- package/dist/tasks/callbacks.js +140 -0
- package/dist/tasks/command.js +74 -0
- package/dist/tasks/definitions.js +316 -0
- package/dist/tasks/execution.js +141 -0
- package/dist/tasks/groups.js +187 -0
- package/dist/tasks/messages.js +248 -0
- package/dist/tasks/routes.js +219 -0
- package/dist/tasks/runs.js +104 -0
- package/dist/tasks/service.js +282 -0
- package/dist/tasks/store.js +168 -0
- package/dist/tasks/tool.js +281 -0
- package/dist/tasks/types.js +5 -0
- package/dist/web/auth.js +280 -0
- package/dist/web/files.js +167 -0
- package/dist/web/public/assets/index-8CinH1uR.css +2 -0
- package/dist/web/public/assets/index-DAgP1Gq8.js +78 -0
- package/dist/web/public/icon-192.png +0 -0
- package/dist/web/public/icon-32.png +0 -0
- package/dist/web/public/icon-512.png +0 -0
- package/dist/web/public/icon-maskable-512.png +0 -0
- package/dist/web/public/icon-touch-192.png +0 -0
- package/dist/web/public/icon.svg +19 -0
- package/dist/web/public/index.html +251 -0
- package/dist/web/public/manifest.webmanifest +16 -0
- package/dist/web/public/sw.js +21 -0
- package/dist/web/server.js +366 -0
- package/dist/web/session-state.js +39 -0
- package/docs/deploy.md +307 -0
- package/package.json +55 -0
- package/skills/pier-boards/SKILL.md +210 -0
- package/skills/pier-slack/SKILL.md +135 -0
- package/skills/pier-tasks/SKILL.md +120 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!-- Pier's mark: a dock in profile over water. Kept to four solid shapes so it
|
|
2
|
+
still reads at 16px in a browser tab. Source of truth for the PNG icons
|
|
3
|
+
next to it (favicon fallback, apple-touch, manifest).
|
|
4
|
+
|
|
5
|
+
The plate is a squircle, not a rect: a superellipse outline (the iOS /
|
|
6
|
+
Android app-icon silhouette) reads as an app rather than as a coloured
|
|
7
|
+
tile. Platforms that apply their own mask get the square PNGs instead. -->
|
|
8
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
|
|
9
|
+
<path fill="#4f46e5"
|
|
10
|
+
d="M32 0c16 0 24 0 28 4s4 12 4 28-0 24-4 28-12 4-28 4-24 0-28-4-4-12-4-28 0-24 4-28 12-4 28-4Z" />
|
|
11
|
+
<g fill="#fff">
|
|
12
|
+
<rect x="11" y="23" width="42" height="6.5" rx="1.5" />
|
|
13
|
+
<rect x="15" y="29.5" width="5.5" height="16" rx="1" />
|
|
14
|
+
<rect x="29.25" y="29.5" width="5.5" height="16" rx="1" />
|
|
15
|
+
<rect x="43.5" y="29.5" width="5.5" height="16" rx="1" />
|
|
16
|
+
</g>
|
|
17
|
+
<path d="M11 51q7-4 14 0t14 0 14 0" fill="none" stroke="#fff" stroke-width="4"
|
|
18
|
+
stroke-linecap="round" opacity=".85" />
|
|
19
|
+
</svg>
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<!-- viewport-fit=cover lets the safe-area insets in style.css do their job
|
|
6
|
+
under an iPhone's home indicator. -->
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
8
|
+
<meta name="theme-color" content="#fafafa" />
|
|
9
|
+
<!-- Installed as a home-screen app, this is also what makes Web Push
|
|
10
|
+
possible on iOS — notifications there require a standalone web app. -->
|
|
11
|
+
<link rel="manifest" href="/manifest.webmanifest" />
|
|
12
|
+
<!-- SVG first (scales to any tab/bookmark size), 32px PNG for the engines
|
|
13
|
+
that still ignore it. iOS wants an opaque square and rounds it itself. -->
|
|
14
|
+
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
|
15
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/icon-32.png" />
|
|
16
|
+
<link rel="apple-touch-icon" href="/icon-touch-192.png" />
|
|
17
|
+
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
18
|
+
<meta name="apple-mobile-web-app-title" content="Pier" />
|
|
19
|
+
<title>Pier</title>
|
|
20
|
+
<script type="module" crossorigin src="/assets/index-DAgP1Gq8.js"></script>
|
|
21
|
+
<link rel="stylesheet" crossorigin href="/assets/index-8CinH1uR.css">
|
|
22
|
+
</head>
|
|
23
|
+
<!-- The document never scrolls: this is a fixed-viewport workbench, and every
|
|
24
|
+
scrollable region is an inner pane. h-dvh, not h-screen, because 100vh can
|
|
25
|
+
exceed the visible viewport (mobile chrome, short windows) — that gap is
|
|
26
|
+
what lets the whole app slide up and take the sidebar with it. -->
|
|
27
|
+
<body class="grid h-dvh grid-cols-1 overflow-hidden text-[16px] leading-[1.45] text-neutral-800 antialiased md:grid-cols-[14.5rem_minmax(0,1fr)]">
|
|
28
|
+
<!-- Below md the aside leaves the grid and slides in as a drawer (style.css);
|
|
29
|
+
the scrim is its dismiss target and never paints on desktop. -->
|
|
30
|
+
<div id="drawer-scrim" class="fixed inset-0 z-30 hidden bg-black/25 md:hidden"></div>
|
|
31
|
+
<aside id="sidebar" class="flex flex-col overflow-hidden border-r border-neutral-200 bg-neutral-50">
|
|
32
|
+
<header class="flex items-center justify-between gap-2 px-3 py-2">
|
|
33
|
+
<h1 class="text-[15px] font-semibold tracking-tight">Pier</h1>
|
|
34
|
+
</header>
|
|
35
|
+
<details id="console-section" open class="flex-none border-b border-neutral-200/70">
|
|
36
|
+
<summary class="flex cursor-pointer select-none items-center gap-1 px-3 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-wide text-neutral-500 hover:bg-neutral-100">
|
|
37
|
+
<span class="chev">▶</span>Console
|
|
38
|
+
</summary>
|
|
39
|
+
<ul class="pb-1 text-[13.5px]">
|
|
40
|
+
<li><button type="button" id="open-activity"
|
|
41
|
+
class="block w-full cursor-pointer py-1.5 pl-6 pr-3 text-left hover:bg-neutral-100">Activity</button></li>
|
|
42
|
+
<li><button type="button" id="open-config"
|
|
43
|
+
class="block w-full cursor-pointer py-1.5 pl-6 pr-3 text-left hover:bg-neutral-100">Configuration</button></li>
|
|
44
|
+
<li><button type="button" id="open-channels"
|
|
45
|
+
class="block w-full cursor-pointer py-1.5 pl-6 pr-3 text-left hover:bg-neutral-100">Channels</button></li>
|
|
46
|
+
<li><button type="button" id="open-boards"
|
|
47
|
+
class="block w-full cursor-pointer py-1.5 pl-6 pr-3 text-left hover:bg-neutral-100">Boards</button></li>
|
|
48
|
+
<li><button type="button" id="open-settings"
|
|
49
|
+
class="block w-full cursor-pointer py-1.5 pl-6 pr-3 text-left hover:bg-neutral-100">Settings</button></li>
|
|
50
|
+
</ul>
|
|
51
|
+
</details>
|
|
52
|
+
<div class="flex items-center gap-1 px-3 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-wide text-neutral-500">
|
|
53
|
+
Projects
|
|
54
|
+
<button type="button" id="open-archive" title="Search all sessions"
|
|
55
|
+
class="icon-btn ml-auto" aria-label="Search all sessions">
|
|
56
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="h-3.5 w-3.5">
|
|
57
|
+
<circle cx="7" cy="7" r="4.25" /><path d="M10.2 10.2 14 14" stroke-linecap="round" />
|
|
58
|
+
</svg>
|
|
59
|
+
</button>
|
|
60
|
+
<button type="button" id="new-session" title="New session"
|
|
61
|
+
class="icon-btn" aria-label="New session">
|
|
62
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="h-3.5 w-3.5">
|
|
63
|
+
<rect x="2.25" y="2.25" width="11.5" height="11.5" rx="2.5" />
|
|
64
|
+
<path d="M8 5.5v5M5.5 8h5" stroke-linecap="round" />
|
|
65
|
+
</svg>
|
|
66
|
+
</button>
|
|
67
|
+
</div>
|
|
68
|
+
<div id="project-tree" class="flex-1 overflow-y-auto text-[13.5px]"></div>
|
|
69
|
+
<footer class="flex flex-none items-center gap-2 border-t border-neutral-200 px-3 py-1.5 text-[11px] text-neutral-400">
|
|
70
|
+
<button type="button" id="notify-toggle" title="Notify me when a turn finishes"
|
|
71
|
+
class="cursor-pointer rounded px-1 hover:bg-neutral-200 hover:text-neutral-700">Notify</button>
|
|
72
|
+
<!-- A link, not a label: AGPL-3.0 section 13 asks a network service to
|
|
73
|
+
point its users at the source it is running. -->
|
|
74
|
+
<a id="version" href="https://github.com/timqi/pier" target="_blank" rel="noreferrer"
|
|
75
|
+
title="Pier is free software (AGPL-3.0) — the source of what you are using"
|
|
76
|
+
class="ml-auto font-mono hover:text-neutral-700 hover:underline"></a>
|
|
77
|
+
</footer>
|
|
78
|
+
</aside>
|
|
79
|
+
|
|
80
|
+
<main class="flex min-w-0 flex-col overflow-hidden">
|
|
81
|
+
<!-- Mobile top bar: replaces the chat header below md (which hides), so a
|
|
82
|
+
Console view keeps the same way back to the drawer as a chat does. -->
|
|
83
|
+
<header id="mobile-bar" class="flex h-12 flex-none items-center gap-1 border-b border-neutral-200 px-1.5 md:hidden">
|
|
84
|
+
<button type="button" id="drawer-toggle" aria-label="Open sidebar"
|
|
85
|
+
class="flex h-10 w-10 flex-none cursor-pointer items-center justify-center rounded-md text-neutral-500 active:bg-neutral-200">
|
|
86
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" class="h-5 w-5">
|
|
87
|
+
<path d="M2.5 4h11M2.5 8h11M2.5 12h11" />
|
|
88
|
+
</svg>
|
|
89
|
+
</button>
|
|
90
|
+
<span id="mobile-title" class="min-w-0 flex-1 truncate text-[16px] font-medium"></span>
|
|
91
|
+
<button type="button" id="mobile-menu" aria-label="Session actions"
|
|
92
|
+
class="hidden h-10 w-10 flex-none cursor-pointer items-center justify-center rounded-md text-[20px] leading-none text-neutral-500 active:bg-neutral-200">⋯</button>
|
|
93
|
+
</header>
|
|
94
|
+
<section id="activity-view" class="hidden min-h-0 flex-1 flex-col"></section>
|
|
95
|
+
<section id="tasks-view" class="hidden min-h-0 flex-1 flex-col"></section>
|
|
96
|
+
<section id="config-view" class="hidden min-h-0 flex-1 flex-col"></section>
|
|
97
|
+
<section id="channels-view" class="hidden min-h-0 flex-1 flex-col"></section>
|
|
98
|
+
<section id="boards-view" class="hidden min-h-0 flex-1 flex-col"></section>
|
|
99
|
+
<section id="settings-view" class="hidden min-h-0 flex-1 flex-col"></section>
|
|
100
|
+
<header id="chat-header" class="flex h-10 flex-none items-center gap-2 border-b border-neutral-200 px-4 max-md:hidden">
|
|
101
|
+
<span id="chat-title" class="truncate font-medium"></span>
|
|
102
|
+
<!-- Right cluster: meta chips sit flush against the ⋯ button. Two
|
|
103
|
+
ml-autos would split the free space, so one wrapper owns it. -->
|
|
104
|
+
<div class="ml-auto flex flex-none items-center gap-1.5">
|
|
105
|
+
<!-- Inline session meta: a resident token chip; model + reasoning
|
|
106
|
+
unfold to its left on hover. Costs the header no extra height. -->
|
|
107
|
+
<div id="session-meta" class="group hidden flex-none items-center gap-1.5 text-[11px]"></div>
|
|
108
|
+
<button type="button" id="chat-menu" title="Session actions" aria-label="Session actions"
|
|
109
|
+
class="icon-btn">⋯</button>
|
|
110
|
+
</div>
|
|
111
|
+
</header>
|
|
112
|
+
<!-- The transcript's own type scale, inherited by rows, chips and markdown. -->
|
|
113
|
+
<div id="turns" class="flex-1 overflow-y-auto pb-4 text-[15px] leading-[1.35]"></div>
|
|
114
|
+
<!-- Pi's queue primitives are whole-queue (steer / clear), so the actions
|
|
115
|
+
live on the header row and read as "the queue", never per message. -->
|
|
116
|
+
<div id="queue-panel" class="hidden flex-col border-t border-neutral-200 px-3 py-1.5">
|
|
117
|
+
<div class="flex h-7 items-center gap-2">
|
|
118
|
+
<span id="queue-label" class="text-[11px] font-semibold uppercase tracking-wide text-amber-700">Queued</span>
|
|
119
|
+
<div class="ml-auto flex items-center gap-0.5 text-amber-700">
|
|
120
|
+
<button type="button" id="queue-steer" title="Send the whole queue now into the running turn" aria-label="Send queue now"
|
|
121
|
+
class="flex h-7 w-7 cursor-pointer items-center justify-center rounded-md hover:bg-amber-100 active:bg-amber-200">
|
|
122
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"
|
|
123
|
+
stroke-linecap="round" stroke-linejoin="round" class="h-3.5 w-3.5">
|
|
124
|
+
<path d="M14.5 1.5 7 9m7.5-7.5-4.2 12.6L7 9 1.7 6.7l12.8-5.2z" />
|
|
125
|
+
</svg>
|
|
126
|
+
</button>
|
|
127
|
+
<button type="button" id="queue-restart" title="Stop the running turn and send the whole queue as a new prompt" aria-label="Stop and send queue"
|
|
128
|
+
class="flex h-7 w-7 cursor-pointer items-center justify-center rounded-md hover:bg-amber-100 active:bg-amber-200">
|
|
129
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"
|
|
130
|
+
stroke-linecap="round" stroke-linejoin="round" class="h-3.5 w-3.5">
|
|
131
|
+
<!-- The arc's ink sits high-right of the box; centre it so the
|
|
132
|
+
three icons share one baseline. -->
|
|
133
|
+
<g transform="translate(-0.8 1.9)">
|
|
134
|
+
<path d="M13 5.2V1.8m0 0H9.6M13 1.8A6 6 0 1 0 14 9" />
|
|
135
|
+
<path d="M6.2 8h4M8.5 5.7 10.8 8l-2.3 2.3" />
|
|
136
|
+
</g>
|
|
137
|
+
</svg>
|
|
138
|
+
</button>
|
|
139
|
+
<button type="button" id="queue-recall" title="Return queued messages to the composer" aria-label="Recall queued messages"
|
|
140
|
+
class="flex h-7 w-7 cursor-pointer items-center justify-center rounded-md hover:bg-amber-100 active:bg-amber-200">
|
|
141
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"
|
|
142
|
+
stroke-linecap="round" stroke-linejoin="round" class="h-3.5 w-3.5">
|
|
143
|
+
<!-- Ink centred on the 16×16 box, or the glyph sits low next to its neighbours. -->
|
|
144
|
+
<path d="M5 3 2 6l3 3M2.5 6H10a3.5 3.5 0 0 1 0 7H8" />
|
|
145
|
+
</svg>
|
|
146
|
+
</button>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
<!-- Rows wrap, so the list grows with the queue and scrolls past ~5 lines
|
|
150
|
+
(18px leading) rather than shrinking the turns pane any further. -->
|
|
151
|
+
<ul id="queue-rows" class="flex max-h-[6rem] flex-col gap-1 overflow-y-auto overscroll-contain"></ul>
|
|
152
|
+
</div>
|
|
153
|
+
<form id="composer" class="border-t border-neutral-200 px-4 py-2">
|
|
154
|
+
<div class="rounded-xl border border-neutral-300 bg-white px-2 py-1.5 transition-shadow focus-within:border-indigo-400 focus-within:ring-2 focus-within:ring-indigo-100">
|
|
155
|
+
<div id="image-strip" class="hidden flex-wrap gap-2 px-1 pb-1.5 pt-1"></div>
|
|
156
|
+
<!-- items-end keeps the icons on the bottom row while the textarea grows upward -->
|
|
157
|
+
<div class="flex items-end gap-1.5">
|
|
158
|
+
<button type="button" id="attach" title="Attach image (or paste / drop)"
|
|
159
|
+
class="flex h-7 w-7 flex-none cursor-pointer items-center justify-center rounded-lg text-[17px] leading-none text-neutral-400 hover:bg-neutral-100 hover:text-neutral-600">+</button>
|
|
160
|
+
<input id="attach-input" type="file" accept="image/*" multiple class="hidden" />
|
|
161
|
+
<textarea id="input" rows="1"
|
|
162
|
+
class="block max-h-48 min-w-0 flex-1 resize-none bg-transparent px-1 py-0.5 focus:outline-none"
|
|
163
|
+
placeholder="Message…"></textarea>
|
|
164
|
+
<button type="button" id="stop" title="Stop the running turn"
|
|
165
|
+
class="hidden h-7 w-7 flex-none cursor-pointer items-center justify-center rounded-lg bg-red-50 text-red-600 hover:bg-red-100">
|
|
166
|
+
<svg viewBox="0 0 16 16" fill="currentColor" class="h-3.5 w-3.5">
|
|
167
|
+
<rect x="3.5" y="3.5" width="9" height="9" rx="1.5" />
|
|
168
|
+
</svg>
|
|
169
|
+
</button>
|
|
170
|
+
<!-- Style + icon double as the state display: indigo plane = send, amber clock = queue -->
|
|
171
|
+
<button type="submit" id="send" title="Send">
|
|
172
|
+
<svg id="send-plane" viewBox="0 0 16 16" fill="none" stroke="currentColor"
|
|
173
|
+
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="h-3.5 w-3.5">
|
|
174
|
+
<path d="M14.5 1.5 7 9m7.5-7.5-4.2 12.6L7 9 1.7 6.7l12.8-5.2z" />
|
|
175
|
+
</svg>
|
|
176
|
+
<svg id="send-queue" viewBox="0 0 16 16" fill="none" stroke="currentColor"
|
|
177
|
+
stroke-width="1.5" stroke-linecap="round" class="hidden h-3.5 w-3.5">
|
|
178
|
+
<circle cx="8" cy="8" r="6" />
|
|
179
|
+
<path d="M8 4.75V8l2.4 1.5" />
|
|
180
|
+
</svg>
|
|
181
|
+
</button>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
</form>
|
|
185
|
+
</main>
|
|
186
|
+
|
|
187
|
+
<dialog id="new-dialog" class="m-auto w-[min(24rem,92vw)] rounded-xl border border-neutral-200 p-4 shadow-xl backdrop:bg-black/20">
|
|
188
|
+
<form id="new-form" method="dialog" class="flex flex-col gap-3">
|
|
189
|
+
<h3 class="font-semibold">New session</h3>
|
|
190
|
+
<label class="flex flex-col gap-1 text-[13px] text-neutral-600">
|
|
191
|
+
Working directory
|
|
192
|
+
<span id="new-cwd-row" class="flex items-center gap-1.5">
|
|
193
|
+
<input id="new-cwd" type="text" required placeholder="/path/to/project" list="known-projects"
|
|
194
|
+
class="min-w-0 flex-1 rounded-md border border-neutral-300 px-2 py-1.5 font-mono text-[13px] focus:border-indigo-400 focus:outline-none" />
|
|
195
|
+
</span>
|
|
196
|
+
<datalist id="known-projects"></datalist>
|
|
197
|
+
</label>
|
|
198
|
+
<div class="flex justify-end gap-2">
|
|
199
|
+
<button type="button" id="new-cancel" class="btn text-[13px]">Cancel</button>
|
|
200
|
+
<button type="submit" class="btn btn-primary text-[13px]">Create</button>
|
|
201
|
+
</div>
|
|
202
|
+
</form>
|
|
203
|
+
</dialog>
|
|
204
|
+
|
|
205
|
+
<dialog id="archive-dialog" class="m-auto w-[35rem] max-w-[90vw] rounded-xl border border-neutral-200 p-0 shadow-xl backdrop:bg-black/20">
|
|
206
|
+
<div class="flex items-center gap-2 border-b border-neutral-200 px-3 py-2">
|
|
207
|
+
<input id="archive-search" type="text" placeholder="Search all sessions…"
|
|
208
|
+
class="w-full bg-transparent text-[14px] focus:outline-none" />
|
|
209
|
+
<span id="archive-count" class="flex-none rounded bg-neutral-100 px-1.5 text-[11px] text-neutral-500"></span>
|
|
210
|
+
<button type="button" id="archive-close" class="btn flex-none text-[12px]">Close</button>
|
|
211
|
+
</div>
|
|
212
|
+
<ul id="archive-list" class="max-h-[60vh] overflow-y-auto py-1 text-[13.5px]"></ul>
|
|
213
|
+
</dialog>
|
|
214
|
+
|
|
215
|
+
<!-- Full-size view of a transcript image; click anywhere (or Esc) closes.
|
|
216
|
+
The arrows page through every thumbnail in the transcript, as do ← / →;
|
|
217
|
+
they hide when there is only one image to look at. -->
|
|
218
|
+
<dialog id="image-dialog" class="m-auto max-h-[92vh] max-w-[92vw] rounded-xl bg-transparent p-0 backdrop:bg-black/70">
|
|
219
|
+
<div class="relative">
|
|
220
|
+
<img id="image-full" alt="" class="max-h-[92vh] max-w-[92vw] cursor-zoom-out rounded-xl object-contain" />
|
|
221
|
+
<button type="button" id="image-prev" aria-label="Previous image" class="lightbox-nav left-2">‹</button>
|
|
222
|
+
<button type="button" id="image-next" aria-label="Next image" class="lightbox-nav right-2">›</button>
|
|
223
|
+
</div>
|
|
224
|
+
</dialog>
|
|
225
|
+
|
|
226
|
+
<!-- Text preview of an agent attachment; download stays one click away. -->
|
|
227
|
+
<dialog id="file-dialog" class="m-auto w-[45rem] max-w-[92vw] rounded-xl border border-neutral-200 p-0 shadow-xl backdrop:bg-black/40">
|
|
228
|
+
<div class="flex items-center gap-2 border-b border-neutral-200 px-3 py-2">
|
|
229
|
+
<span id="file-name" class="min-w-0 flex-1 truncate font-mono text-[12.5px] text-neutral-700"></span>
|
|
230
|
+
<a id="file-download" download class="btn flex-none text-[12px] no-underline">Download</a>
|
|
231
|
+
<button type="button" id="file-close" class="btn flex-none text-[12px]">Close</button>
|
|
232
|
+
</div>
|
|
233
|
+
<pre id="file-text" class="max-h-[70vh] overflow-auto whitespace-pre-wrap break-words px-3 py-2 text-[12.5px] leading-relaxed"></pre>
|
|
234
|
+
</dialog>
|
|
235
|
+
|
|
236
|
+
<!-- Before the bundle, and only until it loads: a module that throws while
|
|
237
|
+
evaluating leaves a blank page, no handler installed and nothing in the
|
|
238
|
+
server's log. ui/report.ts takes over by setting the flag below.
|
|
239
|
+
Signed-in only, like every other /api route: this page is not served to
|
|
240
|
+
anyone else, and a stranger who could write the journal would own it. -->
|
|
241
|
+
<script>
|
|
242
|
+
addEventListener("error", function (e) {
|
|
243
|
+
// A throw inside an error handler is reported as another error: anything
|
|
244
|
+
// missing here has to be a silent return, not an exception.
|
|
245
|
+
if (window.__pierReporting || !navigator.sendBeacon) return;
|
|
246
|
+
var body = JSON.stringify({ message: "boot error: " + e.message, view: location.hash });
|
|
247
|
+
navigator.sendBeacon("/api/client-log", new Blob([body], { type: "application/json" }));
|
|
248
|
+
});
|
|
249
|
+
</script>
|
|
250
|
+
</body>
|
|
251
|
+
</html>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Pier",
|
|
3
|
+
"short_name": "Pier",
|
|
4
|
+
"start_url": "/",
|
|
5
|
+
"scope": "/",
|
|
6
|
+
"display": "standalone",
|
|
7
|
+
"orientation": "portrait-primary",
|
|
8
|
+
"background_color": "#fafafa",
|
|
9
|
+
"theme_color": "#fafafa",
|
|
10
|
+
"icons": [
|
|
11
|
+
{ "src": "/icon.svg", "sizes": "any", "type": "image/svg+xml" },
|
|
12
|
+
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
|
|
13
|
+
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" },
|
|
14
|
+
{ "src": "/icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Notification transport, nothing else. Pier is useless offline, so there is
|
|
2
|
+
// no cache and no fetch handler here — the only reason this file exists is that
|
|
3
|
+
// an installed iOS web app can post notifications solely through a service
|
|
4
|
+
// worker registration. A `push` handler joins it when the server can send one.
|
|
5
|
+
|
|
6
|
+
self.addEventListener("notificationclick", (event) => {
|
|
7
|
+
event.notification.close();
|
|
8
|
+
const url = event.notification.data?.url ?? "/";
|
|
9
|
+
event.waitUntil(
|
|
10
|
+
(async () => {
|
|
11
|
+
const clients = await self.clients.matchAll({ type: "window", includeUncontrolled: true });
|
|
12
|
+
// Reuse the open workbench — a second window would fork the session view.
|
|
13
|
+
for (const client of clients) {
|
|
14
|
+
if (new URL(client.url).origin !== self.location.origin) continue;
|
|
15
|
+
await client.navigate(url);
|
|
16
|
+
return client.focus();
|
|
17
|
+
}
|
|
18
|
+
return self.clients.openWindow(url);
|
|
19
|
+
})(),
|
|
20
|
+
);
|
|
21
|
+
});
|