chore(automation): ensure status/need-triage is applied and never cleared automatically (#16657)

This commit is contained in:
Bryan Morgan
2026-01-14 20:58:50 -05:00
committed by GitHub
parent 4f324b548e
commit 467e869326
8 changed files with 268 additions and 98 deletions

138
.github/scripts/backfill-need-triage.cjs vendored Normal file
View File

@@ -0,0 +1,138 @@
/* eslint-disable */
/* global require, console, process */
/**
* Script to backfill the 'status/need-triage' label to all open issues
* that are NOT currently labeled with '🔒 maintainer only' or 'help wanted'.
*/
const { execFileSync } = require('child_process');
const isDryRun = process.argv.includes('--dry-run');
const REPO = 'google-gemini/gemini-cli';
/**
* Executes a GitHub CLI command safely using an argument array to prevent command injection.
* @param {string[]} args
* @returns {string|null}
*/
function runGh(args) {
try {
// Using execFileSync with an array of arguments is safe as it doesn't use a shell.
// We set a large maxBuffer (10MB) to handle repositories with many issues.
return execFileSync('gh', args, {
encoding: 'utf8',
maxBuffer: 10 * 1024 * 1024,
stdio: ['ignore', 'pipe', 'pipe'],
}).trim();
} catch (error) {
const stderr = error.stderr ? ` Stderr: ${error.stderr.trim()}` : '';
console.error(
`❌ Error running gh ${args.join(' ')}: ${error.message}${stderr}`,
);
return null;
}
}
async function main() {
console.log('🔐 GitHub CLI security check...');
const authStatus = runGh(['auth', 'status']);
if (authStatus === null) {
console.error('❌ GitHub CLI (gh) is not installed or not authenticated.');
process.exit(1);
}
if (isDryRun) {
console.log('🧪 DRY RUN MODE ENABLED - No changes will be made.\n');
}
console.log(`🔍 Fetching and filtering open issues from ${REPO}...`);
// We use the /issues endpoint with pagination to bypass the 1000-result limit.
// The jq filter ensures we exclude PRs, maintainer-only, help-wanted, and existing status/need-triage.
const jqFilter =
'.[] | select(.pull_request == null) | select([.labels[].name] as $l | (any($l[]; . == "🔒 maintainer only") | not) and (any($l[]; . == "help wanted") | not) and (any($l[]; . == "status/need-triage") | not)) | {number: .number, title: .title}';
const output = runGh([
'api',
`repos/${REPO}/issues?state=open&per_page=100`,
'--paginate',
'--jq',
jqFilter,
]);
if (output === null) {
process.exit(1);
}
const issues = output
.split('\n')
.filter((line) => line.trim())
.map((line) => {
try {
return JSON.parse(line);
} catch (_e) {
console.error(`⚠️ Failed to parse line: ${line}`);
return null;
}
})
.filter(Boolean);
console.log(`✅ Found ${issues.length} issues matching criteria.`);
if (issues.length === 0) {
console.log('✨ No issues need backfilling.');
return;
}
let successCount = 0;
let failCount = 0;
if (isDryRun) {
for (const issue of issues) {
console.log(
`[DRY RUN] Would label issue #${issue.number}: ${issue.title}`,
);
}
successCount = issues.length;
} else {
console.log(`🏷️ Applying labels to ${issues.length} issues...`);
for (const issue of issues) {
const issueNumber = String(issue.number);
console.log(`🏷️ Labeling issue #${issueNumber}: ${issue.title}`);
const result = runGh([
'issue',
'edit',
issueNumber,
'--add-label',
'status/need-triage',
'--repo',
REPO,
]);
if (result !== null) {
successCount++;
} else {
failCount++;
}
}
}
console.log(`\n📊 Summary:`);
console.log(` - Success: ${successCount}`);
console.log(` - Failed: ${failCount}`);
if (failCount > 0) {
console.error(`\n❌ Backfill completed with ${failCount} errors.`);
process.exit(1);
} else {
console.log(`\n🎉 ${isDryRun ? 'Dry run' : 'Backfill'} complete!`);
}
}
main().catch((error) => {
console.error('❌ Unexpected error:', error);
process.exit(1);
});

View File

@@ -1,4 +1,8 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# @license
# Copyright 2026 Google LLC
# SPDX-License-Identifier: Apache-2.0
set -euo pipefail set -euo pipefail
# Initialize a comma-separated string to hold PR numbers that need a comment # Initialize a comma-separated string to hold PR numbers that need a comment
@@ -10,7 +14,7 @@ ISSUE_LABELS_CACHE_FLAT=""
# Function to get area and priority labels from an issue (with caching) # Function to get area and priority labels from an issue (with caching)
get_issue_labels() { get_issue_labels() {
local ISSUE_NUM=$1 local ISSUE_NUM="${1}"
if [[ -z "${ISSUE_NUM}" || "${ISSUE_NUM}" == "null" || "${ISSUE_NUM}" == "" ]]; then if [[ -z "${ISSUE_NUM}" || "${ISSUE_NUM}" == "null" || "${ISSUE_NUM}" == "" ]]; then
return return
fi fi
@@ -18,10 +22,13 @@ get_issue_labels() {
# Check cache # Check cache
case " ${ISSUE_LABELS_CACHE_FLAT} " in case " ${ISSUE_LABELS_CACHE_FLAT} " in
*" ${ISSUE_NUM}:"*) *" ${ISSUE_NUM}:"*)
local suffix="${ISSUE_LABELS_CACHE_FLAT#* ${ISSUE_NUM}:}" local suffix="${ISSUE_LABELS_CACHE_FLAT#* " ${ISSUE_NUM}:"}"
echo "${suffix%% *}" echo "${suffix%% *}"
return return
;; ;;
*)
# Cache miss, proceed to fetch
;;
esac esac
echo " 📥 Fetching area and priority labels from issue #${ISSUE_NUM}" >&2 echo " 📥 Fetching area and priority labels from issue #${ISSUE_NUM}" >&2
@@ -33,19 +40,19 @@ get_issue_labels() {
fi fi
local labels local labels
labels=$(echo "${gh_output}" | grep -E "^(area|priority)/" | tr '\n' ',' | sed 's/,$//' || echo "") labels=$(echo "${gh_output}" | grep -E '^(area|priority)/' | tr '\n' ',' | sed 's/,$//' || echo "")
# Save to flat cache # Save to flat cache
ISSUE_LABELS_CACHE_FLAT="${ISSUE_LABELS_CACHE_FLAT} ${ISSUE_NUM}:${labels}" ISSUE_LABELS_CACHE_FLAT="${ISSUE_LABELS_CACHE_FLAT} ${ISSUE_NUM}:${labels}"
echo "$labels" echo "${labels}"
} }
# Function to process a single PR with pre-fetched data # Function to process a single PR with pre-fetched data
process_pr_optimized() { process_pr_optimized() {
local PR_NUMBER=$1 local PR_NUMBER="${1}"
local IS_DRAFT=$2 local IS_DRAFT="${2}"
local ISSUE_NUMBER=$3 local ISSUE_NUMBER="${3}"
local CURRENT_LABELS=$4 # Comma-separated labels local CURRENT_LABELS="${4}" # Comma-separated labels
echo "🔄 Processing PR #${PR_NUMBER}" echo "🔄 Processing PR #${PR_NUMBER}"
@@ -84,7 +91,7 @@ process_pr_optimized() {
ISSUE_LABELS=$(get_issue_labels "${ISSUE_NUMBER}") ISSUE_LABELS=$(get_issue_labels "${ISSUE_NUMBER}")
if [[ -n "${ISSUE_LABELS}" ]]; then if [[ -n "${ISSUE_LABELS}" ]]; then
local IFS_OLD=$IFS local IFS_OLD="${IFS}"
IFS=',' IFS=','
for label in ${ISSUE_LABELS}; do for label in ${ISSUE_LABELS}; do
if [[ -n "${label}" ]] && [[ ",${CURRENT_LABELS}," != *",${label},"* ]]; then if [[ -n "${label}" ]] && [[ ",${CURRENT_LABELS}," != *",${label},"* ]]; then
@@ -94,8 +101,8 @@ process_pr_optimized() {
LABELS_TO_ADD="${LABELS_TO_ADD},${label}" LABELS_TO_ADD="${LABELS_TO_ADD},${label}"
fi fi
fi fi
done done
IFS=$IFS_OLD IFS="${IFS_OLD}"
fi fi
if [[ -z "${LABELS_TO_ADD}" && -z "${LABELS_TO_REMOVE}" ]]; then if [[ -z "${LABELS_TO_ADD}" && -z "${LABELS_TO_REMOVE}" ]]; then
@@ -135,7 +142,7 @@ JQ_EXTRACT_FIELDS='{
labels: [.labels[].name] | join(",") labels: [.labels[].name] | join(",")
}' }'
JQ_TSV_FORMAT='"\((.number | tostring))\t\(.isDraft)\t\((.issue // "null") | tostring)\t\(.labels)"' JQ_TSV_FORMAT='"\((.number | tostring))\t\(.isDraft)\t\((.issue // \"null\") | tostring)\t\(.labels)"' # Corrected escaping for quotes within the string literal
if [[ -n "${PR_NUMBER:-}" ]]; then if [[ -n "${PR_NUMBER:-}" ]]; then
echo "🔄 Processing single PR #${PR_NUMBER}" echo "🔄 Processing single PR #${PR_NUMBER}"
@@ -144,9 +151,9 @@ if [[ -n "${PR_NUMBER:-}" ]]; then
exit 1 exit 1
} }
line=$(echo "$PR_DATA" | jq -r "$JQ_EXTRACT_FIELDS | $JQ_TSV_FORMAT") line=$(echo "${PR_DATA}" | jq -r "${JQ_EXTRACT_FIELDS} | ${JQ_TSV_FORMAT}")
IFS=$'\t' read -r pr_num is_draft issue_num current_labels <<< "$line" IFS=$'\t' read -r pr_num is_draft issue_num current_labels <<< "${line}"
process_pr_optimized "$pr_num" "$is_draft" "$issue_num" "$current_labels" process_pr_optimized "${pr_num}" "${is_draft}" "${issue_num}" "${current_labels}"
else else
echo "📥 Getting all open pull requests..." echo "📥 Getting all open pull requests..."
PR_DATA_ALL=$(gh pr list --repo "${GITHUB_REPOSITORY}" --state open --limit 1000 --json number,closingIssuesReferences,isDraft,body,labels 2>/dev/null) || { PR_DATA_ALL=$(gh pr list --repo "${GITHUB_REPOSITORY}" --state open --limit 1000 --json number,closingIssuesReferences,isDraft,body,labels 2>/dev/null) || {
@@ -157,11 +164,15 @@ else
PR_COUNT=$(echo "${PR_DATA_ALL}" | jq '. | length') PR_COUNT=$(echo "${PR_DATA_ALL}" | jq '. | length')
echo "📊 Found ${PR_COUNT} open PRs to process" echo "📊 Found ${PR_COUNT} open PRs to process"
# Use a temporary file to avoid masking exit codes in process substitution
tmp_file=$(mktemp)
echo "${PR_DATA_ALL}" | jq -r ".[] | ${JQ_EXTRACT_FIELDS} | ${JQ_TSV_FORMAT}" > "${tmp_file}"
while read -r line; do while read -r line; do
[[ -z "$line" ]] && continue [[ -z "${line}" ]] && continue
IFS=$'\t' read -r pr_num is_draft issue_num current_labels <<< "$line" IFS=$'\t' read -r pr_num is_draft issue_num current_labels <<< "${line}"
process_pr_optimized "$pr_num" "$is_draft" "$issue_num" "$current_labels" process_pr_optimized "${pr_num}" "${is_draft}" "${issue_num}" "${current_labels}"
done < <(echo "${PR_DATA_ALL}" | jq -r ".[] | $JQ_EXTRACT_FIELDS | $JQ_TSV_FORMAT") done < "${tmp_file}"
rm -f "${tmp_file}"
fi fi
if [[ -z "${PRS_NEEDING_COMMENT}" ]]; then if [[ -z "${PRS_NEEDING_COMMENT}" ]]; then
@@ -170,4 +181,4 @@ else
echo "prs_needing_comment=[${PRS_NEEDING_COMMENT}]" >> "${GITHUB_OUTPUT}" echo "prs_needing_comment=[${PRS_NEEDING_COMMENT}]" >> "${GITHUB_OUTPUT}"
fi fi
echo "✅ PR triage completed" echo "✅ PR triage completed"

View File

@@ -95,7 +95,8 @@ jobs:
id: 'generate_token' id: 'generate_token'
env: env:
APP_ID: '${{ secrets.APP_ID }}' APP_ID: '${{ secrets.APP_ID }}'
if: "${{ env.APP_ID != '' }}" if: |-
${{ env.APP_ID != '' }}
uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' # ratchet:actions/create-github-app-token@v2 uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' # ratchet:actions/create-github-app-token@v2
with: with:
app-id: '${{ secrets.APP_ID }}' app-id: '${{ secrets.APP_ID }}'
@@ -305,22 +306,6 @@ jobs:
}); });
core.info(`Successfully added labels for #${issueNumber}: ${labelsToAdd.join(', ')}`); core.info(`Successfully added labels for #${issueNumber}: ${labelsToAdd.join(', ')}`);
// Remove the 'status/need-triage' label
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
name: 'status/need-triage'
});
core.info(`Successfully removed 'status/need-triage' label.`);
} catch (error) {
// If the label doesn't exist, the API call will throw a 404. We can ignore this.
if (error.status !== 404) {
core.warning(`Failed to remove 'status/need-triage': ${error.message}`);
}
}
- name: 'Post Issue Analysis Failure Comment' - name: 'Post Issue Analysis Failure Comment'
if: |- if: |-
${{ failure() && steps.gemini_issue_analysis.outcome == 'failure' }} ${{ failure() && steps.gemini_issue_analysis.outcome == 'failure' }}

View File

@@ -40,7 +40,8 @@ jobs:
permission-issues: 'write' permission-issues: 'write'
- name: 'Get issue from event' - name: 'Get issue from event'
if: "github.event_name == 'issues'" if: |-
${{ github.event_name == 'issues' }}
id: 'get_issue_from_event' id: 'get_issue_from_event'
env: env:
ISSUE_EVENT: '${{ toJSON(github.event.issue) }}' ISSUE_EVENT: '${{ toJSON(github.event.issue) }}'
@@ -51,7 +52,8 @@ jobs:
echo "✅ Found issue #${{ github.event.issue.number }} from event to triage! 🎯" echo "✅ Found issue #${{ github.event.issue.number }} from event to triage! 🎯"
- name: 'Find untriaged issues' - name: 'Find untriaged issues'
if: "github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'" if: |-
${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
id: 'find_issues' id: 'find_issues'
env: env:
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token }}' GITHUB_TOKEN: '${{ steps.generate_token.outputs.token }}'
@@ -161,7 +163,6 @@ jobs:
9. For each issue please check if CLI version is present, this is usually in the output of the /about command and will look like 0.1.5 9. For each issue please check if CLI version is present, this is usually in the output of the /about command and will look like 0.1.5
- Anything more than 6 versions older than the most recent should add the status/need-retesting label - Anything more than 6 versions older than the most recent should add the status/need-retesting label
10. If you see that the issue doesn't look like it has sufficient information recommend the status/need-information label and leave a comment politely requesting the relevant information, eg.. if repro steps are missing request for repro steps. if version information is missing request for version information into the explanation section below. 10. If you see that the issue doesn't look like it has sufficient information recommend the status/need-information label and leave a comment politely requesting the relevant information, eg.. if repro steps are missing request for repro steps. if version information is missing request for version information into the explanation section below.
- After identifying appropriate labels to an issue, add "status/need-triage" label to labels_to_remove in the output.
11. If you think an issue might be a Priority/P0 do not apply the priority/p0 label. Instead apply a status/manual-triage label and include a note in your explanation. 11. If you think an issue might be a Priority/P0 do not apply the priority/p0 label. Instead apply a status/manual-triage label and include a note in your explanation.
12. If you are uncertain about a category, use the area/unknown, kind/question, or priority/unknown labels as appropriate. If you are extremely uncertain, apply the status/manual-triage label. 12. If you are uncertain about a category, use the area/unknown, kind/question, or priority/unknown labels as appropriate. If you are extremely uncertain, apply the status/manual-triage label.
@@ -262,24 +263,6 @@ jobs:
core.info(`Successfully added labels for #${issueNumber}: ${labelsToAdd.join(', ')}${explanation}`); core.info(`Successfully added labels for #${issueNumber}: ${labelsToAdd.join(', ')}${explanation}`);
} }
if (entry.labels_to_remove && entry.labels_to_remove.length > 0) {
for (const label of entry.labels_to_remove) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
name: label
});
} catch (error) {
if (error.status !== 404) {
throw error;
}
}
}
core.info(`Successfully removed labels for #${issueNumber}: ${entry.labels_to_remove.join(', ')}`);
}
if (entry.explanation) { if (entry.explanation) {
await github.rest.issues.createComment({ await github.rest.issues.createComment({
owner: context.repo.owner, owner: context.repo.owner,

View File

@@ -0,0 +1,46 @@
name: '🏷️ Issue Opened Labeler'
on:
issues:
types:
- 'opened'
jobs:
label-issue:
runs-on: 'ubuntu-latest'
if: |-
${{ github.repository == 'google-gemini/gemini-cli' || github.repository == 'google-gemini/maintainers-gemini-cli' }}
steps:
- name: 'Generate GitHub App Token'
id: 'generate_token'
env:
APP_ID: '${{ secrets.APP_ID }}'
if: |-
${{ env.APP_ID != '' }}
uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' # ratchet:actions/create-github-app-token@v2
with:
app-id: '${{ secrets.APP_ID }}'
private-key: '${{ secrets.PRIVATE_KEY }}'
- name: 'Add need-triage label'
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
with:
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
script: |-
const { data: issue } = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const hasLabel = issue.labels.some(l => l.name === 'status/need-triage');
if (!hasLabel) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['status/need-triage']
});
} else {
core.info('Issue already has status/need-triage label. Skipping.');
}

View File

@@ -4,37 +4,39 @@
# Example: ./scripts/batch_triage.sh google-gemini/maintainers-gemini-cli # Example: ./scripts/batch_triage.sh google-gemini/maintainers-gemini-cli
set -e set -e
set -o pipefail
REPO="${1:-google-gemini/gemini-cli}" REPO="${1:-google-gemini/gemini-cli}"
WORKFLOW="gemini-automated-issue-triage.yml" WORKFLOW="gemini-automated-issue-triage.yml"
echo "🔍 Searching for open issues in '$REPO' that need triage (missing 'area/' label)..." echo "🔍 Searching for open issues in '${REPO}' that need triage (missing 'area/' label)..."
# Fetch open issues with number, title, and labels # Fetch open issues with number, title, and labels
# We fetch up to 1000 issues. # We fetch up to 1000 issues.
ISSUES_JSON=$(gh issue list --repo "$REPO" --state open --limit 1000 --json number,title,labels) ISSUES_JSON=$(gh issue list --repo "${REPO}" --state open --limit 1000 --json number,title,labels)
# Filter issues that DO NOT have a label starting with 'area/' # Filter issues that DO NOT have a label starting with 'area/'
TARGET_ISSUES=$(echo "$ISSUES_JSON" | jq '[.[] | select(.labels | map(.name) | any(startswith("area/")) | not)]') TARGET_ISSUES=$(echo "${ISSUES_JSON}" | jq '[.[] | select(.labels | map(.name) | any(startswith("area/")) | not)]')
COUNT=$(echo "$TARGET_ISSUES" | jq '. | length') # Avoid masking return value
COUNT=$(jq '. | length' <<< "${TARGET_ISSUES}")
if [ "$COUNT" -eq 0 ]; then if [[ "${COUNT}" -eq 0 ]]; then
echo "✅ No issues found needing triage in '$REPO'." echo "✅ No issues found needing triage in '${REPO}'."
exit 0 exit 0
fi fi
echo "🚀 Found $COUNT issues to triage." echo "🚀 Found ${COUNT} issues to triage."
# Loop through and trigger workflow # Loop through and trigger workflow
echo "$TARGET_ISSUES" | jq -r '.[] | "\(.number)|\(.title)"' | while IFS="|" read -r number title; do echo "${TARGET_ISSUES}" | jq -r '.[] | "\(.number)|\(.title)"' | while IFS="|" read -r number title; do
echo "▶️ Triggering triage for #$number: $title" echo "▶️ Triggering triage for #${number}: ${title}"
# Trigger the workflow dispatch event # Trigger the workflow dispatch event
gh workflow run "$WORKFLOW" --repo "$REPO" -f issue_number="$number" gh workflow run "${WORKFLOW}" --repo "${REPO}" -f issue_number="${number}"
# Sleep briefly to be nice to the API # Sleep briefly to be nice to the API
sleep 1 sleep 1
done done
echo "🎉 All triage workflows triggered!" echo "🎉 All triage workflows triggered!"

View File

@@ -3,40 +3,42 @@
# Usage: ./scripts/relabel_issues.sh <old-label> <new-label> [repository] # Usage: ./scripts/relabel_issues.sh <old-label> <new-label> [repository]
set -e set -e
set -o pipefail
OLD_LABEL="$1" OLD_LABEL="${1}"
NEW_LABEL="$2" NEW_LABEL="${2}"
REPO="${3:-google-gemini/gemini-cli}" REPO="${3:-google-gemini/gemini-cli}"
if [ -z "$OLD_LABEL" ] || [ -z "$NEW_LABEL" ]; then if [[ -z "${OLD_LABEL}" ]] || [[ -z "${NEW_LABEL}" ]]; then
echo "Usage: $0 <old-label> <new-label> [repository]" echo "Usage: $0 <old-label> <new-label> [repository]"
echo "Example: $0 'area/models' 'area/agent'" echo "Example: $0 'area/models' 'area/agent'"
exit 1 exit 1
fi fi
echo "🔍 Searching for open issues in '$REPO' with label '$OLD_LABEL'..." echo "🔍 Searching for open issues in '${REPO}' with label '${OLD_LABEL}'..."
# Fetch issues with the old label # Fetch issues with the old label
ISSUES=$(gh issue list --repo "$REPO" --label "$OLD_LABEL" --state open --limit 1000 --json number,title) ISSUES=$(gh issue list --repo "${REPO}" --label "${OLD_LABEL}" --state open --limit 1000 --json number,title)
COUNT=$(echo "$ISSUES" | jq '. | length') # Avoid masking return value
COUNT=$(jq '. | length' <<< "${ISSUES}")
if [ "$COUNT" -eq 0 ]; then if [[ "${COUNT}" -eq 0 ]]; then
echo "✅ No issues found with label '$OLD_LABEL'." echo "✅ No issues found with label '${OLD_LABEL}'."
exit 0 exit 0
fi fi
echo "found $COUNT issues to relabel." echo "found ${COUNT} issues to relabel."
# Iterate and update # Iterate and update
echo "$ISSUES" | jq -r '.[] | "\(.number) \(.title)"' | while read -r number title; do echo "${ISSUES}" | jq -r '.[] | "\(.number) \(.title)"' | while read -r number title; do
echo "🔄 Processing #$number: $title" echo "🔄 Processing #${number}: ${title}"
echo " - Removing: $OLD_LABEL" echo " - Removing: ${OLD_LABEL}"
echo " + Adding: $NEW_LABEL" echo " + Adding: ${NEW_LABEL}"
gh issue edit "$number" --repo "$REPO" --add-label "$NEW_LABEL" --remove-label "$OLD_LABEL" gh issue edit "${number}" --repo "${REPO}" --add-label "${NEW_LABEL}" --remove-label "${OLD_LABEL}"
echo " ✅ Done." echo " ✅ Done."
done done
echo "🎉 All issues relabeled!" echo "🎉 All issues relabeled!"

View File

@@ -30,9 +30,10 @@
set -e -E set -e -E
# Load environment variables from .env if it exists # Load environment variables from .env if it exists
if [ -f ".env" ]; then if [[ -f ".env" ]]; then
echo "Loading environment variables from .env file..." echo "Loading environment variables from .env file..."
set -a # Automatically export all variables set -a # Automatically export all variables
# shellcheck source=/dev/null
source .env source .env
set +a set +a
fi fi
@@ -49,32 +50,32 @@ STREAM_MODE=false
# Parse command line arguments # Parse command line arguments
while [[ "$#" -gt 0 ]]; do while [[ "$#" -gt 0 ]]; do
case $1 in case $1 in
--payload) PAYLOAD_FILE="$2"; shift ;; --payload) PAYLOAD_FILE="${2}"; shift ;;
--model) MODEL_ID="$2"; shift ;; --model) MODEL_ID="${2}"; shift ;;
--stream) STREAM_MODE=true ;; --stream) STREAM_MODE=true ;;
*) echo "Unknown parameter passed: $1"; usage ;; *) echo "Unknown parameter passed: ${1}"; usage ;;
esac esac
shift shift
done done
# Validate inputs # Validate inputs
if [ -z "$PAYLOAD_FILE" ] || [ -z "$MODEL_ID" ]; then if [[ -z "${PAYLOAD_FILE}" ]] || [[ -z "${MODEL_ID}" ]]; then
echo "Error: Missing required arguments." echo "Error: Missing required arguments."
usage usage
fi fi
if [ -z "$GEMINI_API_KEY" ]; then if [[ -z "${GEMINI_API_KEY}" ]]; then
echo "Error: GEMINI_API_KEY environment variable is not set." echo "Error: GEMINI_API_KEY environment variable is not set."
exit 1 exit 1
fi fi
if [ ! -f "$PAYLOAD_FILE" ]; then if [[ ! -f "${PAYLOAD_FILE}" ]]; then
echo "Error: Payload file '$PAYLOAD_FILE' does not exist." echo "Error: Payload file '${PAYLOAD_FILE}' does not exist."
exit 1 exit 1
fi fi
# API Endpoint definition # API Endpoint definition
if [ "$STREAM_MODE" = true ]; then if [[ "${STREAM_MODE}" = true ]]; then
GENERATE_CONTENT_API="streamGenerateContent" GENERATE_CONTENT_API="streamGenerateContent"
echo "Mode: Streaming" echo "Mode: Streaming"
else else
@@ -82,16 +83,18 @@ else
echo "Mode: Non-streaming (Default)" echo "Mode: Non-streaming (Default)"
fi fi
echo "Sending request to model: $MODEL_ID" echo "Sending request to model: ${MODEL_ID}"
echo "Using payload from: $PAYLOAD_FILE" echo "Using payload from: ${PAYLOAD_FILE}"
echo "----------------------------------------" echo "----------------------------------------"
# Make the cURL request. If non-streaming, pipe through jq for readability if available. # Make the cURL request. If non-streaming, pipe through jq for readability if available.
if [ "$STREAM_MODE" = false ] && command -v jq &> /dev/null; then if [[ "${STREAM_MODE}" = false ]] && command -v jq &> /dev/null; then
curl -s -X POST \ # Invoke curl separately to avoid masking its return value
output=$(curl -s -X POST \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
"https://generativelanguage.googleapis.com/v1beta/models/${MODEL_ID}:${GENERATE_CONTENT_API}?key=${GEMINI_API_KEY}" \ "https://generativelanguage.googleapis.com/v1beta/models/${MODEL_ID}:${GENERATE_CONTENT_API}?key=${GEMINI_API_KEY}" \
-d "@${PAYLOAD_FILE}" | jq . -d "@${PAYLOAD_FILE}")
echo "${output}" | jq .
else else
curl -X POST \ curl -X POST \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \