@tom2012/cc-web 1.5.73 → 1.5.76
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 +1 -1
- package/backend/dist/index.d.ts.map +1 -1
- package/backend/dist/index.js +22 -0
- package/backend/dist/index.js.map +1 -1
- package/backend/dist/plugin-manager.d.ts +88 -0
- package/backend/dist/plugin-manager.d.ts.map +1 -0
- package/backend/dist/plugin-manager.js +307 -0
- package/backend/dist/plugin-manager.js.map +1 -0
- package/backend/dist/routes/claude.js +2 -2
- package/backend/dist/routes/claude.js.map +1 -1
- package/backend/dist/routes/filesystem.js +1 -1
- package/backend/dist/routes/filesystem.js.map +1 -1
- package/backend/dist/routes/plugin-bridge.d.ts +3 -0
- package/backend/dist/routes/plugin-bridge.d.ts.map +1 -0
- package/backend/dist/routes/plugin-bridge.js +141 -0
- package/backend/dist/routes/plugin-bridge.js.map +1 -0
- package/backend/dist/routes/plugins.d.ts +3 -0
- package/backend/dist/routes/plugins.d.ts.map +1 -0
- package/backend/dist/routes/plugins.js +190 -0
- package/backend/dist/routes/plugins.js.map +1 -0
- package/backend/dist/routes/skillhub.d.ts.map +1 -1
- package/backend/dist/routes/skillhub.js +22 -0
- package/backend/dist/routes/skillhub.js.map +1 -1
- package/backend/dist/terminal-manager.d.ts.map +1 -1
- package/backend/dist/terminal-manager.js +2 -1
- package/backend/dist/terminal-manager.js.map +1 -1
- package/backend/package-lock.json +21 -0
- package/backend/package.json +2 -0
- package/bin/ccweb-plugin.js +407 -0
- package/bin/ccweb.js +45 -10
- package/frontend/dist/assets/{GraphPreview-AjRbINBy.js → GraphPreview-CiL6ZjdW.js} +1 -1
- package/frontend/dist/assets/{OfficePreview-Q9JIuSn6.js → OfficePreview-laIUc98k.js} +3 -3
- package/frontend/dist/assets/{ProjectPage-DKIPvilo.js → ProjectPage-BmYhTTYq.js} +57 -57
- package/frontend/dist/assets/{SettingsPage-C8JtKKqC.js → SettingsPage-C30iHtBo.js} +1 -1
- package/frontend/dist/assets/{ShareViewPage-Cai400b7.js → ShareViewPage-BGAvzqO1.js} +1 -1
- package/frontend/dist/assets/SkillHubPage-BSb-9jya.js +11 -0
- package/frontend/dist/assets/{bot-CSseHzMu.js → bot-hRRV5XmX.js} +1 -1
- package/frontend/dist/assets/{chevron-down-DRKgAnVD.js → chevron-down-BR0IMbA9.js} +1 -1
- package/frontend/dist/assets/{download-C4SEOksg.js → download-UC5GvInE.js} +1 -1
- package/frontend/dist/assets/{index-B0AjOEnZ.js → index-BiYAwwZA.js} +24 -24
- package/frontend/dist/assets/index-D7mCoW2r.js +295 -0
- package/frontend/dist/assets/index-DgHvs3fU.css +1 -0
- package/frontend/dist/assets/{jszip.min-Bi48KS2o.js → jszip.min-YWYO5s4l.js} +1 -1
- package/frontend/dist/assets/{save-CFqjwCN9.js → save-CdDjQhdC.js} +1 -1
- package/frontend/dist/assets/{user-CsHBuG4B.js → user-Bv38vpTL.js} +1 -1
- package/frontend/dist/index.html +2 -2
- package/package.json +6 -3
- package/plugin-sdk/ccweb-plugin-sdk.js +123 -0
- package/plugins/pomodoro/frontend/index.html +242 -0
- package/plugins/pomodoro/manifest.json +19 -0
- package/frontend/dist/assets/SkillHubPage-CzcGAwA0.js +0 -6
- package/frontend/dist/assets/index-BKftRi-t.js +0 -280
- package/frontend/dist/assets/index-PNcejD9U.css +0 -1
package/backend/package.json
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@azure/msal-node": "^3.8.10",
|
|
12
12
|
"@microsoft/microsoft-graph-client": "^3.0.7",
|
|
13
|
+
"adm-zip": "^0.5.16",
|
|
13
14
|
"bcryptjs": "^2.4.3",
|
|
14
15
|
"cors": "^2.8.5",
|
|
15
16
|
"dropbox": "^10.34.0",
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
"ws": "^8.16.0"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
28
|
+
"@types/adm-zip": "^0.5.8",
|
|
27
29
|
"@types/bcryptjs": "^2.4.6",
|
|
28
30
|
"@types/cors": "^2.8.17",
|
|
29
31
|
"@types/express": "^4.17.21",
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const readline = require('readline');
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const command = args[0];
|
|
10
|
+
|
|
11
|
+
if (!command || command === '--help' || command === '-h') {
|
|
12
|
+
console.log(`
|
|
13
|
+
ccweb-plugin — CLI scaffold for ccweb plugins
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
ccweb-plugin create <name> Create a new plugin project
|
|
17
|
+
ccweb-plugin build Build plugin into a zip
|
|
18
|
+
ccweb-plugin --help Show this help
|
|
19
|
+
`);
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ── create ──────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
if (command === 'create') {
|
|
26
|
+
const name = args[1];
|
|
27
|
+
if (!name) {
|
|
28
|
+
console.error('Usage: ccweb-plugin create <name>');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
33
|
+
const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
34
|
+
|
|
35
|
+
(async () => {
|
|
36
|
+
const id = name.replace(/[^a-z0-9-]/gi, '-').toLowerCase();
|
|
37
|
+
const displayName = await ask(`Plugin display name [${name}]: `) || name;
|
|
38
|
+
const author = await ask('Author: ') || 'anonymous';
|
|
39
|
+
const description = await ask('Description: ') || '';
|
|
40
|
+
const hasBackend = (await ask('Include backend? (y/N): ')).toLowerCase() === 'y';
|
|
41
|
+
|
|
42
|
+
const scopeInput = await ask('Scope (global/dashboard/project) [global]: ') || 'global';
|
|
43
|
+
const scopes = scopeInput.split(',').map(s => s.trim()).filter(Boolean);
|
|
44
|
+
|
|
45
|
+
const permInput = await ask('Permissions (comma-separated, e.g. system:info,project:list): ') || '';
|
|
46
|
+
const permissions = permInput.split(',').map(s => s.trim()).filter(Boolean);
|
|
47
|
+
|
|
48
|
+
const width = parseInt(await ask('Default width [320]: ') || '320', 10);
|
|
49
|
+
const height = parseInt(await ask('Default height [240]: ') || '240', 10);
|
|
50
|
+
|
|
51
|
+
rl.close();
|
|
52
|
+
|
|
53
|
+
const dir = path.resolve(id);
|
|
54
|
+
if (fs.existsSync(dir)) {
|
|
55
|
+
console.error(`Directory "${id}" already exists`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Create directory structure
|
|
60
|
+
fs.mkdirSync(path.join(dir, 'frontend'), { recursive: true });
|
|
61
|
+
if (hasBackend) fs.mkdirSync(path.join(dir, 'backend'), { recursive: true });
|
|
62
|
+
|
|
63
|
+
// manifest.json
|
|
64
|
+
const manifest = {
|
|
65
|
+
id,
|
|
66
|
+
name: displayName,
|
|
67
|
+
version: '1.0.0',
|
|
68
|
+
author,
|
|
69
|
+
description,
|
|
70
|
+
type: 'float',
|
|
71
|
+
float: {
|
|
72
|
+
defaultWidth: width,
|
|
73
|
+
defaultHeight: height,
|
|
74
|
+
minWidth: 200,
|
|
75
|
+
minHeight: 150,
|
|
76
|
+
resizable: true,
|
|
77
|
+
scope: { allowed: scopes, default: scopes[0] || 'global' },
|
|
78
|
+
clickable: { allowed: [true, false], default: true },
|
|
79
|
+
},
|
|
80
|
+
permissions,
|
|
81
|
+
frontend: { entry: 'frontend/index.html' },
|
|
82
|
+
};
|
|
83
|
+
if (hasBackend) manifest.backend = { entry: 'backend/index.js' };
|
|
84
|
+
fs.writeFileSync(path.join(dir, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
85
|
+
|
|
86
|
+
// frontend/index.html
|
|
87
|
+
fs.writeFileSync(path.join(dir, 'frontend', 'index.html'), `<!DOCTYPE html>
|
|
88
|
+
<html lang="en">
|
|
89
|
+
<head>
|
|
90
|
+
<meta charset="UTF-8">
|
|
91
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
92
|
+
<title>${displayName}</title>
|
|
93
|
+
<script src="/plugin-sdk/ccweb-plugin-sdk.js"><\/script>
|
|
94
|
+
<style>
|
|
95
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
96
|
+
body {
|
|
97
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
98
|
+
background: #0a0a0a;
|
|
99
|
+
color: #e4e4e7;
|
|
100
|
+
padding: 12px;
|
|
101
|
+
font-size: 13px;
|
|
102
|
+
}
|
|
103
|
+
h3 { font-size: 14px; margin-bottom: 8px; color: #a1a1aa; }
|
|
104
|
+
.value { font-size: 20px; font-weight: 600; color: #fafafa; }
|
|
105
|
+
.label { font-size: 11px; color: #71717a; margin-top: 2px; }
|
|
106
|
+
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
107
|
+
.card {
|
|
108
|
+
background: #18181b;
|
|
109
|
+
border: 1px solid #27272a;
|
|
110
|
+
border-radius: 8px;
|
|
111
|
+
padding: 10px;
|
|
112
|
+
}
|
|
113
|
+
</style>
|
|
114
|
+
</head>
|
|
115
|
+
<body>
|
|
116
|
+
<h3>${displayName}</h3>
|
|
117
|
+
<div class="grid" id="content">
|
|
118
|
+
<div class="card">
|
|
119
|
+
<div class="value" id="main-value">--</div>
|
|
120
|
+
<div class="label">Loading...</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<script>
|
|
125
|
+
// Example: fetch data and display
|
|
126
|
+
async function update() {
|
|
127
|
+
try {
|
|
128
|
+
${permissions.includes('system:info')
|
|
129
|
+
? `const info = await ccweb.getSystemInfo();
|
|
130
|
+
document.getElementById('main-value').textContent = info.cpu.usage + '%';
|
|
131
|
+
document.querySelector('.label').textContent = 'CPU Usage';`
|
|
132
|
+
: `document.getElementById('main-value').textContent = 'Hello!';
|
|
133
|
+
document.querySelector('.label').textContent = '${displayName} is running';`}
|
|
134
|
+
} catch (err) {
|
|
135
|
+
document.getElementById('main-value').textContent = 'Error';
|
|
136
|
+
document.querySelector('.label').textContent = err.message;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
update();
|
|
140
|
+
setInterval(update, 3000);
|
|
141
|
+
<\/script>
|
|
142
|
+
</body>
|
|
143
|
+
</html>
|
|
144
|
+
`);
|
|
145
|
+
|
|
146
|
+
// backend/index.js (optional)
|
|
147
|
+
if (hasBackend) {
|
|
148
|
+
fs.writeFileSync(path.join(dir, 'backend', 'index.js'), `'use strict';
|
|
149
|
+
|
|
150
|
+
const { Router } = require('express');
|
|
151
|
+
const router = Router();
|
|
152
|
+
|
|
153
|
+
// Plugin backend API routes
|
|
154
|
+
// These are mounted at /api/plugins/${id}/
|
|
155
|
+
|
|
156
|
+
router.get('/status', (_req, res) => {
|
|
157
|
+
res.json({ status: 'ok', plugin: '${id}' });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Optional lifecycle hooks
|
|
161
|
+
function onStart() {
|
|
162
|
+
console.log('[${id}] Plugin backend started');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function onStop() {
|
|
166
|
+
console.log('[${id}] Plugin backend stopped');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = { router, onStart, onStop };
|
|
170
|
+
`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// package.json
|
|
174
|
+
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({
|
|
175
|
+
name: `ccweb-plugin-${id}`,
|
|
176
|
+
version: '1.0.0',
|
|
177
|
+
description,
|
|
178
|
+
private: true,
|
|
179
|
+
scripts: {
|
|
180
|
+
build: 'node build.js',
|
|
181
|
+
},
|
|
182
|
+
}, null, 2));
|
|
183
|
+
|
|
184
|
+
// build.js — uses Node.js zlib for cross-platform zip creation (no external zip command needed)
|
|
185
|
+
fs.writeFileSync(path.join(dir, 'build.js'), `'use strict';
|
|
186
|
+
const fs = require('fs');
|
|
187
|
+
const path = require('path');
|
|
188
|
+
const zlib = require('zlib');
|
|
189
|
+
|
|
190
|
+
const manifest = require('./manifest.json');
|
|
191
|
+
const outFile = manifest.id + '-' + manifest.version + '.zip';
|
|
192
|
+
|
|
193
|
+
if (fs.existsSync(outFile)) fs.unlinkSync(outFile);
|
|
194
|
+
|
|
195
|
+
// Minimal ZIP creator (store + deflate, no external deps)
|
|
196
|
+
function collectFiles(dir, base) {
|
|
197
|
+
let files = [];
|
|
198
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
199
|
+
if (entry.name === '.DS_Store' || entry.name === 'node_modules') continue;
|
|
200
|
+
const full = path.join(dir, entry.name);
|
|
201
|
+
const rel = base ? base + '/' + entry.name : entry.name;
|
|
202
|
+
if (entry.isDirectory()) files = files.concat(collectFiles(full, rel));
|
|
203
|
+
else files.push({ path: rel, data: fs.readFileSync(full) });
|
|
204
|
+
}
|
|
205
|
+
return files;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const sources = ['manifest.json', 'frontend/'${hasBackend ? ", 'backend/'" : ''}];
|
|
209
|
+
let entries = [];
|
|
210
|
+
for (const src of sources) {
|
|
211
|
+
const full = path.resolve(src);
|
|
212
|
+
if (!fs.existsSync(full)) continue;
|
|
213
|
+
if (fs.statSync(full).isDirectory()) entries = entries.concat(collectFiles(full, src.replace(/\\/$/, '')));
|
|
214
|
+
else entries.push({ path: src, data: fs.readFileSync(full) });
|
|
215
|
+
}
|
|
216
|
+
if (fs.existsSync('icon.svg')) entries.push({ path: 'icon.svg', data: fs.readFileSync('icon.svg') });
|
|
217
|
+
|
|
218
|
+
// Build ZIP binary
|
|
219
|
+
const parts = [];
|
|
220
|
+
const central = [];
|
|
221
|
+
let offset = 0;
|
|
222
|
+
for (const entry of entries) {
|
|
223
|
+
const compressed = zlib.deflateRawSync(entry.data);
|
|
224
|
+
const nameBytes = Buffer.from(entry.path, 'utf-8');
|
|
225
|
+
// Local file header
|
|
226
|
+
const local = Buffer.alloc(30);
|
|
227
|
+
local.writeUInt32LE(0x04034b50, 0); // signature
|
|
228
|
+
local.writeUInt16LE(20, 4); // version needed
|
|
229
|
+
local.writeUInt16LE(8, 8); // compression: deflate
|
|
230
|
+
const crc = crc32(entry.data);
|
|
231
|
+
local.writeUInt32LE(crc, 14);
|
|
232
|
+
local.writeUInt32LE(compressed.length, 18);
|
|
233
|
+
local.writeUInt32LE(entry.data.length, 22);
|
|
234
|
+
local.writeUInt16LE(nameBytes.length, 26);
|
|
235
|
+
parts.push(local, nameBytes, compressed);
|
|
236
|
+
// Central directory header
|
|
237
|
+
const cdir = Buffer.alloc(46);
|
|
238
|
+
cdir.writeUInt32LE(0x02014b50, 0);
|
|
239
|
+
cdir.writeUInt16LE(20, 4);
|
|
240
|
+
cdir.writeUInt16LE(20, 6);
|
|
241
|
+
cdir.writeUInt16LE(8, 10);
|
|
242
|
+
cdir.writeUInt32LE(crc, 16);
|
|
243
|
+
cdir.writeUInt32LE(compressed.length, 20);
|
|
244
|
+
cdir.writeUInt32LE(entry.data.length, 24);
|
|
245
|
+
cdir.writeUInt16LE(nameBytes.length, 28);
|
|
246
|
+
cdir.writeUInt32LE(offset, 42);
|
|
247
|
+
central.push(cdir, nameBytes);
|
|
248
|
+
offset += 30 + nameBytes.length + compressed.length;
|
|
249
|
+
}
|
|
250
|
+
const centralSize = central.reduce((s, b) => s + b.length, 0);
|
|
251
|
+
const eocd = Buffer.alloc(22);
|
|
252
|
+
eocd.writeUInt32LE(0x06054b50, 0);
|
|
253
|
+
eocd.writeUInt16LE(entries.length, 8);
|
|
254
|
+
eocd.writeUInt16LE(entries.length, 10);
|
|
255
|
+
eocd.writeUInt32LE(centralSize, 12);
|
|
256
|
+
eocd.writeUInt32LE(offset, 16);
|
|
257
|
+
parts.push(...central, eocd);
|
|
258
|
+
fs.writeFileSync(outFile, Buffer.concat(parts));
|
|
259
|
+
|
|
260
|
+
function crc32(buf) {
|
|
261
|
+
let crc = 0xFFFFFFFF;
|
|
262
|
+
for (let i = 0; i < buf.length; i++) {
|
|
263
|
+
crc ^= buf[i];
|
|
264
|
+
for (let j = 0; j < 8; j++) crc = (crc >>> 1) ^ (crc & 1 ? 0xEDB88320 : 0);
|
|
265
|
+
}
|
|
266
|
+
return (crc ^ 0xFFFFFFFF) >>> 0;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.log('Built: ' + outFile + ' (' + (fs.statSync(outFile).size / 1024).toFixed(1) + ' KB)');
|
|
270
|
+
`);
|
|
271
|
+
|
|
272
|
+
// README.md
|
|
273
|
+
fs.writeFileSync(path.join(dir, 'README.md'), `# ${displayName}
|
|
274
|
+
|
|
275
|
+
${description}
|
|
276
|
+
|
|
277
|
+
## Development
|
|
278
|
+
|
|
279
|
+
1. Edit \`frontend/index.html\` — the plugin UI (runs in iframe)
|
|
280
|
+
${hasBackend ? '2. Edit `backend/index.js` — Express router (mounted at `/api/plugins/' + id + '/`)\n' : ''}
|
|
281
|
+
## Build
|
|
282
|
+
|
|
283
|
+
\`\`\`bash
|
|
284
|
+
npm run build
|
|
285
|
+
\`\`\`
|
|
286
|
+
|
|
287
|
+
This creates \`${id}-1.0.0.zip\` which can be uploaded to the ccweb Plugin Hub.
|
|
288
|
+
|
|
289
|
+
## SDK API
|
|
290
|
+
|
|
291
|
+
In your \`index.html\`, include the SDK:
|
|
292
|
+
\`\`\`html
|
|
293
|
+
<script src="/plugin-sdk/ccweb-plugin-sdk.js"></script>
|
|
294
|
+
\`\`\`
|
|
295
|
+
|
|
296
|
+
Available methods:
|
|
297
|
+
- \`ccweb.getSystemInfo()\` — CPU, memory, uptime (requires \`system:info\`)
|
|
298
|
+
- \`ccweb.getProjectList()\` — list all projects (requires \`project:list\`)
|
|
299
|
+
- \`ccweb.getProjectStatus(id)\` — project status (requires \`project:status\`)
|
|
300
|
+
- \`ccweb.sendTerminal(id, data)\` — send to terminal (requires \`terminal:send\`)
|
|
301
|
+
- \`ccweb.getSessionHistory(id)\` — chat history (requires \`session:read\`)
|
|
302
|
+
- \`ccweb.getStorage()\` / \`ccweb.setStorage(data)\` — private persistent data
|
|
303
|
+
- \`ccweb.backendApi(method, path, body)\` — call plugin's own backend
|
|
304
|
+
`);
|
|
305
|
+
|
|
306
|
+
console.log(`\n✅ Plugin "${displayName}" created in ./${id}/`);
|
|
307
|
+
console.log(`\nNext steps:`);
|
|
308
|
+
console.log(` cd ${id}`);
|
|
309
|
+
console.log(` # Edit frontend/index.html`);
|
|
310
|
+
${hasBackend ? "console.log(` # Edit backend/index.js`);" : ''}
|
|
311
|
+
console.log(` npm run build # Create zip for Hub upload`);
|
|
312
|
+
})();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ── build ───────────────────────────────────────────────────────────────────
|
|
316
|
+
|
|
317
|
+
if (command === 'build') {
|
|
318
|
+
const manifestPath = path.resolve('manifest.json');
|
|
319
|
+
if (!fs.existsSync(manifestPath)) {
|
|
320
|
+
console.error('manifest.json not found. Run this command from a plugin directory.');
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
// Delegate to build.js if it exists, else do inline build
|
|
324
|
+
const buildScript = path.resolve('build.js');
|
|
325
|
+
if (fs.existsSync(buildScript)) {
|
|
326
|
+
require(buildScript);
|
|
327
|
+
} else {
|
|
328
|
+
const zlib = require('zlib');
|
|
329
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
330
|
+
const outFile = `${manifest.id}-${manifest.version}.zip`;
|
|
331
|
+
|
|
332
|
+
// Collect files
|
|
333
|
+
function collectFiles(dir, base) {
|
|
334
|
+
let files = [];
|
|
335
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
336
|
+
if (entry.name === '.DS_Store' || entry.name === 'node_modules') continue;
|
|
337
|
+
const full = path.join(dir, entry.name);
|
|
338
|
+
const rel = base ? base + '/' + entry.name : entry.name;
|
|
339
|
+
if (entry.isDirectory()) files = files.concat(collectFiles(full, rel));
|
|
340
|
+
else files.push({ path: rel, data: fs.readFileSync(full) });
|
|
341
|
+
}
|
|
342
|
+
return files;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const sources = ['manifest.json', 'frontend/'];
|
|
346
|
+
if (manifest.backend) sources.push('backend/');
|
|
347
|
+
let entries = [];
|
|
348
|
+
for (const src of sources) {
|
|
349
|
+
const full = path.resolve(src);
|
|
350
|
+
if (!fs.existsSync(full)) continue;
|
|
351
|
+
if (fs.statSync(full).isDirectory()) entries = entries.concat(collectFiles(full, src.replace(/\/$/, '')));
|
|
352
|
+
else entries.push({ path: src, data: fs.readFileSync(full) });
|
|
353
|
+
}
|
|
354
|
+
if (fs.existsSync('icon.svg')) entries.push({ path: 'icon.svg', data: fs.readFileSync('icon.svg') });
|
|
355
|
+
|
|
356
|
+
// Build ZIP binary
|
|
357
|
+
function crc32(buf) {
|
|
358
|
+
let crc = 0xFFFFFFFF;
|
|
359
|
+
for (let i = 0; i < buf.length; i++) {
|
|
360
|
+
crc ^= buf[i];
|
|
361
|
+
for (let j = 0; j < 8; j++) crc = (crc >>> 1) ^ (crc & 1 ? 0xEDB88320 : 0);
|
|
362
|
+
}
|
|
363
|
+
return (crc ^ 0xFFFFFFFF) >>> 0;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const parts = [];
|
|
367
|
+
const central = [];
|
|
368
|
+
let offset = 0;
|
|
369
|
+
for (const entry of entries) {
|
|
370
|
+
const compressed = zlib.deflateRawSync(entry.data);
|
|
371
|
+
const nameBytes = Buffer.from(entry.path, 'utf-8');
|
|
372
|
+
const local = Buffer.alloc(30);
|
|
373
|
+
local.writeUInt32LE(0x04034b50, 0);
|
|
374
|
+
local.writeUInt16LE(20, 4);
|
|
375
|
+
local.writeUInt16LE(8, 8);
|
|
376
|
+
const crc = crc32(entry.data);
|
|
377
|
+
local.writeUInt32LE(crc, 14);
|
|
378
|
+
local.writeUInt32LE(compressed.length, 18);
|
|
379
|
+
local.writeUInt32LE(entry.data.length, 22);
|
|
380
|
+
local.writeUInt16LE(nameBytes.length, 26);
|
|
381
|
+
parts.push(local, nameBytes, compressed);
|
|
382
|
+
const cdir = Buffer.alloc(46);
|
|
383
|
+
cdir.writeUInt32LE(0x02014b50, 0);
|
|
384
|
+
cdir.writeUInt16LE(20, 4);
|
|
385
|
+
cdir.writeUInt16LE(20, 6);
|
|
386
|
+
cdir.writeUInt16LE(8, 10);
|
|
387
|
+
cdir.writeUInt32LE(crc, 16);
|
|
388
|
+
cdir.writeUInt32LE(compressed.length, 20);
|
|
389
|
+
cdir.writeUInt32LE(entry.data.length, 24);
|
|
390
|
+
cdir.writeUInt16LE(nameBytes.length, 28);
|
|
391
|
+
cdir.writeUInt32LE(offset, 42);
|
|
392
|
+
central.push(cdir, nameBytes);
|
|
393
|
+
offset += 30 + nameBytes.length + compressed.length;
|
|
394
|
+
}
|
|
395
|
+
const centralSize = central.reduce((s, b) => s + b.length, 0);
|
|
396
|
+
const eocd = Buffer.alloc(22);
|
|
397
|
+
eocd.writeUInt32LE(0x06054b50, 0);
|
|
398
|
+
eocd.writeUInt16LE(entries.length, 8);
|
|
399
|
+
eocd.writeUInt16LE(entries.length, 10);
|
|
400
|
+
eocd.writeUInt32LE(centralSize, 12);
|
|
401
|
+
eocd.writeUInt32LE(offset, 16);
|
|
402
|
+
parts.push(...central, eocd);
|
|
403
|
+
fs.writeFileSync(outFile, Buffer.concat(parts));
|
|
404
|
+
|
|
405
|
+
console.log(`\nBuilt: ${outFile} (${(fs.statSync(outFile).size / 1024).toFixed(1)} KB)`);
|
|
406
|
+
}
|
|
407
|
+
}
|
package/bin/ccweb.js
CHANGED
|
@@ -100,11 +100,24 @@ function askYN(question, defaultYes = true) {
|
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
function isWSL() {
|
|
104
|
+
try { return fs.readFileSync('/proc/version', 'utf-8').toLowerCase().includes('microsoft'); } catch { return false; }
|
|
105
|
+
}
|
|
106
|
+
|
|
103
107
|
function openBrowser(url) {
|
|
104
108
|
try {
|
|
105
|
-
if (process.platform === 'darwin')
|
|
106
|
-
|
|
107
|
-
else
|
|
109
|
+
if (process.platform === 'darwin') {
|
|
110
|
+
execSync(`open ${url}`, { stdio: 'ignore' });
|
|
111
|
+
} else if (process.platform === 'win32') {
|
|
112
|
+
execSync(`start "" "${url}"`, { shell: true, stdio: 'ignore' });
|
|
113
|
+
} else if (isWSL()) {
|
|
114
|
+
// WSL: try wslview (wslu package), then sensible-browser, then xdg-open
|
|
115
|
+
try { execSync(`wslview ${url}`, { stdio: 'ignore' }); }
|
|
116
|
+
catch { try { execSync(`sensible-browser ${url}`, { stdio: 'ignore' }); }
|
|
117
|
+
catch { execSync(`xdg-open ${url}`, { stdio: 'ignore' }); } }
|
|
118
|
+
} else {
|
|
119
|
+
execSync(`xdg-open ${url}`, { stdio: 'ignore' });
|
|
120
|
+
}
|
|
108
121
|
} catch { /* ignore — browser open is best-effort */ }
|
|
109
122
|
}
|
|
110
123
|
|
|
@@ -384,6 +397,7 @@ async function startServer(opts = {}) {
|
|
|
384
397
|
NODE_ENV: 'production',
|
|
385
398
|
},
|
|
386
399
|
cwd: PKG_ROOT,
|
|
400
|
+
detached: daemon, // daemon: new process group so it survives parent exit (critical for WSL)
|
|
387
401
|
stdio: daemon
|
|
388
402
|
? ['ignore', outFd, errFd, 'ipc']
|
|
389
403
|
: ['inherit', 'inherit', 'inherit', 'ipc'],
|
|
@@ -458,13 +472,33 @@ async function startServer(opts = {}) {
|
|
|
458
472
|
function stopServer() {
|
|
459
473
|
const status = getStatus();
|
|
460
474
|
if (!status.running) { console.log('CCWeb is not running.'); return; }
|
|
475
|
+
const pid = status.pid;
|
|
461
476
|
try {
|
|
462
|
-
process
|
|
477
|
+
// Try killing the process group first (detached daemon creates its own group)
|
|
478
|
+
// Negative PID = kill entire process group — ensures child processes (PTYs) are also terminated
|
|
479
|
+
try { process.kill(-pid, 'SIGTERM'); } catch { process.kill(pid, 'SIGTERM'); }
|
|
480
|
+
|
|
481
|
+
// Wait up to 5s for the process to actually exit
|
|
482
|
+
let gone = false;
|
|
483
|
+
for (let i = 0; i < 50; i++) {
|
|
484
|
+
try { process.kill(pid, 0); } catch { gone = true; break; }
|
|
485
|
+
// busy-wait 100ms (synchronous, acceptable for CLI)
|
|
486
|
+
const end = Date.now() + 100;
|
|
487
|
+
while (Date.now() < end) { /* spin */ }
|
|
488
|
+
}
|
|
489
|
+
if (!gone) {
|
|
490
|
+
// Force kill if still alive
|
|
491
|
+
try { process.kill(-pid, 'SIGKILL'); } catch { try { process.kill(pid, 'SIGKILL'); } catch {} }
|
|
492
|
+
}
|
|
493
|
+
|
|
463
494
|
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
464
495
|
try { fs.unlinkSync(PORT_FILE); } catch {}
|
|
465
|
-
console.log(`Stopped (PID ${
|
|
496
|
+
console.log(`Stopped (PID ${pid}).`);
|
|
466
497
|
} catch (err) {
|
|
467
498
|
console.error('Failed to stop:', err.message);
|
|
499
|
+
// Clean up stale PID file even on failure
|
|
500
|
+
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
501
|
+
try { fs.unlinkSync(PORT_FILE); } catch {}
|
|
468
502
|
}
|
|
469
503
|
}
|
|
470
504
|
|
|
@@ -489,17 +523,18 @@ function updatePackage() {
|
|
|
489
523
|
if (status.running) {
|
|
490
524
|
console.log('Stopping CCWeb (terminals will resume after update)...');
|
|
491
525
|
try {
|
|
492
|
-
process.kill(status.pid, 'SIGUSR2');
|
|
526
|
+
try { process.kill(-status.pid, 'SIGUSR2'); } catch { process.kill(status.pid, 'SIGUSR2'); }
|
|
493
527
|
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
494
528
|
try { fs.unlinkSync(PORT_FILE); } catch {}
|
|
495
529
|
console.log(`Stopped (PID ${status.pid}). Running Claude sessions will resume.`);
|
|
496
530
|
} catch (err) {
|
|
497
531
|
console.error('Failed to stop:', err.message);
|
|
498
532
|
}
|
|
499
|
-
//
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
533
|
+
// Wait for process to fully exit
|
|
534
|
+
for (let i = 0; i < 50; i++) {
|
|
535
|
+
if (!isProcessRunning(status.pid)) break;
|
|
536
|
+
const end = Date.now() + 100;
|
|
537
|
+
while (Date.now() < end) { /* spin */ }
|
|
503
538
|
}
|
|
504
539
|
}
|
|
505
540
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{c as je,r as C,d as De,j as y,B as q,m as he}from"./index-
|
|
1
|
+
import{c as je,r as C,d as De,j as y,B as q,m as he}from"./index-D7mCoW2r.js";import{L as Ye,Z as Pe,a as Be}from"./ProjectPage-BmYhTTYq.js";import"./save-CdDjQhdC.js";import"./download-UC5GvInE.js";import"./chevron-down-BR0IMbA9.js";import"./bot-hRRV5XmX.js";import"./user-Bv38vpTL.js";/**
|
|
2
2
|
* @license lucide-react v0.309.0 - ISC
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the ISC license.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/index-
|
|
2
|
-
import{j as e,r as c,_ as m,b as y,h as S}from"./index-
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/index-BiYAwwZA.js","assets/index-D7mCoW2r.js","assets/index-DgHvs3fU.css","assets/jszip.min-YWYO5s4l.js"])))=>i.map(i=>d[i]);
|
|
2
|
+
import{j as e,r as c,_ as m,b as y,h as S}from"./index-D7mCoW2r.js";async function h(a){let l=S(a);const s=y();s&&(l+=`${l.includes("?")?"&":"?"}token=${encodeURIComponent(s)}`);const d=await fetch(l);if(!d.ok)throw new Error(`Failed to fetch file: ${d.status}`);return d.arrayBuffer()}function j({filePath:a,zoom:l}){const[s,d]=c.useState(""),[x,n]=c.useState(null),[u,i]=c.useState(!0);return c.useEffect(()=>{let t=!1;return i(!0),n(null),(async()=>{try{const r=await m(()=>import("./index-BiYAwwZA.js").then(f=>f.i),__vite__mapDeps([0,1,2,3])),o=await h(a),p=await r.convertToHtml({arrayBuffer:o});t||d(p.value)}catch(r){t||n(r instanceof Error?r.message:"Failed to render docx")}finally{t||i(!1)}})(),()=>{t=!0}},[a]),u?e.jsx("p",{className:"text-sm text-muted-foreground p-4",children:"加载 Word 文档中..."}):x?e.jsx("p",{className:"text-sm text-destructive p-4",children:x}):e.jsx("div",{className:"p-6 prose dark:prose-invert max-w-none",style:{fontSize:`${12*l/100}px`},dangerouslySetInnerHTML:{__html:s}})}function _({filePath:a,zoom:l}){var o;const[s,d]=c.useState([]),[x,n]=c.useState(0),[u,i]=c.useState(null),[t,r]=c.useState(!0);return c.useEffect(()=>{let p=!1;return r(!0),i(null),(async()=>{try{const f=await m(()=>import("./xlsx-D_0l8YDs.js"),[]),v=await h(a),g=f.read(v,{type:"array"}),b=g.SheetNames.map(w=>({name:w,html:f.utils.sheet_to_html(g.Sheets[w],{editable:!1})}));p||(d(b),n(0))}catch(f){p||i(f instanceof Error?f.message:"Failed to render xlsx")}finally{p||r(!1)}})(),()=>{p=!0}},[a]),t?e.jsx("p",{className:"text-sm text-muted-foreground p-4",children:"加载 Excel 文件中..."}):u?e.jsx("p",{className:"text-sm text-destructive p-4",children:u}):s.length===0?e.jsx("p",{className:"text-sm text-muted-foreground p-4",children:"空文件"}):e.jsxs("div",{className:"flex flex-col h-full",children:[s.length>1&&e.jsx("div",{className:"flex gap-0.5 px-3 py-1.5 border-b border-border bg-muted/30 flex-shrink-0 overflow-x-auto",children:s.map((p,f)=>e.jsx("button",{onClick:()=>n(f),className:`px-3 py-1 text-xs rounded-t transition-colors whitespace-nowrap ${f===x?"bg-background text-foreground border border-b-0 border-border":"text-muted-foreground hover:text-foreground"}`,children:p.name},f))}),e.jsx("div",{className:"flex-1 overflow-auto p-2 xlsx-preview",style:{fontSize:`${12*l/100}px`},dangerouslySetInnerHTML:{__html:((o=s[x])==null?void 0:o.html)??""}}),e.jsx("style",{children:`
|
|
3
3
|
.xlsx-preview table { border-collapse: collapse; width: auto; min-width: 100%; }
|
|
4
4
|
.xlsx-preview td, .xlsx-preview th {
|
|
5
5
|
border: 1px solid hsl(var(--border));
|
|
@@ -10,4 +10,4 @@ import{j as e,r as c,_ as m,b as y,h as S}from"./index-BKftRi-t.js";async functi
|
|
|
10
10
|
}
|
|
11
11
|
.xlsx-preview th { background: hsl(var(--muted)); font-weight: 600; }
|
|
12
12
|
.xlsx-preview tr:hover td { background: hsl(var(--accent) / 0.3); }
|
|
13
|
-
`})]})}async function E(a){const{default:l}=await m(async()=>{const{default:n}=await import("./jszip.min-
|
|
13
|
+
`})]})}async function E(a){const{default:l}=await m(async()=>{const{default:n}=await import("./jszip.min-YWYO5s4l.js").then(u=>u.j);return{default:n}},__vite__mapDeps([3,1,2])),s=await l.loadAsync(a),d=[],x=Object.keys(s.files).filter(n=>/^ppt\/slides\/slide\d+\.xml$/i.test(n)).sort((n,u)=>{var r,o;const i=parseInt(((r=n.match(/slide(\d+)/))==null?void 0:r[1])??"0"),t=parseInt(((o=u.match(/slide(\d+)/))==null?void 0:o[1])??"0");return i-t});for(let n=0;n<x.length;n++){const u=await s.file(x[n]).async("text"),i=[],t=/<a:t[^>]*>([\s\S]*?)<\/a:t>/g;let r;for(;(r=t.exec(u))!==null;){const o=r[1].replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').trim();o&&i.push(o)}d.push({index:n+1,texts:i})}return d}function N({filePath:a,zoom:l}){const[s,d]=c.useState([]),[x,n]=c.useState(null),[u,i]=c.useState(!0);return c.useEffect(()=>{let t=!1;return i(!0),n(null),(async()=>{try{const r=await h(a),o=await E(r);t||d(o)}catch(r){t||n(r instanceof Error?r.message:"Failed to render pptx")}finally{t||i(!1)}})(),()=>{t=!0}},[a]),u?e.jsx("p",{className:"text-sm text-muted-foreground p-4",children:"加载 PPT 文件中..."}):x?e.jsx("p",{className:"text-sm text-destructive p-4",children:x}):s.length===0?e.jsx("p",{className:"text-sm text-muted-foreground p-4",children:"空文件"}):e.jsx("div",{className:"p-4 space-y-4",style:{fontSize:`${12*l/100}px`},children:s.map(t=>e.jsxs("div",{className:"border border-border rounded-lg p-5 bg-muted/20",children:[e.jsxs("div",{className:"text-xs text-muted-foreground mb-2 font-medium",children:["Slide ",t.index]}),t.texts.length>0?e.jsx("div",{className:"space-y-1.5",children:t.texts.map((r,o)=>e.jsx("p",{className:"text-foreground leading-relaxed",style:{fontSize:"inherit"},children:r},o))}):e.jsx("p",{className:"text-muted-foreground text-sm italic",children:"(无文本内容)"})]},t.index))})}const I=new Set(["docx","xlsx","xls","pptx"]);function L({filePath:a,ext:l,zoom:s}){return l==="docx"?e.jsx(j,{filePath:a,zoom:s}):l==="xlsx"||l==="xls"?e.jsx(_,{filePath:a,zoom:s}):l==="pptx"?e.jsx(N,{filePath:a,zoom:s}):null}export{I as OFFICE_EXTS,L as OfficePreview};
|