ccmanager 3.5.2 → 3.5.4

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.
@@ -9,10 +9,5 @@ interface TextInputWrapperProps {
9
9
  showCursor?: boolean;
10
10
  highlightPastedText?: boolean;
11
11
  }
12
- /**
13
- * Custom text input component that handles rapid input correctly.
14
- * This is a replacement for ink-text-input that uses refs for immediate
15
- * state updates, which is necessary for text expansion tools like Espanso.
16
- */
17
12
  declare const TextInputWrapper: React.FC<TextInputWrapperProps>;
18
13
  export default TextInputWrapper;
@@ -1,142 +1,15 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useEffect, useRef } from 'react';
3
- import { Text, useInput } from 'ink';
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import TextInput from 'ink-text-input';
4
3
  import stripAnsi from 'strip-ansi';
5
- /**
6
- * Custom text input component that handles rapid input correctly.
7
- * This is a replacement for ink-text-input that uses refs for immediate
8
- * state updates, which is necessary for text expansion tools like Espanso.
9
- */
10
- const TextInputWrapper = ({ value, onChange, onSubmit, placeholder = '', focus = true, mask, showCursor = true, }) => {
11
- // Use ref to track the actual current value for immediate updates
12
- // This is critical for handling rapid input from tools like Espanso
13
- const valueRef = useRef(value);
14
- const cursorRef = useRef(value.length);
15
- // State for triggering re-renders
16
- const [, forceUpdate] = useState({});
17
- // Sync refs when value prop changes from parent
18
- useEffect(() => {
19
- valueRef.current = value;
20
- // Adjust cursor if it's beyond the new value length
21
- if (cursorRef.current > value.length) {
22
- cursorRef.current = value.length;
23
- }
24
- }, [value]);
25
- const cleanValue = (val) => {
26
- let cleaned = stripAnsi(val);
27
- cleaned = cleaned.replace(/\[200~/g, '').replace(/\[201~/g, '');
28
- return cleaned;
4
+ const TextInputWrapper = ({ value, onChange, ...props }) => {
5
+ const handleChange = (newValue) => {
6
+ // First strip all ANSI escape sequences
7
+ let cleanedValue = stripAnsi(newValue);
8
+ // Then specifically remove bracketed paste mode markers that might remain
9
+ // These sometimes appear as literal text after ANSI stripping
10
+ cleanedValue = cleanedValue.replace(/\[200~/g, '').replace(/\[201~/g, '');
11
+ onChange(cleanedValue);
29
12
  };
30
- // Process backspace characters that might be embedded in input string
31
- // This handles cases where text expansion tools send backspaces as characters
32
- const processBackspaces = (input, currentValue, cursor) => {
33
- let newValue = currentValue;
34
- let newCursor = cursor;
35
- let remaining = '';
36
- for (let i = 0; i < input.length; i++) {
37
- const char = input[i];
38
- const charCode = char?.charCodeAt(0);
39
- // Check for backspace characters (ASCII 8 or 127)
40
- if (charCode === 8 || charCode === 127) {
41
- if (newCursor > 0) {
42
- newValue =
43
- newValue.slice(0, newCursor - 1) + newValue.slice(newCursor);
44
- newCursor--;
45
- }
46
- }
47
- else {
48
- // Regular character - add to remaining
49
- remaining += char;
50
- }
51
- }
52
- return { value: newValue, cursor: newCursor, remainingInput: remaining };
53
- };
54
- useInput((input, key) => {
55
- // Ignore certain keys
56
- if (key.upArrow ||
57
- key.downArrow ||
58
- (key.ctrl && input === 'c') ||
59
- key.tab ||
60
- (key.shift && key.tab)) {
61
- return;
62
- }
63
- // Handle Enter/Return
64
- if (key.return) {
65
- if (onSubmit) {
66
- onSubmit(valueRef.current);
67
- }
68
- return;
69
- }
70
- let currentValue = valueRef.current;
71
- let cursor = cursorRef.current;
72
- if (key.leftArrow) {
73
- if (showCursor && cursor > 0) {
74
- cursorRef.current = cursor - 1;
75
- forceUpdate({});
76
- }
77
- return;
78
- }
79
- if (key.rightArrow) {
80
- if (showCursor && cursor < currentValue.length) {
81
- cursorRef.current = cursor + 1;
82
- forceUpdate({});
83
- }
84
- return;
85
- }
86
- if (key.backspace || key.delete) {
87
- if (cursor > 0) {
88
- const nextValue = currentValue.slice(0, cursor - 1) + currentValue.slice(cursor);
89
- valueRef.current = nextValue;
90
- cursorRef.current = cursor - 1;
91
- onChange(nextValue);
92
- forceUpdate({});
93
- }
94
- return;
95
- }
96
- // Process input that might contain embedded backspace characters
97
- // (some text expansion tools send backspaces as part of the input string)
98
- const { value: processedValue, cursor: processedCursor, remainingInput, } = processBackspaces(input, currentValue, cursor);
99
- currentValue = processedValue;
100
- cursor = processedCursor;
101
- // Add remaining characters (non-backspace)
102
- if (remainingInput) {
103
- const cleanedInput = cleanValue(remainingInput);
104
- if (cleanedInput) {
105
- currentValue =
106
- currentValue.slice(0, cursor) +
107
- cleanedInput +
108
- currentValue.slice(cursor);
109
- cursor = cursor + cleanedInput.length;
110
- }
111
- }
112
- // Update refs immediately (synchronously)
113
- valueRef.current = currentValue;
114
- cursorRef.current = cursor;
115
- // Notify parent of value change
116
- onChange(currentValue);
117
- // Force re-render to update display
118
- forceUpdate({});
119
- }, { isActive: focus });
120
- // Render the text with cursor
121
- const displayValue = mask
122
- ? mask.repeat(valueRef.current.length)
123
- : valueRef.current;
124
- const cursor = cursorRef.current;
125
- if (!showCursor || !focus) {
126
- return (_jsx(Text, { children: displayValue.length > 0 ? (displayValue) : placeholder ? (_jsx(Text, { dimColor: true, children: placeholder })) : null }));
127
- }
128
- // Show cursor
129
- if (displayValue.length === 0) {
130
- // Show placeholder with cursor on first char
131
- if (placeholder) {
132
- return (_jsxs(Text, { children: [_jsx(Text, { inverse: true, children: placeholder[0] || ' ' }), _jsx(Text, { dimColor: true, children: placeholder.slice(1) })] }));
133
- }
134
- return _jsx(Text, { inverse: true, children: " " });
135
- }
136
- // Render value with cursor
137
- const beforeCursor = displayValue.slice(0, cursor);
138
- const atCursor = displayValue[cursor] || ' ';
139
- const afterCursor = displayValue.slice(cursor + 1);
140
- return (_jsxs(Text, { children: [beforeCursor, _jsx(Text, { inverse: true, children: atCursor }), afterCursor] }));
13
+ return _jsx(TextInput, { value: value, onChange: handleChange, ...props });
141
14
  };
142
15
  export default TextInputWrapper;
@@ -17,7 +17,6 @@ export declare class SessionManager extends EventEmitter implements ISessionMana
17
17
  private spawn;
18
18
  detectTerminalState(session: Session): SessionState;
19
19
  detectBackgroundTask(session: Session): number;
20
- private getTerminalContent;
21
20
  private handleAutoApproval;
22
21
  private cancelAutoApprovalVerification;
23
22
  /**
@@ -46,11 +46,6 @@ export class SessionManager extends EventEmitter {
46
46
  detectBackgroundTask(session) {
47
47
  return session.stateDetector.detectBackgroundTask(session.terminal);
48
48
  }
49
- getTerminalContent(session) {
50
- // Use the new screen capture utility that correctly handles
51
- // both normal and alternate screen buffers
52
- return getTerminalScreenContent(session.terminal, TERMINAL_CONTENT_MAX_LINES);
53
- }
54
49
  handleAutoApproval(session) {
55
50
  // Cancel any existing verification before starting a new one
56
51
  this.cancelAutoApprovalVerification(session, 'Restarting verification for pending auto-approval state');
@@ -61,7 +56,7 @@ export class SessionManager extends EventEmitter {
61
56
  autoApprovalReason: undefined,
62
57
  }));
63
58
  // Get terminal content for verification
64
- const terminalContent = this.getTerminalContent(session);
59
+ const terminalContent = getTerminalScreenContent(session.terminal, TERMINAL_CONTENT_MAX_LINES);
65
60
  // Verify if permission is needed
66
61
  void Effect.runPromise(autoApprovalVerifier.verifyNeedsPermission(terminalContent, {
67
62
  signal: abortController.signal,
@@ -277,6 +272,11 @@ export class SessionManager extends EventEmitter {
277
272
  session.process.onData((data) => {
278
273
  // Write data to virtual terminal
279
274
  session.terminal.write(data);
275
+ // Check for screen clear escape sequence (e.g., from /clear command)
276
+ // When detected, clear the output history to prevent replaying old content on restore
277
+ if (data.includes('\x1B[2J')) {
278
+ session.outputHistory = [];
279
+ }
280
280
  // Store in output history as Buffer
281
281
  const buffer = Buffer.from(data, 'utf8');
282
282
  session.outputHistory.push(buffer);
@@ -1,21 +1,10 @@
1
+ import { getTerminalScreenContent } from '../../utils/screenCapture.js';
1
2
  export class BaseStateDetector {
2
3
  getTerminalLines(terminal, maxLines = 30) {
3
- const buffer = terminal.buffer.active;
4
- const lines = [];
5
- // Start from the bottom and work our way up
6
- for (let i = buffer.length - 1; i >= 0 && lines.length < maxLines; i--) {
7
- const line = buffer.getLine(i);
8
- if (line) {
9
- const text = line.translateToString(true);
10
- // Skip empty lines at the bottom
11
- if (lines.length > 0 || text.trim() !== '') {
12
- lines.unshift(text);
13
- }
14
- }
15
- }
16
- return lines;
4
+ const content = getTerminalScreenContent(terminal, maxLines);
5
+ return content.split('\n');
17
6
  }
18
7
  getTerminalContent(terminal, maxLines = 30) {
19
- return this.getTerminalLines(terminal, maxLines).join('\n');
8
+ return getTerminalScreenContent(terminal, maxLines);
20
9
  }
21
10
  }
@@ -2,6 +2,10 @@ import type { Terminal } from '../../types/index.js';
2
2
  /**
3
3
  * Creates a mock Terminal object for testing state detectors.
4
4
  * @param lines - Array of strings representing terminal output lines
5
+ * @param options - Optional configuration for rows and cols
5
6
  * @returns Mock Terminal object with buffer interface
6
7
  */
7
- export declare const createMockTerminal: (lines: string[]) => Terminal;
8
+ export declare const createMockTerminal: (lines: string[], options?: {
9
+ rows?: number;
10
+ cols?: number;
11
+ }) => Terminal;
@@ -1,9 +1,12 @@
1
1
  /**
2
2
  * Creates a mock Terminal object for testing state detectors.
3
3
  * @param lines - Array of strings representing terminal output lines
4
+ * @param options - Optional configuration for rows and cols
4
5
  * @returns Mock Terminal object with buffer interface
5
6
  */
6
- export const createMockTerminal = (lines) => {
7
+ export const createMockTerminal = (lines, options) => {
8
+ const rows = options?.rows ?? lines.length;
9
+ const cols = options?.cols ?? 80;
7
10
  const buffer = {
8
11
  length: lines.length,
9
12
  active: {
@@ -11,12 +14,12 @@ export const createMockTerminal = (lines) => {
11
14
  getLine: (index) => {
12
15
  if (index >= 0 && index < lines.length) {
13
16
  return {
14
- translateToString: () => lines[index],
17
+ translateToString: (_trimRight, _startCol, _endCol) => lines[index],
15
18
  };
16
19
  }
17
20
  return null;
18
21
  },
19
22
  },
20
23
  };
21
- return { buffer };
24
+ return { buffer, rows, cols };
22
25
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccmanager",
3
- "version": "3.5.2",
3
+ "version": "3.5.4",
4
4
  "description": "TUI application for managing multiple Claude Code sessions across Git worktrees",
5
5
  "license": "MIT",
6
6
  "author": "Kodai Kabasawa",
@@ -26,7 +26,7 @@
26
26
  "build:binary:native": "bun run scripts/build-binaries.ts --target=native",
27
27
  "build:binary:all": "bun run scripts/build-binaries.ts --target=all",
28
28
  "dev": "bun run tsc --watch",
29
- "start": "bun dist/cli.js",
29
+ "start": "bun --no-env-file dist/cli.js",
30
30
  "test": "vitest --run",
31
31
  "lint": "bun run eslint src",
32
32
  "lint:fix": "bun run eslint src --fix",
@@ -41,11 +41,11 @@
41
41
  "bin"
42
42
  ],
43
43
  "optionalDependencies": {
44
- "@kodaikabasawa/ccmanager-darwin-arm64": "3.5.2",
45
- "@kodaikabasawa/ccmanager-darwin-x64": "3.5.2",
46
- "@kodaikabasawa/ccmanager-linux-arm64": "3.5.2",
47
- "@kodaikabasawa/ccmanager-linux-x64": "3.5.2",
48
- "@kodaikabasawa/ccmanager-win32-x64": "3.5.2"
44
+ "@kodaikabasawa/ccmanager-darwin-arm64": "3.5.4",
45
+ "@kodaikabasawa/ccmanager-darwin-x64": "3.5.4",
46
+ "@kodaikabasawa/ccmanager-linux-arm64": "3.5.4",
47
+ "@kodaikabasawa/ccmanager-linux-x64": "3.5.4",
48
+ "@kodaikabasawa/ccmanager-win32-x64": "3.5.4"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@eslint/js": "^9.28.0",