claude-relay 2.3.0 → 2.3.1
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 +1 -0
- package/bin/cli.js +9 -2
- package/lib/config.js +3 -2
- package/lib/daemon.js +5 -1
- package/lib/pages.js +8 -1
- package/lib/project.js +0 -11
- package/lib/public/app.js +26 -11
- package/lib/public/css/filebrowser.css +30 -0
- package/lib/public/css/input.css +2 -0
- package/lib/public/css/messages.css +5 -1
- package/lib/public/index.html +2 -2
- package/lib/public/modules/terminal.js +73 -0
- package/lib/public/modules/tools.js +2 -0
- package/lib/server.js +4 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -133,6 +133,7 @@ Scan the QR code with your phone to connect instantly, or open the URL displayed
|
|
|
133
133
|
* **Send While Processing** - Queue messages without waiting for the current response to finish.
|
|
134
134
|
* **Sticky Todo Overlay** - TodoWrite tasks float as a progress bar while you scroll through the conversation.
|
|
135
135
|
* **Scroll Position Hold** - Reading earlier messages will not get interrupted by new content arriving.
|
|
136
|
+
* **RTL Text Support** - Automatic bidirectional text rendering for Arabic, Hebrew, and other RTL languages.
|
|
136
137
|
|
|
137
138
|
**Server and Security**
|
|
138
139
|
|
package/bin/cli.js
CHANGED
|
@@ -6,6 +6,13 @@ var path = require("path");
|
|
|
6
6
|
var { execSync, execFileSync, spawn } = require("child_process");
|
|
7
7
|
var qrcode = require("qrcode-terminal");
|
|
8
8
|
var net = require("net");
|
|
9
|
+
|
|
10
|
+
// Detect dev mode before loading config (env must be set before require)
|
|
11
|
+
var _isDev = process.argv[1] && path.basename(process.argv[1]) === "claude-relay-dev";
|
|
12
|
+
if (_isDev) {
|
|
13
|
+
process.env.CLAUDE_RELAY_HOME = path.join(os.homedir(), ".claude-relay-dev");
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
var { loadConfig, saveConfig, configPath, socketPath, logPath, ensureConfigDir, isDaemonAlive, isDaemonAliveAsync, generateSlug, clearStaleConfig, loadClayrc, saveClayrc, readCrashInfo } = require("../lib/config");
|
|
10
17
|
var { sendIPCCommand } = require("../lib/ipc");
|
|
11
18
|
var { generateAuthToken } = require("../lib/server");
|
|
@@ -22,7 +29,7 @@ function openUrl(url) {
|
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
var args = process.argv.slice(2);
|
|
25
|
-
var port = 2633;
|
|
32
|
+
var port = _isDev ? 2635 : 2633;
|
|
26
33
|
var useHttps = true;
|
|
27
34
|
var skipUpdate = false;
|
|
28
35
|
var debugMode = false;
|
|
@@ -467,7 +474,7 @@ function getAllIPs() {
|
|
|
467
474
|
|
|
468
475
|
function ensureCerts(ip) {
|
|
469
476
|
var homeDir = os.homedir();
|
|
470
|
-
var certDir = path.join(homeDir, ".claude-relay", "certs");
|
|
477
|
+
var certDir = path.join(process.env.CLAUDE_RELAY_HOME || path.join(homeDir, ".claude-relay"), "certs");
|
|
471
478
|
var keyPath = path.join(certDir, "key.pem");
|
|
472
479
|
var certPath = path.join(certDir, "cert.pem");
|
|
473
480
|
|
package/lib/config.js
CHANGED
|
@@ -3,7 +3,7 @@ var path = require("path");
|
|
|
3
3
|
var os = require("os");
|
|
4
4
|
var net = require("net");
|
|
5
5
|
|
|
6
|
-
var CONFIG_DIR = path.join(os.homedir(), ".claude-relay");
|
|
6
|
+
var CONFIG_DIR = process.env.CLAUDE_RELAY_HOME || path.join(os.homedir(), ".claude-relay");
|
|
7
7
|
var CLAYRC_PATH = path.join(os.homedir(), ".clayrc");
|
|
8
8
|
var CRASH_INFO_PATH = path.join(CONFIG_DIR, "crash.json");
|
|
9
9
|
|
|
@@ -13,7 +13,8 @@ function configPath() {
|
|
|
13
13
|
|
|
14
14
|
function socketPath() {
|
|
15
15
|
if (process.platform === "win32") {
|
|
16
|
-
|
|
16
|
+
var pipeName = process.env.CLAUDE_RELAY_HOME ? "claude-relay-dev-daemon" : "claude-relay-daemon";
|
|
17
|
+
return "\\\\.\\pipe\\" + pipeName;
|
|
17
18
|
}
|
|
18
19
|
return path.join(CONFIG_DIR, "daemon.sock");
|
|
19
20
|
}
|
package/lib/daemon.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// Polyfill Symbol.dispose/asyncDispose for Node 18 (used by claude-agent-sdk)
|
|
4
|
+
if (!Symbol.dispose) Symbol.dispose = Symbol("Symbol.dispose");
|
|
5
|
+
if (!Symbol.asyncDispose) Symbol.asyncDispose = Symbol("Symbol.asyncDispose");
|
|
6
|
+
|
|
3
7
|
var fs = require("fs");
|
|
4
8
|
var path = require("path");
|
|
5
9
|
var { loadConfig, saveConfig, socketPath, generateSlug, syncClayrc, writeCrashInfo, readCrashInfo, clearCrashInfo } = require("./config");
|
|
@@ -20,7 +24,7 @@ try {
|
|
|
20
24
|
var tlsOptions = null;
|
|
21
25
|
if (config.tls) {
|
|
22
26
|
var os = require("os");
|
|
23
|
-
var certDir = path.join(os.homedir(), ".claude-relay", "certs");
|
|
27
|
+
var certDir = path.join(process.env.CLAUDE_RELAY_HOME || path.join(os.homedir(), ".claude-relay"), "certs");
|
|
24
28
|
var keyPath = path.join(certDir, "key.pem");
|
|
25
29
|
var certPath = path.join(certDir, "cert.pem");
|
|
26
30
|
try {
|
package/lib/pages.js
CHANGED
|
@@ -695,8 +695,15 @@ function dashboardPageHtml(projects, version) {
|
|
|
695
695
|
'<div class="subtitle">Select a project</div>' +
|
|
696
696
|
'<div class="cards">' + cards + '</div>' +
|
|
697
697
|
'<div class="footer">v' + escapeHtml(version || "") + '</div>' +
|
|
698
|
+
'<style>' +
|
|
699
|
+
'.toast{position:fixed;top:20px;left:50%;transform:translateX(-50%);background:#3A3936;border:1px solid #DA7756;color:#E8E5DE;padding:12px 24px;border-radius:8px;font-size:14px;z-index:999;opacity:0;transition:opacity .3s}' +
|
|
700
|
+
'.toast.show{opacity:1}' +
|
|
701
|
+
'</style>' +
|
|
698
702
|
'<script>var s=window.matchMedia("(display-mode:standalone)").matches||navigator.standalone;' +
|
|
699
|
-
'if(s&&!localStorage.getItem("setup-done")){var t=/^100\\./.test(location.hostname);location.replace("/setup"+(t?"":"?mode=lan"));}
|
|
703
|
+
'if(s&&!localStorage.getItem("setup-done")){var t=/^100\\./.test(location.hostname);location.replace("/setup"+(t?"":"?mode=lan"));}' +
|
|
704
|
+
'var p=new URLSearchParams(location.search);var g=p.get("gone");' +
|
|
705
|
+
'if(g){history.replaceState(null,"","/");var d=document.createElement("div");d.className="toast";d.textContent="Project \\""+g+"\\" is no longer available";document.body.appendChild(d);' +
|
|
706
|
+
'requestAnimationFrame(function(){d.className="toast show"});setTimeout(function(){d.className="toast";setTimeout(function(){d.remove()},300)},5000);}</script>' +
|
|
700
707
|
'</body></html>';
|
|
701
708
|
}
|
|
702
709
|
|
package/lib/project.js
CHANGED
|
@@ -911,17 +911,6 @@ function createProjectContext(opts) {
|
|
|
911
911
|
if (clients.size === 0) {
|
|
912
912
|
stopFileWatch();
|
|
913
913
|
stopAllDirWatches();
|
|
914
|
-
// Abort all running queries when no clients are connected
|
|
915
|
-
var aborted = 0;
|
|
916
|
-
sm.sessions.forEach(function (session) {
|
|
917
|
-
if (session.isProcessing && session.abortController) {
|
|
918
|
-
try { session.abortController.abort(); } catch (e) {}
|
|
919
|
-
aborted++;
|
|
920
|
-
}
|
|
921
|
-
});
|
|
922
|
-
if (aborted > 0) {
|
|
923
|
-
console.log("[project:" + slug + "] No clients connected, aborted " + aborted + " active queries");
|
|
924
|
-
}
|
|
925
914
|
}
|
|
926
915
|
broadcastClientCount();
|
|
927
916
|
}
|
package/lib/public/app.js
CHANGED
|
@@ -185,6 +185,8 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
185
185
|
// isComposing -> modules/input.js
|
|
186
186
|
var reconnectTimer = null;
|
|
187
187
|
var reconnectDelay = 1000;
|
|
188
|
+
var disconnectNotifTimer = null;
|
|
189
|
+
var disconnectNotifShown = false;
|
|
188
190
|
var activityEl = null;
|
|
189
191
|
var currentMsgEl = null;
|
|
190
192
|
var currentFullText = "";
|
|
@@ -974,6 +976,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
974
976
|
div.dataset.turn = ++turnCounter;
|
|
975
977
|
var bubble = document.createElement("div");
|
|
976
978
|
bubble.className = "bubble";
|
|
979
|
+
bubble.dir = "auto";
|
|
977
980
|
|
|
978
981
|
if (images && images.length > 0) {
|
|
979
982
|
var imgRow = document.createElement("div");
|
|
@@ -1024,7 +1027,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1024
1027
|
currentMsgEl = document.createElement("div");
|
|
1025
1028
|
currentMsgEl.className = "msg-assistant";
|
|
1026
1029
|
currentMsgEl.dataset.turn = turnCounter;
|
|
1027
|
-
currentMsgEl.innerHTML = '<div class="md-content"></div>';
|
|
1030
|
+
currentMsgEl.innerHTML = '<div class="md-content" dir="auto"></div>';
|
|
1028
1031
|
addToMessages(currentMsgEl);
|
|
1029
1032
|
currentFullText = "";
|
|
1030
1033
|
}
|
|
@@ -1164,8 +1167,13 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1164
1167
|
|
|
1165
1168
|
ws.onopen = function () {
|
|
1166
1169
|
if (connectTimeoutId) { clearTimeout(connectTimeoutId); connectTimeoutId = null; }
|
|
1167
|
-
//
|
|
1168
|
-
if (
|
|
1170
|
+
// Cancel pending "connection lost" notification if reconnected quickly
|
|
1171
|
+
if (disconnectNotifTimer) {
|
|
1172
|
+
clearTimeout(disconnectNotifTimer);
|
|
1173
|
+
disconnectNotifTimer = null;
|
|
1174
|
+
}
|
|
1175
|
+
// Only show "restored" notification if "lost" was actually shown
|
|
1176
|
+
if (wasConnected && disconnectNotifShown && !document.hasFocus() && "serviceWorker" in navigator) {
|
|
1169
1177
|
navigator.serviceWorker.ready.then(function (reg) {
|
|
1170
1178
|
reg.showNotification("Claude Relay", {
|
|
1171
1179
|
body: "Server connection restored",
|
|
@@ -1173,6 +1181,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1173
1181
|
});
|
|
1174
1182
|
}).catch(function () {});
|
|
1175
1183
|
}
|
|
1184
|
+
disconnectNotifShown = false;
|
|
1176
1185
|
wasConnected = true;
|
|
1177
1186
|
setStatus("connected");
|
|
1178
1187
|
reconnectDelay = 1000;
|
|
@@ -1198,14 +1207,20 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1198
1207
|
setStatus("disconnected");
|
|
1199
1208
|
processing = false;
|
|
1200
1209
|
setActivity(null);
|
|
1201
|
-
//
|
|
1202
|
-
if (!
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1210
|
+
// Delay "connection lost" notification by 5s to suppress brief disconnects
|
|
1211
|
+
if (!disconnectNotifTimer) {
|
|
1212
|
+
disconnectNotifTimer = setTimeout(function () {
|
|
1213
|
+
disconnectNotifTimer = null;
|
|
1214
|
+
disconnectNotifShown = true;
|
|
1215
|
+
if (!document.hasFocus() && "serviceWorker" in navigator) {
|
|
1216
|
+
navigator.serviceWorker.ready.then(function (reg) {
|
|
1217
|
+
reg.showNotification("Claude Relay", {
|
|
1218
|
+
body: "Server connection lost",
|
|
1219
|
+
tag: "claude-disconnect",
|
|
1220
|
+
});
|
|
1221
|
+
}).catch(function () {});
|
|
1222
|
+
}
|
|
1223
|
+
}, 5000);
|
|
1209
1224
|
}
|
|
1210
1225
|
scheduleReconnect();
|
|
1211
1226
|
};
|
|
@@ -598,6 +598,36 @@
|
|
|
598
598
|
font-style: italic;
|
|
599
599
|
}
|
|
600
600
|
|
|
601
|
+
/* --- Terminal context menu --- */
|
|
602
|
+
.term-ctx-menu {
|
|
603
|
+
position: fixed;
|
|
604
|
+
background: var(--bg-alt);
|
|
605
|
+
border: 1px solid var(--border);
|
|
606
|
+
border-radius: 10px;
|
|
607
|
+
padding: 4px 0;
|
|
608
|
+
min-width: 160px;
|
|
609
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
|
|
610
|
+
z-index: 500;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.term-ctx-item {
|
|
614
|
+
display: flex;
|
|
615
|
+
align-items: center;
|
|
616
|
+
gap: 8px;
|
|
617
|
+
width: 100%;
|
|
618
|
+
padding: 8px 12px;
|
|
619
|
+
font-size: 13px;
|
|
620
|
+
color: var(--text-secondary);
|
|
621
|
+
background: none;
|
|
622
|
+
border: none;
|
|
623
|
+
font-family: inherit;
|
|
624
|
+
cursor: pointer;
|
|
625
|
+
transition: background 0.15s;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.term-ctx-item .lucide { width: 14px; height: 14px; flex-shrink: 0; }
|
|
629
|
+
.term-ctx-item:hover { background: rgba(255, 255, 255, 0.05); }
|
|
630
|
+
|
|
601
631
|
/* --- File Edit History --- */
|
|
602
632
|
|
|
603
633
|
.file-history-panel {
|
package/lib/public/css/input.css
CHANGED
|
@@ -130,6 +130,8 @@
|
|
|
130
130
|
word-break: break-word;
|
|
131
131
|
white-space: pre-wrap;
|
|
132
132
|
color: var(--text);
|
|
133
|
+
unicode-bidi: plaintext;
|
|
134
|
+
text-align: start;
|
|
133
135
|
}
|
|
134
136
|
|
|
135
137
|
/* --- Assistant message --- */
|
|
@@ -176,6 +178,8 @@
|
|
|
176
178
|
line-height: 1.7;
|
|
177
179
|
word-break: break-word;
|
|
178
180
|
color: var(--text);
|
|
181
|
+
unicode-bidi: plaintext;
|
|
182
|
+
text-align: start;
|
|
179
183
|
}
|
|
180
184
|
|
|
181
185
|
.md-content p { margin-bottom: 14px; }
|
|
@@ -282,7 +286,7 @@ pre:hover .code-copy-btn { opacity: 1; }
|
|
|
282
286
|
.md-content th, .md-content td {
|
|
283
287
|
border: 1px solid var(--border);
|
|
284
288
|
padding: 8px 12px;
|
|
285
|
-
text-align:
|
|
289
|
+
text-align: start;
|
|
286
290
|
}
|
|
287
291
|
.md-content th {
|
|
288
292
|
background: rgba(255, 255, 255, 0.04);
|
package/lib/public/index.html
CHANGED
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
</div>
|
|
140
140
|
</div>
|
|
141
141
|
<div id="notif-menu-wrap">
|
|
142
|
-
<button id="notif-btn" title="Notifications"><i data-lucide="
|
|
142
|
+
<button id="notif-btn" title="Notifications"><i data-lucide="bell"></i></button>
|
|
143
143
|
<div id="notif-menu" class="hidden">
|
|
144
144
|
<label class="notif-option" id="notif-push-row">
|
|
145
145
|
<span><i data-lucide="smartphone" style="width:14px;height:14px"></i> Push notifications</span>
|
|
@@ -259,7 +259,7 @@
|
|
|
259
259
|
<span class="context-mini-label" id="context-mini-label">0%</span>
|
|
260
260
|
</div>
|
|
261
261
|
<div id="image-preview-bar"></div>
|
|
262
|
-
<textarea id="input" rows="1" placeholder="Message Claude Code..." enterkeyhint="send"></textarea>
|
|
262
|
+
<textarea id="input" rows="1" placeholder="Message Claude Code..." enterkeyhint="send" dir="auto"></textarea>
|
|
263
263
|
<div id="input-bottom">
|
|
264
264
|
<div id="attach-wrap">
|
|
265
265
|
<button id="attach-btn" type="button" aria-label="Attach"><i data-lucide="plus"></i></button>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
2
2
|
import { closeSidebar } from './sidebar.js';
|
|
3
3
|
import { closeFileViewer } from './filebrowser.js';
|
|
4
|
+
import { copyToClipboard } from './utils.js';
|
|
4
5
|
|
|
5
6
|
var ctx;
|
|
6
7
|
var tabs = new Map(); // termId -> { id, title, exited, xterm, fitAddon, bodyEl }
|
|
@@ -11,6 +12,7 @@ var isTouchDevice = "ontouchstart" in window;
|
|
|
11
12
|
var viewportHandler = null;
|
|
12
13
|
var resizeObserver = null;
|
|
13
14
|
var toolbarBound = false;
|
|
15
|
+
var termCtxMenu = null;
|
|
14
16
|
|
|
15
17
|
// --- Init ---
|
|
16
18
|
export function initTerminal(_ctx) {
|
|
@@ -239,6 +241,11 @@ function createXtermForTab(tab) {
|
|
|
239
241
|
}
|
|
240
242
|
});
|
|
241
243
|
|
|
244
|
+
// Right-click context menu
|
|
245
|
+
bodyEl.addEventListener("contextmenu", function (e) {
|
|
246
|
+
showTermCtxMenu(e, tab);
|
|
247
|
+
});
|
|
248
|
+
|
|
242
249
|
tab.xterm = xterm;
|
|
243
250
|
tab.fitAddon = fitAddon;
|
|
244
251
|
tab.bodyEl = bodyEl;
|
|
@@ -527,6 +534,72 @@ export function resetTerminals() {
|
|
|
527
534
|
renderTabBar();
|
|
528
535
|
}
|
|
529
536
|
|
|
537
|
+
// --- Terminal context menu ---
|
|
538
|
+
function closeTermCtxMenu() {
|
|
539
|
+
if (termCtxMenu) {
|
|
540
|
+
termCtxMenu.remove();
|
|
541
|
+
termCtxMenu = null;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function showTermCtxMenu(e, tab) {
|
|
546
|
+
e.preventDefault();
|
|
547
|
+
e.stopPropagation();
|
|
548
|
+
closeTermCtxMenu();
|
|
549
|
+
|
|
550
|
+
var menu = document.createElement("div");
|
|
551
|
+
menu.className = "term-ctx-menu";
|
|
552
|
+
|
|
553
|
+
// Copy
|
|
554
|
+
var copyItem = document.createElement("button");
|
|
555
|
+
copyItem.className = "term-ctx-item";
|
|
556
|
+
copyItem.innerHTML = iconHtml("clipboard-copy") + " <span>Copy Terminal</span>";
|
|
557
|
+
copyItem.addEventListener("click", function (ev) {
|
|
558
|
+
ev.stopPropagation();
|
|
559
|
+
closeTermCtxMenu();
|
|
560
|
+
if (!tab.xterm) return;
|
|
561
|
+
tab.xterm.selectAll();
|
|
562
|
+
var text = tab.xterm.getSelection();
|
|
563
|
+
tab.xterm.clearSelection();
|
|
564
|
+
if (text) copyToClipboard(text);
|
|
565
|
+
});
|
|
566
|
+
menu.appendChild(copyItem);
|
|
567
|
+
|
|
568
|
+
// Clear
|
|
569
|
+
var clearItem = document.createElement("button");
|
|
570
|
+
clearItem.className = "term-ctx-item";
|
|
571
|
+
clearItem.innerHTML = iconHtml("trash-2") + " <span>Clear Terminal</span>";
|
|
572
|
+
clearItem.addEventListener("click", function (ev) {
|
|
573
|
+
ev.stopPropagation();
|
|
574
|
+
closeTermCtxMenu();
|
|
575
|
+
if (!tab.xterm) return;
|
|
576
|
+
tab.xterm.clear();
|
|
577
|
+
});
|
|
578
|
+
menu.appendChild(clearItem);
|
|
579
|
+
|
|
580
|
+
// Position at mouse cursor
|
|
581
|
+
menu.style.left = e.clientX + "px";
|
|
582
|
+
menu.style.top = e.clientY + "px";
|
|
583
|
+
document.body.appendChild(menu);
|
|
584
|
+
|
|
585
|
+
// Clamp to viewport
|
|
586
|
+
var rect = menu.getBoundingClientRect();
|
|
587
|
+
if (rect.right > window.innerWidth) {
|
|
588
|
+
menu.style.left = (window.innerWidth - rect.width - 4) + "px";
|
|
589
|
+
}
|
|
590
|
+
if (rect.bottom > window.innerHeight) {
|
|
591
|
+
menu.style.top = (window.innerHeight - rect.height - 4) + "px";
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
termCtxMenu = menu;
|
|
595
|
+
refreshIcons();
|
|
596
|
+
|
|
597
|
+
// Close on outside click (next tick to avoid immediate trigger)
|
|
598
|
+
setTimeout(function () {
|
|
599
|
+
document.addEventListener("click", closeTermCtxMenu, { once: true });
|
|
600
|
+
}, 0);
|
|
601
|
+
}
|
|
602
|
+
|
|
530
603
|
// --- Mobile toolbar ---
|
|
531
604
|
var KEY_MAP = {
|
|
532
605
|
tab: "\t",
|
|
@@ -258,6 +258,7 @@ function permissionInputSummary(toolName, input) {
|
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
export function renderPermissionRequest(requestId, toolName, toolInput, decisionReason) {
|
|
261
|
+
if (pendingPermissions[requestId]) return;
|
|
261
262
|
ctx.finalizeAssistantBlock();
|
|
262
263
|
stopThinking();
|
|
263
264
|
|
|
@@ -353,6 +354,7 @@ export function renderPermissionRequest(requestId, toolName, toolInput, decision
|
|
|
353
354
|
}
|
|
354
355
|
|
|
355
356
|
function renderPlanPermission(requestId) {
|
|
357
|
+
if (pendingPermissions[requestId]) return;
|
|
356
358
|
var container = document.createElement("div");
|
|
357
359
|
container.className = "permission-container plan-permission";
|
|
358
360
|
container.dataset.requestId = requestId;
|
package/lib/server.js
CHANGED
|
@@ -250,7 +250,8 @@ function createServer(opts) {
|
|
|
250
250
|
res.end(pinPage);
|
|
251
251
|
return;
|
|
252
252
|
}
|
|
253
|
-
|
|
253
|
+
var hasGoneParam = req.url.indexOf("gone=") !== -1;
|
|
254
|
+
if (projects.size === 1 && !hasGoneParam) {
|
|
254
255
|
var slug = projects.keys().next().value;
|
|
255
256
|
res.writeHead(302, { "Location": "/p/" + slug + "/" });
|
|
256
257
|
res.end();
|
|
@@ -297,8 +298,8 @@ function createServer(opts) {
|
|
|
297
298
|
|
|
298
299
|
var ctx = projects.get(slug);
|
|
299
300
|
if (!ctx) {
|
|
300
|
-
res.writeHead(
|
|
301
|
-
res.end(
|
|
301
|
+
res.writeHead(302, { "Location": "/?gone=" + encodeURIComponent(slug) });
|
|
302
|
+
res.end();
|
|
302
303
|
return;
|
|
303
304
|
}
|
|
304
305
|
|