@duckmind/dm-darwin-x64 0.66.7 → 0.66.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 CHANGED
@@ -4,7 +4,7 @@
4
4
  No UI overhead. Just plan, run, and get things done.
5
5
  </p>
6
6
 
7
- ## What's new in 0.66.7
7
+ ## What's new in 0.66.8
8
8
 
9
- What's new: Web control, monitoring, and multi-agent DM. Use `/tty start` or `dm --host 0.0.0.0`.
9
+ What’s new: Web control, monitoring, multi-agent DM, and terminal text selection, copy & paste on web tty. Start with /tty start or dm --host 0.0.0.0.
10
10
 
@@ -2307,8 +2307,8 @@
2307
2307
  "web/client.js"
2308
2308
  ],
2309
2309
  "file_count": 7,
2310
- "before_bytes": 86145,
2311
- "after_bytes": 83320,
2310
+ "before_bytes": 92857,
2311
+ "after_bytes": 90217,
2312
2312
  "runtime_specifier_rewrites": 3,
2313
2313
  "runtime_filename_rewrites": 6,
2314
2314
  "declaration_files_removed": 0
@@ -27,6 +27,11 @@ const historyText = document.querySelector("#history-text");
27
27
  const historyLines = document.querySelector("#history-lines");
28
28
  const historyCopy = document.querySelector("#history-copy");
29
29
  const copyStatus = document.querySelector("#copy-status");
30
+ const outputMenu = document.querySelector("#output-menu");
31
+ const outputPaste = document.querySelector("#output-paste");
32
+ const outputSelect = document.querySelector("#output-select");
33
+ const outputSelectAll = document.querySelector("#output-select-all");
34
+ const outputCopy = document.querySelector("#output-copy");
30
35
  const accessory = document.querySelector("#accessory");
31
36
  const ctrlButton = document.querySelector("#ctrl");
32
37
  const altButton = document.querySelector("#alt");
@@ -101,6 +106,9 @@ let streamHistory = "";
101
106
  let historyEscape = "text";
102
107
  let historyFrozen = false;
103
108
  let selectedForCopy = null;
109
+ let outputMenuTimer;
110
+ let outputMenuPointer;
111
+ let outputMenuOpen = false;
104
112
  let displayedHistoryLines = MAX_HISTORY_LINES;
105
113
  const TOKEN_STORAGE_KEY = "dm-tty-access-token";
106
114
  try {
@@ -160,7 +168,73 @@ function clearOutputSelection() {
160
168
  if (outputSelection())
161
169
  document.getSelection().removeAllRanges();
162
170
  }
171
+ function closeOutputMenu() {
172
+ clearTimeout(outputMenuTimer);
173
+ outputMenuTimer = undefined;
174
+ outputMenuPointer = undefined;
175
+ outputMenuOpen = false;
176
+ outputMenu.hidden = true;
177
+ }
178
+ function updateOutputMenu() {
179
+ const selected = Boolean(outputSelection());
180
+ outputCopy.hidden = !selected;
181
+ outputCopy.disabled = !selected;
182
+ }
183
+ function showOutputMenu(clientX, clientY) {
184
+ if (!openHistory())
185
+ return false;
186
+ const bounds = terminalContainer.getBoundingClientRect();
187
+ const width = outputMenu.offsetWidth || 148;
188
+ const height = outputMenu.offsetHeight || 170;
189
+ const left = Math.max(4, Math.min(clientX - bounds.left, bounds.width - width - 4));
190
+ const top = Math.max(4, Math.min(clientY - bounds.top, bounds.height - height - 4));
191
+ outputMenu.style.left = `${left}px`;
192
+ outputMenu.style.top = `${top}px`;
193
+ outputMenu.hidden = false;
194
+ outputMenuOpen = true;
195
+ updateOutputMenu();
196
+ return true;
197
+ }
198
+ function pointRange(clientX, clientY) {
199
+ const range = document.caretRangeFromPoint?.(clientX, clientY);
200
+ if (range?.startContainer && historyText.contains(range.startContainer))
201
+ return range;
202
+ const position = document.caretPositionFromPoint?.(clientX, clientY);
203
+ if (!position?.offsetNode || !historyText.contains(position.offsetNode))
204
+ return null;
205
+ const fallback = document.createRange();
206
+ fallback.setStart(position.offsetNode, position.offset);
207
+ fallback.setEnd(position.offsetNode, position.offset);
208
+ return fallback;
209
+ }
210
+ function selectOutputAtPoint(clientX, clientY) {
211
+ if (!openHistory() || !historyText.textContent)
212
+ return false;
213
+ const point = pointRange(clientX, clientY);
214
+ if (!point || point.startContainer.nodeType !== 3)
215
+ return selectAllOutput();
216
+ const text = point.startContainer.textContent ?? "";
217
+ let start = Math.max(0, Math.min(point.startOffset, text.length));
218
+ let end = start;
219
+ while (start > 0 && !/\s/u.test(text[start - 1]))
220
+ start--;
221
+ while (end < text.length && !/\s/u.test(text[end]))
222
+ end++;
223
+ if (start === end)
224
+ end = Math.min(text.length, start + 1);
225
+ const range = document.createRange();
226
+ range.setStart(point.startContainer, start);
227
+ range.setEnd(point.startContainer, end);
228
+ const selection = document.getSelection?.();
229
+ if (!selection)
230
+ return false;
231
+ selection.removeAllRanges();
232
+ selection.addRange(range);
233
+ historyFrozen = true;
234
+ return Boolean(outputSelection());
235
+ }
163
236
  function closeHistory() {
237
+ closeOutputMenu();
164
238
  clearOutputSelection();
165
239
  historyPanel.hidden = true;
166
240
  historyFrozen = false;
@@ -259,6 +333,7 @@ function selectAllOutput() {
259
333
  const selection = document.getSelection();
260
334
  selection.removeAllRanges();
261
335
  selection.addRange(range);
336
+ updateOutputMenu();
262
337
  return true;
263
338
  }
264
339
  async function copyOutputText(selectedText = null) {
@@ -289,12 +364,108 @@ async function copyOutputText(selectedText = null) {
289
364
  copyStatus.textContent = copied ? "Copied output" : "Select text and use the browser Copy menu";
290
365
  }
291
366
  }
292
- historyText.addEventListener("pointerdown", () => {
367
+ async function pasteOutputText() {
368
+ closeOutputMenu();
369
+ const generation = connectionGeneration;
370
+ if (!navigator.clipboard?.readText) {
371
+ copyStatus.textContent = "Clipboard unavailable; use the keyboard Paste action";
372
+ return;
373
+ }
374
+ let text;
375
+ try {
376
+ text = await navigator.clipboard.readText();
377
+ } catch {
378
+ if (generation === connectionGeneration)
379
+ copyStatus.textContent = "Clipboard permission denied";
380
+ return;
381
+ }
382
+ if (generation !== connectionGeneration || pendingSessionId)
383
+ return;
384
+ if (!text) {
385
+ copyStatus.textContent = "Clipboard is empty";
386
+ return;
387
+ }
388
+ if (!authenticated || socket?.readyState !== WebSocket.OPEN) {
389
+ copyStatus.textContent = "Connect before pasting";
390
+ return;
391
+ }
392
+ closeHistory();
393
+ const start = command.selectionStart ?? command.value.length;
394
+ const end = command.selectionEnd ?? start;
395
+ if (terminal.buffer.active.type !== "alternate" && !remoteEditing) {
396
+ remoteEditing = false;
397
+ speechDraft = true;
398
+ command.setRangeText(text, start, end, "end");
399
+ lastNativeValue = command.value;
400
+ focusCommand();
401
+ positionCommand();
402
+ copyStatus.textContent = "Pasted into draft; press Enter to send";
403
+ return;
404
+ }
405
+ const data = terminal.modes.bracketedPasteMode ? `\x1B[200~${text.replace(/\r?\n/g, "\r")}\x1B[201~` : text;
406
+ if (send({ type: "input", data }))
407
+ copyStatus.textContent = "Pasted";
408
+ }
409
+ function beginOutputLongPress(event) {
410
+ if (event.pointerType && event.pointerType !== "touch" && event.pointerType !== "pen")
411
+ return;
293
412
  historyFrozen = true;
413
+ clearTimeout(outputMenuTimer);
414
+ outputMenuPointer = { x: event.clientX ?? 0, y: event.clientY ?? 0 };
415
+ outputMenuTimer = setTimeout(() => {
416
+ if (!outputMenuPointer)
417
+ return;
418
+ const { x, y } = outputMenuPointer;
419
+ outputMenuPointer = undefined;
420
+ selectOutputAtPoint(x, y);
421
+ showOutputMenu(x, y);
422
+ }, 500);
423
+ }
424
+ function cancelOutputLongPress(event) {
425
+ if (!outputMenuPointer)
426
+ return;
427
+ if (event?.type === "pointermove") {
428
+ if (Math.hypot(event.clientX - outputMenuPointer.x, event.clientY - outputMenuPointer.y) > 10)
429
+ closeOutputMenu();
430
+ return;
431
+ }
432
+ if (event && Math.hypot(event.clientX - outputMenuPointer.x, event.clientY - outputMenuPointer.y) > 10)
433
+ closeOutputMenu();
434
+ else if (!outputMenuOpen) {
435
+ clearTimeout(outputMenuTimer);
436
+ outputMenuTimer = undefined;
437
+ outputMenuPointer = undefined;
438
+ }
439
+ }
440
+ historyText.addEventListener("pointerdown", beginOutputLongPress);
441
+ historyText.addEventListener("pointermove", cancelOutputLongPress);
442
+ historyText.addEventListener("pointerup", cancelOutputLongPress);
443
+ historyText.addEventListener("pointercancel", closeOutputMenu);
444
+ terminalContainer.addEventListener("pointerdown", (event) => {
445
+ if (historyPanel.hidden)
446
+ beginOutputLongPress(event);
447
+ });
448
+ terminalContainer.addEventListener("pointermove", cancelOutputLongPress);
449
+ terminalContainer.addEventListener("pointerup", cancelOutputLongPress);
450
+ terminalContainer.addEventListener("pointercancel", closeOutputMenu);
451
+ terminalContainer.addEventListener("contextmenu", (event) => {
452
+ if (!historyPanel.hidden)
453
+ return;
454
+ event.preventDefault();
455
+ selectOutputAtPoint(event.clientX, event.clientY);
456
+ showOutputMenu(event.clientX, event.clientY);
457
+ });
458
+ historyText.addEventListener("contextmenu", (event) => {
459
+ event.preventDefault();
460
+ selectOutputAtPoint(event.clientX, event.clientY);
461
+ showOutputMenu(event.clientX, event.clientY);
294
462
  });
295
463
  document.addEventListener("selectionchange", () => {
296
- if (!historyPanel.hidden && outputSelection())
464
+ if (outputMenuOpen)
465
+ updateOutputMenu();
466
+ if (!historyPanel.hidden && outputSelection()) {
297
467
  historyFrozen = true;
468
+ }
298
469
  });
299
470
  historyPanel.addEventListener("keydown", (event) => {
300
471
  if (event.key.toLowerCase() === "a" && (event.metaKey || event.ctrlKey)) {
@@ -318,6 +489,29 @@ historyCopy.addEventListener("click", () => {
318
489
  selectedForCopy = null;
319
490
  return copyOutputText(selected);
320
491
  });
492
+ outputSelect.addEventListener("click", () => {
493
+ closeOutputMenu();
494
+ });
495
+ outputSelectAll.addEventListener("click", () => {
496
+ selectAllOutput();
497
+ });
498
+ outputMenu.addEventListener("pointerdown", (event) => {
499
+ event.preventDefault();
500
+ });
501
+ outputCopy.addEventListener("click", () => {
502
+ const selected = outputSelection();
503
+ closeOutputMenu();
504
+ return copyOutputText(selected);
505
+ });
506
+ outputPaste.addEventListener("click", pasteOutputText);
507
+ document.addEventListener("pointerdown", (event) => {
508
+ if (outputMenuOpen && !outputMenu.contains(event.target))
509
+ closeOutputMenu();
510
+ });
511
+ document.addEventListener("keydown", (event) => {
512
+ if (event.key === "Escape")
513
+ closeOutputMenu();
514
+ });
321
515
  function updateModifiers() {
322
516
  ctrlButton.textContent = ctrl === 2 ? "CTRL \uD83D\uDD12" : ctrl ? "CTRL" : "Ctrl";
323
517
  ctrlButton.setAttribute("aria-pressed", String(ctrl > 0));
@@ -60,6 +60,12 @@
60
60
  <span id="copy-status" role="status" aria-live="polite"></span>
61
61
  <pre id="history-text"></pre>
62
62
  </section>
63
+ <div id="output-menu" class="output-menu" role="menu" aria-label="Terminal text actions" hidden>
64
+ <button id="output-paste" type="button" role="menuitem">Paste</button>
65
+ <button id="output-select" type="button" role="menuitem">Select</button>
66
+ <button id="output-select-all" type="button" role="menuitem">Select All</button>
67
+ <button id="output-copy" type="button" role="menuitem" hidden>Copy</button>
68
+ </div>
63
69
  <form id="command-form" autocomplete="off" hidden>
64
70
  <label class="sr-only" for="command">Shell command (Vietnamese IME)</label>
65
71
  <textarea id="command" rows="1" wrap="off" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="Shell command (Vietnamese IME)"></textarea>
@@ -20,6 +20,10 @@ input:focus, textarea:focus, select:focus, button:focus-visible, summary:focus-v
20
20
  #history-lines { width: 72px; padding: 3px 5px; font-size: 16px; }
21
21
  #history-text { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; user-select: text; -webkit-user-select: text; font: 14px/1.4 "courier-new", courier, monospace; }
22
22
  ::selection { color: #fff; background: #5b21b6; }
23
+ .output-menu { position: absolute; z-index: 6; display: grid; min-width: 148px; gap: 2px; padding: 4px; border: 1px solid #64748b; border-radius: 7px; background: #1e293b; box-shadow: 0 8px 24px #0009; }
24
+ .output-menu[hidden] { display: none; }
25
+ .output-menu button { min-height: 38px; padding: 7px 10px; border: 0; border-radius: 4px; background: transparent; text-align: left; }
26
+ .output-menu button:hover, .output-menu button:focus-visible { background: #334155; }
23
27
  summary { cursor: pointer; }
24
28
  #session-menu { flex: none; }
25
29
  #session-menu > summary { list-style: none; width: 40px; height: 34px; text-align: center; font-size: 24px; line-height: 30px; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckmind/dm-darwin-x64",
3
- "version": "0.66.7",
3
+ "version": "0.66.8",
4
4
  "description": "DuckMind (dm) binary payload for darwin x64",
5
5
  "license": "MIT",
6
6
  "os": [