Skip to content

Commit 8da2132

Browse files
feat: add support for skills (#111)
Co-authored-by: Averi Kitsch <akitsch@google.com>
1 parent 735dfa8 commit 8da2132

31 files changed

Lines changed: 3129 additions & 18 deletions
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
name: lint
16+
17+
on:
18+
push:
19+
paths-ignore:
20+
- "skills/**"
21+
pull_request:
22+
paths-ignore:
23+
- "skills/**"
24+
pull_request_target:
25+
types: [labeled]
26+
paths-ignore:
27+
- "skills/**"
28+
workflow_dispatch:
29+
30+
jobs:
31+
skills-validate:
32+
runs-on: ubuntu-latest
33+
steps:
34+
- name: Skip Skill Validation
35+
run: |
36+
echo "No changes detected in 'skills/' directory. Skipping validation."
37+
echo "This job ensures the required 'skills-validate' status check passes."
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
name: Validate Skills
16+
17+
on:
18+
push:
19+
paths:
20+
- "skills/**"
21+
pull_request:
22+
paths:
23+
- "skills/**"
24+
pull_request_target:
25+
types: [labeled]
26+
paths:
27+
- "skills/**"
28+
29+
jobs:
30+
skills-validate:
31+
runs-on: ubuntu-latest
32+
steps:
33+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
34+
35+
- name: Set up Python
36+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
37+
with:
38+
python-version: "3.11"
39+
40+
- name: Install skills-ref
41+
run: |
42+
pip install "git+https://github.com/agentskills/agentskills.git#subdirectory=skills-ref"
43+
44+
- name: Validate Skills
45+
run: |
46+
failed=0
47+
for skill_dir in skills/*/; do
48+
if [ -d "$skill_dir" ]; then
49+
echo "Validating $skill_dir..."
50+
if ! skills-ref validate "$skill_dir"; then
51+
echo "Validation failed for $skill_dir"
52+
failed=1
53+
fi
54+
fi
55+
done
56+
exit $failed

gemini-extension.json

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,6 @@
22
"name": "cloud-sql-mysql",
33
"version": "0.1.9",
44
"description": "Connect and interact with a Cloud SQL for MySQL database and data",
5-
"mcpServers": {
6-
"cloud_sql_mysql_admin": {
7-
"command": "${extensionPath}${/}toolbox",
8-
"args": [
9-
"--prebuilt",
10-
"cloud-sql-mysql-admin",
11-
"--stdio"
12-
]
13-
},
14-
"cloud_sql_mysql": {
15-
"command": "${extensionPath}${/}toolbox",
16-
"args": [
17-
"--prebuilt",
18-
"cloud-sql-mysql",
19-
"--stdio"
20-
]
21-
}
22-
},
235
"contextFileName": "CLOUD-SQL-MYSQL.md",
246
"settings": [
257
{
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
---
2+
name: cloud-sql-mysql-admin
3+
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.
4+
---
5+
6+
## Usage
7+
8+
All scripts can be executed using Node.js. Replace `<param_name>` and `<param_value>` with actual values.
9+
10+
**Bash:**
11+
`node <skill_dir>/scripts/<script_name>.js '{"<param_name>": "<param_value>"}'`
12+
13+
**PowerShell:**
14+
`node <skill_dir>/scripts/<script_name>.js '{\"<param_name>\": \"<param_value>\"}'`
15+
16+
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.
17+
18+
19+
## Scripts
20+
21+
22+
### create_database
23+
24+
25+
26+
#### Parameters
27+
28+
| Name | Type | Description | Required | Default |
29+
| :--- | :--- | :--- | :--- | :--- |
30+
| project | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | |
31+
| instance | string | The ID of the instance where the database will be created. | Yes | |
32+
| name | string | The name for the new database. Must be unique within the instance. | Yes | |
33+
34+
35+
---
36+
37+
### create_instance
38+
39+
40+
41+
#### Parameters
42+
43+
| Name | Type | Description | Required | Default |
44+
| :--- | :--- | :--- | :--- | :--- |
45+
| project | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | |
46+
| name | string | The name of the instance | Yes | |
47+
| 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` |
48+
| rootPassword | string | The root password for the instance | Yes | |
49+
| 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` |
50+
51+
52+
---
53+
54+
### create_user
55+
56+
57+
58+
#### Parameters
59+
60+
| Name | Type | Description | Required | Default |
61+
| :--- | :--- | :--- | :--- | :--- |
62+
| project | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | |
63+
| instance | string | The ID of the instance where the user will be created. | Yes | |
64+
| name | string | The name for the new user. Must be unique within the instance. | Yes | |
65+
| password | string | A secure password for the new user. Not required for IAM users. | No | |
66+
| iamUser | boolean | Set to true to create a Cloud IAM user. | Yes | |
67+
68+
69+
---
70+
71+
### get_instance
72+
73+
74+
75+
#### Parameters
76+
77+
| Name | Type | Description | Required | Default |
78+
| :--- | :--- | :--- | :--- | :--- |
79+
| projectId | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | |
80+
| instanceId | string | The instance ID | Yes | |
81+
82+
83+
---
84+
85+
### list_databases
86+
87+
Lists all databases for a Cloud SQL instance.
88+
89+
#### Parameters
90+
91+
| Name | Type | Description | Required | Default |
92+
| :--- | :--- | :--- | :--- | :--- |
93+
| project | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | |
94+
| instance | string | The instance ID | Yes | |
95+
96+
97+
---
98+
99+
### list_instances
100+
101+
Lists all type of Cloud SQL instances for a project.
102+
103+
#### Parameters
104+
105+
| Name | Type | Description | Required | Default |
106+
| :--- | :--- | :--- | :--- | :--- |
107+
| project | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | |
108+
109+
110+
---
111+
112+
### wait_for_operation
113+
114+
115+
116+
#### Parameters
117+
118+
| Name | Type | Description | Required | Default |
119+
| :--- | :--- | :--- | :--- | :--- |
120+
| project | string | The GCP project ID. This is pre-configured; do not ask for it unless the user explicitly provides a different one. | No | |
121+
| operation | string | The operation ID | Yes | |
122+
123+
124+
---
125+
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#!/usr/bin/env node
2+
3+
// Copyright 2026 Google LLC
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
const { spawn, execSync } = require('child_process');
18+
const path = require('path');
19+
const fs = require('fs');
20+
const os = require('os');
21+
22+
const toolName = "create_database";
23+
const configArgs = ["--prebuilt", "cloud-sql-mysql"];
24+
25+
const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [
26+
'CLOUD_SQL_MYSQL_USER',
27+
'CLOUD_SQL_MYSQL_PASSWORD',
28+
'CLOUD_SQL_MYSQL_IP_TYPE',
29+
];
30+
31+
32+
function mergeEnvVars(env) {
33+
if (process.env.GEMINI_CLI === '1') {
34+
const envPath = path.resolve(__dirname, '../../../.env');
35+
if (fs.existsSync(envPath)) {
36+
const envContent = fs.readFileSync(envPath, 'utf-8');
37+
envContent.split('\n').forEach(line => {
38+
const trimmed = line.trim();
39+
if (trimmed && !trimmed.startsWith('#')) {
40+
const splitIdx = trimmed.indexOf('=');
41+
if (splitIdx !== -1) {
42+
const key = trimmed.slice(0, splitIdx).trim();
43+
let value = trimmed.slice(splitIdx + 1).trim();
44+
value = value.replace(/(^['"]|['"]$)/g, '');
45+
if (env[key] === undefined) {
46+
env[key] = value;
47+
}
48+
}
49+
}
50+
});
51+
}
52+
} else if (process.env.CLAUDECODE === '1') {
53+
const prefix = 'CLAUDE_PLUGIN_OPTION_';
54+
for (const key in process.env) {
55+
if (key.startsWith(prefix)) {
56+
env[key.substring(prefix.length)] = process.env[key];
57+
}
58+
}
59+
}
60+
}
61+
62+
function prepareEnvironment() {
63+
let env = { ...process.env };
64+
let userAgent = "skills";
65+
if (process.env.GEMINI_CLI === '1') {
66+
userAgent = "skills-geminicli";
67+
} else if (process.env.CLAUDECODE === '1') {
68+
userAgent = "skills-claudecode";
69+
} else if (process.env.CODEX_CI === '1') {
70+
userAgent = "skills-codex";
71+
}
72+
mergeEnvVars(env);
73+
74+
OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => {
75+
if (env[varName] === '') {
76+
delete env[varName];
77+
}
78+
});
79+
80+
81+
return { env, userAgent };
82+
}
83+
84+
function main() {
85+
const { env, userAgent } = prepareEnvironment();
86+
const args = process.argv.slice(2);
87+
88+
const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx';
89+
const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args;
90+
const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs];
91+
92+
const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env });
93+
94+
95+
child.on('close', (code) => {
96+
process.exit(code);
97+
});
98+
99+
child.on('error', (err) => {
100+
console.error("Error executing toolbox:", err);
101+
process.exit(1);
102+
});
103+
}
104+
105+
main();

0 commit comments

Comments
 (0)