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 = '
' + - '
Local Settings
' + - '
Local server
' + - '
' + esc(url) + '
' + - '
' + - '' + - '' + + '
' + + '
' + esc(title) + '
' + esc(subtitle || '') + '
' + + '' + '
' + + '
' + sections.join('') + '
' + + (footer ? '' : '') + '
'; }; + CC.renderSection = function (eyebrow, title, body) { + return '
' + + '
' + + '
' + esc(eyebrow) + '
' + + '
' + esc(title) + '
' + + '
' + + '
' + body + '
' + + '
'; + }; + + CC.renderRadioOption = function (name, value, checked, title, description) { + return ''; + }; + + CC.openSheet = function (sheet) { + if (sheet === 'app-settings') { + CC.renderAppSettings(); + } else if (sheet === 'computer-access') { + CC.renderComputerAccess(); + } else { + CC.renderLocalSettings(); + } + CC.ui.openSheet = sheet; + overlay.classList.add('open'); + }; + + CC.closeSheet = function () { + CC.ui.openSheet = null; + overlay.classList.remove('open'); + }; + + CC.buildLocalServerSection = function (state, options) { + options = options || {}; + var settings = state.desktopSettings || {}; + var url = localUrl(state) || 'starts on demand'; + var body = '
' + + '
' + esc(url) + '
' + + '
'; + if (options.includePreferences) { + body += + '' + + ''; + } + body += '
'; + return CC.renderSection( + options.eyebrow || 'LOCAL SERVER', + options.title || 'Run Local CloudCLI on this machine', + body + ); + }; + + CC.buildThemeSection = function (state) { + var settings = state.desktopSettings || {}; + return CC.renderSection('APPEARANCE', 'Desktop theme', '' + + '
' + + CC.renderRadioOption('desktop-theme', 'system', settings.themeMode === 'system', 'System', 'Follow the operating system appearance.') + + CC.renderRadioOption('desktop-theme', 'light', settings.themeMode === 'light', 'Light', 'Use the light interface appearance.') + + CC.renderRadioOption('desktop-theme', 'dark', settings.themeMode === 'dark', 'Dark', 'Use the dark interface appearance.') + + '
' + ); + }; + + CC.buildComputerAccessSections = function (state, options) { + options = options || {}; + var computerUse = state.computerUse || {}; + var status = computerUseStatus(state); + var sections = []; + + if (options.includeStatus !== false) { + sections.push(CC.renderSection(options.statusEyebrow || 'STATUS', status.label, '' + + '
' + + '
' + esc(status.label) + '
' + + '
' + esc(status.detail) + '
' + + '
' + )); + } + + sections.push(CC.renderSection(options.accessEyebrow || 'ACCESS', 'Allow desktop access', '' + + '
' + + '' + + '
' + )); + + sections.push(CC.renderSection(options.modeEyebrow || 'ACCESS MODE', 'Choose how agent approval works', '' + + '
' + + CC.renderRadioOption('computer-access-mode', 'ask', computerUse.consentMode !== 'auto', 'Ask before each session', 'Agents can request control, but you approve every session.') + + CC.renderRadioOption('computer-access-mode', 'auto', computerUse.consentMode === 'auto', 'Unattended access', 'Trusted agents can use this computer without a local approval prompt.') + + '
' + )); + + var setupBody = '
' + + '
Linked environments' + esc(String(computerUse.connectedCount || 0)) + '
' + + '
Target environments' + esc(String(computerUse.targetCount || 0)) + '
'; + if (options.includeTheme) { + setupBody += '
Theme' + esc(themeLabel((state.desktopSettings && state.desktopSettings.themeMode) || 'system')) + '
'; + } + if (CC.platform === 'mac') { + setupBody += '
'; + } + setupBody += '
'; + sections.push(CC.renderSection(options.setupEyebrow || 'SETUP', 'System permissions and environment links', setupBody)); + return sections; + }; + + CC.renderLocalSettings = function () { + var state = CC.state || {}; + var sections = [ + CC.buildLocalServerSection(state, { includePreferences: false }), + CC.renderSection('PREFERENCES', 'How the local service behaves', '' + + '
' + + '' + + '' + + '
' + ), + CC.buildThemeSection(state), + ]; + CC.renderSheet('Local Settings', 'Manage how Local CloudCLI runs and appears on this computer.', sections); + }; + + CC.renderAppSettings = function () { + var state = CC.state || {}; + var sections = [ + CC.buildLocalServerSection(state, { + eyebrow: 'GENERAL', + title: 'Local CloudCLI', + includePreferences: true, + }), + CC.buildThemeSection(state), + ]; + sections.push.apply(sections, CC.buildComputerAccessSections(state, { + statusEyebrow: 'COMPUTER ACCESS', + modeEyebrow: 'APPROVAL MODE', + includeTheme: false, + })); + CC.renderSheet('Settings', 'Manage local behavior, appearance, and desktop access for this computer.', sections); + }; + + CC.renderComputerAccess = function () { + var state = CC.state || {}; + var sections = CC.buildComputerAccessSections(state, { includeTheme: true }); + CC.renderSheet('Computer Access', 'Let cloud agents use this computer with explicit approval or unattended access.', sections); + }; + CC.render = function (state) { state = state || CC.state; var titlebar = (CC._reg.titlebar || CC.titlebar)(state); @@ -387,7 +596,7 @@ window.__MOCK_STATE__ = { return; } if (overlay.classList.contains('open') && !event.target.closest('.cc-sheet')) { - overlay.classList.remove('open'); + CC.closeSheet(); } }); @@ -398,12 +607,27 @@ window.__MOCK_STATE__ = { key: setting.getAttribute('data-cc-setting'), value: setting.checked, }); + return; + } + var theme = event.target.closest('[name="desktop-theme"]'); + if (theme) { + CC.act('set-theme-mode', { value: theme.value }); + return; + } + var computerMode = event.target.closest('[name="computer-access-mode"]'); + if (computerMode) { + CC.act('set-computer-mode', { value: computerMode.value }); + return; + } + var computerEnabled = event.target.closest('[data-cc-computer-enabled]'); + if (computerEnabled) { + CC.act('set-computer-enabled', { value: computerEnabled.checked }); } }); document.addEventListener('keydown', function (event) { if (event.key === 'Escape' && overlay.classList.contains('open')) { - overlay.classList.remove('open'); + CC.closeSheet(); return; } if ((event.metaKey || event.ctrlKey) && event.key === ',') { @@ -429,9 +653,21 @@ window.__MOCK_STATE__ = { document.body.classList.add(CC.platform); wireEvents(); + if (window.matchMedia) { + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function () { + CC.applyTheme(CC.state); + }); + } if (bridge.onStateUpdated) { bridge.onStateUpdated(function (state) { CC.setState(state); }); } + if (bridge.onLauncherCommand) { + bridge.onLauncherCommand(function (command) { + if (command && command.type === 'open-sheet') { + CC.openSheet(command.sheet); + } + }); + } CC.refresh().catch(function (error) { CC._status = { msg: errMsg(error), tone: 'error' }; CC.render(CC.state); @@ -462,7 +698,9 @@ window.__MOCK_STATE__ = { function localPane(state) { return '

Local CloudCLI

Run the open-source app on this machine. No account required.

' + '
Local server
' + CC.esc(CC.localUrl(state) || 'Starts on demand') + '
' + - '
'; + '
' + + '
Computer Access
' + CC.esc(computerUseStatus(state).detail) + '
' + CC.esc(computerUseStatus(state).label) + '
' + + '
'; } function envRow(environment) { diff --git a/electron/localServer.js b/electron/localServer.js index 391e0c82..0e0fa8a3 100644 --- a/electron/localServer.js +++ b/electron/localServer.js @@ -222,6 +222,7 @@ export class LocalServerController { this.desktopSettings = { keepLocalServerRunning: false, exposeLocalServerOnNetwork: false, + themeMode: 'system', }; } @@ -295,11 +296,13 @@ export class LocalServerController { this.desktopSettings = { keepLocalServerRunning: Boolean(stored.keepLocalServerRunning), exposeLocalServerOnNetwork: Boolean(stored.exposeLocalServerOnNetwork), + themeMode: stored.themeMode === 'light' || stored.themeMode === 'dark' ? stored.themeMode : 'system', }; } catch { this.desktopSettings = { keepLocalServerRunning: false, exposeLocalServerOnNetwork: false, + themeMode: 'system', }; } } @@ -308,6 +311,7 @@ export class LocalServerController { this.desktopSettings = { keepLocalServerRunning: Boolean(nextSettings.keepLocalServerRunning), exposeLocalServerOnNetwork: Boolean(nextSettings.exposeLocalServerOnNetwork), + themeMode: nextSettings.themeMode === 'light' || nextSettings.themeMode === 'dark' ? nextSettings.themeMode : 'system', }; await fs.mkdir(path.dirname(this.settingsPath), { recursive: true }); await fs.writeFile(this.settingsPath, JSON.stringify(this.desktopSettings, null, 2), 'utf8'); @@ -321,7 +325,8 @@ export class LocalServerController { const wasExposeSetting = key === 'exposeLocalServerOnNetwork'; const wasLocalRunning = Boolean(this.localServerUrl); - await this.saveDesktopSettings({ ...this.desktopSettings, [key]: Boolean(value) }); + const nextValue = key === 'themeMode' ? value : Boolean(value); + await this.saveDesktopSettings({ ...this.desktopSettings, [key]: nextValue }); return { desktopSettings: this.desktopSettings, diff --git a/electron/main.js b/electron/main.js index 3a5e749c..ea96a398 100644 --- a/electron/main.js +++ b/electron/main.js @@ -71,7 +71,7 @@ async function promptComputerUseConsent(sessionId) { buttons: ['Allow this session', 'Deny'], defaultId: 0, cancelId: 1, - title: 'Computer Use request', + title: 'Computer Access request', message: 'An agent wants to control this computer', detail: [ 'A cloud agent is requesting control of your mouse, keyboard, and screen for this session.', @@ -248,7 +248,7 @@ async function copyDiagnostics() { }); } -async function showMacComputerUsePermissions() { +async function showMacComputerAccessPermissions() { if (process.platform !== 'darwin') return; const screenStatus = systemPreferences.getMediaAccessStatus('screen'); const accessibilityTrusted = systemPreferences.isTrustedAccessibilityClient(false); @@ -256,7 +256,7 @@ async function showMacComputerUsePermissions() { `Screen Recording: ${screenStatus === 'granted' ? 'granted' : 'not granted'}`, `Accessibility: ${accessibilityTrusted ? 'granted' : 'not granted'}`, '', - 'Computer Use needs both permissions to capture the screen and control the mouse and keyboard.', + 'Computer Access needs both permissions to capture the screen and control the mouse and keyboard.', 'After granting a permission, fully quit and reopen CloudCLI so the change takes effect.', ].join('\n'); @@ -265,8 +265,8 @@ async function showMacComputerUsePermissions() { buttons: ['Open Screen Recording', 'Open Accessibility', 'Close'], defaultId: 0, cancelId: 2, - title: 'Computer Use Permissions', - message: 'Grant macOS permissions for Computer Use', + title: 'Computer Access Permissions', + message: 'Grant macOS permissions for Computer Access', detail, }); @@ -277,58 +277,21 @@ async function showMacComputerUsePermissions() { } } -// Desktop control for cloud Computer Use: the desktop acts as a TeamViewer-style -// agent for hosted environments. Enabling here lets cloud agents drive THIS -// machine; the user picks whether to auto-connect or be asked per session. -async function showComputerUsePreview() { - const state = computerAgent?.getState() || { enabled: false, consentMode: 'ask' }; - const buttons = []; - const actions = []; +async function showComputerAccess() { + await desktopWindow?.showLauncher(); + desktopWindow?.emitLauncherCommand({ type: 'open-sheet', sheet: 'computer-access' }); + return getDesktopState(); +} - if (!state.enabled) { - buttons.push('Enable — ask each session'); actions.push({ kind: 'enable', consentMode: 'ask' }); - buttons.push('Enable — auto-connect'); actions.push({ kind: 'enable', consentMode: 'auto' }); - } else { - buttons.push('Disable Computer Use'); actions.push({ kind: 'disable' }); - const otherMode = state.consentMode === 'auto' ? 'ask' : 'auto'; - buttons.push(`Switch to ${otherMode === 'auto' ? 'auto-connect' : 'ask each session'}`); - actions.push({ kind: 'enable', consentMode: otherMode }); - } - if (process.platform === 'darwin') { - buttons.push('macOS Permissions…'); actions.push({ kind: 'permissions' }); - } - buttons.push('Close'); actions.push({ kind: 'close' }); - - const statusLine = state.enabled - ? `Enabled — ${state.consentMode === 'auto' ? 'auto-connect' : 'ask each session'} · ${state.connectedCount || 0} environment(s) linked` - : 'Disabled'; - - const { response } = await dialog.showMessageBox(desktopWindow?.getMainWindow() || undefined, { - type: 'question', - buttons, - defaultId: 0, - cancelId: buttons.length - 1, - title: 'Computer Use (Desktop Agent)', - message: 'Let cloud agents control this computer', - detail: [ - `Status: ${statusLine}`, - '', - 'When enabled, agents running in your CloudCLI cloud environments can see this screen and drive its mouse and keyboard.', - '• Ask each session: you approve a prompt the first time each session wants control.', - '• Auto-connect: sessions can act without a prompt.', - process.platform === 'linux' ? '\nLinux needs X utilities (libxtst, imagemagick) installed to capture the screen and drive input.' : '', - ].join('\n'), - }); - - const action = actions[response]; - if (!action) return; - if (action.kind === 'enable') { - await computerAgent?.saveSettings({ enabled: true, consentMode: action.consentMode }); - } else if (action.kind === 'disable') { - await computerAgent?.saveSettings({ enabled: false, consentMode: state.consentMode }); - } else if (action.kind === 'permissions') { - await showMacComputerUsePermissions(); - } +async function updateComputerUse(settings) { + const current = computerAgent?.getSettings() || { enabled: false, consentMode: 'ask' }; + const next = { + enabled: typeof settings?.enabled === 'boolean' ? settings.enabled : current.enabled, + consentMode: settings?.consentMode === 'auto' ? 'auto' : 'ask', + }; + await computerAgent?.saveSettings(next); + syncDesktopState(); + return getDesktopState(); } async function refreshCloudEnvironments({ showErrors = false } = {}) { @@ -725,11 +688,16 @@ function registerIpcHandlers() { await desktopWindow.showLauncher(); return getDesktopState(); }); - ipcMain.handle('cloudcli-desktop:show-computer-use-preview', async () => { - await showComputerUsePreview(); + ipcMain.handle('cloudcli-desktop:show-computer-access', async () => { + await showComputerAccess(); return getDesktopState(); }); - ipcMain.handle('cloudcli-desktop:show-desktop-app-menu', async () => desktopWindow.showDesktopAppMenu()); + ipcMain.handle('cloudcli-desktop:update-computer-use', async (_event, settings) => updateComputerUse(settings)); + ipcMain.handle('cloudcli-desktop:show-computer-use-permissions', async () => { + await showMacComputerAccessPermissions(); + return getDesktopState(); + }); + ipcMain.handle('cloudcli-desktop:show-desktop-settings', async () => desktopWindow.showDesktopSettings()); ipcMain.handle('cloudcli-desktop:show-active-environment-actions-menu', async () => desktopWindow.showActiveEnvironmentActionsMenu()); ipcMain.handle('cloudcli-desktop:show-environment-actions-menu', async (_event, environmentId) => desktopWindow.showEnvironmentActionsMenu(environmentId)); ipcMain.handle('cloudcli-desktop:switch-tab', async (_event, tabId) => desktopWindow.switchDesktopTab(tabId)); @@ -812,7 +780,7 @@ async function createDesktopWindow() { openCloudDashboard, refreshCloudEnvironments: () => refreshCloudEnvironments({ showErrors: true }), setActiveTarget, - showComputerUsePreview, + showComputerAccess, showEnvironmentPicker, showError, startEnvironment, diff --git a/electron/preload.cjs b/electron/preload.cjs index 16de7cb7..a02b2d29 100644 --- a/electron/preload.cjs +++ b/electron/preload.cjs @@ -14,8 +14,10 @@ if (window.location.protocol === 'file:') { refreshEnvironments: () => ipcRenderer.invoke('cloudcli-desktop:refresh-environments'), showEnvironmentPicker: () => ipcRenderer.invoke('cloudcli-desktop:show-environment-picker'), showLauncher: () => ipcRenderer.invoke('cloudcli-desktop:show-launcher'), - showComputerUsePreview: () => ipcRenderer.invoke('cloudcli-desktop:show-computer-use-preview'), - showDesktopAppMenu: () => ipcRenderer.invoke('cloudcli-desktop:show-desktop-app-menu'), + showComputerAccess: () => ipcRenderer.invoke('cloudcli-desktop:show-computer-access'), + updateComputerUse: (settings) => ipcRenderer.invoke('cloudcli-desktop:update-computer-use', settings), + showComputerAccessPermissions: () => ipcRenderer.invoke('cloudcli-desktop:show-computer-access-permissions'), + showDesktopSettings: () => ipcRenderer.invoke('cloudcli-desktop:show-desktop-settings'), showActiveEnvironmentActionsMenu: () => ipcRenderer.invoke('cloudcli-desktop:show-active-environment-actions-menu'), showEnvironmentActionsMenu: (environmentId) => ipcRenderer.invoke('cloudcli-desktop:show-environment-actions-menu', environmentId), switchTab: (tabId) => ipcRenderer.invoke('cloudcli-desktop:switch-tab', tabId), @@ -24,5 +26,8 @@ if (window.location.protocol === 'file:') { onStateUpdated: (callback) => { ipcRenderer.on('cloudcli-desktop:state-updated', (_event, state) => callback(state)); }, + onLauncherCommand: (callback) => { + ipcRenderer.on('cloudcli-desktop:launcher-command', (_event, command) => callback(command)); + }, }); } diff --git a/package.json b/package.json index 215e0f74..6cf4f199 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "desktop:dev": "cross-env ELECTRON_DEV_URL=http://127.0.0.1:5173 electron electron/main.js", "desktop:pack": "npm run build && electron-builder --dir", "desktop:dist:mac": "npm run build && electron-builder --mac dmg zip", + "desktop:dist:win": "npm run build && electron-builder --win nsis zip", "desktop:icon:mac": "node electron/scripts/generate-macos-icon.js", "build": "npm run build:client && npm run build:server", "build:client": "vite build", @@ -100,6 +101,12 @@ } ] } + }, + "win": { + "target": [ + "nsis", + "zip" + ] } }, "keywords": [ diff --git a/src/components/sidebar/view/subcomponents/SidebarContent.tsx b/src/components/sidebar/view/subcomponents/SidebarContent.tsx index ce8554e2..7effbd63 100644 --- a/src/components/sidebar/view/subcomponents/SidebarContent.tsx +++ b/src/components/sidebar/view/subcomponents/SidebarContent.tsx @@ -264,7 +264,7 @@ export default function SidebarContent({
- + {projectResult.projectDisplayName}
@@ -284,7 +284,7 @@ export default function SidebarContent({ >
- + {session.sessionSummary} {session.provider && session.provider !== 'claude' && ( @@ -296,7 +296,7 @@ export default function SidebarContent({
{session.matches.map((match, idx) => (
- + {match.role === 'user' ? 'U' : 'A'} - + {t('running.title', 'Running now')}
- + {runningSessionsCount}
@@ -393,7 +393,7 @@ export default function SidebarContent({
- + {project.displayName} @@ -446,7 +446,7 @@ export default function SidebarContent({
- + {(typeof session.summary === 'string' && session.summary.trim().length > 0 ? session.summary : typeof session.name === 'string' && session.name.trim().length > 0 @@ -482,7 +482,7 @@ export default function SidebarContent({
- + {group.projectDisplayName} {group.isProjectArchived && ( @@ -511,7 +511,7 @@ export default function SidebarContent({
- + {session.sessionTitle} {session.lastActivity && ( diff --git a/src/components/sidebar/view/subcomponents/SidebarFooter.tsx b/src/components/sidebar/view/subcomponents/SidebarFooter.tsx index ef1c7178..44f95548 100644 --- a/src/components/sidebar/view/subcomponents/SidebarFooter.tsx +++ b/src/components/sidebar/view/subcomponents/SidebarFooter.tsx @@ -52,7 +52,7 @@ export default function SidebarFooter({
- + {releaseInfo?.title || `v${latestVersion}`} @@ -73,7 +73,7 @@ export default function SidebarFooter({
- + {releaseInfo?.title || `v${latestVersion}`} @@ -150,7 +150,7 @@ export default function SidebarFooter({
- {t('actions.reportIssue')} + {t('actions.reportIssue')}
@@ -165,7 +165,7 @@ export default function SidebarFooter({
- {t('actions.joinCommunity')} + {t('actions.joinCommunity')}
@@ -178,7 +178,7 @@ export default function SidebarFooter({
- {t('actions.settings')} + {t('actions.settings')}
diff --git a/src/components/sidebar/view/subcomponents/SidebarHeader.tsx b/src/components/sidebar/view/subcomponents/SidebarHeader.tsx index 57ac4aa5..f72d6347 100644 --- a/src/components/sidebar/view/subcomponents/SidebarHeader.tsx +++ b/src/components/sidebar/view/subcomponents/SidebarHeader.tsx @@ -167,7 +167,7 @@ export default function SidebarHeader({ aria-label={t('search.runningTooltip', 'Running sessions')} title={t('search.runningTooltip', 'Running sessions')} className={cn( - "flex items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-xs font-medium transition-all", + "flex items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-xs font-normal transition-all", searchMode === 'running' ? "bg-background shadow-sm text-foreground ring-1 ring-emerald-500/15" : "text-muted-foreground hover:text-foreground" @@ -307,7 +307,7 @@ export default function SidebarHeader({ aria-label={t('search.runningTooltip', 'Running sessions')} title={t('search.runningTooltip', 'Running sessions')} className={cn( - "flex items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-xs font-medium transition-all", + "flex items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-xs font-normal transition-all", searchMode === 'running' ? "bg-background shadow-sm text-foreground ring-1 ring-emerald-500/15" : "text-muted-foreground hover:text-foreground" diff --git a/src/components/sidebar/view/subcomponents/TaskIndicator.tsx b/src/components/sidebar/view/subcomponents/TaskIndicator.tsx index 1bcf3169..fe9df610 100644 --- a/src/components/sidebar/view/subcomponents/TaskIndicator.tsx +++ b/src/components/sidebar/view/subcomponents/TaskIndicator.tsx @@ -102,7 +102,7 @@ export default function TaskIndicator({ title={indicatorConfig.title} > - {indicatorConfig.label} + {indicatorConfig.label}
); } diff --git a/src/contexts/ThemeContext.jsx b/src/contexts/ThemeContext.jsx index 1f574bb4..79958e27 100644 --- a/src/contexts/ThemeContext.jsx +++ b/src/contexts/ThemeContext.jsx @@ -91,4 +91,4 @@ export const ThemeProvider = ({ children }) => { {children} ); -}; \ No newline at end of file +}; diff --git a/src/index.css b/src/index.css index c3de46e2..06028a03 100644 --- a/src/index.css +++ b/src/index.css @@ -129,6 +129,8 @@ body { @apply bg-background text-foreground; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; margin: 0; padding: 0; }