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