pi-tau-web-server 1.0.8
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/README.md +303 -0
- package/bin/auth.js +96 -0
- package/bin/config.js +99 -0
- package/bin/model-utils.js +134 -0
- package/bin/server-main.js +1189 -0
- package/bin/sessions.js +492 -0
- package/bin/tau.js +8 -0
- package/bin/tree.js +248 -0
- package/bin/types.js +2 -0
- package/package.json +74 -0
- package/public/app-main.js +2025 -0
- package/public/app-types.js +1 -0
- package/public/app.js +1 -0
- package/public/command-palette.js +42 -0
- package/public/dialogs.js +199 -0
- package/public/file-browser.js +196 -0
- package/public/icons/apple-touch-icon.png +0 -0
- package/public/icons/favicon-16.png +0 -0
- package/public/icons/favicon-32.png +0 -0
- package/public/icons/tau-192.png +0 -0
- package/public/icons/tau-512.png +0 -0
- package/public/icons/tau-logo.png +0 -0
- package/public/icons/tau-logo.svg +13 -0
- package/public/icons/tau-maskable-512.png +0 -0
- package/public/icons/tau-new.png +0 -0
- package/public/index.html +264 -0
- package/public/launcher-panel.js +54 -0
- package/public/launcher.js +84 -0
- package/public/manifest.json +28 -0
- package/public/markdown.js +336 -0
- package/public/message-renderer.js +268 -0
- package/public/model-picker.js +478 -0
- package/public/session-sidebar.js +460 -0
- package/public/session-stats-card.js +123 -0
- package/public/state.js +81 -0
- package/public/style.css +3864 -0
- package/public/sw.js +66 -0
- package/public/themes.js +72 -0
- package/public/tool-card.js +317 -0
- package/public/tree-view.js +474 -0
- package/public/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/public/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/public/vendor/katex/katex.min.css +1 -0
- package/public/vendor/katex/katex.min.js +1 -0
- package/public/voice-input.js +74 -0
- package/public/websocket-client.js +156 -0
- package/scripts/copy-katex.mjs +32 -0
- package/src/pi-extension/tau-tree.ts +71 -0
- package/src/public/app-main.ts +2190 -0
- package/src/public/app-types.ts +96 -0
- package/src/public/app.ts +1 -0
- package/src/public/command-palette.ts +53 -0
- package/src/public/dialogs.ts +251 -0
- package/src/public/file-browser.ts +224 -0
- package/src/public/launcher-panel.ts +68 -0
- package/src/public/launcher.ts +101 -0
- package/src/public/legacy-dom.d.ts +29 -0
- package/src/public/markdown.ts +372 -0
- package/src/public/message-renderer.ts +311 -0
- package/src/public/model-picker.ts +500 -0
- package/src/public/session-sidebar.ts +522 -0
- package/src/public/session-stats-card.ts +176 -0
- package/src/public/state.ts +96 -0
- package/src/public/sw.ts +79 -0
- package/src/public/themes.ts +73 -0
- package/src/public/tool-card.ts +375 -0
- package/src/public/tree-view.ts +527 -0
- package/src/public/voice-input.ts +98 -0
- package/src/public/websocket-client.ts +165 -0
- package/src/server/auth.ts +88 -0
- package/src/server/config.ts +88 -0
- package/src/server/model-utils.ts +122 -0
- package/src/server/server-main.ts +1004 -0
- package/src/server/sessions.ts +481 -0
- package/src/server/tau.ts +9 -0
- package/src/server/tree.ts +288 -0
- package/src/server/types.ts +68 -0
- package/tsconfig.json +3 -0
- package/tsconfig.public.json +15 -0
- package/tsconfig.server.json +16 -0
package/bin/tree.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Session-tree navigation for the backend-local `navigate_tree` RPC command.
|
|
4
|
+
*
|
|
5
|
+
* pi stores a session as an append-only JSONL tree (entries have id/parentId;
|
|
6
|
+
* the current position is the "leaf"). pi's native RPC protocol only exposes
|
|
7
|
+
* READ-ONLY tree commands (get_tree / get_entries) — there is no command that
|
|
8
|
+
* moves the leaf — so tau bundles a small pi extension
|
|
9
|
+
* (src/pi-extension/tau-tree.ts, loaded into every child via `--extension`)
|
|
10
|
+
* that registers a `/tau-tree-navigate <entryId>` command. The handler calls
|
|
11
|
+
* pi's own ctx.navigateTree() — the same API the TUI /tree uses — so the move
|
|
12
|
+
* happens in-process, respects other extensions' session_before_tree hooks,
|
|
13
|
+
* and persists itself by appending a `custom` marker entry (pi derives the
|
|
14
|
+
* leaf from the LAST entry on load, so a bare in-memory move would not
|
|
15
|
+
* survive a restart). Matched extension commands never reach the LLM and
|
|
16
|
+
* never append a user message, so neither the command text nor the marker is
|
|
17
|
+
* visible to the model.
|
|
18
|
+
*
|
|
19
|
+
* navigateTree below drives that command over RPC: it resolves the target
|
|
20
|
+
* from a preflight get_tree, sends the prompt, then re-fetches the tree to
|
|
21
|
+
* refresh session.entries and VERIFY the move — a handler error does not fail
|
|
22
|
+
* the prompt response (pi reports it as an `extension_error` event instead),
|
|
23
|
+
* so the follow-up get_tree is the real success check.
|
|
24
|
+
*
|
|
25
|
+
* Selection semantics mirror pi's /tree (docs/sessions.md "Selection
|
|
26
|
+
* Behavior"); the extension delegates them to ctx.navigateTree, and tau
|
|
27
|
+
* re-derives them here (selectNavigationTarget) only to detect no-ops, verify
|
|
28
|
+
* the new leaf, and hand the client its editorText — RPC-mode extensions
|
|
29
|
+
* cannot fill an editor:
|
|
30
|
+
* - user (or custom) MESSAGE entry → the leaf moves to the entry's PARENT
|
|
31
|
+
* and the message text goes back as editorText so the client can put it
|
|
32
|
+
* in the input box for edit + resubmit (root user message → reset to an
|
|
33
|
+
* empty conversation).
|
|
34
|
+
* - any other entry → the leaf moves TO that entry, no editorText.
|
|
35
|
+
*/
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.NAVIGATION_MARKER_TYPE = exports.NAVIGATE_COMMAND = void 0;
|
|
38
|
+
exports.selectNavigationTarget = selectNavigationTarget;
|
|
39
|
+
exports.flattenTree = flattenTree;
|
|
40
|
+
exports.pathFromRoot = pathFromRoot;
|
|
41
|
+
exports.leafDescendsFrom = leafDescendsFrom;
|
|
42
|
+
exports.isTreeNavigationInProgress = isTreeNavigationInProgress;
|
|
43
|
+
exports.navigateTree = navigateTree;
|
|
44
|
+
/**
|
|
45
|
+
* Name of the slash command registered by tau's bundled pi extension.
|
|
46
|
+
* Keep in sync with NAVIGATE_COMMAND in src/pi-extension/tau-tree.ts.
|
|
47
|
+
*/
|
|
48
|
+
exports.NAVIGATE_COMMAND = 'tau-tree-navigate';
|
|
49
|
+
/**
|
|
50
|
+
* customType of the marker entries the extension appends to persist a leaf
|
|
51
|
+
* move. Keep in sync with src/pi-extension/tau-tree.ts (and the frontend
|
|
52
|
+
* constant in src/public/tree-view.ts, which hides markers from the tree).
|
|
53
|
+
*/
|
|
54
|
+
exports.NAVIGATION_MARKER_TYPE = 'tau:navigate-tree';
|
|
55
|
+
function textFromContent(content) {
|
|
56
|
+
if (typeof content === 'string')
|
|
57
|
+
return content;
|
|
58
|
+
if (!Array.isArray(content))
|
|
59
|
+
return '';
|
|
60
|
+
return content
|
|
61
|
+
.filter((b) => !!b && typeof b === 'object')
|
|
62
|
+
.filter((b) => b.type === 'text' && typeof b.text === 'string')
|
|
63
|
+
.map((b) => b.text)
|
|
64
|
+
.join('\n');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Decide where the leaf should move for a navigation target, mirroring pi's
|
|
68
|
+
* /tree selection behavior. Pure function — exported for tests.
|
|
69
|
+
*/
|
|
70
|
+
function selectNavigationTarget(entry) {
|
|
71
|
+
if (entry.type === 'message' && entry.message?.role === 'user') {
|
|
72
|
+
return { leafTargetId: entry.parentId ?? null, editorText: textFromContent(entry.message.content) };
|
|
73
|
+
}
|
|
74
|
+
if (entry.type === 'custom_message') {
|
|
75
|
+
return { leafTargetId: entry.parentId ?? null, editorText: textFromContent(entry.content) };
|
|
76
|
+
}
|
|
77
|
+
return { leafTargetId: entry.id };
|
|
78
|
+
}
|
|
79
|
+
/** Flatten get_tree nodes (possibly multiple roots) into an id → entry map. */
|
|
80
|
+
function flattenTree(nodes) {
|
|
81
|
+
const byId = new Map();
|
|
82
|
+
const stack = [...(nodes || [])];
|
|
83
|
+
while (stack.length) {
|
|
84
|
+
const node = stack.pop();
|
|
85
|
+
if (!node || !node.entry || typeof node.entry.id !== 'string')
|
|
86
|
+
continue;
|
|
87
|
+
byId.set(node.entry.id, node.entry);
|
|
88
|
+
if (node.children && node.children.length)
|
|
89
|
+
stack.push(...node.children);
|
|
90
|
+
}
|
|
91
|
+
return byId;
|
|
92
|
+
}
|
|
93
|
+
/** Walk root→leaf along parentId links; returns [] for a null/unknown leaf. */
|
|
94
|
+
function pathFromRoot(byId, leafId) {
|
|
95
|
+
const path = [];
|
|
96
|
+
const seen = new Set();
|
|
97
|
+
let current = leafId ? byId.get(leafId) : undefined;
|
|
98
|
+
while (current && !seen.has(current.id)) {
|
|
99
|
+
seen.add(current.id);
|
|
100
|
+
path.push(current);
|
|
101
|
+
current = current.parentId ? byId.get(current.parentId) : undefined;
|
|
102
|
+
}
|
|
103
|
+
path.reverse();
|
|
104
|
+
return path;
|
|
105
|
+
}
|
|
106
|
+
/** True when `leafId` is `ancestorId` or one of its descendants. */
|
|
107
|
+
function leafDescendsFrom(byId, leafId, ancestorId) {
|
|
108
|
+
const seen = new Set();
|
|
109
|
+
let current = leafId ? byId.get(leafId) : undefined;
|
|
110
|
+
while (current && !seen.has(current.id)) {
|
|
111
|
+
if (current.id === ancestorId)
|
|
112
|
+
return true;
|
|
113
|
+
seen.add(current.id);
|
|
114
|
+
current = current.parentId ? byId.get(current.parentId) : undefined;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
// Session ids with a navigate_tree currently in flight. navigateTree runs a
|
|
119
|
+
// multi-await check→navigate→verify sequence, so without this a second
|
|
120
|
+
// navigation (or a prompt — see the guard in server-main's handleRpcCommand)
|
|
121
|
+
// could interleave with it and land its entries on the wrong branch.
|
|
122
|
+
const navigationsInFlight = new Set();
|
|
123
|
+
/** True while a navigate_tree is moving this session's leaf. */
|
|
124
|
+
function isTreeNavigationInProgress(sessionId) {
|
|
125
|
+
return navigationsInFlight.has(sessionId);
|
|
126
|
+
}
|
|
127
|
+
function assertNotStreaming(session) {
|
|
128
|
+
if (session.isStreaming) {
|
|
129
|
+
throw new Error('Cannot navigate the session tree while the agent is streaming; abort or wait for the turn to finish');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Once per child process, confirm pi actually registered tau's extension
|
|
134
|
+
* command. Without this check a broken install (extension file missing or
|
|
135
|
+
* failing to load) would make the `/tau-tree-navigate …` text fall through
|
|
136
|
+
* pi's command matching and be sent to the LLM as a literal user prompt.
|
|
137
|
+
*/
|
|
138
|
+
async function assertNavigateCommandAvailable(session) {
|
|
139
|
+
if (session.navigateCommandChecked)
|
|
140
|
+
return;
|
|
141
|
+
const resp = await session.send({ type: 'get_commands' }, { timeoutMs: 15000 });
|
|
142
|
+
if (resp.success === false) {
|
|
143
|
+
throw new Error(`pi could not list its commands: ${resp.error || 'unknown error'}`);
|
|
144
|
+
}
|
|
145
|
+
const commands = (resp.data || {}).commands || [];
|
|
146
|
+
const found = commands.some((c) => c.name === exports.NAVIGATE_COMMAND && c.source === 'extension');
|
|
147
|
+
if (!found) {
|
|
148
|
+
throw new Error(`The pi child did not load tau's ${exports.NAVIGATE_COMMAND} extension; cannot navigate the session tree`);
|
|
149
|
+
}
|
|
150
|
+
session.navigateCommandChecked = true;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Full navigate_tree flow for a live session: resolve the target from a
|
|
154
|
+
* preflight get_tree, have the child move its leaf in-process via the bundled
|
|
155
|
+
* extension's /tau-tree-navigate command, re-fetch the tree to confirm the
|
|
156
|
+
* move, refresh session.entries to the new active path, and broadcast a fresh
|
|
157
|
+
* `live_session_snapshot` (plus `live_session_updated`) so every connected
|
|
158
|
+
* browser re-renders the conversation.
|
|
159
|
+
*
|
|
160
|
+
* The streaming guard cannot be made fully airtight from tau's side (a prompt
|
|
161
|
+
* whose agent_start has not yet round-tripped through the child is invisible
|
|
162
|
+
* here), so the flow is defensive instead: one navigation per session at a
|
|
163
|
+
* time, the extension re-checks idleness in-process (ctx.isIdle()) right
|
|
164
|
+
* before moving the leaf, prompts are refused by handleRpcCommand while a
|
|
165
|
+
* navigation is in flight, and if the child still turns out to have moved on
|
|
166
|
+
* (leaf verification fails) the clients are re-synced to the child's real
|
|
167
|
+
* state before the error is reported.
|
|
168
|
+
*/
|
|
169
|
+
async function navigateTree(session, entryId) {
|
|
170
|
+
if (navigationsInFlight.has(session.id)) {
|
|
171
|
+
throw new Error('Another tree navigation for this session is already in progress');
|
|
172
|
+
}
|
|
173
|
+
navigationsInFlight.add(session.id);
|
|
174
|
+
try {
|
|
175
|
+
assertNotStreaming(session);
|
|
176
|
+
// Preflight: fetch the tree to resolve the target entry (clear error for
|
|
177
|
+
// unknown ids), detect no-ops, and derive editorText locally — RPC-mode
|
|
178
|
+
// extensions cannot fill the client's editor, and the /tau-tree-navigate
|
|
179
|
+
// prompt response cannot carry handler data.
|
|
180
|
+
let treeResp = await session.send({ type: 'get_tree' }, { timeoutMs: 15000 });
|
|
181
|
+
if (treeResp.success === false) {
|
|
182
|
+
throw new Error(`pi did not answer the pre-navigation tree check: ${treeResp.error || 'unknown error'}`);
|
|
183
|
+
}
|
|
184
|
+
let treeData = (treeResp.data || {});
|
|
185
|
+
let byId = flattenTree(treeData.tree);
|
|
186
|
+
const entry = byId.get(entryId);
|
|
187
|
+
if (!entry)
|
|
188
|
+
throw new Error(`Entry ${entryId} not found in the session tree`);
|
|
189
|
+
const target = selectNavigationTarget(entry);
|
|
190
|
+
const previousLeafId = treeData.leafId ?? null;
|
|
191
|
+
// Already there: pi's navigateTree no-ops when the selected entry IS the
|
|
192
|
+
// current leaf, and moving to the current position would be pure churn —
|
|
193
|
+
// skip the round-trip (but still hand back the editorText, so selecting
|
|
194
|
+
// the leaf user message keeps prefilling the composer).
|
|
195
|
+
if (entryId === previousLeafId || (target.leafTargetId !== null && target.leafTargetId === previousLeafId)) {
|
|
196
|
+
return target.editorText !== undefined ? { editorText: target.editorText } : {};
|
|
197
|
+
}
|
|
198
|
+
await assertNavigateCommandAvailable(session);
|
|
199
|
+
// A turn may have started while the preflight round-tripped; re-check
|
|
200
|
+
// here for a fast, precise error (the extension re-checks in-process via
|
|
201
|
+
// ctx.isIdle() as the authoritative guard).
|
|
202
|
+
assertNotStreaming(session);
|
|
203
|
+
session.lastExtensionError = null;
|
|
204
|
+
const prompted = await session.send({ type: 'prompt', message: `/${exports.NAVIGATE_COMMAND} ${entryId}` }, { timeoutMs: 30000 });
|
|
205
|
+
if (prompted.success === false) {
|
|
206
|
+
throw new Error(`pi refused the tree-navigation command: ${prompted.error || 'unknown error'}`);
|
|
207
|
+
}
|
|
208
|
+
// The prompt ack only means the handler ran — its errors surface as
|
|
209
|
+
// `extension_error` events, not as a failed response — so the follow-up
|
|
210
|
+
// get_tree is the real success check.
|
|
211
|
+
treeResp = await session.send({ type: 'get_tree' }, { timeoutMs: 30000 });
|
|
212
|
+
if (treeResp.success === false) {
|
|
213
|
+
// The child MAY have moved its leaf; we just cannot re-derive the
|
|
214
|
+
// entries. At least nudge clients so they refetch metadata instead of
|
|
215
|
+
// silently rendering the old branch forever.
|
|
216
|
+
session.manager.broadcastUpdated(session.id);
|
|
217
|
+
throw new Error(`pi could not report the session tree after navigating: ${treeResp.error || 'unknown error'}`);
|
|
218
|
+
}
|
|
219
|
+
treeData = (treeResp.data || {});
|
|
220
|
+
byId = flattenTree(treeData.tree);
|
|
221
|
+
const newLeafId = treeData.leafId ?? null;
|
|
222
|
+
// Refresh entries and broadcast BEFORE verifying the leaf: at this point
|
|
223
|
+
// the child is authoritative, so even when verification fails below every
|
|
224
|
+
// client must be re-rendered onto whatever the child actually has.
|
|
225
|
+
session.entries = pathFromRoot(byId, newLeafId);
|
|
226
|
+
session.manager.broadcast({ type: 'live_session_snapshot', sessionId: session.id, ...session.snapshot() });
|
|
227
|
+
session.manager.broadcastUpdated(session.id);
|
|
228
|
+
// On success the extension appended a marker at the navigation target and
|
|
229
|
+
// that marker IS the child's new leaf (no reload happens, so nothing else
|
|
230
|
+
// gets appended in between). Requiring the leaf to have changed AND to be
|
|
231
|
+
// a marker parented at the target rejects both a silent no-move (handler
|
|
232
|
+
// error) and a leaf that moved somewhere unexpected.
|
|
233
|
+
const newLeaf = newLeafId ? byId.get(newLeafId) : undefined;
|
|
234
|
+
const moved = newLeafId !== previousLeafId &&
|
|
235
|
+
newLeaf !== undefined &&
|
|
236
|
+
newLeaf.type === 'custom' &&
|
|
237
|
+
newLeaf.customType === exports.NAVIGATION_MARKER_TYPE &&
|
|
238
|
+
(newLeaf.parentId ?? null) === target.leafTargetId;
|
|
239
|
+
if (!moved) {
|
|
240
|
+
const detail = session.lastExtensionError ? `: ${session.lastExtensionError}` : '';
|
|
241
|
+
throw new Error(`pi did not move the session leaf to the expected entry${detail}`);
|
|
242
|
+
}
|
|
243
|
+
return target.editorText !== undefined ? { editorText: target.editorText } : {};
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
navigationsInFlight.delete(session.id);
|
|
247
|
+
}
|
|
248
|
+
}
|
package/bin/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-tau-web-server",
|
|
3
|
+
"version": "1.0.8",
|
|
4
|
+
"description": "Web browser workspace for Pi — a standalone web server that manages multiple live Pi RPC sessions in parallel.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi",
|
|
7
|
+
"pi-coding-agent",
|
|
8
|
+
"ai-agent",
|
|
9
|
+
"ai-coding-agent",
|
|
10
|
+
"coding-agent",
|
|
11
|
+
"harness",
|
|
12
|
+
"web-ui",
|
|
13
|
+
"web-server",
|
|
14
|
+
"rpc",
|
|
15
|
+
"standalone",
|
|
16
|
+
"multi-session"
|
|
17
|
+
],
|
|
18
|
+
"author": {
|
|
19
|
+
"name": "Milan Glacier",
|
|
20
|
+
"email": "dev@milanglacier.com",
|
|
21
|
+
"url": "https://github.com/milanglacier"
|
|
22
|
+
},
|
|
23
|
+
"contributors": [
|
|
24
|
+
{
|
|
25
|
+
"name": "deflating",
|
|
26
|
+
"url": "https://github.com/deflating"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"homepage": "https://github.com/milanglacier/pi-tau-web-server#readme",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/milanglacier/pi-tau-web-server/issues"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/milanglacier/pi-tau-web-server.git"
|
|
37
|
+
},
|
|
38
|
+
"main": "./bin/tau.js",
|
|
39
|
+
"bin": {
|
|
40
|
+
"pi-tau-web-server": "./bin/tau.js"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=22.19.0"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public",
|
|
47
|
+
"registry": "https://registry.npmjs.org/"
|
|
48
|
+
},
|
|
49
|
+
"files": [
|
|
50
|
+
"bin",
|
|
51
|
+
"public",
|
|
52
|
+
"scripts",
|
|
53
|
+
"src",
|
|
54
|
+
"tsconfig.json",
|
|
55
|
+
"tsconfig.server.json",
|
|
56
|
+
"tsconfig.public.json"
|
|
57
|
+
],
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsc -p tsconfig.server.json && tsc -p tsconfig.public.json && node scripts/copy-katex.mjs",
|
|
60
|
+
"prepare": "npm run build",
|
|
61
|
+
"typecheck": "tsc -p tsconfig.server.json --noEmit && tsc -p tsconfig.public.json --noEmit && tsc -p tsconfig.test.json",
|
|
62
|
+
"test": "npm run build && node --test"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"ws": "^8.21.1"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@earendil-works/pi-coding-agent": "0.80.10",
|
|
69
|
+
"@types/node": "^26.1.1",
|
|
70
|
+
"@types/ws": "^8.18.1",
|
|
71
|
+
"katex": "^0.18.0",
|
|
72
|
+
"typescript": "^7.0.2"
|
|
73
|
+
}
|
|
74
|
+
}
|