chore(kimi-code): upgrade pi-tui to 0.78.1 and adapt native helpers

Bump @earendil-works/pi-tui from ^0.74.0 to ^0.78.1. pi-tui 0.75.5 replaced its koffi-based Windows VT input with a bundled native helper, and 0.76.0 added a darwin native helper for Terminal.app Shift+Enter.

- SEA build: teach native-deps to collect pi-tui's per-target .node files, drop the koffi registry, and add a native-file-only collect mode so only package.json + the target .node ship (28 -> 2 files).

- Redirect pi-tui's absolute-path native require() into the native-asset cache through the Module._load hook, and extend the native smoke test to actually load the helper.

- npm package: ship pi-tui's native/ directory so macOS Terminal.app Shift+Enter and Windows Shift+Tab keep working for npm installs.
This commit is contained in:
liruifengv 2026-06-29 19:38:00 +08:00
parent 3e98e709b3
commit 0ec5bd62c0
14 changed files with 249 additions and 77 deletions

View file

@ -0,0 +1,70 @@
#include <CoreGraphics/CoreGraphics.h>
#include <dlfcn.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#define NAPI_AUTO_LENGTH ((size_t)-1)
typedef void* napi_env;
typedef void* napi_value;
typedef void* napi_callback_info;
typedef napi_value (*napi_callback)(napi_env, napi_callback_info);
typedef int (*napi_create_function_fn)(napi_env, const char*, size_t, napi_callback, void*, napi_value*);
typedef int (*napi_set_named_property_fn)(napi_env, napi_value, const char*, napi_value);
typedef int (*napi_get_boolean_fn)(napi_env, bool, napi_value*);
typedef int (*napi_get_cb_info_fn)(napi_env, napi_callback_info, size_t*, napi_value*, napi_value*, void**);
typedef int (*napi_get_value_string_utf8_fn)(napi_env, napi_value, char*, size_t, size_t*);
static void* node_symbol(const char* name) {
return dlsym(RTLD_DEFAULT, name);
}
static CGEventFlags modifier_mask_for_name(const char* name) {
if (strcmp(name, "shift") == 0) return kCGEventFlagMaskShift;
if (strcmp(name, "command") == 0) return kCGEventFlagMaskCommand;
if (strcmp(name, "control") == 0) return kCGEventFlagMaskControl;
if (strcmp(name, "option") == 0) return kCGEventFlagMaskAlternate;
return 0;
}
static napi_value is_modifier_pressed(napi_env env, napi_callback_info info) {
napi_get_cb_info_fn napi_get_cb_info = (napi_get_cb_info_fn)node_symbol("napi_get_cb_info");
napi_get_value_string_utf8_fn napi_get_value_string_utf8 = (napi_get_value_string_utf8_fn)node_symbol("napi_get_value_string_utf8");
napi_get_boolean_fn napi_get_boolean = (napi_get_boolean_fn)node_symbol("napi_get_boolean");
bool pressed = false;
if (napi_get_cb_info && napi_get_value_string_utf8) {
size_t argc = 1;
napi_value args[1] = {0};
if (napi_get_cb_info(env, info, &argc, args, 0, 0) == 0 && argc >= 1 && args[0]) {
char name[16] = {0};
size_t copied = 0;
if (napi_get_value_string_utf8(env, args[0], name, sizeof(name), &copied) == 0) {
CGEventFlags mask = modifier_mask_for_name(name);
if (mask != 0) {
CGEventFlags flags = CGEventSourceFlagsState(kCGEventSourceStateCombinedSessionState);
pressed = (flags & mask) != 0;
}
}
}
}
napi_value result = 0;
if (napi_get_boolean) napi_get_boolean(env, pressed, &result);
return result;
}
__attribute__((visibility("default"))) napi_value napi_register_module_v1(napi_env env, napi_value exports) {
napi_create_function_fn napi_create_function = (napi_create_function_fn)node_symbol("napi_create_function");
napi_set_named_property_fn napi_set_named_property = (napi_set_named_property_fn)node_symbol("napi_set_named_property");
napi_value fn = 0;
if (napi_create_function &&
napi_set_named_property &&
napi_create_function(env, "isModifierPressed", NAPI_AUTO_LENGTH, is_modifier_pressed, 0, &fn) == 0) {
napi_set_named_property(env, exports, "isModifierPressed", fn);
}
return exports;
}

View file

@ -0,0 +1,53 @@
#include <windows.h>
#ifndef ENABLE_VIRTUAL_TERMINAL_INPUT
#define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200
#endif
#define NAPI_AUTO_LENGTH ((unsigned long long)-1)
typedef void* napi_env;
typedef void* napi_value;
typedef void* napi_callback_info;
typedef napi_value (__cdecl *napi_callback)(napi_env, napi_callback_info);
typedef int (__cdecl *napi_create_function_fn)(napi_env, const char*, unsigned long long, napi_callback, void*, napi_value*);
typedef int (__cdecl *napi_set_named_property_fn)(napi_env, napi_value, const char*, napi_value);
typedef int (__cdecl *napi_get_boolean_fn)(napi_env, int, napi_value*);
static void* node_symbol(const char* name) {
HMODULE module = GetModuleHandleA(0);
void* proc = module ? (void*)GetProcAddress(module, name) : 0;
if (proc) return proc;
module = GetModuleHandleA("node.dll");
return module ? (void*)GetProcAddress(module, name) : 0;
}
static napi_value __cdecl enable_virtual_terminal_input(napi_env env, napi_callback_info info) {
(void)info;
HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);
DWORD mode = 0;
int enabled = handle != INVALID_HANDLE_VALUE &&
GetConsoleMode(handle, &mode) &&
SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_INPUT);
napi_get_boolean_fn napi_get_boolean = (napi_get_boolean_fn)node_symbol("napi_get_boolean");
napi_value result = 0;
if (napi_get_boolean) napi_get_boolean(env, enabled, &result);
return result;
}
__declspec(dllexport) napi_value __cdecl napi_register_module_v1(napi_env env, napi_value exports) {
napi_create_function_fn napi_create_function = (napi_create_function_fn)node_symbol("napi_create_function");
napi_set_named_property_fn napi_set_named_property = (napi_set_named_property_fn)node_symbol("napi_set_named_property");
napi_value fn = 0;
if (napi_create_function &&
napi_set_named_property &&
napi_create_function(env, "enableVirtualTerminalInput", NAPI_AUTO_LENGTH, enable_virtual_terminal_input, 0, &fn) == 0) {
napi_set_named_property(env, exports, "enableVirtualTerminalInput", fn);
}
return exports;
}

View file

@ -28,6 +28,7 @@
"files": [
"dist",
"dist-web",
"native",
"scripts/postinstall.mjs",
"scripts/postinstall",
"README.md"
@ -75,11 +76,10 @@
},
"optionalDependencies": {
"@mariozechner/clipboard": "^0.3.9",
"koffi": "^2.16.0",
"node-pty": "^1.1.0"
},
"devDependencies": {
"@earendil-works/pi-tui": "^0.74.0",
"@earendil-works/pi-tui": "^0.78.1",
"@moonshot-ai/acp-adapter": "workspace:^",
"@moonshot-ai/kimi-code-oauth": "workspace:^",
"@moonshot-ai/kimi-code-sdk": "workspace:^",

View file

@ -17,9 +17,7 @@ export const NATIVE_TARGETS = Object.freeze(
SUPPORTED_TARGETS.map((t) => {
const deps = resolveTargetDeps(t);
const clipboardTarget = deps.find((d) => d.id === 'clipboard-target')?.resolvedName;
const koffiNativeFile = deps.find((d) => d.id === 'koffi')?.nativeFileRelatives?.[0];
const koffiTriplet = koffiNativeFile?.match(/koffi\/([^/]+)\/koffi\.node$/)?.[1] ?? null;
return [t, { clipboardPackage: clipboardTarget, koffiTriplet }];
return [t, { clipboardPackage: clipboardTarget }];
}),
),
);
@ -161,16 +159,19 @@ async function collectPackageFiles({
packageName,
packageRoot,
includeNativeFiles,
includeEntryJs = true,
nativeFileRelatives = [],
}) {
const packageJsonPath = join(packageRoot, 'package.json');
const packageJson = await readJson(packageJsonPath);
const selected = new Set([packageJsonPath]);
const entry = resolvePackageEntry(packageRoot, packageJson);
if (entry !== null) {
selected.add(entry);
await addRuntimeDependencyFiles(packageRoot, entry, selected);
if (includeEntryJs) {
const entry = resolvePackageEntry(packageRoot, packageJson);
if (entry !== null) {
selected.add(entry);
await addRuntimeDependencyFiles(packageRoot, entry, selected);
}
}
for (const nativeFileRelative of nativeFileRelatives) {
@ -250,6 +251,7 @@ export async function collectNativeAssets({ appRoot, target }) {
packageName: dep.resolvedName,
packageRoot,
includeNativeFiles: dep.collect === 'native-files',
includeEntryJs: dep.collect !== 'native-file-only',
nativeFileRelatives: dep.nativeFileRelatives,
});
const result = await packageManifestEntries({

View file

@ -23,7 +23,7 @@ const optionalRuntimeRequires = new Set([
'utf-8-validate',
]);
const optionalRelativeRuntimeRequires = new Set(['./crypto/build/Release/sshcrypto.node']);
const handledNativeRuntimeRequires = new Set(['koffi']);
const handledNativeRuntimeRequires = new Set();
function isAllowedSpecifier(specifier) {
if (builtins.has(specifier) || specifier.startsWith('node:')) return true;

View file

@ -27,13 +27,16 @@ const clipboardSubpackageByTarget = Object.freeze({
'win32-x64': '@mariozechner/clipboard-win32-x64-msvc',
});
const koffiTripletByTarget = Object.freeze({
'darwin-arm64': 'darwin_arm64',
'darwin-x64': 'darwin_x64',
'linux-arm64': 'linux_arm64',
'linux-x64': 'linux_x64',
'win32-arm64': 'win32_arm64',
'win32-x64': 'win32_x64',
// pi-tui ships platform-specific native helpers (no Linux build):
// - darwin: Shift-modifier detection for Terminal.app Shift+Enter
// - win32: enable ENABLE_VIRTUAL_TERMINAL_INPUT so Shift+Tab is distinguishable
const piTuiNativeFileByTarget = Object.freeze({
'darwin-arm64': ['native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node'],
'darwin-x64': ['native/darwin/prebuilds/darwin-x64/darwin-modifiers.node'],
'linux-arm64': [],
'linux-x64': [],
'win32-arm64': ['native/win32/prebuilds/win32-arm64/win32-console-mode.node'],
'win32-x64': ['native/win32/prebuilds/win32-x64/win32-console-mode.node'],
});
export function isSupportedTarget(target) {
@ -45,13 +48,15 @@ export function isSupportedTarget(target) {
* @property {string} id stable internal id used for parent refs
* @property {(target: string) => string} name
* npm package name (may depend on target)
* @property {'js-only'|'native-files'|'js-and-native-file'|'virtual'} collect
* @property {'js-only'|'native-files'|'js-and-native-file'|'native-file-only'|'virtual'} collect
* @property {string|null} parent
* id of another registered dep this nests under (for pnpm),
* or null for top-level (resolvable from app root)
* @property {(target: string) => string[]} [nativeFileRelatives]
* explicit list of .node files relative to package root
* (used by 'js-and-native-file'; native-files mode auto-scans *.node)
* (used by 'js-and-native-file' and 'native-file-only';
* native-files mode auto-scans *.node). 'native-file-only' collects
* package.json + these .node files but skips the package entry JS.
*/
/** @type {readonly NativeDepDescriptor[]} */
@ -71,17 +76,13 @@ export const nativeDeps = Object.freeze([
{
id: 'pi-tui',
name: () => '@earendil-works/pi-tui',
// pi-tui is bundled into main.cjs at build time — we don't collect it as
// a native dep, only register it so koffi can declare it as parent.
collect: 'virtual',
// pi-tui's JS is bundled into main.cjs, so only the platform-specific
// native helper (.node under native/) ships alongside the binary — its
// dist/ JS is intentionally NOT collected (it stays in the bundle). This
// keeps the SEA native-asset payload small. Linux has no native helper.
collect: 'native-file-only',
parent: null,
},
{
id: 'koffi',
name: () => 'koffi',
collect: 'js-and-native-file',
parent: 'pi-tui',
nativeFileRelatives: (target) => [`build/koffi/${koffiTripletByTarget[target]}/koffi.node`],
nativeFileRelatives: (target) => piTuiNativeFileByTarget[target] ?? [],
},
]);

View file

@ -1,6 +1,8 @@
import { existsSync } from 'node:fs';
import { createRequire } from 'node:module';
import { join } from 'node:path';
import { loadNativePackage } from './native-require';
import { getNativePackageRoot } from './native-assets';
type ModuleLoad = (request: string, parent: unknown, isMain: boolean) => unknown;
@ -10,7 +12,16 @@ interface ModuleWithLoad {
const nodeRequire = createRequire(import.meta.url);
let installed = false;
let loadingNativePackage = false;
// pi-tui loads its platform-specific native helpers via an absolute-path
// require() computed from import.meta.url / process.execPath
// (see pi-tui dist/terminal.js and dist/native-modifiers.js). In a SEA binary
// those .node files live in the native-asset cache, so redirect any absolute
// require of a pi-tui native helper to the cached copy.
//
// Path shape: native/<darwin|win32>/prebuilds/<arch>/<file>.node — note the
// two path segments after "prebuilds", so ".+" (not "[^/]+") is required.
const PI_TUI_NATIVE_PATTERN = /native[\\/](?:win32|darwin)[\\/]prebuilds[\\/].+\.node$/;
export function installNativeModuleHook(): void {
if (installed) return;
@ -26,13 +37,18 @@ export function installNativeModuleHook(): void {
parent: unknown,
isMain: boolean,
): unknown {
if (request === 'koffi' && !loadingNativePackage) {
loadingNativePackage = true;
try {
const pkg = loadNativePackage<unknown>('koffi');
if (pkg !== null) return pkg;
} finally {
loadingNativePackage = false;
if (
typeof request === 'string' &&
PI_TUI_NATIVE_PATTERN.test(request) &&
!existsSync(request)
) {
const pkgRoot = getNativePackageRoot('@earendil-works/pi-tui');
if (pkgRoot !== null) {
const match = request.match(PI_TUI_NATIVE_PATTERN);
if (match !== null) {
const redirected = join(pkgRoot, match[0]);
return originalLoad.call(this, redirected, parent, isMain);
}
}
}
return originalLoad.call(this, request, parent, isMain);

View file

@ -1,6 +1,38 @@
import { createRequire } from 'node:module';
import { dirname, join } from 'node:path';
import { getEmbeddedNativeAssetManifest, getNativePackageRoot } from './native-assets';
const smokePackages = ['@mariozechner/clipboard', 'koffi'];
const smokePackages = ['@mariozechner/clipboard', '@earendil-works/pi-tui'];
// Verify pi-tui's native helper can actually be loaded through the module hook.
// pi-tui computes native helper paths from process.execPath and require()s them;
// those paths do not exist next to the SEA binary, so this only succeeds when
// installNativeModuleHook() redirects the require into the native-asset cache.
function smokePiTuiNativeLoad(): void {
const platform = process.platform;
const arch = process.arch;
let rel: string | undefined;
if (platform === 'darwin' && (arch === 'x64' || arch === 'arm64')) {
rel = join('native', 'darwin', 'prebuilds', `darwin-${arch}`, 'darwin-modifiers.node');
} else if (platform === 'win32' && (arch === 'x64' || arch === 'arm64')) {
rel = join('native', 'win32', 'prebuilds', `win32-${arch}`, 'win32-console-mode.node');
}
if (rel === undefined) return; // Linux: no native helper, nothing to load.
const req = createRequire(import.meta.url);
const bogusPath = join(dirname(process.execPath), rel);
const helper = req(bogusPath) as {
isModifierPressed?: unknown;
enableVirtualTerminalInput?: unknown;
};
const ok =
typeof helper.isModifierPressed === 'function' ||
typeof helper.enableVirtualTerminalInput === 'function';
if (!ok) {
throw new Error(`pi-tui native helper loaded but exports are unexpected: ${rel}`);
}
}
export function runNativeAssetSmokeIfRequested(): boolean {
if (process.env['KIMI_CODE_NATIVE_ASSET_SMOKE'] !== '1') return false;
@ -16,6 +48,7 @@ export function runNativeAssetSmokeIfRequested(): boolean {
throw new Error(`Native package is not available: ${packageName}`);
}
}
smokePiTuiNativeLoad();
process.stdout.write(`Native asset smoke passed: ${manifest.target}\n`);
process.exit(0);
} catch (error) {

View file

@ -41,7 +41,7 @@ describe('resolveTargetDeps', () => {
const names = deps.map((d) => d.resolvedName);
expect(names).toContain('@mariozechner/clipboard');
expect(names).toContain('@mariozechner/clipboard-darwin-arm64');
expect(names).toContain('koffi');
expect(names).toContain('@earendil-works/pi-tui');
});
it('picks the right clipboard subpackage per target', () => {
@ -56,13 +56,23 @@ describe('resolveTargetDeps', () => {
).toContain('@mariozechner/clipboard-win32-arm64-msvc');
});
it('encodes koffi native file path with target triplet', () => {
const linuxKoffi = resolveTargetDeps('linux-arm64').find((d) => d.resolvedName === 'koffi');
expect(linuxKoffi?.nativeFileRelatives).toEqual(['build/koffi/linux_arm64/koffi.node']);
const macKoffi = resolveTargetDeps('darwin-x64').find((d) => d.resolvedName === 'koffi');
expect(macKoffi?.nativeFileRelatives).toEqual(['build/koffi/darwin_x64/koffi.node']);
const winArmKoffi = resolveTargetDeps('win32-arm64').find((d) => d.resolvedName === 'koffi');
expect(winArmKoffi?.nativeFileRelatives).toEqual(['build/koffi/win32_arm64/koffi.node']);
it('encodes pi-tui native file path per target', () => {
const linuxPiTui = resolveTargetDeps('linux-arm64').find(
(d) => d.resolvedName === '@earendil-works/pi-tui',
);
expect(linuxPiTui?.nativeFileRelatives).toEqual([]);
const macPiTui = resolveTargetDeps('darwin-x64').find(
(d) => d.resolvedName === '@earendil-works/pi-tui',
);
expect(macPiTui?.nativeFileRelatives).toEqual([
'native/darwin/prebuilds/darwin-x64/darwin-modifiers.node',
]);
const winArmPiTui = resolveTargetDeps('win32-arm64').find(
(d) => d.resolvedName === '@earendil-works/pi-tui',
);
expect(winArmPiTui?.nativeFileRelatives).toEqual([
'native/win32/prebuilds/win32-arm64/win32-console-mode.node',
]);
});
it('throws on unsupported target', () => {
@ -82,9 +92,9 @@ describe('nativeDeps registry shape', () => {
expect(target?.parent).toBe('clipboard-host');
});
it('has koffi (collect=js-and-native-file, parent=pi-tui)', () => {
const koffi = nativeDeps.find((d) => d.id === 'koffi');
expect(koffi?.collect).toBe('js-and-native-file');
expect(koffi?.parent).toBe('pi-tui');
it('has pi-tui (collect=native-file-only, no parent)', () => {
const piTui = nativeDeps.find((d) => d.id === 'pi-tui');
expect(piTui?.collect).toBe('native-file-only');
expect(piTui?.parent).toBe(null);
});
});

39
pnpm-lock.yaml generated
View file

@ -73,8 +73,8 @@ importers:
apps/kimi-code:
devDependencies:
'@earendil-works/pi-tui':
specifier: ^0.74.0
version: 0.74.0
specifier: ^0.78.1
version: 0.78.1
'@moonshot-ai/acp-adapter':
specifier: workspace:^
version: link:../../packages/acp-adapter
@ -142,9 +142,6 @@ importers:
'@mariozechner/clipboard':
specifier: ^0.3.9
version: 0.3.9
koffi:
specifier: ^2.16.0
version: 2.16.0
node-pty:
specifier: ^1.1.0
version: 1.1.0
@ -984,9 +981,9 @@ packages:
search-insights:
optional: true
'@earendil-works/pi-tui@0.74.0':
resolution: {integrity: sha512-1aIfXZp7D/z+1VlZX8BZcs6pgO8rjmil7kwyhctNDsWvce3Yfl8GVgu4eq+I0Mjhr8Cj+ipBiv9CLIzdoyCOIQ==}
engines: {node: '>=20.0.0'}
'@earendil-works/pi-tui@0.78.1':
resolution: {integrity: sha512-07GVQo/38a0yvIPlWDr3RJn1B8gk3ZuIX9h2oIQ+Biyu3JN0KppWmgWHfaWRydQgse5JtC++KDw5MWaIRnV0mw==}
engines: {node: '>=22.19.0'}
'@emnapi/core@1.10.0':
resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==}
@ -2699,9 +2696,6 @@ packages:
'@types/mdurl@2.0.0':
resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
'@types/mime-types@2.1.4':
resolution: {integrity: sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==}
'@types/ms@2.1.0':
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
@ -3932,6 +3926,10 @@ packages:
resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==}
engines: {node: '>=18'}
get-east-asian-width@1.6.0:
resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==}
engines: {node: '>=18'}
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
@ -4383,9 +4381,6 @@ packages:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
engines: {node: '>=0.10.0'}
koffi@2.16.0:
resolution: {integrity: sha512-h/2NJueOKWd0YYycEOWDspomizgNfuOKf/V7ZE2fytvuRtHoY9Tb+y4x6GJ6pFqaVndWn9dLK+sCI14eWtu5rA==}
layout-base@1.0.2:
resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==}
@ -6867,15 +6862,10 @@ snapshots:
transitivePeerDependencies:
- '@algolia/client-search'
'@earendil-works/pi-tui@0.74.0':
'@earendil-works/pi-tui@0.78.1':
dependencies:
'@types/mime-types': 2.1.4
chalk: 5.6.2
get-east-asian-width: 1.5.0
get-east-asian-width: 1.6.0
marked: 15.0.12
mime-types: 3.0.2
optionalDependencies:
koffi: 2.16.0
'@emnapi/core@1.10.0':
dependencies:
@ -8189,8 +8179,6 @@ snapshots:
'@types/mdurl@2.0.0': {}
'@types/mime-types@2.1.4': {}
'@types/ms@2.1.0': {}
'@types/node@12.20.55': {}
@ -9621,6 +9609,8 @@ snapshots:
get-east-asian-width@1.5.0: {}
get-east-asian-width@1.6.0: {}
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@ -10101,9 +10091,6 @@ snapshots:
kind-of@6.0.3: {}
koffi@2.16.0:
optional: true
layout-base@1.0.2: {}
layout-base@2.0.1: {}