summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/tools/shell.md9
-rw-r--r--package-lock.json713
-rw-r--r--package.json22
-rw-r--r--packages/cli/package.json6
-rw-r--r--packages/cli/src/ui/components/messages/ToolGroupMessage.tsx2
-rw-r--r--packages/cli/src/ui/hooks/shellCommandProcessor.test.ts2
-rw-r--r--packages/cli/src/ui/hooks/shellCommandProcessor.ts78
-rw-r--r--packages/cli/src/ui/hooks/useLoadingIndicator.test.ts58
-rw-r--r--packages/cli/src/utils/sandbox.ts135
-rw-r--r--packages/core/package.json2
-rw-r--r--packages/core/src/core/prompts.ts12
-rw-r--r--packages/core/src/tools/edit.ts4
-rw-r--r--packages/core/src/tools/shell.ts108
-rw-r--r--packages/core/src/utils/editor.test.ts12
-rw-r--r--packages/core/src/utils/editor.ts14
-rw-r--r--scripts/build.js54
-rwxr-xr-xscripts/build.sh30
-rw-r--r--scripts/build_package.js37
-rwxr-xr-xscripts/build_package.sh33
-rw-r--r--scripts/build_sandbox.js125
-rwxr-xr-xscripts/build_sandbox.sh102
-rw-r--r--scripts/clean.js32
-rwxr-xr-xscripts/clean.sh21
-rw-r--r--scripts/copy_bundle_assets.js48
-rwxr-xr-xscripts/copy_bundle_assets.sh13
-rw-r--r--scripts/generate-git-commit-info.js61
-rwxr-xr-xscripts/generate-git-commit-info.sh44
-rw-r--r--scripts/publish-sandbox.js55
-rwxr-xr-xscripts/publish-sandbox.sh41
-rw-r--r--scripts/sandbox.js123
-rwxr-xr-xscripts/sandbox.sh103
-rw-r--r--scripts/sandbox_command.js126
-rwxr-xr-xscripts/sandbox_command.sh122
-rw-r--r--scripts/setup-dev.js42
-rwxr-xr-xscripts/setup-dev.sh34
-rw-r--r--scripts/start.js61
-rwxr-xr-xscripts/start.sh37
-rw-r--r--tmperrors.txt55
38 files changed, 1723 insertions, 853 deletions
diff --git a/docs/tools/shell.md b/docs/tools/shell.md
index d8055deb..e60fb9a4 100644
--- a/docs/tools/shell.md
+++ b/docs/tools/shell.md
@@ -4,9 +4,9 @@ This document provides details on the shell tool.
## `run_shell_command`
-- **Purpose:** Executes a given shell command using `bash -c <command>`. This tool is essential for interacting with the underlying operating system, running scripts, or performing command-line operations.
+- **Purpose:** Executes a given shell command. On Windows, this will be executed with `cmd.exe /c`. On other platforms, it will be executed with `bash -c`. This tool is essential for interacting with the underlying operating system, running scripts, or performing command-line operations.
- **Arguments:**
- - `command` (string, required): The exact bash command to execute.
+ - `command` (string, required): The exact shell command to execute.
- `description` (string, optional): A brief description of the command's purpose, which will be shown to the user.
- `directory` (string, optional): The directory (relative to the project root) in which to execute the command. If not provided, the command runs in the project root.
- **Behavior:**
@@ -22,10 +22,14 @@ This document provides details on the shell tool.
- `Signal`: The signal number if the command was terminated by a signal.
- `Background PIDs`: A list of PIDs for any background processes started.
- **Examples:**
+
- Listing files in the current directory:
+
```
+
run_shell_command(command="ls -la")
```
+
- Running a script in a specific directory:
```
run_shell_command(command="./my_script.sh", directory="scripts", description="Run my custom script")
@@ -34,6 +38,7 @@ This document provides details on the shell tool.
```
run_shell_command(command="npm run dev &", description="Start development server in background")
```
+
- **Important Notes:**
- **Security:** Be cautious when executing commands, especially those constructed from user input, to prevent security vulnerabilities.
- **Interactive Commands:** Avoid commands that require interactive user input, as this can cause the tool to hang. Use non-interactive flags if available (e.g., `npm init -y`).
diff --git a/package-lock.json b/package-lock.json
index dbc8cfdd..becc665a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,13 +18,15 @@
"@types/mime-types": "^2.1.4",
"@types/minimatch": "^5.1.2",
"@vitest/coverage-v8": "^3.1.1",
- "esbuild": "^0.25.4",
+ "cross-env": "^7.0.3",
+ "esbuild": "^0.23.0",
"eslint": "^9.24.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-license-header": "^0.8.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
+ "glob": "^10.4.2",
"globals": "^16.0.0",
"json": "^11.0.0",
"lodash": "^4.17.21",
@@ -293,9 +295,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
- "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
+ "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
"cpu": [
"ppc64"
],
@@ -310,9 +312,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
- "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
+ "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
"cpu": [
"arm"
],
@@ -327,9 +329,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
- "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
+ "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
"cpu": [
"arm64"
],
@@ -344,9 +346,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
- "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
+ "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
"cpu": [
"x64"
],
@@ -361,9 +363,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
- "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
+ "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
"cpu": [
"arm64"
],
@@ -378,9 +380,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
- "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
+ "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
"cpu": [
"x64"
],
@@ -395,9 +397,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
- "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
+ "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
"cpu": [
"arm64"
],
@@ -412,9 +414,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
- "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
+ "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
"cpu": [
"x64"
],
@@ -429,9 +431,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
- "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
+ "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
"cpu": [
"arm"
],
@@ -446,9 +448,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
- "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
+ "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
"cpu": [
"arm64"
],
@@ -463,9 +465,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
- "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
+ "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
"cpu": [
"ia32"
],
@@ -480,9 +482,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
- "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
+ "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
"cpu": [
"loong64"
],
@@ -497,9 +499,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
- "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
+ "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
"cpu": [
"mips64el"
],
@@ -514,9 +516,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
- "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
+ "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
"cpu": [
"ppc64"
],
@@ -531,9 +533,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
- "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
+ "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
"cpu": [
"riscv64"
],
@@ -548,9 +550,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
- "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
+ "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
"cpu": [
"s390x"
],
@@ -565,9 +567,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
- "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
+ "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
"cpu": [
"x64"
],
@@ -594,14 +596,15 @@
"os": [
"netbsd"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
- "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
+ "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
"cpu": [
"x64"
],
@@ -616,9 +619,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
- "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
+ "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
"cpu": [
"arm64"
],
@@ -633,9 +636,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
- "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
+ "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
"cpu": [
"x64"
],
@@ -650,9 +653,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
- "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
+ "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
"cpu": [
"x64"
],
@@ -667,9 +670,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
- "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
+ "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
"cpu": [
"arm64"
],
@@ -684,9 +687,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
- "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
+ "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
"cpu": [
"ia32"
],
@@ -701,9 +704,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
- "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
+ "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
"cpu": [
"x64"
],
@@ -2131,6 +2134,13 @@
"@types/deep-eql": "*"
}
},
+ "node_modules/@types/command-exists": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@types/command-exists/-/command-exists-1.2.3.tgz",
+ "integrity": "sha512-PpbaE2XWLaWYboXD6k70TcXO/OdOyyRFq5TVpmlUELNxdkkmXU9fkImNosmXU1DtsNrqdUgWd/nJQYXgwmtdXQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/deep-eql": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
@@ -3552,6 +3562,12 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
+ "node_modules/command-exists": {
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz",
+ "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==",
+ "license": "MIT"
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -3620,6 +3636,25 @@
"node": ">= 0.10"
}
},
+ "node_modules/cross-env": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
+ "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.1"
+ },
+ "bin": {
+ "cross-env": "src/bin/cross-env.js",
+ "cross-env-shell": "src/bin/cross-env-shell.js"
+ },
+ "engines": {
+ "node": ">=10.14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -4247,9 +4282,9 @@
]
},
"node_modules/esbuild": {
- "version": "0.25.5",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
- "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
+ "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -4260,31 +4295,30 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.5",
- "@esbuild/android-arm": "0.25.5",
- "@esbuild/android-arm64": "0.25.5",
- "@esbuild/android-x64": "0.25.5",
- "@esbuild/darwin-arm64": "0.25.5",
- "@esbuild/darwin-x64": "0.25.5",
- "@esbuild/freebsd-arm64": "0.25.5",
- "@esbuild/freebsd-x64": "0.25.5",
- "@esbuild/linux-arm": "0.25.5",
- "@esbuild/linux-arm64": "0.25.5",
- "@esbuild/linux-ia32": "0.25.5",
- "@esbuild/linux-loong64": "0.25.5",
- "@esbuild/linux-mips64el": "0.25.5",
- "@esbuild/linux-ppc64": "0.25.5",
- "@esbuild/linux-riscv64": "0.25.5",
- "@esbuild/linux-s390x": "0.25.5",
- "@esbuild/linux-x64": "0.25.5",
- "@esbuild/netbsd-arm64": "0.25.5",
- "@esbuild/netbsd-x64": "0.25.5",
- "@esbuild/openbsd-arm64": "0.25.5",
- "@esbuild/openbsd-x64": "0.25.5",
- "@esbuild/sunos-x64": "0.25.5",
- "@esbuild/win32-arm64": "0.25.5",
- "@esbuild/win32-ia32": "0.25.5",
- "@esbuild/win32-x64": "0.25.5"
+ "@esbuild/aix-ppc64": "0.23.1",
+ "@esbuild/android-arm": "0.23.1",
+ "@esbuild/android-arm64": "0.23.1",
+ "@esbuild/android-x64": "0.23.1",
+ "@esbuild/darwin-arm64": "0.23.1",
+ "@esbuild/darwin-x64": "0.23.1",
+ "@esbuild/freebsd-arm64": "0.23.1",
+ "@esbuild/freebsd-x64": "0.23.1",
+ "@esbuild/linux-arm": "0.23.1",
+ "@esbuild/linux-arm64": "0.23.1",
+ "@esbuild/linux-ia32": "0.23.1",
+ "@esbuild/linux-loong64": "0.23.1",
+ "@esbuild/linux-mips64el": "0.23.1",
+ "@esbuild/linux-ppc64": "0.23.1",
+ "@esbuild/linux-riscv64": "0.23.1",
+ "@esbuild/linux-s390x": "0.23.1",
+ "@esbuild/linux-x64": "0.23.1",
+ "@esbuild/netbsd-x64": "0.23.1",
+ "@esbuild/openbsd-arm64": "0.23.1",
+ "@esbuild/openbsd-x64": "0.23.1",
+ "@esbuild/sunos-x64": "0.23.1",
+ "@esbuild/win32-arm64": "0.23.1",
+ "@esbuild/win32-ia32": "0.23.1",
+ "@esbuild/win32-x64": "0.23.1"
}
},
"node_modules/escalade": {
@@ -9535,6 +9569,479 @@
"url": "https://opencollective.com/vitest"
}
},
+ "node_modules/vite/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
+ "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-arm": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
+ "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
+ "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
+ "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
+ "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
+ "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
+ "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
+ "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-arm": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
+ "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
+ "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
+ "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
+ "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
+ "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
+ "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
+ "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
+ "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
+ "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
+ "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
+ "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
+ "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
+ "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
+ "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
+ "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
+ "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/esbuild": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
+ "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.5",
+ "@esbuild/android-arm": "0.25.5",
+ "@esbuild/android-arm64": "0.25.5",
+ "@esbuild/android-x64": "0.25.5",
+ "@esbuild/darwin-arm64": "0.25.5",
+ "@esbuild/darwin-x64": "0.25.5",
+ "@esbuild/freebsd-arm64": "0.25.5",
+ "@esbuild/freebsd-x64": "0.25.5",
+ "@esbuild/linux-arm": "0.25.5",
+ "@esbuild/linux-arm64": "0.25.5",
+ "@esbuild/linux-ia32": "0.25.5",
+ "@esbuild/linux-loong64": "0.25.5",
+ "@esbuild/linux-mips64el": "0.25.5",
+ "@esbuild/linux-ppc64": "0.25.5",
+ "@esbuild/linux-riscv64": "0.25.5",
+ "@esbuild/linux-s390x": "0.25.5",
+ "@esbuild/linux-x64": "0.25.5",
+ "@esbuild/netbsd-arm64": "0.25.5",
+ "@esbuild/netbsd-x64": "0.25.5",
+ "@esbuild/openbsd-arm64": "0.25.5",
+ "@esbuild/openbsd-x64": "0.25.5",
+ "@esbuild/sunos-x64": "0.25.5",
+ "@esbuild/win32-arm64": "0.25.5",
+ "@esbuild/win32-ia32": "0.25.5",
+ "@esbuild/win32-x64": "0.25.5"
+ }
+ },
"node_modules/vite/node_modules/fdir": {
"version": "6.4.5",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz",
@@ -10164,7 +10671,8 @@
"name": "@gemini-cli/cli",
"version": "0.1.0",
"dependencies": {
- "@gemini-cli/core": "0.1.0",
+ "@gemini-cli/core": "file:../core",
+ "command-exists": "^1.2.9",
"diff": "^7.0.0",
"dotenv": "^16.4.7",
"highlight.js": "^11.11.1",
@@ -10190,6 +10698,7 @@
},
"devDependencies": {
"@testing-library/react": "^14.0.0",
+ "@types/command-exists": "^1.2.3",
"@types/diff": "^7.0.2",
"@types/dotenv": "^6.1.1",
"@types/node": "^20.11.24",
diff --git a/package.json b/package.json
index 1babc5d4..2bc3d71f 100644
--- a/package.json
+++ b/package.json
@@ -6,16 +6,16 @@
"packages/*"
],
"scripts": {
- "generate": "scripts/generate-git-commit-info.sh",
- "build": "scripts/build.sh",
- "build:sandbox": "scripts/build_sandbox.sh -s",
+ "generate": "node scripts/generate-git-commit-info.js",
+ "build": "node scripts/build.js",
+ "build:sandbox": "node scripts/build_sandbox.js",
"build:all": "npm run build && npm run build:sandbox",
- "clean": "scripts/clean.sh",
+ "clean": "node scripts/clean.js",
"prepare": "npm run bundle",
"test": "npm run test --workspaces",
"test:ci": "npm run test:ci --workspaces --if-present",
- "start": "NODE_ENV=development scripts/start.sh",
- "debug": "NODE_ENV=development DEBUG=1 scripts/start.sh",
+ "start": "node scripts/start.js",
+ "debug": "cross-env DEBUG=1 node scripts/start.js",
"lint:fix": "eslint . --fix",
"lint": "eslint . --ext .ts,.tsx",
"typecheck": "npm run typecheck --workspaces --if-present",
@@ -25,14 +25,14 @@
"auth:docker": "gcloud auth configure-docker us-west1-docker.pkg.dev",
"auth": "npm run auth:npm && npm run auth:docker",
"prerelease:dev": "npm run prerelease:version --workspaces && npm run prerelease:deps --workspaces",
- "bundle": "npm run generate && node esbuild.config.js && bash scripts/copy_bundle_assets.sh",
+ "bundle": "npm run generate && node esbuild.config.js && node scripts/copy_bundle_assets.js",
"build:cli": "npm run build --workspace packages/cli",
"build:core": "npm run build --workspace packages/core",
"build:packages": "npm run build:core && npm run build:cli",
- "build:docker": "scripts/build_sandbox.sh -s",
+ "build:docker": "node scripts/build_sandbox.js -s",
"tag:docker": "docker tag gemini-cli-sandbox ${SANDBOX_IMAGE_REGISTRY:?SANDBOX_IMAGE_REGISTRY not set}/${SANDBOX_IMAGE_NAME:?SANDBOX_IMAGE_NAME not set}:$npm_package_version",
"prepare:cli-packagejson": "node scripts/prepare-cli-packagejson.js",
- "publish:sandbox": "scripts/publish-sandbox.sh",
+ "publish:sandbox": "node scripts/publish-sandbox.js",
"publish:npm": "npm publish --workspaces ${NPM_PUBLISH_TAG:+--tag=$NPM_PUBLISH_TAG} ${NPM_DRY_RUN:+--dry-run}",
"publish:release": "npm run build:packages && npm run prepare:cli-packagejson && npm run build:docker && npm run tag:docker && npm run publish:sandbox && npm run publish:npm"
},
@@ -49,13 +49,15 @@
"@types/mime-types": "^2.1.4",
"@types/minimatch": "^5.1.2",
"@vitest/coverage-v8": "^3.1.1",
- "esbuild": "^0.25.4",
+ "cross-env": "^7.0.3",
+ "esbuild": "^0.23.0",
"eslint": "^9.24.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-license-header": "^0.8.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
+ "glob": "^10.4.2",
"globals": "^16.0.0",
"json": "^11.0.0",
"lodash": "^4.17.21",
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 0e50e7d5..03eca1c7 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -8,7 +8,7 @@
"gemini": "dist/index.js"
},
"scripts": {
- "build": "../../scripts/build_package.sh",
+ "build": "node ../../scripts/build_package.js",
"clean": "rm -rf dist",
"start": "node dist/index.js",
"debug": "node --inspect-brk dist/index.js",
@@ -29,7 +29,7 @@
"sandboxImageUri": "gemini-cli-sandbox"
},
"dependencies": {
- "@gemini-cli/core": "0.1.0",
+ "@gemini-cli/core": "file:../core",
"diff": "^7.0.0",
"dotenv": "^16.4.7",
"highlight.js": "^11.11.1",
@@ -48,6 +48,7 @@
"string-width": "^7.1.0",
"strip-ansi": "^7.1.0",
"strip-json-comments": "^3.1.1",
+ "command-exists": "^1.2.9",
"yargs": "^17.7.2"
},
"devDependencies": {
@@ -58,6 +59,7 @@
"@types/react": "^18.3.1",
"@types/shell-quote": "^1.7.5",
"@types/yargs": "^17.0.32",
+ "@types/command-exists": "^1.2.3",
"ink-testing-library": "^4.0.0",
"jsdom": "^26.1.0",
"typescript": "^5.3.3",
diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx
index 46fcecff..b01e5f9b 100644
--- a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx
+++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx
@@ -58,7 +58,7 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
{toolCalls.map((tool) => {
const isConfirming = toolAwaitingApproval?.callId === tool.callId;
return (
- <Box key={tool.callId} flexDirection="column">
+ <Box key={tool.callId} flexDirection="column" minHeight={1}>
<Box flexDirection="row" alignItems="center">
<ToolMessage
callId={tool.callId}
diff --git a/packages/cli/src/ui/hooks/shellCommandProcessor.test.ts b/packages/cli/src/ui/hooks/shellCommandProcessor.test.ts
index 0ec2bb60..129a5401 100644
--- a/packages/cli/src/ui/hooks/shellCommandProcessor.test.ts
+++ b/packages/cli/src/ui/hooks/shellCommandProcessor.test.ts
@@ -44,8 +44,10 @@ vi.mock('path', () => ({
vi.mock('os', () => ({
default: {
tmpdir: vi.fn(() => '/tmp'),
+ platform: vi.fn(() => 'linux'),
},
tmpdir: vi.fn(() => '/tmp'),
+ platform: vi.fn(() => 'linux'),
}));
// Configure the fs mock to use new vi.fn() instances created within the factory
diff --git a/packages/cli/src/ui/hooks/shellCommandProcessor.ts b/packages/cli/src/ui/hooks/shellCommandProcessor.ts
index 9d6ff03a..f7502f3f 100644
--- a/packages/cli/src/ui/hooks/shellCommandProcessor.ts
+++ b/packages/cli/src/ui/hooks/shellCommandProcessor.ts
@@ -43,13 +43,23 @@ export const useShellCommandProcessor = (
return false;
}
- // wrap command to write pwd to temporary file
- let commandToExecute = rawQuery.trim();
- const pwdFileName = `shell_pwd_${crypto.randomBytes(6).toString('hex')}.tmp`;
- const pwdFilePath = path.join(os.tmpdir(), pwdFileName);
- if (!commandToExecute.endsWith('&')) commandToExecute += ';';
- // note here we could also restore a previous pwd with `cd {cwd}; { ... }`
- commandToExecute = `{ ${commandToExecute} }; __code=$?; pwd >${pwdFilePath}; exit $__code`;
+ const isWindows = os.platform() === 'win32';
+ let commandToExecute: string;
+ let pwdFilePath: string | undefined;
+
+ if (isWindows) {
+ commandToExecute = rawQuery;
+ } else {
+ // wrap command to write pwd to temporary file
+ let command = rawQuery.trim();
+ const pwdFileName = `shell_pwd_${crypto
+ .randomBytes(6)
+ .toString('hex')}.tmp`;
+ pwdFilePath = path.join(os.tmpdir(), pwdFileName);
+ if (!command.endsWith('&')) command += ';';
+ // note here we could also restore a previous pwd with `cd {cwd}; { ... }`
+ commandToExecute = `{ ${command} }; __code=$?; pwd >${pwdFilePath}; exit $__code`;
+ }
const userMessageTimestamp = Date.now();
addItemToHistory(
@@ -101,7 +111,7 @@ export const useShellCommandProcessor = (
userMessageTimestamp,
);
}
- if (fs.existsSync(pwdFilePath)) {
+ if (pwdFilePath && fs.existsSync(pwdFilePath)) {
const pwd = fs.readFileSync(pwdFilePath, 'utf8').trim();
if (pwd !== targetDir) {
addItemToHistory(
@@ -118,11 +128,16 @@ export const useShellCommandProcessor = (
},
);
} else {
- const child = spawn('bash', ['-c', commandToExecute], {
- cwd: targetDir,
- stdio: ['ignore', 'pipe', 'pipe'],
- detached: true, // Important for process group killing
- });
+ const child = isWindows
+ ? spawn('cmd.exe', ['/c', commandToExecute], {
+ cwd: targetDir,
+ stdio: ['ignore', 'pipe', 'pipe'],
+ })
+ : spawn('bash', ['-c', commandToExecute], {
+ cwd: targetDir,
+ stdio: ['ignore', 'pipe', 'pipe'],
+ detached: true, // Important for process group killing
+ });
let exited = false;
let output = '';
@@ -155,24 +170,29 @@ export const useShellCommandProcessor = (
onDebugMessage(
`Aborting shell command (PID: ${child.pid}) due to signal.`,
);
- try {
- // attempt to SIGTERM process group (negative PID)
- // fall back to SIGKILL (to group) after 200ms
- process.kill(-child.pid, 'SIGTERM');
- await new Promise((resolve) => setTimeout(resolve, 200));
- if (child.pid && !exited) {
- process.kill(-child.pid, 'SIGKILL');
- }
- } catch (_e) {
- // if group kill fails, fall back to killing just the main process
+ if (os.platform() === 'win32') {
+ // For Windows, use taskkill to kill the process tree
+ spawn('taskkill', ['/pid', child.pid.toString(), '/f', '/t']);
+ } else {
try {
- if (child.pid) {
- child.kill('SIGKILL');
+ // attempt to SIGTERM process group (negative PID)
+ // fall back to SIGKILL (to group) after 200ms
+ process.kill(-child.pid, 'SIGTERM');
+ await new Promise((resolve) => setTimeout(resolve, 200));
+ if (child.pid && !exited) {
+ process.kill(-child.pid, 'SIGKILL');
}
} catch (_e) {
- console.error(
- `failed to kill shell process ${child.pid}: ${_e}`,
- );
+ // if group kill fails, fall back to killing just the main process
+ try {
+ if (child.pid) {
+ child.kill('SIGKILL');
+ }
+ } catch (_e) {
+ console.error(
+ `failed to kill shell process ${child.pid}: ${_e}`,
+ );
+ }
}
}
}
@@ -208,7 +228,7 @@ export const useShellCommandProcessor = (
userMessageTimestamp,
);
}
- if (fs.existsSync(pwdFilePath)) {
+ if (pwdFilePath && fs.existsSync(pwdFilePath)) {
const pwd = fs.readFileSync(pwdFilePath, 'utf8').trim();
if (pwd !== targetDir) {
addItemToHistory(
diff --git a/packages/cli/src/ui/hooks/useLoadingIndicator.test.ts b/packages/cli/src/ui/hooks/useLoadingIndicator.test.ts
index 92ae81a2..ec6732c7 100644
--- a/packages/cli/src/ui/hooks/useLoadingIndicator.test.ts
+++ b/packages/cli/src/ui/hooks/useLoadingIndicator.test.ts
@@ -5,7 +5,7 @@
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
-import { act, renderHook } from '@testing-library/react';
+import { renderHook, act } from '@testing-library/react';
import { useLoadingIndicator } from './useLoadingIndicator.js';
import { StreamingState } from '../types.js';
import {
@@ -32,7 +32,7 @@ describe('useLoadingIndicator', () => {
expect(result.current.currentLoadingPhrase).toBe(WITTY_LOADING_PHRASES[0]);
});
- it('should reflect values when Responding', () => {
+ it('should reflect values when Responding', async () => {
const { result } = renderHook(() =>
useLoadingIndicator(StreamingState.Responding),
);
@@ -42,29 +42,33 @@ describe('useLoadingIndicator', () => {
expect(WITTY_LOADING_PHRASES).toContain(
result.current.currentLoadingPhrase,
);
- const _initialPhrase = result.current.currentLoadingPhrase;
+ const initialPhrase = result.current.currentLoadingPhrase;
- act(() => {
- vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS);
+ await act(async () => {
+ await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS);
});
+
// Phrase should cycle if PHRASE_CHANGE_INTERVAL_MS has passed
+ expect(result.current.currentLoadingPhrase).not.toBe(initialPhrase);
expect(WITTY_LOADING_PHRASES).toContain(
result.current.currentLoadingPhrase,
);
});
- it('should show waiting phrase and retain elapsedTime when WaitingForConfirmation', () => {
+ it('should show waiting phrase and retain elapsedTime when WaitingForConfirmation', async () => {
const { result, rerender } = renderHook(
({ streamingState }) => useLoadingIndicator(streamingState),
{ initialProps: { streamingState: StreamingState.Responding } },
);
- act(() => {
- vi.advanceTimersByTime(60000);
+ await act(async () => {
+ await vi.advanceTimersByTimeAsync(60000);
});
expect(result.current.elapsedTime).toBe(60);
- rerender({ streamingState: StreamingState.WaitingForConfirmation });
+ act(() => {
+ rerender({ streamingState: StreamingState.WaitingForConfirmation });
+ });
expect(result.current.currentLoadingPhrase).toBe(
'Waiting for user confirmation...',
@@ -72,60 +76,66 @@ describe('useLoadingIndicator', () => {
expect(result.current.elapsedTime).toBe(60); // Elapsed time should be retained
// Timer should not advance further
- act(() => {
- vi.advanceTimersByTime(2000);
+ await act(async () => {
+ await vi.advanceTimersByTimeAsync(2000);
});
expect(result.current.elapsedTime).toBe(60);
});
- it('should reset elapsedTime and use a witty phrase when transitioning from WaitingForConfirmation to Responding', () => {
+ it('should reset elapsedTime and use a witty phrase when transitioning from WaitingForConfirmation to Responding', async () => {
const { result, rerender } = renderHook(
({ streamingState }) => useLoadingIndicator(streamingState),
{ initialProps: { streamingState: StreamingState.Responding } },
);
- act(() => {
- vi.advanceTimersByTime(5000); // 5s
+ await act(async () => {
+ await vi.advanceTimersByTimeAsync(5000); // 5s
});
expect(result.current.elapsedTime).toBe(5);
- rerender({ streamingState: StreamingState.WaitingForConfirmation });
+ act(() => {
+ rerender({ streamingState: StreamingState.WaitingForConfirmation });
+ });
expect(result.current.elapsedTime).toBe(5);
expect(result.current.currentLoadingPhrase).toBe(
'Waiting for user confirmation...',
);
- rerender({ streamingState: StreamingState.Responding });
+ act(() => {
+ rerender({ streamingState: StreamingState.Responding });
+ });
expect(result.current.elapsedTime).toBe(0); // Should reset
expect(WITTY_LOADING_PHRASES).toContain(
result.current.currentLoadingPhrase,
);
- act(() => {
- vi.advanceTimersByTime(1000);
+ await act(async () => {
+ await vi.advanceTimersByTimeAsync(1000);
});
expect(result.current.elapsedTime).toBe(1);
});
- it('should reset timer and phrase when streamingState changes from Responding to Idle', () => {
+ it('should reset timer and phrase when streamingState changes from Responding to Idle', async () => {
const { result, rerender } = renderHook(
({ streamingState }) => useLoadingIndicator(streamingState),
{ initialProps: { streamingState: StreamingState.Responding } },
);
- act(() => {
- vi.advanceTimersByTime(10000); // 10s
+ await act(async () => {
+ await vi.advanceTimersByTimeAsync(10000); // 10s
});
expect(result.current.elapsedTime).toBe(10);
- rerender({ streamingState: StreamingState.Idle });
+ act(() => {
+ rerender({ streamingState: StreamingState.Idle });
+ });
expect(result.current.elapsedTime).toBe(0);
expect(result.current.currentLoadingPhrase).toBe(WITTY_LOADING_PHRASES[0]);
// Timer should not advance
- act(() => {
- vi.advanceTimersByTime(2000);
+ await act(async () => {
+ await vi.advanceTimersByTimeAsync(2000);
});
expect(result.current.elapsedTime).toBe(0);
});
diff --git a/packages/cli/src/utils/sandbox.ts b/packages/cli/src/utils/sandbox.ts
index cc51a327..c6cee188 100644
--- a/packages/cli/src/utils/sandbox.ts
+++ b/packages/cli/src/utils/sandbox.ts
@@ -11,11 +11,24 @@ import fs from 'node:fs';
import { readFile } from 'node:fs/promises';
import { quote } from 'shell-quote';
import { readPackageUp } from 'read-package-up';
+import commandExists from 'command-exists';
import {
USER_SETTINGS_DIR,
SETTINGS_DIRECTORY_NAME,
} from '../config/settings.js';
+function getContainerPath(hostPath: string): string {
+ if (os.platform() !== 'win32') {
+ return hostPath;
+ }
+ const withForwardSlashes = hostPath.replace(/\\/g, '/');
+ const match = withForwardSlashes.match(/^([A-Z]):\/(.*)/i);
+ if (match) {
+ return `/${match[1].toLowerCase()}/${match[2]}`;
+ }
+ return hostPath;
+}
+
const LOCAL_DEV_SANDBOX_IMAGE_NAME = 'gemini-cli-sandbox';
/**
@@ -98,9 +111,9 @@ export function sandbox_command(sandbox?: string | boolean): string {
if (sandbox === true) {
// look for docker or podman, in that order
- if (execSync('command -v docker || true').toString().trim()) {
+ if (commandExists.sync('docker')) {
return 'docker'; // Set sandbox to 'docker' if found
- } else if (execSync('command -v podman || true').toString().trim()) {
+ } else if (commandExists.sync('podman')) {
return 'podman'; // Set sandbox to 'podman' if found
} else {
console.error(
@@ -111,7 +124,7 @@ export function sandbox_command(sandbox?: string | boolean): string {
}
} else if (sandbox) {
// confirm that specfied command exists
- if (execSync(`command -v ${sandbox} || true`).toString().trim()) {
+ if (commandExists.sync(sandbox)) {
return sandbox;
} else {
console.error(
@@ -124,7 +137,7 @@ export function sandbox_command(sandbox?: string | boolean): string {
// unless SEATBELT_PROFILE is set to 'none', which we allow as an escape hatch
if (
os.platform() === 'darwin' &&
- execSync('command -v sandbox-exec || true').toString().trim() &&
+ commandExists.sync('sandbox-exec') &&
process.env.SEATBELT_PROFILE !== 'none'
) {
return 'sandbox-exec';
@@ -150,71 +163,68 @@ function ports(): string[] {
}
function entrypoint(workdir: string): string[] {
- // set up bash command to be run inside container
- // start with setting up PATH and PYTHONPATH with optional suffixes from host
- const bashCmds = [];
+ const isWindows = os.platform() === 'win32';
+ const containerWorkdir = getContainerPath(workdir);
+ const shellCmds = [];
+ const pathSeparator = isWindows ? ';' : ':';
- // copy any paths in PATH that are under working directory in sandbox
- // note we can't just pass these as --env since that would override base PATH
- // instead we construct a suffix and append as part of bashCmd below
let pathSuffix = '';
if (process.env.PATH) {
- const paths = process.env.PATH.split(':');
- for (const path of paths) {
- if (path.startsWith(workdir)) {
- pathSuffix += `:${path}`;
+ const paths = process.env.PATH.split(pathSeparator);
+ for (const p of paths) {
+ const containerPath = getContainerPath(p);
+ if (
+ containerPath.toLowerCase().startsWith(containerWorkdir.toLowerCase())
+ ) {
+ pathSuffix += `:${containerPath}`;
}
}
}
if (pathSuffix) {
- bashCmds.push(`export PATH="$PATH${pathSuffix}";`); // suffix includes leading ':'
+ shellCmds.push(`export PATH="$PATH${pathSuffix}";`);
}
- // copy any paths in PYTHONPATH that are under working directory in sandbox
- // note we can't just pass these as --env since that would override base PYTHONPATH
- // instead we construct a suffix and append as part of bashCmd below
let pythonPathSuffix = '';
if (process.env.PYTHONPATH) {
- const paths = process.env.PYTHONPATH.split(':');
- for (const path of paths) {
- if (path.startsWith(workdir)) {
- pythonPathSuffix += `:${path}`;
+ const paths = process.env.PYTHONPATH.split(pathSeparator);
+ for (const p of paths) {
+ const containerPath = getContainerPath(p);
+ if (
+ containerPath.toLowerCase().startsWith(containerWorkdir.toLowerCase())
+ ) {
+ pythonPathSuffix += `:${containerPath}`;
}
}
}
if (pythonPathSuffix) {
- bashCmds.push(`export PYTHONPATH="$PYTHONPATH${pythonPathSuffix}";`); // suffix includes leading ':'
+ shellCmds.push(`export PYTHONPATH="$PYTHONPATH${pythonPathSuffix}";`);
}
- // source sandbox.bashrc if exists under project settings directory
const projectSandboxBashrc = path.join(
SETTINGS_DIRECTORY_NAME,
'sandbox.bashrc',
);
if (fs.existsSync(projectSandboxBashrc)) {
- bashCmds.push(`source ${projectSandboxBashrc};`);
+ shellCmds.push(`source ${getContainerPath(projectSandboxBashrc)};`);
}
- // also set up redirects (via socat) so servers can listen on localhost instead of 0.0.0.0
ports().forEach((p) =>
- bashCmds.push(
+ shellCmds.push(
`socat TCP4-LISTEN:${p},bind=$(hostname -i),fork,reuseaddr TCP4:127.0.0.1:${p} 2> /dev/null &`,
),
);
- // append remaining args (bash -c "gemini cli_args...")
- // cli_args need to be quoted before being inserted into bash_cmd
const cliArgs = process.argv.slice(2).map((arg) => quote([arg]));
const cliCmd =
process.env.NODE_ENV === 'development'
? process.env.DEBUG
? 'npm run debug --'
: 'npm rebuild && npm run start --'
- : process.env.DEBUG // for production binary debugging
+ : process.env.DEBUG
? `node --inspect-brk=0.0.0.0:${process.env.DEBUG_PORT || '9229'} $(which gemini)`
: 'gemini';
- const args = [...bashCmds, cliCmd, ...cliArgs];
+ const args = [...shellCmds, cliCmd, ...cliArgs];
return ['bash', '-c', args.join(' ')];
}
@@ -259,7 +269,7 @@ export async function start_sandbox(sandbox: string) {
`CACHE_DIR=${fs.realpathSync(execSync(`getconf DARWIN_USER_CACHE_DIR`).toString().trim())}`,
'-f',
profileFile,
- 'bash',
+ 'sh',
'-c',
[
`SANDBOX=sandbox-exec`,
@@ -274,7 +284,7 @@ export async function start_sandbox(sandbox: string) {
console.error(`hopping into sandbox (command: ${sandbox}) ...`);
// determine full path for gemini-cli to distinguish linked vs installed setting
- const gcPath = execSync(`realpath $(which gemini)`).toString().trim();
+ const gcPath = fs.realpathSync(process.argv[1]);
const projectSandboxDockerfile = path.join(
SETTINGS_DIRECTORY_NAME,
@@ -283,7 +293,8 @@ export async function start_sandbox(sandbox: string) {
const isCustomProjectSandbox = fs.existsSync(projectSandboxDockerfile);
const image = await getSandboxImageName(isCustomProjectSandbox);
- const workdir = process.cwd();
+ const workdir = path.resolve(process.cwd());
+ const containerWorkdir = getContainerPath(workdir);
// if BUILD_SANDBOX is set, then call scripts/build_sandbox.sh under gemini-cli repo
//
@@ -332,7 +343,7 @@ export async function start_sandbox(sandbox: string) {
// use interactive mode and auto-remove container on exit
// run init binary inside container to forward signals & reap zombies
- const args = ['run', '-i', '--rm', '--init', '--workdir', workdir];
+ const args = ['run', '-i', '--rm', '--init', '--workdir', containerWorkdir];
// add TTY only if stdin is TTY as well, i.e. for piped input don't init TTY in container
if (process.stdin.isTTY) {
@@ -340,25 +351,25 @@ export async function start_sandbox(sandbox: string) {
}
// mount current directory as working directory in sandbox (set via --workdir)
- args.push('--volume', `${process.cwd()}:${workdir}`);
+ args.push('--volume', `${workdir}:${containerWorkdir}`);
// mount user settings directory inside container, after creating if missing
// note user/home changes inside sandbox and we mount at BOTH paths for consistency
const userSettingsDirOnHost = USER_SETTINGS_DIR;
- const userSettingsDirInSandbox = `/home/node/${SETTINGS_DIRECTORY_NAME}`;
+ const userSettingsDirInSandbox = getContainerPath(userSettingsDirOnHost);
if (!fs.existsSync(userSettingsDirOnHost)) {
fs.mkdirSync(userSettingsDirOnHost);
}
- args.push('--volume', `${userSettingsDirOnHost}:${userSettingsDirOnHost}`);
+ args.push('--volume', `${userSettingsDirOnHost}:${userSettingsDirInSandbox}`);
if (userSettingsDirInSandbox !== userSettingsDirOnHost) {
args.push(
'--volume',
- `${userSettingsDirOnHost}:${userSettingsDirInSandbox}`,
+ `${userSettingsDirOnHost}:${getContainerPath(userSettingsDirOnHost)}`,
);
}
- // mount os.tmpdir() as /tmp inside container
- args.push('--volume', `${os.tmpdir()}:/tmp`);
+ // mount os.tmpdir() as os.tmpdir() inside container
+ args.push('--volume', `${os.tmpdir()}:${getContainerPath(os.tmpdir())}`);
// mount paths listed in SANDBOX_MOUNTS
if (process.env.SANDBOX_MOUNTS) {
@@ -401,13 +412,10 @@ export async function start_sandbox(sandbox: string) {
// name container after image, plus numeric suffix to avoid conflicts
const imageName = parseImageName(image);
let index = 0;
- while (
- execSync(
- `${sandbox} ps -a --format "{{.Names}}" | grep "${imageName}-${index}" || true`,
- )
- .toString()
- .trim()
- ) {
+ const containerNameCheck = execSync(`${sandbox} ps -a --format "{{.Names}}"`)
+ .toString()
+ .trim();
+ while (containerNameCheck.includes(`${imageName}-${index}`)) {
index++;
}
const containerName = `${imageName}-${index}`;
@@ -435,7 +443,9 @@ export async function start_sandbox(sandbox: string) {
// also mount-replace VIRTUAL_ENV directory with <project_settings>/sandbox.venv
// sandbox can then set up this new VIRTUAL_ENV directory using sandbox.bashrc (see below)
// directory will be empty if not set up, which is still preferable to having host binaries
- if (process.env.VIRTUAL_ENV?.startsWith(workdir)) {
+ if (
+ process.env.VIRTUAL_ENV?.toLowerCase().startsWith(workdir.toLowerCase())
+ ) {
const sandboxVenvPath = path.resolve(
SETTINGS_DIRECTORY_NAME,
'sandbox.venv',
@@ -443,8 +453,14 @@ export async function start_sandbox(sandbox: string) {
if (!fs.existsSync(sandboxVenvPath)) {
fs.mkdirSync(sandboxVenvPath, { recursive: true });
}
- args.push('--volume', `${sandboxVenvPath}:${process.env.VIRTUAL_ENV}`);
- args.push('--env', `VIRTUAL_ENV=${process.env.VIRTUAL_ENV}`);
+ args.push(
+ '--volume',
+ `${sandboxVenvPath}:${getContainerPath(process.env.VIRTUAL_ENV)}`,
+ );
+ args.push(
+ '--env',
+ `VIRTUAL_ENV=${getContainerPath(process.env.VIRTUAL_ENV)}`,
+ );
}
// copy additional environment variables from SANDBOX_ENV
@@ -498,13 +514,24 @@ export async function start_sandbox(sandbox: string) {
// spawn child and let it inherit stdio
const child = spawn(sandbox, args, {
stdio: 'inherit',
- detached: true,
+ detached: os.platform() !== 'win32',
+ });
+
+ child.on('error', (err) => {
+ console.error('Sandbox process error:', err);
});
// uncomment this line (and comment the await on following line) to let parent exit
// child.unref();
- await new Promise((resolve) => {
- child.on('close', resolve);
+ await new Promise<void>((resolve) => {
+ child.on('close', (code, signal) => {
+ if (code !== 0) {
+ console.log(
+ `Sandbox process exited with code: ${code}, signal: ${signal}`,
+ );
+ }
+ resolve();
+ });
});
}
diff --git a/packages/core/package.json b/packages/core/package.json
index a750b1ab..344f3a5a 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -6,7 +6,7 @@
"main": "dist/index.js",
"scripts": {
"start": "node dist/src/index.js",
- "build": "../../scripts/build_package.sh",
+ "build": "node ../../scripts/build_package.js",
"clean": "rm -rf dist",
"lint": "eslint . --ext .ts,.tsx",
"format": "prettier --write .",
diff --git a/packages/core/src/core/prompts.ts b/packages/core/src/core/prompts.ts
index ca04fd38..084ab1f3 100644
--- a/packages/core/src/core/prompts.ts
+++ b/packages/core/src/core/prompts.ts
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
+import os from 'node:os';
import path from 'node:path';
import fs from 'node:fs';
import { LSTool } from '../tools/ls.js';
@@ -131,8 +132,15 @@ You are running outside of a sandbox container, directly on the user's system. F
${(function () {
// note git repo can change so we need to check every time system prompt is generated
- const gitRootCmd = 'git rev-parse --show-toplevel 2>/dev/null || true';
- const gitRoot = execSync(gitRootCmd)?.toString()?.trim();
+ const isWindows = os.platform() === 'win32';
+ const devNull = isWindows ? 'NUL' : '/dev/null';
+ const gitRootCmd = `git rev-parse --show-toplevel 2>${devNull}`;
+ let gitRoot = '';
+ try {
+ gitRoot = execSync(gitRootCmd)?.toString()?.trim();
+ } catch (_e) {
+ // ignore
+ }
if (gitRoot) {
return `
# Git Repository
diff --git a/packages/core/src/tools/edit.ts b/packages/core/src/tools/edit.ts
index 3240fa30..fdabc5b6 100644
--- a/packages/core/src/tools/edit.ts
+++ b/packages/core/src/tools/edit.ts
@@ -206,6 +206,8 @@ Expectation for required parameters:
try {
currentContent = fs.readFileSync(params.file_path, 'utf8');
+ // Normalize line endings to LF for consistent processing.
+ currentContent = currentContent.replace(/\r\n/g, '\n');
fileExists = true;
} catch (err: unknown) {
if (!isNodeError(err) || err.code !== 'ENOENT') {
@@ -303,6 +305,8 @@ Expectation for required parameters:
try {
currentContent = fs.readFileSync(params.file_path, 'utf8');
+ // Normalize line endings to LF for consistent processing.
+ currentContent = currentContent.replace(/\r\n/g, '\n');
fileExists = true;
} catch (err: unknown) {
if (isNodeError(err) && err.code === 'ENOENT') {
diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts
index e1cde43b..4bae498b 100644
--- a/packages/core/src/tools/shell.ts
+++ b/packages/core/src/tools/shell.ts
@@ -169,20 +169,35 @@ export class ShellTool extends BaseTool<ShellToolParams, ToolResult> {
};
}
- // wrap command to append subprocess pids (via pgrep) to temporary file
- const tempFileName = `shell_pgrep_${crypto.randomBytes(6).toString('hex')}.tmp`;
- const tempFilePath = path.join(os.tmpdir(), tempFileName);
+ const isWindows = os.platform() === 'win32';
- let command = params.command.trim();
- if (!command.endsWith('&')) command += ';';
- command = `{ ${command} }; __code=$?; pgrep -g 0 >${tempFilePath} 2>&1; exit $__code;`;
+ // pgrep is not available on Windows, so we can't get background PIDs
+ const command = isWindows
+ ? params.command
+ : (() => {
+ // wrap command to append subprocess pids (via pgrep) to temporary file
+ const tempFileName = `shell_pgrep_${crypto
+ .randomBytes(6)
+ .toString('hex')}.tmp`;
+ const tempFilePath = path.join(os.tmpdir(), tempFileName);
+
+ let command = params.command.trim();
+ if (!command.endsWith('&')) command += ';';
+ return `{ ${command} }; __code=$?; pgrep -g 0 >${tempFilePath} 2>&1; exit $__code;`;
+ })();
// spawn command in specified directory (or project root if not specified)
- const shell = spawn('bash', ['-c', command], {
- stdio: ['ignore', 'pipe', 'pipe'],
- detached: true, // ensure subprocess starts its own process group (esp. in Linux)
- cwd: path.resolve(this.config.getTargetDir(), params.directory || ''),
- });
+ const shell = isWindows
+ ? spawn('cmd.exe', ['/c', command], {
+ stdio: ['ignore', 'pipe', 'pipe'],
+ // detached: true, // ensure subprocess starts its own process group (esp. in Linux)
+ cwd: path.resolve(this.config.getTargetDir(), params.directory || ''),
+ })
+ : spawn('bash', ['-c', command], {
+ stdio: ['ignore', 'pipe', 'pipe'],
+ detached: true, // ensure subprocess starts its own process group (esp. in Linux)
+ cwd: path.resolve(this.config.getTargetDir(), params.directory || ''),
+ });
let exited = false;
let stdout = '';
@@ -241,22 +256,27 @@ export class ShellTool extends BaseTool<ShellToolParams, ToolResult> {
const abortHandler = async () => {
if (shell.pid && !exited) {
- try {
- // attempt to SIGTERM process group (negative PID)
- // fall back to SIGKILL (to group) after 200ms
- process.kill(-shell.pid, 'SIGTERM');
- await new Promise((resolve) => setTimeout(resolve, 200));
- if (shell.pid && !exited) {
- process.kill(-shell.pid, 'SIGKILL');
- }
- } catch (_e) {
- // if group kill fails, fall back to killing just the main process
+ if (os.platform() === 'win32') {
+ // For Windows, use taskkill to kill the process tree
+ spawn('taskkill', ['/pid', shell.pid.toString(), '/f', '/t']);
+ } else {
try {
- if (shell.pid) {
- shell.kill('SIGKILL');
+ // attempt to SIGTERM process group (negative PID)
+ // fall back to SIGKILL (to group) after 200ms
+ process.kill(-shell.pid, 'SIGTERM');
+ await new Promise((resolve) => setTimeout(resolve, 200));
+ if (shell.pid && !exited) {
+ process.kill(-shell.pid, 'SIGKILL');
}
} catch (_e) {
- console.error(`failed to kill shell process ${shell.pid}: ${_e}`);
+ // if group kill fails, fall back to killing just the main process
+ try {
+ if (shell.pid) {
+ shell.kill('SIGKILL');
+ }
+ } catch (_e) {
+ console.error(`failed to kill shell process ${shell.pid}: ${_e}`);
+ }
}
}
}
@@ -272,26 +292,32 @@ export class ShellTool extends BaseTool<ShellToolParams, ToolResult> {
// parse pids (pgrep output) from temporary file and remove it
const backgroundPIDs: number[] = [];
- if (fs.existsSync(tempFilePath)) {
- const pgrepLines = fs
- .readFileSync(tempFilePath, 'utf8')
- .split('\n')
- .filter(Boolean);
- for (const line of pgrepLines) {
- if (!/^\d+$/.test(line)) {
- console.error(`pgrep: ${line}`);
+ if (os.platform() !== 'win32') {
+ const tempFileName = `shell_pgrep_${crypto
+ .randomBytes(6)
+ .toString('hex')}.tmp`;
+ const tempFilePath = path.join(os.tmpdir(), tempFileName);
+ if (fs.existsSync(tempFilePath)) {
+ const pgrepLines = fs
+ .readFileSync(tempFilePath, 'utf8')
+ .split('\n')
+ .filter(Boolean);
+ for (const line of pgrepLines) {
+ if (!/^\d+$/.test(line)) {
+ console.error(`pgrep: ${line}`);
+ }
+ const pid = Number(line);
+ // exclude the shell subprocess pid
+ if (pid !== shell.pid) {
+ backgroundPIDs.push(pid);
+ }
}
- const pid = Number(line);
- // exclude the shell subprocess pid
- if (pid !== shell.pid) {
- backgroundPIDs.push(pid);
+ fs.unlinkSync(tempFilePath);
+ } else {
+ if (!abortSignal.aborted) {
+ console.error('missing pgrep output');
}
}
- fs.unlinkSync(tempFilePath);
- } else {
- if (!abortSignal.aborted) {
- console.error('missing pgrep output');
- }
}
let llmContent = '';
diff --git a/packages/core/src/utils/editor.test.ts b/packages/core/src/utils/editor.test.ts
index 74237c74..20917c0f 100644
--- a/packages/core/src/utils/editor.test.ts
+++ b/packages/core/src/utils/editor.test.ts
@@ -21,7 +21,11 @@ describe('checkHasEditor', () => {
it('should return true for vscode if "code" command exists', () => {
(execSync as Mock).mockReturnValue(Buffer.from('/usr/bin/code'));
expect(checkHasEditor('vscode')).toBe(true);
- expect(execSync).toHaveBeenCalledWith('which code', { stdio: 'ignore' });
+ const expectedCommand =
+ process.platform === 'win32' ? 'where.exe code.cmd' : 'command -v code';
+ expect(execSync).toHaveBeenCalledWith(expectedCommand, {
+ stdio: 'ignore',
+ });
});
it('should return false for vscode if "code" command does not exist', () => {
@@ -34,7 +38,11 @@ describe('checkHasEditor', () => {
it('should return true for vim if "vim" command exists', () => {
(execSync as Mock).mockReturnValue(Buffer.from('/usr/bin/vim'));
expect(checkHasEditor('vim')).toBe(true);
- expect(execSync).toHaveBeenCalledWith('which vim', { stdio: 'ignore' });
+ const expectedCommand =
+ process.platform === 'win32' ? 'where.exe vim' : 'command -v vim';
+ expect(execSync).toHaveBeenCalledWith(expectedCommand, {
+ stdio: 'ignore',
+ });
});
it('should return false for vim if "vim" command does not exist', () => {
diff --git a/packages/core/src/utils/editor.ts b/packages/core/src/utils/editor.ts
index 447aa0d2..6be5cffb 100644
--- a/packages/core/src/utils/editor.ts
+++ b/packages/core/src/utils/editor.ts
@@ -15,7 +15,10 @@ interface DiffCommand {
function commandExists(cmd: string): boolean {
try {
- execSync(`which ${cmd}`, { stdio: 'ignore' });
+ execSync(
+ process.platform === 'win32' ? `where.exe ${cmd}` : `command -v ${cmd}`,
+ { stdio: 'ignore' },
+ );
return true;
} catch {
return false;
@@ -24,7 +27,9 @@ function commandExists(cmd: string): boolean {
export function checkHasEditor(editor: EditorType): boolean {
if (editor === 'vscode') {
- return commandExists('code');
+ return process.platform === 'win32'
+ ? commandExists('code.cmd')
+ : commandExists('code');
} else if (editor === 'vim') {
return commandExists('vim');
}
@@ -116,7 +121,10 @@ export async function openDiff(
});
} else {
// Use execSync for terminal-based editors like vim
- const command = `${diffCommand.command} ${diffCommand.args.map((arg) => `"${arg}"`).join(' ')}`;
+ const command =
+ process.platform === 'win32'
+ ? `${diffCommand.command} ${diffCommand.args.join(' ')}`
+ : `${diffCommand.command} ${diffCommand.args.map((arg) => `"${arg}"`).join(' ')}`;
execSync(command, {
stdio: 'inherit',
encoding: 'utf8',
diff --git a/scripts/build.js b/scripts/build.js
new file mode 100644
index 00000000..373e268e
--- /dev/null
+++ b/scripts/build.js
@@ -0,0 +1,54 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { execSync } from 'child_process';
+import { existsSync } from 'fs';
+import { join } from 'path';
+
+const root = join(import.meta.dirname, '..');
+
+// npm install if node_modules was removed (e.g. via npm run clean or scripts/clean.js)
+if (!existsSync(join(root, 'node_modules'))) {
+ execSync('npm install', { stdio: 'inherit', cwd: root });
+}
+
+// build all workspaces/packages
+execSync('npm run generate', { stdio: 'inherit', cwd: root });
+execSync('npm run build --workspaces', { stdio: 'inherit', cwd: root });
+
+// also build container image if sandboxing is enabled
+// skip (-s) npm install + build since we did that above
+try {
+ execSync('node scripts/sandbox_command.js -q', {
+ stdio: 'inherit',
+ cwd: root,
+ });
+ if (
+ process.env.BUILD_SANDBOX === '1' ||
+ process.env.BUILD_SANDBOX === 'true'
+ ) {
+ execSync('node scripts/build_sandbox.js -s', {
+ stdio: 'inherit',
+ cwd: root,
+ });
+ }
+} catch {
+ // ignore
+}
diff --git a/scripts/build.sh b/scripts/build.sh
deleted file mode 100755
index 0eba264f..00000000
--- a/scripts/build.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/bash
-# Copyright 2025 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -euo pipefail
-
-# npm install if node_modules was removed (e.g. via npm run clean or scripts/clean.sh)
-if [ ! -d "node_modules" ]; then
- npm install
-fi
-
-# build all workspaces/packages
-npm run build --workspaces
-
-# also build container image if sandboxing is enabled
-# skip (-s) npm install + build since we did that above
-if scripts/sandbox_command.sh -q && [[ "${BUILD_SANDBOX:-}" =~ ^(1|true)$ ]]; then
- scripts/build_sandbox.sh -s
-fi
diff --git a/scripts/build_package.js b/scripts/build_package.js
new file mode 100644
index 00000000..9e1b05bf
--- /dev/null
+++ b/scripts/build_package.js
@@ -0,0 +1,37 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { execSync } from 'child_process';
+import { writeFileSync } from 'fs';
+import { join } from 'path';
+
+if (!process.cwd().includes('packages')) {
+ console.error('must be invoked from a package directory');
+ process.exit(1);
+}
+
+// build typescript files
+execSync('tsc --build', { stdio: 'inherit' });
+
+// copy .{md,json} files
+execSync('node ../../scripts/copy_files.js', { stdio: 'inherit' });
+
+// touch dist/.last_build
+writeFileSync(join(process.cwd(), 'dist', '.last_build'), '');
+process.exit(0);
diff --git a/scripts/build_package.sh b/scripts/build_package.sh
deleted file mode 100755
index e64d98cc..00000000
--- a/scripts/build_package.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/bash
-# Copyright 2025 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -euo pipefail
-
-if [[ $(pwd) != *"/packages/"* ]]; then
- echo "must be invoked from a package directory"
- exit 1
-fi
-
-# clean dist directory
-# rm -rf dist/*
-
-# build typescript files
-tsc --build
-
-# copy .{md,json} files
-node ../../scripts/copy_files.js
-
-# touch dist/.last_build
-touch dist/.last_build
diff --git a/scripts/build_sandbox.js b/scripts/build_sandbox.js
new file mode 100644
index 00000000..bfcf1bf9
--- /dev/null
+++ b/scripts/build_sandbox.js
@@ -0,0 +1,125 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { execSync } from 'child_process';
+import { chmodSync, readFileSync, rmSync } from 'fs';
+import { join } from 'path';
+import yargs from 'yargs';
+import { hideBin } from 'yargs/helpers';
+
+const argv = yargs(hideBin(process.argv))
+ .option('s', {
+ alias: 'skip-npm-install-build',
+ type: 'boolean',
+ default: false,
+ description: 'skip npm install + npm run build',
+ })
+ .option('f', {
+ alias: 'dockerfile',
+ type: 'string',
+ description: 'use <dockerfile> for custom image',
+ })
+ .option('i', {
+ alias: 'image',
+ type: 'string',
+ description: 'use <image> name for custom image',
+ }).argv;
+
+let sandboxCommand;
+try {
+ sandboxCommand = execSync('node scripts/sandbox_command.js')
+ .toString()
+ .trim();
+} catch {
+ console.warn(
+ 'WARNING: container-based sandboxing is disabled (see README.md#sandboxing)',
+ );
+ process.exit(0);
+}
+
+if (sandboxCommand === 'sandbox-exec') {
+ console.warn(
+ 'WARNING: container-based sandboxing is disabled (see README.md#sandboxing)',
+ );
+ process.exit(0);
+}
+
+console.log(`using ${sandboxCommand} for sandboxing`);
+
+const baseImage = 'gemini-cli-sandbox';
+const customImage = argv.i;
+const baseDockerfile = 'Dockerfile';
+const customDockerfile = argv.f;
+
+if (!argv.s) {
+ execSync('npm install', { stdio: 'inherit' });
+ execSync('npm run build --workspaces', { stdio: 'inherit' });
+}
+
+console.log('packing @gemini-cli/cli ...');
+const cliPackageDir = join('packages', 'cli');
+rmSync(join(cliPackageDir, 'dist', 'gemini-cli-cli-*.tgz'), { force: true });
+execSync(`npm pack -w @gemini-cli/cli --pack-destination ./packages/cli/dist`, {
+ stdio: 'ignore',
+});
+
+console.log('packing @gemini-cli/core ...');
+const corePackageDir = join('packages', 'core');
+rmSync(join(corePackageDir, 'dist', 'gemini-cli-core-*.tgz'), { force: true });
+execSync(
+ `npm pack -w @gemini-cli/core --pack-destination ./packages/core/dist`,
+ { stdio: 'ignore' },
+);
+
+const packageVersion = JSON.parse(
+ readFileSync(join(process.cwd(), 'package.json'), 'utf-8'),
+).version;
+
+chmodSync(
+ join(cliPackageDir, 'dist', `gemini-cli-cli-${packageVersion}.tgz`),
+ 0o755,
+);
+chmodSync(
+ join(corePackageDir, 'dist', `gemini-cli-core-${packageVersion}.tgz`),
+ 0o755,
+);
+
+const buildStdout = process.env.VERBOSE ? 'inherit' : 'ignore';
+
+function buildImage(imageName, dockerfile) {
+ console.log(`building ${imageName} ... (can be slow first time)`);
+ const buildCommand =
+ sandboxCommand === 'podman'
+ ? `${sandboxCommand} build --authfile=<(echo '{}')`
+ : `${sandboxCommand} --config=".docker" buildx build`;
+
+ execSync(
+ `${buildCommand} ${process.env.BUILD_SANDBOX_FLAGS || ''} -f "${dockerfile}" -t "${imageName}" .`,
+ { stdio: buildStdout },
+ );
+ console.log(`built ${imageName}`);
+}
+
+buildImage(baseImage, baseDockerfile);
+
+if (customDockerfile && customImage) {
+ buildImage(customImage, customDockerfile);
+}
+
+execSync(`${sandboxCommand} image prune -f`, { stdio: 'ignore' });
diff --git a/scripts/build_sandbox.sh b/scripts/build_sandbox.sh
deleted file mode 100755
index dabcffcf..00000000
--- a/scripts/build_sandbox.sh
+++ /dev/null
@@ -1,102 +0,0 @@
-#!/bin/bash
-# Copyright 2025 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -euo pipefail
-
-# exit with warning if container-based sandboxing is disabled
-# note this includes the case where sandbox-exec (seatbelt) is used
-# this happens most commonly when user runs `npm run build:all` without enabling sandboxing
-if ! scripts/sandbox_command.sh -q || [ "$(scripts/sandbox_command.sh)" == "sandbox-exec" ]; then
- echo "WARNING: container-based sandboxing is disabled (see CONTRIBUTING.md#enabling-sandboxing)"
- exit 0
-fi
-
-CMD=$(scripts/sandbox_command.sh)
-echo "using $CMD for sandboxing"
-
-BASE_IMAGE=gemini-cli-sandbox
-CUSTOM_IMAGE=''
-BASE_DOCKERFILE=Dockerfile
-CUSTOM_DOCKERFILE=''
-
-SKIP_NPM_INSTALL_BUILD=false
-while getopts "sf:i:" opt; do
- case ${opt} in
- s) SKIP_NPM_INSTALL_BUILD=true ;;
- f)
- CUSTOM_DOCKERFILE=$OPTARG
- ;;
- i)
- CUSTOM_IMAGE=$OPTARG
- ;;
- \?)
- echo "usage: $(basename "$0") [-s] [-f <dockerfile>]"
- echo " -s: skip npm install + npm run build"
- echo " -f <dockerfile>: use <dockerfile> for custom image"
- echo " -i <image>: use <image> name for custom image"
- exit 1
- ;;
- esac
-done
-shift $((OPTIND - 1))
-
-# npm install + npm run build unless skipping via -s option
-if [ "$SKIP_NPM_INSTALL_BUILD" = false ]; then
- npm install
- npm run build --workspaces
-fi
-
-# prepare global installation files for prod builds
-# pack cli
-echo "packing @gemini-cli/cli ..."
-rm -f packages/cli/dist/gemini-cli-cli-*.tgz
-npm pack -w @gemini-cli/cli --pack-destination ./packages/cli/dist &>/dev/null
-# pack core
-echo "packing @gemini-cli/core ..."
-rm -f packages/core/dist/gemini-cli-core-*.tgz
-npm pack -w @gemini-cli/core --pack-destination ./packages/core/dist &>/dev/null
-# give node user (used during installation, see Dockerfile) access to these files
-chmod 755 packages/*/dist/gemini-cli-*.tgz
-
-# redirect build output to /dev/null unless VERBOSE is set
-BUILD_STDOUT="/dev/null"
-if [ -n "${VERBOSE:-}" ]; then
- BUILD_STDOUT="/dev/stdout"
-fi
-
-build_image() {
- if [[ "$CMD" == "podman" ]]; then
- # use empty --authfile to skip unnecessary auth refresh overhead
- $CMD build --authfile=<(echo '{}') "$@" >$BUILD_STDOUT
- elif [[ "$CMD" == "docker" ]]; then
- $CMD --config=".docker" buildx build "$@" >$BUILD_STDOUT
- else
- $CMD build "$@" >$BUILD_STDOUT
- fi
-}
-
-echo "building $BASE_IMAGE ... (can be slow first time)"
-# shellcheck disable=SC2086 # allow globbing and word splitting for BUILD_SANDBOX_FLAGS
-build_image ${BUILD_SANDBOX_FLAGS:-} -f "$BASE_DOCKERFILE" -t "$BASE_IMAGE" .
-echo "built $BASE_IMAGE"
-
-if [[ -n "$CUSTOM_DOCKERFILE" && -n "$CUSTOM_IMAGE" ]]; then
- echo "building $CUSTOM_IMAGE ... (can be slow first time)"
- # shellcheck disable=SC2086 # allow globbing and word splitting for BUILD_SANDBOX_FLAGS
- build_image ${BUILD_SANDBOX_FLAGS:-} -f "$CUSTOM_DOCKERFILE" -t "$CUSTOM_IMAGE" .
- echo "built $CUSTOM_IMAGE"
-fi
-
-$CMD image prune -f >/dev/null
diff --git a/scripts/clean.js b/scripts/clean.js
new file mode 100644
index 00000000..dd2911e9
--- /dev/null
+++ b/scripts/clean.js
@@ -0,0 +1,32 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { execSync } from 'child_process';
+import { rmSync } from 'fs';
+import { join } from 'path';
+
+const root = join(import.meta.dirname, '..');
+
+// remove npm install/build artifacts
+rmSync(join(root, 'node_modules'), { recursive: true, force: true });
+rmSync(join(root, 'packages/cli/src/generated/'), {
+ recursive: true,
+ force: true,
+});
+execSync('npm run clean --workspaces', { stdio: 'inherit', cwd: root });
diff --git a/scripts/clean.sh b/scripts/clean.sh
deleted file mode 100755
index 2f6bc3c0..00000000
--- a/scripts/clean.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-# Copyright 2025 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -euo pipefail
-
-# remove npm install/build artifacts
-rm -rf node_modules
-rm -rf packages/cli/src/generated/
-npm run clean --workspaces
diff --git a/scripts/copy_bundle_assets.js b/scripts/copy_bundle_assets.js
new file mode 100644
index 00000000..cb1ac197
--- /dev/null
+++ b/scripts/copy_bundle_assets.js
@@ -0,0 +1,48 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { copyFileSync, existsSync, mkdirSync } from 'fs';
+import { join, basename } from 'path';
+import { glob } from 'glob';
+
+const root = join(import.meta.dirname, '..');
+const bundleDir = join(root, 'bundle');
+
+// Create the bundle directory if it doesn't exist
+if (!existsSync(bundleDir)) {
+ mkdirSync(bundleDir);
+}
+
+// Copy specific shell files to the root of the bundle directory
+copyFileSync(
+ join(root, 'packages/core/src/tools/shell.md'),
+ join(bundleDir, 'shell.md'),
+);
+copyFileSync(
+ join(root, 'packages/core/src/tools/shell.json'),
+ join(bundleDir, 'shell.json'),
+);
+
+// Find and copy all .sb files from packages to the root of the bundle directory
+const sbFiles = glob.sync('packages/**/*.sb', { cwd: root });
+for (const file of sbFiles) {
+ copyFileSync(join(root, file), join(bundleDir, basename(file)));
+}
+
+console.log('Assets copied to bundle/');
diff --git a/scripts/copy_bundle_assets.sh b/scripts/copy_bundle_assets.sh
deleted file mode 100755
index 93d46364..00000000
--- a/scripts/copy_bundle_assets.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/bash
-
-# Create the bundle directory if it doesn't exist
-mkdir -p bundle
-
-# Copy specific shell files to the root of the bundle directory
-cp "packages/core/src/tools/shell.md" "bundle/shell.md"
-cp "packages/core/src/tools/shell.json" "bundle/shell.json"
-
-# Find and copy all .sb files from packages to the root of the bundle directory
-find packages -name '*.sb' -exec cp -f {} bundle/ \;
-
-echo "Assets copied to bundle/" \ No newline at end of file
diff --git a/scripts/generate-git-commit-info.js b/scripts/generate-git-commit-info.js
new file mode 100644
index 00000000..f046a7fb
--- /dev/null
+++ b/scripts/generate-git-commit-info.js
@@ -0,0 +1,61 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { execSync } from 'child_process';
+import { existsSync, mkdirSync, writeFileSync } from 'fs';
+import { join } from 'path';
+
+const root = join(import.meta.dirname, '..');
+const generatedDir = join(root, 'packages/cli/src/generated');
+const gitCommitFile = join(generatedDir, 'git-commit.ts');
+let gitCommitInfo = 'N/A';
+
+if (!existsSync(generatedDir)) {
+ mkdirSync(generatedDir, { recursive: true });
+}
+
+try {
+ const gitHash = execSync('git rev-parse --short HEAD', {
+ encoding: 'utf-8',
+ }).trim();
+ if (gitHash) {
+ gitCommitInfo = gitHash;
+ const gitStatus = execSync('git status --porcelain', {
+ encoding: 'utf-8',
+ }).trim();
+ if (gitStatus) {
+ gitCommitInfo = `${gitHash} (local modifications)`;
+ }
+ }
+} catch {
+ // ignore
+}
+
+const fileContent = `/**
+ * @license
+ * Copyright ${new Date().getFullYear()} Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+// This file is auto-generated by the build script (scripts/build.js)
+// Do not edit this file manually.
+export const GIT_COMMIT_INFO = '${gitCommitInfo}';
+`;
+
+writeFileSync(gitCommitFile, fileContent);
diff --git a/scripts/generate-git-commit-info.sh b/scripts/generate-git-commit-info.sh
deleted file mode 100755
index 2a64830f..00000000
--- a/scripts/generate-git-commit-info.sh
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/bin/bash
-# Copyright 2025 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -euo pipefail
-
-GENERATED_DIR="packages/cli/src/generated"
-GIT_COMMIT_FILE="$GENERATED_DIR/git-commit.ts"
-GIT_COMMIT_INFO="N/A"
-
-mkdir -p "$GENERATED_DIR"
-
-if command -v git &> /dev/null && git rev-parse --is-inside-work-tree &> /dev/null; then
- GIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "")
- if [ -n "$GIT_HASH" ]; then
- GIT_COMMIT_INFO="$GIT_HASH"
- if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
- GIT_COMMIT_INFO="$GIT_HASH (local modifications)"
- fi
- fi
-fi
-
-cat <<EOL > "$GIT_COMMIT_FILE"
-/**
- * @license
- * Copyright $(date +%Y) Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-
-// This file is auto-generated by the build script (scripts/build.sh)
-// Do not edit this file manually.
-export const GIT_COMMIT_INFO = '$GIT_COMMIT_INFO';
-EOL
diff --git a/scripts/publish-sandbox.js b/scripts/publish-sandbox.js
new file mode 100644
index 00000000..916089be
--- /dev/null
+++ b/scripts/publish-sandbox.js
@@ -0,0 +1,55 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { execSync } from 'child_process';
+
+const {
+ SANDBOX_IMAGE_REGISTRY,
+ SANDBOX_IMAGE_NAME,
+ npm_package_version,
+ DOCKER_DRY_RUN,
+} = process.env;
+
+if (!SANDBOX_IMAGE_REGISTRY) {
+ console.error(
+ 'Error: SANDBOX_IMAGE_REGISTRY environment variable is not set.',
+ );
+ process.exit(1);
+}
+
+if (!SANDBOX_IMAGE_NAME) {
+ console.error('Error: SANDBOX_IMAGE_NAME environment variable is not set.');
+ process.exit(1);
+}
+
+if (!npm_package_version) {
+ console.error(
+ 'Error: npm_package_version environment variable is not set (should be run via npm).',
+ );
+ process.exit(1);
+}
+
+const imageUri = `${SANDBOX_IMAGE_REGISTRY}/${SANDBOX_IMAGE_NAME}:${npm_package_version}`;
+
+if (DOCKER_DRY_RUN) {
+ console.log(`DRY RUN: Would execute: docker push "${imageUri}"`);
+} else {
+ console.log(`Executing: docker push "${imageUri}"`);
+ execSync(`docker push "${imageUri}"`, { stdio: 'inherit' });
+}
diff --git a/scripts/publish-sandbox.sh b/scripts/publish-sandbox.sh
deleted file mode 100755
index dfc16353..00000000
--- a/scripts/publish-sandbox.sh
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/bin/bash
-# Copyright 2025 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -euo pipefail
-
-# Ensure required environment variables are set
-if [ -z "${SANDBOX_IMAGE_REGISTRY}" ]; then
- echo "Error: SANDBOX_IMAGE_REGISTRY environment variable is not set." >&2
- exit 1
-fi
-
-if [ -z "${SANDBOX_IMAGE_NAME}" ]; then
- echo "Error: SANDBOX_IMAGE_NAME environment variable is not set." >&2
- exit 1
-fi
-
-if [ -z "${npm_package_version}" ]; then
- echo "Error: npm_package_version environment variable is not set (should be run via npm)." >&2
- exit 1
-fi
-
-IMAGE_URI="${SANDBOX_IMAGE_REGISTRY}/${SANDBOX_IMAGE_NAME}:${npm_package_version}"
-
-if [ -n "${DOCKER_DRY_RUN:-}" ]; then
- echo "DRY RUN: Would execute: docker push \"${IMAGE_URI}\""
-else
- echo "Executing: docker push \"${IMAGE_URI}\""
- docker push "${IMAGE_URI}"
-fi
diff --git a/scripts/sandbox.js b/scripts/sandbox.js
new file mode 100644
index 00000000..58223180
--- /dev/null
+++ b/scripts/sandbox.js
@@ -0,0 +1,123 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { execSync, spawn } from 'child_process';
+import yargs from 'yargs';
+import { hideBin } from 'yargs/helpers';
+
+try {
+ execSync('node scripts/sandbox_command.js -q');
+} catch {
+ console.error('ERROR: sandboxing disabled. See docs to enable sandboxing.');
+ process.exit(1);
+}
+
+const argv = yargs(hideBin(process.argv)).option('i', {
+ alias: 'interactive',
+ type: 'boolean',
+ default: false,
+}).argv;
+
+if (argv.i && !process.stdin.isTTY) {
+ console.error(
+ 'ERROR: interactive mode (-i) requested without a terminal attached',
+ );
+ process.exit(1);
+}
+
+const image = 'gemini-cli-sandbox';
+const sandboxCommand = execSync('node scripts/sandbox_command.js')
+ .toString()
+ .trim();
+
+const sandboxes = execSync(
+ `${sandboxCommand} ps --filter "ancestor=${image}" --format "{{.Names}}"`,
+)
+ .toString()
+ .trim()
+ .split('\n')
+ .filter(Boolean);
+
+let sandboxName;
+const firstArg = argv._[0];
+
+if (firstArg) {
+ if (firstArg.startsWith(image) || /^\d+$/.test(firstArg)) {
+ sandboxName = firstArg.startsWith(image)
+ ? firstArg
+ : `${image}-${firstArg}`;
+ argv._.shift();
+ }
+}
+
+if (!sandboxName) {
+ if (sandboxes.length === 0) {
+ console.error(
+ 'No sandboxes found. Are you running gemini-cli with sandboxing enabled?',
+ );
+ process.exit(1);
+ }
+ if (sandboxes.length > 1) {
+ console.error('Multiple sandboxes found:');
+ sandboxes.forEach((s) => console.error(` ${s}`));
+ console.error(
+ 'Sandbox name or index (0,1,...) must be specified as first argument',
+ );
+ process.exit(1);
+ }
+ sandboxName = sandboxes[0];
+}
+
+if (!sandboxes.includes(sandboxName)) {
+ console.error(`unknown sandbox ${sandboxName}`);
+ console.error('known sandboxes:');
+ sandboxes.forEach((s) => console.error(` ${s}`));
+ process.exit(1);
+}
+
+const execArgs = [];
+let commandToRun = [];
+
+// Determine interactive flags.
+// If a command is provided, only be interactive if -i is passed.
+// If no command is provided, always be interactive.
+if (argv._.length > 0) {
+ if (argv.i) {
+ execArgs.push('-it');
+ }
+} else {
+ execArgs.push('-it');
+}
+
+// Determine the command to run inside the container.
+if (argv._.length > 0) {
+ // Join all positional arguments into a single command string.
+ const userCommand = argv._.join(' ');
+ // The container is Linux, so we use bash -l -c to execute the command string.
+ // This is cross-platform because it's what the container runs, not the host.
+ commandToRun = ['bash', '-l', '-c', userCommand];
+} else {
+ // No command provided, so we start an interactive bash login shell.
+ commandToRun = ['bash', '-l'];
+}
+
+const spawnArgs = ['exec', ...execArgs, sandboxName, ...commandToRun];
+
+// Use spawn to avoid shell injection issues and handle arguments correctly.
+spawn(sandboxCommand, spawnArgs, { stdio: 'inherit' });
diff --git a/scripts/sandbox.sh b/scripts/sandbox.sh
deleted file mode 100755
index b6a52c14..00000000
--- a/scripts/sandbox.sh
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/bin/bash
-# Copyright 2025 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -euo pipefail
-
-if ! scripts/sandbox_command.sh -q; then
- echo "ERROR: sandboxing disabled. See docs to enable sandboxing."
- exit 1
-fi
-
-# parse flags
-interactive=false
-while getopts "i" opt; do
- case "$opt" in
- \?)
- echo "usage: sandbox.sh [-i] [sandbox-name-or-index = AUTO] [command... = bash -l]"
- echo " -i: enable interactive mode for custom command (enabled by default for login shell)"
- echo " (WARNING: interactive mode causes stderr to be redirected to stdout)"
- exit 1
- ;;
- i)
- interactive=true
- if [ ! -t 0 ]; then
- echo "ERROR: interactive mode (-i) requested without a terminal attached"
- exit 1
- fi
- ;;
- esac
-done
-shift $((OPTIND - 1))
-
-IMAGE=gemini-cli-sandbox
-CMD=$(scripts/sandbox_command.sh)
-
-# list all containers running on sandbox image
-sandboxes=()
-while IFS= read -r line; do
- sandboxes+=("$line")
-done < <($CMD ps --filter "ancestor=$IMAGE" --format "{{.Names}}")
-
-# take first argument as sandbox name if it starts with image name or is an integer
-# otherwise require a unique sandbox to be running and take its name
-if [[ "${1:-}" =~ ^$IMAGE(-[0-9]+)?$ ]]; then
- SANDBOX=$1
- shift
-elif [[ "${1:-}" =~ ^[0-9]+$ ]]; then
- SANDBOX=$IMAGE-$1
- shift
-else
- # exit if no sandbox is running
- if [ ${#sandboxes[@]} -eq 0 ]; then
- echo "No sandboxes found. Are you running gemini-cli with sandboxing enabled?"
- exit 1
- fi
- # exit if multiple sandboxes are running
- if [ ${#sandboxes[@]} -gt 1 ]; then
- echo "Multiple sandboxes found:"
- for sandbox in "${sandboxes[@]}"; do
- echo " $sandbox"
- done
- echo "Sandbox name or index (0,1,...) must be specified as first argument"
- exit 1
- fi
- SANDBOX=${sandboxes[0]}
-fi
-
-# check that sandbox exists
-if ! [[ " ${sandboxes[*]} " == *" $SANDBOX "* ]]; then
- echo "unknown sandbox $SANDBOX"
- echo "known sandboxes:"
- for sandbox in "${sandboxes[@]}"; do
- echo " $sandbox"
- done
- exit 1
-fi
-
-# determine command and args for exec
-if [ $# -gt 0 ]; then
- cmd=(bash -l -c "$(printf '%q ' "$@")") # fixes quoting, e.g. bash -c 'echo $SANDBOX'
- exec_args=()
- if [ "$interactive" = true ]; then
- exec_args=(-it)
- fi
-else
- cmd=(bash -l)
- exec_args=(-it)
-fi
-
-# run command in sandbox
-exec_args+=("$SANDBOX" "${cmd[@]}")
-$CMD exec "${exec_args[@]}"
diff --git a/scripts/sandbox_command.js b/scripts/sandbox_command.js
new file mode 100644
index 00000000..7f8e8381
--- /dev/null
+++ b/scripts/sandbox_command.js
@@ -0,0 +1,126 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { execSync } from 'child_process';
+import { existsSync, readFileSync } from 'fs';
+import { join, dirname } from 'path';
+import os from 'os';
+import yargs from 'yargs';
+import { hideBin } from 'yargs/helpers';
+import dotenv from 'dotenv';
+
+const argv = yargs(hideBin(process.argv)).option('q', {
+ alias: 'quiet',
+ type: 'boolean',
+ default: false,
+}).argv;
+
+let geminiSandbox = process.env.GEMINI_SANDBOX;
+
+if (!geminiSandbox) {
+ const userSettingsFile = join(os.homedir(), '.gemini', 'settings.json');
+ if (existsSync(userSettingsFile)) {
+ const settings = JSON.parse(readFileSync(userSettingsFile, 'utf-8'));
+ if (settings.sandbox) {
+ geminiSandbox = settings.sandbox;
+ }
+ }
+}
+
+if (!geminiSandbox) {
+ let currentDir = process.cwd();
+ while (currentDir !== '/') {
+ const geminiEnv = join(currentDir, '.gemini', '.env');
+ const regularEnv = join(currentDir, '.env');
+ if (existsSync(geminiEnv)) {
+ dotenv.config({ path: geminiEnv });
+ break;
+ } else if (existsSync(regularEnv)) {
+ dotenv.config({ path: regularEnv });
+ break;
+ }
+ currentDir = dirname(currentDir);
+ }
+ geminiSandbox = process.env.GEMINI_SANDBOX;
+}
+
+if (process.env.GEMINI_CODE_SANDBOX) {
+ console.warn(
+ 'WARNING: GEMINI_CODE_SANDBOX is deprecated. Use GEMINI_SANDBOX instead.',
+ );
+ geminiSandbox = process.env.GEMINI_CODE_SANDBOX;
+}
+
+geminiSandbox = (geminiSandbox || '').toLowerCase();
+
+const commandExists = (cmd) => {
+ const checkCommand = os.platform() === 'win32' ? 'where' : 'command -v';
+ try {
+ execSync(`${checkCommand} ${cmd}`, { stdio: 'ignore' });
+ return true;
+ } catch {
+ if (os.platform() === 'win32') {
+ try {
+ execSync(`${checkCommand} ${cmd}.exe`, { stdio: 'ignore' });
+ return true;
+ } catch {
+ return false;
+ }
+ }
+ return false;
+ }
+};
+
+let command = '';
+if (['1', 'true'].includes(geminiSandbox)) {
+ if (commandExists('docker')) {
+ command = 'docker';
+ } else if (commandExists('podman')) {
+ command = 'podman';
+ } else {
+ console.error(
+ 'ERROR: install docker or podman or specify command in GEMINI_SANDBOX',
+ );
+ process.exit(1);
+ }
+} else if (geminiSandbox && !['0', 'false'].includes(geminiSandbox)) {
+ if (commandExists(geminiSandbox)) {
+ command = geminiSandbox;
+ } else {
+ console.error(
+ `ERROR: missing sandbox command '${geminiSandbox}' (from GEMINI_SANDBOX)`,
+ );
+ process.exit(1);
+ }
+} else {
+ if (os.platform() === 'darwin' && process.env.SEATBELT_PROFILE !== 'none') {
+ if (commandExists('sandbox-exec')) {
+ command = 'sandbox-exec';
+ } else {
+ process.exit(1);
+ }
+ } else {
+ process.exit(1);
+ }
+}
+
+if (!argv.q) {
+ console.log(command);
+}
+process.exit(0);
diff --git a/scripts/sandbox_command.sh b/scripts/sandbox_command.sh
deleted file mode 100755
index 468a4834..00000000
--- a/scripts/sandbox_command.sh
+++ /dev/null
@@ -1,122 +0,0 @@
-#!/bin/bash
-# Copyright 2025 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# usage: scripts/sandbox_command.sh [-q]
-# -q: quiet mode (do not print command, just exit w/ code 0 or 1)
-
-set -euo pipefail
-
-# parse flags
-QUIET=false
-while getopts ":q" opt; do
- case ${opt} in
- q) QUIET=true ;;
- \?)
- echo "Usage: $0 [-q]"
- exit 1
- ;;
- esac
-done
-shift $((OPTIND - 1))
-
-# if GEMINI_SANDBOX is not set, see if it is set in user settings
-# note it can be string or boolean, and if missing `npx json` will return empty string
-USER_SETTINGS_FILE="$HOME/.gemini/settings.json"
-if [ -z "${GEMINI_SANDBOX:-}" ] && [ -f "$USER_SETTINGS_FILE" ]; then
- # Check if jq is available (more reliable than npx json)
- if command -v jq &>/dev/null; then
- USER_SANDBOX_SETTING=$(jq -r '.sandbox // empty' "$USER_SETTINGS_FILE" 2>/dev/null || echo "")
- else
- # Fallback to npx json with error handling
- USER_SANDBOX_SETTING=$(sed -e 's/\/\/.*//' -e 's/\/\*.*\*\///g' -e '/^[[:space:]]*\/\//d' "$USER_SETTINGS_FILE" | npx json 'sandbox' 2>/dev/null || echo "")
- fi
-
- # Avoid setting GEMINI_SANDBOX to complex objects
- if [ -n "$USER_SANDBOX_SETTING" ] && [[ ! "$USER_SANDBOX_SETTING" =~ ^\{.*\}$ ]]; then
- GEMINI_SANDBOX=$USER_SANDBOX_SETTING
- fi
-fi
-
-# if GEMINI_SANDBOX is not set, try to source .env in case set there
-# allow .env to be in any ancestor directory (same as findEnvFile in config.ts)
-# prefer gemini-specific .env under .gemini folder (also same as in findEnvFile)
-if [ -z "${GEMINI_SANDBOX:-}" ]; then
- current_dir=$(pwd)
- dot_env_sourced=false
- while [ "$current_dir" != "/" ]; do
- if [ -f "$current_dir/.gemini/.env" ]; then
- source "$current_dir/.gemini/.env"
- dot_env_sourced=true
- break
- elif [ -f "$current_dir/.env" ]; then
- source "$current_dir/.env"
- dot_env_sourced=true
- break
- fi
- current_dir=$(dirname "$current_dir")
- done
- # if .env is not found in any ancestor directory, try home as fallback
- if [ "$dot_env_sourced" = false ]; then
- if [ -f "$HOME/.gemini/.env" ]; then
- source "$HOME/.gemini/.env"
- dot_env_sourced=true
- elif [ -f "$HOME/.env" ]; then
- source "$HOME/.env"
- dot_env_sourced=true
- fi
- fi
-fi
-
-# copy and warn about deprecated GEMINI_CODE_SANDBOX
-if [ -n "${GEMINI_CODE_SANDBOX:-}" ]; then
- echo "WARNING: GEMINI_CODE_SANDBOX is deprecated. Use GEMINI_SANDBOX instead." >&2
- GEMINI_SANDBOX=$GEMINI_CODE_SANDBOX
- export GEMINI_SANDBOX
-fi
-
-# lowercase GEMINI_SANDBOX
-GEMINI_SANDBOX=$(echo "${GEMINI_SANDBOX:-}" | tr '[:upper:]' '[:lower:]')
-
-# if GEMINI_SANDBOX is set to 1|true, then try to use docker or podman
-# if non-empty and not 0|false, treat as custom command and check that it exists
-# if empty or 0|false, then fail silently (after checking for possible fallbacks)
-command=""
-if [[ "${GEMINI_SANDBOX:-}" =~ ^(1|true)$ ]]; then
- if command -v docker &>/dev/null; then
- command="docker"
- elif command -v podman &>/dev/null; then
- command="podman"
- else
- echo "ERROR: install docker or podman or specify command in GEMINI_SANDBOX" >&2
- exit 1
- fi
-elif [ -n "${GEMINI_SANDBOX:-}" ] && [[ ! "${GEMINI_SANDBOX:-}" =~ ^(0|false)$ ]]; then
- if ! command -v "$GEMINI_SANDBOX" &>/dev/null; then
- echo "ERROR: missing sandbox command '$GEMINI_SANDBOX' (from GEMINI_SANDBOX)" >&2
- exit 1
- fi
- command="$GEMINI_SANDBOX"
-else
- # if we are on macOS and sandbox-exec is available, use that for minimal sandboxing
- # unless SEATBELT_PROFILE is set to 'none', which we allow as an escape hatch
- if [ "$(uname)" = "Darwin" ] && command -v sandbox-exec &>/dev/null && [ "${SEATBELT_PROFILE:-}" != "none" ]; then
- command="sandbox-exec"
- else # GEMINI_SANDBOX is empty or 0|false, so we fail w/o error msg
- exit 1
- fi
-fi
-
-if [ "$QUIET" = false ]; then echo "$command"; fi
-exit 0
diff --git a/scripts/setup-dev.js b/scripts/setup-dev.js
new file mode 100644
index 00000000..c4e2b22c
--- /dev/null
+++ b/scripts/setup-dev.js
@@ -0,0 +1,42 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { execSync } from 'child_process';
+
+try {
+ execSync('command -v npm', { stdio: 'ignore' });
+} catch {
+ console.log('npm not found. Installing npm via nvm...');
+ try {
+ execSync(
+ 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash',
+ { stdio: 'inherit' },
+ );
+ const nvmsh = `\\. "$HOME/.nvm/nvm.sh"`;
+ execSync(`${nvmsh} && nvm install 22`, { stdio: 'inherit' });
+ execSync(`${nvmsh} && node -v`, { stdio: 'inherit' });
+ execSync(`${nvmsh} && nvm current`, { stdio: 'inherit' });
+ execSync(`${nvmsh} && npm -v`, { stdio: 'inherit' });
+ } catch {
+ console.error('Failed to install nvm or node.');
+ process.exit(1);
+ }
+}
+
+console.log('Development environment setup complete.');
diff --git a/scripts/setup-dev.sh b/scripts/setup-dev.sh
deleted file mode 100755
index de2ae336..00000000
--- a/scripts/setup-dev.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/bash
-# Copyright 2025 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -euo pipefail
-
-# Check if npm is installed
-if ! command -v npm &>/dev/null; then
- echo "npm not found. Installing npm via nvm..."
- # Download and install nvm:
- curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
- # in lieu of restarting the shell
- \. "$HOME/.nvm/nvm.sh"
- # Download and install Node.js:
- nvm install 22
- # Verify the Node.js version:
- node -v # Should print "v22.15.0".
- nvm current # Should print "v22.15.0".
- # Verify npm version:
- npm -v # Should print "10.9.2".
-fi
-
-echo "Development environment setup complete."
diff --git a/scripts/start.js b/scripts/start.js
new file mode 100644
index 00000000..f9f85c8e
--- /dev/null
+++ b/scripts/start.js
@@ -0,0 +1,61 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law_or_agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { spawn, execSync } from 'child_process';
+import { join } from 'path';
+
+const root = join(import.meta.dirname, '..');
+
+// check build status, write warnings to file for app to display if needed
+execSync('node ./scripts/check-build-status.js', {
+ stdio: 'inherit',
+ cwd: root,
+});
+
+// if debugging is enabled and sandboxing is disabled, use --inspect-brk flag
+// note with sandboxing this flag is passed to the binary inside the sandbox
+// inside sandbox SANDBOX should be set and sandbox_command.js should fail
+const nodeArgs = [];
+try {
+ execSync('node scripts/sandbox_command.js -q', {
+ stdio: 'inherit',
+ cwd: root,
+ });
+ if (process.env.DEBUG) {
+ if (process.env.SANDBOX) {
+ const port = process.env.DEBUG_PORT || '9229';
+ nodeArgs.push(`--inspect-brk=0.0.0.0:${port}`);
+ } else {
+ nodeArgs.push('--inspect-brk');
+ }
+ }
+} catch {
+ // ignore
+}
+
+nodeArgs.push('./packages/cli');
+nodeArgs.push(...process.argv.slice(2));
+
+const env = {
+ ...process.env,
+ CLI_VERSION: 'development',
+ DEV: 'true',
+};
+
+spawn('node', nodeArgs, { stdio: 'inherit', env });
diff --git a/scripts/start.sh b/scripts/start.sh
deleted file mode 100755
index 2caf540d..00000000
--- a/scripts/start.sh
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/bin/bash
-# Copyright 2025 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -euo pipefail
-
-# check build status, write warnings to file for app to display if needed
-node ./scripts/check-build-status.js
-
-# if debugging is enabled and sandboxing is disabled, use --inspect-brk flag
-# note with sandboxing this flag is passed to the binary inside the sandbox
-# inside sandbox SANDBOX should be set and sandbox_command.sh should fail
-node_args=()
-if [ -n "${DEBUG:-}" ] && ! scripts/sandbox_command.sh -q; then
- if [ -n "${SANDBOX:-}" ]; then
- port="${DEBUG_PORT:-9229}"
- node_args=("--inspect-brk=0.0.0.0:$port")
- else
- node_args=(--inspect-brk)
- fi
-fi
-node_args+=("./packages/cli" "$@")
-
-# DEV=true to enable React Dev Tools (https://github.com/vadimdemedes/ink?tab=readme-ov-file#using-react-devtools)
-# CLI_VERSION to display in the app ui footer
-CLI_VERSION='development' DEV=true node "${node_args[@]}"
diff --git a/tmperrors.txt b/tmperrors.txt
new file mode 100644
index 00000000..f0e3b068
--- /dev/null
+++ b/tmperrors.txt
@@ -0,0 +1,55 @@
+PS C:\Users\mattk> npx -ddd https://github.com/google-gemini/gemini-cli#windows
+npm verbose cli C:\nvm4w\nodejs\node.exe C:\Users\mattk\AppData\Roaming\npm\node_modules\npm\bin\npm-cli.js
+npm info using [email protected]
+npm info using [email protected]
+npm silly config load:file:C:\Users\mattk\AppData\Roaming\npm\node_modules\npm\npmrc
+npm silly config load:file:C:\Users\mattk\.npmrc
+npm silly config load:file:C:\Users\mattk\AppData\Roaming\npm\etc\npmrc
+npm verbose title npm exec https://github.com/google-gemini/gemini-cli#windows
+npm verbose argv "exec" "--loglevel" "silly" "--" "https://github.com/google-gemini/gemini-cli#windows"
+npm verbose logfile logs-max:10 dir:C:\Users\mattk\AppData\Local\npm-cache\_logs\2025-06-06T04_53_50_750Z-
+npm verbose logfile C:\Users\mattk\AppData\Local\npm-cache\_logs\2025-06-06T04_53_50_750Z-debug-0.log
+npm silly logfile start cleaning logs, removing 1 files
+npm silly logfile done cleaning log files
+npm silly packumentCache heap:4345298944 maxSize:1086324736 maxEntrySize:543162368
+npm silly packumentCache heap:4345298944 maxSize:1086324736 maxEntrySize:543162368
+npm silly packumentCache heap:4345298944 maxSize:1086324736 maxEntrySize:543162368
+Need to install the following packages:
+github:google-gemini/gemini-cli#windows
+Ok to proceed? (y) y
+
+npm silly idealTree buildDeps
+npm silly fetch manifest gemini-cli@github:google-gemini/gemini-cli#windows
+npm silly placeDep ROOT [email protected] OK for: want: github:google-gemini/gemini-cli#windows
+npm silly fetch manifest dotenv@^16.5.0
+npm silly packumentCache full:https://registry.npmjs.org/dotenv cache-miss
+npm http cache https://registry.npmjs.org/dotenv 25ms (cache hit)
+npm silly packumentCache full:https://registry.npmjs.org/dotenv set size:238345 disposed:false
+npm silly placeDep ROOT [email protected] OK for: [email protected] want: ^16.5.0
+npm silly reify moves {}
+npm silly audit bulk request { 'gemini-cli': [ '0.1.0' ], dotenv: [ '16.5.0' ] }
+npm http cache dotenv@https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz 0ms (cache hit)
+npm http fetch POST 200 https://registry.npmjs.org/-/npm/v1/security/advisories/bulk 226ms
+npm silly audit report {}
+npm http fetch GET 404 https://codeload.github.com/google-gemini/gemini-cli/tar.gz/76909ce82dc4a0eab0cd79daecda01e3b0726368 202ms (cache skip)
+npm warn cleanup Failed to remove some directories [
+npm warn cleanup [
+npm warn cleanup 'C:\\Users\\mattk\\AppData\\Local\\npm-cache\\_npx\\43478f5c7b4786be\\node_modules\\dotenv',
+npm warn cleanup [Error: EPERM: operation not permitted, rmdir 'C:\Users\mattk\AppData\Local\npm-cache\_npx\43478f5c7b4786be\node_modules\dotenv\lib'] {
+npm warn cleanup errno: -4048,
+npm warn cleanup code: 'EPERM',
+npm warn cleanup syscall: 'rmdir',
+npm warn cleanup path: 'C:\\Users\\mattk\\AppData\\Local\\npm-cache\\_npx\\43478f5c7b4786be\\node_modules\\dotenv\\lib'
+npm warn cleanup }
+npm warn cleanup ]
+npm warn cleanup ]
+npm silly unfinished npm timer reify 1749185639289
+npm silly unfinished npm timer reify:unpack 1749185646392
+npm silly unfinished npm timer reifyNode:node_modules/gemini-cli 1749185646392
+npm verbose cwd C:\Users\mattk
+npm verbose os Windows_NT 10.0.26100
+npm verbose node v20.19.2
+npm verbose npm v11.4.1
+npm verbose exit 1
+npm verbose code 1
+PS C:\Users\mattk>