@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
|
+
## What's new in 0.66.8
|
|
8
8
|
|
|
9
|
-
What
|
|
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":
|
|
2311
|
-
"after_bytes":
|
|
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
|
-
|
|
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 (
|
|
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; }
|