diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index db5516bdfb7..0c13ae57aee 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", + "@google/generative-ai": "^0.3.1", "abort-controller": "^3.0.0", "ajv": "^6.12.6", "archiver": "^5.0.0", @@ -2557,6 +2558,14 @@ "node": ">=10" } }, + "node_modules/@google/generative-ai": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.3.1.tgz", + "integrity": "sha512-Zh1EK5nCWqIhxPm3K1KOM2mOwwgBvp6lkze74yTj6+Zqibtf55Db1q87cbbzWZeET3ZbNgHMYjVf2JyMM6pI7A==", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@grpc/grpc-js": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", @@ -20756,6 +20765,11 @@ "integrity": "sha512-97u6AUfEXo6TxoBAdbziuhSL56+l69WzFahR6eTQE/bSjGPqT1+W4vS7eKaR7r60pGFrZZfqdFZ99uMbns3qgA==", "dev": true }, + "@google/generative-ai": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.3.1.tgz", + "integrity": "sha512-Zh1EK5nCWqIhxPm3K1KOM2mOwwgBvp6lkze74yTj6+Zqibtf55Db1q87cbbzWZeET3ZbNgHMYjVf2JyMM6pI7A==" + }, "@grpc/grpc-js": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", diff --git a/package.json b/package.json index 43ea9f37fc5..62a1155faae 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ }, "dependencies": { "@google-cloud/pubsub": "^3.0.1", + "@google/generative-ai": "^0.3.1", "abort-controller": "^3.0.0", "ajv": "^6.12.6", "archiver": "^5.0.0", diff --git a/src/commands/gemini-help.ts b/src/commands/gemini-help.ts new file mode 100644 index 00000000000..ac4a99f13c9 --- /dev/null +++ b/src/commands/gemini-help.ts @@ -0,0 +1,8 @@ +import { Command } from "../command"; +import * as gemini from "../gemini"; + +export const command = new Command("gemini:help [prompt]") + .description("Ask gemini a question about how to use firebase-tools") + .action(async (prompt: string) => { + await gemini.ask(prompt); + }); diff --git a/src/commands/index.ts b/src/commands/index.ts index 1af7485b1bf..863346bfe19 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -138,6 +138,7 @@ export function load(client: any): any { client.functions.secrets.prune = loadCommand("functions-secrets-prune"); client.functions.secrets.set = loadCommand("functions-secrets-set"); client.help = loadCommand("help"); + client.help = loadCommand("gemini-help"); client.hosting = {}; client.hosting.channel = {}; client.hosting.channel.create = loadCommand("hosting-channel-create"); diff --git a/src/gemini/index.ts b/src/gemini/index.ts new file mode 100644 index 00000000000..957d00eb8c8 --- /dev/null +++ b/src/gemini/index.ts @@ -0,0 +1,80 @@ +import { FirebaseError } from "../error"; +import { logger } from "../logger"; +import * as fs from "fs"; +import * as path from "path"; +import { promptOnce } from "../prompt"; +import { execSync } from "child_process"; +import * as clc from "colorette"; + +const { GoogleGenerativeAI } = require("@google/generative-ai"); + +// TODO(christhompson): Do we have an endpoint that's open and doesn't require a project w/ billing? +// Estimated QPS is around 1. +// TODO(christhompson): Add project ID for this. +// TODO(christhompson): Add preamble information about command flags. +// TODO(christhompson): Figure out how to test this. +const generationConfig = { + maxOutputTokens: 200, + temperature: 0.9, + topP: 0.1, + topK: 16, +}; + +const genAI = new GoogleGenerativeAI("AIzaSyDDqii7EVJbMgfC3vhp3ER2o-EYa9qOSQw"); +const model = genAI.getGenerativeModel({ model: "gemini-pro", generationConfig }); + +function getPreamble(): string { + try { + const TEMPLATE_ROOT = path.resolve(__dirname, "../../templates/"); + + let data = fs.readFileSync(path.join(TEMPLATE_ROOT, "gemini", "preamble.txt"), "utf8"); + data = data + fs.readFileSync(path.join(TEMPLATE_ROOT, "gemini", "commandReadme.txt"), "utf8"); + return data; + } catch (err) { + throw new FirebaseError("Error reading preamble file" + err); + } +} + +async function run(prompt: string): Promise { + let result; + try { + const newPrompt = getPreamble() + prompt; + logger.debug("Running prompt: " + newPrompt); + result = await model.generateContent(newPrompt); + } catch (error) { + console.error("Promise rejected with error: " + error); + } + const response = await result.response; + return response.text(); +} + +export const ask = async function (prompt: string) { + const responseText = await run(prompt); + logger.info(clc.bold("Gemini Responded:")); + if ( + responseText.length > 0 && + responseText[0] === "`" && + responseText[responseText.length - 1] === "`" + ) { + // Assume this is a single command with backticks on each side. + const trimmedResponse = responseText.slice(1, responseText.length - 1); + logger.info(trimmedResponse); + const runNow = await promptOnce({ + type: "confirm", + name: "runNow", + message: `Would you like to run this command?`, + default: true, + }); + + // Try to add the role to the service account + if (runNow) { + logger.info("Running: " + trimmedResponse); + const newCommandOutput = execSync(trimmedResponse).toString(); // Doesn't output to console correctly + // TODO(christhompson): This doesn't transition well, only good for one-off commands that + // don't spawn long running subprocesses (not like emulators:start). + logger.info(newCommandOutput); + } + } else { + logger.info(responseText); + } +}; diff --git a/templates/gemini/commandReadme.txt b/templates/gemini/commandReadme.txt new file mode 100644 index 00000000000..b3623305033 --- /dev/null +++ b/templates/gemini/commandReadme.txt @@ -0,0 +1,135 @@ +Below are some sample commands. If you're not able to recommend a command, please refer me to https://firebase.google.com/docs/cli. + +## Commands + +**The command `firebase --help` lists the available commands and `firebase --help` shows more details for an individual command.** + +If a command is project-specific, you must either be inside a project directory with an +active project alias or specify the Firebase project id with the `-P ` flag. + +Below is a brief list of the available commands and their function: + +### Configuration Commands + +| Command | Description | +| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| **login** | Authenticate to your Firebase account. Requires access to a web browser. | +| **logout** | Sign out of the Firebase CLI. | +| **login:ci** | Generate an authentication token for use in non-interactive environments. | +| **login:add** | Authorize the CLI for an additional account. | +| **login:list** | List authorized CLI accounts. | +| **login:use** | Set the default account to use for this project | +| **use** | Set active Firebase project, manage project aliases. | +| **open** | Quickly open a browser to relevant project resources. | +| **init** | Setup a new Firebase project in the current directory. This command will create a `firebase.json` configuration file in your current directory. | +| **help** | Display help information about the CLI or specific commands. | + +Append `--no-localhost` to login (i.e., `firebase login --no-localhost`) to copy and paste code instead of starting a local server for authentication. A use case might be if you SSH into an instance somewhere and you need to authenticate to Firebase on that machine. + +### Project Management Commands + +| Command | Description | +| ------------------------ | ---------------------------------------------------------- | +| **apps:create** | Create a new Firebase app in a project. | +| **apps:list** | List the registered apps of a Firebase project. | +| **apps:sdkconfig** | Print the configuration of a Firebase app. | +| **projects:addfirebase** | Add Firebase resources to a Google Cloud Platform project. | +| **projects:create** | Create a new Firebase project. | +| **projects:list** | Print a list of all of your Firebase projects. | + +### Deployment and Local Emulation + +These commands let you deploy and interact with your Firebase services. + +| Command | Description | +| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| **emulators:exec** | Start the local Firebase emulators, run a test script, then shut down the emulators. | +| **emulators:start** | Start the local Firebase emulators. | +| **deploy** | Deploys your Firebase project. Relies on `firebase.json` configuration and your local project folder. | +| **serve** | Start a local server with your Firebase Hosting configuration and HTTPS-triggered Cloud Functions. Relies on `firebase.json`. | +| **setup:emulators:database** | Downloads the database emulator. | +| **setup:emulators:firestore** | Downloads the firestore emulator. | + +### App Distribution Commands + +| Command | Description | +| ------------------------------ | ---------------------- | +| **appdistribution:distribute** | Upload a distribution. | + +### Auth Commands + +| Command | Description | +| --------------- | ------------------------------------------------------ | +| **auth:import** | Batch importing accounts into Firebase from data file. | +| **auth:export** | Batch exporting accounts from Firebase into data file. | + +Detailed doc is [here](https://firebase.google.com/docs/cli/auth). + +### Realtime Database Commands + +| Command | Description | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| **database:get** | Fetch data from the current project's database and display it as JSON. Supports querying on indexed data. | +| **database:set** | Replace all data at a specified location in the current project's database. Takes input from file, STDIN, or command-line argument. | +| **database:push** | Push new data to a list at a specified location in the current project's database. Takes input from file, STDIN, or command-line argument. | +| **database:remove** | Delete all data at a specified location in the current project's database. | +| **database:update** | Perform a partial update at a specified location in the current project's database. Takes input from file, STDIN, or command-line argument. | +| **database:profile** | Profile database usage and generate a report. | +| **database:instances:create** | Create a realtime database instance. | +| **database:instances:list** | List realtime database instances. | +| **database:settings:get** | Read the realtime database setting at path | +| **database:settings:set** | Set the realtime database setting at path. | + +### Extensions Commands + +| Command | Description | +| ----------------- | ------------------------------------------------------------------------------------------- | +| **ext** | Display information on how to use ext commands and extensions installed to your project. | +| **ext:configure** | Configure an existing extension instance. | +| **ext:info** | Display information about an extension by name (extensionName@x.y.z for a specific version) | +| **ext:install** | Install an extension. | +| **ext:list** | List all the extensions that are installed in your Firebase project. | +| **ext:uninstall** | Uninstall an extension that is installed in your Firebase project by Instance ID. | +| **ext:update** | Update an existing extension instance to the latest version. | + +### Cloud Firestore Commands + +| Command | Description | +| --------------------- | ------------------------------------------------------------------------------------------------------------------- | +| **firestore:delete** | Delete documents or collections from the current project's database. Supports recursive deletion of subcollections. | +| **firestore:indexes** | List all deployed indexes from the current project. | + +### Cloud Functions Commands + +| Command | Description | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------ | +| **functions:log** | Read logs from deployed Cloud Functions. | +| **functions:list** | List all deployed functions in your Firebase project. | +| **functions:config:set** | Store runtime configuration values for the current project's Cloud Functions. | +| **functions:config:get** | Retrieve existing configuration values for the current project's Cloud Functions. | +| **functions:config:unset** | Remove values from the current project's runtime configuration. | +| **functions:config:clone** | Copy runtime configuration from one project environment to another. | +| **functions:secrets:set** | Create or update a secret for use in Cloud Functions for Firebase. | +| **functions:secrets:get** | Get metadata for secret and its versions. | +| **functions:secrets:access** | Access secret value given secret and its version. Defaults to accessing the latest version. | +| **functions:secrets:prune** | Destroys unused secrets. | +| **functions:secrets:destroy** | Destroy a secret. Defaults to destroying the latest version. | +| **functions:delete** | Delete one or more Cloud Functions by name or group name. | +| **functions:shell** | Locally emulate functions and start Node.js shell where these local functions can be invoked with test data. | + +### Hosting Commands + +| Command | Description | +| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **hosting:disable** | Stop serving Firebase Hosting traffic for the active project. A "Site Not Found" message will be displayed at your project's Hosting URL after running this command. | + +### Remote Config Commands + +| Command | Description | +| ------------------------------ | ---------------------------------------------------------------------------------------------------------- | +| **remoteconfig:get** | Get a Firebase project's Remote Config template. | +| **remoteconfig:versions:list** | Get a list of the most recent Firebase Remote Config template versions that have been published. | +| **remoteconfig:rollback** | Roll back a project's published Remote Config template to the version provided by `--version_number` flag. | + +Use `firebase:deploy --only remoteconfig` to update and publish a project's Firebase Remote Config template. + diff --git a/templates/gemini/preamble.txt b/templates/gemini/preamble.txt new file mode 100644 index 00000000000..6768b68c0f4 --- /dev/null +++ b/templates/gemini/preamble.txt @@ -0,0 +1,3 @@ +I am a user using the firebase command line interface. I am logged in with a project ID and I need help writing valid commands. In this environment I can't render markdown, please put commands on a single line with inline backticks like this: `firebase emulators:start`. + +Keep your responses to less than 300 characters.