Skip to content

Commit 7bdfd3d

Browse files
ytsparclaude
andcommitted
fix(sweetlink): bind --serve to 0.0.0.0 + clipboard fallback for file://
Fixes two issues from hook feedback: 1. report --serve: Now binds to 0.0.0.0 (all interfaces) instead of loopback. Prints both local and network URLs with LAN IP auto-detection. Other devices on the network can now access the served viewer. 2. Viewer Copy Report: Falls back to document.execCommand('copy') when navigator.clipboard is unavailable (file:// contexts, non-HTTPS). The downloaded standalone viewer now works offline for clipboard copy. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8e6d86f commit 7bdfd3d

2 files changed

Lines changed: 33 additions & 4 deletions

File tree

packages/sweetlink/src/cli/sweetlink.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2874,9 +2874,22 @@ async function handleStatusCommand(): Promise<StatusData> {
28742874
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
28752875
res.end(viewerContent);
28762876
});
2877-
server.listen(port, () => {
2878-
const url = `http://localhost:${port}`;
2879-
console.log(`[Sweetlink] Serving viewer at ${url}`);
2877+
server.listen(port, '0.0.0.0', () => {
2878+
const os = require('os');
2879+
const nets = os.networkInterfaces();
2880+
let lanIp = 'localhost';
2881+
for (const name of Object.keys(nets)) {
2882+
for (const net of nets[name]) {
2883+
if (net.family === 'IPv4' && !net.internal) {
2884+
lanIp = net.address;
2885+
break;
2886+
}
2887+
}
2888+
if (lanIp !== 'localhost') break;
2889+
}
2890+
console.log(`[Sweetlink] Serving viewer at:`);
2891+
console.log(` Local: http://localhost:${port}`);
2892+
console.log(` Network: http://${lanIp}:${port}`);
28802893
console.log(' Press Ctrl+C to stop.');
28812894
});
28822895
// Keep running until Ctrl+C

packages/sweetlink/src/daemon/viewer.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,10 +524,26 @@ document.addEventListener('keydown', function(e) {
524524
525525
// Start at first action
526526
// Share buttons
527+
// Clipboard helper — falls back to execCommand for file:// contexts
528+
function copyToClipboard(text) {
529+
if (navigator.clipboard && window.isSecureContext) {
530+
return navigator.clipboard.writeText(text);
531+
}
532+
// Fallback for file:// or non-secure contexts
533+
var textarea = document.createElement('textarea');
534+
textarea.value = text;
535+
textarea.style.cssText = 'position:fixed;left:-9999px';
536+
document.body.appendChild(textarea);
537+
textarea.select();
538+
var ok = document.execCommand('copy');
539+
document.body.removeChild(textarea);
540+
return ok ? Promise.resolve() : Promise.reject(new Error('execCommand failed'));
541+
}
542+
527543
var btnCopyReport = document.getElementById('btn-copy-report');
528544
if (btnCopyReport) {
529545
btnCopyReport.addEventListener('click', function() {
530-
navigator.clipboard.writeText(summaryReport).then(function() {
546+
copyToClipboard(summaryReport).then(function() {
531547
btnCopyReport.textContent = 'Copied!';
532548
btnCopyReport.classList.add('copied');
533549
setTimeout(function() {

0 commit comments

Comments
 (0)