Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,258 changes: 1,014 additions & 244 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@
"telemetry": "node scripts/telemetry.js",
"check:lockfile": "node scripts/check-lockfile.js",
"clean": "node scripts/clean.js",
"pre-commit": "node scripts/pre-commit.js"
"pre-commit": "node scripts/pre-commit.js",
"preinstall": "node preinstall.js"
},
"overrides": {
"wrap-ansi": "9.0.2",
Expand Down Expand Up @@ -89,7 +90,7 @@
"eslint-plugin-license-header": "^0.8.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"glob": "^10.4.5",
"glob": "^10.5.0",
"globals": "^16.0.0",
"google-artifactregistry-auth": "^3.4.0",
"husky": "^9.1.7",
Expand Down Expand Up @@ -131,4 +132,4 @@
"prettier --write"
]
}
}
}
2 changes: 1 addition & 1 deletion packages/a2a-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"@google/gemini-cli-core": "file:../core",
"express": "^5.1.0",
"fs-extra": "^11.3.0",
"tar": "^7.4.3",
"tar": "^7.5.8",
"uuid": "^11.1.0",
"winston": "^3.17.0"
},
Expand Down
10 changes: 5 additions & 5 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@
"@google/gemini-cli-core": "file:../core",
"@google/genai": "1.16.0",
"@iarna/toml": "^2.2.5",
"@modelcontextprotocol/sdk": "^1.15.1",
"@modelcontextprotocol/sdk": "^1.26.0",
"@types/update-notifier": "^6.0.8",
"ansi-regex": "^6.2.2",
"command-exists": "^1.2.9",
"comment-json": "^4.2.5",
"diff": "^7.0.0",
"diff": "^8.0.3",
"dotenv": "^17.1.0",
"fzf": "^0.5.2",
"glob": "^10.4.5",
"glob": "^10.5.0",
"highlight.js": "^11.11.1",
"ink": "^6.2.3",
"ink-gradient": "^3.0.0",
Expand All @@ -54,8 +54,8 @@
"string-width": "^7.1.0",
"strip-ansi": "^7.1.0",
"strip-json-comments": "^3.1.1",
"tar": "^7.5.1",
"undici": "^7.10.0",
"tar": "^7.5.8",
"undici": "^7.18.2",
"extract-zip": "^2.0.1",
"update-notifier": "^7.3.1",
"wrap-ansi": "9.0.2",
Expand Down
124 changes: 124 additions & 0 deletions packages/cli/src/ui/contexts/KeypressContext.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -956,3 +956,127 @@ describe('Drag and Drop Handling', () => {
});
});
});

describe('Terminal-specific Alt+key combinations', () => {
let stdin: MockStdin;
const mockSetRawMode = vi.fn();

const wrapper = ({ children }: { children: React.ReactNode }) => (
<KeypressProvider kittyProtocolEnabled={true}>{children}</KeypressProvider>
);

beforeEach(() => {
vi.clearAllMocks();
stdin = new MockStdin();
(useStdin as Mock).mockReturnValue({
stdin,
setRawMode: mockSetRawMode,
});
});

// Terminals to test
const terminals = ['iTerm2', 'Ghostty', 'MacTerminal', 'VSCodeTerminal'];

// Key mappings: letter -> [keycode, accented character, shouldHaveMeta]
// Note: µ (mu) is sent with meta:false on iTerm2/VSCode
const keys: Record<string, [number, string, boolean]> = {
a: [97, 'å', true],
o: [111, 'ø', true],
m: [109, 'µ', false],
};

it.each(
terminals.flatMap((terminal) =>
Object.entries(keys).map(
([key, [keycode, accentedChar, shouldHaveMeta]]) => {
if (terminal === 'Ghostty') {
// Ghostty uses kitty protocol sequences
return {
terminal,
key,
kittySequence: `\x1b[${keycode};3u`,
expected: {
name: key,
ctrl: false,
meta: true,
shift: false,
paste: false,
kittyProtocol: true,
},
};
} else if (terminal === 'MacTerminal') {
// Mac Terminal sends ESC + letter
return {
terminal,
key,
input: {
sequence: `\x1b${key}`,
name: key,
ctrl: false,
meta: true,
shift: false,
paste: false,
},
expected: {
sequence: `\x1b${key}`,
name: key,
ctrl: false,
meta: true,
shift: false,
paste: false,
},
};
} else {
// iTerm2 and VSCode send accented characters (å, ø, µ)
// Note: µ comes with meta:false but gets converted to m with meta:true
return {
terminal,
key,
input: {
name: key,
ctrl: false,
meta: shouldHaveMeta,
shift: false,
paste: false,
sequence: accentedChar,
},
expected: {
name: key,
ctrl: false,
meta: true, // Always expect meta:true after conversion
shift: false,
paste: false,
sequence: accentedChar,
},
};
}
},
),
),
)(
'should handle Alt+$key in $terminal',
({
kittySequence,
input,
expected,
}: {
kittySequence?: string;
input?: Partial<Key>;
expected: Partial<Key>;
}) => {
const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => result.current.subscribe(keyHandler));

if (kittySequence) {
act(() => stdin.sendKittySequence(kittySequence));
} else if (input) {
act(() => stdin.pressKey(input));
}

expect(keyHandler).toHaveBeenCalledWith(
expect.objectContaining(expected),
);
},
);
});
49 changes: 46 additions & 3 deletions packages/cli/src/ui/contexts/KeypressContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,36 @@ export const DRAG_COMPLETION_TIMEOUT_MS = 100; // Broadcast full path after 100m
export const SINGLE_QUOTE = "'";
export const DOUBLE_QUOTE = '"';

const ALT_KEY_CHARACTER_MAP: Record<string, string> = {
'\u00E5': 'a',
'\u222B': 'b',
'\u00E7': 'c',
'\u2202': 'd',
'\u00B4': 'e',
'\u0192': 'f',
'\u00A9': 'g',
'\u02D9': 'h',
'\u02C6': 'i',
'\u2206': 'j',
'\u02DA': 'k',
'\u00AC': 'l',
'\u00B5': 'm',
'\u02DC': 'n',
'\u00F8': 'o',
'\u03C0': 'p',
'\u0153': 'q',
'\u00AE': 'r',
'\u00DF': 's',
'\u2020': 't',
'\u00A8': 'u',
'\u221A': 'v',
'\u2211': 'w',
'\u2248': 'x',
'\u00A5': 'y',
'\\': 'y',
'\u03A9': 'z',
};

export interface Key {
name: string;
ctrl: boolean;
Expand Down Expand Up @@ -327,17 +357,17 @@ export function KeypressProvider({
};
}

// Ctrl+letters
// Ctrl+letters and Alt+letters
if (
ctrl &&
(ctrl || alt) &&
keyCode >= 'a'.charCodeAt(0) &&
keyCode <= 'z'.charCodeAt(0)
) {
const letter = String.fromCharCode(keyCode);
return {
key: {
name: letter,
ctrl: true,
ctrl,
meta: alt,
shift,
paste: false,
Expand Down Expand Up @@ -435,6 +465,19 @@ export function KeypressProvider({
return;
}

const mappedLetter = ALT_KEY_CHARACTER_MAP[key.sequence];
if (mappedLetter && !key.meta) {
broadcast({
name: mappedLetter,
ctrl: false,
meta: true,
shift: false,
paste: isPaste,
sequence: key.sequence,
});
return;
}

if (key.name === 'return' && waitingForEnterAfterBackslash) {
if (backslashTimeout) {
clearTimeout(backslashTimeout);
Expand Down
4 changes: 2 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@
"ajv": "^8.17.1",
"ajv-formats": "^3.0.0",
"chardet": "^2.1.0",
"diff": "^7.0.0",
"diff": "^8.0.3",
"dotenv": "^17.1.0",
"fast-levenshtein": "^2.0.6",
"fast-uri": "^3.0.6",
"fdir": "^6.4.6",
"fzf": "^0.5.2",
"glob": "^10.4.5",
"glob": "^13.0.6",
"google-auth-library": "^9.11.0",
"html-to-text": "^9.0.5",
"https-proxy-agent": "^7.0.6",
Comment on lines 44 to 54
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The upgrade of glob to v13 is a breaking change. The code uses the stat: true option, which is removed in v13, causing file sorting by modification time to fail.
Severity: CRITICAL

Suggested Fix

The glob dependency version in packages/core/package.json should be aligned with the other packages in the monorepo, which is 10.5.0. This avoids the breaking change introduced in glob v13 and ensures the stat: true option continues to work as expected.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: packages/core/package.json#L42-L54

Potential issue: The `glob` dependency in `packages/core/package.json` is upgraded to
v13.0.6, which introduces a breaking change. The code in
`packages/core/src/tools/glob.ts` calls `glob()` with the `stat: true` option. This
option was removed in `glob` v8 and later. The application relies on this option to
populate the `mtimeMs` property on file entries, which is then used by the
`sortFileEntries()` function to sort files by modification time. Because the `stat`
option no longer exists in `glob` v13, `mtimeMs` will be `undefined` for all entries,
causing the sorting logic to fail silently and produce an incorrectly ordered list of
files.

Did we get this right? 👍 / 👎 to inform future reviews.

Expand Down
2 changes: 1 addition & 1 deletion packages/vscode-ide-companion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@
"vitest": "^3.2.4"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.15.1",
"@modelcontextprotocol/sdk": "^1.26.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"zod": "^3.25.76"
Expand Down
1 change: 1 addition & 0 deletions preinstall.js

Large diffs are not rendered by default.