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/DEVELOPER.md b/DEVELOPER.md index 898d35a..1854deb 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -1,91 +1,67 @@ # DEVELOPER.md -This document provides instructions for setting up your development environment -and contributing to the Dataplex Gemini CLI Extension project. +This document provides instructions for setting up your development environment and contributing to the Firestore Native Gemini CLI Extension project. ## Prerequisites Before you begin, ensure you have the following: -1. **Gemini CLI:** Install the Gemini CLI version v0.6.0 or above. Installation - instructions can be found on the official Gemini CLI documentation. You can - verify your version by running `gemini --version`. -2. **Firestore database:** For testing data plane tools, you will need access to an active Firestore database. +1. **Gemini CLI:** Install the Gemini CLI version v0.6.0 or above. Installation instructions can be found on the [official Gemini CLI documentation](https://google-gemini.github.io/gemini-cli/). You can verify your version by running `gemini --version`. +2. **Firestore database:** For testing skills, you will need access to an active Firestore database. ## Developing the Extension ### Running from Local Source -The core logic for this extension is handled by a pre-built `toolbox` binary. The development process involves installing the extension locally into the Gemini CLI to test changes. +This extension uses **Agent Skills** to interact with Firestore. The development process involves generating these skills locally and linking the extension to the Gemini CLI. 1. **Clone the Repository:** ```bash git clone https://github.com/gemini-cli-extensions/firestore-native.git cd firestore-native - ``` - -2. **Download the Toolbox Binary:** The required version of the `toolbox` binary - is specified in `toolbox_version.txt`. Download it for your platform. - - ```bash - # Read the required version - VERSION=$(cat toolbox_version.txt) - # Example for macOS/amd64 - curl -L -o toolbox https://storage.googleapis.com/mcp-toolbox-for-databases/geminicli/v$VERSION/darwin/amd64/toolbox - chmod +x toolbox - ``` - Adjust the URL for your operating system (`linux/amd64`, `darwin/arm64`, `windows/amd64`). - -3. **Link the Extension Locally:** Use the Gemini CLI to install the +2. **Install the Extension Locally:** Use the Gemini CLI to install the extension from your local directory. ```bash - gemini extensions link . + gemini extensions install . ``` - The CLI will prompt you to confirm the linking. Accept it to proceed. + The CLI will prompt you to confirm the installation. Accept it to proceed. -4. **Testing Changes:** After linking, start the Gemini CLI (`gemini`). - You can now interact with the `firestore-native` tools to manually test your changes +3. **Testing Changes:** After installation, start the Gemini CLI (`gemini`). + You can now interact with the `firestore-native` skills to manually test your changes against your connected database. ## Testing -### Automated Presubmit Checks +### Automated Skills Validation A GitHub Actions workflow (`.github/workflows/presubmit-tests.yml`) is triggered for every pull request. This workflow primarily verifies that the extension can be successfully installed by the Gemini CLI. -Currently, there are no automated unit or integration test suites -within this repository. All functional testing must be performed manually. All tools -are currently tested in the [MCP Toolbox GitHub](https://github.com/googleapis/mcp-toolbox). +All tools are currently tested in the [MCP Toolbox GitHub](https://github.com/googleapis/mcp-toolbox). + +The skills themselves are validated using the `skills-validate.yml` workflow. ### Other GitHub Checks -* **License Header Check:** A workflow ensures all necessary files contain the - proper license header. -* **Conventional Commits:** This repository uses - [Release Please](https://github.com/googleapis/release-please) to manage - releases. Your commit messages must adhere to the - [Conventional Commits](https://www.conventionalcommits.org/) specification. -* **Dependency Updates:** [Renovate](https://github.com/apps/forking-renovate) - is configured to automatically create pull requests for dependency updates. +* **License Header Check:** A workflow ensures all necessary files contain the proper license header. +* **Conventional Commits:** This repository uses [Release Please](https://github.com/googleapis/release-please) to manage releases. Your commit messages must adhere to the [Conventional Commits](https://www.conventionalcommits.org/) specification. ## Maintainer Information ### Team -The primary maintainers for this repository are defined in the -[`.github/CODEOWNERS`](.github/CODEOWNERS) file: +The primary maintainers for this repository are defined in the [`.github/CODEOWNERS`](.github/CODEOWNERS) file: * `@gemini-cli-extensions/senseai-eco` * `@gemini-cli-extensions/firestore-native-maintainers` ### Releasing -The release process is automated using `release-please`. It consists of an automated changelog preparation step followed by the manual merging of a Release PR. +The release process is automated using `release-please`. It consists of an automated changelog preparation step followed by the manual merging of a Release PR. When the Release PR is merged, `release-please` creates a new GitHub tag and a corresponding GitHub Release. #### Automated Changelog Enrichment diff --git a/FIRESTORE-NATIVE.md b/FIRESTORE-NATIVE.md index f7a0d9f..1f95003 100644 --- a/FIRESTORE-NATIVE.md +++ b/FIRESTORE-NATIVE.md @@ -1,6 +1,4 @@ -You are a highly skilled database engineer and database administrator. Your purpose is to -help the developer build and interact with databases and utilize data context throughout the entire -software delivery cycle. +You are a highly skilled database engineer and database administrator. Your purpose is to help the developer build and interact with databases and utilize data context throughout the entire software delivery cycle. --- @@ -25,21 +23,22 @@ This section covers connecting to a Firestore instance. * Granting Roles: https://cloud.google.com/iam/docs/grant-role-console * Firestore Permissions: https://cloud.google.com/iam/docs/roles-permissions/firestore - --- # Usage Guidelines ## Connecting to New Resources -You will need to perform the following steps to change the current database connection: +When you want to change the current database connection, you will need to perform the following steps: 1. **(Optional) Save your conversation:** To avoid losing your progress, save the current session by running the command: `/chat save ` -2. **Stop the CLI:** Terminate the Gemini CLI. -3. **Update Environment Variables:** Set or update your environment variables (e.g. `FIRESTORE_DATABASE`) to point to the new resource. -4. **Restart:** Relaunch the Gemini CLI +2. **Stop the CLI**: Terminate the Gemini CLI. +3. **Update Extension Configuration**: Use the command `gemini extensions config firestore-native` to update your settings (e.g. `FIRESTORE_DATABASE`) to point to the new resource. +4. **Restart**: Relaunch the Gemini CLI 5. **(Optional) Resume conversation:** Resume your conversation with the command: `/chat resume ` +**Important:** Do not assume a connection to a newly created resource is active. Always follow the steps above to reconfigure your connection. + ## Reusing Project Values Users may have set project environment variables: @@ -47,5 +46,6 @@ Users may have set project environment variables: * `FIRESTORE_PROJECT`: The GCP project ID. * `FIRESTORE_DATABASE`: The Firestore database ID. -Instead of prompting the user for these values for specific tool calls, prompt the user to verify reuse a specific value. -Make sure to not use the environment variable name like `FIRESTORE_PROJECT`, `${FIRESTORE_PROJECT}`, or `$FIRESTORE_PROJECT`. The value can be found by using command: `echo $FIRESTORE_PROJECT`. +Instead of prompting the user for these values for specific skill calls, prompt the user to verify the reuse of a specific setting value. +Make sure to not use the environment variable name like `FIRESTORE_PROJECT`, `${FIRESTORE_PROJECT}`, or `$FIRESTORE_PROJECT`. +The value can be verified by the user using the `gemini extensions config firestore-native` command or by checking their local settings. diff --git a/README.md b/README.md index a5fd15d..6c6072c 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,39 @@ -# Gemini CLI Extension - Firestore Native +# Firestore Native Agent Skills > [!NOTE] -> This extension is currently in beta (pre-v1.0), and may see breaking changes until the first stable release (v1.0). +> Currently in beta (pre-v1.0), and may see breaking changes until the first stable release (v1.0). -This Gemini CLI extension provides a set of tools to interact with [Firestore](https://cloud.google.com/firestore/docs) databases. It allows you to manage your databases, documents, and collections directly from the [Gemini CLI](https://google-gemini.github.io/gemini-cli/), using natural language prompts. +This repository provides a set of agent skills to interact with [Firestore](https://cloud.google.com/firestore/docs) databases. These skills can be used with various AI agents, including [Gemini CLI](https://google-gemini.github.io/gemini-cli/), Claude Code, and Codex, to manage your databases, collections, and documents using natural language prompts. -Learn more about [Gemini CLI Extensions](https://github.com/google-gemini/gemini-cli/blob/main/docs/extensions/index.md). > [!IMPORTANT] > **We Want Your Feedback!** -> Please share your thoughts with us by filling out our feedback [form][form]. +> Please share your thoughts with us by filling out our feedback [form][form]. > Your input is invaluable and helps us improve the project for everyone. [form]: https://docs.google.com/forms/d/e/1FAIpQLSfEGmLR46iipyNTgwTmIDJqzkAwDPXxbocpXpUbHXydiN1RTw/viewform?usp=pp_url&entry.157487=firestore-native -## Why Use the Firestore Native Extension? - -* **Natural Language Management:** Stop wrestling with complex commands. Explore schemas and query data by describing what you want in plain English. -* **Seamless Workflow:** As a Google-developed extension, it integrates seamlessly into the Gemini CLI environment. No need to constantly switch contexts for common database tasks. -* **Code Generation:** Accelerate development by asking Gemini to generate data classes and other code snippets based on your table schemas. +## Table of Contents + +- [Why Use Firestore Native Agent Skills?](#why-use-firestore-native-agent-skills) +- [Prerequisites](#prerequisites) +- [Getting Started](#getting-started) + - [Configuration](#configuration) + - [Installation & Usage](#installation--usage) + - [Gemini CLI](#gemini-cli) + - [Claude Code](#claude-code) + - [Codex](#codex) + - [Antigravity](#antigravity) +- [Usage Examples](#usage-examples) +- [Supported Skills](#supported-skills) +- [Additional Agent Skills](#additional-agent-skills) +- [Troubleshooting](#troubleshooting) + +## Why Use Firestore Native Agent Skills? + +- **Seamless Workflow:** Integrates seamlessly into your AI agent's environment. No need to constantly switch contexts for common database tasks. +- **Natural Language Queries:** Stop wrestling with complex commands. Explore schemas and query data by describing what you want in plain English. +- **Data Management:** Manage your Firestore data directly from your agent. +- **Code Generation:** Accelerate development by asking your agent to generate data classes and other code snippets based on your document structures. ## 📺 Video Walkthrough Follow this step-by-step video walkthrough to setup and use the Firestore Native Extension. @@ -29,108 +45,207 @@ Follow this step-by-step video walkthrough to setup and use the Firestore Native Before you begin, ensure you have the following: -* [Gemini CLI](https://github.com/google-gemini/gemini-cli) installed with version **+v0.6.0**. -* Setup Gemini CLI [Authentication](https://github.com/google-gemini/gemini-cli/tree/main?tab=readme-ov-file#-authentication-options). -* A Google Cloud project with the **Firestore API** enabled. -* Ensure [Application Default Credentials](https://cloud.google.com/docs/authentication/gcloud) are available in your environment. -* IAM Permissions +- One of these AI agents installed + - [Gemini CLI](https://github.com/google-gemini/gemini-cli) version **v0.6.0** or higher + - [Claude Code](https://claude.com/product/claude-code) version **v2.1.94** or higher + - [Codex](https://developers.openai.com/codex) **v0.117.0** or higher + - [Antigravity](https://antigravity.google) **v1.14.2** or higher +- A Google Cloud project with the **Firestore API** enabled. +- Ensure [Application Default Credentials](https://cloud.google.com/docs/authentication/gcloud) are available in your environment. +- IAM Permissions: * Cloud Datastore User (`roles/datastore.user`) * Firebase Rules Viewer (`roles/firebaserules.viewer`) ## Getting Started -### Installation +### Configuration -To install the extension, use the `gemini extensions install` command: +Please keep these env vars handy during the installation process: -```bash -gemini extensions install https://github.com/gemini-cli-extensions/firestore-native -``` +- `FIRESTORE_PROJECT`: The GCP project ID. +- `FIRESTORE_DATABASE`: (Optional) The Firestore database ID. Defaults to `(default)`. -### Configuration +> [!NOTE] +> +> - Ensure [Application Default Credentials](https://cloud.google.com/docs/authentication/gcloud) are available in your environment. -You will be prompted to configure the following settings during installation. These settings are saved in an `.env` file within the extension's directory. +### Installation & Usage -* `FIRESTORE_PROJECT`: The GCP project ID. -* `FIRESTORE_DATABASE`: (Optional) The Firestore database ID. Defaults to `(default)`. +To start interacting with your database, install the skills for your preferred AI agent, then launch the agent and use natural language to ask questions or perform tasks. -To view or update your configuration: +For the latest version, check the [releases page][releases]. -**List Settings:** -* Terminal: `gemini extensions list` -* Gemini CLI: `/extensions list` +[releases]: https://github.com/gemini-cli-extensions/firestore-native/releases -**Update Settings:** -* Terminal: `gemini extensions config firestore-native [setting name] [--scope ]` - * `setting name`: (Optional) The single setting to configure. - * `scope`: (Optional) The scope of the setting in (`user` or `workspace`). Defaults to `user`. -* Currently, you must restart the Gemini CLI for changes to take effect. We recommend using `gemini --resume` to resume your session. + -Alternatively, you can manually set these environment variables before starting the Gemini CLI: +
+Gemini CLI + +**1. Install the extension:** ```bash -export FIRESTORE_PROJECT="" -export FIRESTORE_DATABASE="(default)" # Optional +gemini extensions install https://github.com/gemini-cli-extensions/firestore-native ``` -> [!NOTE] -> * Ensure [Application Default Credentials](https://cloud.google.com/docs/authentication/gcloud) are available in your environment. -> * See [Troubleshooting](#troubleshooting) for debugging your configuration. +During the installation, enter your environment vars as described in the [configuration section](#configuration). + +**2. (Optional) Manage Configuration:** +To view or update your configuration in Gemini CLI: -### Start Gemini CLI +- Terminal: `gemini extensions config firestore-native [setting name] [--scope ]` +- Gemini CLI: `/extensions list` -To start the Gemini CLI, use the following command: +**3. Start the agent:** ```bash gemini ``` +_(Tip: Run `/extensions list` to verify your configuration and active extensions.)_ + > [!WARNING] > **Changing Database Connections** -> Currently, the database connection must be configured before starting the Gemini CLI and can not be changed during a session. -> To save and resume conversation history use command: `/chat save ` and `/chat resume `. +> Currently, the database connection must be configured before starting the agent and can not be changed during a session. +> To save and resume conversation history in Gemini CLI use command: `/chat save ` and `/chat resume `. -## Usage Examples +
+ +
+Claude Code + +**1. Set env vars:** +In your terminal, set your environment vars as described in the [configuration section](#configuration). + +**2. Start the agent:** + +```bash +claude +``` + +**3. Add the marketplace:** + +```bash +/plugin marketplace add https://github.com/gemini-cli-extensions/firestore-native.git#0.2.1 +``` + +**4. Install the plugin:** + +```bash +/plugin install firestore-native@firestore-native-marketplace +``` + +_(Tip: Run `/plugin list` inside Claude Code to verify the plugin is active, or `/reload-plugins` if you just installed it.)_ -Interact with Firestore using natural language right from your IDE: +
-* **Document and Data Retrieval**: +
+Codex - * "Show me the Firestore data for the test users qa_user_123 and qa_user_456 from the users-staging collection." - * "Find all users in the users-staging collection whose wishlist contains product-glasses." +**1. Clone the Repo:** + +```bash +git clone --branch 0.2.1 git@github.com:gemini-cli-extensions/firestore-native.git +``` + +**2. Install the plugin:** + +```bash +mkdir -p ~/.codex/plugins +cp -R /absolute/path/to/firestore-native ~/.codex/plugins/firestore-native +``` + +**3. Set env vars:** +Enter your environment vars as described in the [configuration section](#configuration). + +**4. Create or update marketplace.json:** +`~/.agents/plugins/marketplace.json` + +```json +{ + "name": "my-data-cloud-google-marketplace", + "interface": { + "displayName": "Google Data Cloud Skills" + }, + "plugins": [ + { + "name": "firestore-native", + "source": { + "source": "local", + "path": "./plugins/firestore-native" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Database" + } + ] +} +``` + +_(Tip: Run `codex plugin list` or use the `/plugins` interactive menu to verify your installed plugins.)_ + +
+ +
+Antigravity + +**1. Clone the Repo:** + +```bash +git clone --branch 0.2.1 https://github.com/gemini-cli-extensions/firestore-native.git +``` + +**2. Install the skills:** + +Choose a location for the skills: +- **Global (all workspaces):** `~/.gemini/antigravity/skills/` +- **Workspace-specific:** `/.agents/skills/` + +Copy the skill folders from the cloned repository's `skills/` directory to your chosen location: + +```bash +cp -R firestore-native/skills/* ~/.gemini/antigravity/skills/ +``` + +**3. Set env vars:** +Set your environment vars as described in the [configuration section](#configuration). + +_(Tip: Antigravity automatically discovers skills in these directories at the start of a session.)_ + +
+ + + +## Usage Examples -* **Document Updates and Cleanup**: - * "For all 20 test users you just found, please remove product-glasses(inactive) from their wishlist." - * "Update the document with ID order-987 in the orders collection to set the status to 'Shipped'." +Interact with Firestore using natural language: -* **Security Rules Management**: - * "new_rules.txt is a new Firestore Security Rule I'm working on for staging. Can you validate it for me?" - * "Show me the active Firestore security rules for this project." +- **Document and Data Retrieval**: + - "Show me the Firestore data for the test users qa_user_123 and qa_user_456 from the users-staging collection." + - "Find all users in the users-staging collection whose wishlist contains product-glasses." +- **Document Updates and Cleanup**: + - "For all 20 test users you just found, please remove product-glasses(inactive) from their wishlist." + - "Update the document with ID order-987 in the orders collection to set the status to 'Shipped'." -## Supported Tools +## Supported Skills -This extension provides a comprehensive set of tools: +The following skills are available in this repository: -* `add_documents`: Use this tool to add documents to a Firestore collection path. -* `get_documents`: Use this tool to get multiple documents from Firestore by their paths. -* `list_collections`: Use this tool to list Firestore collections for a given parent path. -* `delete_documents`: Use this tool to delete multiple documents from Firestore. -* `query_collection`: Use this tool to query documents from a collection with filtering, ordering, and limit options. -* `get_rules`: Use this tool to retrieve the active Firestore security rules for the current project. -* `update_document`: Use this tool to update an existing document in Firestore by its path. -* `validate_rules`: Use this tool to validate Firestore security rules syntax and errors. +- [Firestore Data](./skills/firestore-data/SKILL.md) - Handles NoSQL document operations and collection hierarchy exploration. Use for CRUD tasks and data retrieval. Provides flexible document manipulation and structured querying. -## Additional Extensions +## Additional Agent Skills -Find additional extensions to support your entire software development lifecycle at [github.com/gemini-cli-extensions](https://github.com/gemini-cli-extensions). +Find additional skills to support your entire software development lifecycle at [github.com/gemini-cli-extensions](https://github.com/gemini-cli-extensions). ## Troubleshooting -Use `gemini --debug` to enable debugging. +Use the debug mode of your agent (e.g., `gemini --debug`) to enable debugging. Common issues: -* "failed to find default credentials: google: could not find default credentials.": Ensure [Application Default Credentials](https://cloud.google.com/docs/authentication/gcloud) are available in your environment. See [Set up Application Default Credentials](https://cloud.google.com/docs/authentication/external/set-up-adc) for more information. -* "✖ Error during discovery for server: MCP error -32000: Connection closed": The database connection has not been established. Ensure your configuration is set via environment variables. -* "✖ MCP ERROR: Error: spawn /Users/USER/.gemini/extensions/firestore-native/toolbox ENOENT": The Toolbox binary did not download correctly. Ensure you are using Gemini CLI v0.6.0+. -* "cannot execute binary file": The Toolbox binary did not download correctly. Ensure the correct binary for your OS/Architecture has been downloaded. See [Installing the server](https://mcp-toolbox.dev/documentation/introduction/#install-toolbox) for more information. +- "failed to find default credentials: google: could not find default credentials.": Ensure [Application Default Credentials](https://cloud.google.com/docs/authentication/gcloud) are available in your environment. See [Set up Application Default Credentials](https://cloud.google.com/docs/authentication/external/set-up-adc) for more information. +- "✖ Error during discovery for server: MCP error -32000: Connection closed": The database connection has not been established. Ensure your configuration is set via environment variables. +- "✖ MCP ERROR: Error: spawn .../toolbox ENOENT": The Toolbox binary did not download correctly. Ensure you are using the latest version of your agent. +- "cannot execute binary file": The Toolbox binary did not download correctly. Ensure the correct binary for your OS/Architecture has been downloaded. See [Installing the server](https://mcp-toolbox.dev/documentation/introduction/#install-toolbox) for more information. diff --git a/gemini-extension.json b/gemini-extension.json index a283dde..f405aa3 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -2,16 +2,6 @@ "name": "firestore-native", "version": "0.2.1", "description": "Connect and interact with Firestore databases, collections, and documents.", - "mcpServers": { - "firestore": { - "command": "${extensionPath}${/}toolbox", - "args": [ - "--prebuilt", - "firestore", - "--stdio" - ] - } - }, "contextFileName": "FIRESTORE-NATIVE.md", "settings": [ { diff --git a/skills/firestore-data/SKILL.md b/skills/firestore-data/SKILL.md new file mode 100644 index 0000000..85553a1 --- /dev/null +++ b/skills/firestore-data/SKILL.md @@ -0,0 +1,152 @@ +--- +name: firestore-data +description: Handles NoSQL document operations and collection hierarchy exploration. Use for CRUD tasks and data retrieval. Provides flexible document manipulation and structured querying. +--- + +## 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 + + +### add_documents + +Adds a new document to a Firestore collection. Please follow the best practices : + 1. Always use typed values in the documentData: Every field must be wrapped with its appropriate type indicator (e.g., {"stringValue": "text"}) + 2. Integer values can be strings in the documentData: The tool accepts integer values as strings (e.g., {"integerValue": "1500"}) + 3. Use returnData sparingly: Only set to true when you need to verify the exact data that was written + 4. Validate data before sending: Ensure your data matches Firestore's native JSON format + 5. Handle timestamps properly: Use RFC3339 format for timestamp strings + 6. Base64 encode binary data: Binary data must be base64 encoded in the bytesValue field + 7. Consider security rules: Ensure your Firestore security rules allow document creation in the target collection + + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| collectionPath | string | The relative path of the collection where the document will be added to (e.g., 'users' or 'users/userId/posts'). Note: This is a relative path, NOT an absolute path like 'projects/{project_id}/databases/{database_id}/documents/...' | Yes | | +| documentData | object | The document data in Firestore's native JSON format. Each field must be wrapped with a type indicator: +- Strings: {"stringValue": "text"} +- Integers: {"integerValue": "123"} or {"integerValue": 123} +- Doubles: {"doubleValue": 123.45} +- Booleans: {"booleanValue": true} +- Timestamps: {"timestampValue": "2025-01-07T10:00:00Z"} +- GeoPoints: {"geoPointValue": {"latitude": 34.05, "longitude": -118.24}} +- Arrays: {"arrayValue": {"values": [{"stringValue": "item1"}, {"integerValue": "2"}]}} +- Maps: {"mapValue": {"fields": {"key1": {"stringValue": "value1"}, "key2": {"booleanValue": true}}}} +- Null: {"nullValue": null} +- Bytes: {"bytesValue": "base64EncodedString"} +- References: {"referenceValue": "collection/document"} | Yes | | +| returnData | boolean | If set to true the output will have the data of the created document. This flag if set to false will help avoid overloading the context of the agent. | No | `false` | + + +--- + +### delete_documents + +Delete multiple documents from Firestore + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| documentPaths | array | Array of relative document paths to delete from Firestore (e.g., 'users/userId' or 'users/userId/posts/postId'). Note: These are relative paths, NOT absolute paths like 'projects/{project_id}/databases/{database_id}/documents/...' | Yes | | + + +--- + +### get_documents + +Gets multiple documents from Firestore by their paths + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| documentPaths | array | Array of relative document paths to retrieve from Firestore (e.g., 'users/userId' or 'users/userId/posts/postId'). Note: These are relative paths, NOT absolute paths like 'projects/{project_id}/databases/{database_id}/documents/...' | Yes | | + + +--- + +### list_collections + +List Firestore collections for a given parent path + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| parentPath | string | Relative parent document path to list subcollections from (e.g., 'users/userId'). If not provided, lists root collections. Note: This is a relative path, NOT an absolute path like 'projects/{project_id}/databases/{database_id}/documents/...' | No | | + + +--- + +### query_collection + +Retrieves one or more Firestore documents from a collection in a database in the current project by a collection with a full document path. +Use this if you know the exact path of a collection and the filtering clause you would like for the document. + + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| collectionPath | string | The relative path to the Firestore collection to query (e.g., 'users' or 'users/userId/posts'). Note: This is a relative path, NOT an absolute path like 'projects/{project_id}/databases/{database_id}/documents/...' | Yes | | +| filters | array | Array of filter objects to apply to the query. Each filter is a JSON string with: +- field: The field name to filter on +- op: The operator to use ("<", "<=", ">", ">=", "==", "!=", "array-contains", "array-contains-any", "in", "not-in") +- value: The value to compare against (can be string, number, boolean, or array) +Example: {"field": "age", "op": ">", "value": 18} | Yes | | +| orderBy | string | JSON string specifying the field and direction to order by (e.g., {"field": "name", "direction": "ASCENDING"}). Leave empty if not specified | Yes | | +| limit | integer | The maximum number of documents to return | No | `100` | +| analyzeQuery | boolean | If true, returns query explain metrics including execution statistics | No | `false` | + + +--- + +### update_document + +Updates an existing document in Firestore. Supports both full document updates and selective field updates using an update mask. Please follow the best practices: + 1. Use update masks for precision: When you only need to update specific fields, use the updateMask parameter to avoid unintended changes + 2. Always use typed values in the documentData: Every field must be wrapped with its appropriate type indicator (e.g., {"stringValue": "text"}) + 3. Delete fields using update mask: To delete fields, include them in the updateMask but omit them from documentData + 4. Integer values can be strings: The skill accepts integer values as strings (e.g., {"integerValue": "1500"}) + 5. Use returnData sparingly: Only set to true when you need to verify the exact data after the update + 6. Handle timestamps properly: Use RFC3339 format for timestamp strings + 7. Consider security rules: Ensure your Firestore security rules allow document updates + + +#### Parameters + +| Name | Type | Description | Required | Default | +| :--- | :--- | :--- | :--- | :--- | +| documentPath | string | The relative path of the document which needs to be updated (e.g., 'users/userId' or 'users/userId/posts/postId'). Note: This is a relative path, NOT an absolute path like 'projects/{project_id}/databases/{database_id}/documents/...' | Yes | | +| documentData | object | The document data in Firestore's native JSON format. Each field must be wrapped with a type indicator: +- Strings: {"stringValue": "text"} +- Integers: {"integerValue": "123"} or {"integerValue": 123} +- Doubles: {"doubleValue": 123.45} +- Booleans: {"booleanValue": true} +- Timestamps: {"timestampValue": "2025-01-07T10:00:00Z"} +- GeoPoints: {"geoPointValue": {"latitude": 34.05, "longitude": -118.24}} +- Arrays: {"arrayValue": {"values": [{"stringValue": "item1"}, {"integerValue": "2"}]}} +- Maps: {"mapValue": {"fields": {"key1": {"stringValue": "value1"}, "key2": {"booleanValue": true}}}} +- Null: {"nullValue": null} +- Bytes: {"bytesValue": "base64EncodedString"} +- References: {"referenceValue": "collection/document"} | Yes | | +| updateMask | array | The selective fields to update. If not provided, all fields in documentData will be updated. When provided, only the specified fields will be updated. Fields referenced in the mask but not present in documentData will be deleted from the document | No | | +| returnData | boolean | If set to true the output will have the data of the updated document. This flag if set to false will help avoid overloading the context of the agent. | No | `false` | + + +--- + diff --git a/skills/firestore-data/scripts/add_documents.js b/skills/firestore-data/scripts/add_documents.js new file mode 100755 index 0000000..152d491 --- /dev/null +++ b/skills/firestore-data/scripts/add_documents.js @@ -0,0 +1,103 @@ +#!/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 = "add_documents"; +const configArgs = ["--prebuilt", "firestore"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'FIRESTORE_DATABASE', +]; + + +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/firestore-data/scripts/delete_documents.js b/skills/firestore-data/scripts/delete_documents.js new file mode 100755 index 0000000..247bee7 --- /dev/null +++ b/skills/firestore-data/scripts/delete_documents.js @@ -0,0 +1,103 @@ +#!/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 = "delete_documents"; +const configArgs = ["--prebuilt", "firestore"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'FIRESTORE_DATABASE', +]; + + +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/firestore-data/scripts/get_documents.js b/skills/firestore-data/scripts/get_documents.js new file mode 100755 index 0000000..f15b4ae --- /dev/null +++ b/skills/firestore-data/scripts/get_documents.js @@ -0,0 +1,103 @@ +#!/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_documents"; +const configArgs = ["--prebuilt", "firestore"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'FIRESTORE_DATABASE', +]; + + +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/firestore-data/scripts/list_collections.js b/skills/firestore-data/scripts/list_collections.js new file mode 100755 index 0000000..7a589e9 --- /dev/null +++ b/skills/firestore-data/scripts/list_collections.js @@ -0,0 +1,103 @@ +#!/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_collections"; +const configArgs = ["--prebuilt", "firestore"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'FIRESTORE_DATABASE', +]; + + +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/firestore-data/scripts/query_collection.js b/skills/firestore-data/scripts/query_collection.js new file mode 100755 index 0000000..afa7487 --- /dev/null +++ b/skills/firestore-data/scripts/query_collection.js @@ -0,0 +1,103 @@ +#!/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 = "query_collection"; +const configArgs = ["--prebuilt", "firestore"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'FIRESTORE_DATABASE', +]; + + +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/firestore-data/scripts/update_document.js b/skills/firestore-data/scripts/update_document.js new file mode 100755 index 0000000..5dd489d --- /dev/null +++ b/skills/firestore-data/scripts/update_document.js @@ -0,0 +1,103 @@ +#!/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 = "update_document"; +const configArgs = ["--prebuilt", "firestore"]; + +const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [ + 'FIRESTORE_DATABASE', +]; + + +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();