@zhongqian97-code/ecode 0.5.60 → 0.5.61

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.
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ const _ew=process.emitWarning.bind(process);process.emitWarning=function(w,...a){if((w?.message??w)?.includes?.('punycode'))return;_ew(w,...a);};
3
+
4
+ // src/ui/altScreen.ts
5
+ import { writeSync as fsWriteSync } from "fs";
6
+ var ENTER_ALT_SCREEN_SEQ = "\x1B[?1049h\x1B[H";
7
+ var EXIT_ALT_SCREEN_SEQ = "\x1B[?1049l";
8
+ var DISABLE_TERMINAL_MODES_SEQ = "\x1B[?1004l\x1B[?1002l\x1B[?1006l";
9
+ function installCrashCleanup(opts = {}) {
10
+ const writeSync = opts.writeSync ?? ((s) => {
11
+ try {
12
+ fsWriteSync(1, s);
13
+ } catch {
14
+ }
15
+ });
16
+ const signals = opts.signals ?? ["SIGINT", "SIGTERM", "SIGHUP"];
17
+ let cleaned = false;
18
+ const cleanup = () => {
19
+ if (cleaned) return;
20
+ cleaned = true;
21
+ writeSync(DISABLE_TERMINAL_MODES_SEQ);
22
+ writeSync(EXIT_ALT_SCREEN_SEQ);
23
+ };
24
+ const onExit = () => {
25
+ cleanup();
26
+ };
27
+ process.on("exit", onExit);
28
+ const sigHandlers = signals.map((sig) => {
29
+ const h = () => {
30
+ cleanup();
31
+ process.exit(0);
32
+ };
33
+ process.on(sig, h);
34
+ return [sig, h];
35
+ });
36
+ return () => {
37
+ process.off("exit", onExit);
38
+ for (const [sig, h] of sigHandlers) process.off(sig, h);
39
+ };
40
+ }
41
+ export {
42
+ DISABLE_TERMINAL_MODES_SEQ,
43
+ ENTER_ALT_SCREEN_SEQ,
44
+ EXIT_ALT_SCREEN_SEQ,
45
+ installCrashCleanup
46
+ };
package/dist/index.js CHANGED
@@ -903,21 +903,9 @@ Press Ctrl+C to stop.
903
903
  process.exit(0);
904
904
  }
905
905
  if (process.stdout.isTTY) {
906
- process.stdout.write("\x1B[?1049h\x1B[H");
907
- const exitAltScreen = () => process.stdout.write("\x1B[?1049l");
908
- process.on("exit", exitAltScreen);
909
- process.on("SIGINT", () => {
910
- exitAltScreen();
911
- process.exit(0);
912
- });
913
- process.on("SIGTERM", () => {
914
- exitAltScreen();
915
- process.exit(0);
916
- });
917
- process.on("SIGHUP", () => {
918
- exitAltScreen();
919
- process.exit(0);
920
- });
906
+ const { ENTER_ALT_SCREEN_SEQ, installCrashCleanup } = await import("./altScreen-I6W4LBRE.js");
907
+ process.stdout.write(ENTER_ALT_SCREEN_SEQ);
908
+ installCrashCleanup();
921
909
  }
922
910
  const [nodeMaj] = process.versions.node.split(".").map(Number);
923
911
  if (nodeMaj < 20) {
@@ -928,7 +916,7 @@ Node.js 16/18 \u8BF7\u4F7F\u7528 --web \u6216 --pipe \u6A21\u5F0F\u3002
928
916
  );
929
917
  process.exit(1);
930
918
  }
931
- const { App, React, render, createStdinFilter } = await import("./ui-3HB6NDEE.js");
919
+ const { App, React, render, createStdinFilter } = await import("./ui-DFEOOA36.js");
932
920
  const stdinFilter = process.stdin.isTTY ? createStdinFilter(process.stdin) : process.stdin;
933
921
  render(
934
922
  React.createElement(App, { config: finalConfig, version: VERSION, autoMode, registry, trustedSkillDirs, initialMessages, stdinFilter }),
@@ -47,7 +47,7 @@ import { render } from "ink";
47
47
 
48
48
  // src/ui/App.tsx
49
49
  import { useState as useState3, useCallback, useRef as useRef2, useEffect as useEffect3, useMemo } from "react";
50
- import { Box as Box6, Text as Text6, useInput as useInput2, useStdout } from "ink";
50
+ import { Box as Box6, Text as Text6, useInput as useInput2, useStdout, measureElement } from "ink";
51
51
 
52
52
  // src/skills/executor.ts
53
53
  import { exec } from "child_process";
@@ -296,6 +296,7 @@ function messageToLines(msg, expandTools, terminalWidth) {
296
296
  }
297
297
 
298
298
  // src/ui/mouseSelection.ts
299
+ import stringWidth2 from "string-width";
299
300
  function initialSelectionState() {
300
301
  return { anchor: null, focus: null, dragging: false };
301
302
  }
@@ -328,6 +329,21 @@ function isCellSelected(row, col, state) {
328
329
  if (row === end.row) return col <= end.col;
329
330
  return true;
330
331
  }
332
+ function colToStringIndex(line, col, roundUp) {
333
+ if (col <= 0) return 0;
334
+ let w = 0;
335
+ let idx = 0;
336
+ for (const ch of line) {
337
+ const cw = stringWidth2(ch);
338
+ if (w >= col) return idx;
339
+ if (w + cw > col) {
340
+ return roundUp ? idx + ch.length : idx;
341
+ }
342
+ w += cw;
343
+ idx += ch.length;
344
+ }
345
+ return idx;
346
+ }
331
347
  function extractSelectedText(lines, state) {
332
348
  const norm = normalizeSelection(state);
333
349
  if (!norm) return "";
@@ -335,9 +351,11 @@ function extractSelectedText(lines, state) {
335
351
  const parts = [];
336
352
  for (let r = start.row; r <= end.row; r++) {
337
353
  const line = lines[r] ?? "";
338
- const colStart = r === start.row ? start.col : 0;
339
- const colEnd = r === end.row ? Math.min(end.col + 1, line.length) : line.length;
340
- parts.push(line.slice(colStart, colEnd));
354
+ const colStartVis = r === start.row ? start.col : 0;
355
+ const colEndVis = r === end.row ? end.col + 1 : Number.POSITIVE_INFINITY;
356
+ const idxStart = colToStringIndex(line, colStartVis, false);
357
+ const idxEnd = colToStringIndex(line, colEndVis, true);
358
+ parts.push(line.slice(idxStart, idxEnd));
341
359
  }
342
360
  return parts.join("\n");
343
361
  }
@@ -405,7 +423,20 @@ import { useState as useState2, useEffect as useEffect2, useRef, forwardRef, use
405
423
  import { Box as Box3, Text as Text3, useInput } from "ink";
406
424
 
407
425
  // src/ui/inputClick.ts
426
+ import stringWidth3 from "string-width";
408
427
  var INPUT_PREFIX_WIDTH = 2;
428
+ function colToStringIndex2(line, col) {
429
+ if (col <= 0) return 0;
430
+ let w = 0;
431
+ let idx = 0;
432
+ for (const ch of line) {
433
+ const cw = stringWidth3(ch);
434
+ if (w + cw > col) break;
435
+ w += cw;
436
+ idx += ch.length;
437
+ }
438
+ return idx;
439
+ }
409
440
  function clickToCursor(value, localRow, localCol) {
410
441
  if (value.length === 0) return 0;
411
442
  const lines = value.split("\n");
@@ -416,7 +447,7 @@ function clickToCursor(value, localRow, localCol) {
416
447
  pos += lines[i].length + 1;
417
448
  }
418
449
  const lineCol = Math.max(0, localCol - INPUT_PREFIX_WIDTH);
419
- pos += Math.min(lineCol, lines[localRow].length);
450
+ pos += colToStringIndex2(lines[localRow], lineCol);
420
451
  return pos;
421
452
  }
422
453
 
@@ -962,7 +993,7 @@ function computeVisibleWindow(totalLines, scrollOffset, historyMaxHeight) {
962
993
  }
963
994
  function mouseRowToLineIndex(opts) {
964
995
  if (opts.visibleCount <= 0) return null;
965
- const bottomRows = STATUS_BAR_ROWS + opts.skillAcRows + opts.fileAcRows + opts.selectedItemsRows + opts.inputRows;
996
+ const bottomRows = opts.bottomChromeRows ?? STATUS_BAR_ROWS + (opts.skillAcRows ?? 0) + (opts.fileAcRows ?? 0) + (opts.selectedItemsRows ?? 0) + (opts.inputRows ?? 1);
966
997
  const innerBoxHeight = opts.terminalRows - bottomRows;
967
998
  const chTopRow = innerBoxHeight - opts.visibleCount;
968
999
  const chBottomRow = innerBoxHeight - 1;
@@ -2542,6 +2573,7 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2542
2573
  const inputHistoryRef = useRef2([]);
2543
2574
  inputHistoryRef.current = inputHistory;
2544
2575
  const historyIndexRef = useRef2(-1);
2576
+ const historyDraftRef = useRef2("");
2545
2577
  const isNavigatingHistoryRef = useRef2(false);
2546
2578
  const totalLines = useMemo(() => {
2547
2579
  const visible = messages.filter((m) => m.role !== "system");
@@ -2558,10 +2590,8 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2558
2590
  historyMaxHeightRef.current = historyMaxHeight;
2559
2591
  const selectionStateRef = useRef2(selectionState);
2560
2592
  selectionStateRef.current = selectionState;
2593
+ const bottomChromeRef = useRef2(null);
2561
2594
  const inputRowsRef = useRef2(1);
2562
- const skillAcRowsRef = useRef2(0);
2563
- const fileAcRowsRef = useRef2(0);
2564
- const selectedItemsRowsRef = useRef2(0);
2565
2595
  const terminalRowsRef = useRef2((stdout == null ? void 0 : stdout.rows) ?? 24);
2566
2596
  terminalRowsRef.current = (stdout == null ? void 0 : stdout.rows) ?? 24;
2567
2597
  const pendingConfirmRef = useRef2(null);
@@ -2626,13 +2656,24 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2626
2656
  automationManagerRef.current.stop();
2627
2657
  };
2628
2658
  }, []);
2659
+ const enableModesSeq = "\x1B[?1002h\x1B[?1006h\x1B[?1004h";
2629
2660
  useEffect3(() => {
2630
2661
  if (!stdout) return;
2631
- stdout.write("\x1B[?1002h\x1B[?1006h");
2662
+ stdout.write(enableModesSeq);
2632
2663
  return () => {
2633
- stdout.write("\x1B[?1002l\x1B[?1006l");
2664
+ stdout.write("\x1B[?1004l\x1B[?1002l\x1B[?1006l");
2634
2665
  };
2635
2666
  }, [stdout]);
2667
+ useEffect3(() => {
2668
+ if (!stdinFilter || !stdout) return;
2669
+ const onResumed = () => {
2670
+ stdout.write(enableModesSeq);
2671
+ };
2672
+ stdinFilter.on("resumed", onResumed);
2673
+ return () => {
2674
+ stdinFilter.off("resumed", onResumed);
2675
+ };
2676
+ }, [stdinFilter, stdout]);
2636
2677
  useEffect3(() => {
2637
2678
  if (!stdinFilter) return;
2638
2679
  const onMouse = (e) => {
@@ -2672,13 +2713,16 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2672
2713
  scrollOffsetRef.current,
2673
2714
  historyMaxHeightRef.current
2674
2715
  );
2716
+ let bottomRows;
2717
+ if (bottomChromeRef.current) {
2718
+ const m = measureElement(bottomChromeRef.current);
2719
+ if (m && m.height > 0) bottomRows = m.height;
2720
+ }
2675
2721
  const lineIdx = mouseRowToLineIndex({
2676
2722
  mouseRow: e.row,
2677
2723
  terminalRows: tRows,
2724
+ bottomChromeRows: bottomRows,
2678
2725
  inputRows: inRows,
2679
- skillAcRows: skillAcRowsRef.current,
2680
- fileAcRows: fileAcRowsRef.current,
2681
- selectedItemsRows: selectedItemsRowsRef.current,
2682
2726
  visibleCount: window.visibleCount,
2683
2727
  hasScrollIndicator: window.hasScrollIndicator
2684
2728
  });
@@ -2721,7 +2765,7 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2721
2765
  };
2722
2766
  }, [fileAcState.query]);
2723
2767
  useInput2((input, key) => {
2724
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
2768
+ var _a, _b, _c;
2725
2769
  if (key.escape && status === "thinking" && abortControllerRef.current) {
2726
2770
  abortControllerRef.current.abort();
2727
2771
  return;
@@ -2796,52 +2840,47 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2796
2840
  return;
2797
2841
  }
2798
2842
  const currentInput = ((_c = inputRef.current) == null ? void 0 : _c.getValue()) ?? "";
2799
- if (currentInput === "" && key.upArrow) {
2843
+ const inHistory = historyIndexRef.current >= 0;
2844
+ const canEnterHistory = currentInput === "" || inHistory;
2845
+ const navUp = () => {
2846
+ var _a2;
2800
2847
  const history = inputHistoryRef.current;
2801
2848
  const newIndex = Math.min(historyIndexRef.current + 1, history.length - 1);
2802
- if (newIndex >= 0 && newIndex < history.length) {
2803
- historyIndexRef.current = newIndex;
2804
- isNavigatingHistoryRef.current = true;
2805
- (_d = inputRef.current) == null ? void 0 : _d.fill(history[newIndex]);
2806
- }
2807
- return;
2808
- }
2809
- if (currentInput === "" && key.downArrow) {
2849
+ if (newIndex < 0 || newIndex >= history.length) return;
2850
+ if (historyIndexRef.current === -1) historyDraftRef.current = currentInput;
2851
+ historyIndexRef.current = newIndex;
2852
+ isNavigatingHistoryRef.current = true;
2853
+ (_a2 = inputRef.current) == null ? void 0 : _a2.fill(history[newIndex]);
2854
+ };
2855
+ const navDown = () => {
2856
+ var _a2, _b2;
2810
2857
  const history = inputHistoryRef.current;
2811
2858
  if (historyIndexRef.current > 0) {
2812
2859
  const newIndex = historyIndexRef.current - 1;
2813
2860
  historyIndexRef.current = newIndex;
2814
2861
  isNavigatingHistoryRef.current = true;
2815
- (_e = inputRef.current) == null ? void 0 : _e.fill(history[newIndex]);
2862
+ (_a2 = inputRef.current) == null ? void 0 : _a2.fill(history[newIndex]);
2816
2863
  } else if (historyIndexRef.current === 0) {
2817
2864
  historyIndexRef.current = -1;
2818
2865
  isNavigatingHistoryRef.current = true;
2819
- (_f = inputRef.current) == null ? void 0 : _f.fill("");
2866
+ (_b2 = inputRef.current) == null ? void 0 : _b2.fill(historyDraftRef.current);
2867
+ historyDraftRef.current = "";
2820
2868
  }
2869
+ };
2870
+ if (canEnterHistory && key.upArrow) {
2871
+ navUp();
2872
+ return;
2873
+ }
2874
+ if (canEnterHistory && key.downArrow) {
2875
+ navDown();
2821
2876
  return;
2822
2877
  }
2823
2878
  if (key.ctrl && input === "p") {
2824
- const history = inputHistoryRef.current;
2825
- const newIndex = Math.min(historyIndexRef.current + 1, history.length - 1);
2826
- if (newIndex >= 0 && newIndex < history.length) {
2827
- historyIndexRef.current = newIndex;
2828
- isNavigatingHistoryRef.current = true;
2829
- (_g = inputRef.current) == null ? void 0 : _g.fill(history[newIndex]);
2830
- }
2879
+ navUp();
2831
2880
  return;
2832
2881
  }
2833
2882
  if (key.ctrl && input === "n") {
2834
- const history = inputHistoryRef.current;
2835
- if (historyIndexRef.current > 0) {
2836
- const newIndex = historyIndexRef.current - 1;
2837
- historyIndexRef.current = newIndex;
2838
- isNavigatingHistoryRef.current = true;
2839
- (_h = inputRef.current) == null ? void 0 : _h.fill(history[newIndex]);
2840
- } else if (historyIndexRef.current === 0) {
2841
- historyIndexRef.current = -1;
2842
- isNavigatingHistoryRef.current = true;
2843
- (_i = inputRef.current) == null ? void 0 : _i.fill("");
2844
- }
2883
+ navDown();
2845
2884
  return;
2846
2885
  }
2847
2886
  });
@@ -3186,9 +3225,6 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
3186
3225
  const skillSuggestions = registry ? computeSuggestions(registry.list(), acState) : [];
3187
3226
  const acOpen = isOpen(acState, skillSuggestions);
3188
3227
  const facOpen = isOpen2(fileAcState, fileSuggestions);
3189
- skillAcRowsRef.current = acOpen ? skillSuggestions.length + 2 : 0;
3190
- fileAcRowsRef.current = facOpen ? fileSuggestions.length + 2 : 0;
3191
- selectedItemsRowsRef.current = selectedItems.length > 0 ? 1 : 0;
3192
3228
  return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height: "100%", children: [
3193
3229
  /* @__PURE__ */ jsx6(Box6, { flexGrow: 1, flexDirection: "column", justifyContent: "flex-end", children: /* @__PURE__ */ jsx6(
3194
3230
  ConversationHistory,
@@ -3201,46 +3237,48 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
3201
3237
  selection: selectionState
3202
3238
  }
3203
3239
  ) }),
3204
- /* @__PURE__ */ jsx6(
3205
- StatusBar,
3206
- {
3207
- status,
3208
- toolName,
3209
- confirmPrompt,
3210
- version,
3211
- tokenUsage
3212
- }
3213
- ),
3214
- /* @__PURE__ */ jsx6(
3215
- SkillAutocomplete,
3216
- {
3217
- suggestions: skillSuggestions,
3218
- selectedIndex: acState.selectedIndex,
3219
- isOpen: acOpen
3220
- }
3221
- ),
3222
- /* @__PURE__ */ jsx6(
3223
- FileAutocomplete,
3224
- {
3225
- suggestions: fileSuggestions,
3226
- selectedIndex: fileAcState.selectedIndex,
3227
- isOpen: facOpen
3228
- }
3229
- ),
3230
- selectedItems.length > 0 && /* @__PURE__ */ jsx6(Box6, { paddingLeft: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: "cyan", dimColor: true, children: [
3231
- "Selected: ",
3232
- selectedItems.map((i) => i.label).join(", ")
3233
- ] }) }),
3234
- /* @__PURE__ */ jsx6(
3235
- Input_default,
3236
- {
3237
- ref: inputRef,
3238
- isActive: isInputActive,
3239
- onSubmit: handleSubmit,
3240
- onChange: handleInputTextChange,
3241
- placeholder: status === "awaiting_confirm" ? "y / n" : void 0
3242
- }
3243
- )
3240
+ /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", ref: bottomChromeRef, children: [
3241
+ /* @__PURE__ */ jsx6(
3242
+ StatusBar,
3243
+ {
3244
+ status,
3245
+ toolName,
3246
+ confirmPrompt,
3247
+ version,
3248
+ tokenUsage
3249
+ }
3250
+ ),
3251
+ /* @__PURE__ */ jsx6(
3252
+ SkillAutocomplete,
3253
+ {
3254
+ suggestions: skillSuggestions,
3255
+ selectedIndex: acState.selectedIndex,
3256
+ isOpen: acOpen
3257
+ }
3258
+ ),
3259
+ /* @__PURE__ */ jsx6(
3260
+ FileAutocomplete,
3261
+ {
3262
+ suggestions: fileSuggestions,
3263
+ selectedIndex: fileAcState.selectedIndex,
3264
+ isOpen: facOpen
3265
+ }
3266
+ ),
3267
+ selectedItems.length > 0 && /* @__PURE__ */ jsx6(Box6, { paddingLeft: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: "cyan", dimColor: true, children: [
3268
+ "Selected: ",
3269
+ selectedItems.map((i) => i.label).join(", ")
3270
+ ] }) }),
3271
+ /* @__PURE__ */ jsx6(
3272
+ Input_default,
3273
+ {
3274
+ ref: inputRef,
3275
+ isActive: isInputActive,
3276
+ onSubmit: handleSubmit,
3277
+ onChange: handleInputTextChange,
3278
+ placeholder: status === "awaiting_confirm" ? "y / n" : void 0
3279
+ }
3280
+ )
3281
+ ] })
3244
3282
  ] });
3245
3283
  }
3246
3284
 
@@ -3306,6 +3344,7 @@ function tokenizeStdin(input, pendingIn) {
3306
3344
  let i = 0;
3307
3345
  let keyboardOut = "";
3308
3346
  const events = [];
3347
+ const focusEvents = [];
3309
3348
  while (i < data.length) {
3310
3349
  const ch = data[i];
3311
3350
  if (ch !== "\x1B") {
@@ -3327,13 +3366,18 @@ function tokenizeStdin(input, pendingIn) {
3327
3366
  i += mLen;
3328
3367
  continue;
3329
3368
  }
3369
+ if (tail.length >= 3 && tail[1] === "[" && (tail[2] === "I" || tail[2] === "O")) {
3370
+ focusEvents.push({ kind: "focus", focused: tail[2] === "I" });
3371
+ i += 3;
3372
+ continue;
3373
+ }
3330
3374
  if (isMousePartial(tail) && tail.length < MAX_MOUSE_SEQ) {
3331
- return { keyboard: keyboardOut, pending: tail, events };
3375
+ return { keyboard: keyboardOut, pending: tail, events, focusEvents };
3332
3376
  }
3333
3377
  keyboardOut += ch;
3334
3378
  i++;
3335
3379
  }
3336
- return { keyboard: keyboardOut, pending: "", events };
3380
+ return { keyboard: keyboardOut, pending: "", events, focusEvents };
3337
3381
  }
3338
3382
 
3339
3383
  // src/ui/stdinFilter.ts
@@ -3341,12 +3385,22 @@ var StdinFilterImpl = class extends Transform {
3341
3385
  isTTY;
3342
3386
  source;
3343
3387
  pending = "";
3344
- constructor(source) {
3388
+ // 焦点状态——cc-haha 把 focus 作 signal 而非 mode switch(§5.4)。
3389
+ // CSI I/O 切换;任何字节到达时若为 false 由 failsafe 强制回 true。
3390
+ focused = true;
3391
+ // resume 检测:最近一次活动时间戳;间隔 > resumeIdleMs 算 SSH 重连 / tmux detach 回来。
3392
+ lastByteAt = 0;
3393
+ resumeIdleMs;
3394
+ constructor(source, opts = {}) {
3345
3395
  super();
3346
3396
  this.source = source;
3347
3397
  this.isTTY = source.isTTY ?? false;
3398
+ this.resumeIdleMs = opts.resumeIdleMs ?? 5e3;
3348
3399
  source.pipe(this);
3349
3400
  }
3401
+ isFocused() {
3402
+ return this.focused;
3403
+ }
3350
3404
  setRawMode(mode) {
3351
3405
  var _a, _b;
3352
3406
  (_b = (_a = this.source).setRawMode) == null ? void 0 : _b.call(_a, mode);
@@ -3361,9 +3415,23 @@ var StdinFilterImpl = class extends Transform {
3361
3415
  }
3362
3416
  _transform(chunk, _encoding, callback) {
3363
3417
  const s = typeof chunk === "string" ? chunk : chunk.toString("binary");
3364
- const { keyboard, pending, events } = tokenizeStdin(s, this.pending);
3418
+ const now = Date.now();
3419
+ if (this.lastByteAt > 0 && now - this.lastByteAt >= this.resumeIdleMs) {
3420
+ this.emit("resumed");
3421
+ }
3422
+ this.lastByteAt = now;
3423
+ const { keyboard, pending, events, focusEvents } = tokenizeStdin(s, this.pending);
3365
3424
  this.pending = pending;
3425
+ for (const ev of focusEvents) {
3426
+ this.focused = ev.focused;
3427
+ this.emit("focus", ev);
3428
+ }
3366
3429
  for (const ev of events) this.emit("mouse", ev);
3430
+ const hadNonFocusActivity = events.length > 0 || keyboard.length > 0;
3431
+ if (hadNonFocusActivity && !this.focused) {
3432
+ this.focused = true;
3433
+ this.emit("focus", { kind: "focus", focused: true });
3434
+ }
3367
3435
  if (keyboard.length > 0) {
3368
3436
  callback(null, Buffer.from(keyboard, "binary"));
3369
3437
  } else {
@@ -3371,8 +3439,8 @@ var StdinFilterImpl = class extends Transform {
3371
3439
  }
3372
3440
  }
3373
3441
  };
3374
- function createStdinFilter(source) {
3375
- return new StdinFilterImpl(source);
3442
+ function createStdinFilter(source, opts) {
3443
+ return new StdinFilterImpl(source, opts);
3376
3444
  }
3377
3445
  export {
3378
3446
  App,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhongqian97-code/ecode",
3
- "version": "0.5.60",
3
+ "version": "0.5.61",
4
4
  "description": "A minimal Claude Code clone with REPL interface and bash tool calling",
5
5
  "type": "module",
6
6
  "author": "zhongqian97-code",