@tom2012/cc-web 1.5.20 → 1.5.21

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/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # CC Web
2
2
 
3
- A self-hosted web application (and macOS Electron desktop app) that provides a browser-based interface for [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI sessions. Create projects, each with a persistent terminal running Claude Code, and interact with them through a real-time terminal UI.
3
+ A self-hosted web application (distributed as npm package) that provides a browser-based interface for [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI sessions. Create projects, each with a persistent terminal running Claude Code, and interact with them through a real-time terminal UI.
4
4
 
5
- **Current version**: v1.5.20 | [GitHub](https://github.com/zbc0315/cc-web) | MIT License
5
+ **Current version**: v1.5.21 | [GitHub](https://github.com/zbc0315/cc-web) | MIT License
6
6
 
7
7
  ## Features
8
8
 
@@ -16,7 +16,7 @@ A self-hosted web application (and macOS Electron desktop app) that provides a b
16
16
  - **File Browser**: Browse directories and preview/edit files with zoom-level memory per file
17
17
  - **Auto-restart**: Terminals automatically recover from crashes
18
18
  - **Usage Tracking**: Monitor Claude Code plan usage directly from the dashboard
19
- - **In-app Updates**: Check GitHub releases, download, and install without leaving the app
19
+ - **CLI Update**: `ccweb update` stops the server and updates to the latest npm version
20
20
  - **Localhost Auto-auth**: Local access skips login entirely; JWT only required for remote access
21
21
  - **Auto Port Switching**: Backend tries ports 3001–3020 and reports the actual port
22
22
  - **Network Access Modes**: Local only (127.0.0.1), LAN (private IPs), or public — selectable at startup
@@ -57,6 +57,7 @@ ccweb stop # stop background server
57
57
  ccweb status # show PID, port, data location
58
58
  ccweb open # open browser to running server
59
59
  ccweb setup # reconfigure username / password
60
+ ccweb update # stop server & update to latest version
60
61
  ccweb enable-autostart # start automatically on login
61
62
  ccweb disable-autostart # remove auto-start
62
63
  ccweb logs # tail background log file
@@ -116,7 +117,7 @@ Browser (React/Vite :5173 dev | Express :3001 prod)
116
117
  | `usage-terminal.ts` | Claude Code OAuth usage stats |
117
118
  | `routes/auth.ts` | `POST /login`, `GET /local-token` (localhost only) |
118
119
  | `routes/projects.ts` | CRUD + start/stop + `POST /open` (restore from `.ccweb/`) |
119
- | `routes/update.ts` | `GET /check-running`, `POST /prepare` (save memory → wait idle → stop all) |
120
+ | `routes/update.ts` | `GET /check-running`, `POST /prepare` (save memory → wait idle → stop all, used by in-app update flow) |
120
121
  | `routes/filesystem.ts` | Directory browser, file read/write |
121
122
  | `routes/shortcuts.ts` | Global shortcut CRUD with inheritance |
122
123
  | `routes/backup.ts` | Cloud backup API (providers, OAuth, backup trigger, schedule) |
@@ -140,7 +141,7 @@ Browser (React/Vite :5173 dev | Express :3001 prod)
140
141
  | `components/GraphPreview.tsx` | SVG topology graph of `.notebook/graph.yaml` (layered DAG, zoom/pan) |
141
142
  | `components/FileTree.tsx` | Expandable directory tree |
142
143
  | `components/FilePreviewDialog.tsx` | File viewer with plain/rendered/edit modes, zoom memory per file |
143
- | `components/UpdateButton.tsx` | In-app update: check GitHub → save memory → download → install |
144
+ | `components/UpdateButton.tsx` | Version display and update check |
144
145
  | `components/OpenProjectDialog.tsx` | Open existing project from `.ccweb/` folder |
145
146
  | `components/NewProjectDialog.tsx` | 3-step wizard: name → folder → permissions |
146
147
  | `lib/api.ts` | Typed REST client, dynamic base URL (relative in prod, localhost:3001 in dev) |
@@ -149,7 +150,7 @@ Browser (React/Vite :5173 dev | Express :3001 prod)
149
150
 
150
151
  ### Data Storage
151
152
 
152
- **Application data** (`data/` gitignored, or `~/Library/Application Support/cc-web/data/` in Electron):
153
+ **Application data** (`~/.ccweb/` for npm install, `data/` for dev):
153
154
 
154
155
  ```
155
156
  data/
@@ -226,33 +227,7 @@ Localhost WebSocket connections are pre-authenticated — no `auth` message need
226
227
  | `GET/PUT` | `/api/backup/excludes` | Exclude patterns |
227
228
  | `GET` | `/api/backup/history` | Backup history |
228
229
 
229
- ## macOS Desktop App (Electron)
230
-
231
- CC Web can be packaged as a standalone macOS app using Electron.
232
-
233
- ```bash
234
- # Install all dependencies first
235
- npm run install:all
236
- npm install
237
-
238
- # Build DMG (outputs to release/)
239
- npm run dist:dmg
240
- ```
241
-
242
- The DMG will be at `release/CCWeb-{version}-arm64.dmg`. Double-click to install.
243
-
244
- On first launch, the app auto-generates login credentials and displays them in a dialog. Local access (localhost) skips login entirely. You'll need `claude` CLI installed and authenticated on your machine.
245
-
246
- **Update flow**: Download zip from GitHub releases → extract with `ditto` → replace app via detached shell script (no code signing required).
247
-
248
- ### Building for other architectures
249
-
250
- Edit the `arch` field in `package.json` under `build.mac.target`:
251
- - `["arm64"]` — Apple Silicon (default)
252
- - `["x64"]` — Intel Mac
253
- - `["arm64", "x64"]` — Universal
254
-
255
- ## Server Deployment (without Electron)
230
+ ## Server Deployment
256
231
 
257
232
  ```bash
258
233
  # Build everything
@@ -279,37 +254,26 @@ Environment variables:
279
254
  ## Build & Release
280
255
 
281
256
  ```bash
282
- # Full build (frontend + backend + electron)
283
- npm run build:all
284
-
285
- # Build DMG + ZIP for release
286
- npm run dist
257
+ # Full build (frontend + backend)
258
+ npm run build
287
259
 
288
260
  # Release checklist:
289
- # 1. Bump version in package.json
290
- # 2. Update currentVersion in frontend/src/components/UpdateButton.tsx
291
- # 3. npm run dist
261
+ # 1. Bump version in package.json, UpdateButton.tsx, README.md, CLAUDE.md
262
+ # 2. Update docs with new features
263
+ # 3. npm run build
292
264
  # 4. git add -A && git commit && git push
293
- # 5. gh release create vX.Y.Z --title "CCWeb vX.Y.Z" \
294
- # release/CCWeb-X.Y.Z-arm64.dmg \
295
- # release/CCWeb-X.Y.Z-arm64-mac.zip \
296
- # release/CCWeb-X.Y.Z-arm64-mac.zip.blockmap \
297
- # release/latest-mac.yml
265
+ # 5. npm publish --registry https://registry.npmjs.org --access=public
298
266
  ```
299
267
 
300
- > **Note**: `productName` in `package.json` must not contain spaces (currently `CCWeb`), otherwise GitHub mangles the filename and auto-update gets 404.
301
-
302
268
  ## Development Guide
303
269
 
304
270
  ### Project Structure
305
271
 
306
272
  ```
307
273
  cc-web/
308
- ├── package.json ← Root scripts + Electron build config
274
+ ├── package.json ← Root scripts + npm package config
275
+ ├── bin/ccweb.js ← CLI entry point (ccweb command)
309
276
  ├── setup.js ← Interactive credential setup
310
- ├── electron/
311
- │ ├── main.ts ← Electron main process
312
- │ └── tsconfig.json
313
277
  ├── backend/
314
278
  │ ├── package.json
315
279
  │ ├── tsconfig.json
@@ -331,7 +295,7 @@ cc-web/
331
295
  - **Scrollback buffer**: 5 MB per terminal for client reconnect replay.
332
296
  - **Session pruning**: Keeps latest 20 sessions per project, deletes oldest on new session start.
333
297
  - **Zoom memory**: `FilePreviewDialog` persists zoom level per file path in `localStorage`.
334
- - **Auto port switching**: Backend tries ports 3001–3020, reports actual port to Electron via IPC.
298
+ - **Auto port switching**: Backend tries ports 3001–3020, reports actual port via IPC.
335
299
  - **Localhost auto-auth**: Local requests skip JWT verification entirely.
336
300
 
337
301
  ### Adding a New API Endpoint
@@ -351,7 +315,6 @@ cc-web/
351
315
 
352
316
  **Backend**: Node.js, Express, WebSocket (ws), node-pty, TypeScript
353
317
  **Frontend**: React 18, Vite, Tailwind CSS, shadcn/ui, xterm.js, TypeScript
354
- **Desktop**: Electron
355
318
  **Auth**: JWT (bcryptjs for password hashing)
356
319
 
357
320
  ## License
package/bin/ccweb.js CHANGED
@@ -405,6 +405,36 @@ function showStatus() {
405
405
  }
406
406
  }
407
407
 
408
+ function updatePackage() {
409
+ // Stop running server first
410
+ const status = getStatus();
411
+ if (status.running) {
412
+ console.log('Stopping CCWeb...');
413
+ try {
414
+ process.kill(status.pid, 'SIGTERM');
415
+ try { fs.unlinkSync(PID_FILE); } catch {}
416
+ try { fs.unlinkSync(PORT_FILE); } catch {}
417
+ console.log(`Stopped (PID ${status.pid}).`);
418
+ } catch (err) {
419
+ console.error('Failed to stop:', err.message);
420
+ }
421
+ // Brief wait for process to fully exit
422
+ const deadline = Date.now() + 5000;
423
+ while (Date.now() < deadline && isProcessRunning(status.pid)) {
424
+ execSync('sleep 0.2');
425
+ }
426
+ }
427
+
428
+ console.log('\nUpdating @tom2012/cc-web to latest version...\n');
429
+ try {
430
+ execSync('npm install -g @tom2012/cc-web@latest', { stdio: 'inherit' });
431
+ console.log('\nUpdate complete! Run "ccweb" to start the new version.');
432
+ } catch (err) {
433
+ console.error('\nUpdate failed:', err.message);
434
+ process.exit(1);
435
+ }
436
+ }
437
+
408
438
  function showHelp() {
409
439
  console.log(`
410
440
  CCWeb — Self-hosted Claude Code web interface
@@ -421,6 +451,7 @@ Usage:
421
451
  ccweb status Show running status
422
452
  ccweb open Open browser to running server
423
453
  ccweb setup Reconfigure username / password
454
+ ccweb update Update to latest version
424
455
  ccweb enable-autostart Enable auto-start on login
425
456
  ccweb disable-autostart Disable auto-start on login
426
457
  ccweb logs Tail log file (background mode)
@@ -480,6 +511,10 @@ const accessModeFlag = args.includes('--local') ? 'local'
480
511
  await disableAutoStart();
481
512
  break;
482
513
 
514
+ case 'update':
515
+ updatePackage();
516
+ break;
517
+
483
518
  case 'logs': {
484
519
  if (!fs.existsSync(LOG_FILE)) { console.log('No log file found.'); break; }
485
520
  // Tail the log file (cross-platform)
@@ -403,7 +403,7 @@ Error generating stack: `+s.message+`
403
403
 
404
404
  If you want to hide the \`${t.titleName}\`, you can wrap it with our VisuallyHidden component.
405
405
 
406
- For more information, see https://radix-ui.com/primitives/docs/components/${t.docsSlug}`;return y.useEffect(()=>{e&&(document.getElementById(e)||console.error(n))},[n,e]),null},i3="DialogDescriptionWarning",s3=({contentRef:e,descriptionId:t})=>{const r=`Warning: Missing \`Description\` or \`aria-describedby={undefined}\` for {${gA(i3).contentName}}.`;return y.useEffect(()=>{var s;const i=(s=e.current)==null?void 0:s.getAttribute("aria-describedby");t&&i&&(document.getElementById(t)||console.warn(r))},[r,e,t]),null},a3=nA,o3=sA,mA=aA,bA=oA,_A=cA,yA=dA,l3=pA;const ul=a3,c3=o3,vA=y.forwardRef(({className:e,...t},n)=>h.jsx(mA,{ref:n,className:ee("fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",e),...t}));vA.displayName=mA.displayName;const fa=y.forwardRef(({className:e,children:t,...n},r)=>h.jsxs(c3,{children:[h.jsx(vA,{}),h.jsxs(bA,{ref:r,className:ee("fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",e),...n,children:[t,h.jsxs(l3,{className:"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",children:[h.jsx(rs,{className:"h-4 w-4"}),h.jsx("span",{className:"sr-only",children:"Close"})]})]})]}));fa.displayName=bA.displayName;const ga=({className:e,...t})=>h.jsx("div",{className:ee("flex flex-col space-y-1.5 text-center sm:text-left",e),...t});ga.displayName="DialogHeader";const ma=({className:e,...t})=>h.jsx("div",{className:ee("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",e),...t});ma.displayName="DialogFooter";const ba=y.forwardRef(({className:e,...t},n)=>h.jsx(_A,{ref:n,className:ee("text-lg font-semibold leading-none tracking-tight",e),...t}));ba.displayName=_A.displayName;const _a=y.forwardRef(({className:e,...t},n)=>h.jsx(yA,{ref:n,className:ee("text-sm text-muted-foreground",e),...t}));_a.displayName=yA.displayName;function SA({onSelect:e}){const[t,n]=y.useState(""),[r,i]=y.useState(null),[s,a]=y.useState([]),[o,l]=y.useState(!0),[c,d]=y.useState(null),[u,p]=y.useState(!1),[f,g]=y.useState(""),[b,v]=y.useState(null),[m,_]=y.useState(!1),S=y.useRef(null),w=async O=>{l(!0),d(null);try{const A=await DT(O);n(A.path),i(A.parent),a(A.entries)}catch(A){d(A instanceof Error?A.message:"Failed to load directory")}finally{l(!1)}};y.useEffect(()=>{w()},[]),y.useEffect(()=>{u&&setTimeout(()=>{var O;return(O=S.current)==null?void 0:O.focus()},0)},[u]);const k=()=>{g(""),v(null),p(!0)},E=()=>{p(!1),g(""),v(null)},C=async()=>{const O=f.trim();if(O){_(!0),v(null);try{const{path:A}=await AP(t,O);p(!1),g(""),await w(t),w(A)}catch(A){v(A instanceof Error?A.message:"Failed to create folder")}finally{_(!1)}}},R=O=>{O.key==="Enter"?(O.preventDefault(),C()):O.key==="Escape"&&E()},M=t.split("/").filter(Boolean),N=O=>{const A="/"+M.slice(0,O+1).join("/");w(A)};return h.jsxs("div",{className:"flex flex-col h-64 border rounded-md overflow-hidden",children:[h.jsxs("div",{className:"flex items-center gap-1 px-2 py-2 bg-muted border-b flex-shrink-0",children:[h.jsx(re,{variant:"ghost",size:"sm",className:"h-6 px-1 flex-shrink-0",onClick:()=>void w(),title:"Home",children:h.jsx(sF,{className:"h-3 w-3"})}),h.jsx("div",{className:"flex items-center gap-1 flex-1 overflow-x-auto min-w-0",children:M.map((O,A)=>h.jsxs(ft.Fragment,{children:[h.jsx(Em,{className:"h-3 w-3 text-muted-foreground flex-shrink-0"}),h.jsx("button",{className:"text-xs hover:text-foreground text-muted-foreground whitespace-nowrap",onClick:()=>N(A),children:O})]},A))}),h.jsxs(re,{variant:"ghost",size:"sm",className:"h-6 px-1.5 flex-shrink-0 gap-1 text-xs",onClick:k,disabled:u||o||!!c,title:"New folder",children:[h.jsx(iF,{className:"h-3.5 w-3.5"}),h.jsx("span",{className:"hidden sm:inline",children:"New folder"})]})]}),h.jsxs("div",{className:"flex-1 overflow-y-auto",children:[o&&h.jsx("div",{className:"flex items-center justify-center h-full text-sm text-muted-foreground",children:"Loading..."}),c&&h.jsx("div",{className:"flex items-center justify-center h-full text-sm text-destructive px-4 text-center",children:c}),!o&&!c&&h.jsxs("div",{className:"p-1",children:[r&&h.jsxs("button",{className:"flex items-center gap-2 w-full px-2 py-1.5 text-sm rounded hover:bg-accent hover:text-accent-foreground",onClick:()=>void w(r),children:[h.jsx(du,{className:"h-4 w-4 text-muted-foreground"}),h.jsx("span",{children:".."})]}),u&&h.jsxs("div",{className:"flex items-center gap-1 px-2 py-1.5",children:[h.jsx(uf,{className:"h-4 w-4 text-blue-500 flex-shrink-0"}),h.jsx("input",{ref:S,className:ee("flex-1 text-sm bg-background border rounded px-1.5 py-0.5 outline-none","focus:ring-1 focus:ring-ring",b&&"border-destructive"),placeholder:"Folder name",value:f,onChange:O=>{g(O.target.value),v(null)},onKeyDown:R,disabled:m}),h.jsx("button",{className:"p-1 rounded hover:bg-accent disabled:opacity-50",onClick:()=>void C(),disabled:!f.trim()||m,title:"Create",children:h.jsx(BT,{className:"h-3.5 w-3.5 text-green-600"})}),h.jsx("button",{className:"p-1 rounded hover:bg-accent",onClick:E,disabled:m,title:"Cancel",children:h.jsx(rs,{className:"h-3.5 w-3.5 text-muted-foreground"})})]}),b&&h.jsx("p",{className:"px-3 pb-1 text-xs text-destructive",children:b}),s.length===0&&!u&&h.jsx("div",{className:"text-sm text-muted-foreground px-2 py-4 text-center",children:"Empty directory"}),s.map(O=>h.jsxs("button",{className:ee("flex items-center gap-2 w-full px-2 py-1.5 text-sm rounded text-left",O.type==="dir"?"hover:bg-accent hover:text-accent-foreground cursor-pointer":"opacity-40 cursor-not-allowed"),onClick:()=>{O.type==="dir"&&w(O.path)},disabled:O.type==="file",children:[O.type==="dir"?h.jsx(uf,{className:"h-4 w-4 text-blue-500 flex-shrink-0"}):h.jsx(rF,{className:"h-4 w-4 text-muted-foreground flex-shrink-0"}),h.jsx("span",{className:"truncate",children:O.name})]},O.path))]})]}),h.jsxs("div",{className:"border-t px-3 py-2 flex items-center justify-between gap-2 flex-shrink-0 bg-background",children:[h.jsx("span",{className:"text-xs text-muted-foreground truncate flex-1",children:t}),h.jsx(re,{size:"sm",onClick:()=>e(t),disabled:!t,children:"Select this folder"})]})]})}const u3=[{value:"claude",label:"Claude",desc:"Anthropic Claude Code CLI"},{value:"opencode",label:"OpenCode",desc:"OpenCode CLI (sst/opencode)"},{value:"codex",label:"Codex",desc:"OpenAI Codex CLI"},{value:"qwen",label:"Qwen",desc:"Qwen Code CLI (QwenLM)"}],fx={claude:{limited:"claude",unlimited:"claude --dangerously-skip-permissions"},opencode:{limited:"opencode",unlimited:"opencode --dangerously-skip-permissions"},codex:{limited:"codex",unlimited:"codex --ask-for-approval never --sandbox danger-full-access"},qwen:{limited:"qwen-code",unlimited:"qwen-code --yolo"}};function d3({open:e,onOpenChange:t,onCreated:n}){const[r,i]=y.useState("name"),[s,a]=y.useState(""),[o,l]=y.useState(""),[c,d]=y.useState("claude"),[u,p]=y.useState("limited"),[f,g]=y.useState(!1),[b,v]=y.useState(null),m=()=>{i("name"),a(""),l(""),d("claude"),p("limited"),v(null),g(!1)},_=R=>{R||m(),t(R)},S=()=>{if(!s.trim()){v("Please enter a project name");return}v(null),i("folder")},w=R=>{l(R),i("settings")},k=async()=>{g(!0),v(null);try{const R=await uP({name:s.trim(),folderPath:o,permissionMode:u,cliTool:c});n(R),_(!1)}catch(R){v(R instanceof Error?R.message:"Failed to create project")}finally{g(!1)}},E={name:"New Project — Name",folder:"New Project — Select Folder",settings:"New Project — Settings"},C=["name","folder","settings"];return h.jsx(ul,{open:e,onOpenChange:_,children:h.jsxs(fa,{className:"sm:max-w-lg",children:[h.jsxs(ga,{children:[h.jsx(ba,{children:E[r]}),h.jsxs(_a,{children:[r==="name"&&"Give your project a name.",r==="folder"&&"Choose the working directory for this project.",r==="settings"&&"Choose the AI coding tool and run mode."]})]}),h.jsx("div",{className:"flex gap-2 mb-2",children:C.map((R,M)=>h.jsx("div",{className:`h-1.5 flex-1 rounded-full ${r===R?"bg-primary":C.indexOf(r)>M?"bg-primary/40":"bg-muted"}`},R))}),r==="name"&&h.jsxs("div",{className:"space-y-4",children:[h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{htmlFor:"project-name",children:"Project Name"}),h.jsx(gr,{id:"project-name",placeholder:"My Project",value:s,onChange:R=>a(R.target.value),onKeyDown:R=>{R.key==="Enter"&&S()},autoFocus:!0})]}),b&&h.jsx("p",{className:"text-sm text-destructive",children:b})]}),r==="folder"&&h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{children:"Working Directory"}),h.jsx(SA,{onSelect:w})]}),r==="settings"&&h.jsxs("div",{className:"space-y-5",children:[h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{children:"AI Coding Tool"}),h.jsx("div",{className:"grid grid-cols-2 gap-2",children:u3.map(R=>h.jsxs("label",{className:`flex items-center gap-2.5 cursor-pointer p-3 rounded-md border transition-colors ${c===R.value?"border-primary bg-primary/5":"hover:bg-accent"}`,children:[h.jsx("input",{type:"radio",name:"cliTool",value:R.value,checked:c===R.value,onChange:()=>d(R.value),className:"accent-primary"}),h.jsxs("div",{children:[h.jsx("div",{className:"font-medium text-sm",children:R.label}),h.jsx("div",{className:"text-xs text-muted-foreground",children:R.desc})]})]},R.value))})]}),h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{children:"Permission Mode"}),h.jsxs("div",{className:"space-y-2",children:[h.jsxs("label",{className:"flex items-start gap-3 cursor-pointer p-3 rounded-md border hover:bg-accent transition-colors",children:[h.jsx("input",{type:"radio",name:"permissionMode",value:"limited",checked:u==="limited",onChange:()=>p("limited"),className:"mt-0.5"}),h.jsxs("div",{children:[h.jsx("div",{className:"font-medium text-sm",children:"Limited"}),h.jsxs("div",{className:"text-xs text-muted-foreground",children:["Runs ",h.jsx("code",{className:"bg-muted px-1 rounded",children:fx[c].limited})," — asks for permission before file changes."]})]})]}),h.jsxs("label",{className:"flex items-start gap-3 cursor-pointer p-3 rounded-md border hover:bg-accent transition-colors",children:[h.jsx("input",{type:"radio",name:"permissionMode",value:"unlimited",checked:u==="unlimited",onChange:()=>p("unlimited"),className:"mt-0.5"}),h.jsxs("div",{children:[h.jsx("div",{className:"font-medium text-sm",children:"Unlimited"}),h.jsxs("div",{className:"text-xs text-muted-foreground",children:["Runs ",h.jsx("code",{className:"bg-muted px-1 rounded",children:fx[c].unlimited})," — acts autonomously."]})]})]})]})]}),h.jsxs("div",{className:"text-xs text-muted-foreground",children:[h.jsx("span",{className:"font-medium",children:"Folder:"})," ",h.jsx("span",{className:"font-mono",children:o})]}),b&&h.jsx("p",{className:"text-sm text-destructive",children:b})]}),h.jsxs(ma,{children:[r!=="name"&&h.jsx(re,{variant:"outline",onClick:()=>i(r==="settings"?"folder":"name"),disabled:f,children:"Back"}),r==="name"&&h.jsx(re,{onClick:S,children:"Next"}),r==="folder"&&h.jsx(re,{variant:"outline",onClick:()=>t(!1),children:"Cancel"}),r==="settings"&&h.jsx(re,{onClick:()=>void k(),disabled:f,children:f?"Creating...":"Create Project"})]})]})})}function h3({open:e,onOpenChange:t,onOpened:n}){const[r,i]=y.useState(""),[s,a]=y.useState(!1),[o,l]=y.useState(null),c=()=>{i(""),l(null),a(!1)},d=p=>{p||c(),t(p)},u=async p=>{i(p),a(!0),l(null);try{const f=await dP(p);n(f),d(!1)}catch(f){l(f instanceof Error?f.message:"Failed to open project"),a(!1)}};return h.jsx(ul,{open:e,onOpenChange:d,children:h.jsxs(fa,{className:"sm:max-w-lg",children:[h.jsxs(ga,{children:[h.jsx(ba,{children:"Open Existing Project"}),h.jsxs(_a,{children:["Select a folder that contains a ",h.jsx("code",{className:"bg-muted px-1 rounded text-xs",children:".ccweb/"})," configuration. The project's history and settings will be restored."]})]}),h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{children:"Project Folder"}),h.jsx(SA,{onSelect:p=>void u(p)})]}),o&&h.jsx("p",{className:"text-sm text-destructive",children:o}),r&&!o&&s&&h.jsxs("p",{className:"text-sm text-muted-foreground",children:["Opening project from ",r,"..."]}),h.jsx(ma,{children:h.jsx(re,{variant:"outline",onClick:()=>d(!1),disabled:s,children:"Cancel"})})]})})}function p3(e){if(!e)return"";const t=new Date(e).getTime()-Date.now();if(t<=0)return"即将重置";const n=Math.floor(t/36e5),r=Math.floor(t%36e5/6e4);return n>0?`${n}h${r}m`:`${r}m`}function Yl({label:e,bucket:t}){if(!t||t.utilization===void 0)return null;const n=t.utilization,r=n<50?"text-green-400":n<80?"text-yellow-400":"text-red-400",i=p3(t.resetAt);return h.jsxs("span",{className:"text-muted-foreground whitespace-nowrap",children:[e," ",h.jsxs("span",{className:ee("font-medium",r),children:[n,"%"]}),i&&h.jsxs("span",{className:"text-muted-foreground/70 ml-0.5",children:["(",i,")"]})]})}function wA({className:e}){const[t,n]=y.useState("loading"),[r,i]=y.useState(!1);y.useEffect(()=>{const u=async()=>{try{n(await kP())}catch{n(null)}};u();const p=setInterval(()=>void u(),5*60*1e3);return()=>clearInterval(p)},[]);const s=async()=>{i(!0);try{n(await CP())}catch{n(null)}finally{i(!1)}};if(t==="loading"||!t)return null;const a=!!t.fiveHour,o=!!t.sevenDay,l=!!t.sevenDaySonnet,c=!!t.sevenDayOpus,d=a||o||l||c;return h.jsxs("div",{className:ee("flex items-center gap-1.5 text-xs",e),children:[t.planName&&h.jsx("span",{className:"font-medium text-foreground bg-muted border border-border px-2 py-0.5 rounded-full",children:t.planName}),d&&h.jsxs(h.Fragment,{children:[h.jsx("span",{className:"text-muted-foreground/50",children:"|"}),h.jsx(Yl,{label:"5h",bucket:t.fiveHour}),o&&h.jsxs(h.Fragment,{children:[h.jsx("span",{className:"text-muted-foreground/50",children:"·"}),h.jsx(Yl,{label:"7d",bucket:t.sevenDay})]}),l&&h.jsxs(h.Fragment,{children:[h.jsx("span",{className:"text-muted-foreground/50",children:"·"}),h.jsx(Yl,{label:"7d Sonnet",bucket:t.sevenDaySonnet})]}),c&&h.jsxs(h.Fragment,{children:[h.jsx("span",{className:"text-muted-foreground/50",children:"·"}),h.jsx(Yl,{label:"7d Opus",bucket:t.sevenDayOpus})]})]}),h.jsx("button",{onClick:()=>void s(),className:"text-muted-foreground hover:text-foreground transition-colors",title:"刷新用量信息",children:h.jsx(Wi,{className:ee("h-3 w-3",r&&"animate-spin")})})]})}const gx="v1.5.20",Xl=!!window.electronUpdater;function f3(e,t){const n=e.split(".").map(Number),r=t.split(".").map(Number);for(let i=0;i<Math.max(n.length,r.length);i++){const s=n[i]||0,a=r[i]||0;if(s!==a)return s-a}return 0}function g3(){const[e,t]=y.useState("idle"),[n,r]=y.useState(!1),[i,s]=y.useState(""),[a,o]=y.useState(0),[l,c]=y.useState([]),[d,u]=y.useState([]),[p,f]=y.useState(null),[g,b]=y.useState(null);y.useEffect(()=>window.electronUpdater?window.electronUpdater.onUpdateStatus(E=>{switch(E.type){case"progress":{const C=E.info;o(Math.round((C==null?void 0:C.percent)??0));break}case"downloaded":t("downloaded");break;case"error":f(E.info||"Download failed"),t("error");break}}):void 0,[]);const v=y.useCallback(async()=>{t("checking"),f(null),r(!0);try{if(Xl){const E=await window.electronUpdater.checkForUpdate();if(E.error)throw new Error(E.error);if(!E.available){t("no_update");return}s(E.version?`v${E.version}`:"new version")}else{const E=await fetch("https://api.github.com/repos/zbc0315/cc-web/releases/latest");if(E.status===404){t("no_update");return}if(!E.ok)throw new Error(`GitHub API ${E.status}`);const C=await E.json(),R=C.tag_name.replace(/^v/,""),M=gx.replace(/^v/,"");if(f3(R,M)<=0){t("no_update");return}s(C.tag_name)}const k=await RP();c(k.projects),t(k.runningCount>0?"confirm_prepare":"update_available")}catch(k){f(k instanceof Error?k.message:"Check failed"),t("error")}},[]),m=async()=>{t("preparing");try{const k=await IP();u(k.results),t("update_available")}catch(k){f(k instanceof Error?k.message:"Prepare failed"),t("error")}},_=async()=>{if(Xl){t("downloading"),o(0);const k=await window.electronUpdater.downloadUpdate();k.success?k.path&&b(k.path):(f(k.error||"Download failed"),t("error"))}else window.open("https://github.com/zbc0315/cc-web/releases/latest","_blank")},S=()=>{var k;(k=window.electronUpdater)==null||k.quitAndInstall(g||void 0)},w=()=>{r(!1),setTimeout(()=>{t("idle"),s(""),c([]),u([]),f(null),o(0)},200)};return h.jsxs(h.Fragment,{children:[h.jsxs(re,{variant:"ghost",size:"sm",onClick:()=>void v(),disabled:e==="checking"||e==="preparing"||e==="downloading",children:[e==="checking"||e==="preparing"||e==="downloading"?h.jsx(Fi,{className:"h-4 w-4 mr-2 animate-spin"}):h.jsx(UT,{className:"h-4 w-4 mr-2"}),"Update"]}),h.jsx(ul,{open:n,onOpenChange:k=>{k||w()},children:h.jsxs(fa,{className:"sm:max-w-md",children:[h.jsxs(ga,{children:[h.jsxs(ba,{children:[e==="checking"&&"Checking for updates...",e==="no_update"&&"Up to date",e==="update_available"&&`Update ${i} available`,e==="confirm_prepare"&&"Save project memory first",e==="preparing"&&"Saving project memory...",e==="downloading"&&"Downloading update...",e==="downloaded"&&"Update ready to install",e==="error"&&"Update error"]}),h.jsxs(_a,{children:[e==="checking"&&"Checking for the latest version...",e==="no_update"&&`Current version ${gx} is the latest.`,e==="update_available"&&(d.length>0?"All projects saved and stopped. Ready to update.":`A new version is available. ${Xl?"Click download to update automatically.":""}`),e==="confirm_prepare"&&`${l.length} project(s) running. Each Claude will be asked to save memory, then terminals will stop.`,e==="preparing"&&"Sending save commands and waiting for Claude to finish...",e==="downloading"&&`Downloading... ${a}%`,e==="downloaded"&&"The update has been downloaded. Click install to restart and apply.",e==="error"&&(p||"An error occurred.")]})]}),e==="downloading"&&h.jsx("div",{className:"w-full bg-muted rounded-full h-2 overflow-hidden",children:h.jsx("div",{className:"bg-primary h-full transition-all duration-300",style:{width:`${a}%`}})}),e==="confirm_prepare"&&l.length>0&&h.jsx("div",{className:"space-y-1 text-sm",children:l.map(k=>h.jsxs("div",{className:"flex items-center gap-2 px-3 py-1.5 rounded bg-muted",children:[h.jsx("span",{className:"h-2 w-2 rounded-full bg-green-500 shrink-0"}),h.jsx("span",{className:"truncate",children:k.name})]},k.id))}),e==="preparing"&&h.jsx("div",{className:"flex items-center justify-center py-6",children:h.jsx(Fi,{className:"h-8 w-8 animate-spin text-muted-foreground"})}),e==="update_available"&&d.length>0&&h.jsx("div",{className:"space-y-1 text-sm",children:d.map(k=>h.jsxs("div",{className:"flex items-center gap-2 px-3 py-1.5 rounded bg-muted",children:[k.status==="stopped"?h.jsx(QP,{className:"h-4 w-4 text-green-500 shrink-0"}):h.jsx(qP,{className:"h-4 w-4 text-yellow-500 shrink-0"}),h.jsx("span",{className:"truncate",children:k.name}),h.jsx("span",{className:"text-xs text-muted-foreground ml-auto shrink-0",children:k.message})]},k.id))}),h.jsxs(ma,{children:[e==="no_update"&&h.jsx(re,{onClick:w,children:"OK"}),e==="update_available"&&h.jsxs(h.Fragment,{children:[h.jsx(re,{variant:"outline",onClick:w,children:"Later"}),h.jsx(re,{onClick:()=>void _(),children:Xl?"Download & Install":`Download ${i}`})]}),e==="confirm_prepare"&&h.jsxs(h.Fragment,{children:[h.jsx(re,{variant:"outline",onClick:w,children:"Cancel"}),h.jsx(re,{onClick:()=>void m(),children:"Save Memory & Stop All"})]}),e==="downloaded"&&h.jsxs(h.Fragment,{children:[h.jsx(re,{variant:"outline",onClick:w,children:"Later"}),h.jsx(re,{onClick:S,children:"Restart & Install"})]}),e==="error"&&h.jsx(re,{onClick:w,children:"Close"})]})]})})]})}function m3(){const[e,t]=y.useState([]),[n,r]=y.useState(!1),[i,s]=y.useState(""),[a,o]=y.useState(""),[l,c]=y.useState(""),[d,u]=y.useState(null),[p,f]=y.useState(""),[g,b]=y.useState(""),[v,m]=y.useState(""),_=y.useRef(null),S=y.useRef(null);y.useEffect(()=>{LT().then(t).catch(()=>t([]))},[]),y.useEffect(()=>{n&&setTimeout(()=>{var C;return(C=_.current)==null?void 0:C.focus()},0)},[n]),y.useEffect(()=>{d&&setTimeout(()=>{var C;return(C=S.current)==null?void 0:C.focus()},0)},[d]);const w=async()=>{const C=a.trim();if(C)try{const R=await _P({label:i.trim()||C,command:C,...l?{parentId:l}:{}});t(M=>[...M,R]),s(""),o(""),c(""),r(!1)}catch(R){console.error("[GlobalShortcuts] Failed to add:",R)}},k=async C=>{try{await vP(C),t(R=>R.filter(M=>M.id!==C)),d===C&&u(null)}catch(R){console.error("[GlobalShortcuts] Failed to delete:",R)}},E=async()=>{const C=g.trim();if(!(!C||!d))try{const R=await yP(d,{label:p.trim()||C,command:C,parentId:v||null});t(M=>M.map(N=>N.id===d?R:N)),u(null)}catch(R){console.error("[GlobalShortcuts] Failed to save:",R)}};return h.jsxs("section",{className:"mt-10",children:[h.jsxs("div",{className:"flex items-center justify-between mb-4",children:[h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx(hu,{className:"h-4 w-4 text-muted-foreground"}),h.jsx("h2",{className:"text-lg font-semibold",children:"全局快捷命令"}),h.jsx("span",{className:"text-xs text-muted-foreground",children:"在所有项目的终端中可用"})]}),h.jsxs("button",{onClick:()=>{r(C=>!C),u(null)},className:ee("flex items-center gap-1.5 text-sm px-3 py-1.5 rounded-md border transition-colors",n?"border-border text-foreground bg-muted":"border-border text-muted-foreground hover:text-foreground hover:bg-muted"),children:[h.jsx(Ho,{className:"h-3.5 w-3.5"}),"新建"]})]}),n&&h.jsxs("div",{className:"mb-4 p-4 rounded-lg border border-border bg-background space-y-3",children:[h.jsx("input",{ref:_,placeholder:"名称(可选)",value:i,onChange:C=>s(C.target.value),className:"w-full text-sm bg-muted border border-border rounded px-3 py-1.5 text-foreground placeholder:text-muted-foreground outline-none focus:border-border transition-colors"}),h.jsx("textarea",{placeholder:"命令内容…",value:a,onChange:C=>o(C.target.value),rows:3,className:"w-full text-sm bg-muted border border-border rounded px-3 py-1.5 text-foreground placeholder:text-muted-foreground font-mono outline-none focus:border-border transition-colors resize-none",onKeyDown:C=>{C.key==="Enter"&&(C.metaKey||C.ctrlKey)&&(C.preventDefault(),w()),C.key==="Escape"&&(r(!1),s(""),o(""),c(""))}}),h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx(wc,{className:"h-3.5 w-3.5 text-muted-foreground shrink-0"}),h.jsxs("select",{value:l,onChange:C=>c(C.target.value),className:"flex-1 text-sm bg-muted border border-border rounded px-3 py-1.5 text-foreground outline-none focus:border-border transition-colors",children:[h.jsx("option",{value:"",children:"无继承"}),e.map(C=>h.jsx("option",{value:C.id,children:C.label},C.id))]})]}),h.jsxs("div",{className:"flex items-center justify-between",children:[h.jsx("span",{className:"text-xs text-muted-foreground",children:"⌘↩ 保存 · Esc 取消"}),h.jsxs("div",{className:"flex gap-2",children:[h.jsx("button",{onClick:()=>{r(!1),s(""),o("")},className:"text-sm px-3 py-1 rounded bg-secondary hover:bg-accent text-foreground transition-colors",children:"取消"}),h.jsx("button",{onClick:()=>void w(),disabled:!a.trim(),className:ee("text-sm px-3 py-1 rounded transition-colors text-white",a.trim()?"bg-blue-600 hover:bg-blue-500":"bg-secondary opacity-40 cursor-not-allowed"),children:"保存"})]})]})]}),e.length===0&&!n?h.jsxs("div",{className:"flex flex-col items-center gap-2 py-10 text-muted-foreground/50 border border-dashed border-border rounded-lg",children:[h.jsx(hu,{className:"h-6 w-6"}),h.jsx("p",{className:"text-sm",children:"还没有全局快捷命令"})]}):h.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3",children:e.map(C=>d===C.id?h.jsxs("div",{className:"rounded-lg border border-border bg-background p-3 space-y-2",children:[h.jsx("input",{ref:S,value:p,onChange:R=>f(R.target.value),placeholder:"名称(可选)",className:"w-full text-sm bg-muted border border-border rounded px-2.5 py-1 text-foreground placeholder:text-muted-foreground outline-none focus:border-border transition-colors"}),h.jsx("textarea",{value:g,onChange:R=>b(R.target.value),rows:3,className:"w-full text-sm bg-muted border border-border rounded px-2.5 py-1 text-foreground font-mono outline-none focus:border-border transition-colors resize-none",onKeyDown:R=>{R.key==="Enter"&&(R.metaKey||R.ctrlKey)&&(R.preventDefault(),E()),R.key==="Escape"&&u(null)}}),h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx(wc,{className:"h-3.5 w-3.5 text-muted-foreground shrink-0"}),h.jsxs("select",{value:v,onChange:R=>m(R.target.value),className:"flex-1 text-sm bg-muted border border-border rounded px-2.5 py-1 text-foreground outline-none focus:border-border transition-colors",children:[h.jsx("option",{value:"",children:"无继承"}),e.filter(R=>R.id!==C.id).map(R=>h.jsx("option",{value:R.id,children:R.label},R.id))]})]}),h.jsxs("div",{className:"flex gap-2 justify-end",children:[h.jsx("button",{onClick:()=>u(null),className:"text-xs px-2.5 py-1 rounded bg-secondary hover:bg-accent text-foreground transition-colors",children:"取消"}),h.jsx("button",{onClick:()=>void E(),disabled:!g.trim(),className:ee("text-xs px-2.5 py-1 rounded transition-colors text-white",g.trim()?"bg-blue-600 hover:bg-blue-500":"bg-secondary opacity-40 cursor-not-allowed"),children:"保存"})]})]},C.id):h.jsxs("div",{className:"group relative rounded-lg border border-border bg-background hover:border-muted-foreground/30 transition-colors p-3",children:[h.jsx("div",{className:"text-sm font-medium text-foreground truncate pr-14",children:C.label}),C.parentId&&(()=>{const R=e.find(M=>M.id===C.parentId);return R?h.jsxs("div",{className:"flex items-center gap-1 mt-1",children:[h.jsx(wc,{className:"h-3 w-3 text-muted-foreground"}),h.jsxs("span",{className:"text-xs text-muted-foreground truncate",children:["继承: ",R.label]})]}):null})(),!C.parentId&&C.label!==C.command&&h.jsx("div",{className:"text-xs text-muted-foreground font-mono truncate mt-1 pr-14",children:C.command}),h.jsxs("div",{className:"absolute top-2.5 right-2.5 flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity",children:[h.jsx("button",{onClick:()=>{u(C.id),f(C.label),b(C.command),m(C.parentId||""),r(!1)},className:"p-1 rounded text-muted-foreground hover:text-blue-400 transition-colors",title:"编辑",children:h.jsx(Cm,{className:"h-3.5 w-3.5"})}),h.jsx("button",{onClick:()=>void k(C.id),className:"p-1 rounded text-muted-foreground hover:text-red-400 transition-colors",title:"删除",children:h.jsx(rs,{className:"h-3.5 w-3.5"})})]})]},C.id))})]})}const EA=y.createContext({theme:"system",resolved:"dark",setTheme:()=>{}}),mx="cc_web_theme";function b3(){return window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function _3(){return y.useSyncExternalStore(e=>{const t=window.matchMedia("(prefers-color-scheme: dark)");return t.addEventListener("change",e),()=>t.removeEventListener("change",e)},()=>b3())}function y3({children:e}){const[t,n]=y.useState(()=>{try{return localStorage.getItem(mx)||"system"}catch{return"system"}}),r=_3(),i=t==="system"?r:t;y.useEffect(()=>{const a=document.documentElement;a.classList.remove("light","dark"),a.classList.add(i)},[i]);const s=a=>{n(a);try{localStorage.setItem(mx,a)}catch{}};return h.jsx(EA.Provider,{value:{theme:t,resolved:i,setTheme:s},children:e})}function Om(){return y.useContext(EA)}function xA(){const{theme:e,setTheme:t}=Om(),n=[{value:"light",icon:yF,label:"浅色"},{value:"dark",icon:uF,label:"深色"},{value:"system",icon:cF,label:"系统"}];return h.jsx("div",{className:"flex items-center rounded-md border border-border bg-muted/50 p-0.5",children:n.map(({value:r,icon:i,label:s})=>h.jsx("button",{onClick:()=>t(r),className:ee("p-1 rounded-sm transition-colors",e===r?"bg-background text-foreground shadow-sm":"text-muted-foreground hover:text-foreground"),title:s,children:h.jsx(i,{className:"h-3.5 w-3.5"})},r))})}function v3(){const e=da(),[t,n]=y.useState([]),[r,i]=y.useState(!0),[s,a]=y.useState(null),[o,l]=y.useState(!1),[c,d]=y.useState(!1),[u,p]=y.useState(new Set),[f,g]=y.useState(!!document.fullscreenElement),[b,v]=y.useState(!1),m=()=>{document.fullscreenElement?document.exitFullscreen():document.documentElement.requestFullscreen()};y.useEffect(()=>{const A=()=>g(!!document.fullscreenElement);return document.addEventListener("fullscreenchange",A),()=>document.removeEventListener("fullscreenchange",A)},[]);const _=async()=>{try{const A=await OT();n(A)}catch(A){a(A instanceof Error?A.message:"Failed to load projects")}finally{i(!1)}};y.useEffect(()=>{_()},[]),y.useEffect(()=>{let I=null;const P=async()=>{if(!document.hidden)try{const D=await bP(),j=Date.now(),x=new Set(Object.entries(D).filter(([,$])=>typeof $=="number"&&j-$<2e3).map(([$])=>$));p(x)}catch{}},F=()=>{P(),I=setInterval(()=>void P(),2e3)},B=()=>{I&&(clearInterval(I),I=null)},U=()=>{document.hidden?B():F()};return F(),document.addEventListener("visibilitychange",U),()=>{B(),document.removeEventListener("visibilitychange",U)}},[]);const S=()=>{RT(),e("/login")},w=A=>{n(I=>[...I,A])},k=A=>{n(I=>[...I,A])},E=async A=>{try{await hP(A),n(I=>I.filter(P=>P.id!==A))}catch(I){alert(I instanceof Error?I.message:"Failed to delete project")}},C=async A=>{try{const I=await gP(A);n(P=>P.map(F=>F.id===A?I:F))}catch(I){alert(I instanceof Error?I.message:"Failed to archive project")}},R=async A=>{try{const I=await mP(A);n(P=>P.map(F=>F.id===A?I:F))}catch(I){alert(I instanceof Error?I.message:"Failed to restore project")}},M=t.filter(A=>!A.archived),N=t.filter(A=>A.archived),O={onDelete:A=>void E(A),onArchive:A=>void C(A),onUnarchive:A=>void R(A)};return h.jsxs("div",{className:"min-h-screen bg-background",children:[h.jsx("header",{className:"border-b sticky top-0 bg-background z-10",children:h.jsxs("div",{className:"max-w-6xl mx-auto px-4 h-14 flex items-center justify-between",children:[h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx(JE,{className:"h-5 w-5"}),h.jsx("span",{className:"font-semibold text-lg",children:"CC Web"})]}),h.jsx(wA,{}),h.jsx(re,{variant:"ghost",size:"sm",onClick:()=>e("/settings"),title:"设置",children:h.jsx(bF,{className:"h-4 w-4"})}),h.jsx(g3,{}),h.jsx(re,{variant:"ghost",size:"sm",onClick:m,title:f?"Exit fullscreen":"Fullscreen",children:f?h.jsx(km,{className:"h-4 w-4"}):h.jsx(xm,{className:"h-4 w-4"})}),h.jsx(xA,{}),h.jsxs(re,{variant:"ghost",size:"sm",onClick:S,children:[h.jsx(oF,{className:"h-4 w-4 mr-2"}),"Logout"]})]})}),h.jsxs("main",{className:"max-w-6xl mx-auto px-4 py-8",children:[h.jsxs("div",{className:"flex items-center justify-between mb-6",children:[h.jsxs("div",{children:[h.jsx("h1",{className:"text-2xl font-bold",children:"Projects"}),h.jsx("p",{className:"text-muted-foreground text-sm mt-1",children:"Each project runs Claude CLI in a dedicated terminal session."})]}),h.jsxs("div",{className:"flex gap-2",children:[h.jsxs(re,{variant:"outline",onClick:()=>d(!0),children:[h.jsx(zo,{className:"h-4 w-4 mr-2"}),"Open Project"]}),h.jsxs(re,{onClick:()=>l(!0),children:[h.jsx(Ho,{className:"h-4 w-4 mr-2"}),"New Project"]})]})]}),r&&h.jsx("div",{className:"text-center text-muted-foreground py-12",children:"Loading projects..."}),s&&h.jsx("div",{className:"text-center text-destructive py-12",children:s}),!r&&!s&&t.length===0&&h.jsxs("div",{className:"text-center py-20",children:[h.jsx(JE,{className:"h-12 w-12 text-muted-foreground mx-auto mb-4"}),h.jsx("h2",{className:"text-lg font-semibold mb-2",children:"No projects yet"}),h.jsx("p",{className:"text-muted-foreground text-sm mb-6",children:"Create a new project or open an existing one."}),h.jsxs("div",{className:"flex gap-2 justify-center",children:[h.jsxs(re,{variant:"outline",onClick:()=>d(!0),children:[h.jsx(zo,{className:"h-4 w-4 mr-2"}),"Open Project"]}),h.jsxs(re,{onClick:()=>l(!0),children:[h.jsx(Ho,{className:"h-4 w-4 mr-2"}),"New Project"]})]})]}),!r&&!s&&M.length>0&&h.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",children:M.map(A=>h.jsx(tx,{project:A,active:u.has(A.id),...O},A.id))}),!r&&!s&&N.length>0&&h.jsxs("div",{className:"mt-10",children:[h.jsxs("button",{className:"flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors mb-4 group",onClick:()=>v(A=>!A),children:[b?h.jsx(rd,{className:"h-4 w-4"}):h.jsx(Em,{className:"h-4 w-4"}),h.jsx("span",{className:"font-medium",children:"Archived"}),h.jsx("span",{className:"text-xs bg-muted rounded-full px-2 py-0.5",children:N.length})]}),b&&h.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",children:N.map(A=>h.jsx(tx,{project:A,active:!1,...O},A.id))})]}),h.jsx(m3,{})]}),h.jsx(d3,{open:o,onOpenChange:l,onCreated:w}),h.jsx(h3,{open:c,onOpenChange:d,onOpened:k})]})}/**
406
+ For more information, see https://radix-ui.com/primitives/docs/components/${t.docsSlug}`;return y.useEffect(()=>{e&&(document.getElementById(e)||console.error(n))},[n,e]),null},i3="DialogDescriptionWarning",s3=({contentRef:e,descriptionId:t})=>{const r=`Warning: Missing \`Description\` or \`aria-describedby={undefined}\` for {${gA(i3).contentName}}.`;return y.useEffect(()=>{var s;const i=(s=e.current)==null?void 0:s.getAttribute("aria-describedby");t&&i&&(document.getElementById(t)||console.warn(r))},[r,e,t]),null},a3=nA,o3=sA,mA=aA,bA=oA,_A=cA,yA=dA,l3=pA;const ul=a3,c3=o3,vA=y.forwardRef(({className:e,...t},n)=>h.jsx(mA,{ref:n,className:ee("fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",e),...t}));vA.displayName=mA.displayName;const fa=y.forwardRef(({className:e,children:t,...n},r)=>h.jsxs(c3,{children:[h.jsx(vA,{}),h.jsxs(bA,{ref:r,className:ee("fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",e),...n,children:[t,h.jsxs(l3,{className:"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",children:[h.jsx(rs,{className:"h-4 w-4"}),h.jsx("span",{className:"sr-only",children:"Close"})]})]})]}));fa.displayName=bA.displayName;const ga=({className:e,...t})=>h.jsx("div",{className:ee("flex flex-col space-y-1.5 text-center sm:text-left",e),...t});ga.displayName="DialogHeader";const ma=({className:e,...t})=>h.jsx("div",{className:ee("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",e),...t});ma.displayName="DialogFooter";const ba=y.forwardRef(({className:e,...t},n)=>h.jsx(_A,{ref:n,className:ee("text-lg font-semibold leading-none tracking-tight",e),...t}));ba.displayName=_A.displayName;const _a=y.forwardRef(({className:e,...t},n)=>h.jsx(yA,{ref:n,className:ee("text-sm text-muted-foreground",e),...t}));_a.displayName=yA.displayName;function SA({onSelect:e}){const[t,n]=y.useState(""),[r,i]=y.useState(null),[s,a]=y.useState([]),[o,l]=y.useState(!0),[c,d]=y.useState(null),[u,p]=y.useState(!1),[f,g]=y.useState(""),[b,v]=y.useState(null),[m,_]=y.useState(!1),S=y.useRef(null),w=async O=>{l(!0),d(null);try{const A=await DT(O);n(A.path),i(A.parent),a(A.entries)}catch(A){d(A instanceof Error?A.message:"Failed to load directory")}finally{l(!1)}};y.useEffect(()=>{w()},[]),y.useEffect(()=>{u&&setTimeout(()=>{var O;return(O=S.current)==null?void 0:O.focus()},0)},[u]);const k=()=>{g(""),v(null),p(!0)},E=()=>{p(!1),g(""),v(null)},C=async()=>{const O=f.trim();if(O){_(!0),v(null);try{const{path:A}=await AP(t,O);p(!1),g(""),await w(t),w(A)}catch(A){v(A instanceof Error?A.message:"Failed to create folder")}finally{_(!1)}}},R=O=>{O.key==="Enter"?(O.preventDefault(),C()):O.key==="Escape"&&E()},M=t.split("/").filter(Boolean),N=O=>{const A="/"+M.slice(0,O+1).join("/");w(A)};return h.jsxs("div",{className:"flex flex-col h-64 border rounded-md overflow-hidden",children:[h.jsxs("div",{className:"flex items-center gap-1 px-2 py-2 bg-muted border-b flex-shrink-0",children:[h.jsx(re,{variant:"ghost",size:"sm",className:"h-6 px-1 flex-shrink-0",onClick:()=>void w(),title:"Home",children:h.jsx(sF,{className:"h-3 w-3"})}),h.jsx("div",{className:"flex items-center gap-1 flex-1 overflow-x-auto min-w-0",children:M.map((O,A)=>h.jsxs(ft.Fragment,{children:[h.jsx(Em,{className:"h-3 w-3 text-muted-foreground flex-shrink-0"}),h.jsx("button",{className:"text-xs hover:text-foreground text-muted-foreground whitespace-nowrap",onClick:()=>N(A),children:O})]},A))}),h.jsxs(re,{variant:"ghost",size:"sm",className:"h-6 px-1.5 flex-shrink-0 gap-1 text-xs",onClick:k,disabled:u||o||!!c,title:"New folder",children:[h.jsx(iF,{className:"h-3.5 w-3.5"}),h.jsx("span",{className:"hidden sm:inline",children:"New folder"})]})]}),h.jsxs("div",{className:"flex-1 overflow-y-auto",children:[o&&h.jsx("div",{className:"flex items-center justify-center h-full text-sm text-muted-foreground",children:"Loading..."}),c&&h.jsx("div",{className:"flex items-center justify-center h-full text-sm text-destructive px-4 text-center",children:c}),!o&&!c&&h.jsxs("div",{className:"p-1",children:[r&&h.jsxs("button",{className:"flex items-center gap-2 w-full px-2 py-1.5 text-sm rounded hover:bg-accent hover:text-accent-foreground",onClick:()=>void w(r),children:[h.jsx(du,{className:"h-4 w-4 text-muted-foreground"}),h.jsx("span",{children:".."})]}),u&&h.jsxs("div",{className:"flex items-center gap-1 px-2 py-1.5",children:[h.jsx(uf,{className:"h-4 w-4 text-blue-500 flex-shrink-0"}),h.jsx("input",{ref:S,className:ee("flex-1 text-sm bg-background border rounded px-1.5 py-0.5 outline-none","focus:ring-1 focus:ring-ring",b&&"border-destructive"),placeholder:"Folder name",value:f,onChange:O=>{g(O.target.value),v(null)},onKeyDown:R,disabled:m}),h.jsx("button",{className:"p-1 rounded hover:bg-accent disabled:opacity-50",onClick:()=>void C(),disabled:!f.trim()||m,title:"Create",children:h.jsx(BT,{className:"h-3.5 w-3.5 text-green-600"})}),h.jsx("button",{className:"p-1 rounded hover:bg-accent",onClick:E,disabled:m,title:"Cancel",children:h.jsx(rs,{className:"h-3.5 w-3.5 text-muted-foreground"})})]}),b&&h.jsx("p",{className:"px-3 pb-1 text-xs text-destructive",children:b}),s.length===0&&!u&&h.jsx("div",{className:"text-sm text-muted-foreground px-2 py-4 text-center",children:"Empty directory"}),s.map(O=>h.jsxs("button",{className:ee("flex items-center gap-2 w-full px-2 py-1.5 text-sm rounded text-left",O.type==="dir"?"hover:bg-accent hover:text-accent-foreground cursor-pointer":"opacity-40 cursor-not-allowed"),onClick:()=>{O.type==="dir"&&w(O.path)},disabled:O.type==="file",children:[O.type==="dir"?h.jsx(uf,{className:"h-4 w-4 text-blue-500 flex-shrink-0"}):h.jsx(rF,{className:"h-4 w-4 text-muted-foreground flex-shrink-0"}),h.jsx("span",{className:"truncate",children:O.name})]},O.path))]})]}),h.jsxs("div",{className:"border-t px-3 py-2 flex items-center justify-between gap-2 flex-shrink-0 bg-background",children:[h.jsx("span",{className:"text-xs text-muted-foreground truncate flex-1",children:t}),h.jsx(re,{size:"sm",onClick:()=>e(t),disabled:!t,children:"Select this folder"})]})]})}const u3=[{value:"claude",label:"Claude",desc:"Anthropic Claude Code CLI"},{value:"opencode",label:"OpenCode",desc:"OpenCode CLI (sst/opencode)"},{value:"codex",label:"Codex",desc:"OpenAI Codex CLI"},{value:"qwen",label:"Qwen",desc:"Qwen Code CLI (QwenLM)"}],fx={claude:{limited:"claude",unlimited:"claude --dangerously-skip-permissions"},opencode:{limited:"opencode",unlimited:"opencode --dangerously-skip-permissions"},codex:{limited:"codex",unlimited:"codex --ask-for-approval never --sandbox danger-full-access"},qwen:{limited:"qwen-code",unlimited:"qwen-code --yolo"}};function d3({open:e,onOpenChange:t,onCreated:n}){const[r,i]=y.useState("name"),[s,a]=y.useState(""),[o,l]=y.useState(""),[c,d]=y.useState("claude"),[u,p]=y.useState("limited"),[f,g]=y.useState(!1),[b,v]=y.useState(null),m=()=>{i("name"),a(""),l(""),d("claude"),p("limited"),v(null),g(!1)},_=R=>{R||m(),t(R)},S=()=>{if(!s.trim()){v("Please enter a project name");return}v(null),i("folder")},w=R=>{l(R),i("settings")},k=async()=>{g(!0),v(null);try{const R=await uP({name:s.trim(),folderPath:o,permissionMode:u,cliTool:c});n(R),_(!1)}catch(R){v(R instanceof Error?R.message:"Failed to create project")}finally{g(!1)}},E={name:"New Project — Name",folder:"New Project — Select Folder",settings:"New Project — Settings"},C=["name","folder","settings"];return h.jsx(ul,{open:e,onOpenChange:_,children:h.jsxs(fa,{className:"sm:max-w-lg",children:[h.jsxs(ga,{children:[h.jsx(ba,{children:E[r]}),h.jsxs(_a,{children:[r==="name"&&"Give your project a name.",r==="folder"&&"Choose the working directory for this project.",r==="settings"&&"Choose the AI coding tool and run mode."]})]}),h.jsx("div",{className:"flex gap-2 mb-2",children:C.map((R,M)=>h.jsx("div",{className:`h-1.5 flex-1 rounded-full ${r===R?"bg-primary":C.indexOf(r)>M?"bg-primary/40":"bg-muted"}`},R))}),r==="name"&&h.jsxs("div",{className:"space-y-4",children:[h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{htmlFor:"project-name",children:"Project Name"}),h.jsx(gr,{id:"project-name",placeholder:"My Project",value:s,onChange:R=>a(R.target.value),onKeyDown:R=>{R.key==="Enter"&&S()},autoFocus:!0})]}),b&&h.jsx("p",{className:"text-sm text-destructive",children:b})]}),r==="folder"&&h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{children:"Working Directory"}),h.jsx(SA,{onSelect:w})]}),r==="settings"&&h.jsxs("div",{className:"space-y-5",children:[h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{children:"AI Coding Tool"}),h.jsx("div",{className:"grid grid-cols-2 gap-2",children:u3.map(R=>h.jsxs("label",{className:`flex items-center gap-2.5 cursor-pointer p-3 rounded-md border transition-colors ${c===R.value?"border-primary bg-primary/5":"hover:bg-accent"}`,children:[h.jsx("input",{type:"radio",name:"cliTool",value:R.value,checked:c===R.value,onChange:()=>d(R.value),className:"accent-primary"}),h.jsxs("div",{children:[h.jsx("div",{className:"font-medium text-sm",children:R.label}),h.jsx("div",{className:"text-xs text-muted-foreground",children:R.desc})]})]},R.value))})]}),h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{children:"Permission Mode"}),h.jsxs("div",{className:"space-y-2",children:[h.jsxs("label",{className:"flex items-start gap-3 cursor-pointer p-3 rounded-md border hover:bg-accent transition-colors",children:[h.jsx("input",{type:"radio",name:"permissionMode",value:"limited",checked:u==="limited",onChange:()=>p("limited"),className:"mt-0.5"}),h.jsxs("div",{children:[h.jsx("div",{className:"font-medium text-sm",children:"Limited"}),h.jsxs("div",{className:"text-xs text-muted-foreground",children:["Runs ",h.jsx("code",{className:"bg-muted px-1 rounded",children:fx[c].limited})," — asks for permission before file changes."]})]})]}),h.jsxs("label",{className:"flex items-start gap-3 cursor-pointer p-3 rounded-md border hover:bg-accent transition-colors",children:[h.jsx("input",{type:"radio",name:"permissionMode",value:"unlimited",checked:u==="unlimited",onChange:()=>p("unlimited"),className:"mt-0.5"}),h.jsxs("div",{children:[h.jsx("div",{className:"font-medium text-sm",children:"Unlimited"}),h.jsxs("div",{className:"text-xs text-muted-foreground",children:["Runs ",h.jsx("code",{className:"bg-muted px-1 rounded",children:fx[c].unlimited})," — acts autonomously."]})]})]})]})]}),h.jsxs("div",{className:"text-xs text-muted-foreground",children:[h.jsx("span",{className:"font-medium",children:"Folder:"})," ",h.jsx("span",{className:"font-mono",children:o})]}),b&&h.jsx("p",{className:"text-sm text-destructive",children:b})]}),h.jsxs(ma,{children:[r!=="name"&&h.jsx(re,{variant:"outline",onClick:()=>i(r==="settings"?"folder":"name"),disabled:f,children:"Back"}),r==="name"&&h.jsx(re,{onClick:S,children:"Next"}),r==="folder"&&h.jsx(re,{variant:"outline",onClick:()=>t(!1),children:"Cancel"}),r==="settings"&&h.jsx(re,{onClick:()=>void k(),disabled:f,children:f?"Creating...":"Create Project"})]})]})})}function h3({open:e,onOpenChange:t,onOpened:n}){const[r,i]=y.useState(""),[s,a]=y.useState(!1),[o,l]=y.useState(null),c=()=>{i(""),l(null),a(!1)},d=p=>{p||c(),t(p)},u=async p=>{i(p),a(!0),l(null);try{const f=await dP(p);n(f),d(!1)}catch(f){l(f instanceof Error?f.message:"Failed to open project"),a(!1)}};return h.jsx(ul,{open:e,onOpenChange:d,children:h.jsxs(fa,{className:"sm:max-w-lg",children:[h.jsxs(ga,{children:[h.jsx(ba,{children:"Open Existing Project"}),h.jsxs(_a,{children:["Select a folder that contains a ",h.jsx("code",{className:"bg-muted px-1 rounded text-xs",children:".ccweb/"})," configuration. The project's history and settings will be restored."]})]}),h.jsxs("div",{className:"space-y-2",children:[h.jsx(Xe,{children:"Project Folder"}),h.jsx(SA,{onSelect:p=>void u(p)})]}),o&&h.jsx("p",{className:"text-sm text-destructive",children:o}),r&&!o&&s&&h.jsxs("p",{className:"text-sm text-muted-foreground",children:["Opening project from ",r,"..."]}),h.jsx(ma,{children:h.jsx(re,{variant:"outline",onClick:()=>d(!1),disabled:s,children:"Cancel"})})]})})}function p3(e){if(!e)return"";const t=new Date(e).getTime()-Date.now();if(t<=0)return"即将重置";const n=Math.floor(t/36e5),r=Math.floor(t%36e5/6e4);return n>0?`${n}h${r}m`:`${r}m`}function Yl({label:e,bucket:t}){if(!t||t.utilization===void 0)return null;const n=t.utilization,r=n<50?"text-green-400":n<80?"text-yellow-400":"text-red-400",i=p3(t.resetAt);return h.jsxs("span",{className:"text-muted-foreground whitespace-nowrap",children:[e," ",h.jsxs("span",{className:ee("font-medium",r),children:[n,"%"]}),i&&h.jsxs("span",{className:"text-muted-foreground/70 ml-0.5",children:["(",i,")"]})]})}function wA({className:e}){const[t,n]=y.useState("loading"),[r,i]=y.useState(!1);y.useEffect(()=>{const u=async()=>{try{n(await kP())}catch{n(null)}};u();const p=setInterval(()=>void u(),5*60*1e3);return()=>clearInterval(p)},[]);const s=async()=>{i(!0);try{n(await CP())}catch{n(null)}finally{i(!1)}};if(t==="loading"||!t)return null;const a=!!t.fiveHour,o=!!t.sevenDay,l=!!t.sevenDaySonnet,c=!!t.sevenDayOpus,d=a||o||l||c;return h.jsxs("div",{className:ee("flex items-center gap-1.5 text-xs",e),children:[t.planName&&h.jsx("span",{className:"font-medium text-foreground bg-muted border border-border px-2 py-0.5 rounded-full",children:t.planName}),d&&h.jsxs(h.Fragment,{children:[h.jsx("span",{className:"text-muted-foreground/50",children:"|"}),h.jsx(Yl,{label:"5h",bucket:t.fiveHour}),o&&h.jsxs(h.Fragment,{children:[h.jsx("span",{className:"text-muted-foreground/50",children:"·"}),h.jsx(Yl,{label:"7d",bucket:t.sevenDay})]}),l&&h.jsxs(h.Fragment,{children:[h.jsx("span",{className:"text-muted-foreground/50",children:"·"}),h.jsx(Yl,{label:"7d Sonnet",bucket:t.sevenDaySonnet})]}),c&&h.jsxs(h.Fragment,{children:[h.jsx("span",{className:"text-muted-foreground/50",children:"·"}),h.jsx(Yl,{label:"7d Opus",bucket:t.sevenDayOpus})]})]}),h.jsx("button",{onClick:()=>void s(),className:"text-muted-foreground hover:text-foreground transition-colors",title:"刷新用量信息",children:h.jsx(Wi,{className:ee("h-3 w-3",r&&"animate-spin")})})]})}const gx="v1.5.21",Xl=!!window.electronUpdater;function f3(e,t){const n=e.split(".").map(Number),r=t.split(".").map(Number);for(let i=0;i<Math.max(n.length,r.length);i++){const s=n[i]||0,a=r[i]||0;if(s!==a)return s-a}return 0}function g3(){const[e,t]=y.useState("idle"),[n,r]=y.useState(!1),[i,s]=y.useState(""),[a,o]=y.useState(0),[l,c]=y.useState([]),[d,u]=y.useState([]),[p,f]=y.useState(null),[g,b]=y.useState(null);y.useEffect(()=>window.electronUpdater?window.electronUpdater.onUpdateStatus(E=>{switch(E.type){case"progress":{const C=E.info;o(Math.round((C==null?void 0:C.percent)??0));break}case"downloaded":t("downloaded");break;case"error":f(E.info||"Download failed"),t("error");break}}):void 0,[]);const v=y.useCallback(async()=>{t("checking"),f(null),r(!0);try{if(Xl){const E=await window.electronUpdater.checkForUpdate();if(E.error)throw new Error(E.error);if(!E.available){t("no_update");return}s(E.version?`v${E.version}`:"new version")}else{const E=await fetch("https://api.github.com/repos/zbc0315/cc-web/releases/latest");if(E.status===404){t("no_update");return}if(!E.ok)throw new Error(`GitHub API ${E.status}`);const C=await E.json(),R=C.tag_name.replace(/^v/,""),M=gx.replace(/^v/,"");if(f3(R,M)<=0){t("no_update");return}s(C.tag_name)}const k=await RP();c(k.projects),t(k.runningCount>0?"confirm_prepare":"update_available")}catch(k){f(k instanceof Error?k.message:"Check failed"),t("error")}},[]),m=async()=>{t("preparing");try{const k=await IP();u(k.results),t("update_available")}catch(k){f(k instanceof Error?k.message:"Prepare failed"),t("error")}},_=async()=>{if(Xl){t("downloading"),o(0);const k=await window.electronUpdater.downloadUpdate();k.success?k.path&&b(k.path):(f(k.error||"Download failed"),t("error"))}else window.open("https://github.com/zbc0315/cc-web/releases/latest","_blank")},S=()=>{var k;(k=window.electronUpdater)==null||k.quitAndInstall(g||void 0)},w=()=>{r(!1),setTimeout(()=>{t("idle"),s(""),c([]),u([]),f(null),o(0)},200)};return h.jsxs(h.Fragment,{children:[h.jsxs(re,{variant:"ghost",size:"sm",onClick:()=>void v(),disabled:e==="checking"||e==="preparing"||e==="downloading",children:[e==="checking"||e==="preparing"||e==="downloading"?h.jsx(Fi,{className:"h-4 w-4 mr-2 animate-spin"}):h.jsx(UT,{className:"h-4 w-4 mr-2"}),"Update"]}),h.jsx(ul,{open:n,onOpenChange:k=>{k||w()},children:h.jsxs(fa,{className:"sm:max-w-md",children:[h.jsxs(ga,{children:[h.jsxs(ba,{children:[e==="checking"&&"Checking for updates...",e==="no_update"&&"Up to date",e==="update_available"&&`Update ${i} available`,e==="confirm_prepare"&&"Save project memory first",e==="preparing"&&"Saving project memory...",e==="downloading"&&"Downloading update...",e==="downloaded"&&"Update ready to install",e==="error"&&"Update error"]}),h.jsxs(_a,{children:[e==="checking"&&"Checking for the latest version...",e==="no_update"&&`Current version ${gx} is the latest.`,e==="update_available"&&(d.length>0?"All projects saved and stopped. Ready to update.":`A new version is available. ${Xl?"Click download to update automatically.":""}`),e==="confirm_prepare"&&`${l.length} project(s) running. Each Claude will be asked to save memory, then terminals will stop.`,e==="preparing"&&"Sending save commands and waiting for Claude to finish...",e==="downloading"&&`Downloading... ${a}%`,e==="downloaded"&&"The update has been downloaded. Click install to restart and apply.",e==="error"&&(p||"An error occurred.")]})]}),e==="downloading"&&h.jsx("div",{className:"w-full bg-muted rounded-full h-2 overflow-hidden",children:h.jsx("div",{className:"bg-primary h-full transition-all duration-300",style:{width:`${a}%`}})}),e==="confirm_prepare"&&l.length>0&&h.jsx("div",{className:"space-y-1 text-sm",children:l.map(k=>h.jsxs("div",{className:"flex items-center gap-2 px-3 py-1.5 rounded bg-muted",children:[h.jsx("span",{className:"h-2 w-2 rounded-full bg-green-500 shrink-0"}),h.jsx("span",{className:"truncate",children:k.name})]},k.id))}),e==="preparing"&&h.jsx("div",{className:"flex items-center justify-center py-6",children:h.jsx(Fi,{className:"h-8 w-8 animate-spin text-muted-foreground"})}),e==="update_available"&&d.length>0&&h.jsx("div",{className:"space-y-1 text-sm",children:d.map(k=>h.jsxs("div",{className:"flex items-center gap-2 px-3 py-1.5 rounded bg-muted",children:[k.status==="stopped"?h.jsx(QP,{className:"h-4 w-4 text-green-500 shrink-0"}):h.jsx(qP,{className:"h-4 w-4 text-yellow-500 shrink-0"}),h.jsx("span",{className:"truncate",children:k.name}),h.jsx("span",{className:"text-xs text-muted-foreground ml-auto shrink-0",children:k.message})]},k.id))}),h.jsxs(ma,{children:[e==="no_update"&&h.jsx(re,{onClick:w,children:"OK"}),e==="update_available"&&h.jsxs(h.Fragment,{children:[h.jsx(re,{variant:"outline",onClick:w,children:"Later"}),h.jsx(re,{onClick:()=>void _(),children:Xl?"Download & Install":`Download ${i}`})]}),e==="confirm_prepare"&&h.jsxs(h.Fragment,{children:[h.jsx(re,{variant:"outline",onClick:w,children:"Cancel"}),h.jsx(re,{onClick:()=>void m(),children:"Save Memory & Stop All"})]}),e==="downloaded"&&h.jsxs(h.Fragment,{children:[h.jsx(re,{variant:"outline",onClick:w,children:"Later"}),h.jsx(re,{onClick:S,children:"Restart & Install"})]}),e==="error"&&h.jsx(re,{onClick:w,children:"Close"})]})]})})]})}function m3(){const[e,t]=y.useState([]),[n,r]=y.useState(!1),[i,s]=y.useState(""),[a,o]=y.useState(""),[l,c]=y.useState(""),[d,u]=y.useState(null),[p,f]=y.useState(""),[g,b]=y.useState(""),[v,m]=y.useState(""),_=y.useRef(null),S=y.useRef(null);y.useEffect(()=>{LT().then(t).catch(()=>t([]))},[]),y.useEffect(()=>{n&&setTimeout(()=>{var C;return(C=_.current)==null?void 0:C.focus()},0)},[n]),y.useEffect(()=>{d&&setTimeout(()=>{var C;return(C=S.current)==null?void 0:C.focus()},0)},[d]);const w=async()=>{const C=a.trim();if(C)try{const R=await _P({label:i.trim()||C,command:C,...l?{parentId:l}:{}});t(M=>[...M,R]),s(""),o(""),c(""),r(!1)}catch(R){console.error("[GlobalShortcuts] Failed to add:",R)}},k=async C=>{try{await vP(C),t(R=>R.filter(M=>M.id!==C)),d===C&&u(null)}catch(R){console.error("[GlobalShortcuts] Failed to delete:",R)}},E=async()=>{const C=g.trim();if(!(!C||!d))try{const R=await yP(d,{label:p.trim()||C,command:C,parentId:v||null});t(M=>M.map(N=>N.id===d?R:N)),u(null)}catch(R){console.error("[GlobalShortcuts] Failed to save:",R)}};return h.jsxs("section",{className:"mt-10",children:[h.jsxs("div",{className:"flex items-center justify-between mb-4",children:[h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx(hu,{className:"h-4 w-4 text-muted-foreground"}),h.jsx("h2",{className:"text-lg font-semibold",children:"全局快捷命令"}),h.jsx("span",{className:"text-xs text-muted-foreground",children:"在所有项目的终端中可用"})]}),h.jsxs("button",{onClick:()=>{r(C=>!C),u(null)},className:ee("flex items-center gap-1.5 text-sm px-3 py-1.5 rounded-md border transition-colors",n?"border-border text-foreground bg-muted":"border-border text-muted-foreground hover:text-foreground hover:bg-muted"),children:[h.jsx(Ho,{className:"h-3.5 w-3.5"}),"新建"]})]}),n&&h.jsxs("div",{className:"mb-4 p-4 rounded-lg border border-border bg-background space-y-3",children:[h.jsx("input",{ref:_,placeholder:"名称(可选)",value:i,onChange:C=>s(C.target.value),className:"w-full text-sm bg-muted border border-border rounded px-3 py-1.5 text-foreground placeholder:text-muted-foreground outline-none focus:border-border transition-colors"}),h.jsx("textarea",{placeholder:"命令内容…",value:a,onChange:C=>o(C.target.value),rows:3,className:"w-full text-sm bg-muted border border-border rounded px-3 py-1.5 text-foreground placeholder:text-muted-foreground font-mono outline-none focus:border-border transition-colors resize-none",onKeyDown:C=>{C.key==="Enter"&&(C.metaKey||C.ctrlKey)&&(C.preventDefault(),w()),C.key==="Escape"&&(r(!1),s(""),o(""),c(""))}}),h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx(wc,{className:"h-3.5 w-3.5 text-muted-foreground shrink-0"}),h.jsxs("select",{value:l,onChange:C=>c(C.target.value),className:"flex-1 text-sm bg-muted border border-border rounded px-3 py-1.5 text-foreground outline-none focus:border-border transition-colors",children:[h.jsx("option",{value:"",children:"无继承"}),e.map(C=>h.jsx("option",{value:C.id,children:C.label},C.id))]})]}),h.jsxs("div",{className:"flex items-center justify-between",children:[h.jsx("span",{className:"text-xs text-muted-foreground",children:"⌘↩ 保存 · Esc 取消"}),h.jsxs("div",{className:"flex gap-2",children:[h.jsx("button",{onClick:()=>{r(!1),s(""),o("")},className:"text-sm px-3 py-1 rounded bg-secondary hover:bg-accent text-foreground transition-colors",children:"取消"}),h.jsx("button",{onClick:()=>void w(),disabled:!a.trim(),className:ee("text-sm px-3 py-1 rounded transition-colors text-white",a.trim()?"bg-blue-600 hover:bg-blue-500":"bg-secondary opacity-40 cursor-not-allowed"),children:"保存"})]})]})]}),e.length===0&&!n?h.jsxs("div",{className:"flex flex-col items-center gap-2 py-10 text-muted-foreground/50 border border-dashed border-border rounded-lg",children:[h.jsx(hu,{className:"h-6 w-6"}),h.jsx("p",{className:"text-sm",children:"还没有全局快捷命令"})]}):h.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3",children:e.map(C=>d===C.id?h.jsxs("div",{className:"rounded-lg border border-border bg-background p-3 space-y-2",children:[h.jsx("input",{ref:S,value:p,onChange:R=>f(R.target.value),placeholder:"名称(可选)",className:"w-full text-sm bg-muted border border-border rounded px-2.5 py-1 text-foreground placeholder:text-muted-foreground outline-none focus:border-border transition-colors"}),h.jsx("textarea",{value:g,onChange:R=>b(R.target.value),rows:3,className:"w-full text-sm bg-muted border border-border rounded px-2.5 py-1 text-foreground font-mono outline-none focus:border-border transition-colors resize-none",onKeyDown:R=>{R.key==="Enter"&&(R.metaKey||R.ctrlKey)&&(R.preventDefault(),E()),R.key==="Escape"&&u(null)}}),h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx(wc,{className:"h-3.5 w-3.5 text-muted-foreground shrink-0"}),h.jsxs("select",{value:v,onChange:R=>m(R.target.value),className:"flex-1 text-sm bg-muted border border-border rounded px-2.5 py-1 text-foreground outline-none focus:border-border transition-colors",children:[h.jsx("option",{value:"",children:"无继承"}),e.filter(R=>R.id!==C.id).map(R=>h.jsx("option",{value:R.id,children:R.label},R.id))]})]}),h.jsxs("div",{className:"flex gap-2 justify-end",children:[h.jsx("button",{onClick:()=>u(null),className:"text-xs px-2.5 py-1 rounded bg-secondary hover:bg-accent text-foreground transition-colors",children:"取消"}),h.jsx("button",{onClick:()=>void E(),disabled:!g.trim(),className:ee("text-xs px-2.5 py-1 rounded transition-colors text-white",g.trim()?"bg-blue-600 hover:bg-blue-500":"bg-secondary opacity-40 cursor-not-allowed"),children:"保存"})]})]},C.id):h.jsxs("div",{className:"group relative rounded-lg border border-border bg-background hover:border-muted-foreground/30 transition-colors p-3",children:[h.jsx("div",{className:"text-sm font-medium text-foreground truncate pr-14",children:C.label}),C.parentId&&(()=>{const R=e.find(M=>M.id===C.parentId);return R?h.jsxs("div",{className:"flex items-center gap-1 mt-1",children:[h.jsx(wc,{className:"h-3 w-3 text-muted-foreground"}),h.jsxs("span",{className:"text-xs text-muted-foreground truncate",children:["继承: ",R.label]})]}):null})(),!C.parentId&&C.label!==C.command&&h.jsx("div",{className:"text-xs text-muted-foreground font-mono truncate mt-1 pr-14",children:C.command}),h.jsxs("div",{className:"absolute top-2.5 right-2.5 flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity",children:[h.jsx("button",{onClick:()=>{u(C.id),f(C.label),b(C.command),m(C.parentId||""),r(!1)},className:"p-1 rounded text-muted-foreground hover:text-blue-400 transition-colors",title:"编辑",children:h.jsx(Cm,{className:"h-3.5 w-3.5"})}),h.jsx("button",{onClick:()=>void k(C.id),className:"p-1 rounded text-muted-foreground hover:text-red-400 transition-colors",title:"删除",children:h.jsx(rs,{className:"h-3.5 w-3.5"})})]})]},C.id))})]})}const EA=y.createContext({theme:"system",resolved:"dark",setTheme:()=>{}}),mx="cc_web_theme";function b3(){return window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function _3(){return y.useSyncExternalStore(e=>{const t=window.matchMedia("(prefers-color-scheme: dark)");return t.addEventListener("change",e),()=>t.removeEventListener("change",e)},()=>b3())}function y3({children:e}){const[t,n]=y.useState(()=>{try{return localStorage.getItem(mx)||"system"}catch{return"system"}}),r=_3(),i=t==="system"?r:t;y.useEffect(()=>{const a=document.documentElement;a.classList.remove("light","dark"),a.classList.add(i)},[i]);const s=a=>{n(a);try{localStorage.setItem(mx,a)}catch{}};return h.jsx(EA.Provider,{value:{theme:t,resolved:i,setTheme:s},children:e})}function Om(){return y.useContext(EA)}function xA(){const{theme:e,setTheme:t}=Om(),n=[{value:"light",icon:yF,label:"浅色"},{value:"dark",icon:uF,label:"深色"},{value:"system",icon:cF,label:"系统"}];return h.jsx("div",{className:"flex items-center rounded-md border border-border bg-muted/50 p-0.5",children:n.map(({value:r,icon:i,label:s})=>h.jsx("button",{onClick:()=>t(r),className:ee("p-1 rounded-sm transition-colors",e===r?"bg-background text-foreground shadow-sm":"text-muted-foreground hover:text-foreground"),title:s,children:h.jsx(i,{className:"h-3.5 w-3.5"})},r))})}function v3(){const e=da(),[t,n]=y.useState([]),[r,i]=y.useState(!0),[s,a]=y.useState(null),[o,l]=y.useState(!1),[c,d]=y.useState(!1),[u,p]=y.useState(new Set),[f,g]=y.useState(!!document.fullscreenElement),[b,v]=y.useState(!1),m=()=>{document.fullscreenElement?document.exitFullscreen():document.documentElement.requestFullscreen()};y.useEffect(()=>{const A=()=>g(!!document.fullscreenElement);return document.addEventListener("fullscreenchange",A),()=>document.removeEventListener("fullscreenchange",A)},[]);const _=async()=>{try{const A=await OT();n(A)}catch(A){a(A instanceof Error?A.message:"Failed to load projects")}finally{i(!1)}};y.useEffect(()=>{_()},[]),y.useEffect(()=>{let I=null;const P=async()=>{if(!document.hidden)try{const D=await bP(),j=Date.now(),x=new Set(Object.entries(D).filter(([,$])=>typeof $=="number"&&j-$<2e3).map(([$])=>$));p(x)}catch{}},F=()=>{P(),I=setInterval(()=>void P(),2e3)},B=()=>{I&&(clearInterval(I),I=null)},U=()=>{document.hidden?B():F()};return F(),document.addEventListener("visibilitychange",U),()=>{B(),document.removeEventListener("visibilitychange",U)}},[]);const S=()=>{RT(),e("/login")},w=A=>{n(I=>[...I,A])},k=A=>{n(I=>[...I,A])},E=async A=>{try{await hP(A),n(I=>I.filter(P=>P.id!==A))}catch(I){alert(I instanceof Error?I.message:"Failed to delete project")}},C=async A=>{try{const I=await gP(A);n(P=>P.map(F=>F.id===A?I:F))}catch(I){alert(I instanceof Error?I.message:"Failed to archive project")}},R=async A=>{try{const I=await mP(A);n(P=>P.map(F=>F.id===A?I:F))}catch(I){alert(I instanceof Error?I.message:"Failed to restore project")}},M=t.filter(A=>!A.archived),N=t.filter(A=>A.archived),O={onDelete:A=>void E(A),onArchive:A=>void C(A),onUnarchive:A=>void R(A)};return h.jsxs("div",{className:"min-h-screen bg-background",children:[h.jsx("header",{className:"border-b sticky top-0 bg-background z-10",children:h.jsxs("div",{className:"max-w-6xl mx-auto px-4 h-14 flex items-center justify-between",children:[h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx(JE,{className:"h-5 w-5"}),h.jsx("span",{className:"font-semibold text-lg",children:"CC Web"})]}),h.jsx(wA,{}),h.jsx(re,{variant:"ghost",size:"sm",onClick:()=>e("/settings"),title:"设置",children:h.jsx(bF,{className:"h-4 w-4"})}),h.jsx(g3,{}),h.jsx(re,{variant:"ghost",size:"sm",onClick:m,title:f?"Exit fullscreen":"Fullscreen",children:f?h.jsx(km,{className:"h-4 w-4"}):h.jsx(xm,{className:"h-4 w-4"})}),h.jsx(xA,{}),h.jsxs(re,{variant:"ghost",size:"sm",onClick:S,children:[h.jsx(oF,{className:"h-4 w-4 mr-2"}),"Logout"]})]})}),h.jsxs("main",{className:"max-w-6xl mx-auto px-4 py-8",children:[h.jsxs("div",{className:"flex items-center justify-between mb-6",children:[h.jsxs("div",{children:[h.jsx("h1",{className:"text-2xl font-bold",children:"Projects"}),h.jsx("p",{className:"text-muted-foreground text-sm mt-1",children:"Each project runs Claude CLI in a dedicated terminal session."})]}),h.jsxs("div",{className:"flex gap-2",children:[h.jsxs(re,{variant:"outline",onClick:()=>d(!0),children:[h.jsx(zo,{className:"h-4 w-4 mr-2"}),"Open Project"]}),h.jsxs(re,{onClick:()=>l(!0),children:[h.jsx(Ho,{className:"h-4 w-4 mr-2"}),"New Project"]})]})]}),r&&h.jsx("div",{className:"text-center text-muted-foreground py-12",children:"Loading projects..."}),s&&h.jsx("div",{className:"text-center text-destructive py-12",children:s}),!r&&!s&&t.length===0&&h.jsxs("div",{className:"text-center py-20",children:[h.jsx(JE,{className:"h-12 w-12 text-muted-foreground mx-auto mb-4"}),h.jsx("h2",{className:"text-lg font-semibold mb-2",children:"No projects yet"}),h.jsx("p",{className:"text-muted-foreground text-sm mb-6",children:"Create a new project or open an existing one."}),h.jsxs("div",{className:"flex gap-2 justify-center",children:[h.jsxs(re,{variant:"outline",onClick:()=>d(!0),children:[h.jsx(zo,{className:"h-4 w-4 mr-2"}),"Open Project"]}),h.jsxs(re,{onClick:()=>l(!0),children:[h.jsx(Ho,{className:"h-4 w-4 mr-2"}),"New Project"]})]})]}),!r&&!s&&M.length>0&&h.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",children:M.map(A=>h.jsx(tx,{project:A,active:u.has(A.id),...O},A.id))}),!r&&!s&&N.length>0&&h.jsxs("div",{className:"mt-10",children:[h.jsxs("button",{className:"flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors mb-4 group",onClick:()=>v(A=>!A),children:[b?h.jsx(rd,{className:"h-4 w-4"}):h.jsx(Em,{className:"h-4 w-4"}),h.jsx("span",{className:"font-medium",children:"Archived"}),h.jsx("span",{className:"text-xs bg-muted rounded-full px-2 py-0.5",children:N.length})]}),b&&h.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",children:N.map(A=>h.jsx(tx,{project:A,active:!1,...O},A.id))})]}),h.jsx(m3,{})]}),h.jsx(d3,{open:o,onOpenChange:l,onCreated:w}),h.jsx(h3,{open:c,onOpenChange:d,onOpened:k})]})}/**
407
407
  * Copyright (c) 2014-2024 The xterm.js authors. All rights reserved.
408
408
  * @license MIT
409
409
  *
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/terminal.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>CC Web</title>
8
- <script type="module" crossorigin src="/assets/index-BDrcGT83.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-CEcwUYC4.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-Cx18Pfpi.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tom2012/cc-web",
3
- "version": "1.5.20",
3
+ "version": "1.5.21",
4
4
  "description": "Self-hosted web UI for Claude Code CLI — run Claude in your browser",
5
5
  "private": false,
6
6
  "main": "electron/dist/main.js",