Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/skills-validate-fallback.yml
Original file line number Diff line number Diff line change
@@ -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."
56 changes: 56 additions & 0 deletions .github/workflows/skills-validate.yml
Original file line number Diff line number Diff line change
@@ -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
12 changes: 1 addition & 11 deletions gemini-extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,6 @@
"name": "bigquery-data-analytics",
"version": "0.1.7",
"description": "Connect, query, and generate data insights for BigQuery datasets and data.",
"mcpServers": {
"bigquery_data_analytics": {
"command": "${extensionPath}${/}toolbox",
"args": [
"--tools-file",
"${extensionPath}${/}tools.yaml",
"--stdio"
]
}
},
"contextFileName": "BIGQUERY.md",
"settings": [
{
Expand All @@ -25,4 +15,4 @@
"envVar": "BIGQUERY_LOCATION"
}
]
}
}
101 changes: 101 additions & 0 deletions skills/bigquery-analytics/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
name: bigquery-analytics
description: Use these skills when you need to handle advanced data intelligence and predictive tasks. Use when a user asks "why" data changed or needs future projections. Provides automated insight generation and time-series forecasting.
---

## Usage

All scripts can be executed using Node.js. Replace `<param_name>` and `<param_value>` with actual values.

**Bash:**
`node <skill_dir>/scripts/<script_name>.js '{"<param_name>": "<param_value>"}'`

**PowerShell:**
`node <skill_dir>/scripts/<script_name>.js '{\"<param_name>\": \"<param_value>\"}'`

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


### analyze_contribution

Use this skill to analyze the contribution about changes to key metrics in multi-dimensional data.

#### Parameters

| Name | Type | Description | Required | Default |
| :--- | :--- | :--- | :--- | :--- |
| input_data | string | The data that contain the test and control data to analyze. Can be a fully qualified BigQuery table ID or a SQL query. | Yes | |
| contribution_metric | string | The name of the column that contains the metric to analyze.
Provides the expression to use to calculate the metric you are analyzing.
To calculate a summable metric, the expression must be in the form SUM(metric_column_name),
where metric_column_name is a numeric data type.

To calculate a summable ratio metric, the expression must be in the form
SUM(numerator_metric_column_name)/SUM(denominator_metric_column_name),
where numerator_metric_column_name and denominator_metric_column_name are numeric data types.

To calculate a summable by category metric, the expression must be in the form
SUM(metric_sum_column_name)/COUNT(DISTINCT categorical_column_name). The summed column must be a numeric data type.
The categorical column must have type BOOL, DATE, DATETIME, TIME, TIMESTAMP, STRING, or INT64. | Yes | |
| is_test_col | string | The name of the column that identifies whether a row is in the test or control group. | Yes | |
| dimension_id_cols | array | An array of column names that uniquely identify each dimension. | No | |
| top_k_insights_by_apriori_support | integer | The number of top insights to return, ranked by apriori support. | No | `30` |
| pruning_method | string | The method to use for pruning redundant insights. Can be 'NO_PRUNING' or 'PRUNE_REDUNDANT_INSIGHTS'. | No | `PRUNE_REDUNDANT_INSIGHTS` |


---

### ask_data_insights

Use this skill to perform data analysis, get insights,
or answer complex questions about the contents of specific
BigQuery tables.


#### Parameters

| Name | Type | Description | Required | Default |
| :--- | :--- | :--- | :--- | :--- |
| user_query_with_context | string | The user's question, potentially including conversation history and system instructions for context. | Yes | |
| table_references | string | A JSON string of a list of BigQuery tables to use as context. Each object in the list must contain 'projectId', 'datasetId', and 'tableId'. Example: '[{"projectId": "my-gcp-project", "datasetId": "my_dataset", "tableId": "my_table"}]'. | Yes | |


---

### forecast

Use this skill to forecast time series data.

#### Parameters

| Name | Type | Description | Required | Default |
| :--- | :--- | :--- | :--- | :--- |
| history_data | string | The table id or the query of the history time series data. | Yes | |
| timestamp_col | string | The name of the time series timestamp column. | Yes | |
| data_col | string | The name of the time series data column. | Yes | |
| id_cols | array | An array of the time series id column names. | No | `[]` |
| horizon | integer | The number of forecasting steps. | No | `10` |


---

### search_catalog

Use this skill to find tables, views, models, routines or connections.

#### Parameters

| Name | Type | Description | Required | Default |
| :--- | :--- | :--- | :--- | :--- |
| prompt | string | Prompt representing search intention. Do not rewrite the prompt. | Yes | |
| datasetIds | array | Array of dataset IDs. | No | `[]` |
| projectIds | array | Array of project IDs. | No | `[]` |
| types | array | Array of data types to filter by. | No | `[]` |
| pageSize | integer | Number of results in the search page. | No | `5` |


---

107 changes: 107 additions & 0 deletions skills/bigquery-analytics/scripts/analyze_contribution.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/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 = "analyze_contribution";
const configArgs = ["--prebuilt", "bigquery"];

const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [
'BIGQUERY_LOCATION',
'BIGQUERY_USE_CLIENT_OAUTH',
'BIGQUERY_SCOPES',
'BIGQUERY_MAX_QUERY_RESULT_ROWS',
'BIGQUERY_IMPERSONATE_SERVICE_ACCOUNT',
];


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();
Loading
Loading