@zhongqian97-code/ecode 0.5.20 → 0.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.
Files changed (2) hide show
  1. package/dist/index.js +172 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5552,6 +5552,21 @@ function generateAdminHtml(version2) {
5552
5552
  }
5553
5553
  #approve-btn:hover { background: #2ea043; color: #fff; }
5554
5554
 
5555
+ /* \u2500\u2500 Config & upgrade modals \u2500\u2500 */
5556
+ .modal-overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,.7); z-index:100; align-items:center; justify-content:center; }
5557
+ .modal-overlay.open { display:flex; }
5558
+ .modal { background:#161b22; border:1px solid #30363d; border-radius:8px; padding:24px; width:min(480px,90vw); max-height:80vh; overflow-y:auto; }
5559
+ .modal h3 { color:#e6edf3; margin-bottom:16px; font-size:14px; }
5560
+ .form-row { margin-bottom:12px; }
5561
+ .form-row label { display:block; font-size:12px; color:#8b949e; margin-bottom:4px; }
5562
+ .form-row input, .form-row textarea { width:100%; background:#0d1117; border:1px solid #30363d; border-radius:4px; color:#c9d1d9; font-family:monospace; font-size:13px; padding:6px 8px; }
5563
+ .form-row textarea { resize:vertical; min-height:60px; }
5564
+ .btn { padding:6px 14px; border-radius:4px; border:1px solid #30363d; cursor:pointer; font-family:monospace; font-size:13px; }
5565
+ .btn-primary { background:#1f6feb; color:#fff; border-color:#1f6feb; }
5566
+ .btn-danger { background:#da3633; color:#fff; border-color:#da3633; }
5567
+ .modal-footer { display:flex; gap:8px; justify-content:flex-end; margin-top:16px; }
5568
+ #upgrade-output { display:none; background:#0d1117; border:1px solid #30363d; border-radius:4px; padding:8px; font-family:monospace; font-size:12px; color:#c9d1d9; white-space:pre-wrap; max-height:200px; overflow-y:auto; margin-top:8px; }
5569
+
5555
5570
  /* \u2500\u2500 Mobile \u2500\u2500 */
5556
5571
  @media (max-width: 600px) {
5557
5572
  #hamburger { display: block; }
@@ -5571,6 +5586,8 @@ function generateAdminHtml(version2) {
5571
5586
  <button id="hamburger" aria-label="Toggle sidebar">\u2630</button>
5572
5587
  <h1>\u26A1 ecode web admin</h1>
5573
5588
  <span class="version">v${version2}</span>
5589
+ <button id="config-btn" class="btn">\u914D\u7F6E</button>
5590
+ <button id="upgrade-btn" class="btn">\u5347\u7EA7</button>
5574
5591
  </div>
5575
5592
 
5576
5593
  <div id="app">
@@ -5604,6 +5621,35 @@ function generateAdminHtml(version2) {
5604
5621
  </div>
5605
5622
  </div>
5606
5623
 
5624
+ <!-- Config modal -->
5625
+ <div id="config-modal" class="modal-overlay">
5626
+ <div class="modal">
5627
+ <h3>\u2699 \u914D\u7F6E</h3>
5628
+ <div class="form-row"><label>Model</label><input id="cfg-model" name="model" type="text" /></div>
5629
+ <div class="form-row"><label>Base URL</label><input id="cfg-baseurl" name="baseUrl" type="text" /></div>
5630
+ <div class="form-row"><label>API Key</label><input id="cfg-apikey" name="apiKey" type="password" placeholder="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" /></div>
5631
+ <div class="form-row"><label>Log Dir</label><input id="cfg-logdir" name="logDir" type="text" /></div>
5632
+ <div class="form-row"><label>System Prompt</label><textarea id="cfg-systemprompt"></textarea></div>
5633
+ <div class="modal-footer">
5634
+ <button id="cancel-config" class="btn">\u53D6\u6D88</button>
5635
+ <button id="save-config" class="btn btn-primary">\u4FDD\u5B58</button>
5636
+ </div>
5637
+ </div>
5638
+ </div>
5639
+
5640
+ <!-- Upgrade modal -->
5641
+ <div id="upgrade-modal" class="modal-overlay">
5642
+ <div class="modal">
5643
+ <h3>\u2B06 \u5347\u7EA7 ecode</h3>
5644
+ <div id="upgrade-status">\u6B63\u5728\u68C0\u67E5\u7248\u672C\u2026</div>
5645
+ <div id="upgrade-output"></div>
5646
+ <div class="modal-footer">
5647
+ <button id="cancel-upgrade" class="btn">\u5173\u95ED</button>
5648
+ <button id="confirm-upgrade" class="btn btn-primary" disabled>\u5347\u7EA7</button>
5649
+ </div>
5650
+ </div>
5651
+ </div>
5652
+
5607
5653
  <!-- Bash approval modal -->
5608
5654
  <div id="approval-modal" role="dialog" aria-modal="true">
5609
5655
  <div id="approval-box">
@@ -5636,7 +5682,7 @@ function generateAdminHtml(version2) {
5636
5682
  return path + sep + 'token=' + encodeURIComponent(state.token);
5637
5683
  }
5638
5684
 
5639
- async function apiFetch(path, opts) {
5685
+ async function apiFetch(path, opts = {}) {
5640
5686
  const url = apiUrl(path);
5641
5687
  const res = await fetch(url, opts);
5642
5688
  if (!res.ok) throw new Error(res.status + ' ' + res.statusText);
@@ -5979,6 +6025,94 @@ function generateAdminHtml(version2) {
5979
6025
  document.getElementById('sidebar').classList.toggle('open');
5980
6026
  });
5981
6027
 
6028
+ // \u2500\u2500 \u914D\u7F6E\u6A21\u6001\u6846 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
6029
+ document.getElementById('config-btn').addEventListener('click', async () => {
6030
+ try {
6031
+ const data = await apiFetch('/api/config');
6032
+ document.getElementById('cfg-model').value = data.model || '';
6033
+ document.getElementById('cfg-baseurl').value = data.baseUrl || '';
6034
+ document.getElementById('cfg-logdir').value = data.logDir || '';
6035
+ document.getElementById('cfg-systemprompt').value = data.systemPrompt || '';
6036
+ } catch (e) {
6037
+ // open modal even if fetch fails; fields will be empty
6038
+ }
6039
+ document.getElementById('config-modal').classList.add('open');
6040
+ });
6041
+
6042
+ document.getElementById('cancel-config').addEventListener('click', () => {
6043
+ document.getElementById('config-modal').classList.remove('open');
6044
+ });
6045
+
6046
+ document.getElementById('save-config').addEventListener('click', async () => {
6047
+ const body = {
6048
+ model: document.getElementById('cfg-model').value,
6049
+ baseUrl: document.getElementById('cfg-baseurl').value,
6050
+ logDir: document.getElementById('cfg-logdir').value,
6051
+ systemPrompt: document.getElementById('cfg-systemprompt').value,
6052
+ };
6053
+ const apiKeyVal = document.getElementById('cfg-apikey').value;
6054
+ if (apiKeyVal) body.apiKey = apiKeyVal;
6055
+ try {
6056
+ await apiFetch('/api/config', { method: 'PUT', headers: {'Content-Type':'application/json'}, body: JSON.stringify(body) });
6057
+ } catch (e) {
6058
+ // ignore errors silently for now
6059
+ }
6060
+ document.getElementById('config-modal').classList.remove('open');
6061
+ });
6062
+
6063
+ // \u2500\u2500 \u5347\u7EA7 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
6064
+ document.getElementById('upgrade-btn').addEventListener('click', async () => {
6065
+ const modal = document.getElementById('upgrade-modal');
6066
+ const statusEl = document.getElementById('upgrade-status');
6067
+ const outputEl = document.getElementById('upgrade-output');
6068
+ const confirmBtn = document.getElementById('confirm-upgrade');
6069
+ outputEl.style.display = 'none';
6070
+ outputEl.textContent = '';
6071
+ confirmBtn.disabled = true;
6072
+ statusEl.textContent = '\u6B63\u5728\u68C0\u67E5\u7248\u672C\u2026';
6073
+ modal.classList.add('open');
6074
+ try {
6075
+ const r = await fetch(apiUrl('/api/version/check'));
6076
+ const v = await r.json();
6077
+ if (v.needsUpdate) {
6078
+ statusEl.textContent = '\u5F53\u524D v' + v.current + '\uFF0C\u6700\u65B0 v' + v.latest + '\uFF0C\u786E\u8BA4\u5347\u7EA7\uFF1F';
6079
+ confirmBtn.disabled = false;
6080
+ } else {
6081
+ statusEl.textContent = '\u5DF2\u662F\u6700\u65B0\u7248\u672C v' + v.current;
6082
+ }
6083
+ } catch(e) {
6084
+ statusEl.textContent = '\u7248\u672C\u68C0\u67E5\u5931\u8D25\uFF1A' + e.message;
6085
+ }
6086
+ });
6087
+
6088
+ document.getElementById('cancel-upgrade').addEventListener('click', () => {
6089
+ document.getElementById('upgrade-modal').classList.remove('open');
6090
+ });
6091
+
6092
+ document.getElementById('confirm-upgrade').addEventListener('click', async () => {
6093
+ const outputEl = document.getElementById('upgrade-output');
6094
+ const confirmBtn = document.getElementById('confirm-upgrade');
6095
+ const statusEl = document.getElementById('upgrade-status');
6096
+ confirmBtn.disabled = true;
6097
+ outputEl.style.display = 'block';
6098
+ outputEl.textContent = '';
6099
+ statusEl.textContent = '\u5347\u7EA7\u4E2D\u2026';
6100
+ try {
6101
+ const r = await fetch(apiUrl('/api/system/upgrade'), { method: 'POST' });
6102
+ const reader = r.body.getReader();
6103
+ const decoder = new TextDecoder();
6104
+ while (true) {
6105
+ const { done, value } = await reader.read();
6106
+ if (done) break;
6107
+ outputEl.textContent += decoder.decode(value);
6108
+ outputEl.scrollTop = outputEl.scrollHeight;
6109
+ }
6110
+ statusEl.textContent = '\u5347\u7EA7\u5B8C\u6210\uFF0C\u8BF7\u91CD\u542F ecode web';
6111
+ } catch(e) {
6112
+ statusEl.textContent = '\u5347\u7EA7\u5931\u8D25\uFF1A' + e.message;
6113
+ }
6114
+ });
6115
+
5982
6116
  // \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5983
6117
  loadSessions();
5984
6118
  </script>
@@ -6279,6 +6413,42 @@ async function sessionHubRoutes(app, opts) {
6279
6413
  );
6280
6414
  }
6281
6415
 
6416
+ // src/web/routes/system.ts
6417
+ import { execSync } from "child_process";
6418
+ import { spawn } from "child_process";
6419
+ async function systemRoutes(app, opts) {
6420
+ app.get("/api/version/check", async (_request, _reply) => {
6421
+ const current = opts.version;
6422
+ let latest;
6423
+ try {
6424
+ latest = execSync("npm view @zhongqian97-code/ecode version", {
6425
+ encoding: "utf-8",
6426
+ timeout: 5e3
6427
+ }).trim();
6428
+ } catch {
6429
+ latest = "unknown";
6430
+ }
6431
+ const needsUpdate = latest !== "unknown" && latest !== current;
6432
+ return { current, latest, needsUpdate };
6433
+ });
6434
+ app.post("/api/system/upgrade", (_request, reply) => {
6435
+ reply.raw.setHeader("Content-Type", "text/plain");
6436
+ reply.raw.write("Upgrading @zhongqian97-code/ecode...\n");
6437
+ const child = spawn("npm", ["update", "-g", "@zhongqian97-code/ecode"]);
6438
+ child.stdout.on("data", (chunk) => {
6439
+ reply.raw.write(chunk);
6440
+ });
6441
+ child.stderr.on("data", (chunk) => {
6442
+ reply.raw.write(chunk);
6443
+ });
6444
+ child.on("close", () => {
6445
+ reply.raw.write("Upgrade complete. Please restart ecode web.\n");
6446
+ reply.raw.end();
6447
+ });
6448
+ return reply;
6449
+ });
6450
+ }
6451
+
6282
6452
  // src/web/server.ts
6283
6453
  async function buildServer(opts) {
6284
6454
  const app = Fastify({ logger: false });
@@ -6306,6 +6476,7 @@ async function buildServer(opts) {
6306
6476
  await app.register(configRoutes, { config: opts.config });
6307
6477
  await app.register(automationRoutes, { config: opts.config });
6308
6478
  await app.register(chatRoutes, { config: opts.config, manager: opts.manager });
6479
+ await app.register(systemRoutes, { version: opts.version });
6309
6480
  await app.register(sessionHubRoutes, { manager: opts.manager });
6310
6481
  return app;
6311
6482
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhongqian97-code/ecode",
3
- "version": "0.5.20",
3
+ "version": "0.5.21",
4
4
  "description": "A minimal Claude Code clone with REPL interface and bash tool calling",
5
5
  "type": "module",
6
6
  "author": "zhongqian97-code",