Skip to content

Commit 3764602

Browse files
committed
feat: initial commit with skills and workflows
0 parents  commit 3764602

9 files changed

Lines changed: 555 additions & 0 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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+
name: lint
15+
16+
on:
17+
push:
18+
paths-ignore:
19+
- "skills/**"
20+
pull_request:
21+
paths-ignore:
22+
- "skills/**"
23+
pull_request_target:
24+
types: [labeled]
25+
paths-ignore:
26+
- "skills/**"
27+
workflow_dispatch:
28+
29+
jobs:
30+
skills-validate:
31+
runs-on: ubuntu-latest
32+
steps:
33+
- name: Skip Skill Validation
34+
run: |
35+
echo "No changes detected in 'skills/' directory. Skipping validation."
36+
echo "This job ensures the required 'skills-validate' status check passes."
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
name: Validate Skills
15+
16+
on:
17+
push:
18+
paths:
19+
- "skills/**"
20+
pull_request:
21+
paths:
22+
- "skills/**"
23+
pull_request_target:
24+
types: [labeled]
25+
paths:
26+
- "skills/**"
27+
28+
jobs:
29+
skills-validate:
30+
runs-on: ubuntu-latest
31+
steps:
32+
- uses: actions/checkout@v4
33+
- name: Set up Python
34+
uses: actions/setup-python@v5
35+
with:
36+
python-version: "3.11"
37+
- name: Install skills-ref
38+
run: |
39+
pip install "git+https://github.com/agentskills/agentskills.git#subdirectory=skills-ref"
40+
- name: Validate Skills
41+
run: |
42+
failed=0
43+
for skill_dir in skills/*/; do
44+
if [ -d "$skill_dir" ]; then
45+
echo "Validating $skill_dir..."
46+
if ! skills-ref validate "$skill_dir"; then
47+
echo "Validation failed for $skill_dir"
48+
failed=1
49+
fi
50+
fi
51+
done
52+
exit $failed

‎.gitignore‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
toolbox*
2+
*.sh
3+
skills-gen/
4+
node_modules/
5+
.env
6+
.DS_Store

‎gemini-extension.json‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "dataproc",
3+
"version": "0.1.0",
4+
"description": "Manage Dataproc clusters and jobs.",
5+
"contextFileName": "DATAPROC.md",
6+
"settings": [
7+
{
8+
"name": "Project ID",
9+
"description": "ID of the Google Cloud project",
10+
"envVar": "DATAPROC_PROJECT"
11+
},
12+
{
13+
"name": "Location",
14+
"description": "Region of the Dataproc resources",
15+
"envVar": "DATAPROC_REGION"
16+
}
17+
]
18+
}

‎skills/dataproc-skills/SKILL.md‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
name: dataproc-skills
3+
description: Skills to interact with your Dataproc clusters and jobs.
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+
## Scripts
19+
20+
### get_cluster
21+
22+
Gets a Dataproc cluster
23+
24+
#### Parameters
25+
26+
| Name | Type | Description | Required | Default |
27+
| :---------- | :----- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------- | :------ |
28+
| clusterName | string | The short name of the cluster, e.g. for "projects/my-project/regions/us-central1/clusters/my-cluster", pass "my-cluster" (the project and region are inherited from the source) | No | |
29+
30+
---
31+
32+
### get_job
33+
34+
Gets a Dataproc job
35+
36+
#### Parameters
37+
38+
| Name | Type | Description | Required | Default |
39+
| :---- | :----- | :----------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :------ |
40+
| jobId | string | The job ID, e.g. for "projects/my-project/regions/us-central1/jobs/my-job", pass "my-job" (the project and region are inherited from the source) | No | |
41+
42+
---
43+
44+
### list_clusters
45+
46+
Lists and filters Dataproc clusters
47+
48+
#### Parameters
49+
50+
| Name | Type | Description | Required | Default |
51+
| :-------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :------ |
52+
| filter | string | A filter constraining the clusters to list. Filters are case-sensitive and have the following syntax: field = value [AND [field = value]] ... where field is one of status.state, clusterName, or labels.[KEY], and [KEY] is a label key. value can be \* to match all values. status.state can be one of the following: ACTIVE, INACTIVE, CREATING, RUNNING, ERROR, DELETING, UPDATING, STOPPING, or STOPPED. ACTIVE contains the CREATING, UPDATING, and RUNNING states. INACTIVE contains the DELETING, ERROR, STOPPING, and STOPPED states. clusterName is the name of the cluster provided at creation time. Only the logical AND operator is supported; space-separated items are treated as having an implicit AND operator. | No | |
53+
| pageSize | integer | The maximum number of clusters to return in a single page (default 20) | No | `20` |
54+
| pageToken | string | A page token, received from a previous `ListClusters` call | No | |
55+
56+
---
57+
58+
### list_jobs
59+
60+
Lists and filters Dataproc jobs
61+
62+
#### Parameters
63+
64+
| Name | Type | Description | Required | Default |
65+
| :-------------- | :------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :------ |
66+
| filter | string | A filter constraining the jobs to list. Filters are case-sensitive and have the following syntax: field = value [AND [field = value]] ... where field is clusterName, status.state, or labels.[KEY], and [KEY] is a label key. value can be \* to match all values. status.state can be one of the following: PENDING, RUNNING, CANCEL_PENDING, JOB_STATE_CANCELLED, DONE, ERROR, or ATTEMPT_FAILURE. Only the logical AND operator is supported; space-separated items are treated as having an implicit AND operator. Filtering by clusterName is recommended to improve query performance. | No | |
67+
| jobStateMatcher | string | Specifies if the job state matcher should match ALL jobs, only ACTIVE jobs, or only NON_ACTIVE jobs. Defaults to ALL. Supported values: ALL, ACTIVE, NON_ACTIVE. | No | |
68+
| pageSize | integer | The maximum number of jobs to return in a single page (default 20) | No | `20` |
69+
| pageToken | string | A page token, received from a previous `ListJobs` call | No | |
70+
71+
---
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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 = "get_cluster";
23+
const configArgs = ["--prebuilt", "dataproc"];
24+
25+
26+
function mergeEnvVars(env) {
27+
if (process.env.GEMINI_CLI === '1') {
28+
const envPath = path.resolve(__dirname, '../../../.env');
29+
if (fs.existsSync(envPath)) {
30+
const envContent = fs.readFileSync(envPath, 'utf-8');
31+
envContent.split('\n').forEach(line => {
32+
const trimmed = line.trim();
33+
if (trimmed && !trimmed.startsWith('#')) {
34+
const splitIdx = trimmed.indexOf('=');
35+
if (splitIdx !== -1) {
36+
const key = trimmed.slice(0, splitIdx).trim();
37+
let value = trimmed.slice(splitIdx + 1).trim();
38+
value = value.replace(/(^['"]|['"]$)/g, '');
39+
if (env[key] === undefined) {
40+
env[key] = value;
41+
}
42+
}
43+
}
44+
});
45+
}
46+
} else if (process.env.CLAUDECODE === '1') {
47+
const prefix = 'CLAUDE_PLUGIN_OPTION_';
48+
for (const key in process.env) {
49+
if (key.startsWith(prefix)) {
50+
env[key.substring(prefix.length)] = process.env[key];
51+
}
52+
}
53+
}
54+
}
55+
56+
function prepareEnvironment() {
57+
let env = { ...process.env };
58+
let userAgent = "skills";
59+
if (process.env.GEMINI_CLI === '1') {
60+
userAgent = "skills-geminicli";
61+
} else if (process.env.CLAUDECODE === '1') {
62+
userAgent = "skills-claudecode";
63+
} else if (process.env.CODEX_CI === '1') {
64+
userAgent = "skills-codex";
65+
}
66+
mergeEnvVars(env);
67+
68+
69+
return { env, userAgent };
70+
}
71+
72+
function main() {
73+
const { env, userAgent } = prepareEnvironment();
74+
const args = process.argv.slice(2);
75+
76+
const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx';
77+
const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args;
78+
const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs];
79+
80+
const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env });
81+
82+
83+
child.on('close', (code) => {
84+
process.exit(code);
85+
});
86+
87+
child.on('error', (err) => {
88+
console.error("Error executing toolbox:", err);
89+
process.exit(1);
90+
});
91+
}
92+
93+
main();
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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 = "get_job";
23+
const configArgs = ["--prebuilt", "dataproc"];
24+
25+
26+
function mergeEnvVars(env) {
27+
if (process.env.GEMINI_CLI === '1') {
28+
const envPath = path.resolve(__dirname, '../../../.env');
29+
if (fs.existsSync(envPath)) {
30+
const envContent = fs.readFileSync(envPath, 'utf-8');
31+
envContent.split('\n').forEach(line => {
32+
const trimmed = line.trim();
33+
if (trimmed && !trimmed.startsWith('#')) {
34+
const splitIdx = trimmed.indexOf('=');
35+
if (splitIdx !== -1) {
36+
const key = trimmed.slice(0, splitIdx).trim();
37+
let value = trimmed.slice(splitIdx + 1).trim();
38+
value = value.replace(/(^['"]|['"]$)/g, '');
39+
if (env[key] === undefined) {
40+
env[key] = value;
41+
}
42+
}
43+
}
44+
});
45+
}
46+
} else if (process.env.CLAUDECODE === '1') {
47+
const prefix = 'CLAUDE_PLUGIN_OPTION_';
48+
for (const key in process.env) {
49+
if (key.startsWith(prefix)) {
50+
env[key.substring(prefix.length)] = process.env[key];
51+
}
52+
}
53+
}
54+
}
55+
56+
function prepareEnvironment() {
57+
let env = { ...process.env };
58+
let userAgent = "skills";
59+
if (process.env.GEMINI_CLI === '1') {
60+
userAgent = "skills-geminicli";
61+
} else if (process.env.CLAUDECODE === '1') {
62+
userAgent = "skills-claudecode";
63+
} else if (process.env.CODEX_CI === '1') {
64+
userAgent = "skills-codex";
65+
}
66+
mergeEnvVars(env);
67+
68+
69+
return { env, userAgent };
70+
}
71+
72+
function main() {
73+
const { env, userAgent } = prepareEnvironment();
74+
const args = process.argv.slice(2);
75+
76+
const command = os.platform() === 'win32' ? 'npx.cmd' : 'npx';
77+
const processedArgs = os.platform() === 'win32' ? args.map(arg => arg.includes('"') ? '"' + arg.replace(/"/g, '""') + '"' : arg) : args;
78+
const npxArgs = ["--yes", "@toolbox-sdk/server@1.1.0", "--log-level", "error", ...configArgs, "invoke", toolName, "--user-agent-metadata", userAgent, ...processedArgs];
79+
80+
const child = spawn(command, npxArgs, { shell: os.platform() === 'win32', stdio: 'inherit', env });
81+
82+
83+
child.on('close', (code) => {
84+
process.exit(code);
85+
});
86+
87+
child.on('error', (err) => {
88+
console.error("Error executing toolbox:", err);
89+
process.exit(1);
90+
});
91+
}
92+
93+
main();

0 commit comments

Comments
 (0)