diff --git a/.github/workflows/desktop-windows-branch-build.yml b/.github/workflows/desktop-windows-branch-build.yml new file mode 100644 index 00000000..dcb5ac11 --- /dev/null +++ b/.github/workflows/desktop-windows-branch-build.yml @@ -0,0 +1,61 @@ +name: Desktop Windows Branch Build + +on: + workflow_dispatch: + +jobs: + build-windows: + name: Build unsigned Windows desktop artifact + runs-on: windows-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Typecheck + run: npm run typecheck + + - name: Resolve artifact metadata + id: artifact + shell: bash + run: | + SAFE_REF="$(printf '%s' "${GITHUB_REF_NAME}" | tr -c 'A-Za-z0-9._-' '-')" + echo "name=CloudCLI-windows-${SAFE_REF}-${GITHUB_RUN_NUMBER}" >> "$GITHUB_OUTPUT" + + - name: Build unsigned Windows artifacts + run: npm run desktop:dist:win -- --publish never + env: + CSC_IDENTITY_AUTO_DISCOVERY: "false" + + - name: Verify Windows artifacts + shell: bash + run: | + test -n "$(find release -maxdepth 1 -name '*.exe' -print -quit)" + test -n "$(find release -maxdepth 1 -name '*.zip' -print -quit)" + sha256sum release/*.{exe,zip} > release/SHASUMS256.txt + cat release/SHASUMS256.txt + + - name: Upload branch build artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.artifact.outputs.name }} + path: | + release/*.exe + release/*.zip + release/*.yml + release/*.blockmap + release/SHASUMS256.txt + if-no-files-found: error + retention-days: 14 diff --git a/electron/desktopWindow.js b/electron/desktopWindow.js index a22aa49d..606f5f16 100644 --- a/electron/desktopWindow.js +++ b/electron/desktopWindow.js @@ -209,6 +209,11 @@ export class DesktopWindowManager { this.mainWindow.webContents.send('cloudcli-desktop:state-updated', this.getDesktopState()); } + emitLauncherCommand(command) { + if (!this.mainWindow || this.mainWindow.webContents.isDestroyed()) return; + this.mainWindow.webContents.send('cloudcli-desktop:launcher-command', command); + } + async showTarget(target, { trackTab = true } = {}) { if (!this.mainWindow) return; if (trackTab) { @@ -367,8 +372,8 @@ export class DesktopWindowManager { label: 'Services', submenu: [ { - label: 'Computer Use Preview', - click: () => void this.actions.showComputerUsePreview(), + label: 'Computer Access', + click: () => void this.actions.showComputerAccess(), }, ], }, @@ -565,19 +570,10 @@ export class DesktopWindowManager { this.tray.setContextMenu(Menu.buildFromTemplate(template)); } - async showDesktopAppMenu() { + async showDesktopSettings() { if (!this.mainWindow) return this.getDesktopState(); - const menu = Menu.buildFromTemplate([ - { - label: 'Copy Diagnostics', - click: () => void this.actions.copyDiagnostics(), - }, - { - label: 'Computer Use Preview', - click: () => void this.actions.showComputerUsePreview(), - }, - ]); - menu.popup({ window: this.mainWindow }); + await this.showLauncher(); + this.emitLauncherCommand({ type: 'open-sheet', sheet: 'app-settings' }); return this.getDesktopState(); } diff --git a/electron/launcher/launcher.css b/electron/launcher/launcher.css index 76fbed5f..edd18acc 100644 --- a/electron/launcher/launcher.css +++ b/electron/launcher/launcher.css @@ -1 +1,753 @@ -*{box-sizing:border-box}html,body{margin:0;height:100%}:root{--bg:#0a0a0a;--s1:#111111;--s2:#1a1a1a;--s3:#202020;--b-subtle:#1f1f1f;--b:#262626;--b-strong:#333333;--tx:#fafafa;--tx2:#a1a1a1;--tx3:#6b7280;--brand:#0b60ea;--brand-2:#60A5FA;--brand-faint:rgba(11,96,234,.16);--ok:#10b981;--warn:#f59e0b;--err:#ef4444;--tab-hover-bg:rgba(255,255,255,.10);--tab-active-bg:rgba(255,255,255,.16);--tab-active-border:rgba(255,255,255,.18);--mono:'Geist Mono','JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;--sans:'Geist','Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;color-scheme:dark}@media (prefers-color-scheme:light){:root{--bg:#ffffff;--s1:#f7f8fa;--s2:#eef0f3;--s3:#e6e9ee;--b-subtle:#eceef1;--b:#dfe3e8;--b-strong:#c8d0d9;--tx:#0b0d10;--tx2:#5b6470;--tx3:#8a929e;--brand-faint:rgba(11,96,234,.10);--tab-hover-bg:rgba(0,0,0,.06);--tab-active-bg:rgba(0,0,0,.10);--tab-active-border:rgba(0,0,0,.12);color-scheme:light}}body{background:var(--bg);color:var(--tx);font-family:var(--sans);font-size:14px;-webkit-font-smoothing:antialiased;overflow:hidden;user-select:none}input{user-select:text}#app{height:100vh;display:flex;flex-direction:column;min-height:0}button{font:inherit;color:inherit;cursor:pointer;border:0;background:none}input{font:inherit}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-thumb{background:var(--b);border-radius:6px;border:2px solid transparent;background-clip:content-box}.mono{font-family:var(--mono)}.lbl{font-family:var(--mono);font-size:11px;letter-spacing:1.2px;text-transform:uppercase;color:var(--tx2)}svg{display:block}.dot{width:8px;height:8px;border-radius:50%;background:var(--tx3);flex:0 0 auto;display:inline-block}.titlebar{-webkit-app-region:drag;display:flex;align-items:center;gap:12px;height:44px;padding:0 12px;border-bottom:1px solid var(--b-subtle);background:var(--s1);flex:0 0 auto}.titlebar button,.titlebar input,.titlebar .no-drag{-webkit-app-region:no-drag}.brand{display:flex;align-items:center;gap:8px;font-weight:600}.brand .mk{width:22px;height:22px;display:block;flex:0 0 auto;object-fit:contain}.tb-acc{display:inline-flex;align-items:center;gap:7px;font-size:12px;color:var(--tx2);max-width:38vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.btn{display:inline-flex;align-items:center;gap:7px;height:32px;padding:0 13px;border-radius:7px;border:1px solid var(--b);background:var(--s1);color:var(--tx);font-weight:500;transition:border-color .12s,background .12s,filter .12s}.btn:hover{border-color:var(--b-strong);background:var(--s2)}.btn.pri{background:var(--brand);border-color:var(--brand);color:#fff}.btn.pri:hover{filter:brightness(1.08);background:var(--brand)}.btn.sm{height:28px;padding:0 10px;font-size:12px}.btn:disabled{opacity:.55;cursor:default}.icon-btn{width:30px;height:30px;display:grid;place-items:center;border-radius:7px;border:1px solid transparent;color:var(--tx2)}.icon-btn:hover{background:var(--s2);border-color:var(--b);color:var(--tx)}.badge{display:inline-flex;align-items:center;gap:6px;height:21px;padding:0 9px;border-radius:999px;font-size:11px;background:var(--s2);color:var(--tx2);font-family:var(--mono);white-space:nowrap}.badge.ok{color:var(--ok)}.badge.warn{color:var(--warn)}.badge.idle{color:var(--tx3)}.cc-body{flex:1;min-height:0;overflow:auto;position:relative}.statusbar{flex:0 0 auto;display:flex;align-items:center;gap:12px;height:27px;padding:0 12px;border-top:1px solid var(--b-subtle);background:var(--s1);font-size:11px;color:var(--tx2);font-family:var(--mono)}.statusbar .sep{opacity:.4}.status-msg.progress{color:var(--brand-2)}.status-msg.error{color:var(--err)}.cc-overlay{position:fixed;inset:0;background:rgba(0,0,0,.45);display:none;z-index:50;align-items:center;justify-content:center;padding:20px}.cc-overlay.open{display:flex}.cc-sheet{width:420px;max-width:92vw;max-height:86vh;background:var(--s1);border:1px solid var(--b);border-radius:10px;padding:16px;overflow:auto;display:flex;flex-direction:column;gap:18px;box-shadow:0 20px 70px rgba(0,0,0,.35)}.cc-sheet-h{display:flex;align-items:center;justify-content:space-between}.cc-grp{display:flex;flex-direction:column;gap:10px}.cc-row2{display:grid;grid-template-columns:1fr 1fr;gap:8px}.cc-meta{color:var(--tx2);font-size:12px}.cc-toggle{display:grid;grid-template-columns:18px 1fr;gap:10px;align-items:start;color:var(--tx2);font-size:12px;line-height:1.4}.cc-toggle input{width:16px;height:16px;margin-top:1px;accent-color:var(--brand)}.cc-toggle b{color:var(--tx)}.cc-about{margin-top:auto}.v-sidebar{display:grid;grid-template-columns:248px 1fr;overflow:hidden}.sb{display:flex;flex-direction:column;gap:8px;padding:14px 12px;border-right:1px solid var(--b-subtle);background:var(--s1);overflow:auto}.sb-grp{display:flex;flex-direction:column;gap:3px}.sb-grp .lbl{padding:6px 8px}.sb-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;color:var(--tx2);text-align:left}.sb-item>span:nth-child(2){flex:1}.sb-item .sb-meta{font-size:11px;color:var(--tx3);font-family:var(--mono)}.sb-item:hover{background:var(--s2)}.sb-item.active{background:var(--brand-faint);color:var(--tx)}.sb-item.active svg{color:var(--brand-2)}.sb-main{overflow:auto;padding:24px;min-width:0}.pane-h{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:18px}.pane-title{margin:0;font-size:18px;font-weight:600}.pane-sub{margin:4px 0 0;color:var(--tx2);font-size:13px}.card{border:1px solid var(--b);border-radius:10px;background:var(--s1);padding:18px;display:flex;flex-direction:column;gap:16px;max-width:560px}.card-actions{display:flex;gap:8px;flex-wrap:wrap}.env{display:flex;align-items:center;gap:12px;cursor:pointer;padding:12px 14px;border:1px solid var(--b);border-radius:10px;background:var(--s1);margin-bottom:8px}.env:hover{border-color:var(--b-strong)}.env-i{flex:1;min-width:0}.env-n{font-weight:500}.env-u{font-size:12px;color:var(--tx3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.env-tags{display:flex;gap:6px}.tag{font-family:var(--mono);font-size:11px;color:var(--tx2);background:var(--s2);border:1px solid var(--b-subtle);border-radius:5px;padding:2px 7px;white-space:nowrap}.empty{border:1px dashed var(--b);border-radius:10px;padding:28px;text-align:center;color:var(--tx2);max-width:560px}body.mac .titlebar{padding-left:92px;padding-right:12px}body.win .titlebar{padding-right:150px}.titlebar .brand{margin-right:6px}.tb-tabs{display:flex;align-items:center;gap:5px;min-width:0;overflow:hidden}.tb-tab{display:inline-flex;align-items:center;gap:10px;min-width:112px;max-width:232px;flex:0 0 auto;height:30px;padding:0 7px 0 12px;border:1px solid transparent;border-radius:8px;color:var(--tx2);font-size:12px;background:transparent;transition:background .12s,color .12s}.tb-tab:hover{background:var(--tab-hover-bg)}.tb-tab.active{background:var(--tab-active-bg);backdrop-filter:blur(8px);color:var(--tx)}.tb-tab span:first-child{flex:1;min-width:0;max-width:20ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tb-close{display:grid;width:20px;height:20px;margin-left:8px;place-items:center;border-radius:6px;color:var(--tx3);font-size:14px;line-height:1;flex:0 0 auto}.tb-close:hover{background:rgba(255,255,255,.14);color:var(--tx)}.tb-env-actions{display:flex;align-items:center;gap:6px;min-width:0}.tb-env-actions .btn{height:28px;padding:0 9px;font-size:12px}.tb-action{flex:0 0 auto}.card-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.card-tools{display:flex;align-items:center;gap:8px}@media (max-width:760px){.v-sidebar{grid-template-columns:1fr}.sb{flex-direction:row;align-items:center;overflow:auto}.env-tags{display:none}} +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + height: 100%; +} + +:root { + --bg: #111315; + --s1: #171a1d; + --s2: #1e2328; + --s3: #262d34; + --b-subtle: #28303a; + --b: #313b46; + --b-strong: #42505f; + --tx: #f5f7fa; + --tx2: #adb8c5; + --tx3: #7f8b98; + --brand: #0a66d9; + --brand-2: #5fa5ff; + --brand-faint: rgba(10, 102, 217, 0.14); + --ok: #2aa775; + --warn: #d48b20; + --err: #d65252; + --tab-hover-bg: rgba(255, 255, 255, 0.08); + --tab-active-bg: rgba(255, 255, 255, 0.14); + --mono: "SF Mono", "Geist Mono", "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace; + --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, sans-serif; + color-scheme: dark; +} + +:root[data-theme="light"] { + --bg: #f3f5f8; + --s1: #ffffff; + --s2: #f7f9fb; + --s3: #edf1f5; + --b-subtle: #e5eaf0; + --b: #d8dee6; + --b-strong: #c3ccd6; + --tx: #11151a; + --tx2: #566171; + --tx3: #7f8b98; + --brand: #0a66d9; + --brand-2: #0f5fc6; + --brand-faint: rgba(10, 102, 217, 0.09); + --ok: #1f8e61; + --warn: #b67515; + --err: #c24747; + --tab-hover-bg: rgba(15, 23, 42, 0.05); + --tab-active-bg: rgba(15, 23, 42, 0.08); + color-scheme: light; +} + +body { + background: var(--bg); + color: var(--tx); + font-family: var(--sans); + font-size: 14px; + -webkit-font-smoothing: antialiased; + overflow: hidden; + user-select: none; +} + +input { + font: inherit; + user-select: text; +} + +button { + font: inherit; + color: inherit; + cursor: pointer; + border: 0; + background: none; +} + +#app { + height: 100vh; + display: flex; + flex-direction: column; + min-height: 0; +} + +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-thumb { + background: var(--b); + border-radius: 6px; + border: 2px solid transparent; + background-clip: content-box; +} + +svg { + display: block; +} + +.mono { + font-family: var(--mono); +} + +.lbl { + font-family: var(--mono); + font-size: 11px; + letter-spacing: 1.1px; + text-transform: uppercase; + color: var(--tx3); +} + +.dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--tx3); + flex: 0 0 auto; + display: inline-block; +} + +.titlebar { + -webkit-app-region: drag; + display: flex; + align-items: center; + gap: 12px; + height: 44px; + padding: 0 12px; + border-bottom: 1px solid var(--b-subtle); + background: color-mix(in srgb, var(--s1) 90%, transparent); + flex: 0 0 auto; +} + +.titlebar button, +.titlebar input, +.titlebar .no-drag { + -webkit-app-region: no-drag; +} + +.brand { + display: flex; + align-items: center; + gap: 8px; + font-weight: 600; +} + +.brand .mk { + width: 22px; + height: 22px; + display: block; + flex: 0 0 auto; + object-fit: contain; +} + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 7px; + min-width: 0; + height: 32px; + padding: 0 13px; + border-radius: 9px; + border: 1px solid var(--b); + background: var(--s1); + color: var(--tx); + font-weight: 500; + transition: border-color 0.12s, background 0.12s, filter 0.12s; +} + +.btn:hover { + border-color: var(--b-strong); + background: var(--s2); +} + +.btn.pri { + background: var(--brand); + border-color: var(--brand); + color: #fff; +} + +.btn.pri:hover { + filter: brightness(1.05); +} + +.btn.sm { + height: 28px; + padding: 0 10px; + font-size: 12px; +} + +.btn:disabled { + opacity: 0.55; + cursor: default; +} + +.icon-btn { + width: 32px; + height: 32px; + display: grid; + place-items: center; + border-radius: 9px; + border: 1px solid transparent; + color: var(--tx2); +} + +.icon-btn:hover { + background: var(--s2); + border-color: var(--b); + color: var(--tx); +} + +.badge { + display: inline-flex; + align-items: center; + gap: 6px; + min-height: 22px; + padding: 0 9px; + border-radius: 999px; + font-size: 11px; + background: var(--s2); + color: var(--tx2); + border: 1px solid var(--b-subtle); + white-space: nowrap; +} + +.badge.ok { + color: var(--ok); +} + +.badge.warn { + color: var(--warn); +} + +.badge.idle { + color: var(--tx3); +} + +.cc-body { + flex: 1; + min-height: 0; + overflow: auto; + position: relative; +} + +.statusbar { + flex: 0 0 auto; + display: flex; + align-items: center; + gap: 12px; + height: 27px; + padding: 0 12px; + border-top: 1px solid var(--b-subtle); + background: var(--s1); + font-size: 11px; + color: var(--tx2); + font-family: var(--mono); +} + +.statusbar .sep { + opacity: 0.4; +} + +.status-msg.progress { + color: var(--brand-2); +} + +.status-msg.error { + color: var(--err); +} + +.cc-overlay { + position: fixed; + inset: 0; + background: rgba(6, 8, 11, 0.46); + backdrop-filter: blur(16px); + display: none; + z-index: 50; + align-items: center; + justify-content: center; + padding: 24px; +} + +.cc-overlay.open { + display: flex; +} + +.cc-sheet { + width: 620px; + max-width: min(92vw, 620px); + max-height: 86vh; + overflow: hidden; + display: flex; + flex-direction: column; + border-radius: 18px; + border: 1px solid var(--b); + background: color-mix(in srgb, var(--s1) 94%, transparent); + box-shadow: 0 32px 80px rgba(0, 0, 0, 0.32); +} + +.cc-sheet-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + padding: 20px 20px 18px; + border-bottom: 1px solid var(--b-subtle); +} + +.cc-sheet-copy { + min-width: 0; +} + +.cc-sheet-title { + font-size: 20px; + font-weight: 600; + line-height: 1.2; +} + +.cc-sheet-subtitle { + margin-top: 6px; + color: var(--tx2); + line-height: 1.45; +} + +.cc-sheet-close { + flex: 0 0 auto; +} + +.cc-sheet-body { + overflow: auto; + padding: 16px 20px 20px; + display: flex; + flex-direction: column; + gap: 14px; +} + +.cc-sheet-footer { + padding: 14px 20px 18px; + border-top: 1px solid var(--b-subtle); +} + +.cc-section { + display: flex; + flex-direction: column; + gap: 8px; +} + +.cc-section-head { + display: flex; + flex-direction: column; + gap: 2px; +} + +.cc-section-title { + font-size: 14px; + font-weight: 600; + color: var(--tx); +} + +.cc-section-body { + display: flex; + flex-direction: column; + gap: 10px; +} + +.cc-surface { + display: flex; + flex-direction: column; + gap: 10px; + padding: 14px; + border: 1px solid var(--b-subtle); + border-radius: 14px; + background: linear-gradient(180deg, color-mix(in srgb, var(--s2) 86%, transparent), var(--s1)); +} + +.cc-row2 { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; +} + +.cc-meta { + color: var(--tx2); + font-size: 12px; + line-height: 1.45; +} + +.cc-toggle, +.cc-choice { + display: grid; + grid-template-columns: 18px minmax(0, 1fr); + gap: 12px; + align-items: start; + color: var(--tx2); + font-size: 13px; + line-height: 1.45; +} + +.cc-toggle input, +.cc-choice input { + width: 16px; + height: 16px; + margin-top: 2px; + accent-color: var(--brand); +} + +.cc-toggle b, +.cc-choice b { + color: var(--tx); + font-weight: 600; +} + +.cc-choice-group { + gap: 12px; +} + +.cc-kv { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 2px 0; + color: var(--tx2); +} + +.cc-kv span:last-child { + color: var(--tx); + font-weight: 500; +} + +.cc-actions-inline { + display: flex; + gap: 8px; + flex-wrap: wrap; + padding-top: 4px; +} + +.cc-status-badge { + display: inline-flex; + align-items: center; + align-self: flex-start; + min-height: 28px; + padding: 0 10px; + border-radius: 999px; + border: 1px solid var(--b-subtle); + background: var(--s2); + font-size: 12px; + font-weight: 600; +} + +.cc-status-badge.ok { + color: var(--ok); +} + +.cc-status-badge.warn { + color: var(--warn); +} + +.cc-status-badge.idle { + color: var(--tx3); +} + +.v-sidebar { + display: grid; + grid-template-columns: 248px 1fr; + overflow: hidden; +} + +.sb { + display: flex; + flex-direction: column; + gap: 8px; + padding: 14px 12px; + border-right: 1px solid var(--b-subtle); + background: var(--s1); + overflow: auto; +} + +.sb-grp { + display: flex; + flex-direction: column; + gap: 3px; +} + +.sb-grp .lbl { + padding: 6px 8px; +} + +.sb-item { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 10px; + border-radius: 10px; + color: var(--tx2); + text-align: left; +} + +.sb-item > span:nth-child(2) { + flex: 1; +} + +.sb-item .sb-meta { + font-size: 11px; + color: var(--tx3); + font-family: var(--mono); +} + +.sb-item:hover { + background: var(--s2); +} + +.sb-item.active { + background: var(--brand-faint); + color: var(--tx); +} + +.sb-item.active svg { + color: var(--brand-2); +} + +.sb-main { + overflow: auto; + padding: 24px; + min-width: 0; +} + +.pane-h { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + margin-bottom: 18px; +} + +.pane-title { + margin: 0; + font-size: 18px; + font-weight: 600; +} + +.pane-sub { + margin: 4px 0 0; + color: var(--tx2); + font-size: 13px; +} + +.card { + border: 1px solid var(--b); + border-radius: 14px; + background: color-mix(in srgb, var(--s1) 94%, transparent); + padding: 18px; + display: flex; + flex-direction: column; + gap: 16px; + max-width: 620px; + margin-bottom: 12px; + box-shadow: 0 14px 40px rgba(0, 0, 0, 0.08); +} + +.card-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; +} + +.card-tools { + display: flex; + align-items: center; + gap: 8px; +} + +.card-t { + font-size: 15px; + font-weight: 600; +} + +.card-sub { + margin-top: 4px; + color: var(--tx2); + font-size: 13px; + line-height: 1.45; +} + +.card-actions { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.env { + display: flex; + align-items: center; + gap: 12px; + cursor: pointer; + padding: 12px 14px; + border: 1px solid var(--b); + border-radius: 12px; + background: var(--s1); + margin-bottom: 8px; +} + +.env:hover { + border-color: var(--b-strong); +} + +.env-i { + flex: 1; + min-width: 0; +} + +.env-n { + font-weight: 500; +} + +.env-u { + font-size: 12px; + color: var(--tx3); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.env-tags { + display: flex; + gap: 6px; +} + +.tag { + font-family: var(--mono); + font-size: 11px; + color: var(--tx2); + background: var(--s2); + border: 1px solid var(--b-subtle); + border-radius: 999px; + padding: 2px 7px; + white-space: nowrap; +} + +.empty { + border: 1px dashed var(--b); + border-radius: 12px; + padding: 28px; + text-align: center; + color: var(--tx2); + max-width: 560px; +} + +body.mac .titlebar { + padding-left: 92px; + padding-right: 12px; +} + +body.win .titlebar { + padding-right: 150px; +} + +.titlebar .brand { + margin-right: 6px; +} + +.tb-tabs { + display: flex; + align-items: center; + gap: 5px; + min-width: 0; + overflow: hidden; +} + +.tb-tab { + display: inline-flex; + align-items: center; + gap: 10px; + min-width: 112px; + max-width: 232px; + flex: 0 0 auto; + height: 30px; + padding: 0 7px 0 12px; + border: 1px solid transparent; + border-radius: 8px; + color: var(--tx2); + font-size: 12px; + background: transparent; + transition: background 0.12s, color 0.12s; +} + +.tb-tab:hover { + background: var(--tab-hover-bg); +} + +.tb-tab.active { + background: var(--tab-active-bg); + color: var(--tx); +} + +.tb-tab span:first-child { + flex: 1; + min-width: 0; + max-width: 20ch; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.tb-close { + display: grid; + width: 20px; + height: 20px; + margin-left: 8px; + place-items: center; + border-radius: 6px; + color: var(--tx3); + font-size: 14px; + line-height: 1; + flex: 0 0 auto; +} + +.tb-close:hover { + background: var(--tab-hover-bg); + color: var(--tx); +} + +.tb-action { + flex: 0 0 auto; +} + +@media (max-width: 760px) { + .v-sidebar { + grid-template-columns: 1fr; + } + + .sb { + flex-direction: row; + align-items: center; + overflow: auto; + } + + .env-tags { + display: none; + } + + .cc-sheet { + max-width: 100%; + } + + .cc-row2 { + grid-template-columns: 1fr; + } +} diff --git a/electron/launcher/launcher.js b/electron/launcher/launcher.js index 39651ecf..0d5f2aa3 100644 --- a/electron/launcher/launcher.js +++ b/electron/launcher/launcher.js @@ -3,11 +3,12 @@ window.__MOCK_STATE__ = { account: { connected: true, email: 'you@cloudcli.ai' }, activeTarget: { kind: 'launcher', name: 'Launcher', url: null }, cloudLoading: false, - desktopSettings: { keepLocalServerRunning: false, exposeLocalServerOnNetwork: false }, + desktopSettings: { keepLocalServerRunning: false, exposeLocalServerOnNetwork: false, themeMode: 'system' }, localWebUrl: 'http://localhost:3001', shareableWebUrl: 'http://localhost:3001', localServerRunning: false, localStartupLogs: [], + computerUse: { enabled: false, consentMode: 'ask', running: false, connectedCount: 0, targetCount: 0 }, environments: [ { id: 'env-api', name: 'api-gateway', subdomain: 'api-gateway', access_url: 'https://api-gateway.cloudcli.ai', status: 'running', region: 'fra1', agent: 'Claude Code' }, { id: 'env-web', name: 'web-frontend', subdomain: 'web-frontend', access_url: 'https://web-frontend.cloudcli.ai', status: 'stopped', region: 'sfo1', agent: 'Codex' }, @@ -44,10 +45,10 @@ window.__MOCK_STATE__ = { }, refreshEnvironments: function () { return Promise.resolve(clone(mockState)); }, copyDiagnostics: function () { return Promise.resolve(clone(mockState)); }, - showComputerUsePreview: function () { return Promise.resolve(clone(mockState)); }, + showComputerAccess: function () { return Promise.resolve(clone(mockState)); }, showEnvironmentPicker: function () { return Promise.resolve(clone(mockState)); }, showLauncher: function () { return Promise.resolve(clone(mockState)); }, - showDesktopAppMenu: function () { return Promise.resolve(clone(mockState)); }, + showDesktopSettings: function () { return Promise.resolve(clone(mockState)); }, showActiveEnvironmentActionsMenu: function () { return Promise.resolve(clone(mockState)); }, openCloudDashboard: function () { return Promise.resolve(clone(mockState)); }, runActiveEnvironmentAction: function () { return Promise.resolve(clone(mockState)); }, @@ -59,7 +60,17 @@ window.__MOCK_STATE__ = { }, updateSetting: function (key, value) { mockState.desktopSettings = mockState.desktopSettings || {}; - mockState.desktopSettings[key] = !!value; + mockState.desktopSettings[key] = key === 'themeMode' ? value : !!value; + return Promise.resolve(clone(mockState)); + }, + updateComputerUse: function (settings) { + mockState.computerUse = mockState.computerUse || { enabled: false, consentMode: 'ask', running: false, connectedCount: 0, targetCount: 0 }; + if (typeof settings.enabled === 'boolean') mockState.computerUse.enabled = settings.enabled; + if (settings.consentMode === 'auto' || settings.consentMode === 'ask') mockState.computerUse.consentMode = settings.consentMode; + mockState.computerUse.running = mockState.computerUse.enabled; + return Promise.resolve(clone(mockState)); + }, + showComputerAccessPermissions: function () { return Promise.resolve(clone(mockState)); }, openEnvironment: function (id) { @@ -144,6 +155,30 @@ window.__MOCK_STATE__ = { return error && error.message ? error.message : String(error); } + function themeLabel(mode) { + if (mode === 'light') return 'Light'; + if (mode === 'dark') return 'Dark'; + return 'System'; + } + + function resolveTheme(state) { + var settings = state && state.desktopSettings ? state.desktopSettings : {}; + var mode = settings.themeMode || 'system'; + if (mode === 'light' || mode === 'dark') return mode; + return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + } + + function computerUseStatus(state) { + var computerUse = state && state.computerUse ? state.computerUse : {}; + if (!computerUse.enabled) { + return { label: 'Off', tone: 'idle', detail: 'CloudCLI cannot use this computer.' }; + } + if (computerUse.consentMode === 'auto') { + return { label: 'Ready · Unattended', tone: 'warn', detail: 'Trusted agents can use this computer without a local approval prompt.' }; + } + return { label: 'Ready · Ask first', tone: 'ok', detail: 'CloudCLI can use this computer. Agents need approval before control starts.' }; + } + var CC = { icon: icon, esc: esc, @@ -172,7 +207,19 @@ window.__MOCK_STATE__ = { CC.setState = function (state) { if (state && typeof state === 'object') CC.state = state; + CC.applyTheme(CC.state); CC.render(CC.state); + if (CC.ui.openSheet) { + CC.openSheet(CC.ui.openSheet); + } + }; + + CC.applyTheme = function (state) { + var settings = state && state.desktopSettings ? state.desktopSettings : {}; + var themeMode = settings.themeMode || 'system'; + var resolvedTheme = resolveTheme(state); + document.documentElement.setAttribute('data-theme', resolvedTheme); + document.documentElement.setAttribute('data-theme-mode', themeMode); }; CC.refresh = function () { @@ -268,12 +315,30 @@ window.__MOCK_STATE__ = { return CC.run('Copied local URL to clipboard', function () { return bridge.copyLocalWebUrl(); }); case 'diagnostics': return CC.run('Copied diagnostics to clipboard', function () { return bridge.copyDiagnostics(); }); - case 'computer-use': - return CC.run('Opening Computer Use preview...', function () { return bridge.showComputerUsePreview(); }); case 'set-setting': return CC.run('Saved', function () { return bridge.updateSetting(node.key, node.value); }); + case 'set-theme-mode': + return CC.run('Saved', function () { return bridge.updateSetting('themeMode', node.value); }); + case 'set-computer-mode': + return CC.run('Saved', function () { + return bridge.updateComputerUse({ + enabled: true, + consentMode: node.value, + }); + }); + case 'set-computer-enabled': + return CC.run('Saved', function () { + var current = (CC.state && CC.state.computerUse) || { consentMode: 'ask' }; + return bridge.updateComputerUse({ + enabled: !!node.value, + consentMode: current.consentMode === 'auto' ? 'auto' : 'ask', + }); + }); + case 'computer-permissions': + return CC.run('Opening system permissions...', function () { return bridge.showComputerAccessPermissions(); }); case 'settings-toggle': - return CC.run('Opening settings...', function () { return bridge.showDesktopAppMenu(); }); + CC.openSheet('app-settings'); + return; case 'dashboard': return CC.run('Opening CloudCLI dashboard...', function () { return bridge.openCloudDashboard(); }); case 'env-action': @@ -283,11 +348,13 @@ window.__MOCK_STATE__ = { case 'env-row-menu': return CC.run('Opening environment actions...', function () { return bridge.showEnvironmentActionsMenu(node.getAttribute('data-cc-environment-id')); }); case 'local-settings-toggle': - CC.renderLocalSettings(); - overlay.classList.toggle('open'); + CC.openSheet('local-settings'); + return; + case 'computer-settings-toggle': + CC.openSheet('computer-access'); return; case 'settings-close': - overlay.classList.remove('open'); + CC.closeSheet(); return; default: return; @@ -334,22 +401,164 @@ window.__MOCK_STATE__ = { ''; }; - CC.renderLocalSettings = function () { - var state = CC.state || {}; - var settings = state.desktopSettings || {}; - var url = localUrl(state) || 'starts on demand'; + CC.renderSheet = function (title, subtitle, sections, footer) { overlay.innerHTML = '
Run the open-source app on this machine. No account required.