@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 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.6
7
+ ## What's new in 0.66.8
8
8
 
9
- Remote control and monitoring of DM through a web terminal, plus multi-agent DM collaboration. Run `/tty start` inside DM, or start DM with `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
 
@@ -1988,8 +1988,8 @@
1988
1988
  "tools/symbol-search.js"
1989
1989
  ],
1990
1990
  "file_count": 1042,
1991
- "before_bytes": 19610078,
1992
- "after_bytes": 9249384,
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": 2449681,
2193
- "after_bytes": 2069292,
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": 86031,
2311
- "after_bytes": 83201,
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 Project-size limits disabled background warm scans (heavy scans, TODO scan, LSP pre-warm) (${bound.hint}).`, "warning");
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
- 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));
@@ -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; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckmind/dm-darwin-x64",
3
- "version": "0.66.6",
3
+ "version": "0.66.8",
4
4
  "description": "DuckMind (dm) binary payload for darwin x64",
5
5
  "license": "MIT",
6
6
  "os": [