@duckmind/dm-darwin-x64 0.66.6 → 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 +2 -2
- package/extensions/.dm-extensions.json +6 -6
- package/extensions/dm-lens/clients/runtime-session.js +1 -1
- package/extensions/dm-mcp-adapter/config.js +24 -0
- package/extensions/dm-tty/web/client.js +198 -2
- package/extensions/dm-tty/web/index.html +6 -0
- package/extensions/dm-tty/web/style.css +4 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
|
|
@@ -1988,8 +1988,8 @@
|
|
|
1988
1988
|
"tools/symbol-search.js"
|
|
1989
1989
|
],
|
|
1990
1990
|
"file_count": 1042,
|
|
1991
|
-
"before_bytes":
|
|
1992
|
-
"after_bytes":
|
|
1991
|
+
"before_bytes": 19610076,
|
|
1992
|
+
"after_bytes": 9249382,
|
|
1993
1993
|
"runtime_specifier_rewrites": 12,
|
|
1994
1994
|
"runtime_filename_rewrites": 502,
|
|
1995
1995
|
"declaration_files_removed": 3
|
|
@@ -2189,8 +2189,8 @@
|
|
|
2189
2189
|
"utils.js"
|
|
2190
2190
|
],
|
|
2191
2191
|
"file_count": 141,
|
|
2192
|
-
"before_bytes":
|
|
2193
|
-
"after_bytes":
|
|
2192
|
+
"before_bytes": 2450535,
|
|
2193
|
+
"after_bytes": 2070087,
|
|
2194
2194
|
"runtime_specifier_rewrites": 57,
|
|
2195
2195
|
"runtime_filename_rewrites": 88,
|
|
2196
2196
|
"declaration_files_removed": 48
|
|
@@ -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
|
|
@@ -135,7 +135,7 @@ function notifyStartupWarmSkip(args) {
|
|
|
135
135
|
reason: `${reason}; ${bound.name}=${bound.value}`,
|
|
136
136
|
metadata: { reason, [bound.name]: bound.value }
|
|
137
137
|
});
|
|
138
|
-
args.notify(`\uD83D\uDCE6
|
|
138
|
+
args.notify(`\uD83D\uDCE6 Background warm scans skipped for this project size (heavy scans, TODO scan, LSP pre-warm) (${bound.hint}).`, "info");
|
|
139
139
|
}
|
|
140
140
|
async function demandBootstrapDeps(deps, reason) {
|
|
141
141
|
const clients = await deps.bootstrap.request(reason);
|
|
@@ -151,6 +151,27 @@ function legacyMcpConfigHasContent(filePath) {
|
|
|
151
151
|
return false;
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
+
function migrateLegacyMcpConfig(source, target) {
|
|
155
|
+
if (!existsSync(source) || existsSync(target))
|
|
156
|
+
return;
|
|
157
|
+
try {
|
|
158
|
+
const sourceStat = lstatSync(source);
|
|
159
|
+
if (!sourceStat.isFile())
|
|
160
|
+
return;
|
|
161
|
+
const raw = readFileSync(source, "utf-8");
|
|
162
|
+
const parsed = parseJsonWithComments(raw);
|
|
163
|
+
if (!isRecord(parsed))
|
|
164
|
+
return;
|
|
165
|
+
mkdirSync(dirname(target), { recursive: true, mode: 448 });
|
|
166
|
+
const temporary = `${target}.tmp-${process.pid}-${Date.now()}`;
|
|
167
|
+
writeFileSync(temporary, raw, { encoding: "utf-8", mode: sourceStat.mode & 511 });
|
|
168
|
+
renameSync(temporary, target);
|
|
169
|
+
rmSync(source);
|
|
170
|
+
return `Migrated ${source} to ${target}.`;
|
|
171
|
+
} catch {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
154
175
|
export function getLegacyMcpMigrationNotices(cwd = process.cwd(), overridePath) {
|
|
155
176
|
if (piMcpConfigEnabled) {
|
|
156
177
|
return getConfigSources(overridePath, cwd).flatMap((source) => {
|
|
@@ -179,6 +200,9 @@ export function getLegacyMcpMigrationNotices(cwd = process.cwd(), overridePath)
|
|
|
179
200
|
return candidates.flatMap(([source, target]) => {
|
|
180
201
|
if (resolve(source) === explicitPath || !legacyMcpConfigHasContent(source))
|
|
181
202
|
return [];
|
|
203
|
+
const migrated = migrateLegacyMcpConfig(source, target);
|
|
204
|
+
if (migrated)
|
|
205
|
+
return [migrated];
|
|
182
206
|
const fix = existsSync(target) ? `Merge ${source} into ${target}, then remove ${source}.` : `Move it with: mv ${JSON.stringify(source)} ${JSON.stringify(target)}`;
|
|
183
207
|
return [`dm-mcp-adapter no longer reads ${source}. ${fix}`];
|
|
184
208
|
});
|
|
@@ -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));
|
|
@@ -638,6 +832,8 @@ terminal.attachCustomKeyEventHandler((event) => {
|
|
|
638
832
|
return false;
|
|
639
833
|
if (event.type !== "keydown" || !ctrl && !alt)
|
|
640
834
|
return true;
|
|
835
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "c" && terminal.hasSelection())
|
|
836
|
+
return true;
|
|
641
837
|
if (event.metaKey || event.ctrlKey && event.shiftKey && ["c", "v"].includes(event.key.toLowerCase()))
|
|
642
838
|
return true;
|
|
643
839
|
event.preventDefault();
|
|
@@ -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; }
|