clay-server 2.9.0 → 2.9.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/lib/pages.js +18 -17
- package/lib/project.js +13 -26
- package/lib/public/app.js +247 -70
- package/lib/public/css/base.css +3 -0
- package/lib/public/css/filebrowser.css +8 -1
- package/lib/public/css/home-hub.css +1 -1
- package/lib/public/css/input.css +4 -0
- package/lib/public/css/menus.css +0 -40
- package/lib/public/css/messages.css +1 -0
- package/lib/public/css/mobile-nav.css +2 -2
- package/lib/public/css/overlays.css +175 -16
- package/lib/public/css/scheduler.css +1 -0
- package/lib/public/css/server-settings.css +37 -34
- package/lib/public/css/title-bar.css +0 -7
- package/lib/public/index.html +62 -21
- package/lib/public/manifest.json +2 -2
- package/lib/public/modules/notifications.js +15 -103
- package/lib/public/modules/scheduler.js +55 -48
- package/lib/public/modules/server-settings.js +13 -4
- package/package.json +1 -1
package/lib/pages.js
CHANGED
|
@@ -22,11 +22,10 @@ function pinPageHtml() {
|
|
|
22
22
|
'if(d.ok){location.reload();return}' +
|
|
23
23
|
'if(d.locked){for(var i=0;i<boxes.length;i++)boxes[i].disabled=true;' +
|
|
24
24
|
'err.textContent="Too many attempts. Try again in "+Math.ceil(d.retryAfter/60)+" min";' +
|
|
25
|
-
'setTimeout(function(){for(var i=0;i<boxes.length;i++)
|
|
26
|
-
'
|
|
25
|
+
'setTimeout(function(){for(var i=0;i<boxes.length;i++)boxes[i].disabled=false;' +
|
|
26
|
+
'err.textContent="";resetPinBoxes()},d.retryAfter*1000);return}' +
|
|
27
27
|
'var msg="Wrong PIN";if(typeof d.attemptsLeft==="number"&&d.attemptsLeft<=3)msg+=" ("+d.attemptsLeft+" left)";' +
|
|
28
|
-
'err.textContent=msg;
|
|
29
|
-
'document.getElementById("pin").value="";boxes[0].focus()})' +
|
|
28
|
+
'err.textContent=msg;resetPinBoxes()})' +
|
|
30
29
|
'.catch(function(){err.textContent="Connection error"})}' +
|
|
31
30
|
'initPinBoxes("pin-boxes","pin",submitPin);' +
|
|
32
31
|
'</script></div></body></html>';
|
|
@@ -727,9 +726,9 @@ var authPageStyles =
|
|
|
727
726
|
// PIN digit boxes
|
|
728
727
|
'.pin-wrap{display:flex;gap:8px;justify-content:center}' +
|
|
729
728
|
'.pin-digit{width:44px;height:56px;background:var(--input-bg);border:1.5px solid var(--border);border-radius:8px;' +
|
|
730
|
-
'color:var(--
|
|
729
|
+
'color:var(--text);font-family:"Courier New",Courier,"Roboto Mono",monospace;font-size:28px;font-weight:700;' +
|
|
731
730
|
'text-align:center;line-height:56px;outline:none;caret-color:transparent;' +
|
|
732
|
-
'
|
|
731
|
+
'transition:border-color 0.15s,box-shadow 0.15s}' +
|
|
733
732
|
'.pin-digit:focus{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-20)}' +
|
|
734
733
|
'.pin-digit.filled{color:var(--text)}' +
|
|
735
734
|
// Legacy single-input fallback
|
|
@@ -754,28 +753,30 @@ var pinBoxScript =
|
|
|
754
753
|
'function initPinBoxes(cId,hId,onComplete){' +
|
|
755
754
|
'var wrap=document.getElementById(cId),hidden=document.getElementById(hId);' +
|
|
756
755
|
'var boxes=wrap.querySelectorAll(".pin-digit");' +
|
|
756
|
+
'var digits=["","","","","",""];' +
|
|
757
757
|
'boxes[0].focus();' +
|
|
758
|
+
'function setDigit(idx,v){digits[idx]=v;boxes[idx].value=v?"\\u2022":"";boxes[idx].classList.toggle("filled",v.length>0);syncHidden()}' +
|
|
758
759
|
'for(var i=0;i<boxes.length;i++){(function(idx){' +
|
|
759
760
|
'boxes[idx].addEventListener("input",function(e){' +
|
|
760
|
-
'var
|
|
761
|
-
'if(
|
|
762
|
-
'
|
|
763
|
-
'
|
|
764
|
-
'syncHidden();' +
|
|
761
|
+
'var raw=this.value.replace(/[^0-9]/g,"");' +
|
|
762
|
+
'if(!raw){setDigit(idx,"");return}' +
|
|
763
|
+
'var v=raw.charAt(raw.length-1);' +
|
|
764
|
+
'setDigit(idx,v);' +
|
|
765
765
|
'if(v&&idx<5)boxes[idx+1].focus();' +
|
|
766
766
|
'if(hidden.value.length===6&&onComplete)onComplete()});' +
|
|
767
767
|
'boxes[idx].addEventListener("keydown",function(e){' +
|
|
768
|
-
'if(e.key==="Backspace"
|
|
768
|
+
'if(e.key==="Backspace"){if(!digits[idx]&&idx>0){setDigit(idx-1,"");boxes[idx-1].focus()}else{setDigit(idx,"")}syncHidden();return}' +
|
|
769
769
|
'if(e.key==="ArrowLeft"&&idx>0)boxes[idx-1].focus();' +
|
|
770
770
|
'if(e.key==="ArrowRight"&&idx<5)boxes[idx+1].focus();' +
|
|
771
771
|
'if(e.key==="Enter"&&hidden.value.length===6&&onComplete){e.preventDefault();onComplete()}});' +
|
|
772
772
|
'boxes[idx].addEventListener("paste",function(e){' +
|
|
773
773
|
'e.preventDefault();var d=(e.clipboardData||window.clipboardData).getData("text").replace(/[^0-9]/g,"").slice(0,6);' +
|
|
774
|
-
'for(var j=0;j<d.length&&j<6;j++){
|
|
775
|
-
'
|
|
774
|
+
'for(var j=0;j<d.length&&j<6;j++){setDigit(j,d.charAt(j))}' +
|
|
775
|
+
'if(d.length>=6){boxes[5].focus();if(onComplete)onComplete()}else if(d.length>0)boxes[d.length].focus()});' +
|
|
776
776
|
'boxes[idx].addEventListener("focus",function(){this.select()});' +
|
|
777
777
|
'})(i)}' +
|
|
778
|
-
'function syncHidden(){var v="";for(var j=0;j<
|
|
778
|
+
'function syncHidden(){var v="";for(var j=0;j<digits.length;j++)v+=digits[j];hidden.value=v}' +
|
|
779
|
+
'window.resetPinBoxes=function(){for(var j=0;j<6;j++){digits[j]="";boxes[j].value="";boxes[j].classList.remove("filled")}hidden.value="";boxes[0].focus()}' +
|
|
779
780
|
'}';
|
|
780
781
|
|
|
781
782
|
// HTML fragment for 6 PIN digit boxes + hidden input
|
|
@@ -938,8 +939,8 @@ function multiUserLoginPageHtml() {
|
|
|
938
939
|
|
|
939
940
|
'function resetPin(){' +
|
|
940
941
|
'var boxes=document.querySelectorAll(".pin-digit");' +
|
|
941
|
-
'for(var i=0;i<boxes.length;i++)
|
|
942
|
-
'
|
|
942
|
+
'for(var i=0;i<boxes.length;i++)boxes[i].disabled=false;' +
|
|
943
|
+
'if(window.resetPinBoxes)resetPinBoxes();btns[1].disabled=true}' +
|
|
943
944
|
|
|
944
945
|
'function goBackToUsername(){' +
|
|
945
946
|
'steps[1].classList.remove("active");dots[1].classList.remove("current");dots[1].classList.remove("done");' +
|
package/lib/project.js
CHANGED
|
@@ -156,6 +156,15 @@ function createProjectContext(opts) {
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
function sendToSessionOthers(sender, sessionId, obj) {
|
|
160
|
+
var data = JSON.stringify(obj);
|
|
161
|
+
for (var ws of clients) {
|
|
162
|
+
if (ws !== sender && ws.readyState === 1 && ws._clayActiveSession === sessionId) {
|
|
163
|
+
ws.send(data);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
159
168
|
// --- File watcher ---
|
|
160
169
|
var fileWatcher = null;
|
|
161
170
|
var watchedPath = null;
|
|
@@ -1530,7 +1539,7 @@ function createProjectContext(opts) {
|
|
|
1530
1539
|
}
|
|
1531
1540
|
|
|
1532
1541
|
if (msg.type === "input_sync") {
|
|
1533
|
-
|
|
1542
|
+
sendToSessionOthers(ws, ws._clayActiveSession, msg);
|
|
1534
1543
|
return;
|
|
1535
1544
|
}
|
|
1536
1545
|
|
|
@@ -2612,7 +2621,7 @@ function createProjectContext(opts) {
|
|
|
2612
2621
|
}
|
|
2613
2622
|
session.history.push(userMsg);
|
|
2614
2623
|
sm.appendToSessionFile(session, userMsg);
|
|
2615
|
-
|
|
2624
|
+
sendToSessionOthers(ws, session.localId, userMsg);
|
|
2616
2625
|
|
|
2617
2626
|
if (!session.title) {
|
|
2618
2627
|
session.title = (msg.text || "Image").substring(0, 50);
|
|
@@ -2845,7 +2854,8 @@ function createProjectContext(opts) {
|
|
|
2845
2854
|
return;
|
|
2846
2855
|
}
|
|
2847
2856
|
var spawnCwd = scope === "global" ? os.homedir() : cwd;
|
|
2848
|
-
var
|
|
2857
|
+
var scopeFlag = scope === "global" ? "--global" : "--project";
|
|
2858
|
+
var child = spawn("npx", ["skills", "add", url, "--skill", skill, "--yes", scopeFlag], {
|
|
2849
2859
|
cwd: spawnCwd,
|
|
2850
2860
|
stdio: "ignore",
|
|
2851
2861
|
detached: false,
|
|
@@ -3116,29 +3126,6 @@ function createProjectContext(opts) {
|
|
|
3116
3126
|
},
|
|
3117
3127
|
warmup: function () {
|
|
3118
3128
|
sdk.warmup();
|
|
3119
|
-
// Auto-install clay-ralph skill globally if not present
|
|
3120
|
-
var clayRalphDir = path.join(os.homedir(), ".claude", "skills", "clay-ralph", "SKILL.md");
|
|
3121
|
-
try {
|
|
3122
|
-
fs.accessSync(clayRalphDir, fs.constants.R_OK);
|
|
3123
|
-
} catch (e) {
|
|
3124
|
-
console.log("[project] Auto-installing clay-ralph skill...");
|
|
3125
|
-
var child = spawn("npx", ["skills", "add", "https://github.com/chadbyte/clay-ralph", "--skill", "clay-ralph"], {
|
|
3126
|
-
cwd: os.homedir(),
|
|
3127
|
-
stdio: "ignore",
|
|
3128
|
-
detached: false,
|
|
3129
|
-
});
|
|
3130
|
-
child.on("close", function (code) {
|
|
3131
|
-
if (code === 0) {
|
|
3132
|
-
console.log("[project] clay-ralph skill installed successfully");
|
|
3133
|
-
send({ type: "skill_installed", skill: "clay-ralph", scope: "global", success: true, error: null });
|
|
3134
|
-
} else {
|
|
3135
|
-
console.log("[project] clay-ralph skill install failed (code " + code + ")");
|
|
3136
|
-
}
|
|
3137
|
-
});
|
|
3138
|
-
child.on("error", function (err) {
|
|
3139
|
-
console.log("[project] clay-ralph skill install error: " + err.message);
|
|
3140
|
-
});
|
|
3141
|
-
}
|
|
3142
3129
|
},
|
|
3143
3130
|
destroy: destroy,
|
|
3144
3131
|
};
|
package/lib/public/app.js
CHANGED
|
@@ -542,7 +542,11 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
542
542
|
}
|
|
543
543
|
renderHomeHub(cachedProjects);
|
|
544
544
|
startTipRotation();
|
|
545
|
-
|
|
545
|
+
if (document.documentElement.classList.contains("pwa-standalone")) {
|
|
546
|
+
history.replaceState(null, "", "/");
|
|
547
|
+
} else {
|
|
548
|
+
history.pushState(null, "", "/");
|
|
549
|
+
}
|
|
546
550
|
// Update icon strip active state
|
|
547
551
|
var homeIcon = document.querySelector(".icon-strip-home");
|
|
548
552
|
if (homeIcon) homeIcon.classList.add("active");
|
|
@@ -557,7 +561,11 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
557
561
|
hubCloseBtn.addEventListener("click", function () {
|
|
558
562
|
hideHomeHub();
|
|
559
563
|
if (currentSlug) {
|
|
560
|
-
|
|
564
|
+
if (document.documentElement.classList.contains("pwa-standalone")) {
|
|
565
|
+
history.replaceState(null, "", "/p/" + currentSlug + "/");
|
|
566
|
+
} else {
|
|
567
|
+
history.pushState(null, "", "/p/" + currentSlug + "/");
|
|
568
|
+
}
|
|
561
569
|
// Restore icon strip active state
|
|
562
570
|
var homeIcon = document.querySelector(".icon-strip-home");
|
|
563
571
|
if (homeIcon) homeIcon.classList.remove("active");
|
|
@@ -1830,6 +1838,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
1830
1838
|
|
|
1831
1839
|
// --- DOM: Messages ---
|
|
1832
1840
|
function addUserMessage(text, images, pastes) {
|
|
1841
|
+
if (!text && (!images || images.length === 0) && (!pastes || pastes.length === 0)) return;
|
|
1833
1842
|
var div = document.createElement("div");
|
|
1834
1843
|
div.className = "msg-user";
|
|
1835
1844
|
div.dataset.turn = ++turnCounter;
|
|
@@ -2360,7 +2369,11 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
2360
2369
|
currentSlug = slug;
|
|
2361
2370
|
basePath = "/p/" + slug + "/";
|
|
2362
2371
|
wsPath = "/p/" + slug + "/ws";
|
|
2363
|
-
|
|
2372
|
+
if (document.documentElement.classList.contains("pwa-standalone")) {
|
|
2373
|
+
history.replaceState(null, "", basePath);
|
|
2374
|
+
} else {
|
|
2375
|
+
history.pushState(null, "", basePath);
|
|
2376
|
+
}
|
|
2364
2377
|
resetClientState();
|
|
2365
2378
|
connect();
|
|
2366
2379
|
}
|
|
@@ -2555,10 +2568,6 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
2555
2568
|
var vEl = $("footer-version");
|
|
2556
2569
|
if (vEl) vEl.textContent = "v" + msg.version;
|
|
2557
2570
|
}
|
|
2558
|
-
if (msg.debug) {
|
|
2559
|
-
var debugWrap = $("debug-menu-wrap");
|
|
2560
|
-
if (debugWrap) debugWrap.classList.remove("hidden");
|
|
2561
|
-
}
|
|
2562
2571
|
if (msg.lanHost) window.__lanHost = msg.lanHost;
|
|
2563
2572
|
if (msg.dangerouslySkipPermissions) {
|
|
2564
2573
|
skipPermsEnabled = true;
|
|
@@ -2667,24 +2676,13 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
2667
2676
|
|
|
2668
2677
|
case "skill_installed":
|
|
2669
2678
|
handleSkillInstalled(msg);
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
ralphSkillInstalling = false;
|
|
2673
|
-
ralphSkillInstalled = true;
|
|
2674
|
-
if (msg.success) {
|
|
2675
|
-
wizardStep = 2;
|
|
2676
|
-
updateWizardStep();
|
|
2677
|
-
} else {
|
|
2678
|
-
var rNextBtn = document.getElementById("ralph-wizard-next");
|
|
2679
|
-
if (rNextBtn) { rNextBtn.disabled = false; rNextBtn.textContent = "Get Started"; }
|
|
2680
|
-
var rStatusEl = document.getElementById("ralph-install-status");
|
|
2681
|
-
if (rStatusEl) { rStatusEl.innerHTML = "Failed to install skill. Try again."; }
|
|
2682
|
-
}
|
|
2683
|
-
}
|
|
2679
|
+
if (msg.success) knownInstalledSkills[msg.skill] = true;
|
|
2680
|
+
handleSkillInstallWs(msg);
|
|
2684
2681
|
break;
|
|
2685
2682
|
|
|
2686
2683
|
case "skill_uninstalled":
|
|
2687
2684
|
handleSkillUninstalled(msg);
|
|
2685
|
+
if (msg.success) delete knownInstalledSkills[msg.skill];
|
|
2688
2686
|
break;
|
|
2689
2687
|
|
|
2690
2688
|
case "loop_registry_updated":
|
|
@@ -3797,35 +3795,191 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
3797
3795
|
}
|
|
3798
3796
|
}
|
|
3799
3797
|
|
|
3800
|
-
// ---
|
|
3801
|
-
var
|
|
3802
|
-
var
|
|
3798
|
+
// --- Skill install dialog (generic) ---
|
|
3799
|
+
var skillInstallModal = document.getElementById("skill-install-modal");
|
|
3800
|
+
var skillInstallTitle = document.getElementById("skill-install-title");
|
|
3801
|
+
var skillInstallReason = document.getElementById("skill-install-reason");
|
|
3802
|
+
var skillInstallList = document.getElementById("skill-install-list");
|
|
3803
|
+
var skillInstallOk = document.getElementById("skill-install-ok");
|
|
3804
|
+
var skillInstallCancel = document.getElementById("skill-install-cancel");
|
|
3805
|
+
var skillInstallStatus = document.getElementById("skill-install-status");
|
|
3806
|
+
|
|
3807
|
+
var pendingSkillInstalls = []; // [{ name, url, scope, installed }]
|
|
3808
|
+
var skillInstallCallback = null;
|
|
3809
|
+
var skillInstalling = false;
|
|
3810
|
+
var knownInstalledSkills = {}; // client-side cache of installed skills
|
|
3811
|
+
|
|
3812
|
+
function renderSkillInstallDialog(opts, missing) {
|
|
3813
|
+
skillInstallTitle.textContent = opts.title || "Skill Installation Required";
|
|
3814
|
+
skillInstallReason.textContent = opts.reason || "";
|
|
3815
|
+
skillInstallList.innerHTML = "";
|
|
3816
|
+
for (var i = 0; i < missing.length; i++) {
|
|
3817
|
+
var s = missing[i];
|
|
3818
|
+
var item = document.createElement("div");
|
|
3819
|
+
item.className = "skill-install-item";
|
|
3820
|
+
item.setAttribute("data-skill", s.name);
|
|
3821
|
+
item.innerHTML = '<span class="skill-icon">🧩</span>' +
|
|
3822
|
+
'<div class="skill-info">' +
|
|
3823
|
+
'<span class="skill-name">' + escapeHtml(s.name) + '</span>' +
|
|
3824
|
+
'<span class="skill-scope">' + escapeHtml(s.scope || "global") + '</span>' +
|
|
3825
|
+
'</div>' +
|
|
3826
|
+
'<span class="skill-status"></span>';
|
|
3827
|
+
skillInstallList.appendChild(item);
|
|
3828
|
+
}
|
|
3829
|
+
skillInstallStatus.classList.add("hidden");
|
|
3830
|
+
skillInstallStatus.innerHTML = "";
|
|
3831
|
+
skillInstallOk.disabled = false;
|
|
3832
|
+
skillInstallOk.textContent = "Install";
|
|
3833
|
+
skillInstallOk.className = "confirm-btn confirm-delete";
|
|
3834
|
+
skillInstallModal.classList.remove("hidden");
|
|
3835
|
+
}
|
|
3836
|
+
|
|
3837
|
+
function hideSkillInstallModal() {
|
|
3838
|
+
skillInstallModal.classList.add("hidden");
|
|
3839
|
+
skillInstallCallback = null;
|
|
3840
|
+
pendingSkillInstalls = [];
|
|
3841
|
+
skillInstalling = false;
|
|
3842
|
+
skillInstallDone = false;
|
|
3843
|
+
}
|
|
3844
|
+
|
|
3845
|
+
skillInstallCancel.addEventListener("click", hideSkillInstallModal);
|
|
3846
|
+
skillInstallModal.querySelector(".confirm-backdrop").addEventListener("click", hideSkillInstallModal);
|
|
3847
|
+
|
|
3848
|
+
var skillInstallDone = false;
|
|
3849
|
+
|
|
3850
|
+
skillInstallOk.addEventListener("click", function () {
|
|
3851
|
+
// "Proceed" state — all done, close and invoke callback
|
|
3852
|
+
if (skillInstallDone) {
|
|
3853
|
+
var proceedCb = skillInstallCallback;
|
|
3854
|
+
skillInstallCallback = null;
|
|
3855
|
+
hideSkillInstallModal();
|
|
3856
|
+
if (proceedCb) proceedCb();
|
|
3857
|
+
return;
|
|
3858
|
+
}
|
|
3859
|
+
if (skillInstalling) return;
|
|
3860
|
+
skillInstalling = true;
|
|
3861
|
+
skillInstallOk.disabled = true;
|
|
3862
|
+
skillInstallOk.textContent = "Installing...";
|
|
3863
|
+
|
|
3864
|
+
var total = 0;
|
|
3865
|
+
for (var i = 0; i < pendingSkillInstalls.length; i++) {
|
|
3866
|
+
if (!pendingSkillInstalls[i].installed) total++;
|
|
3867
|
+
}
|
|
3868
|
+
skillInstallStatus.classList.remove("hidden");
|
|
3869
|
+
updateSkillInstallProgress(0, total);
|
|
3870
|
+
|
|
3871
|
+
for (var j = 0; j < pendingSkillInstalls.length; j++) {
|
|
3872
|
+
var s = pendingSkillInstalls[j];
|
|
3873
|
+
if (s.installed) continue;
|
|
3874
|
+
fetch(basePath + "api/install-skill", {
|
|
3875
|
+
method: "POST",
|
|
3876
|
+
headers: { "Content-Type": "application/json" },
|
|
3877
|
+
body: JSON.stringify({ url: s.url, skill: s.name, scope: s.scope || "global" }),
|
|
3878
|
+
}).catch(function () {});
|
|
3879
|
+
}
|
|
3880
|
+
});
|
|
3881
|
+
|
|
3882
|
+
function updateSkillInstallProgress(done, total) {
|
|
3883
|
+
skillInstallStatus.innerHTML = '<div class="skills-spinner small"></div> Installing skills... (' + done + '/' + total + ')';
|
|
3884
|
+
}
|
|
3885
|
+
|
|
3886
|
+
function updateSkillListItems() {
|
|
3887
|
+
var items = skillInstallList.querySelectorAll(".skill-install-item");
|
|
3888
|
+
for (var i = 0; i < items.length; i++) {
|
|
3889
|
+
var name = items[i].getAttribute("data-skill");
|
|
3890
|
+
for (var j = 0; j < pendingSkillInstalls.length; j++) {
|
|
3891
|
+
if (pendingSkillInstalls[j].name === name) {
|
|
3892
|
+
var statusEl = items[i].querySelector(".skill-status");
|
|
3893
|
+
if (pendingSkillInstalls[j].installed) {
|
|
3894
|
+
if (statusEl) {
|
|
3895
|
+
statusEl.innerHTML = '<span class="skill-status-ok">' + iconHtml("circle-check") + '</span>';
|
|
3896
|
+
refreshIcons();
|
|
3897
|
+
}
|
|
3898
|
+
}
|
|
3899
|
+
break;
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
|
|
3905
|
+
function handleSkillInstallWs(msg) {
|
|
3906
|
+
if (!skillInstalling || pendingSkillInstalls.length === 0) return;
|
|
3907
|
+
for (var i = 0; i < pendingSkillInstalls.length; i++) {
|
|
3908
|
+
if (pendingSkillInstalls[i].name === msg.skill) {
|
|
3909
|
+
if (msg.success) {
|
|
3910
|
+
pendingSkillInstalls[i].installed = true;
|
|
3911
|
+
knownInstalledSkills[msg.skill] = true;
|
|
3912
|
+
} else {
|
|
3913
|
+
skillInstalling = false;
|
|
3914
|
+
skillInstallOk.disabled = false;
|
|
3915
|
+
skillInstallOk.textContent = "Install";
|
|
3916
|
+
skillInstallStatus.innerHTML = "Failed to install " + escapeHtml(msg.skill) + ". Try again.";
|
|
3917
|
+
updateSkillListItems();
|
|
3918
|
+
return;
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
|
|
3923
|
+
var doneCount = 0;
|
|
3924
|
+
var totalCount = pendingSkillInstalls.length;
|
|
3925
|
+
for (var k = 0; k < pendingSkillInstalls.length; k++) {
|
|
3926
|
+
if (pendingSkillInstalls[k].installed) doneCount++;
|
|
3927
|
+
}
|
|
3928
|
+
updateSkillListItems();
|
|
3929
|
+
updateSkillInstallProgress(doneCount, totalCount);
|
|
3930
|
+
|
|
3931
|
+
if (doneCount === totalCount) {
|
|
3932
|
+
skillInstallDone = true;
|
|
3933
|
+
skillInstallStatus.innerHTML = '<span class="skill-status-ok">' + iconHtml("circle-check") + '</span> All skills installed successfully.';
|
|
3934
|
+
refreshIcons();
|
|
3935
|
+
skillInstallOk.disabled = false;
|
|
3936
|
+
skillInstallOk.textContent = "Proceed";
|
|
3937
|
+
skillInstallOk.className = "confirm-btn confirm-proceed";
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3803
3940
|
|
|
3804
|
-
function
|
|
3941
|
+
function requireSkills(opts, cb) {
|
|
3805
3942
|
fetch(basePath + "api/installed-skills")
|
|
3806
3943
|
.then(function (res) { return res.json(); })
|
|
3807
3944
|
.then(function (data) {
|
|
3808
3945
|
var installed = data.installed || {};
|
|
3809
|
-
|
|
3810
|
-
|
|
3946
|
+
var missing = [];
|
|
3947
|
+
for (var i = 0; i < opts.skills.length; i++) {
|
|
3948
|
+
var sName = opts.skills[i].name;
|
|
3949
|
+
if (!installed[sName] && !knownInstalledSkills[sName]) {
|
|
3950
|
+
missing.push({ name: sName, url: opts.skills[i].url, scope: opts.skills[i].scope || "global", installed: false });
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
if (missing.length === 0) { cb(); return; }
|
|
3954
|
+
pendingSkillInstalls = missing;
|
|
3955
|
+
skillInstallCallback = cb;
|
|
3956
|
+
renderSkillInstallDialog(opts, missing);
|
|
3811
3957
|
})
|
|
3812
|
-
.catch(function () { cb(
|
|
3958
|
+
.catch(function () { cb(); });
|
|
3813
3959
|
}
|
|
3814
3960
|
|
|
3961
|
+
function requireClayRalph(cb) {
|
|
3962
|
+
requireSkills({
|
|
3963
|
+
title: "Skill Installation Required",
|
|
3964
|
+
reason: "This feature requires the following skill to be installed.",
|
|
3965
|
+
skills: [{ name: "clay-ralph", url: "https://github.com/chadbyte/clay-ralph", scope: "global" }]
|
|
3966
|
+
}, cb);
|
|
3967
|
+
}
|
|
3968
|
+
|
|
3969
|
+
// --- Ralph Wizard ---
|
|
3970
|
+
|
|
3815
3971
|
function openRalphWizard() {
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3972
|
+
requireClayRalph(function () {
|
|
3973
|
+
wizardData = { name: "", task: "", maxIterations: 3 };
|
|
3974
|
+
var el = document.getElementById("ralph-wizard");
|
|
3975
|
+
if (!el) return;
|
|
3820
3976
|
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3977
|
+
var taskEl = document.getElementById("ralph-task");
|
|
3978
|
+
if (taskEl) taskEl.value = "";
|
|
3979
|
+
var iterEl = document.getElementById("ralph-max-iterations");
|
|
3980
|
+
if (iterEl) iterEl.value = "25";
|
|
3825
3981
|
|
|
3826
|
-
|
|
3827
|
-
checkRalphSkillInstalled(function (installed) {
|
|
3828
|
-
wizardStep = installed ? 2 : 1;
|
|
3982
|
+
wizardStep = 1;
|
|
3829
3983
|
el.classList.remove("hidden");
|
|
3830
3984
|
var statusEl = document.getElementById("ralph-install-status");
|
|
3831
3985
|
if (statusEl) { statusEl.classList.add("hidden"); statusEl.innerHTML = ""; }
|
|
@@ -3928,38 +4082,9 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
3928
4082
|
function wizardNext() {
|
|
3929
4083
|
collectWizardData();
|
|
3930
4084
|
|
|
3931
|
-
// Step 1: install clay-ralph skill if needed, otherwise just advance
|
|
3932
4085
|
if (wizardStep === 1) {
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
updateWizardStep();
|
|
3936
|
-
return;
|
|
3937
|
-
}
|
|
3938
|
-
if (ralphSkillInstalling) return;
|
|
3939
|
-
ralphSkillInstalling = true;
|
|
3940
|
-
var nextBtn = document.getElementById("ralph-wizard-next");
|
|
3941
|
-
if (nextBtn) {
|
|
3942
|
-
nextBtn.disabled = true;
|
|
3943
|
-
nextBtn.textContent = "Installing...";
|
|
3944
|
-
}
|
|
3945
|
-
var statusEl = document.getElementById("ralph-install-status");
|
|
3946
|
-
if (statusEl) {
|
|
3947
|
-
statusEl.classList.remove("hidden");
|
|
3948
|
-
statusEl.innerHTML = '<div class="skills-spinner small"></div> Installing clay-ralph skill...';
|
|
3949
|
-
}
|
|
3950
|
-
fetch(basePath + "api/install-skill", {
|
|
3951
|
-
method: "POST",
|
|
3952
|
-
headers: { "Content-Type": "application/json" },
|
|
3953
|
-
body: JSON.stringify({ url: "https://github.com/chadbyte/clay-ralph", skill: "clay-ralph", scope: "global" }),
|
|
3954
|
-
})
|
|
3955
|
-
.then(function () {
|
|
3956
|
-
// Wait for skill_installed websocket message to advance
|
|
3957
|
-
})
|
|
3958
|
-
.catch(function () {
|
|
3959
|
-
ralphSkillInstalling = false;
|
|
3960
|
-
if (nextBtn) { nextBtn.disabled = false; nextBtn.textContent = "Get Started"; }
|
|
3961
|
-
if (statusEl) { statusEl.innerHTML = "Failed to install skill. Try again."; }
|
|
3962
|
-
});
|
|
4086
|
+
wizardStep++;
|
|
4087
|
+
updateWizardStep();
|
|
3963
4088
|
return;
|
|
3964
4089
|
}
|
|
3965
4090
|
|
|
@@ -4299,6 +4424,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
4299
4424
|
basePath: basePath,
|
|
4300
4425
|
currentSlug: currentSlug,
|
|
4301
4426
|
openRalphWizard: function () { openRalphWizard(); },
|
|
4427
|
+
requireClayRalph: function (cb) { requireClayRalph(cb); },
|
|
4302
4428
|
getProjects: function () { return cachedProjects; },
|
|
4303
4429
|
});
|
|
4304
4430
|
|
|
@@ -4607,6 +4733,57 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
4607
4733
|
closeAddProjectModal();
|
|
4608
4734
|
});
|
|
4609
4735
|
|
|
4736
|
+
// --- PWA install prompt ---
|
|
4737
|
+
(function () {
|
|
4738
|
+
var installPill = document.getElementById("pwa-install-pill");
|
|
4739
|
+
var modal = document.getElementById("pwa-install-modal");
|
|
4740
|
+
var confirmBtn = document.getElementById("pwa-modal-confirm");
|
|
4741
|
+
var cancelBtn = document.getElementById("pwa-modal-cancel");
|
|
4742
|
+
if (!installPill || !modal) return;
|
|
4743
|
+
|
|
4744
|
+
// Already standalone — never show
|
|
4745
|
+
if (document.documentElement.classList.contains("pwa-standalone")) return;
|
|
4746
|
+
|
|
4747
|
+
// Show pill on mobile browsers (the primary target for PWA install)
|
|
4748
|
+
var isMobile = /Mobi|Android|iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
4749
|
+
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
|
|
4750
|
+
if (isMobile) {
|
|
4751
|
+
installPill.classList.remove("hidden");
|
|
4752
|
+
}
|
|
4753
|
+
|
|
4754
|
+
// Also show on desktop if beforeinstallprompt fires
|
|
4755
|
+
window.addEventListener("beforeinstallprompt", function (e) {
|
|
4756
|
+
e.preventDefault();
|
|
4757
|
+
installPill.classList.remove("hidden");
|
|
4758
|
+
});
|
|
4759
|
+
|
|
4760
|
+
function openModal() {
|
|
4761
|
+
modal.classList.remove("hidden");
|
|
4762
|
+
lucide.createIcons({ nodes: [modal] });
|
|
4763
|
+
}
|
|
4764
|
+
|
|
4765
|
+
function closeModal() {
|
|
4766
|
+
modal.classList.add("hidden");
|
|
4767
|
+
}
|
|
4768
|
+
|
|
4769
|
+
installPill.addEventListener("click", openModal);
|
|
4770
|
+
cancelBtn.addEventListener("click", closeModal);
|
|
4771
|
+
modal.querySelector(".pwa-modal-backdrop").addEventListener("click", closeModal);
|
|
4772
|
+
|
|
4773
|
+
confirmBtn.addEventListener("click", function () {
|
|
4774
|
+
// Redirect to HTTP setup page (port + 1)
|
|
4775
|
+
var port = parseInt(location.port, 10) || (location.protocol === "https:" ? 443 : 80);
|
|
4776
|
+
var setupUrl = "http://" + location.hostname + ":" + (port + 1) + "/setup";
|
|
4777
|
+
location.href = setupUrl;
|
|
4778
|
+
});
|
|
4779
|
+
|
|
4780
|
+
// Hide after install
|
|
4781
|
+
window.addEventListener("appinstalled", function () {
|
|
4782
|
+
installPill.classList.add("hidden");
|
|
4783
|
+
closeModal();
|
|
4784
|
+
});
|
|
4785
|
+
})();
|
|
4786
|
+
|
|
4610
4787
|
// --- Init ---
|
|
4611
4788
|
lucide.createIcons();
|
|
4612
4789
|
connect();
|
package/lib/public/css/base.css
CHANGED
|
@@ -158,6 +158,8 @@ img.emoji {
|
|
|
158
158
|
--content-width: 760px;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
/* PWA standalone: home indicator safe area still applies on notched devices */
|
|
162
|
+
|
|
161
163
|
::selection {
|
|
162
164
|
background: rgba(80, 250, 123, 0.25);
|
|
163
165
|
}
|
|
@@ -170,6 +172,7 @@ html, body {
|
|
|
170
172
|
font-size: 15px;
|
|
171
173
|
line-height: 1.6;
|
|
172
174
|
overflow: hidden;
|
|
175
|
+
overscroll-behavior: none;
|
|
173
176
|
-webkit-font-smoothing: antialiased;
|
|
174
177
|
-moz-osx-font-smoothing: grayscale;
|
|
175
178
|
}
|
|
@@ -677,6 +677,7 @@
|
|
|
677
677
|
color: var(--text);
|
|
678
678
|
white-space: pre-wrap;
|
|
679
679
|
word-break: break-word;
|
|
680
|
+
tab-size: 2;
|
|
680
681
|
}
|
|
681
682
|
|
|
682
683
|
.file-viewer-body pre code {
|
|
@@ -700,6 +701,7 @@
|
|
|
700
701
|
line-height: 1.55;
|
|
701
702
|
white-space: pre;
|
|
702
703
|
word-break: normal;
|
|
704
|
+
tab-size: 2;
|
|
703
705
|
}
|
|
704
706
|
|
|
705
707
|
.file-viewer-gutter {
|
|
@@ -890,7 +892,12 @@
|
|
|
890
892
|
@media (max-width: 1023px) {
|
|
891
893
|
#terminal-container {
|
|
892
894
|
position: fixed;
|
|
893
|
-
|
|
895
|
+
top: 0;
|
|
896
|
+
left: 0;
|
|
897
|
+
right: 0;
|
|
898
|
+
bottom: 0;
|
|
899
|
+
padding-top: var(--safe-top);
|
|
900
|
+
padding-bottom: var(--safe-bottom);
|
|
894
901
|
background: var(--bg);
|
|
895
902
|
z-index: 300;
|
|
896
903
|
display: flex;
|
package/lib/public/css/input.css
CHANGED
package/lib/public/css/menus.css
CHANGED
|
@@ -142,46 +142,6 @@
|
|
|
142
142
|
flex-shrink: 0;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
/* --- Debug menu --- */
|
|
146
|
-
#debug-menu-wrap {
|
|
147
|
-
position: relative;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
#debug-menu-wrap.hidden { display: none; }
|
|
151
|
-
|
|
152
|
-
#debug-btn {
|
|
153
|
-
display: flex;
|
|
154
|
-
align-items: center;
|
|
155
|
-
justify-content: center;
|
|
156
|
-
background: none;
|
|
157
|
-
border: 1px solid transparent;
|
|
158
|
-
border-radius: 8px;
|
|
159
|
-
color: var(--error);
|
|
160
|
-
cursor: pointer;
|
|
161
|
-
padding: 4px;
|
|
162
|
-
opacity: 0.6;
|
|
163
|
-
transition: opacity 0.15s, background 0.15s, border-color 0.15s;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
#debug-btn .lucide { width: 15px; height: 15px; }
|
|
167
|
-
#debug-btn:hover { opacity: 1; background: var(--error-8); border-color: var(--error-25); }
|
|
168
|
-
#debug-btn.active { opacity: 1; background: var(--error-12); border-color: var(--error-25); }
|
|
169
|
-
|
|
170
|
-
#debug-menu {
|
|
171
|
-
position: absolute;
|
|
172
|
-
top: calc(100% + 6px);
|
|
173
|
-
right: 0;
|
|
174
|
-
background: var(--bg-alt);
|
|
175
|
-
border: 1px solid var(--border);
|
|
176
|
-
border-radius: 10px;
|
|
177
|
-
padding: 8px 0;
|
|
178
|
-
min-width: 200px;
|
|
179
|
-
box-shadow: 0 4px 16px rgba(var(--shadow-rgb), 0.4);
|
|
180
|
-
z-index: 200;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
#debug-menu.hidden { display: none; }
|
|
184
|
-
|
|
185
145
|
/* --- Terminal toggle button (mobile only) --- */
|
|
186
146
|
#footer-status,
|
|
187
147
|
#terminal-toggle-btn {
|