@galda/cli 0.10.11 → 0.10.12
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/app/index.html +39 -11
- package/engine/relay-client.mjs +12 -1
- package/engine/server.mjs +11 -1
- package/package.json +1 -1
package/app/index.html
CHANGED
|
@@ -971,7 +971,7 @@
|
|
|
971
971
|
.gearbtn{display:grid;place-items:center;width:30px;height:28px;border:1px solid var(--hair2);border-radius:8px;background:transparent;color:var(--ink2);cursor:pointer}
|
|
972
972
|
.gearbtn:hover{background:var(--surface2);color:var(--ink)}
|
|
973
973
|
.gearbtn svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
|
|
974
|
-
.gearpop{display:none;position:absolute;top:36px;right:0;z-index:80;background:var(--panel);border-radius:12px;box-shadow:0 10px 30px rgba(20,23,31,.18),0 0 0 1px var(--hair);padding:12px 13px;width:
|
|
974
|
+
.gearpop{display:none;position:absolute;top:36px;right:0;z-index:80;background:var(--panel);border-radius:12px;box-shadow:0 10px 30px rgba(20,23,31,.18),0 0 0 1px var(--hair);padding:12px 13px;width:320px;
|
|
975
975
|
max-height:78vh;overflow-y:auto;scrollbar-width:thin; /* TB edge + colour sections made it taller than the viewport */
|
|
976
976
|
/* opaque chip fill (HANDOFF-v45 §9): a translucent --canvas would let the wallpaper
|
|
977
977
|
bleed through and colour each row differently — this token mixes off the OPAQUE
|
|
@@ -1008,7 +1008,7 @@
|
|
|
1008
1008
|
the rail (left is set on open from the measured rail edge), max-height = viewport −
|
|
1009
1009
|
margins, internal scroll. White-translucent per §3: color-mix 74% + blur28 (theme-tinted).
|
|
1010
1010
|
The node is adopted to <body> in flagship (positionLayout) — .top is display:none there. */
|
|
1011
|
-
body[data-layout="flagship"] .gearpop{position:fixed;top:120px;right:auto;width:
|
|
1011
|
+
body[data-layout="flagship"] .gearpop{position:fixed;top:120px;right:auto;width:380px;max-height:calc(100vh - 160px);border-radius:14px;
|
|
1012
1012
|
background:color-mix(in srgb,var(--panel) 74%,transparent);
|
|
1013
1013
|
-webkit-backdrop-filter:blur(28px) saturate(1.35);backdrop-filter:blur(28px) saturate(1.35)}
|
|
1014
1014
|
/* settings = EDIT SESSION (HANDOFF-v45 §3, flagship .cfoot/.mcpbox port): sticky footer,
|
|
@@ -2183,9 +2183,8 @@
|
|
|
2183
2183
|
<div class="lbrow"><span class="lb">Projects</span>
|
|
2184
2184
|
<button class="ghostbtn morebtn" id="fsMoreBtn" title="Settings"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3.2"/><path d="M19.4 15a1.7 1.7 0 00.34 1.87l.06.06a2 2 0 11-2.83 2.83l-.06-.06a1.7 1.7 0 00-1.87-.34 1.7 1.7 0 00-1 1.56V21a2 2 0 11-4 0v-.09a1.7 1.7 0 00-1-1.56 1.7 1.7 0 00-1.87.34l-.06.06a2 2 0 11-2.83-2.83l.06-.06a1.7 1.7 0 00.34-1.87 1.7 1.7 0 00-1.56-1H3a2 2 0 110-4h.09a1.7 1.7 0 001.56-1 1.7 1.7 0 00-.34-1.87l-.06-.06a2 2 0 112.83-2.83l.06.06a1.7 1.7 0 001.87.34h.09a1.7 1.7 0 001-1.56V3a2 2 0 114 0v.09a1.7 1.7 0 001 1.56h.09a1.7 1.7 0 001.87-.34l.06-.06a2 2 0 112.83 2.83l-.06.06a1.7 1.7 0 00-.34 1.87v.09a1.7 1.7 0 001.56 1H21a2 2 0 110 4h-.09a1.7 1.7 0 00-1.56 1z"/></svg></button>
|
|
2185
2185
|
<div class="moremenu" id="fsMoreMenu">
|
|
2186
|
-
<button class="ghostbtn" id="fsCust">
|
|
2186
|
+
<button class="ghostbtn" id="fsCust">Theme Color…</button>
|
|
2187
2187
|
<button class="ghostbtn" id="fsConn">Connect MCP</button>
|
|
2188
|
-
<button class="ghostbtn" id="fsTodoBd">To Do board</button>
|
|
2189
2188
|
<button class="ghostbtn" id="fsHist">History</button>
|
|
2190
2189
|
<button class="ghostbtn" id="fsSet">Settings</button>
|
|
2191
2190
|
</div>
|
|
@@ -5401,6 +5400,19 @@ function renderClawdHero(){
|
|
|
5401
5400
|
tgUpdate();
|
|
5402
5401
|
$('fsMoreBtn').onclick = (e) => { e.stopPropagation(); $('fsMoreMenu').classList.toggle('open'); };
|
|
5403
5402
|
document.addEventListener('click', (e) => { if (!e.target.closest('#fsRoot .lbrow')) $('fsMoreMenu').classList.remove('open'); });
|
|
5403
|
+
// Hover-first (Masa指示 2026-07-16): on a real pointer, aligning the cursor over
|
|
5404
|
+
// the gear reveals the menu with no click, and resting on a menu row previews
|
|
5405
|
+
// its panel straight away — click still works underneath for touch. A short
|
|
5406
|
+
// hover-intent delay (120ms) stops a mouse just passing through from flashing
|
|
5407
|
+
// every panel open.
|
|
5408
|
+
if (matchMedia('(hover:hover)').matches) {
|
|
5409
|
+
const lbrow = $('fsMoreBtn').closest('.lbrow');
|
|
5410
|
+
let closeT = null;
|
|
5411
|
+
if (lbrow) {
|
|
5412
|
+
lbrow.addEventListener('mouseenter', () => { clearTimeout(closeT); $('fsMoreMenu').classList.add('open'); });
|
|
5413
|
+
lbrow.addEventListener('mouseleave', () => { closeT = setTimeout(() => $('fsMoreMenu').classList.remove('open'), 150); });
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5404
5416
|
const closeMobilePanels = () => {
|
|
5405
5417
|
document.body.classList.remove('laneopen', 'projectsopen');
|
|
5406
5418
|
};
|
|
@@ -5422,11 +5434,21 @@ function renderClawdHero(){
|
|
|
5422
5434
|
// v46 (HANDOFF-v45 §3): the menu entries deep-link into the 4-tab settings panel —
|
|
5423
5435
|
// Customize…→THEME / Connect MCP→MCP / History→HISTORY / Settings→SETTINGS.
|
|
5424
5436
|
// stopPropagation: the document click-away handler otherwise closes it in the same event.
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5437
|
+
// Each row also opens on hover-intent (120ms), so resting the cursor on a row is
|
|
5438
|
+
// enough to see its panel — click still fires the same action underneath.
|
|
5439
|
+
const menuItem = (btn, tab) => {
|
|
5440
|
+
const open = (e) => { e && e.stopPropagation(); $('fsMoreMenu').classList.remove('open'); custOpen(tab); };
|
|
5441
|
+
btn.onclick = open;
|
|
5442
|
+
if (matchMedia('(hover:hover)').matches) {
|
|
5443
|
+
let t = null;
|
|
5444
|
+
btn.addEventListener('mouseenter', () => { t = setTimeout(open, 120); });
|
|
5445
|
+
btn.addEventListener('mouseleave', () => clearTimeout(t));
|
|
5446
|
+
}
|
|
5447
|
+
};
|
|
5448
|
+
menuItem($('fsCust'), 'theme');
|
|
5449
|
+
menuItem($('fsConn'), 'mcp');
|
|
5450
|
+
menuItem($('fsHist'), 'history');
|
|
5451
|
+
menuItem($('fsSet'), 'settings');
|
|
5430
5452
|
// parallel-runs switcher (§1.5-1/-7): same toggle+click-away shape as fsMoreMenu above.
|
|
5431
5453
|
// Row content is rebuilt by renderFsFeed() (running list changes live); the open/closed
|
|
5432
5454
|
// class here is left alone by that rebuild so the popover doesn't snap shut mid-render.
|
|
@@ -7451,11 +7473,11 @@ function gearSetHTML(){
|
|
|
7451
7473
|
<button class="optchip" data-gsignin="1">Sign in with Google</button></div>
|
|
7452
7474
|
<div class="srow dis" id="licKeyMsg" style="display:none"></div>`;
|
|
7453
7475
|
} else if (bill.plan === 'paid') {
|
|
7454
|
-
billingRow = `<div class="srow"><div class="sl">Account<span class="sd">Signed in with Google</span></div><
|
|
7476
|
+
billingRow = `<div class="srow"><div class="sl">Account<span class="sd">Signed in with Google · ${esc(bill.email)}</span></div><button class="optchip" data-gsignout="1">Log out</button></div>
|
|
7455
7477
|
<div class="srow"><div class="sl">Plan</div><span class="bx-pdot"><i></i>Paid</span></div>
|
|
7456
7478
|
<div class="srow"><div class="sl">Billing<span class="sd">manage or cancel in Stripe</span></div><button class="bx-cchip" data-gportal="1">Manage</button></div>`;
|
|
7457
7479
|
} else {
|
|
7458
|
-
billingRow = `<div class="srow"><div class="sl">Account<span class="sd">Signed in with Google</span></div><
|
|
7480
|
+
billingRow = `<div class="srow"><div class="sl">Account<span class="sd">Signed in with Google · ${esc(bill.email)}</span></div><button class="optchip" data-gsignout="1">Log out</button></div>
|
|
7459
7481
|
<div class="srow"><div class="sl">Plan</div><span class="bx-sval">Free</span></div>
|
|
7460
7482
|
<div class="srow"><div class="sl">To Dos<span class="sd">pending, this install</span></div><span class="bx-sval bx-num">${u.pending ?? 0} / ${lim.pending ?? 5}</span></div>
|
|
7461
7483
|
<div class="srow"><div class="sl">Lifetime</div><span class="bx-sval bx-num">${u.cumulative ?? 0} / ${lim.cumulative ?? 19}</span></div>
|
|
@@ -7517,6 +7539,12 @@ function buildGearPop(){
|
|
|
7517
7539
|
if (upBtn) upBtn.onclick = () => startCheckout();
|
|
7518
7540
|
const portalBtn = pop.querySelector('[data-gportal]');
|
|
7519
7541
|
if (portalBtn) portalBtn.onclick = () => openBillingPortal();
|
|
7542
|
+
const signOutBtn = pop.querySelector('[data-gsignout]');
|
|
7543
|
+
if (signOutBtn) signOutBtn.onclick = async () => {
|
|
7544
|
+
signOutBtn.disabled = true;
|
|
7545
|
+
try { await fetch(withKey('/api/signout'), { method: 'POST' }); }
|
|
7546
|
+
finally { await refresh(); if ($('gearPop')) buildGearPop(); }
|
|
7547
|
+
};
|
|
7520
7548
|
return;
|
|
7521
7549
|
}
|
|
7522
7550
|
if (custTab === 'mcp') {
|
package/engine/relay-client.mjs
CHANGED
|
@@ -100,8 +100,19 @@ function connect(retryMs = 1000) {
|
|
|
100
100
|
live.delete(m.id);
|
|
101
101
|
}
|
|
102
102
|
};
|
|
103
|
-
ws.onclose = () => {
|
|
103
|
+
ws.onclose = (ev) => {
|
|
104
104
|
if (ws !== currentWs) return; // a superseded socket (we already moved on) — don't reconnect
|
|
105
|
+
// Close 4000 = the relay evicted us because a NEWER agent for the SAME account
|
|
106
|
+
// connected (server.mjs "replaced by a newer connection"). If we reconnect we
|
|
107
|
+
// evict THAT one → the two instances mutual-kick forever, which surfaces to the
|
|
108
|
+
// user as app.galda.app reload-looping. Stand down: the newest instance is
|
|
109
|
+
// authoritative. (A normal network drop uses a different code and still
|
|
110
|
+
// reconnects; sign-in via the watcher below can still retake.)
|
|
111
|
+
if (ev && ev.code === 4000) {
|
|
112
|
+
console.log('[agent] relay: superseded by a newer instance of this account — standing down (run only one manager per account)');
|
|
113
|
+
currentWs = null;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
105
116
|
console.log(`[agent] relay connection lost — retrying in ${retryMs}ms`);
|
|
106
117
|
scheduleReconnect(retryMs);
|
|
107
118
|
};
|
package/engine/server.mjs
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { createServer } from 'node:http';
|
|
15
15
|
import { spawn, execFile, spawnSync } from 'node:child_process';
|
|
16
|
-
import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync, statSync, readdirSync } from 'node:fs';
|
|
16
|
+
import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync, statSync, readdirSync, unlinkSync } from 'node:fs';
|
|
17
17
|
import { emitEvent } from './analytics-client.mjs';
|
|
18
18
|
import { randomBytes, createHash } from 'node:crypto';
|
|
19
19
|
import { resolve, dirname, join, basename } from 'node:path';
|
|
@@ -2556,6 +2556,16 @@ const server = createServer(async (req, res) => {
|
|
|
2556
2556
|
return;
|
|
2557
2557
|
}
|
|
2558
2558
|
|
|
2559
|
+
// POST /api/signout — forget the local license so the settings panel can
|
|
2560
|
+
// offer a plain "Log out" instead of requiring the `--signin` CLI flag to
|
|
2561
|
+
// switch Google accounts. Clears the on-disk token and the in-memory cache;
|
|
2562
|
+
// idempotent when already signed out.
|
|
2563
|
+
if (url.pathname === '/api/signout' && req.method === 'POST') {
|
|
2564
|
+
if (existsSync(licenseFile)) unlinkSync(licenseFile);
|
|
2565
|
+
licenseState = { email: null, isPaying: false, verifiedAt: 0 };
|
|
2566
|
+
return json(res, 200, { ok: true });
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2559
2569
|
// GET /api/signin-url — mints a one-time nonce and returns the billing
|
|
2560
2570
|
// Worker's /connect URL for "Sign in with Google". The app opens this in the
|
|
2561
2571
|
// browser; Google bounces back to /oauth/callback (below) with a token.
|
package/package.json
CHANGED