@zhongqian97-code/ecode 0.5.30 → 0.5.32

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 +287 -18
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1380,10 +1380,10 @@ function metadataPathFromLogFile(logFilePath2) {
1380
1380
  const dir = path6.dirname(logFilePath2);
1381
1381
  return path6.join(dir, `${base}-session.json`);
1382
1382
  }
1383
- function createSessionMetadata(logFilePath2, model) {
1383
+ function createSessionMetadata(logFilePath2, model, id) {
1384
1384
  const now = (/* @__PURE__ */ new Date()).toISOString();
1385
1385
  return {
1386
- id: crypto2.randomUUID(),
1386
+ id: id ?? crypto2.randomUUID(),
1387
1387
  startTime: now,
1388
1388
  lastActivity: now,
1389
1389
  cwd: process.cwd(),
@@ -5700,6 +5700,21 @@ function generateAdminHtml(version2) {
5700
5700
  to { opacity: 1; transform: translateX(0); }
5701
5701
  }
5702
5702
 
5703
+ /* \u2500\u2500 Skill dropdown \u2500\u2500 */
5704
+ #skill-dropdown .skill-item {
5705
+ padding: 8px 12px;
5706
+ cursor: pointer;
5707
+ border-bottom: 1px solid #30363d;
5708
+ display: flex;
5709
+ gap: 8px;
5710
+ align-items: baseline;
5711
+ }
5712
+ #skill-dropdown .skill-item:last-child { border-bottom: none; }
5713
+ #skill-dropdown .skill-item:hover,
5714
+ #skill-dropdown .skill-item.selected { background: #2d333b; }
5715
+ #skill-dropdown .skill-name { color: #79c0ff; font-weight: 600; font-size: 0.9em; }
5716
+ #skill-dropdown .skill-desc { color: #8b949e; font-size: 0.8em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 300px; }
5717
+
5703
5718
  /* \u2500\u2500 Mobile \u2500\u2500 */
5704
5719
  @media (max-width: 600px) {
5705
5720
  #hamburger { display: block; }
@@ -5711,6 +5726,22 @@ function generateAdminHtml(version2) {
5711
5726
  transition: transform .2s ease;
5712
5727
  }
5713
5728
  #sidebar.open { transform: translateX(0); }
5729
+
5730
+ /* Fix input bar on mobile */
5731
+ #input-bar {
5732
+ position: fixed;
5733
+ bottom: 0;
5734
+ left: 0;
5735
+ right: 0;
5736
+ bottom: env(safe-area-inset-bottom, 0px);
5737
+ z-index: 40;
5738
+ padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
5739
+ }
5740
+
5741
+ /* Add bottom padding to chat so messages don't hide behind fixed input */
5742
+ #messages {
5743
+ padding-bottom: 80px;
5744
+ }
5714
5745
  }
5715
5746
  </style>
5716
5747
  </head>
@@ -5749,6 +5780,7 @@ function generateAdminHtml(version2) {
5749
5780
  <div class="empty-chat">\u2190 \u4ECE\u5DE6\u4FA7\u9009\u62E9\u4F1A\u8BDD\uFF0C\u6216\u70B9\u51FB"\u65B0\u5EFA\u4F1A\u8BDD"\u5F00\u59CB</div>
5750
5781
  </div>
5751
5782
  <div id="ws-status" class="ws-status"></div>
5783
+ <div id="skill-dropdown" style="display:none;position:fixed;bottom:60px;left:14px;right:14px;max-height:200px;overflow-y:auto;background:#1c2128;border:1px solid #30363d;border-radius:6px;z-index:60;"></div>
5752
5784
  <div id="input-bar">
5753
5785
  <textarea id="msg-input" rows="1" placeholder="\u8F93\u5165\u6D88\u606F\uFF0C\u6309 Enter \u53D1\u9001\uFF08Shift+Enter \u6362\u884C\uFF09\u2026" disabled></textarea>
5754
5786
  <button id="send-btn" disabled>\u53D1\u9001</button>
@@ -5813,6 +5845,8 @@ function generateAdminHtml(version2) {
5813
5845
  wsRetries: 0,
5814
5846
  streamingMsgEl: null, // DOM element currently receiving delta tokens
5815
5847
  showTools: true,
5848
+ skills: [], // cached skills from /api/skills
5849
+ skillsLoaded: false,
5816
5850
  };
5817
5851
 
5818
5852
  // \u2500\u2500 Toast \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
@@ -6161,6 +6195,70 @@ function generateAdminHtml(version2) {
6161
6195
  .replace(/>/g, '&gt;');
6162
6196
  }
6163
6197
 
6198
+ // \u2500\u2500 Skill dropdown \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
6199
+ var selectedSkillIndex = -1;
6200
+
6201
+ async function loadSkills() {
6202
+ if (state.skillsLoaded) return;
6203
+ try {
6204
+ const data = await apiFetch('/api/skills');
6205
+ state.skills = Array.isArray(data) ? data : [];
6206
+ state.skillsLoaded = true;
6207
+ } catch (e) {
6208
+ state.skills = [];
6209
+ }
6210
+ }
6211
+
6212
+ function showSkillDropdown(query) {
6213
+ const dropdown = document.getElementById('skill-dropdown');
6214
+ if (!dropdown) return;
6215
+ const q = query.toLowerCase();
6216
+ const filtered = state.skills.filter(function(s) {
6217
+ return s.name.toLowerCase().startsWith(q);
6218
+ });
6219
+ if (!filtered.length) {
6220
+ dropdown.style.display = 'none';
6221
+ selectedSkillIndex = -1;
6222
+ return;
6223
+ }
6224
+ selectedSkillIndex = -1;
6225
+ dropdown.innerHTML = filtered.map(function(s, i) {
6226
+ return '<div class="skill-item" data-name="' + escHtml(s.name) + '">' +
6227
+ '<span class="skill-name">/' + escHtml(s.name) + '</span>' +
6228
+ '<span class="skill-desc">' + escHtml(s.description || '') + '</span>' +
6229
+ '</div>';
6230
+ }).join('');
6231
+ dropdown.querySelectorAll('.skill-item').forEach(function(el) {
6232
+ el.addEventListener('click', function() {
6233
+ var name = el.getAttribute('data-name');
6234
+ var inputEl = document.getElementById('msg-input');
6235
+ inputEl.value = '/' + name + ' ';
6236
+ hideSkillDropdown();
6237
+ inputEl.focus();
6238
+ });
6239
+ });
6240
+ dropdown.style.display = 'block';
6241
+ }
6242
+
6243
+ function hideSkillDropdown() {
6244
+ const dropdown = document.getElementById('skill-dropdown');
6245
+ if (dropdown) dropdown.style.display = 'none';
6246
+ selectedSkillIndex = -1;
6247
+ }
6248
+
6249
+ function updateSkillSelection() {
6250
+ const dropdown = document.getElementById('skill-dropdown');
6251
+ if (!dropdown) return;
6252
+ const items = dropdown.querySelectorAll('.skill-item');
6253
+ items.forEach(function(el, i) {
6254
+ if (i === selectedSkillIndex) {
6255
+ el.classList.add('selected');
6256
+ } else {
6257
+ el.classList.remove('selected');
6258
+ }
6259
+ });
6260
+ }
6261
+
6164
6262
  // \u2500\u2500 Approval modal \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
6165
6263
  function showApprovalModal(msg) {
6166
6264
  const box = document.getElementById('approval-box');
@@ -6260,17 +6358,57 @@ function generateAdminHtml(version2) {
6260
6358
 
6261
6359
  document.getElementById('send-btn').addEventListener('click', sendMessage);
6262
6360
 
6263
- document.getElementById('msg-input').addEventListener('keydown', (e) => {
6361
+ document.getElementById('msg-input').addEventListener('keydown', function(e) {
6362
+ const dropdown = document.getElementById('skill-dropdown');
6363
+ const dropdownVisible = dropdown && dropdown.style.display !== 'none';
6364
+ if (dropdownVisible) {
6365
+ const items = dropdown.querySelectorAll('.skill-item');
6366
+ if (e.key === 'ArrowDown') {
6367
+ e.preventDefault();
6368
+ selectedSkillIndex = Math.min(selectedSkillIndex + 1, items.length - 1);
6369
+ updateSkillSelection();
6370
+ return;
6371
+ }
6372
+ if (e.key === 'ArrowUp') {
6373
+ e.preventDefault();
6374
+ selectedSkillIndex = Math.max(selectedSkillIndex - 1, -1);
6375
+ updateSkillSelection();
6376
+ return;
6377
+ }
6378
+ if (e.key === 'Enter' && selectedSkillIndex >= 0) {
6379
+ e.preventDefault();
6380
+ const selected = items[selectedSkillIndex];
6381
+ if (selected) {
6382
+ var name = selected.getAttribute('data-name');
6383
+ var inputEl = document.getElementById('msg-input');
6384
+ inputEl.value = '/' + name + ' ';
6385
+ hideSkillDropdown();
6386
+ inputEl.focus();
6387
+ }
6388
+ return;
6389
+ }
6390
+ if (e.key === 'Escape') {
6391
+ e.preventDefault();
6392
+ hideSkillDropdown();
6393
+ return;
6394
+ }
6395
+ }
6264
6396
  if (e.key === 'Enter' && !e.shiftKey) {
6265
6397
  e.preventDefault();
6266
6398
  sendMessage();
6267
6399
  }
6268
6400
  });
6269
6401
 
6270
- // Auto-resize textarea
6402
+ // Auto-resize textarea + slash command dropdown
6271
6403
  document.getElementById('msg-input').addEventListener('input', function() {
6272
6404
  this.style.height = 'auto';
6273
6405
  this.style.height = Math.min(this.scrollHeight, 120) + 'px';
6406
+ const val = this.value;
6407
+ if (val.startsWith('/')) {
6408
+ loadSkills().then(function() { showSkillDropdown(val.slice(1)); });
6409
+ } else {
6410
+ hideSkillDropdown();
6411
+ }
6274
6412
  });
6275
6413
 
6276
6414
  // \u2500\u2500 Hamburger (mobile) \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
@@ -6382,6 +6520,13 @@ function generateAdminHtml(version2) {
6382
6520
  });
6383
6521
  });
6384
6522
 
6523
+ // \u2500\u2500 Skill dropdown: hide on outside click \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
6524
+ document.addEventListener('click', function(e) {
6525
+ if (!e.target.closest('#skill-dropdown') && !e.target.closest('#msg-input')) {
6526
+ hideSkillDropdown();
6527
+ }
6528
+ });
6529
+
6385
6530
  // \u2500\u2500 Mobile keyboard adaptation \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
6386
6531
  document.getElementById('msg-input').addEventListener('focus', () => {
6387
6532
  setTimeout(() => {
@@ -6389,6 +6534,15 @@ function generateAdminHtml(version2) {
6389
6534
  }, 300);
6390
6535
  });
6391
6536
 
6537
+ if (window.visualViewport) {
6538
+ window.visualViewport.addEventListener('resize', function() {
6539
+ const bar = document.getElementById('input-bar');
6540
+ if (!bar) return;
6541
+ const offsetBottom = window.innerHeight - window.visualViewport.height - window.visualViewport.offsetTop;
6542
+ bar.style.bottom = Math.max(0, offsetBottom) + 'px';
6543
+ });
6544
+ }
6545
+
6392
6546
  // \u2500\u2500 Global error boundary \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
6393
6547
  window.onerror = function(msg, src, line) {
6394
6548
  const wsStatusEl = document.getElementById('ws-status');
@@ -6664,6 +6818,9 @@ async function chatRoutes(app, opts) {
6664
6818
  if (typeof body.systemPrompt === "string") {
6665
6819
  sessionOpts.systemPrompt = body.systemPrompt;
6666
6820
  }
6821
+ if (opts.autoApprove) {
6822
+ sessionOpts.autoApproveNormal = true;
6823
+ }
6667
6824
  if (!opts.config.apiKey) {
6668
6825
  return reply.code(400).send({
6669
6826
  success: false,
@@ -6828,6 +6985,80 @@ async function systemRoutes(app, opts) {
6828
6985
  });
6829
6986
  }
6830
6987
 
6988
+ // src/web/routes/skills.ts
6989
+ import { readdir as readdir5, readFile as readFile10 } from "fs/promises";
6990
+ import { join as join15 } from "path";
6991
+ import os2 from "os";
6992
+ var BUILTIN_COMMANDS = [
6993
+ { name: "clear", description: "Clear conversation history", type: "builtin" },
6994
+ { name: "interrupt", description: "Interrupt current operation", type: "builtin" },
6995
+ { name: "help", description: "Show help information", type: "builtin" }
6996
+ ];
6997
+ function parseFrontmatter2(content) {
6998
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
6999
+ if (!match) return null;
7000
+ const frontmatter = match[1];
7001
+ const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
7002
+ if (!nameMatch) return null;
7003
+ const descMatch = frontmatter.match(/^description:\s*(?:>-?|[|>+])?\s*(.+)$/m);
7004
+ if (!descMatch) return null;
7005
+ const name = nameMatch[1].trim();
7006
+ const description = descMatch[1].trim();
7007
+ if (!name || !description) return null;
7008
+ return { name, description };
7009
+ }
7010
+ async function loadAgents() {
7011
+ const agentsDir = join15(os2.homedir(), ".claude", "agents");
7012
+ let files;
7013
+ try {
7014
+ const entries = await readdir5(agentsDir);
7015
+ files = entries.filter((f) => f.endsWith(".md"));
7016
+ } catch {
7017
+ return [];
7018
+ }
7019
+ const items = [];
7020
+ for (const file of files) {
7021
+ try {
7022
+ const content = await readFile10(join15(agentsDir, file), "utf-8");
7023
+ const parsed = parseFrontmatter2(content);
7024
+ if (parsed) {
7025
+ items.push({ ...parsed, type: "agent" });
7026
+ }
7027
+ } catch {
7028
+ }
7029
+ }
7030
+ return items;
7031
+ }
7032
+ async function loadSkills() {
7033
+ const skillsDir = join15(process.cwd(), "skills");
7034
+ let subdirs;
7035
+ try {
7036
+ const entries = await readdir5(skillsDir, { withFileTypes: true });
7037
+ subdirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
7038
+ } catch {
7039
+ return [];
7040
+ }
7041
+ const items = [];
7042
+ for (const subdir of subdirs) {
7043
+ try {
7044
+ const skillFile = join15(skillsDir, subdir, "SKILL.md");
7045
+ const content = await readFile10(skillFile, "utf-8");
7046
+ const parsed = parseFrontmatter2(content);
7047
+ if (parsed) {
7048
+ items.push({ ...parsed, type: "skill" });
7049
+ }
7050
+ } catch {
7051
+ }
7052
+ }
7053
+ return items;
7054
+ }
7055
+ async function skillsRoutes(app) {
7056
+ app.get("/api/skills", async (_request, _reply) => {
7057
+ const [agents, skills] = await Promise.all([loadAgents(), loadSkills()]);
7058
+ return [...BUILTIN_COMMANDS, ...agents, ...skills];
7059
+ });
7060
+ }
7061
+
6831
7062
  // src/web/server.ts
6832
7063
  async function buildServer(opts) {
6833
7064
  const app = Fastify({ logger: false });
@@ -6854,8 +7085,9 @@ async function buildServer(opts) {
6854
7085
  await app.register(sessionsRoutes, { config: opts.config, manager: opts.manager });
6855
7086
  await app.register(configRoutes, { config: opts.config, save: saveConfig });
6856
7087
  await app.register(automationRoutes, { config: opts.config });
6857
- await app.register(chatRoutes, { config: opts.config, manager: opts.manager });
7088
+ await app.register(chatRoutes, { config: opts.config, manager: opts.manager, autoApprove: opts.autoApprove });
6858
7089
  await app.register(systemRoutes, { version: opts.version });
7090
+ await app.register(skillsRoutes);
6859
7091
  await app.register(sessionHubRoutes, { manager: opts.manager });
6860
7092
  return app;
6861
7093
  }
@@ -6951,18 +7183,28 @@ var SessionRuntime = class {
6951
7183
  messages;
6952
7184
  config;
6953
7185
  model;
7186
+ autoApproveNormal;
6954
7187
  abortController = null;
6955
7188
  _stopAfterToolRound = false;
6956
7189
  _turnCount = 0;
6957
7190
  _totalTokens = 0;
6958
7191
  startedAt = (/* @__PURE__ */ new Date()).toISOString();
6959
7192
  lastActivity = (/* @__PURE__ */ new Date()).toISOString();
6960
- title = "New Session";
7193
+ _title = "New Session";
7194
+ logger;
6961
7195
  constructor(config2, opts = {}) {
6962
7196
  this.id = opts.sessionId ?? crypto.randomUUID();
6963
7197
  this.config = config2;
6964
7198
  this.llm = opts.llm ?? createProvider(resolveActiveProfile(config2));
6965
7199
  this.model = config2.model;
7200
+ this.autoApproveNormal = opts.autoApproveNormal ?? false;
7201
+ if (config2.logDir) {
7202
+ this.logger = createLogger(config2.logDir, /* @__PURE__ */ new Date());
7203
+ const meta = createSessionMetadata(this.logger.filePath, this.model, this.id);
7204
+ writeSessionMetadata(this.logger.filePath, meta);
7205
+ } else {
7206
+ this.logger = null;
7207
+ }
6966
7208
  const systemContent = opts.systemPrompt ?? (config2.systemPrompt ?? DEFAULT_SYSTEM_PROMPT);
6967
7209
  this.messages = systemContent ? [{ role: "system", content: systemContent }] : [];
6968
7210
  if (opts.initialMessages) {
@@ -6987,6 +7229,7 @@ var SessionRuntime = class {
6987
7229
  */
6988
7230
  async submit(userInput) {
6989
7231
  this.messages.push({ role: "user", content: userInput });
7232
+ this.logger?.append({ ts: (/* @__PURE__ */ new Date()).toISOString(), role: "user", content: userInput });
6990
7233
  this.abortController = new AbortController();
6991
7234
  const signal = this.abortController.signal;
6992
7235
  try {
@@ -7002,6 +7245,17 @@ var SessionRuntime = class {
7002
7245
  }
7003
7246
  this._turnCount++;
7004
7247
  this.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
7248
+ if (this.logger) {
7249
+ if (this._turnCount === 1) {
7250
+ this._title = generateTitle(userInput);
7251
+ }
7252
+ updateSessionMetadata(this.logger.filePath, {
7253
+ title: this._title,
7254
+ turnCount: this._turnCount,
7255
+ totalTokens: this._totalTokens,
7256
+ lastActivity: this.lastActivity
7257
+ });
7258
+ }
7005
7259
  this._status = "idle";
7006
7260
  this.bus.emit({ type: "session.idle", sessionId: this.id });
7007
7261
  }
@@ -7064,12 +7318,19 @@ var SessionRuntime = class {
7064
7318
  ...assistantReasoning ? { reasoning_content: assistantReasoning } : {},
7065
7319
  ...lastReasoningDetails ? { reasoning_details: lastReasoningDetails } : {}
7066
7320
  });
7321
+ this.logger?.append({
7322
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
7323
+ role: "assistant",
7324
+ content: assistantText || null,
7325
+ tool_calls: toolCalls.map((tc) => ({ id: tc.id, type: "function", function: { name: tc.name, arguments: tc.arguments } }))
7326
+ });
7067
7327
  for (const tc of toolCalls) {
7068
7328
  if (signal.aborted) break;
7069
7329
  this.bus.emit({ type: "tool.started", callId: tc.id, toolName: tc.name, argsPreview: tc.arguments });
7070
7330
  const toolResult = await this.executeToolCall(tc, signal);
7071
7331
  this.bus.emit({ type: "tool.completed", callId: tc.id, toolName: tc.name, output: toolResult });
7072
7332
  this.messages.push({ role: "tool", tool_call_id: tc.id, content: toolResult });
7333
+ this.logger?.append({ ts: (/* @__PURE__ */ new Date()).toISOString(), role: "tool", tool_call_id: tc.id, content: toolResult });
7073
7334
  }
7074
7335
  if (this._stopAfterToolRound) {
7075
7336
  this._stopAfterToolRound = false;
@@ -7082,6 +7343,7 @@ var SessionRuntime = class {
7082
7343
  content: assistantText,
7083
7344
  ...assistantReasoning ? { reasoning_content: assistantReasoning } : {}
7084
7345
  });
7346
+ this.logger?.append({ ts: (/* @__PURE__ */ new Date()).toISOString(), role: "assistant", content: assistantText || null });
7085
7347
  }
7086
7348
  break;
7087
7349
  }
@@ -7101,17 +7363,20 @@ var SessionRuntime = class {
7101
7363
  const parsed = JSON.parse(args);
7102
7364
  const cls = classifyCommand(parsed.command, this.config.dangerousPatterns);
7103
7365
  if (cls === "normal" || cls === "danger") {
7104
- this._status = "awaiting_confirm";
7105
- const kind = cls === "danger" ? "danger" : "normal";
7106
- const prompt = cls === "danger" ? `\u26A0\uFE0F DANGEROUS COMMAND: ${parsed.command}` : `Execute command: ${parsed.command}
7366
+ if (cls === "normal" && this.autoApproveNormal) {
7367
+ } else {
7368
+ this._status = "awaiting_confirm";
7369
+ const kind = cls === "danger" ? "danger" : "normal";
7370
+ const prompt = cls === "danger" ? `\u26A0\uFE0F DANGEROUS COMMAND: ${parsed.command}` : `Execute command: ${parsed.command}
7107
7371
  Proceed?`;
7108
- const { id: reqId, promise } = this.approvals.request(kind, prompt);
7109
- this.bus.emit({ type: "approval.requested", requestId: reqId, kind, prompt });
7110
- const approved = await promise;
7111
- this._status = "tool_calling";
7112
- if (!approved) {
7113
- this._stopAfterToolRound = true;
7114
- return SKIP_MESSAGE;
7372
+ const { id: reqId, promise } = this.approvals.request(kind, prompt);
7373
+ this.bus.emit({ type: "approval.requested", requestId: reqId, kind, prompt });
7374
+ const approved = await promise;
7375
+ this._status = "tool_calling";
7376
+ if (!approved) {
7377
+ this._stopAfterToolRound = true;
7378
+ return SKIP_MESSAGE;
7379
+ }
7115
7380
  }
7116
7381
  }
7117
7382
  if (signal.aborted) return SKIP_MESSAGE;
@@ -7191,7 +7456,7 @@ Proceed?`;
7191
7456
  snapshot() {
7192
7457
  return {
7193
7458
  id: this.id,
7194
- title: this.title,
7459
+ title: this._title,
7195
7460
  model: this.model,
7196
7461
  status: this._status,
7197
7462
  turnCount: this._turnCount,
@@ -7389,6 +7654,7 @@ if (rawArgs[0] === "sessions") {
7389
7654
  if (rawArgs[0] === "web") {
7390
7655
  let webPort = 4310;
7391
7656
  let webHost = "127.0.0.1";
7657
+ let webAutoApprove = false;
7392
7658
  for (let i = 1; i < rawArgs.length; i++) {
7393
7659
  const arg = rawArgs[i];
7394
7660
  const next = rawArgs[i + 1];
@@ -7399,6 +7665,8 @@ if (rawArgs[0] === "web") {
7399
7665
  } else if (arg === "--host" && next && !next.startsWith("-")) {
7400
7666
  webHost = next;
7401
7667
  i++;
7668
+ } else if (arg === "--auto") {
7669
+ webAutoApprove = true;
7402
7670
  }
7403
7671
  }
7404
7672
  const token = generateAccessToken();
@@ -7407,7 +7675,8 @@ if (rawArgs[0] === "web") {
7407
7675
  config: finalConfig,
7408
7676
  manager,
7409
7677
  token,
7410
- version: VERSION
7678
+ version: VERSION,
7679
+ autoApprove: webAutoApprove
7411
7680
  });
7412
7681
  await server.listen({ port: webPort, host: webHost });
7413
7682
  const browserHost = webHost === "0.0.0.0" ? "localhost" : webHost;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhongqian97-code/ecode",
3
- "version": "0.5.30",
3
+ "version": "0.5.32",
4
4
  "description": "A minimal Claude Code clone with REPL interface and bash tool calling",
5
5
  "type": "module",
6
6
  "author": "zhongqian97-code",