diff --git a/.github/workflows/skills-validate-fallback.yml b/.github/workflows/skills-validate-fallback.yml new file mode 100644 index 0000000..b49eb62 --- /dev/null +++ b/.github/workflows/skills-validate-fallback.yml @@ -0,0 +1,37 @@ +# Copyright 2026 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. + +name: lint + +on: + push: + paths-ignore: + - "skills/**" + pull_request: + paths-ignore: + - "skills/**" + pull_request_target: + types: [labeled] + paths-ignore: + - "skills/**" + workflow_dispatch: + +jobs: + skills-validate: + runs-on: ubuntu-latest + steps: + - name: Skip Skill Validation + run: | + echo "No changes detected in 'skills/' directory. Skipping validation." + echo "This job ensures the required 'skills-validate' status check passes." diff --git a/.github/workflows/skills-validate.yml b/.github/workflows/skills-validate.yml new file mode 100644 index 0000000..becb9b4 --- /dev/null +++ b/.github/workflows/skills-validate.yml @@ -0,0 +1,56 @@ +# Copyright 2026 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. + +name: Validate Skills + +on: + push: + paths: + - "skills/**" + pull_request: + paths: + - "skills/**" + pull_request_target: + types: [labeled] + paths: + - "skills/**" + +jobs: + skills-validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 + with: + python-version: "3.11" + + - name: Install skills-ref + run: | + pip install "git+https://github.com/agentskills/agentskills.git#subdirectory=skills-ref" + + - name: Validate Skills + run: | + failed=0 + for skill_dir in skills/*/; do + if [ -d "$skill_dir" ]; then + echo "Validating $skill_dir..." + if ! skills-ref validate "$skill_dir"; then + echo "Validation failed for $skill_dir" + failed=1 + fi + fi + done + exit $failed diff --git a/gemini-extension.json b/gemini-extension.json index c46401a..2221178 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -2,24 +2,6 @@ "name": "cloud-sql-mysql", "version": "0.1.9", "description": "Connect and interact with a Cloud SQL for MySQL database and data", - "mcpServers": { - "cloud_sql_mysql_admin": { - "command": "${extensionPath}${/}toolbox", - "args": [ - "--prebuilt", - "cloud-sql-mysql-admin", - "--stdio" - ] - }, - "cloud_sql_mysql": { - "command": "${extensionPath}${/}toolbox", - "args": [ - "--prebuilt", - "cloud-sql-mysql", - "--stdio" - ] - } - }, "contextFileName": "CLOUD-SQL-MYSQL.md", "settings": [ { diff --git a/skills/cloud-sql-mysql-admin/SKILL.md b/skills/cloud-sql-mysql-admin/SKILL.md new file mode 100644 index 0000000..6f1624e --- /dev/null +++ b/skills/cloud-sql-mysql-admin/SKILL.md @@ -0,0 +1,125 @@ +--- +name: cloud-sql-mysql-admin +description: Use these skills when you need to provision new Cloud SQL for MySQL instances, create databases and users, clone existing environments, and monitor the progress of infrastructure operations. +--- + +## Usage + +All scripts can be executed using Node.js. Replace `` and `` with actual values. + +**Bash:** +`node /scripts/.js '{"": ""}'` + +**PowerShell:** +`node /scripts/.js '{\"\": \"\"}'` + +Note: The scripts automatically load the environment variables from various .env files. Do not ask the user to set vars unless skill executions fails due to env var absence. + + +## Scripts + + +### create_database + + + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| project | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | | +| instance | string | The ID of the instance where the database will be created. | Yes | | +| name | string | The name for the new database. Must be unique within the instance. | Yes | | + + +--- + +### create_instance + + + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| project | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | | +| name | string | The name of the instance | Yes | | +| databaseVersion | string | The database version for MySQL. If not specified, defaults to the latest available version (e.g., MYSQL_8_4). | No | `MYSQL_8_4` | +| rootPassword | string | The root password for the instance | Yes | | +| editionPreset | string | The edition of the instance. Can be `Production` or `Development`. This determines the default machine type and availability. Defaults to `Development`. | No | `Development` | + + +--- + +### create_user + + + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| project | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | | +| instance | string | The ID of the instance where the user will be created. | Yes | | +| name | string | The name for the new user. Must be unique within the instance. | Yes | | +| password | string | A secure password for the new user. Not required for IAM users. | No | | +| iamUser | boolean | Set to true to create a Cloud IAM user. | Yes | | + + +--- + +### get_instance + + + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| projectId | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | | +| instanceId | string | The instance ID | Yes | | + + +--- + +### list_databases + +Lists all databases for a Cloud SQL instance. + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| project | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | | +| instance | string | The instance ID | Yes | | + + +--- + +### list_instances + +Lists all type of Cloud SQL instances for a project. + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| project | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | | + + +--- + +### wait_for_operation + + + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| project | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | | +| operation | string | The operation ID | Yes | | + + +--- + diff --git a/skills/cloud-sql-mysql-admin/scripts/create_database.js b/skills/cloud-sql-mysql-admin/scripts/create_database.js new file mode 100755 index 0000000..ca2ced2 --- /dev/null +++ b/skills/cloud-sql-mysql-admin/scripts/create_database.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "create_database"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-admin/scripts/create_instance.js b/skills/cloud-sql-mysql-admin/scripts/create_instance.js new file mode 100755 index 0000000..3c9a178 --- /dev/null +++ b/skills/cloud-sql-mysql-admin/scripts/create_instance.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "create_instance"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-admin/scripts/create_user.js b/skills/cloud-sql-mysql-admin/scripts/create_user.js new file mode 100755 index 0000000..5b36f99 --- /dev/null +++ b/skills/cloud-sql-mysql-admin/scripts/create_user.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "create_user"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-admin/scripts/get_instance.js b/skills/cloud-sql-mysql-admin/scripts/get_instance.js new file mode 100755 index 0000000..7b555e5 --- /dev/null +++ b/skills/cloud-sql-mysql-admin/scripts/get_instance.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "get_instance"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-admin/scripts/list_databases.js b/skills/cloud-sql-mysql-admin/scripts/list_databases.js new file mode 100755 index 0000000..a3ee6d8 --- /dev/null +++ b/skills/cloud-sql-mysql-admin/scripts/list_databases.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "list_databases"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-admin/scripts/list_instances.js b/skills/cloud-sql-mysql-admin/scripts/list_instances.js new file mode 100755 index 0000000..d0f39e6 --- /dev/null +++ b/skills/cloud-sql-mysql-admin/scripts/list_instances.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "list_instances"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-admin/scripts/wait_for_operation.js b/skills/cloud-sql-mysql-admin/scripts/wait_for_operation.js new file mode 100755 index 0000000..6135122 --- /dev/null +++ b/skills/cloud-sql-mysql-admin/scripts/wait_for_operation.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "wait_for_operation"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-data/SKILL.md b/skills/cloud-sql-mysql-data/SKILL.md new file mode 100644 index 0000000..1df8dc8 --- /dev/null +++ b/skills/cloud-sql-mysql-data/SKILL.md @@ -0,0 +1,75 @@ +--- +name: cloud-sql-mysql-data +description: Use these skills when you need to explore your database schema, execute SQL queries to interact with your data, and inspect how MySQL plans to execute your statements. +--- + +## Usage + +All scripts can be executed using Node.js. Replace `` and `` with actual values. + +**Bash:** +`node /scripts/.js '{"": ""}'` + +**PowerShell:** +`node /scripts/.js '{\"\": \"\"}'` + +Note: The scripts automatically load the environment variables from various .env files. Do not ask the user to set vars unless skill executions fails due to env var absence. + + +## Scripts + + +### execute_sql + +Use this skill to execute SQL. + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| sql | string | The sql to execute. | Yes | | + + +--- + +### get_query_plan + +Provide information about how MySQL executes a SQL statement. Common use cases include: 1) analyze query plan to improve its performance, and 2) determine effectiveness of existing indexes and evalueate new ones. + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| sql_statement | string | The sql statement to explain. | Yes | | + + +--- + +### list_active_queries + +Lists top N (default 10) ongoing queries from processlist and innodb_trx, ordered by execution time in descending order. Returns detailed information of those queries in json format, including process id, query, transaction duration, transaction wait duration, process time, transaction state, process state, username with host, transaction rows locked, transaction rows modified, and db schema. + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| min_duration_secs | integer | Optional: Only show queries running for at least this long in seconds | No | `0` | +| limit | integer | Optional: The maximum number of rows to return. | No | `100` | + + +--- + +### list_tables + +Lists detailed schema information (object type, columns, constraints, indexes, triggers, comment) as JSON for user-created tables (ordinary or partitioned). Filters by a comma-separated list of names. If names are omitted, lists all tables in user schemas. + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| table_names | string | Optional: A comma-separated list of table names. If empty, details for all tables will be listed. | No | `` | +| output_format | string | Optional: Use 'simple' for names only or 'detailed' for full info. | No | `detailed` | + + +--- + diff --git a/skills/cloud-sql-mysql-data/scripts/execute_sql.js b/skills/cloud-sql-mysql-data/scripts/execute_sql.js new file mode 100755 index 0000000..27e4127 --- /dev/null +++ b/skills/cloud-sql-mysql-data/scripts/execute_sql.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "execute_sql"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-data/scripts/get_query_plan.js b/skills/cloud-sql-mysql-data/scripts/get_query_plan.js new file mode 100755 index 0000000..d49e104 --- /dev/null +++ b/skills/cloud-sql-mysql-data/scripts/get_query_plan.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "get_query_plan"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-data/scripts/list_active_queries.js b/skills/cloud-sql-mysql-data/scripts/list_active_queries.js new file mode 100755 index 0000000..ca9e382 --- /dev/null +++ b/skills/cloud-sql-mysql-data/scripts/list_active_queries.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "list_active_queries"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-data/scripts/list_tables.js b/skills/cloud-sql-mysql-data/scripts/list_tables.js new file mode 100755 index 0000000..b8f38a7 --- /dev/null +++ b/skills/cloud-sql-mysql-data/scripts/list_tables.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "list_tables"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-lifecycle/SKILL.md b/skills/cloud-sql-mysql-lifecycle/SKILL.md new file mode 100644 index 0000000..f2e6c86 --- /dev/null +++ b/skills/cloud-sql-mysql-lifecycle/SKILL.md @@ -0,0 +1,113 @@ +--- +name: cloud-sql-mysql-lifecycle +description: Use these skills when you need to manage the durability and safety of your data by creating backups, restoring from previous states, or cloning instances for recovery and testing. +--- + +## Usage + +All scripts can be executed using Node.js. Replace `` and `` with actual values. + +**Bash:** +`node /scripts/.js '{"": ""}'` + +**PowerShell:** +`node /scripts/.js '{\"\": \"\"}'` + +Note: The scripts automatically load the environment variables from various .env files. Do not ask the user to set vars unless skill executions fails due to env var absence. + + +## Scripts + + +### clone_instance + +Clone an existing Cloud SQL instance into a new instance. The clone can be a direct copy of the source instance, or a point-in-time-recovery (PITR) clone from a specific timestamp. The call returns a Cloud SQL Operation object. Call wait_for_operation skill after this, make sure to use multiplier as 4 to poll the opertation status till it is marked DONE. + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| project | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | | +| sourceInstanceName | string | The name of the instance to be cloned. | Yes | | +| destinationInstanceName | string | The name of the new instance that will be created by cloning the source instance. | Yes | | +| pointInTime | string | The timestamp in RFC 3339 format to which the source instance should be cloned. | No | | +| preferredZone | string | The preferred zone for the new instance. | No | | +| preferredSecondaryZone | string | The preferred secondary zone for the new instance. | No | | + + +--- + +### create_backup + +Creates a backup on a Cloud SQL instance. + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| project | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | | +| instance | string | Cloud SQL instance ID. This does not include the project ID. | Yes | | +| location | string | Location of the backup run. | No | | +| backup_description | string | The description of this backup run. | No | | + + +--- + +### get_instance + + + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| projectId | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | | +| instanceId | string | The instance ID | Yes | | + + +--- + +### list_instances + +Lists all type of Cloud SQL instances for a project. + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| project | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | | + + +--- + +### restore_backup + +Restores a backup on a Cloud SQL instance. + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| target_project | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | | +| target_instance | string | Cloud SQL instance ID of the target instance. This does not include the project ID. | Yes | | +| backup_id | string | Identifier of the backup being restored. Can be a BackupRun ID, backup name, or BackupDR backup name. Use the full backup ID as provided, do not try to parse it | Yes | | +| source_project | string | GCP project ID of the instance that the backup belongs to. Only required if the backup_id is a BackupRun ID. | No | | +| source_instance | string | Cloud SQL instance ID of the instance that the backup belongs to. Only required if the backup_id is a BackupRun ID. | No | | + + +--- + +### wait_for_operation + + + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| project | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | | +| operation | string | The operation ID | Yes | | + + +--- + diff --git a/skills/cloud-sql-mysql-lifecycle/scripts/clone_instance.js b/skills/cloud-sql-mysql-lifecycle/scripts/clone_instance.js new file mode 100755 index 0000000..323b55f --- /dev/null +++ b/skills/cloud-sql-mysql-lifecycle/scripts/clone_instance.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "clone_instance"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-lifecycle/scripts/create_backup.js b/skills/cloud-sql-mysql-lifecycle/scripts/create_backup.js new file mode 100755 index 0000000..5fc85c8 --- /dev/null +++ b/skills/cloud-sql-mysql-lifecycle/scripts/create_backup.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "create_backup"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-lifecycle/scripts/get_instance.js b/skills/cloud-sql-mysql-lifecycle/scripts/get_instance.js new file mode 100755 index 0000000..7b555e5 --- /dev/null +++ b/skills/cloud-sql-mysql-lifecycle/scripts/get_instance.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "get_instance"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-lifecycle/scripts/list_instances.js b/skills/cloud-sql-mysql-lifecycle/scripts/list_instances.js new file mode 100755 index 0000000..d0f39e6 --- /dev/null +++ b/skills/cloud-sql-mysql-lifecycle/scripts/list_instances.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "list_instances"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-lifecycle/scripts/restore_backup.js b/skills/cloud-sql-mysql-lifecycle/scripts/restore_backup.js new file mode 100755 index 0000000..92bff3f --- /dev/null +++ b/skills/cloud-sql-mysql-lifecycle/scripts/restore_backup.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "restore_backup"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-lifecycle/scripts/wait_for_operation.js b/skills/cloud-sql-mysql-lifecycle/scripts/wait_for_operation.js new file mode 100755 index 0000000..6135122 --- /dev/null +++ b/skills/cloud-sql-mysql-lifecycle/scripts/wait_for_operation.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "wait_for_operation"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-monitor/SKILL.md b/skills/cloud-sql-mysql-monitor/SKILL.md new file mode 100644 index 0000000..862736b --- /dev/null +++ b/skills/cloud-sql-mysql-monitor/SKILL.md @@ -0,0 +1,203 @@ +--- +name: cloud-sql-mysql-monitor +description: Use these skills when you need to troubleshoot slow queries, analyze system-level PromQL metrics, and identify structural performance issues like table fragmentation or missing unique indexes. +--- + +## Usage + +All scripts can be executed using Node.js. Replace `` and `` with actual values. + +**Bash:** +`node /scripts/.js '{"": ""}'` + +**PowerShell:** +`node /scripts/.js '{\"\": \"\"}'` + +Note: The scripts automatically load the environment variables from various .env files. Do not ask the user to set vars unless skill executions fails due to env var absence. + + +## Scripts + + +### get_query_metrics + +Fetches query level cloudmonitoring data (timeseries metrics) for queries running in Mysql instance using a PromQL query. Take projectID and instanceID from the user for which the metrics timeseries data needs to be fetched. +To use this skill, you must provide the Google Cloud `projectId` and a PromQL `query`. + +Generate PromQL `query` for Mysql query metrics. Use the provided metrics and rules to construct queries, Get the labels like `instance_id`, `query_hash` from user intent. If query_hash is provided then use the per_query metrics. Query hash and query id are same. + +Defaults: +1. Interval: Use a default interval of `5m` for `_over_time` aggregation functions unless a different window is specified by the user. + +PromQL Query Examples: +1. Basic Time Series: `avg_over_time({"__name__"="dbinsights.googleapis.com/aggregate/execution_time","monitored_resource"="cloudsql_instance_database","project_id"="my-projectId","resource_id"="my-projectId:my-instanceId"}[5m])` +2. Top K: `topk(30, avg_over_time({"__name__"="dbinsights.googleapis.com/aggregate/execution_time","monitored_resource"="cloudsql_instance_database","project_id"="my-projectId","resource_id"="my-projectId:my-instanceId"}[5m]))` +3. Mean: `avg(avg_over_time({"__name__"="dbinsights.googleapis.com/aggregate/execution_time","monitored_resource"="cloudsql_instance_database","project_id"="my-projectId","resource_id"="my-projectId:my-instanceId"}[5m]))` +4. Minimum: `min(min_over_time({"__name__"="dbinsights.googleapis.com/aggregate/execution_time","monitored_resource"="cloudsql_instance_database","project_id"="my-projectId","resource_id"="my-projectId:my-instanceId"}[5m]))` +5. Maximum: `max(max_over_time({"__name__"="dbinsights.googleapis.com/aggregate/execution_time","monitored_resource"="cloudsql_instance_database","project_id"="my-projectId","resource_id"="my-projectId:my-instanceId"}[5m]))` +6. Sum: `sum(avg_over_time({"__name__"="dbinsights.googleapis.com/aggregate/execution_time","monitored_resource"="cloudsql_instance_database","project_id"="my-projectId","resource_id"="my-projectId:my-instanceId"}[5m]))` +7. Count streams: `count(avg_over_time({"__name__"="dbinsights.googleapis.com/aggregate/execution_time","monitored_resource"="cloudsql_instance_database","project_id"="my-projectId","resource_id"="my-projectId:my-instanceId"}[5m]))` +8. Percentile with groupby on resource_id, database: `quantile by ("resource_id","database")(0.99,avg_over_time({"__name__"="dbinsights.googleapis.com/aggregate/execution_time","monitored_resource"="cloudsql_instance_database","project_id"="my-projectId","resource_id"="my-projectId:my-instanceId"}[5m]))` + +Available Metrics List: metricname. description. monitored resource. labels. resource_id label format is `project_id:instance_id` which is actually instance id only. aggregate is the aggregated values for all query stats, Use aggregate metrics if query id is not provided. For perquery metrics do not fetch querystring unless specified by user specifically. Have the aggregation on query hash to avoid fetching the querystring. Do not use latency metrics for anything. +1. `dbinsights.googleapis.com/aggregate/latencies`: Cumulative query latency distribution per user and database. `cloudsql_instance_database`. `user`, `client_addr`, `database`, `project_id`, `resource_id`. +2. `dbinsights.googleapis.com/aggregate/execution_time`: Cumulative query execution time per user and database. `cloudsql_instance_database`. `user`, `client_addr`, `database`, `project_id`, `resource_id`. +3. `dbinsights.googleapis.com/aggregate/execution_count`: Total number of query executions per user and database. `cloudsql_instance_database`. `user`, `client_addr`, `database`, `project_id`, `resource_id`. +4. `dbinsights.googleapis.com/aggregate/lock_time`: Cumulative lock wait time per user and database. `cloudsql_instance_database`. `user`, `client_addr`, `lock_type`, `database`, `project_id`, `resource_id`. +5. `dbinsights.googleapis.com/aggregate/io_time`: Cumulative IO wait time per user and database. `cloudsql_instance_database`. `user`, `client_addr`, `database`, `project_id`, `resource_id`. +6. `dbinsights.googleapis.com/aggregate/row_count`: Total number of rows affected during query execution. `cloudsql_instance_database`. `user`, `client_addr`, `row_status`, `database`, `project_id`, `resource_id`. +7. `dbinsights.googleapis.com/perquery/latencies`: Cumulative query latency distribution per user, database, and query. `cloudsql_instance_database`. `querystring`, `user`, `client_addr`, `query_hash`, `database`, `project_id`, `resource_id`. +8. `dbinsights.googleapis.com/perquery/execution_time`: Cumulative query execution time per user, database, and query. `cloudsql_instance_database`. `querystring`, `user`, `client_addr`, `query_hash`, `database`, `project_id`, `resource_id`. +9. `dbinsights.googleapis.com/perquery/execution_count`: Total number of query executions per user, database, and query. `cloudsql_instance_database`. `querystring`, `user`, `client_addr`, `query_hash`, `database`, `project_id`, `resource_id`. +10. `dbinsights.googleapis.com/perquery/lock_time`: Cumulative lock wait time per user, database, and query. `cloudsql_instance_database`. `querystring`, `user`, `client_addr`, `lock_type`, `query_hash`, `database`, `project_id`, `resource_id`. +11. `dbinsights.googleapis.com/perquery/io_time`: Cumulative io wait time per user, database, and query. `cloudsql_instance_database`. `querystring`, `user`, `client_addr`, `query_hash`, `database`, `project_id`, `resource_id`. +12. `dbinsights.googleapis.com/perquery/row_count`: Total number of rows affected during query execution. `cloudsql_instance_database`. `querystring`, `user`, `client_addr`, `query_hash`, `row_status`, `database`, `project_id`, `resource_id`. +13. `dbinsights.googleapis.com/pertag/latencies`: Cumulative query latency distribution per user, database, and tag. `cloudsql_instance_database`. `user`, `client_addr`, `action`, `application`, `controller`, `db_driver`, `framework`, `route`, `tag_hash`, `database`, `project_id`, `resource_id`. +14. `dbinsights.googleapis.com/pertag/execution_time`: Cumulative query execution time per user, database, and tag. `cloudsql_instance_database`. `user`, `client_addr`, `action`, `application`, `controller`, `db_driver`, `framework`, `route`, `tag_hash`, `database`, `project_id`, `resource_id`. +15. `dbinsights.googleapis.com/pertag/execution_count`: Total number of query executions per user, database, and tag. `cloudsql_instance_database`. `user`, `client_addr`, `action`, `application`, `controller`, `db_driver`, `framework`, `route`, `tag_hash`, `database`, `project_id`, `resource_id`. +16. `dbinsights.googleapis.com/pertag/lock_time`: Cumulative lock wait time per user, database and tag. `cloudsql_instance_database`. `user`, `client_addr`, `action`, `application`, `controller`, `db_driver`, `framework`, `route`, `lock_type`, `tag_hash`, `database`, `project_id`, `resource_id`. +17. `dbinsights.googleapis.com/pertag/io_time`: Cumulative IO wait time per user, database and tag. `cloudsql_instance_database`. `user`, `client_addr`, `action`, `application`, `controller`, `db_driver`, `framework`, `route`, `tag_hash`, `database`, `project_id`, `resource_id`. +18. `dbinsights.googleapis.com/pertag/row_count`: Total number of rows affected during query execution. `cloudsql_instance_database`. `user`, `client_addr`, `action`, `application`, `controller`, `db_driver`, `framework`, `route`, `tag_hash`, `row_status`, `database`, `project_id`, `resource_id`. + + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| projectId | string | The Id of the Google Cloud project. | Yes | | +| query | string | The promql query to execute. | Yes | | + + +--- + +### get_query_plan + +Provide information about how MySQL executes a SQL statement. Common use cases include: 1) analyze query plan to improve its performance, and 2) determine effectiveness of existing indexes and evalueate new ones. + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| sql_statement | string | The sql statement to explain. | Yes | | + + +--- + +### get_system_metrics + +Fetches system level cloudmonitoring data (timeseries metrics) for a MySQL instance using a PromQL query. Take projectId and instanceId from the user for which the metrics timeseries data needs to be fetched. +To use this skill, you must provide the Google Cloud `projectId` and a PromQL `query`. + +Generate PromQL `query` for MySQL system metrics. Use the provided metrics and rules to construct queries, Get the labels like `instance_id` from user intent. + +Defaults: +1. Interval: Use a default interval of `5m` for `_over_time` aggregation functions unless a different window is specified by the user. + +PromQL Query Examples: +1. Basic Time Series: `avg_over_time({"__name__"="cloudsql.googleapis.com/database/cpu/utilization","monitored_resource"="cloudsql_database","project_id"="my-projectId","database_id"="my-projectId:my-instanceId"}[5m])` +2. Top K: `topk(30, avg_over_time({"__name__"="cloudsql.googleapis.com/database/cpu/utilization","monitored_resource"="cloudsql_database","project_id"="my-projectId","database_id"="my-projectId:my-instanceId"}[5m]))` +3. Mean: `avg(avg_over_time({"__name__"="cloudsql.googleapis.com/database/cpu/utilization","monitored_resource"="cloudsql_database","project_id"="my-projectId","database_id"="my-projectId:my-instanceId"}[5m]))` +4. Minimum: `min(min_over_time({"__name__"="cloudsql.googleapis.com/database/cpu/utilization","monitored_resource"="cloudsql_database","project_id"="my-projectId","database_id"="my-projectId:my-instanceId"}[5m]))` +5. Maximum: `max(max_over_time({"__name__"="cloudsql.googleapis.com/database/cpu/utilization","monitored_resource"="cloudsql_database","project_id"="my-projectId","database_id"="my-projectId:my-instanceId"}[5m]))` +6. Sum: `sum(avg_over_time({"__name__"="cloudsql.googleapis.com/database/cpu/utilization","monitored_resource"="cloudsql_database","project_id"="my-projectId","database_id"="my-projectId:my-instanceId"}[5m]))` +7. Count streams: `count(avg_over_time({"__name__"="cloudsql.googleapis.com/database/cpu/utilization","monitored_resource"="cloudsql_database","project_id"="my-projectId","database_id"="my-projectId:my-instanceId"}[5m]))` +8. Percentile with groupby on database_id: `quantile by ("database_id")(0.99,avg_over_time({"__name__"="cloudsql.googleapis.com/database/cpu/utilization","monitored_resource"="cloudsql_database","project_id"="my-projectId","database_id"="my-projectId:my-instanceId"}[5m]))` + +Available Metrics List: metricname. description. monitored resource. labels. database_id is actually the instance id and the format is `project_id:instance_id`. +1. `cloudsql.googleapis.com/database/cpu/utilization`: Current CPU utilization as a percentage of reserved CPU. `cloudsql_database`. `database`, `project_id`, `database_id`. +2. `cloudsql.googleapis.com/database/network/connections`: Number of connections to the database instance. `cloudsql_database`. `database`, `project_id`, `database_id`. +3. `cloudsql.googleapis.com/database/network/received_bytes_count`: Delta count of bytes received through the network. `cloudsql_database`. `database`, `project_id`, `database_id`. +4. `cloudsql.googleapis.com/database/network/sent_bytes_count`: Delta count of bytes sent through the network. `cloudsql_database`. `destination`, `database`, `project_id`, `database_id`. +5. `cloudsql.googleapis.com/database/memory/components`: Memory usage for components like usage, cache, and free memory. `cloudsql_database`. `component`, `database`, `project_id`, `database_id`. +6. `cloudsql.googleapis.com/database/disk/bytes_used_by_data_type`: Data utilization in bytes. `cloudsql_database`. `data_type`, `database`, `project_id`, `database_id`. +7. `cloudsql.googleapis.com/database/disk/read_ops_count`: Delta count of data disk read IO operations. `cloudsql_database`. `database`, `project_id`, `database_id`. +8. `cloudsql.googleapis.com/database/disk/write_ops_count`: Delta count of data disk write IO operations. `cloudsql_database`. `database`, `project_id`, `database_id`. +9. `cloudsql.googleapis.com/database/mysql/queries`: Delta count of statements executed by the server. `cloudsql_database`. `database`, `project_id`, `database_id`. +10. `cloudsql.googleapis.com/database/mysql/questions`: Delta count of statements sent by the client. `cloudsql_database`. `database`, `project_id`, `database_id`. +11. `cloudsql.googleapis.com/database/mysql/received_bytes_count`: Delta count of bytes received by MySQL process. `cloudsql_database`. `database`, `project_id`, `database_id`. +12. `cloudsql.googleapis.com/database/mysql/sent_bytes_count`: Delta count of bytes sent by MySQL process. `cloudsql_database`. `database`, `project_id`, `database_id`. +13. `cloudsql.googleapis.com/database/mysql/innodb_buffer_pool_pages_dirty`: Number of unflushed pages in the InnoDB buffer pool. `cloudsql_database`. `database`, `project_id`, `database_id`. +14. `cloudsql.googleapis.com/database/mysql/innodb_buffer_pool_pages_free`: Number of unused pages in the InnoDB buffer pool. `cloudsql_database`. `database`, `project_id`, `database_id`. +15. `cloudsql.googleapis.com/database/mysql/innodb_buffer_pool_pages_total`: Total number of pages in the InnoDB buffer pool. `cloudsql_database`. `database`, `project_id`, `database_id`. +16. `cloudsql.googleapis.com/database/mysql/innodb_data_fsyncs`: Delta count of InnoDB fsync() calls. `cloudsql_database`. `database`, `project_id`, `database_id`. +17. `cloudsql.googleapis.com/database/mysql/innodb_os_log_fsyncs`: Delta count of InnoDB fsync() calls to the log file. `cloudsql_database`. `database`, `project_id`, `database_id`. +18. `cloudsql.googleapis.com/database/mysql/innodb_pages_read`: Delta count of InnoDB pages read. `cloudsql_database`. `database`, `project_id`, `database_id`. +19. `cloudsql.googleapis.com/database/mysql/innodb_pages_written`: Delta count of InnoDB pages written. `cloudsql_database`. `database`, `project_id`, `database_id`. +20. `cloudsql.googleapis.com/database/mysql/open_tables`: The number of tables that are currently open. `cloudsql_database`. `database`, `project_id`, `database_id`. +21. `cloudsql.googleapis.com/database/mysql/opened_table_count`: The number of tables opened since the last sample. `cloudsql_database`. `database`, `project_id`, `database_id`. +22. `cloudsql.googleapis.com/database/mysql/open_table_definitions`: The number of table definitions currently cached. `cloudsql_database`. `database`, `project_id`, `database_id`. +23. `cloudsql.googleapis.com/database/mysql/opened_table_definitions_count`: The number of table definitions cached since the last sample. `cloudsql_database`. `database`, `project_id`, `database_id`. +24. `cloudsql.googleapis.com/database/mysql/innodb/dictionary_memory`: Memory allocated for the InnoDB dictionary cache. `cloudsql_database`. `database`, `project_id`, `database_id`. + + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| projectId | string | The Id of the Google Cloud project. | Yes | | +| query | string | The promql query to execute. | Yes | | + + +--- + +### list_active_queries + +Lists top N (default 10) ongoing queries from processlist and innodb_trx, ordered by execution time in descending order. Returns detailed information of those queries in json format, including process id, query, transaction duration, transaction wait duration, process time, transaction state, process state, username with host, transaction rows locked, transaction rows modified, and db schema. + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| min_duration_secs | integer | Optional: Only show queries running for at least this long in seconds | No | `0` | +| limit | integer | Optional: The maximum number of rows to return. | No | `100` | + + +--- + +### list_table_fragmentation + +List table fragmentation in MySQL, by calculating the size of the data and index files and free space allocated to each table. The query calculates fragmentation percentage which represents the proportion of free space relative to the total data and index size. Storage can be reclaimed for tables with high fragmentation using OPTIMIZE TABLE. + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| table_schema | string | (Optional) The database where fragmentation check is to be executed. Check all tables visible to the current user if not specified | No | `` | +| table_name | string | (Optional) Name of the table to be checked. Check all tables visible to the current user if not specified. | No | `` | +| data_free_threshold_bytes | integer | (Optional) Only show tables with at least this much free space in bytes. Default is 1 | No | `1` | +| limit | integer | (Optional) Max rows to return, default is 10 | No | `10` | + + +--- + +### list_table_stats + +Display table statistics including table size, total latency, rows read, rows written, read and write latency for entire instance, a specified database, or a specified table. Specifying a database name or table name filters the output to that specific db or table. Results are limited to 10 by default. + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| table_schema | string | (Optional) The database where statistics is to be executed. Check all tables visible to the current user if not specified | No | `` | +| table_name | string | (Optional) Name of the table to be checked. Check all tables visible to the current user if not specified. | No | `` | +| sort_by | string | (Optional) The column to sort by | No | `` | +| limit | integer | (Optional) Max rows to return, default is 10 | No | `10` | +| connected_schema | string | (Optional) The connected db | No | | + + +--- + +### list_tables_missing_unique_indexes + +Find tables that do not have primary or unique key constraint. A primary key or unique key is the only mechanism that guaranttes a row is unique. Without them, the database-level protection against data integrity issues will be missing. + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| table_schema | string | (Optional) The database where the check is to be performed. Check all tables visible to the current user if not specified | No | `` | +| limit | integer | (Optional) Max rows to return, default is 50 | No | `50` | + + +--- + diff --git a/skills/cloud-sql-mysql-monitor/scripts/get_query_metrics.js b/skills/cloud-sql-mysql-monitor/scripts/get_query_metrics.js new file mode 100755 index 0000000..016a423 --- /dev/null +++ b/skills/cloud-sql-mysql-monitor/scripts/get_query_metrics.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "get_query_metrics"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-monitor/scripts/get_query_plan.js b/skills/cloud-sql-mysql-monitor/scripts/get_query_plan.js new file mode 100755 index 0000000..d49e104 --- /dev/null +++ b/skills/cloud-sql-mysql-monitor/scripts/get_query_plan.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "get_query_plan"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-monitor/scripts/get_system_metrics.js b/skills/cloud-sql-mysql-monitor/scripts/get_system_metrics.js new file mode 100755 index 0000000..17adcb9 --- /dev/null +++ b/skills/cloud-sql-mysql-monitor/scripts/get_system_metrics.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "get_system_metrics"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-monitor/scripts/list_active_queries.js b/skills/cloud-sql-mysql-monitor/scripts/list_active_queries.js new file mode 100755 index 0000000..ca9e382 --- /dev/null +++ b/skills/cloud-sql-mysql-monitor/scripts/list_active_queries.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "list_active_queries"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-monitor/scripts/list_table_fragmentation.js b/skills/cloud-sql-mysql-monitor/scripts/list_table_fragmentation.js new file mode 100755 index 0000000..c055097 --- /dev/null +++ b/skills/cloud-sql-mysql-monitor/scripts/list_table_fragmentation.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "list_table_fragmentation"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-monitor/scripts/list_table_stats.js b/skills/cloud-sql-mysql-monitor/scripts/list_table_stats.js new file mode 100755 index 0000000..d22d90f --- /dev/null +++ b/skills/cloud-sql-mysql-monitor/scripts/list_table_stats.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "list_table_stats"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main(); diff --git a/skills/cloud-sql-mysql-monitor/scripts/list_tables_missing_unique_indexes.js b/skills/cloud-sql-mysql-monitor/scripts/list_tables_missing_unique_indexes.js new file mode 100755 index 0000000..3e3cb68 --- /dev/null +++ b/skills/cloud-sql-mysql-monitor/scripts/list_tables_missing_unique_indexes.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Copyright 2026 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. + +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const toolName = "list_tables_missing_unique_indexes"; +const configArgs = ["--prebuilt", "cloud-sql-mysql"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'CLOUD_SQL_MYSQL_USER', + 'CLOUD_SQL_MYSQL_PASSWORD', + 'CLOUD_SQL_MYSQL_IP_TYPE', +]; + + +function mergeEnvVars(env) { + if (process.env.GEMINI_CLI === '1') { + const envPath = path.resolve(__dirname, '../../../.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const splitIdx = trimmed.indexOf('='); + if (splitIdx !== -1) { + const key = trimmed.slice(0, splitIdx).trim(); + let value = trimmed.slice(splitIdx + 1).trim(); + value = value.replace(/(^['"]|['"]$)/g, ''); + if (env[key] === undefined) { + env[key] = value; + } + } + } + }); + } + } else if (process.env.CLAUDECODE === '1') { + const prefix = 'CLAUDE_PLUGIN_OPTION_'; + for (const key in process.env) { + if (key.startsWith(prefix)) { + env[key.substring(prefix.length)] = process.env[key]; + } + } + } +} + +function prepareEnvironment() { + let env = { ...process.env }; + let userAgent = "skills"; + if (process.env.GEMINI_CLI === '1') { + userAgent = "skills-geminicli"; + } else if (process.env.CLAUDECODE === '1') { + userAgent = "skills-claudecode"; + } else if (process.env.CODEX_CI === '1') { + userAgent = "skills-codex"; + } + mergeEnvVars(env); + + OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => { + if (env[varName] === '') { + delete env[varName]; + } + }); + + + return { env, userAgent }; +} + +function main() { + const { env, userAgent } = prepareEnvironment(); + const args = process.argv.slice(2); + + const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx'; + const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args; + const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs]; + + const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env }); + + + child.on('close', (code) => { + process.exit(code); + }); + + child.on('error', (err) => { + console.error("Error executing toolbox:", err); + process.exit(1); + }); +} + +main();