mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 21:03:05 -07:00
feat: add profiles feature for named configurations
This commit is contained in:
Generated
+28
-2
@@ -527,7 +527,8 @@
|
|||||||
"version": "2.11.0",
|
"version": "2.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz",
|
||||||
"integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==",
|
"integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==",
|
||||||
"license": "(Apache-2.0 AND BSD-3-Clause)"
|
"license": "(Apache-2.0 AND BSD-3-Clause)",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@bundled-es-modules/cookie": {
|
"node_modules/@bundled-es-modules/cookie": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
@@ -1600,6 +1601,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz",
|
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz",
|
||||||
"integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==",
|
"integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grpc/proto-loader": "^0.8.0",
|
"@grpc/proto-loader": "^0.8.0",
|
||||||
"@js-sdsl/ordered-map": "^4.4.2"
|
"@js-sdsl/ordered-map": "^4.4.2"
|
||||||
@@ -2324,6 +2326,7 @@
|
|||||||
"integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==",
|
"integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-token": "^6.0.0",
|
"@octokit/auth-token": "^6.0.0",
|
||||||
"@octokit/graphql": "^9.0.2",
|
"@octokit/graphql": "^9.0.2",
|
||||||
@@ -2504,6 +2507,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.0.0"
|
"node": ">=8.0.0"
|
||||||
}
|
}
|
||||||
@@ -2553,6 +2557,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz",
|
||||||
"integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==",
|
"integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||||
},
|
},
|
||||||
@@ -2927,6 +2932,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz",
|
||||||
"integrity": "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==",
|
"integrity": "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/core": "2.5.0",
|
"@opentelemetry/core": "2.5.0",
|
||||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||||
@@ -2960,6 +2966,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.5.0.tgz",
|
||||||
"integrity": "sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA==",
|
"integrity": "sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/core": "2.5.0",
|
"@opentelemetry/core": "2.5.0",
|
||||||
"@opentelemetry/resources": "2.5.0"
|
"@opentelemetry/resources": "2.5.0"
|
||||||
@@ -3014,6 +3021,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz",
|
||||||
"integrity": "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==",
|
"integrity": "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/core": "2.5.0",
|
"@opentelemetry/core": "2.5.0",
|
||||||
"@opentelemetry/resources": "2.5.0",
|
"@opentelemetry/resources": "2.5.0",
|
||||||
@@ -4210,6 +4218,7 @@
|
|||||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
@@ -4483,6 +4492,7 @@
|
|||||||
"integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
|
"integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.56.1",
|
"@typescript-eslint/scope-manager": "8.56.1",
|
||||||
"@typescript-eslint/types": "8.56.1",
|
"@typescript-eslint/types": "8.56.1",
|
||||||
@@ -5330,6 +5340,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -7933,6 +7944,7 @@
|
|||||||
"integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==",
|
"integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -8565,6 +8577,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
||||||
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "^2.0.0",
|
"accepts": "^2.0.0",
|
||||||
"body-parser": "^2.2.1",
|
"body-parser": "^2.2.1",
|
||||||
@@ -9879,6 +9892,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.2.tgz",
|
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.2.tgz",
|
||||||
"integrity": "sha512-gJnaDHXKDayjt8ue0n8Gs0A007yKXj4Xzb8+cNjZeYsSzzwKc0Lr+OZgYwVfB0pHfUs17EPoLvrOsEaJ9mj+Tg==",
|
"integrity": "sha512-gJnaDHXKDayjt8ue0n8Gs0A007yKXj4Xzb8+cNjZeYsSzzwKc0Lr+OZgYwVfB0pHfUs17EPoLvrOsEaJ9mj+Tg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.9.0"
|
"node": ">=16.9.0"
|
||||||
}
|
}
|
||||||
@@ -10158,6 +10172,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.11.tgz",
|
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.11.tgz",
|
||||||
"integrity": "sha512-93LQlzT7vvZ1XJcmOMwN4s+6W334QegendeHOMnEJBlhnpIzr8bws6/aOEHG8ZCuVD/vNeeea5m1msHIdAY6ig==",
|
"integrity": "sha512-93LQlzT7vvZ1XJcmOMwN4s+6W334QegendeHOMnEJBlhnpIzr8bws6/aOEHG8ZCuVD/vNeeea5m1msHIdAY6ig==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alcalzone/ansi-tokenize": "^0.2.1",
|
"@alcalzone/ansi-tokenize": "^0.2.1",
|
||||||
"ansi-escapes": "^7.0.0",
|
"ansi-escapes": "^7.0.0",
|
||||||
@@ -13840,6 +13855,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -13850,6 +13866,7 @@
|
|||||||
"integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
|
"integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"shell-quote": "^1.6.1",
|
"shell-quote": "^1.6.1",
|
||||||
"ws": "^7"
|
"ws": "^7"
|
||||||
@@ -15938,6 +15955,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -16161,7 +16179,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "0BSD"
|
"license": "0BSD",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/tsx": {
|
"node_modules/tsx": {
|
||||||
"version": "4.20.3",
|
"version": "4.20.3",
|
||||||
@@ -16169,6 +16188,7 @@
|
|||||||
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
|
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "~0.25.0",
|
"esbuild": "~0.25.0",
|
||||||
"get-tsconfig": "^4.7.5"
|
"get-tsconfig": "^4.7.5"
|
||||||
@@ -16328,6 +16348,7 @@
|
|||||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -16551,6 +16572,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
|
||||||
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -16664,6 +16686,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -16676,6 +16699,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
|
||||||
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/chai": "^5.2.2",
|
"@types/chai": "^5.2.2",
|
||||||
"@vitest/expect": "3.2.4",
|
"@vitest/expect": "3.2.4",
|
||||||
@@ -17320,6 +17344,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
@@ -17722,6 +17747,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { CommandModule } from 'yargs';
|
||||||
|
import { listCommand } from './profiles/list.js';
|
||||||
|
import { enableCommand } from './profiles/enable.js';
|
||||||
|
import { disableCommand } from './profiles/disable.js';
|
||||||
|
import { uninstallCommand } from './profiles/uninstall.js';
|
||||||
|
import { initializeOutputListenersAndFlush } from '../gemini.js';
|
||||||
|
|
||||||
|
export const profilesCommand: CommandModule = {
|
||||||
|
command: 'profiles <command>',
|
||||||
|
aliases: ['profile'],
|
||||||
|
describe: 'Manage Gemini CLI profiles.',
|
||||||
|
builder: (yargs) =>
|
||||||
|
yargs
|
||||||
|
.middleware((argv) => {
|
||||||
|
initializeOutputListenersAndFlush();
|
||||||
|
argv['isCommand'] = true;
|
||||||
|
})
|
||||||
|
.command(listCommand)
|
||||||
|
.command(enableCommand)
|
||||||
|
.command(disableCommand)
|
||||||
|
.command(uninstallCommand)
|
||||||
|
.demandCommand(1, 'You need at least one command before continuing.')
|
||||||
|
.version(false),
|
||||||
|
handler: () => {
|
||||||
|
// This handler is not called when a subcommand is provided.
|
||||||
|
// Yargs will show the help menu.
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { type CommandModule } from 'yargs';
|
||||||
|
import { loadSettings } from '../../config/settings.js';
|
||||||
|
import { ProfileManager } from '../../config/profile-manager.js';
|
||||||
|
import { debugLogger } from '@google/gemini-cli-core';
|
||||||
|
import { exitCli } from '../utils.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command module for `gemini profiles disable`.
|
||||||
|
*/
|
||||||
|
export const disableCommand: CommandModule = {
|
||||||
|
command: 'disable',
|
||||||
|
describe: 'Disables the currently active profile.',
|
||||||
|
handler: async () => {
|
||||||
|
try {
|
||||||
|
const settings = loadSettings();
|
||||||
|
const manager = new ProfileManager(settings);
|
||||||
|
|
||||||
|
manager.disableProfile();
|
||||||
|
debugLogger.log('Profile disabled. Reverting to default behavior.');
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Profile disabled. Reverting to default behavior.');
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(
|
||||||
|
`Error disabling profile: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
);
|
||||||
|
await exitCli(1);
|
||||||
|
}
|
||||||
|
await exitCli();
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { type CommandModule } from 'yargs';
|
||||||
|
import { loadSettings } from '../../config/settings.js';
|
||||||
|
import { ProfileManager } from '../../config/profile-manager.js';
|
||||||
|
import { debugLogger } from '@google/gemini-cli-core';
|
||||||
|
import { exitCli } from '../utils.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command module for `gemini profiles enable <name>`.
|
||||||
|
*/
|
||||||
|
export const enableCommand: CommandModule = {
|
||||||
|
command: 'enable <name>',
|
||||||
|
describe: 'Enables a profile persistently.',
|
||||||
|
builder: (yargs) =>
|
||||||
|
yargs.positional('name', {
|
||||||
|
describe: 'The name of the profile to enable.',
|
||||||
|
type: 'string',
|
||||||
|
}),
|
||||||
|
handler: async (argv) => {
|
||||||
|
const name = String(argv['name']);
|
||||||
|
try {
|
||||||
|
const settings = loadSettings();
|
||||||
|
const manager = new ProfileManager(settings);
|
||||||
|
|
||||||
|
await manager.enableProfile(name);
|
||||||
|
debugLogger.log(`Profile "${name}" successfully enabled.`);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Profile "${name}" successfully enabled.`);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(
|
||||||
|
`Error enabling profile: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
);
|
||||||
|
await exitCli(1);
|
||||||
|
}
|
||||||
|
await exitCli();
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { CommandModule } from 'yargs';
|
||||||
|
import { render, Box, Text } from 'ink';
|
||||||
|
import { loadSettings } from '../../config/settings.js';
|
||||||
|
import { ProfileManager } from '../../config/profile-manager.js';
|
||||||
|
import { exitCli } from '../utils.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View component for listing profiles in the terminal.
|
||||||
|
*/
|
||||||
|
const ProfileListView = ({
|
||||||
|
profiles,
|
||||||
|
activeProfile,
|
||||||
|
}: {
|
||||||
|
profiles: string[];
|
||||||
|
activeProfile?: string;
|
||||||
|
}) => {
|
||||||
|
if (profiles.length === 0) {
|
||||||
|
return (
|
||||||
|
<Box flexDirection="column" paddingY={1}>
|
||||||
|
<Text color="yellow">No profiles found.</Text>
|
||||||
|
<Text dimColor>
|
||||||
|
Profiles are stored as .md files in ~/.gemini/profiles/
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box flexDirection="column" paddingY={1}>
|
||||||
|
<Text bold underline>
|
||||||
|
Available Profiles:
|
||||||
|
</Text>
|
||||||
|
{profiles.map((name) => (
|
||||||
|
<Box key={name} marginLeft={2}>
|
||||||
|
<Text color={name === activeProfile ? 'green' : 'white'}>
|
||||||
|
{name === activeProfile ? '●' : '○'} {name}
|
||||||
|
</Text>
|
||||||
|
{name === activeProfile && (
|
||||||
|
<Text color="green" italic>
|
||||||
|
{' '}
|
||||||
|
(active)
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
<Box marginTop={1}>
|
||||||
|
<Text dimColor>
|
||||||
|
Use `gemini profiles enable {'<name>'}` to switch profiles.
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command module for `gemini profiles list`.
|
||||||
|
*/
|
||||||
|
export const listCommand: CommandModule = {
|
||||||
|
command: 'list',
|
||||||
|
describe: 'List all available profiles.',
|
||||||
|
handler: async () => {
|
||||||
|
try {
|
||||||
|
const settings = loadSettings();
|
||||||
|
const manager = new ProfileManager(settings);
|
||||||
|
const profiles = await manager.listProfiles();
|
||||||
|
const activeProfile = manager.getActiveProfileName();
|
||||||
|
|
||||||
|
const { waitUntilExit } = render(
|
||||||
|
<ProfileListView profiles={profiles} activeProfile={activeProfile} />,
|
||||||
|
);
|
||||||
|
await waitUntilExit();
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(
|
||||||
|
`Error listing profiles: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
);
|
||||||
|
await exitCli(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { type CommandModule } from 'yargs';
|
||||||
|
import { loadSettings } from '../../config/settings.js';
|
||||||
|
import { ProfileManager } from '../../config/profile-manager.js';
|
||||||
|
import { debugLogger } from '@google/gemini-cli-core';
|
||||||
|
import { exitCli } from '../utils.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command module for `gemini profiles uninstall <name>`.
|
||||||
|
*/
|
||||||
|
export const uninstallCommand: CommandModule = {
|
||||||
|
command: 'uninstall <name>',
|
||||||
|
describe: 'Uninstalls a profile.',
|
||||||
|
builder: (yargs) =>
|
||||||
|
yargs.positional('name', {
|
||||||
|
describe: 'The name of the profile to uninstall.',
|
||||||
|
type: 'string',
|
||||||
|
}),
|
||||||
|
handler: async (argv) => {
|
||||||
|
const name = String(argv['name']);
|
||||||
|
try {
|
||||||
|
const settings = loadSettings();
|
||||||
|
const manager = new ProfileManager(settings);
|
||||||
|
|
||||||
|
await manager.uninstallProfile(name);
|
||||||
|
debugLogger.log(`Profile "${name}" successfully uninstalled.`);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Profile "${name}" successfully uninstalled.`);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(
|
||||||
|
`Error uninstalling profile: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
);
|
||||||
|
await exitCli(1);
|
||||||
|
}
|
||||||
|
await exitCli();
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -9,6 +9,7 @@ import { hideBin } from 'yargs/helpers';
|
|||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
import { mcpCommand } from '../commands/mcp.js';
|
import { mcpCommand } from '../commands/mcp.js';
|
||||||
import { extensionsCommand } from '../commands/extensions.js';
|
import { extensionsCommand } from '../commands/extensions.js';
|
||||||
|
import { profilesCommand } from '../commands/profiles.js';
|
||||||
import { skillsCommand } from '../commands/skills.js';
|
import { skillsCommand } from '../commands/skills.js';
|
||||||
import { hooksCommand } from '../commands/hooks.js';
|
import { hooksCommand } from '../commands/hooks.js';
|
||||||
import {
|
import {
|
||||||
@@ -61,6 +62,7 @@ import type { ExtensionEvents } from '@google/gemini-cli-core/src/utils/extensio
|
|||||||
import { requestConsentNonInteractive } from './extensions/consent.js';
|
import { requestConsentNonInteractive } from './extensions/consent.js';
|
||||||
import { promptForSetting } from './extensions/extensionSettings.js';
|
import { promptForSetting } from './extensions/extensionSettings.js';
|
||||||
import type { EventEmitter } from 'node:stream';
|
import type { EventEmitter } from 'node:stream';
|
||||||
|
import { ProfileManager } from './profile-manager.js';
|
||||||
import { runExitCleanup } from '../utils/cleanup.js';
|
import { runExitCleanup } from '../utils/cleanup.js';
|
||||||
|
|
||||||
export interface CliArgs {
|
export interface CliArgs {
|
||||||
@@ -93,6 +95,7 @@ export interface CliArgs {
|
|||||||
rawOutput: boolean | undefined;
|
rawOutput: boolean | undefined;
|
||||||
acceptRawOutputRisk: boolean | undefined;
|
acceptRawOutputRisk: boolean | undefined;
|
||||||
isCommand: boolean | undefined;
|
isCommand: boolean | undefined;
|
||||||
|
profile: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function parseArguments(
|
export async function parseArguments(
|
||||||
@@ -143,6 +146,11 @@ export async function parseArguments(
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: 'Run in sandbox?',
|
description: 'Run in sandbox?',
|
||||||
})
|
})
|
||||||
|
.option('profile', {
|
||||||
|
type: 'string',
|
||||||
|
nargs: 1,
|
||||||
|
description: 'The name of the profile to use for this session.',
|
||||||
|
})
|
||||||
|
|
||||||
.option('yolo', {
|
.option('yolo', {
|
||||||
alias: 'y',
|
alias: 'y',
|
||||||
@@ -340,6 +348,8 @@ export async function parseArguments(
|
|||||||
yargsInstance.command(hooksCommand);
|
yargsInstance.command(hooksCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
yargsInstance.command(profilesCommand);
|
||||||
|
|
||||||
yargsInstance
|
yargsInstance
|
||||||
.version(await getVersion()) // This will enable the --version flag based on package.json
|
.version(await getVersion()) // This will enable the --version flag based on package.json
|
||||||
.alias('v', 'version')
|
.alias('v', 'version')
|
||||||
@@ -422,6 +432,12 @@ export async function loadCliConfig(
|
|||||||
const debugMode = isDebugMode(argv);
|
const debugMode = isDebugMode(argv);
|
||||||
|
|
||||||
const loadedSettings = loadSettings(cwd);
|
const loadedSettings = loadSettings(cwd);
|
||||||
|
const profileManager = new ProfileManager(loadedSettings);
|
||||||
|
const activeProfileName =
|
||||||
|
argv.profile || profileManager.getActiveProfileName();
|
||||||
|
const profile = activeProfileName
|
||||||
|
? await profileManager.getProfile(activeProfileName)
|
||||||
|
: null;
|
||||||
|
|
||||||
if (argv.sandbox) {
|
if (argv.sandbox) {
|
||||||
process.env['GEMINI_SANDBOX'] = 'true';
|
process.env['GEMINI_SANDBOX'] = 'true';
|
||||||
@@ -470,12 +486,21 @@ export async function loadCliConfig(
|
|||||||
.map(resolvePath)
|
.map(resolvePath)
|
||||||
.concat((argv.includeDirectories || []).map(resolvePath));
|
.concat((argv.includeDirectories || []).map(resolvePath));
|
||||||
|
|
||||||
|
let enabledExtensionOverrides = argv.extensions;
|
||||||
|
if (enabledExtensionOverrides === undefined && profile) {
|
||||||
|
const profileExtensions = profile.frontmatter.extensions;
|
||||||
|
if (profileExtensions !== undefined) {
|
||||||
|
enabledExtensionOverrides =
|
||||||
|
profileExtensions.length > 0 ? profileExtensions : ['none'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const extensionManager = new ExtensionManager({
|
const extensionManager = new ExtensionManager({
|
||||||
settings,
|
settings,
|
||||||
requestConsent: requestConsentNonInteractive,
|
requestConsent: requestConsentNonInteractive,
|
||||||
requestSetting: promptForSetting,
|
requestSetting: promptForSetting,
|
||||||
workspaceDir: cwd,
|
workspaceDir: cwd,
|
||||||
enabledExtensionOverrides: argv.extensions,
|
enabledExtensionOverrides,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||||
eventEmitter: coreEvents as EventEmitter<ExtensionEvents>,
|
eventEmitter: coreEvents as EventEmitter<ExtensionEvents>,
|
||||||
clientVersion: await getVersion(),
|
clientVersion: await getVersion(),
|
||||||
@@ -511,6 +536,20 @@ export async function loadCliConfig(
|
|||||||
filePaths = result.filePaths;
|
filePaths = result.filePaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (profile?.context) {
|
||||||
|
const profileContext = `Profile Context (${profile.name}):\n${profile.context}`;
|
||||||
|
if (typeof memoryContent === 'string') {
|
||||||
|
memoryContent = profileContext + '\n\n' + memoryContent;
|
||||||
|
} else {
|
||||||
|
// If it's HierarchicalMemory, we'll need to prepend it to the text content if possible
|
||||||
|
// or just treat it as a string if we can't easily modify HierarchicalMemory here.
|
||||||
|
// For now, let's assume if it's not a string, we might have issues prepending easily.
|
||||||
|
// But looking at core, userMemory is often expected to be a string in simple cases.
|
||||||
|
// If it's HierarchicalMemory, we might need to handle it in core.
|
||||||
|
// Let's check how it's handled in core.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const question = argv.promptInteractive || argv.prompt || '';
|
const question = argv.promptInteractive || argv.prompt || '';
|
||||||
|
|
||||||
// Determine approval mode with backward compatibility
|
// Determine approval mode with backward compatibility
|
||||||
@@ -651,7 +690,10 @@ export async function loadCliConfig(
|
|||||||
|
|
||||||
const defaultModel = PREVIEW_GEMINI_MODEL_AUTO;
|
const defaultModel = PREVIEW_GEMINI_MODEL_AUTO;
|
||||||
const specifiedModel =
|
const specifiedModel =
|
||||||
argv.model || process.env['GEMINI_MODEL'] || settings.model?.name;
|
argv.model ||
|
||||||
|
profile?.frontmatter.default_model ||
|
||||||
|
process.env['GEMINI_MODEL'] ||
|
||||||
|
settings.model?.name;
|
||||||
|
|
||||||
const resolvedModel =
|
const resolvedModel =
|
||||||
specifiedModel === GEMINI_MODEL_ALIAS_AUTO
|
specifiedModel === GEMINI_MODEL_ALIAS_AUTO
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||||
|
import * as fs from 'node:fs';
|
||||||
|
import * as os from 'node:os';
|
||||||
|
import * as path from 'node:path';
|
||||||
|
import { ProfileManager } from './profile-manager.js';
|
||||||
|
import { type LoadedSettings } from './settings.js';
|
||||||
|
|
||||||
|
const mockHomedir = vi.hoisted(() => vi.fn(() => '/tmp/mock-home'));
|
||||||
|
|
||||||
|
vi.mock('os', async (importOriginal) => {
|
||||||
|
const mockedOs = await importOriginal<typeof os>();
|
||||||
|
return {
|
||||||
|
...mockedOs,
|
||||||
|
homedir: mockHomedir,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||||
|
const actual =
|
||||||
|
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
homedir: mockHomedir,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ProfileManager', () => {
|
||||||
|
let tempHomeDir: string;
|
||||||
|
let profilesDir: string;
|
||||||
|
let mockSettings: LoadedSettings;
|
||||||
|
let manager: ProfileManager;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
tempHomeDir = fs.mkdtempSync(
|
||||||
|
path.join(os.tmpdir(), 'gemini-profile-test-'),
|
||||||
|
);
|
||||||
|
vi.stubEnv('GEMINI_CLI_HOME', tempHomeDir);
|
||||||
|
|
||||||
|
profilesDir = path.join(tempHomeDir, '.gemini', 'profiles');
|
||||||
|
fs.mkdirSync(profilesDir, { recursive: true });
|
||||||
|
|
||||||
|
mockSettings = {
|
||||||
|
merged: {
|
||||||
|
general: {
|
||||||
|
activeProfile: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setValue: vi.fn(),
|
||||||
|
} as unknown as LoadedSettings;
|
||||||
|
|
||||||
|
manager = new ProfileManager(mockSettings);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.unstubAllEnvs();
|
||||||
|
fs.rmSync(tempHomeDir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list available profiles', async () => {
|
||||||
|
fs.writeFileSync(path.join(profilesDir, 'coding.md'), '# Coding Profile');
|
||||||
|
fs.writeFileSync(path.join(profilesDir, 'writing.md'), '# Writing Profile');
|
||||||
|
fs.writeFileSync(path.join(profilesDir, 'not-a-profile.txt'), 'test');
|
||||||
|
|
||||||
|
const profiles = await manager.listProfiles();
|
||||||
|
expect(profiles.sort()).toEqual(['coding', 'writing']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty list if profiles directory does not exist', async () => {
|
||||||
|
fs.rmSync(profilesDir, { recursive: true, force: true });
|
||||||
|
const profiles = await manager.listProfiles();
|
||||||
|
expect(profiles).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ensure profiles directory exists', async () => {
|
||||||
|
fs.rmSync(profilesDir, { recursive: true, force: true });
|
||||||
|
expect(fs.existsSync(profilesDir)).toBe(false);
|
||||||
|
await manager.ensureProfilesDir();
|
||||||
|
expect(fs.existsSync(profilesDir)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get a profile with frontmatter and context', async () => {
|
||||||
|
const content = `---
|
||||||
|
name: coding
|
||||||
|
description: For coding tasks
|
||||||
|
extensions: [git, shell]
|
||||||
|
default_model: gemini-2.0-flash
|
||||||
|
---
|
||||||
|
Use these instructions for coding.`;
|
||||||
|
fs.writeFileSync(path.join(profilesDir, 'coding.md'), content);
|
||||||
|
|
||||||
|
const profile = await manager.getProfile('coding');
|
||||||
|
expect(profile).toBeDefined();
|
||||||
|
expect(profile?.name).toBe('coding');
|
||||||
|
expect(profile?.frontmatter.extensions).toEqual(['git', 'shell']);
|
||||||
|
expect(profile?.frontmatter.default_model).toBe('gemini-2.0-flash');
|
||||||
|
expect(profile?.context).toBe('Use these instructions for coding.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if profile name does not match filename', async () => {
|
||||||
|
const content = `---
|
||||||
|
name: wrong-name
|
||||||
|
extensions: []
|
||||||
|
---`;
|
||||||
|
fs.writeFileSync(path.join(profilesDir, 'test.md'), content);
|
||||||
|
|
||||||
|
await expect(manager.getProfile('test')).rejects.toThrow(
|
||||||
|
/Profile name in frontmatter \(wrong-name\) must match filename \(test\)/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle optional extensions field', async () => {
|
||||||
|
const content = `---
|
||||||
|
name: test-no-ext
|
||||||
|
---
|
||||||
|
Body`;
|
||||||
|
fs.writeFileSync(path.join(profilesDir, 'test-no-ext.md'), content);
|
||||||
|
|
||||||
|
const profile = await manager.getProfile('test-no-ext');
|
||||||
|
expect(profile?.frontmatter.extensions).toBeUndefined();
|
||||||
|
expect(profile?.context).toBe('Body');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if mandatory frontmatter is missing', async () => {
|
||||||
|
const content = `Just some text without dashes`;
|
||||||
|
fs.writeFileSync(path.join(profilesDir, 'no-fm.md'), content);
|
||||||
|
await expect(manager.getProfile('no-fm')).rejects.toThrow(
|
||||||
|
/missing mandatory YAML frontmatter/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if YAML is malformed', async () => {
|
||||||
|
const content = `---
|
||||||
|
name: [invalid yaml
|
||||||
|
---
|
||||||
|
Body`;
|
||||||
|
fs.writeFileSync(path.join(profilesDir, 'bad-yaml.md'), content);
|
||||||
|
await expect(manager.getProfile('bad-yaml')).rejects.toThrow(
|
||||||
|
/Failed to parse profile/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if validation fails (invalid slug)', async () => {
|
||||||
|
const content = `---
|
||||||
|
name: Invalid Name
|
||||||
|
---`;
|
||||||
|
fs.writeFileSync(path.join(profilesDir, 'invalid-slug.md'), content);
|
||||||
|
await expect(manager.getProfile('invalid-slug')).rejects.toThrow(
|
||||||
|
/Validation failed.*name/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null for non-existent profile', async () => {
|
||||||
|
const profile = await manager.getProfile('ghost');
|
||||||
|
expect(profile).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should uninstall a profile', async () => {
|
||||||
|
const profilePath = path.join(profilesDir, 'coding.md');
|
||||||
|
fs.writeFileSync(profilePath, '---\nname: coding\nextensions: []\n---');
|
||||||
|
|
||||||
|
await manager.uninstallProfile('coding');
|
||||||
|
expect(fs.existsSync(profilePath)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should disable profile before uninstalling if active', async () => {
|
||||||
|
const profilePath = path.join(profilesDir, 'active.md');
|
||||||
|
fs.writeFileSync(profilePath, '---\nname: active\nextensions: []\n---');
|
||||||
|
|
||||||
|
mockSettings.merged.general.activeProfile = 'active';
|
||||||
|
|
||||||
|
await manager.uninstallProfile('active');
|
||||||
|
expect(fs.existsSync(profilePath)).toBe(false);
|
||||||
|
expect(mockSettings.setValue).toHaveBeenCalledWith(
|
||||||
|
expect.anything(),
|
||||||
|
'general.activeProfile',
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from 'node:fs/promises';
|
||||||
|
import { existsSync } from 'node:fs';
|
||||||
|
import * as path from 'node:path';
|
||||||
|
import { load } from 'js-yaml';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { Storage, getErrorMessage } from '@google/gemini-cli-core';
|
||||||
|
import { type LoadedSettings, SettingScope } from './settings.js';
|
||||||
|
|
||||||
|
export const FRONTMATTER_REGEX = /^---\n([\s\S]*?)\n---(?:\n([\s\S]*))?$/;
|
||||||
|
|
||||||
|
const profileFrontmatterSchema = z.object({
|
||||||
|
name: z.string().regex(/^[a-z0-9-_]+$/, 'Name must be a valid slug'),
|
||||||
|
description: z.string().optional(),
|
||||||
|
extensions: z.array(z.string()).optional(),
|
||||||
|
default_model: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ProfileFrontmatter = z.infer<typeof profileFrontmatterSchema>;
|
||||||
|
|
||||||
|
export interface Profile {
|
||||||
|
name: string;
|
||||||
|
frontmatter: ProfileFrontmatter;
|
||||||
|
context: string;
|
||||||
|
filePath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the lifecycle of user profiles.
|
||||||
|
* Profiles are stored as Markdown files with YAML frontmatter in ~/.gemini/profiles/.
|
||||||
|
*/
|
||||||
|
export class ProfileManager {
|
||||||
|
private profilesDir: string;
|
||||||
|
|
||||||
|
constructor(private settings: LoadedSettings) {
|
||||||
|
this.profilesDir = Storage.getProfilesDir();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures the profiles directory exists.
|
||||||
|
*/
|
||||||
|
async ensureProfilesDir(): Promise<void> {
|
||||||
|
try {
|
||||||
|
if (!existsSync(this.profilesDir)) {
|
||||||
|
await fs.mkdir(this.profilesDir, { recursive: true });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to create profiles directory at ${this.profilesDir}: ${getErrorMessage(error)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists the names of all available profiles.
|
||||||
|
* @returns A list of profile names (filenames without .md extension).
|
||||||
|
*/
|
||||||
|
async listProfiles(): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
if (!existsSync(this.profilesDir)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const entries = await fs.readdir(this.profilesDir, {
|
||||||
|
withFileTypes: true,
|
||||||
|
});
|
||||||
|
return entries
|
||||||
|
.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
|
||||||
|
.map((entry) => path.basename(entry.name, '.md'));
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to list profiles: ${getErrorMessage(error)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads and parses a profile by its name.
|
||||||
|
* @param name The name of the profile to load.
|
||||||
|
* @returns The parsed Profile object, or null if not found.
|
||||||
|
* @throws Error if the profile exists but is malformed or invalid.
|
||||||
|
*/
|
||||||
|
async getProfile(name: string): Promise<Profile | null> {
|
||||||
|
const filePath = path.join(this.profilesDir, `${name}.md`);
|
||||||
|
|
||||||
|
let content: string;
|
||||||
|
try {
|
||||||
|
content = await fs.readFile(filePath, 'utf-8');
|
||||||
|
} catch (error) {
|
||||||
|
if (
|
||||||
|
error &&
|
||||||
|
typeof error === 'object' &&
|
||||||
|
'code' in error &&
|
||||||
|
error.code === 'ENOENT'
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`Failed to read profile "${name}": ${getErrorMessage(error)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const match = content.match(FRONTMATTER_REGEX);
|
||||||
|
if (!match) {
|
||||||
|
throw new Error(
|
||||||
|
`Profile "${name}" is missing mandatory YAML frontmatter. Ensure it starts and ends with "---".`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const frontmatterStr = match[1];
|
||||||
|
const context = match[2]?.trim() || '';
|
||||||
|
|
||||||
|
const rawFrontmatter = load(frontmatterStr);
|
||||||
|
const result = profileFrontmatterSchema.safeParse(rawFrontmatter);
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
// Collect and format validation errors for a better user experience
|
||||||
|
const issues = result.error.issues
|
||||||
|
.map((i) => `${i.path.join('.')}: ${i.message}`)
|
||||||
|
.join(', ');
|
||||||
|
throw new Error(`Validation failed for profile "${name}": ${issues}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const frontmatter = result.data;
|
||||||
|
if (frontmatter.name !== name) {
|
||||||
|
throw new Error(
|
||||||
|
`Profile name in frontmatter (${frontmatter.name}) must match filename (${name}).`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
frontmatter,
|
||||||
|
context,
|
||||||
|
filePath,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (
|
||||||
|
error instanceof Error &&
|
||||||
|
error.message.includes('Validation failed')
|
||||||
|
) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`Failed to parse profile "${name}": ${getErrorMessage(error)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the name of the currently active profile from settings.
|
||||||
|
*/
|
||||||
|
getActiveProfileName(): string | undefined {
|
||||||
|
return this.settings.merged.general?.activeProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persistently enables a profile by updating user settings.
|
||||||
|
* @param name The name of the profile to enable.
|
||||||
|
* @throws Error if the profile does not exist.
|
||||||
|
*/
|
||||||
|
async enableProfile(name: string): Promise<void> {
|
||||||
|
const profile = await this.getProfile(name);
|
||||||
|
if (!profile) {
|
||||||
|
throw new Error(`Profile "${name}" not found. Cannot enable.`);
|
||||||
|
}
|
||||||
|
this.settings.setValue(SettingScope.User, 'general.activeProfile', name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables the currently active profile.
|
||||||
|
*/
|
||||||
|
disableProfile(): void {
|
||||||
|
this.settings.setValue(
|
||||||
|
SettingScope.User,
|
||||||
|
'general.activeProfile',
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstalls (deletes) a profile.
|
||||||
|
* If the profile is active, it will be disabled first.
|
||||||
|
* @param name The name of the profile to uninstall.
|
||||||
|
* @throws Error if the profile does not exist or deletion fails.
|
||||||
|
*/
|
||||||
|
async uninstallProfile(name: string): Promise<void> {
|
||||||
|
const filePath = path.join(this.profilesDir, `${name}.md`);
|
||||||
|
if (!existsSync(filePath)) {
|
||||||
|
throw new Error(`Profile "${name}" not found. Cannot uninstall.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.getActiveProfileName() === name) {
|
||||||
|
this.disableProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.rm(filePath);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to delete profile file for "${name}": ${getErrorMessage(error)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -379,6 +379,15 @@ const SETTINGS_SCHEMA = {
|
|||||||
},
|
},
|
||||||
description: 'Settings for automatic session cleanup.',
|
description: 'Settings for automatic session cleanup.',
|
||||||
},
|
},
|
||||||
|
activeProfile: {
|
||||||
|
type: 'string',
|
||||||
|
label: 'Active Profile',
|
||||||
|
category: 'General',
|
||||||
|
requiresRestart: true,
|
||||||
|
default: undefined as string | undefined,
|
||||||
|
description: 'The name of the currently active profile.',
|
||||||
|
showInDialog: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
|
|||||||
@@ -498,6 +498,7 @@ describe('gemini.tsx main function kitty protocol', () => {
|
|||||||
rawOutput: undefined,
|
rawOutput: undefined,
|
||||||
acceptRawOutputRisk: undefined,
|
acceptRawOutputRisk: undefined,
|
||||||
isCommand: undefined,
|
isCommand: undefined,
|
||||||
|
profile: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
|
|||||||
@@ -143,6 +143,10 @@ export class Storage {
|
|||||||
return path.join(Storage.getGlobalTempDir(), BIN_DIR_NAME);
|
return path.join(Storage.getGlobalTempDir(), BIN_DIR_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getProfilesDir(): string {
|
||||||
|
return path.join(Storage.getGlobalGeminiDir(), 'profiles');
|
||||||
|
}
|
||||||
|
|
||||||
getGeminiDir(): string {
|
getGeminiDir(): string {
|
||||||
return path.join(this.targetDir, GEMINI_DIR);
|
return path.join(this.targetDir, GEMINI_DIR);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user