@zhongqian97-code/ecode 0.5.17 → 0.5.19

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 (2) hide show
  1. package/dist/index.js +142 -7
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5234,15 +5234,145 @@ function generateAccessToken() {
5234
5234
  }
5235
5235
  function createAuthHook(token) {
5236
5236
  return function authHook(request, reply, done) {
5237
- const incoming = request.headers["x-ecode-token"];
5238
- if (incoming !== token) {
5239
- reply.code(401).send({ success: false, error: "Unauthorized" });
5237
+ const headerToken = request.headers["x-ecode-token"];
5238
+ const queryToken = request.query?.["token"];
5239
+ if (headerToken === token || queryToken === token) {
5240
+ done();
5240
5241
  return;
5241
5242
  }
5242
- done();
5243
+ reply.code(401).send({ success: false, error: "Unauthorized" });
5243
5244
  };
5244
5245
  }
5245
5246
 
5247
+ // src/web/admin-html.ts
5248
+ function generateAdminHtml(version2) {
5249
+ return `<!DOCTYPE html>
5250
+ <html lang="zh">
5251
+ <head>
5252
+ <meta charset="UTF-8">
5253
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
5254
+ <title>ecode web admin</title>
5255
+ <style>
5256
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
5257
+ body {
5258
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace;
5259
+ background: #0d1117; color: #c9d1d9; min-height: 100vh; padding: 24px;
5260
+ }
5261
+ header {
5262
+ display: flex; align-items: center; gap: 12px;
5263
+ padding-bottom: 16px; border-bottom: 1px solid #21262d; margin-bottom: 24px;
5264
+ }
5265
+ header h1 { font-size: 18px; font-weight: 600; color: #e6edf3; }
5266
+ header .version { font-size: 12px; color: #8b949e; background: #21262d;
5267
+ padding: 2px 8px; border-radius: 20px; }
5268
+ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; }
5269
+ .card {
5270
+ background: #161b22; border: 1px solid #21262d; border-radius: 8px; padding: 16px;
5271
+ }
5272
+ .card h2 { font-size: 13px; font-weight: 600; color: #8b949e;
5273
+ text-transform: uppercase; letter-spacing: .05em; margin-bottom: 12px; }
5274
+ .stat { display: flex; justify-content: space-between; align-items: center;
5275
+ padding: 6px 0; border-bottom: 1px solid #21262d; font-size: 13px; }
5276
+ .stat:last-child { border-bottom: none; }
5277
+ .stat .label { color: #8b949e; }
5278
+ .stat .value { color: #e6edf3; font-weight: 500; }
5279
+ .badge { display: inline-block; padding: 2px 8px; border-radius: 20px;
5280
+ font-size: 11px; font-weight: 600; }
5281
+ .badge.ok { background: #1a4731; color: #3fb950; }
5282
+ .badge.err { background: #4d1f24; color: #f85149; }
5283
+ .session-item {
5284
+ padding: 8px 0; border-bottom: 1px solid #21262d; font-size: 13px;
5285
+ }
5286
+ .session-item:last-child { border-bottom: none; }
5287
+ .session-id { color: #79c0ff; font-family: monospace; font-size: 12px; }
5288
+ .session-title { color: #e6edf3; margin-top: 2px; }
5289
+ .empty { color: #8b949e; font-size: 13px; font-style: italic; }
5290
+ .error { color: #f85149; font-size: 13px; }
5291
+ .loading { color: #8b949e; font-size: 13px; font-style: italic; }
5292
+ .api-row { font-size: 12px; font-family: monospace; padding: 4px 0;
5293
+ border-bottom: 1px solid #21262d; display: flex; gap: 8px; }
5294
+ .api-row:last-child { border-bottom: none; }
5295
+ .method { color: #79c0ff; min-width: 50px; }
5296
+ .path { color: #e6edf3; }
5297
+ </style>
5298
+ </head>
5299
+ <body>
5300
+ <header>
5301
+ <h1>\u26A1 ecode web admin</h1>
5302
+ <span class="version">v${version2}</span>
5303
+ </header>
5304
+
5305
+ <div class="grid">
5306
+ <div class="card">
5307
+ <h2>\u670D\u52A1\u72B6\u6001</h2>
5308
+ <div id="status-content"><span class="loading">\u52A0\u8F7D\u4E2D\u2026</span></div>
5309
+ </div>
5310
+
5311
+ <div class="card">
5312
+ <h2>\u8FD0\u884C\u4E2D\u7684\u4F1A\u8BDD</h2>
5313
+ <div id="sessions-content"><span class="loading">\u52A0\u8F7D\u4E2D\u2026</span></div>
5314
+ </div>
5315
+
5316
+ <div class="card">
5317
+ <h2>API \u7AEF\u70B9</h2>
5318
+ <div class="api-row"><span class="method">GET</span><span class="path">/api/status</span></div>
5319
+ <div class="api-row"><span class="method">GET</span><span class="path">/api/sessions</span></div>
5320
+ <div class="api-row"><span class="method">GET</span><span class="path">/api/config</span></div>
5321
+ <div class="api-row"><span class="method">GET</span><span class="path">/api/automation/jobs</span></div>
5322
+ <div class="api-row"><span class="method">POST</span><span class="path">/api/chat/sessions</span></div>
5323
+ <div class="api-row"><span class="method">WS</span><span class="path">/api/ws/sessions/:id</span></div>
5324
+ </div>
5325
+ </div>
5326
+
5327
+ <script>
5328
+ const token = new URLSearchParams(location.search).get('token') || '';
5329
+
5330
+ async function apiFetch(path) {
5331
+ const sep = path.includes('?') ? '&' : '?';
5332
+ const r = await fetch(path + sep + 'token=' + encodeURIComponent(token));
5333
+ if (!r.ok) throw new Error(r.status + ' ' + r.statusText);
5334
+ return r.json();
5335
+ }
5336
+
5337
+ function setHtml(id, html) {
5338
+ document.getElementById(id).innerHTML = html;
5339
+ }
5340
+
5341
+ // \u72B6\u6001\u5361
5342
+ apiFetch('/api/status').then(d => {
5343
+ const rows = [
5344
+ ['\u72B6\u6001', '<span class="badge ok">\u8FD0\u884C\u4E2D</span>'],
5345
+ ['\u7248\u672C', d.version ?? '-'],
5346
+ ['\u6D3B\u8DC3\u4F1A\u8BDD', d.activeSessions ?? 0],
5347
+ ];
5348
+ setHtml('status-content',
5349
+ rows.map(([l,v]) =>
5350
+ '<div class="stat"><span class="label">' + l + '</span><span class="value">' + v + '</span></div>'
5351
+ ).join('')
5352
+ );
5353
+ }).catch(e => setHtml('status-content', '<span class="error">' + e.message + '</span>'));
5354
+
5355
+ // \u4F1A\u8BDD\u5217\u8868
5356
+ apiFetch('/api/sessions').then(d => {
5357
+ const sessions = d.sessions ?? d.data ?? d ?? [];
5358
+ if (!sessions.length) {
5359
+ setHtml('sessions-content', '<span class="empty">\u6682\u65E0\u4F1A\u8BDD</span>');
5360
+ return;
5361
+ }
5362
+ setHtml('sessions-content',
5363
+ sessions.slice(0, 10).map(s =>
5364
+ '<div class="session-item">' +
5365
+ '<div class="session-id">' + s.id + '</div>' +
5366
+ '<div class="session-title">' + (s.title || '(\u65E0\u6807\u9898)') + '</div>' +
5367
+ '</div>'
5368
+ ).join('')
5369
+ );
5370
+ }).catch(e => setHtml('sessions-content', '<span class="error">' + e.message + '</span>'));
5371
+ </script>
5372
+ </body>
5373
+ </html>`;
5374
+ }
5375
+
5246
5376
  // src/web/routes/status.ts
5247
5377
  async function statusRoutes(app, opts) {
5248
5378
  app.get(
@@ -5550,6 +5680,10 @@ async function buildServer(opts) {
5550
5680
  authHook(request, reply, done);
5551
5681
  });
5552
5682
  app.get("/health", async () => ({ ok: true }));
5683
+ const adminHtml = generateAdminHtml(opts.version);
5684
+ app.get("/", async (_req, reply) => {
5685
+ return reply.type("text/html").send(adminHtml);
5686
+ });
5553
5687
  await app.register(statusRoutes, {
5554
5688
  config: opts.config,
5555
5689
  manager: opts.manager,
@@ -6101,11 +6235,12 @@ if (rawArgs[0] === "web") {
6101
6235
  version: VERSION
6102
6236
  });
6103
6237
  await server.listen({ port: webPort, host: webHost });
6104
- const displayHost = webHost === "0.0.0.0" ? "localhost" : webHost;
6238
+ const browserHost = webHost === "0.0.0.0" ? "localhost" : webHost;
6239
+ const accessUrl = `http://${browserHost}:${webPort}?token=${token}`;
6105
6240
  console.log(`
6106
6241
  ecode web admin started`);
6107
- console.log(` URL: http://${displayHost}:${webPort}`);
6108
- console.log(` Token: ${token}`);
6242
+ console.log(` Bind: ${webHost}:${webPort}`);
6243
+ console.log(` URL: ${accessUrl}`);
6109
6244
  console.log(`
6110
6245
  Press Ctrl+C to stop.
6111
6246
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhongqian97-code/ecode",
3
- "version": "0.5.17",
3
+ "version": "0.5.19",
4
4
  "description": "A minimal Claude Code clone with REPL interface and bash tool calling",
5
5
  "type": "module",
6
6
  "author": "zhongqian97-code",