@oharness/server 0.8.0 → 0.9.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.
Files changed (43) hide show
  1. package/dist/account-usage.d.ts +29 -0
  2. package/dist/account-usage.js +56 -0
  3. package/dist/account-usage.js.map +1 -0
  4. package/dist/files.d.ts +40 -1
  5. package/dist/files.js +94 -3
  6. package/dist/files.js.map +1 -1
  7. package/dist/i18n.js +126 -2
  8. package/dist/i18n.js.map +1 -1
  9. package/dist/page/client/base.js +4 -0
  10. package/dist/page/client/base.js.map +1 -1
  11. package/dist/page/client/browser.js +3 -1
  12. package/dist/page/client/browser.js.map +1 -1
  13. package/dist/page/client/composer.js +20 -0
  14. package/dist/page/client/composer.js.map +1 -1
  15. package/dist/page/client/files.d.ts +7 -0
  16. package/dist/page/client/files.js +412 -0
  17. package/dist/page/client/files.js.map +1 -0
  18. package/dist/page/client/index.js +4 -0
  19. package/dist/page/client/index.js.map +1 -1
  20. package/dist/page/client/panels.js +1 -1
  21. package/dist/page/client/routines.js +19 -1
  22. package/dist/page/client/routines.js.map +1 -1
  23. package/dist/page/client/shell.js +12 -1
  24. package/dist/page/client/shell.js.map +1 -1
  25. package/dist/page/client/state.js +12 -4
  26. package/dist/page/client/state.js.map +1 -1
  27. package/dist/page/client/terminal.d.ts +7 -0
  28. package/dist/page/client/terminal.js +347 -0
  29. package/dist/page/client/terminal.js.map +1 -0
  30. package/dist/page/client/transcript.js +167 -0
  31. package/dist/page/client/transcript.js.map +1 -1
  32. package/dist/page/client/usage.js +400 -70
  33. package/dist/page/client/usage.js.map +1 -1
  34. package/dist/page/style.js +258 -3
  35. package/dist/page/style.js.map +1 -1
  36. package/dist/page.js +35 -1
  37. package/dist/page.js.map +1 -1
  38. package/dist/server.js +238 -31
  39. package/dist/server.js.map +1 -1
  40. package/dist/terminal.d.ts +34 -0
  41. package/dist/terminal.js +211 -0
  42. package/dist/terminal.js.map +1 -0
  43. package/package.json +9 -8
@@ -0,0 +1,412 @@
1
+ /**
2
+ * The files pane — a project's tree beside the file picked from it — and the
3
+ * terminal that opens in the same folder.
4
+ *
5
+ * One part of the client script; see ./index.ts for how the parts join.
6
+ */
7
+ export const FILES = String.raw `
8
+ // --- files pane --------------------------------------------------------------
9
+ // Opened from a project's menu in the sidebar or from a session's tab, and
10
+ // always about one folder: the tree is fetched a directory at a time as it is
11
+ // unfolded, so a repository with a node_modules costs nothing until somebody
12
+ // actually walks into it.
13
+
14
+ const explorer = document.getElementById("explorer");
15
+ const explorerTitle = document.getElementById("explorer-title");
16
+ const explorerTree = document.getElementById("explorer-tree");
17
+ const explorerView = document.getElementById("explorer-view");
18
+
19
+ /** The project folder on show, absolute; null while the pane has never opened. */
20
+ let explorerCwd = null;
21
+ /** Each listed folder's children, keyed by path relative to the project. */
22
+ let explorerChildren = new Map();
23
+ /** Unfolded folders, remembered per project so a return finds them as left. */
24
+ const explorerOpenDirs = new Map();
25
+ /** The file on the right, relative to the project. */
26
+ let explorerSelected = null;
27
+ /** Markdown shows rendered unless somebody asked for the source. */
28
+ let explorerSource = false;
29
+
30
+ /** Past this a file is shown as plain text: the highlighter is one regex pass. */
31
+ const EXPLORER_HIGHLIGHT_MAX = 200 * 1024;
32
+ const EXPLORER_MARKDOWN = /\.(md|markdown|mdx)$/i;
33
+
34
+ function explorerIsOpen() {
35
+ return explorer.classList.contains("open");
36
+ }
37
+
38
+ function explorerDirs() {
39
+ let dirs = explorerOpenDirs.get(explorerCwd);
40
+ if (!dirs) explorerOpenDirs.set(explorerCwd, (dirs = new Set()));
41
+ return dirs;
42
+ }
43
+
44
+ /**
45
+ * Show one project's files.
46
+ *
47
+ * The same project asked for twice is a toggle, which is what the tab's button
48
+ * wants; the project menu passes force so that choosing "Files" never closes
49
+ * the thing it just asked to see.
50
+ */
51
+ async function openExplorer(cwd, force) {
52
+ if (!cwd) return;
53
+ if (explorerIsOpen() && explorerCwd === cwd && !force) return closeExplorer();
54
+ track("panel_opened", { panel: "files" });
55
+
56
+ // One column beside the transcript at a time: the review pane, the artifact
57
+ // drawer and the browser are all the same shape, and two of them open at
58
+ // once leaves the conversation a strip down the middle.
59
+ review.classList.remove("open");
60
+ shelf.classList.remove("open");
61
+ if (browserPane.classList.contains("open")) browserOpen(false);
62
+
63
+ if (explorerCwd !== cwd) {
64
+ explorerCwd = cwd;
65
+ explorerChildren = new Map();
66
+ explorerSelected = null;
67
+ explorerSource = false;
68
+ }
69
+ explorerTitle.textContent = projectName(cwd);
70
+ explorerTitle.title = cwd;
71
+ explorer.classList.add("open");
72
+ paintTabs();
73
+ await explorerLoad();
74
+ }
75
+
76
+ function closeExplorer() {
77
+ explorer.classList.remove("open");
78
+ paintTabs();
79
+ }
80
+
81
+ /** Keep the pane on the project of whichever tab is in front. */
82
+ function explorerFollow(cwd) {
83
+ if (explorerIsOpen() && cwd && cwd !== explorerCwd) void openExplorer(cwd, true);
84
+ }
85
+
86
+ /** Every folder on screen, fetched again: what the refresh button does. */
87
+ async function explorerLoad() {
88
+ const cwd = explorerCwd;
89
+ const dirs = ["", ...explorerDirs()];
90
+ const bodies = await Promise.all(dirs.map((dir) => explorerFetch(dir)));
91
+ if (cwd !== explorerCwd) return;
92
+ explorerChildren = new Map();
93
+ dirs.forEach((dir, index) => {
94
+ const body = bodies[index];
95
+ if (body.error) explorerDirs().delete(dir);
96
+ else explorerChildren.set(dir, body.entries || []);
97
+ });
98
+ paintExplorerTree();
99
+ if (explorerSelected) await explorerShow(explorerSelected);
100
+ else explorerView.replaceChildren(el("p", "muted explorer-hint", t("files.pick")));
101
+ }
102
+
103
+ function explorerFetch(dir) {
104
+ return getJson("/files/tree?cwd=" + encodeURIComponent(explorerCwd) + "&dir=" + encodeURIComponent(dir));
105
+ }
106
+
107
+ async function explorerToggleDir(dir) {
108
+ const dirs = explorerDirs();
109
+ if (dirs.has(dir)) {
110
+ dirs.delete(dir);
111
+ return paintExplorerTree();
112
+ }
113
+ dirs.add(dir);
114
+ if (!explorerChildren.has(dir)) {
115
+ const cwd = explorerCwd;
116
+ const body = await explorerFetch(dir);
117
+ if (cwd !== explorerCwd) return;
118
+ if (body.error) {
119
+ dirs.delete(dir);
120
+ return void addNotice(body.error, "err");
121
+ }
122
+ explorerChildren.set(dir, body.entries || []);
123
+ }
124
+ paintExplorerTree();
125
+ }
126
+
127
+ function paintExplorerTree() {
128
+ const scroll = explorerTree.scrollTop;
129
+ explorerTree.replaceChildren();
130
+ const root = explorerChildren.get("") || [];
131
+ if (!root.length) explorerTree.appendChild(el("p", "muted explorer-hint", t("files.empty")));
132
+ const dirs = explorerDirs();
133
+
134
+ const walk = (entries, depth) => {
135
+ for (const entry of entries) {
136
+ const row = el("button", "explorer-row" + (entry.path === explorerSelected ? " on" : ""));
137
+ row.type = "button";
138
+ row.setAttribute("role", "treeitem");
139
+ row.style.paddingLeft = 8 + depth * 14 + "px";
140
+ row.title = entry.path;
141
+ const open = entry.dir && dirs.has(entry.path);
142
+ if (entry.dir) row.setAttribute("aria-expanded", String(open));
143
+ const caret = el("span", "explorer-caret");
144
+ if (entry.dir) caret.appendChild(svgIcon(open ? "caret" : "chevronRight"));
145
+ row.appendChild(caret);
146
+ row.appendChild(svgIcon(entry.dir ? (open ? "folderOpen" : "folder") : "file"));
147
+ row.appendChild(el("span", "explorer-name", entry.name));
148
+ row.addEventListener("click", () => {
149
+ if (entry.dir) void explorerToggleDir(entry.path);
150
+ else void explorerShow(entry.path);
151
+ });
152
+ explorerTree.appendChild(row);
153
+ if (open) walk(explorerChildren.get(entry.path) || [], depth + 1);
154
+ }
155
+ };
156
+ walk(root, 0);
157
+ explorerTree.scrollTop = scroll;
158
+ }
159
+
160
+ function explorerSize(bytes) {
161
+ if (bytes < 1024) return bytes + " B";
162
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
163
+ return (bytes / 1024 / 1024).toFixed(1) + " MB";
164
+ }
165
+
166
+ async function explorerShow(file) {
167
+ const cwd = explorerCwd;
168
+ if (explorerSelected !== file) explorerSource = false;
169
+ explorerSelected = file;
170
+ for (const row of explorerTree.children) row.classList.toggle("on", row.title === file);
171
+
172
+ const body = await getJson("/files/read?cwd=" + encodeURIComponent(cwd) + "&path=" + encodeURIComponent(file));
173
+ if (cwd !== explorerCwd || file !== explorerSelected) return;
174
+ explorerView.replaceChildren();
175
+ if (body.error) return void explorerView.appendChild(el("p", "muted explorer-hint", body.error));
176
+
177
+ const bar = el("div", "explorer-bar");
178
+ bar.appendChild(el("span", "explorer-path mono", body.path));
179
+ bar.appendChild(el("span", "explorer-size", explorerSize(body.size)));
180
+
181
+ const markdown = body.kind === "text" && EXPLORER_MARKDOWN.test(body.path);
182
+ if (markdown) {
183
+ const flip = el("button", "explorer-btn", t(explorerSource ? "files.rendered" : "files.source"));
184
+ flip.type = "button";
185
+ flip.addEventListener("click", () => {
186
+ explorerSource = !explorerSource;
187
+ void explorerShow(file);
188
+ });
189
+ bar.appendChild(flip);
190
+ }
191
+ // A reference is a path relative to the session's own tree, so it is only
192
+ // offered when the tree on show is that one.
193
+ const active = activeId && panes.get(activeId);
194
+ if (active && active.cwd === cwd) {
195
+ const mention = el("button", "explorer-btn", t("files.mention"));
196
+ mention.type = "button";
197
+ mention.addEventListener("click", () => {
198
+ addRef("file", body.path);
199
+ input.focus();
200
+ });
201
+ bar.appendChild(mention);
202
+ }
203
+ explorerView.appendChild(bar);
204
+
205
+ const content = el("div", "explorer-content");
206
+ const raw = q("/files/raw?cwd=" + encodeURIComponent(cwd) + "&path=" + encodeURIComponent(body.path));
207
+ if (body.kind === "image") {
208
+ const img = el("img", "explorer-img");
209
+ img.alt = body.path;
210
+ img.src = raw;
211
+ content.appendChild(img);
212
+ } else if (body.kind === "video" || body.kind === "audio") {
213
+ const media = el(body.kind, "explorer-" + body.kind);
214
+ media.controls = true;
215
+ media.preload = "metadata";
216
+ media.src = raw;
217
+ content.appendChild(media);
218
+ } else if (body.kind === "pdf") {
219
+ content.classList.add("explorer-fill");
220
+ const frame = el("iframe", "explorer-pdf");
221
+ frame.title = body.path;
222
+ frame.src = raw;
223
+ content.appendChild(frame);
224
+ } else if (body.kind === "text" && markdown && !explorerSource) {
225
+ const md = renderMarkdown(body.content);
226
+ md.classList.add("explorer-md");
227
+ content.appendChild(md);
228
+ } else if (body.kind === "text") {
229
+ content.appendChild(explorerCode(body.content, body.path));
230
+ } else {
231
+ content.appendChild(el("p", "muted explorer-hint",
232
+ body.kind === "large" ? t("files.large", { size: explorerSize(body.size) }) : t("files.binary")));
233
+ }
234
+ explorerView.appendChild(content);
235
+ }
236
+
237
+ /** Source with a line-number gutter; the numbers are not part of a selection. */
238
+ function explorerCode(source, file) {
239
+ const text = source.replace(/\r\n?/g, "\n");
240
+ const lines = text.endsWith("\n") ? text.slice(0, -1).split("\n") : text.split("\n");
241
+ const wrap = el("div", "explorer-code");
242
+ const gutter = el("pre", "explorer-gutter");
243
+ gutter.setAttribute("aria-hidden", "true");
244
+ gutter.textContent = lines.map((_, index) => index + 1).join("\n");
245
+ const pre = el("pre", "code explorer-src");
246
+ const ext = (file.match(/\.([^./]+)$/) || [])[1] || "";
247
+ if (text.length > EXPLORER_HIGHLIGHT_MAX) pre.textContent = text;
248
+ else pre.appendChild(highlight(text, ext));
249
+ wrap.appendChild(gutter);
250
+ wrap.appendChild(pre);
251
+ return wrap;
252
+ }
253
+
254
+ /** The tab's two ways into its tree: the files pane and a terminal. */
255
+ function tabProjectButtons(id, cwd) {
256
+ const files = el("button", "tab-tool");
257
+ files.type = "button";
258
+ files.title = t("files.open");
259
+ files.setAttribute("aria-label", t("files.open"));
260
+ files.classList.toggle("on", id === activeId && explorerIsOpen() && explorerCwd === cwd);
261
+ files.appendChild(svgIcon("folder"));
262
+ files.addEventListener("click", async (event) => {
263
+ event.stopPropagation();
264
+ if (id !== activeId) {
265
+ await focusTab(id);
266
+ return void openExplorer(cwd, true);
267
+ }
268
+ void openExplorer(cwd);
269
+ });
270
+
271
+ const term = el("button", "tab-tool");
272
+ term.type = "button";
273
+ term.title = t("files.terminalTab");
274
+ term.setAttribute("aria-label", t("files.terminalTab"));
275
+ term.appendChild(svgIcon("command"));
276
+ term.classList.toggle("on", id === activeId && terminalShownFor(cwd));
277
+ term.addEventListener("click", async (event) => {
278
+ event.stopPropagation();
279
+ if (id !== activeId) {
280
+ await focusTab(id);
281
+ return void openTerminalIn(cwd, true);
282
+ }
283
+ void openTerminalIn(cwd);
284
+ });
285
+ return [files, term];
286
+ }
287
+
288
+ document.getElementById("explorer-close").addEventListener("click", closeExplorer);
289
+ document.getElementById("explorer-refresh").addEventListener("click", () => void explorerLoad());
290
+ document.getElementById("explorer-terminal").addEventListener("click", () => void openTerminalIn(explorerCwd, true));
291
+
292
+ // --- dragging a size -----------------------------------------------------------
293
+
294
+ /**
295
+ * A handle that resizes something while it is dragged.
296
+ *
297
+ * Pointer events, so a finger drags it as well as a mouse; the move handler is
298
+ * given the distance from where the drag started and does its own clamping.
299
+ * Arrow keys nudge it for anyone not using a pointer at all.
300
+ */
301
+ function dragHandle(handle, opts) {
302
+ handle.addEventListener("pointerdown", (event) => {
303
+ if (event.button !== 0 || (opts.enabled && !opts.enabled())) return;
304
+ event.preventDefault();
305
+ const x0 = event.clientX;
306
+ const y0 = event.clientY;
307
+ const state = opts.start();
308
+ try { handle.setPointerCapture(event.pointerId); } catch { /* the window listeners keep it going */ }
309
+ document.body.classList.add("dragging", opts.axis === "x" ? "dragging-x" : "dragging-y");
310
+ const move = (next) => opts.move(next.clientX - x0, next.clientY - y0, state);
311
+ const done = () => {
312
+ window.removeEventListener("pointermove", move);
313
+ window.removeEventListener("pointerup", done);
314
+ window.removeEventListener("pointercancel", done);
315
+ document.body.classList.remove("dragging", "dragging-x", "dragging-y");
316
+ if (opts.end) opts.end();
317
+ };
318
+ window.addEventListener("pointermove", move);
319
+ window.addEventListener("pointerup", done);
320
+ window.addEventListener("pointercancel", done);
321
+ });
322
+ handle.addEventListener("keydown", (event) => {
323
+ const keys = opts.axis === "x" ? ["ArrowLeft", "ArrowRight"] : ["ArrowUp", "ArrowDown"];
324
+ if (!keys.includes(event.key) || (opts.enabled && !opts.enabled())) return;
325
+ event.preventDefault();
326
+ const state = opts.start();
327
+ const step = event.key === keys[0] ? -16 : 16;
328
+ opts.move(opts.axis === "x" ? step : 0, opts.axis === "x" ? 0 : step, state);
329
+ if (opts.end) opts.end();
330
+ });
331
+ if (opts.reset) handle.addEventListener("dblclick", opts.reset);
332
+ }
333
+
334
+ /** A remembered number, or undefined; storage can be missing or refuse. */
335
+ function storedSize(key) {
336
+ try {
337
+ const n = Number(localStorage.getItem(key));
338
+ return Number.isFinite(n) && n > 0 ? n : undefined;
339
+ } catch {
340
+ return undefined;
341
+ }
342
+ }
343
+ function storeSize(key, value) {
344
+ try {
345
+ if (value === undefined) localStorage.removeItem(key);
346
+ else localStorage.setItem(key, String(Math.round(value)));
347
+ } catch { /* the size still holds for this page */ }
348
+ }
349
+
350
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), Math.max(min, max));
351
+
352
+ // The pane's width, from its left edge. Desktop only: on a phone it is the
353
+ // whole screen.
354
+ const EXPLORER_WIDTH_KEY = "oharness.explorerWidth";
355
+ const EXPLORER_TREE_KEY = "oharness.explorerTreeWidth";
356
+ const EXPLORER_TREE_H_KEY = "oharness.explorerTreeHeight";
357
+
358
+ function explorerSetWidth(px) {
359
+ const split = document.getElementById("split").getBoundingClientRect().width;
360
+ const width = clamp(px, 360, split - 360);
361
+ explorer.style.setProperty("--explorer-width", width + "px");
362
+ return width;
363
+ }
364
+ function explorerSetTree(px) {
365
+ const body = document.getElementById("explorer-body").getBoundingClientRect();
366
+ if (isPhone()) {
367
+ const height = clamp(px, 80, body.height - 120);
368
+ explorer.style.setProperty("--explorer-tree-height", height + "px");
369
+ return height;
370
+ }
371
+ const width = clamp(px, 140, body.width - 200);
372
+ explorer.style.setProperty("--explorer-tree-width", width + "px");
373
+ return width;
374
+ }
375
+ {
376
+ const width = storedSize(EXPLORER_WIDTH_KEY);
377
+ if (width) explorer.style.setProperty("--explorer-width", width + "px");
378
+ const tree = storedSize(EXPLORER_TREE_KEY);
379
+ if (tree) explorer.style.setProperty("--explorer-tree-width", tree + "px");
380
+ const treeH = storedSize(EXPLORER_TREE_H_KEY);
381
+ if (treeH) explorer.style.setProperty("--explorer-tree-height", treeH + "px");
382
+ }
383
+
384
+ let explorerDragged;
385
+ dragHandle(document.getElementById("explorer-resizer"), {
386
+ axis: "x",
387
+ enabled: () => !isPhone(),
388
+ start: () => explorer.getBoundingClientRect().width,
389
+ move: (dx, dy, start) => { explorerDragged = explorerSetWidth(start - dx); },
390
+ end: () => storeSize(EXPLORER_WIDTH_KEY, explorerDragged),
391
+ reset: () => { explorer.style.removeProperty("--explorer-width"); storeSize(EXPLORER_WIDTH_KEY); },
392
+ });
393
+
394
+ const explorerSplit = document.getElementById("explorer-split");
395
+ let explorerTreeDragged;
396
+ // Beside the file on a desktop, above it on a phone: the same handle drags
397
+ // whichever way the two are stacked.
398
+ dragHandle(explorerSplit, {
399
+ get axis() { return isPhone() ? "y" : "x"; },
400
+ start: () => {
401
+ const rect = explorerTree.getBoundingClientRect();
402
+ return isPhone() ? rect.height : rect.width;
403
+ },
404
+ move: (dx, dy, start) => { explorerTreeDragged = explorerSetTree(start + (isPhone() ? dy : dx)); },
405
+ end: () => storeSize(isPhone() ? EXPLORER_TREE_H_KEY : EXPLORER_TREE_KEY, explorerTreeDragged),
406
+ reset: () => {
407
+ explorer.style.removeProperty(isPhone() ? "--explorer-tree-height" : "--explorer-tree-width");
408
+ storeSize(isPhone() ? EXPLORER_TREE_H_KEY : EXPLORER_TREE_KEY);
409
+ },
410
+ });
411
+ `;
412
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../../../src/page/client/files.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoZ9B,CAAC"}
@@ -11,6 +11,8 @@ import { ROUTINES } from "./routines.js";
11
11
  import { USAGE } from "./usage.js";
12
12
  import { PANELS } from "./panels.js";
13
13
  import { BROWSER } from "./browser.js";
14
+ import { FILES } from "./files.js";
15
+ import { TERMINAL } from "./terminal.js";
14
16
  import { SHELL } from "./shell.js";
15
17
  /**
16
18
  * The client script, assembled.
@@ -40,6 +42,8 @@ export const APP_SCRIPT = [
40
42
  ROUTINES,
41
43
  USAGE,
42
44
  PANELS,
45
+ FILES,
46
+ TERMINAL,
43
47
  BROWSER,
44
48
  SHELL,
45
49
  ].join("") + "\n";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/page/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,UAAU,GACrB;IACA,IAAI;IACJ,OAAO;IACP,UAAU;IACV,KAAK;IACL,MAAM;IACN,OAAO;IACP,QAAQ;IACR,SAAS;IACT,OAAO;IACP,QAAQ;IACR,KAAK;IACL,MAAM;IACN,OAAO;IACP,KAAK;CACJ,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/page/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,UAAU,GACrB;IACA,IAAI;IACJ,OAAO;IACP,UAAU;IACV,KAAK;IACL,MAAM;IACN,OAAO;IACP,QAAQ;IACR,SAAS;IACT,OAAO;IACP,QAAQ;IACR,KAAK;IACL,MAAM;IACN,KAAK;IACL,QAAQ;IACR,OAAO;IACP,KAAK;CACJ,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC"}
@@ -604,7 +604,7 @@ async function showMemoryPage(id) {
604
604
  * sessions have no reason to open. It keeps the row it came from lit, so the
605
605
  * sidebar still says where in the app this is.
606
606
  */
607
- const SUB_PANELS = { usage: "nav-settings", audit: "nav-settings", permissions: "nav-settings" };
607
+ const SUB_PANELS = { usage: "nav-settings", "usage-sessions": "nav-settings", audit: "nav-settings", permissions: "nav-settings" };
608
608
 
609
609
  /** Which panel the panel view is currently showing, sub-panels included. */
610
610
  let shownPanel = null;
@@ -197,7 +197,7 @@ async function showSettings() {
197
197
 
198
198
  // Rows are grouped by what they are about. Each group is filled as the page
199
199
  // is built and drawn at the end under its heading, and only when it has rows.
200
- const groups = { workspace: el("div"), sync: el("div"), memory: el("div"), activity: el("div"), advanced: el("div") };
200
+ const groups = { workspace: el("div"), sync: el("div"), memory: el("div"), activity: el("div"), advanced: el("div"), about: el("div") };
201
201
  let target = groups.workspace;
202
202
 
203
203
  // A click on a button inside a summary toggles the disclosure as well as
@@ -707,11 +707,29 @@ async function showSettings() {
707
707
  body.appendChild(status);
708
708
  body.appendChild(extensionHelp(ext));
709
709
  }
710
+ // The desktop app's version, with its release notes beside it. A row that
711
+ // is always there, because the menu bar is not where people look and the
712
+ // post-update hint is gone after one click.
713
+ const desktopApp = IS_DESKTOP && window.oharness.app;
714
+ if (desktopApp && desktopApp.version) {
715
+ target = groups.about;
716
+ const notes = el("button", "ghost", t("settings.releaseNotes"));
717
+ notes.type = "button";
718
+ notes.addEventListener("click", () => desktopApp.releaseNotes());
719
+ let check = null;
720
+ if (desktopApp.checkForUpdates) {
721
+ check = el("button", "ghost", t("settings.checkUpdates"));
722
+ check.type = "button";
723
+ check.addEventListener("click", () => desktopApp.checkForUpdates());
724
+ }
725
+ field(t("settings.version"), desktopApp.version, t("settings.releaseNotesHint"), check, notes);
726
+ }
710
727
  const sections = [
711
728
  ["settings.section.workspace", [groups.workspace]],
712
729
  ["settings.section.sync", [groups.sync, groups.memory]],
713
730
  ["settings.section.activity", [groups.activity]],
714
731
  ["settings.section.advanced", [groups.advanced]],
732
+ ["settings.section.about", [groups.about]],
715
733
  ];
716
734
  for (const [title, parts] of sections) {
717
735
  if (!parts.some((part) => part.childNodes.length)) continue;
@@ -1 +1 @@
1
- {"version":3,"file":"routines.js","sourceRoot":"","sources":["../../../src/page/client/routines.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kFAu4BgD,CAAC"}
1
+ {"version":3,"file":"routines.js","sourceRoot":"","sources":["../../../src/page/client/routines.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kFAy5BgD,CAAC"}
@@ -41,6 +41,7 @@ function repaintPanel() {
41
41
  else if (shownPanel === "nav-routines") void showRoutines();
42
42
  else if (shownPanel === "nav-settings") void showSettings();
43
43
  else if (shownPanel === "usage") void showUsage();
44
+ else if (shownPanel === "usage-sessions") void showUsageSessions();
44
45
  else if (shownPanel === "audit") void showAudit();
45
46
  else if (shownPanel === "permissions") void showPermissions();
46
47
  else if (shownPanel?.startsWith("nav-plugin-")) void showMemoryPage(shownPanel.slice("nav-plugin-".length));
@@ -648,7 +649,13 @@ void (async () => {
648
649
  // Reopen whatever the server still has running, so a reload does not silently
649
650
  // abandon turns that are still streaming in other tabs.
650
651
  const opened = await getJson("/open");
651
- const list = (opened.open || []).length ? opened.open : [{ id: state.sessionId, current: true }];
652
+ // With nothing open yet — a server just started — the one tab is the
653
+ // current session, and it needs its tree like any other: without it the tab
654
+ // lost its project name, its files and terminal buttons, and anything that
655
+ // reads a file from the project.
656
+ const list = (opened.open || []).length
657
+ ? opened.open
658
+ : [{ id: state.sessionId, title: state.title, cwd: state.cwd, current: true }];
652
659
  for (const entry of list) {
653
660
  const record = pane(entry.id);
654
661
  record.title = entry.title || record.title;
@@ -662,6 +669,10 @@ void (async () => {
662
669
  P = pane(activeId);
663
670
  await loadModels();
664
671
  applyState(state);
672
+ if (!P.cwd && state.cwd && state.sessionId === activeId) {
673
+ P.cwd = state.cwd;
674
+ paintTabs();
675
+ }
665
676
  const cwdEl = document.getElementById("empty-cwd");
666
677
  if (cwdEl) cwdEl.textContent = projectName(state.cwd || "");
667
678
 
@@ -1 +1 @@
1
- {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../../src/page/client/shell.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA2qBzB,CAAC"}
1
+ {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../../src/page/client/shell.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAsrBzB,CAAC"}
@@ -276,9 +276,17 @@ function projectHead(cwd, name, count, open) {
276
276
  closeRowMenu();
277
277
  void chooseProject(cwd);
278
278
  } }),
279
- menuItem(t("projects.rename"), { key: "n", run: () => {
279
+ menuItem(t(pinned ? "projects.unpin" : "projects.pin"), { key: "p", run: () => {
280
280
  closeRowMenu();
281
- void renameProjectUi(cwd, name);
281
+ void togglePin("project", cwd, !pinned);
282
+ } }),
283
+ menuItem(t("projects.files"), { key: "i", run: () => {
284
+ closeRowMenu();
285
+ void openExplorer(cwd, true);
286
+ } }),
287
+ menuItem(t("projects.terminal"), { key: "t", run: () => {
288
+ closeRowMenu();
289
+ void openTerminalIn(cwd, true);
282
290
  } }),
283
291
  menuItem(t(IS_MAC ? "projects.revealMac" : IS_WIN ? "projects.revealWin" : "projects.reveal"), { key: "f", run: () => {
284
292
  closeRowMenu();
@@ -290,9 +298,9 @@ function projectHead(cwd, name, count, open) {
290
298
  closeRowMenu();
291
299
  exportProjectUi(cwd);
292
300
  } }),
293
- menuItem(t(pinned ? "projects.unpin" : "projects.pin"), { key: "p", run: () => {
301
+ menuItem(t("projects.rename"), { key: "n", run: () => {
294
302
  closeRowMenu();
295
- void togglePin("project", cwd, !pinned);
303
+ void renameProjectUi(cwd, name);
296
304
  } }),
297
305
  ];
298
306
  // Forgetting a path is only offered where it can do something: this row
@@ -1 +1 @@
1
- {"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/page/client/state.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmvC9B,CAAC"}
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/page/client/state.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2vC9B,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The embedded terminal: a real shell per project, drawn by xterm.js in a
3
+ * panel under the conversation.
4
+ *
5
+ * One part of the client script; see ./index.ts for how the parts join.
6
+ */
7
+ export declare const TERMINAL: string;