@galda/cli 0.10.12 → 0.10.14

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 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:320px;
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:250px;
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:380px;max-height:calc(100vh - 160px);border-radius:14px;
1011
+ body[data-layout="flagship"] .gearpop{position:fixed;top:120px;right:auto;width:300px;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,8 +2183,9 @@
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">Theme Color…</button>
2186
+ <button class="ghostbtn" id="fsCust">Customize…</button>
2187
2187
  <button class="ghostbtn" id="fsConn">Connect MCP</button>
2188
+ <button class="ghostbtn" id="fsTodoBd">To Do board</button>
2188
2189
  <button class="ghostbtn" id="fsHist">History</button>
2189
2190
  <button class="ghostbtn" id="fsSet">Settings</button>
2190
2191
  </div>
@@ -5400,19 +5401,6 @@ function renderClawdHero(){
5400
5401
  tgUpdate();
5401
5402
  $('fsMoreBtn').onclick = (e) => { e.stopPropagation(); $('fsMoreMenu').classList.toggle('open'); };
5402
5403
  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
- }
5416
5404
  const closeMobilePanels = () => {
5417
5405
  document.body.classList.remove('laneopen', 'projectsopen');
5418
5406
  };
@@ -5434,21 +5422,11 @@ function renderClawdHero(){
5434
5422
  // v46 (HANDOFF-v45 §3): the menu entries deep-link into the 4-tab settings panel —
5435
5423
  // Customize…→THEME / Connect MCP→MCP / History→HISTORY / Settings→SETTINGS.
5436
5424
  // stopPropagation: the document click-away handler otherwise closes it in the same event.
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');
5425
+ $('fsCust').onclick = (e) => { e.stopPropagation(); $('fsMoreMenu').classList.remove('open'); custOpen('theme'); };
5426
+ $('fsConn').onclick = (e) => { e.stopPropagation(); $('fsMoreMenu').classList.remove('open'); custOpen('mcp'); };
5427
+ $('fsTodoBd').onclick = () => { $('fsMoreMenu').classList.remove('open'); $('toggle').click(); }; // To Do board stays reachable from the menu; ⌘K itself now opens the project switcher (HANDOFF-v45 §11)
5428
+ $('fsHist').onclick = (e) => { e.stopPropagation(); $('fsMoreMenu').classList.remove('open'); custOpen('history'); };
5429
+ $('fsSet').onclick = (e) => { e.stopPropagation(); $('fsMoreMenu').classList.remove('open'); custOpen('settings'); };
5452
5430
  // parallel-runs switcher (§1.5-1/-7): same toggle+click-away shape as fsMoreMenu above.
5453
5431
  // Row content is rebuilt by renderFsFeed() (running list changes live); the open/closed
5454
5432
  // class here is left alone by that rebuild so the popover doesn't snap shut mid-render.
@@ -5836,12 +5814,19 @@ function bootDisconnected(info){
5836
5814
  const cw = document.getElementById('composerWrap');
5837
5815
  if (cw) cw.addEventListener('mousedown', (e) => { if (e.target.closest('#appsel, #modelsel, #effortsel')) return; gate(e); });
5838
5816
 
5839
- // come alive automatically: when the relay stops flagging disconnected (the
5840
- // engine connected its live app is proxied), reload into the real board.
5817
+ // Come alive automatically: while this device's engine is offline the relay
5818
+ // 503s every engine-backed path (only /, /theme.css, /fonts.css are served);
5819
+ // the instant the agent connects, the same path is proxied to the live engine
5820
+ // (200). So we probe an engine-only endpoint by STATUS and reload when it stops
5821
+ // 503-ing. We must NOT decide this by fetching `/` and grepping its HTML for a
5822
+ // sentinel: this very poll's source contains the string '__GALDA_DISCONNECTED',
5823
+ // so it is baked into every proxied copy of the page — the match is ALWAYS
5824
+ // "found", the reload never fires, and the tab stays stuck on the connect
5825
+ // screen forever ("npx said done but nothing happens"). (fix: status, not body)
5841
5826
  setInterval(() => {
5842
- fetch('/', { cache: 'no-store', credentials: 'same-origin' }).then((r) => r.text()).then((t) => {
5843
- if (t.indexOf('__GALDA_DISCONNECTED') === -1) location.reload();
5844
- }).catch(() => {});
5827
+ fetch('/api/state', { cache: 'no-store', credentials: 'same-origin' })
5828
+ .then((r) => { if (r.status !== 503) location.reload(); })
5829
+ .catch(() => {});
5845
5830
  }, 4000);
5846
5831
  }
5847
5832
 
@@ -7473,11 +7458,11 @@ function gearSetHTML(){
7473
7458
  <button class="optchip" data-gsignin="1">Sign in with Google</button></div>
7474
7459
  <div class="srow dis" id="licKeyMsg" style="display:none"></div>`;
7475
7460
  } else if (bill.plan === 'paid') {
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>
7461
+ billingRow = `<div class="srow"><div class="sl">Account<span class="sd">Signed in with Google</span></div><span class="bx-sval">${esc(bill.email)}</span></div>
7477
7462
  <div class="srow"><div class="sl">Plan</div><span class="bx-pdot"><i></i>Paid</span></div>
7478
7463
  <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>`;
7479
7464
  } else {
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>
7465
+ billingRow = `<div class="srow"><div class="sl">Account<span class="sd">Signed in with Google</span></div><span class="bx-sval">${esc(bill.email)}</span></div>
7481
7466
  <div class="srow"><div class="sl">Plan</div><span class="bx-sval">Free</span></div>
7482
7467
  <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>
7483
7468
  <div class="srow"><div class="sl">Lifetime</div><span class="bx-sval bx-num">${u.cumulative ?? 0} / ${lim.cumulative ?? 19}</span></div>
@@ -7539,12 +7524,6 @@ function buildGearPop(){
7539
7524
  if (upBtn) upBtn.onclick = () => startCheckout();
7540
7525
  const portalBtn = pop.querySelector('[data-gportal]');
7541
7526
  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
- };
7548
7527
  return;
7549
7528
  }
7550
7529
  if (custTab === 'mcp') {
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, unlinkSync } from 'node:fs';
16
+ import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync, statSync, readdirSync } 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,16 +2556,6 @@ 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
-
2569
2559
  // GET /api/signin-url — mints a one-time nonce and returns the billing
2570
2560
  // Worker's /connect URL for "Sign in with Google". The app opens this in the
2571
2561
  // browser; Google bounces back to /oauth/callback (below) with a token.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@galda/cli",
3
- "version": "0.10.12",
3
+ "version": "0.10.14",
4
4
  "type": "module",
5
5
  "description": "Galda - hand off work to your Claude Code, get proof back. Runs on your existing subscription, no extra API cost.",
6
6
  "scripts": {
@@ -1,20 +0,0 @@
1
- {
2
- "task": {
3
- "id": "task-001",
4
- "title": "Save button toast",
5
- "passCondition": "click the #save button, then an element matching .toast becomes visible with text containing 'Saved' within 2 seconds"
6
- },
7
- "status": "done",
8
- "attempts": [
9
- {
10
- "attempt": 1,
11
- "workerSecs": "21.7",
12
- "pass": true,
13
- "detail": "clicked #save → toast \"Saved!\" visible",
14
- "proofScreenshot": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T06-23-57-441Z/attempt-1-proof.png",
15
- "proofVideo": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T06-23-57-441Z/attempt-1.webm"
16
- }
17
- ],
18
- "runDir": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T06-23-57-441Z",
19
- "finishedAt": "2026-07-03T06:24:22.279Z"
20
- }
@@ -1,20 +0,0 @@
1
- {
2
- "task": {
3
- "id": "task-001",
4
- "title": "Save button toast",
5
- "passCondition": "click the #save button, then an element matching .toast becomes visible with text containing 'Saved' within 2 seconds"
6
- },
7
- "status": "done",
8
- "attempts": [
9
- {
10
- "attempt": 1,
11
- "workerSecs": "19.8",
12
- "pass": true,
13
- "detail": "clicked #save → toast \"Saved!\" visible",
14
- "proofScreenshot": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T06-29-54-517Z/attempt-1-proof.png",
15
- "proofVideo": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T06-29-54-517Z/attempt-1.webm"
16
- }
17
- ],
18
- "runDir": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T06-29-54-517Z",
19
- "finishedAt": "2026-07-03T06:30:17.155Z"
20
- }
@@ -1,20 +0,0 @@
1
- {
2
- "task": {
3
- "id": "task-001",
4
- "title": "Save button toast",
5
- "passCondition": "click the #save button, then an element matching .toast becomes visible with text containing 'Saved' within 2 seconds"
6
- },
7
- "status": "done",
8
- "attempts": [
9
- {
10
- "attempt": 1,
11
- "workerSecs": "18.2",
12
- "pass": true,
13
- "detail": "clicked #save → toast \"Saved!\" visible",
14
- "proofScreenshot": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T06-40-23-231Z/attempt-1-proof.png",
15
- "proofVideo": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T06-40-23-231Z/attempt-1.webm"
16
- }
17
- ],
18
- "runDir": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T06-40-23-231Z",
19
- "finishedAt": "2026-07-03T06:40:46.785Z"
20
- }
@@ -1,21 +0,0 @@
1
- {
2
- "task": {
3
- "id": "task-001",
4
- "title": "Save button toast",
5
- "passCondition": "click the #save button, then an element matching .toast becomes visible with text containing 'Saved' within 2 seconds"
6
- },
7
- "status": "done",
8
- "attempts": [
9
- {
10
- "attempt": 1,
11
- "workerSecs": "22.3",
12
- "pass": true,
13
- "detail": "clicked #save → toast \"Saved!\" visible",
14
- "proofScreenshot": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T07-34-58-109Z/attempt-1-proof.png",
15
- "proofVideo": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T07-34-58-109Z/attempt-1.mp4"
16
- }
17
- ],
18
- "runDir": "/Users/masakinakata/common/project/galda2/tasks/task-001/runs/2026-07-03T07-34-58-109Z",
19
- "finishedAt": "2026-07-03T07:35:25.892Z",
20
- "pr": "https://github.com/kodo-inc/common/pull/1"
21
- }
@@ -1,21 +0,0 @@
1
- {
2
- "task": {
3
- "id": "task-001",
4
- "title": "Save button toast",
5
- "passCondition": "click the #save button, then an element matching .toast becomes visible with text containing 'Saved' within 2 seconds"
6
- },
7
- "status": "done",
8
- "attempts": [
9
- {
10
- "attempt": 1,
11
- "workerSecs": "21.9",
12
- "pass": true,
13
- "detail": "clicked #save → toast \"Saved!\" visible",
14
- "proofScreenshot": "/Users/masakinakata/galda2/tasks/task-001/runs/2026-07-03T07-53-44-639Z/attempt-1-proof.png",
15
- "proofVideo": "/Users/masakinakata/galda2/tasks/task-001/runs/2026-07-03T07-53-44-639Z/attempt-1.mp4"
16
- }
17
- ],
18
- "runDir": "/Users/masakinakata/galda2/tasks/task-001/runs/2026-07-03T07-53-44-639Z",
19
- "finishedAt": "2026-07-03T07:54:11.642Z",
20
- "pr": "https://github.com/kodo-inc/galda2/pull/1"
21
- }
@@ -1,21 +0,0 @@
1
- {
2
- "task": {
3
- "id": "task-001",
4
- "title": "Save button toast",
5
- "passCondition": "click the #save button, then an element matching .toast becomes visible with text containing 'Saved' within 2 seconds"
6
- },
7
- "status": "done",
8
- "attempts": [
9
- {
10
- "attempt": 1,
11
- "workerSecs": "16.3",
12
- "pass": true,
13
- "detail": "clicked #save → toast \"Saved!\" visible",
14
- "proofScreenshot": "/Users/masakinakata/galda2/tasks/task-001/runs/2026-07-03T08-02-12-117Z/attempt-1-proof.png",
15
- "proofVideo": "/Users/masakinakata/galda2/tasks/task-001/runs/2026-07-03T08-02-12-117Z/attempt-1.mp4"
16
- }
17
- ],
18
- "runDir": "/Users/masakinakata/galda2/tasks/task-001/runs/2026-07-03T08-02-12-117Z",
19
- "finishedAt": "2026-07-03T08:02:33.935Z",
20
- "pr": "https://github.com/kodo-inc/galda2/pull/2"
21
- }