@tom2012/cc-web 1.5.10

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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +339 -0
  3. package/backend/dist/auth.d.ts +15 -0
  4. package/backend/dist/auth.d.ts.map +1 -0
  5. package/backend/dist/auth.js +92 -0
  6. package/backend/dist/auth.js.map +1 -0
  7. package/backend/dist/config.d.ts +33 -0
  8. package/backend/dist/config.d.ts.map +1 -0
  9. package/backend/dist/config.js +155 -0
  10. package/backend/dist/config.js.map +1 -0
  11. package/backend/dist/index.d.ts +3 -0
  12. package/backend/dist/index.d.ts.map +1 -0
  13. package/backend/dist/index.js +499 -0
  14. package/backend/dist/index.js.map +1 -0
  15. package/backend/dist/routes/auth.d.ts +3 -0
  16. package/backend/dist/routes/auth.d.ts.map +1 -0
  17. package/backend/dist/routes/auth.js +108 -0
  18. package/backend/dist/routes/auth.js.map +1 -0
  19. package/backend/dist/routes/filesystem.d.ts +3 -0
  20. package/backend/dist/routes/filesystem.d.ts.map +1 -0
  21. package/backend/dist/routes/filesystem.js +243 -0
  22. package/backend/dist/routes/filesystem.js.map +1 -0
  23. package/backend/dist/routes/projects.d.ts +3 -0
  24. package/backend/dist/routes/projects.d.ts.map +1 -0
  25. package/backend/dist/routes/projects.js +235 -0
  26. package/backend/dist/routes/projects.js.map +1 -0
  27. package/backend/dist/routes/shortcuts.d.ts +3 -0
  28. package/backend/dist/routes/shortcuts.d.ts.map +1 -0
  29. package/backend/dist/routes/shortcuts.js +88 -0
  30. package/backend/dist/routes/shortcuts.js.map +1 -0
  31. package/backend/dist/routes/update.d.ts +3 -0
  32. package/backend/dist/routes/update.d.ts.map +1 -0
  33. package/backend/dist/routes/update.js +104 -0
  34. package/backend/dist/routes/update.js.map +1 -0
  35. package/backend/dist/session-manager.d.ts +47 -0
  36. package/backend/dist/session-manager.d.ts.map +1 -0
  37. package/backend/dist/session-manager.js +345 -0
  38. package/backend/dist/session-manager.js.map +1 -0
  39. package/backend/dist/terminal-manager.d.ts +27 -0
  40. package/backend/dist/terminal-manager.d.ts.map +1 -0
  41. package/backend/dist/terminal-manager.js +211 -0
  42. package/backend/dist/terminal-manager.js.map +1 -0
  43. package/backend/dist/types.d.ts +17 -0
  44. package/backend/dist/types.d.ts.map +1 -0
  45. package/backend/dist/types.js +3 -0
  46. package/backend/dist/types.js.map +1 -0
  47. package/backend/dist/usage-terminal.d.ts +18 -0
  48. package/backend/dist/usage-terminal.d.ts.map +1 -0
  49. package/backend/dist/usage-terminal.js +189 -0
  50. package/backend/dist/usage-terminal.js.map +1 -0
  51. package/backend/package-lock.json +1965 -0
  52. package/backend/package.json +31 -0
  53. package/bin/ccweb.js +478 -0
  54. package/electron/dist/main.js +455 -0
  55. package/frontend/dist/assets/index-CQjbS4zv.css +32 -0
  56. package/frontend/dist/assets/index-CtyR65A4.js +434 -0
  57. package/frontend/dist/index.html +14 -0
  58. package/frontend/dist/terminal.svg +4 -0
  59. package/package.json +88 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 zbc0315
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,339 @@
1
+ # CC Web
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.
4
+
5
+ **Current version**: v1.5.10 | [GitHub](https://github.com/zbc0315/cc-web) | MIT License
6
+
7
+ ## Features
8
+
9
+ - **Project Management**: Create, open, start, stop, and delete projects from a dashboard
10
+ - **Real-time Terminal**: Full xterm.js terminal in the browser, connected to Claude Code via WebSocket
11
+ - **Persistent Sessions**: Conversation history stored in each project's `.ccweb/` folder — survives uninstall/reinstall (max 20 sessions per project, auto-pruned)
12
+ - **Permission Modes**: Run Claude in limited mode (asks before acting) or unlimited mode (`--dangerously-skip-permissions`)
13
+ - **Shortcuts Panel**: Define reusable prompt commands at project or global level with inheritance
14
+ - **Session History**: Browse past conversations with full message history
15
+ - **Graph Visualization**: Topology graph from `.notebook/graph.yaml` with zoom/pan (layered DAG layout)
16
+ - **File Browser**: Browse directories and preview/edit files with zoom-level memory per file
17
+ - **Auto-restart**: Terminals automatically recover from crashes
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
20
+ - **Localhost Auto-auth**: Local access skips login entirely; JWT only required for remote access
21
+ - **Auto Port Switching**: Backend tries ports 3001–3020 and reports the actual port
22
+ - **Dark/Light Theme**: Toggle between themes
23
+
24
+ ## Prerequisites
25
+
26
+ - **Node.js** >= 18
27
+ - **Claude Code CLI** installed and authenticated (`claude` command available in PATH)
28
+
29
+ ## Quick Start — npm / npx
30
+
31
+ The fastest way to get running:
32
+
33
+ ```bash
34
+ # Try without installing (one-time)
35
+ npx cc-web
36
+
37
+ # Or install globally
38
+ npm install -g cc-web
39
+ ccweb
40
+ ```
41
+
42
+ On first launch you'll be prompted to set a username and password. The server auto-selects an available port (starting from 3001) and opens your browser automatically.
43
+
44
+ ### CLI Commands
45
+
46
+ ```bash
47
+ ccweb # start (interactive prompts)
48
+ ccweb start --daemon # start in background, no prompts
49
+ ccweb start --foreground # start in foreground, no prompts
50
+ ccweb stop # stop background server
51
+ ccweb status # show PID, port, data location
52
+ ccweb open # open browser to running server
53
+ ccweb setup # reconfigure username / password
54
+ ccweb enable-autostart # start automatically on login
55
+ ccweb disable-autostart # remove auto-start
56
+ ccweb logs # tail background log file
57
+ ```
58
+
59
+ All user data (credentials, projects, sessions) is stored in `~/.ccweb/` and survives package updates.
60
+
61
+ **Auto-start on login**: `ccweb enable-autostart` registers a launchd agent (macOS) or systemd user service (Linux) so the server starts automatically when you log in.
62
+
63
+ ## Quick Start — from source (development)
64
+
65
+ ```bash
66
+ # 1. Clone the repository
67
+ git clone https://github.com/zbc0315/cc-web.git
68
+ cd cc-web
69
+
70
+ # 2. Install dependencies
71
+ npm run install:all
72
+
73
+ # 3. First-time setup (creates login credentials)
74
+ npm run setup
75
+
76
+ # 4. Start backend (Terminal 1)
77
+ npm run dev:backend
78
+
79
+ # 5. Start frontend (Terminal 2)
80
+ npm run dev:frontend
81
+ ```
82
+
83
+ Open http://localhost:5173 in your browser.
84
+
85
+ ## Architecture
86
+
87
+ ```
88
+ Browser (React/Vite :5173 dev | Express :3001 prod)
89
+
90
+ ├── REST API ──────────► Express (:3001, auto-switches port if busy)
91
+ │ │
92
+ └── WebSocket ─────────► ws server (same port)
93
+
94
+ TerminalManager
95
+
96
+ node-pty (PTY, user's $SHELL -ilc "claude")
97
+
98
+ claude / claude --dangerously-skip-permissions
99
+ ```
100
+
101
+ ### Backend (`backend/src/`)
102
+
103
+ | File | Purpose |
104
+ |------|---------|
105
+ | `index.ts` | Express + WS server, route mounting, static frontend serving, auto port switching, project config migration |
106
+ | `auth.ts` | JWT middleware, localhost auto-auth (`isLocalRequest`), `generateLocalToken()` |
107
+ | `config.ts` | File-based JSON store, `.ccweb/` per-project config helpers |
108
+ | `terminal-manager.ts` | PTY lifecycle (`$SHELL -ilc "claude"`), scrollback buffer (5 MB), auto-restart, activity tracking |
109
+ | `session-manager.ts` | Tails Claude's JSONL files, stores sessions in `.ccweb/sessions/`, prunes to latest 20 per project |
110
+ | `usage-terminal.ts` | Claude Code OAuth usage stats |
111
+ | `routes/auth.ts` | `POST /login`, `GET /local-token` (localhost only) |
112
+ | `routes/projects.ts` | CRUD + start/stop + `POST /open` (restore from `.ccweb/`) |
113
+ | `routes/update.ts` | `GET /check-running`, `POST /prepare` (save memory → wait idle → stop all) |
114
+ | `routes/filesystem.ts` | Directory browser, file read/write |
115
+ | `routes/shortcuts.ts` | Global shortcut CRUD with inheritance |
116
+
117
+ ### Frontend (`frontend/src/`)
118
+
119
+ | File/Dir | Purpose |
120
+ |----------|---------|
121
+ | `App.tsx` | Router with auto-auth `PrivateRoute` (local token for localhost) |
122
+ | `pages/LoginPage.tsx` | Login form, auto-login on localhost |
123
+ | `pages/DashboardPage.tsx` | Project grid, new/open project, fullscreen toggle, update button |
124
+ | `pages/ProjectPage.tsx` | Three-panel layout: FileTree \| WebTerminal \| RightPanel |
125
+ | `components/WebTerminal.tsx` | xterm.js terminal with fit addon |
126
+ | `components/RightPanel.tsx` | Three tabs: Shortcuts / History / Graph |
127
+ | `components/ShortcutPanel.tsx` | Project + global shortcuts, dialog editor for add/edit |
128
+ | `components/GraphPreview.tsx` | SVG topology graph of `.notebook/graph.yaml` (layered DAG, zoom/pan) |
129
+ | `components/FileTree.tsx` | Expandable directory tree |
130
+ | `components/FilePreviewDialog.tsx` | File viewer with plain/rendered/edit modes, zoom memory per file |
131
+ | `components/UpdateButton.tsx` | In-app update: check GitHub → save memory → download → install |
132
+ | `components/OpenProjectDialog.tsx` | Open existing project from `.ccweb/` folder |
133
+ | `components/NewProjectDialog.tsx` | 3-step wizard: name → folder → permissions |
134
+ | `lib/api.ts` | Typed REST client, dynamic base URL (relative in prod, localhost:3001 in dev) |
135
+ | `lib/websocket.ts` | `useProjectWebSocket` hook, dynamic WS URL |
136
+ | `components/ui/` | shadcn/ui components (zinc theme) |
137
+
138
+ ### Data Storage
139
+
140
+ **Application data** (`data/` — gitignored, or `~/Library/Application Support/cc-web/data/` in Electron):
141
+
142
+ ```
143
+ data/
144
+ ├── config.json ← credentials & JWT secret
145
+ ├── projects.json ← registered project list
146
+ └── global-shortcuts.json ← shared shortcut commands
147
+ ```
148
+
149
+ **Per-project data** (inside each project folder, portable):
150
+
151
+ ```
152
+ your-project/
153
+ ├── .ccweb/
154
+ │ ├── project.json ← project metadata (id, name, mode, created)
155
+ │ └── sessions/ ← conversation history (max 20, auto-pruned)
156
+ │ └── {timestamp}-{uuid}.json
157
+ └── .notebook/ ← structured notes
158
+ ├── pages/
159
+ └── graph.yaml
160
+ ```
161
+
162
+ The `.ccweb/` folder travels with the project. If you reinstall CC Web later, use **Open Project** to point at the folder and all history is restored.
163
+
164
+ ## WebSocket Protocol
165
+
166
+ **Client → Server:**
167
+
168
+ | Type | Payload | Purpose |
169
+ |------|---------|---------|
170
+ | `auth` | `{ token }` | Authenticate (skipped for localhost) |
171
+ | `terminal_subscribe` | `{ cols, rows }` | Subscribe + replay scrollback |
172
+ | `terminal_input` | `{ data }` | Keystrokes to PTY |
173
+ | `terminal_resize` | `{ cols, rows }` | Resize PTY |
174
+
175
+ **Server → Client:**
176
+
177
+ | Type | Payload | Purpose |
178
+ |------|---------|---------|
179
+ | `connected` | `{ projectId }` | Ready |
180
+ | `status` | `{ status }` | running/stopped/restarting |
181
+ | `terminal_data` | `{ data }` | PTY output |
182
+ | `terminal_subscribed` | `{}` | Subscription confirmed |
183
+ | `error` | `{ message }` | Error |
184
+
185
+ Localhost WebSocket connections are pre-authenticated — no `auth` message needed.
186
+
187
+ ## REST API
188
+
189
+ | Method | Endpoint | Purpose |
190
+ |--------|----------|---------|
191
+ | `POST` | `/api/auth/login` | Login, returns JWT |
192
+ | `GET` | `/api/auth/local-token` | Get local token (localhost only) |
193
+ | `GET` | `/api/projects` | List all projects |
194
+ | `POST` | `/api/projects` | Create new project |
195
+ | `POST` | `/api/projects/open` | Open existing project by folder path |
196
+ | `DELETE` | `/api/projects/:id` | Delete project |
197
+ | `PATCH` | `/api/projects/:id/start` | Start project terminal |
198
+ | `PATCH` | `/api/projects/:id/stop` | Stop project terminal |
199
+ | `GET` | `/api/projects/:id/sessions` | List sessions |
200
+ | `GET` | `/api/projects/:id/sessions/:sid` | Get session with messages |
201
+ | `GET` | `/api/projects/activity` | Terminal activity timestamps |
202
+ | `GET` | `/api/projects/usage` | Claude Code usage stats |
203
+ | `GET/POST/PUT/DELETE` | `/api/shortcuts` | Global shortcut CRUD |
204
+ | `GET` | `/api/filesystem` | Browse directories |
205
+ | `POST` | `/api/filesystem/mkdir` | Create folder |
206
+ | `GET/PUT` | `/api/filesystem/file` | Read/write files |
207
+ | `GET` | `/api/update/check-running` | Check if processes are running |
208
+ | `POST` | `/api/update/prepare` | Save memory, wait idle, stop all |
209
+
210
+ ## macOS Desktop App (Electron)
211
+
212
+ CC Web can be packaged as a standalone macOS app using Electron.
213
+
214
+ ```bash
215
+ # Install all dependencies first
216
+ npm run install:all
217
+ npm install
218
+
219
+ # Build DMG (outputs to release/)
220
+ npm run dist:dmg
221
+ ```
222
+
223
+ The DMG will be at `release/CCWeb-{version}-arm64.dmg`. Double-click to install.
224
+
225
+ 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.
226
+
227
+ **Update flow**: Download zip from GitHub releases → extract with `ditto` → replace app via detached shell script (no code signing required).
228
+
229
+ ### Building for other architectures
230
+
231
+ Edit the `arch` field in `package.json` under `build.mac.target`:
232
+ - `["arm64"]` — Apple Silicon (default)
233
+ - `["x64"]` — Intel Mac
234
+ - `["arm64", "x64"]` — Universal
235
+
236
+ ## Server Deployment (without Electron)
237
+
238
+ ```bash
239
+ # Build everything
240
+ npm run build
241
+
242
+ # Run backend (serves built frontend statically)
243
+ cd backend
244
+ npm start
245
+
246
+ # Or use pm2
247
+ pm2 start backend/dist/index.js --name cc-web
248
+ pm2 save
249
+ pm2 startup
250
+ ```
251
+
252
+ Environment variables:
253
+
254
+ | Variable | Purpose | Default |
255
+ |----------|---------|---------|
256
+ | `CCWEB_DATA_DIR` | Override data directory | `data/` relative to backend |
257
+ | `CCWEB_PORT` | Preferred server port | `3001` |
258
+
259
+ ## Build & Release
260
+
261
+ ```bash
262
+ # Full build (frontend + backend + electron)
263
+ npm run build:all
264
+
265
+ # Build DMG + ZIP for release
266
+ npm run dist
267
+
268
+ # Release checklist:
269
+ # 1. Bump version in package.json
270
+ # 2. Update currentVersion in frontend/src/components/UpdateButton.tsx
271
+ # 3. npm run dist
272
+ # 4. git add -A && git commit && git push
273
+ # 5. gh release create vX.Y.Z --title "CCWeb vX.Y.Z" \
274
+ # release/CCWeb-X.Y.Z-arm64.dmg \
275
+ # release/CCWeb-X.Y.Z-arm64-mac.zip \
276
+ # release/CCWeb-X.Y.Z-arm64-mac.zip.blockmap \
277
+ # release/latest-mac.yml
278
+ ```
279
+
280
+ > **Note**: `productName` in `package.json` must not contain spaces (currently `CCWeb`), otherwise GitHub mangles the filename and auto-update gets 404.
281
+
282
+ ## Development Guide
283
+
284
+ ### Project Structure
285
+
286
+ ```
287
+ cc-web/
288
+ ├── package.json ← Root scripts + Electron build config
289
+ ├── setup.js ← Interactive credential setup
290
+ ├── electron/
291
+ │ ├── main.ts ← Electron main process
292
+ │ └── tsconfig.json
293
+ ├── backend/
294
+ │ ├── package.json
295
+ │ ├── tsconfig.json
296
+ │ └── src/ ← TypeScript source
297
+ └── frontend/
298
+ ├── package.json
299
+ ├── tsconfig.json
300
+ ├── vite.config.ts
301
+ ├── tailwind.config.js
302
+ └── src/ ← React + TypeScript source
303
+ ```
304
+
305
+ ### Key Design Decisions
306
+
307
+ - **PTY-first**: Spawns the real `claude` CLI via `node-pty` using the user's `$SHELL -ilc`. All Claude Code features (slash commands, MCP, hooks, etc.) work natively.
308
+ - **No database**: Pure JSON files, in-memory CRUD. Simple to understand, back up, and debug.
309
+ - **Per-project `.ccweb/`**: Data travels with the project folder, survives app reinstall.
310
+ - **Session tailing**: Reads Claude Code's native JSONL (`~/.claude/projects/`) rather than parsing PTY output.
311
+ - **Scrollback buffer**: 5 MB per terminal for client reconnect replay.
312
+ - **Session pruning**: Keeps latest 20 sessions per project, deletes oldest on new session start.
313
+ - **Zoom memory**: `FilePreviewDialog` persists zoom level per file path in `localStorage`.
314
+ - **Auto port switching**: Backend tries ports 3001–3020, reports actual port to Electron via IPC.
315
+ - **Localhost auto-auth**: Local requests skip JWT verification entirely.
316
+
317
+ ### Adding a New API Endpoint
318
+
319
+ 1. Add the route handler in `backend/src/routes/*.ts`
320
+ 2. Auth is already applied — routes are mounted under `authMiddleware`
321
+ 3. Add the typed call in `frontend/src/lib/api.ts`
322
+ 4. Call it from your component
323
+
324
+ ### Adding a New Frontend Page
325
+
326
+ 1. Create `frontend/src/pages/YourPage.tsx`
327
+ 2. Add a route in `frontend/src/App.tsx`
328
+ 3. Use existing UI components from `frontend/src/components/ui/`
329
+
330
+ ### Tech Stack
331
+
332
+ **Backend**: Node.js, Express, WebSocket (ws), node-pty, TypeScript
333
+ **Frontend**: React 18, Vite, Tailwind CSS, shadcn/ui, xterm.js, TypeScript
334
+ **Desktop**: Electron
335
+ **Auth**: JWT (bcryptjs for password hashing)
336
+
337
+ ## License
338
+
339
+ MIT
@@ -0,0 +1,15 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ export interface AuthRequest extends Request {
3
+ user?: {
4
+ username: string;
5
+ };
6
+ }
7
+ /** Check if request originates from localhost */
8
+ export declare function isLocalRequest(req: Request): boolean;
9
+ export declare function verifyToken(token: string): {
10
+ username: string;
11
+ } | null;
12
+ /** Generate a JWT for local access (no credentials needed) */
13
+ export declare function generateLocalToken(): string;
14
+ export declare function authMiddleware(req: AuthRequest, res: Response, next: NextFunction): void;
15
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAI1D,MAAM,WAAW,WAAY,SAAQ,OAAO;IAC1C,IAAI,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7B;AAED,iDAAiD;AACjD,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAOpD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAQtE;AAED,8DAA8D;AAC9D,wBAAgB,kBAAkB,IAAI,MAAM,CAG3C;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CA2BxF"}
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.isLocalRequest = isLocalRequest;
37
+ exports.verifyToken = verifyToken;
38
+ exports.generateLocalToken = generateLocalToken;
39
+ exports.authMiddleware = authMiddleware;
40
+ const jwt = __importStar(require("jsonwebtoken"));
41
+ const config_1 = require("./config");
42
+ /** Check if request originates from localhost */
43
+ function isLocalRequest(req) {
44
+ const ip = req.ip || req.socket.remoteAddress || '';
45
+ return (ip === '127.0.0.1' ||
46
+ ip === '::1' ||
47
+ ip === '::ffff:127.0.0.1');
48
+ }
49
+ function verifyToken(token) {
50
+ try {
51
+ const config = (0, config_1.getConfig)();
52
+ const decoded = jwt.verify(token, config.jwtSecret);
53
+ return { username: decoded.username };
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ /** Generate a JWT for local access (no credentials needed) */
60
+ function generateLocalToken() {
61
+ const config = (0, config_1.getConfig)();
62
+ return jwt.sign({ username: config.username }, config.jwtSecret, { expiresIn: '30d' });
63
+ }
64
+ function authMiddleware(req, res, next) {
65
+ // Local access: auto-authenticate without token
66
+ if (isLocalRequest(req)) {
67
+ try {
68
+ const config = (0, config_1.getConfig)();
69
+ req.user = { username: config.username };
70
+ }
71
+ catch {
72
+ req.user = { username: 'local' };
73
+ }
74
+ next();
75
+ return;
76
+ }
77
+ // Remote access: require Bearer token
78
+ const authHeader = req.headers['authorization'];
79
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
80
+ res.status(401).json({ error: 'Unauthorized' });
81
+ return;
82
+ }
83
+ const token = authHeader.slice(7);
84
+ const user = verifyToken(token);
85
+ if (!user) {
86
+ res.status(401).json({ error: 'Invalid or expired token' });
87
+ return;
88
+ }
89
+ req.user = user;
90
+ next();
91
+ }
92
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASA,wCAOC;AAED,kCAQC;AAGD,gDAGC;AAED,wCA2BC;AA5DD,kDAAoC;AACpC,qCAAqC;AAMrC,iDAAiD;AACjD,SAAgB,cAAc,CAAC,GAAY;IACzC,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,EAAE,CAAC;IACpD,OAAO,CACL,EAAE,KAAK,WAAW;QAClB,EAAE,KAAK,KAAK;QACZ,EAAE,KAAK,kBAAkB,CAC1B,CAAC;AACJ,CAAC;AAED,SAAgB,WAAW,CAAC,KAAa;IACvC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAA,kBAAS,GAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,SAAS,CAAyB,CAAC;QAC5E,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,8DAA8D;AAC9D,SAAgB,kBAAkB;IAChC,MAAM,MAAM,GAAG,IAAA,kBAAS,GAAE,CAAC;IAC3B,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,MAAM,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;AACzF,CAAC;AAED,SAAgB,cAAc,CAAC,GAAgB,EAAE,GAAa,EAAE,IAAkB;IAChF,gDAAgD;IAChD,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAA,kBAAS,GAAE,CAAC;YAC3B,GAAG,CAAC,IAAI,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,CAAC,IAAI,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;QACnC,CAAC;QACD,IAAI,EAAE,CAAC;QACP,OAAO;IACT,CAAC;IAED,sCAAsC;IACtC,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAChD,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACrD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;QAChD,OAAO;IACT,CAAC;IACD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAChC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC;QAC5D,OAAO;IACT,CAAC;IACD,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;IAChB,IAAI,EAAE,CAAC;AACT,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { Project, Config, CliTool } from './types';
2
+ export interface GlobalShortcut {
3
+ id: string;
4
+ label: string;
5
+ command: string;
6
+ parentId?: string;
7
+ }
8
+ export declare const DATA_DIR: string;
9
+ export declare function initDataDirs(): void;
10
+ export declare function getConfig(): Config;
11
+ export declare function getProjects(): Project[];
12
+ export declare function saveProjects(projects: Project[]): void;
13
+ export declare function getProject(id: string): Project | undefined;
14
+ export declare function saveProject(project: Project): void;
15
+ export declare function deleteProject(id: string): void;
16
+ export declare function getGlobalShortcuts(): GlobalShortcut[];
17
+ export declare function saveGlobalShortcuts(shortcuts: GlobalShortcut[]): void;
18
+ export interface ProjectConfig {
19
+ id: string;
20
+ name: string;
21
+ permissionMode: 'limited' | 'unlimited';
22
+ cliTool: CliTool;
23
+ createdAt: string;
24
+ }
25
+ export declare function ccwebDir(folderPath: string): string;
26
+ export declare function ccwebSessionsDir(folderPath: string): string;
27
+ /** Write .ccweb/project.json into the project folder */
28
+ export declare function writeProjectConfig(folderPath: string, project: Project): void;
29
+ /** Read .ccweb/project.json from a project folder. Returns null if not found. */
30
+ export declare function readProjectConfig(folderPath: string): ProjectConfig | null;
31
+ /** Update .ccweb/project.json (partial update) */
32
+ export declare function updateProjectConfig(folderPath: string, updates: Partial<ProjectConfig>): void;
33
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEnD,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,QAAQ,QAAmE,CAAC;AAYzF,wBAAgB,YAAY,IAAI,IAAI,CAOnC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAMlC;AAED,wBAAgB,WAAW,IAAI,OAAO,EAAE,CAIvC;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAI,CAEtD;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAE1D;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CASlD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE9C;AAED,wBAAgB,kBAAkB,IAAI,cAAc,EAAE,CAGrD;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,cAAc,EAAE,GAAG,IAAI,CAErE;AAOD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,SAAS,GAAG,WAAW,CAAC;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wDAAwD;AACxD,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAW7E;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAQ1E;AAED,kDAAkD;AAClD,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAM7F"}
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.DATA_DIR = void 0;
37
+ exports.initDataDirs = initDataDirs;
38
+ exports.getConfig = getConfig;
39
+ exports.getProjects = getProjects;
40
+ exports.saveProjects = saveProjects;
41
+ exports.getProject = getProject;
42
+ exports.saveProject = saveProject;
43
+ exports.deleteProject = deleteProject;
44
+ exports.getGlobalShortcuts = getGlobalShortcuts;
45
+ exports.saveGlobalShortcuts = saveGlobalShortcuts;
46
+ exports.ccwebDir = ccwebDir;
47
+ exports.ccwebSessionsDir = ccwebSessionsDir;
48
+ exports.writeProjectConfig = writeProjectConfig;
49
+ exports.readProjectConfig = readProjectConfig;
50
+ exports.updateProjectConfig = updateProjectConfig;
51
+ const fs = __importStar(require("fs"));
52
+ const path = __importStar(require("path"));
53
+ exports.DATA_DIR = process.env.CCWEB_DATA_DIR || path.join(__dirname, '../../data');
54
+ /** Atomic write: write to temp file then rename, preventing corruption on crash */
55
+ function atomicWriteSync(filePath, data) {
56
+ const tmpPath = filePath + `.tmp.${process.pid}`;
57
+ fs.writeFileSync(tmpPath, data, 'utf-8');
58
+ fs.renameSync(tmpPath, filePath);
59
+ }
60
+ const CONFIG_FILE = path.join(exports.DATA_DIR, 'config.json');
61
+ const PROJECTS_FILE = path.join(exports.DATA_DIR, 'projects.json');
62
+ const SHORTCUTS_FILE = path.join(exports.DATA_DIR, 'global-shortcuts.json');
63
+ function initDataDirs() {
64
+ if (!fs.existsSync(exports.DATA_DIR)) {
65
+ fs.mkdirSync(exports.DATA_DIR, { recursive: true });
66
+ }
67
+ if (!fs.existsSync(PROJECTS_FILE)) {
68
+ fs.writeFileSync(PROJECTS_FILE, JSON.stringify([], null, 2), 'utf-8');
69
+ }
70
+ }
71
+ function getConfig() {
72
+ if (!fs.existsSync(CONFIG_FILE)) {
73
+ throw new Error('Config file not found. Please run: npm run setup');
74
+ }
75
+ const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');
76
+ return JSON.parse(raw);
77
+ }
78
+ function getProjects() {
79
+ if (!fs.existsSync(PROJECTS_FILE))
80
+ return [];
81
+ const raw = fs.readFileSync(PROJECTS_FILE, 'utf-8');
82
+ return JSON.parse(raw);
83
+ }
84
+ function saveProjects(projects) {
85
+ atomicWriteSync(PROJECTS_FILE, JSON.stringify(projects, null, 2));
86
+ }
87
+ function getProject(id) {
88
+ return getProjects().find((p) => p.id === id);
89
+ }
90
+ function saveProject(project) {
91
+ const projects = getProjects();
92
+ const index = projects.findIndex((p) => p.id === project.id);
93
+ if (index >= 0) {
94
+ projects[index] = project;
95
+ }
96
+ else {
97
+ projects.push(project);
98
+ }
99
+ saveProjects(projects);
100
+ }
101
+ function deleteProject(id) {
102
+ saveProjects(getProjects().filter((p) => p.id !== id));
103
+ }
104
+ function getGlobalShortcuts() {
105
+ if (!fs.existsSync(SHORTCUTS_FILE))
106
+ return [];
107
+ return JSON.parse(fs.readFileSync(SHORTCUTS_FILE, 'utf-8'));
108
+ }
109
+ function saveGlobalShortcuts(shortcuts) {
110
+ atomicWriteSync(SHORTCUTS_FILE, JSON.stringify(shortcuts, null, 2));
111
+ }
112
+ // ── .ccweb/ per-project config ────────────────────────────────────────────────
113
+ const CCWEB_DIR = '.ccweb';
114
+ const PROJECT_CONFIG_FILE = 'project.json';
115
+ function ccwebDir(folderPath) {
116
+ return path.join(folderPath, CCWEB_DIR);
117
+ }
118
+ function ccwebSessionsDir(folderPath) {
119
+ return path.join(folderPath, CCWEB_DIR, 'sessions');
120
+ }
121
+ /** Write .ccweb/project.json into the project folder */
122
+ function writeProjectConfig(folderPath, project) {
123
+ const dir = ccwebDir(folderPath);
124
+ fs.mkdirSync(dir, { recursive: true });
125
+ const config = {
126
+ id: project.id,
127
+ name: project.name,
128
+ permissionMode: project.permissionMode,
129
+ cliTool: project.cliTool,
130
+ createdAt: project.createdAt,
131
+ };
132
+ atomicWriteSync(path.join(dir, PROJECT_CONFIG_FILE), JSON.stringify(config, null, 2));
133
+ }
134
+ /** Read .ccweb/project.json from a project folder. Returns null if not found. */
135
+ function readProjectConfig(folderPath) {
136
+ const file = path.join(ccwebDir(folderPath), PROJECT_CONFIG_FILE);
137
+ if (!fs.existsSync(file))
138
+ return null;
139
+ try {
140
+ return JSON.parse(fs.readFileSync(file, 'utf-8'));
141
+ }
142
+ catch {
143
+ return null;
144
+ }
145
+ }
146
+ /** Update .ccweb/project.json (partial update) */
147
+ function updateProjectConfig(folderPath, updates) {
148
+ const existing = readProjectConfig(folderPath);
149
+ if (!existing)
150
+ return;
151
+ const merged = { ...existing, ...updates };
152
+ const dir = ccwebDir(folderPath);
153
+ atomicWriteSync(path.join(dir, PROJECT_CONFIG_FILE), JSON.stringify(merged, null, 2));
154
+ }
155
+ //# sourceMappingURL=config.js.map