Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
89c43a5
Point 5 of rigorous GAQL analysis
bobhancockg Jan 27, 2026
ee36006
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Jan 30, 2026
5818ef7
Merge branch 'main' into v1.6.0
bobhancockg Jan 30, 2026
ee89302
Merge branch 'main' into v1.6.0
bobhancockg Jan 30, 2026
db5e296
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Jan 30, 2026
243c22e
Added install-deps to setup procedure and updated tests.
bobhancockg Jan 30, 2026
b3b9a75
Add skill to get extension version
bobhancockg Feb 2, 2026
5702c64
Added hook at start session to make a custom google-ads.yaml
bobhancockg Feb 4, 2026
00433f0
Added hook at start session to make a custom google-ads.yaml
bobhancockg Feb 4, 2026
fbb1ef0
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Feb 4, 2026
e5695e1
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Feb 4, 2026
4d35ab6
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Feb 4, 2026
c497eb6
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Feb 4, 2026
7d5868e
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Feb 4, 2026
7468ace
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Feb 4, 2026
a79e9bc
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Feb 4, 2026
f3f2688
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Feb 4, 2026
b24f1e1
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Feb 4, 2026
f166b71
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Feb 4, 2026
8f2788e
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Feb 4, 2026
ec8ac6d
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Feb 4, 2026
417034b
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Feb 4, 2026
f689cd8
Added hooks for start and end of session.
bobhancockg Feb 5, 2026
c303992
3.3.2. MANDATORY GAQL Query Workflow
bobhancockg Feb 6, 2026
57fe242
Code example in GEMII.md for error handling
bobhancockg Feb 6, 2026
772dac9
Policy-Summary Field Rules
bobhancockg Feb 6, 2026
c173ab6
Field Existence Verification
bobhancockg Feb 6, 2026
a47d767
How to handle date ranges and refined handling of errors
bobhancockg Feb 6, 2026
dd4a2aa
Service-Specific Query Syntax
bobhancockg Feb 6, 2026
c3e6ea1
Reformatted GEMINI.md to make it consistent
bobhancockg Feb 6, 2026
cad8468
Much stronger directive to use config/google-ads.yaml
bobhancockg Feb 6, 2026
b6e6eef
Backup and override process for setting current API version.
bobhancockg Feb 9, 2026
795ccf2
Added hierarchical context file for conversion troubleshooting
bobhancockg Feb 9, 2026
882c538
Increased the importance to verify a GAQL query before presenting the
bobhancockg Feb 11, 2026
ea13234
Made conversions GEMINI.md hierarchical
bobhancockg Feb 11, 2026
92349c5
Structured conversions/GEMINI.md for AI machine comprehension
bobhancockg Feb 11, 2026
e9053a4
Python installed by default in setup process.
bobhancockg Feb 11, 2026
419e500
Removed debug log and print statements from exit hook
bobhancockg Feb 12, 2026
3c559f8
Modified setup proccess to install python by default. Other languages
bobhancockg Feb 12, 2026
0d447a0
- Setup now installs the extension after cloning from github.
bobhancockg Feb 12, 2026
8f4d6f5
Add instruction to not include env var for config file when explaining
bobhancockg Feb 12, 2026
bcf89a3
Added rule for inter-field compatibility check in queries.
bobhancockg Feb 14, 2026
7cb8736
Update segment verification rules for GAQL
bobhancockg Feb 14, 2026
2519ca8
Removed documentation link for conversions from the top level GEMINI.md
bobhancockg Feb 14, 2026
534ed3b
- Link from main gemini.md to conversions/gemini.md troubleshooting
bobhancockg Feb 16, 2026
326906a
Rules to GEMINI.md files to avoid errors in conversions debugging.
bobhancockg Feb 16, 2026
73eaaae
Changed directory structue to have one save directory with
bobhancockg Feb 16, 2026
a1e6a41
Additional rules for Python one liners to ensure correct syntax.
bobhancockg Feb 16, 2026
3c9eb11
Command to create support data for a ticket.
bobhancockg Feb 16, 2026
d99b55a
Reformated process to compose support data.
bobhancockg Feb 16, 2026
8bc6dff
Reformated process to compose support data.
bobhancockg Feb 16, 2026
7e0cf14
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Feb 16, 2026
97ac45b
Test for api_examples/collect converstion diagnostics
bobhancockg Feb 16, 2026
e9b72e6
Admonition in GEMINI.md to not use OR in WHERE clause.
bobhancockg Feb 17, 2026
2877039
Additional rules for specific GAQL queries.
bobhancockg Feb 17, 2026
2d6bcf2
Rules for GAQL and conversions
bobhancockg Feb 18, 2026
35e0557
GAQL rule specific to conversions
bobhancockg Feb 18, 2026
77782c7
Command for conversion report adds additional data
bobhancockg Feb 18, 2026
ee58b59
Added rule to ensure complete diagnostics in output files
bobhancockg Feb 18, 2026
40bf085
Updates to README and associated instructional files.
bobhancockg Feb 18, 2026
1d43a2d
Rules to make conversion diagnostic report clearer.
bobhancockg Feb 19, 2026
d8d4a34
Added rule to the the environment variable to the correct configuration
bobhancockg Feb 19, 2026
47b1e0d
Removed print to console on startup with the details of the config files
bobhancockg Feb 19, 2026
fedc57d
Added rule to handle change event quereies.
bobhancockg Feb 19, 2026
400ccf6
Optimized section 3.3.1 on GAQL validation.
bobhancockg Feb 19, 2026
33f7b02
Call Simply query validation dry run before presenting queries in
bobhancockg Feb 19, 2026
3462c96
Optimized GAQL validation section in tandem with calling the back end
bobhancockg Feb 19, 2026
597c6c2
Logic to deal with configuration if no google-ads.yaml in the home
bobhancockg Feb 19, 2026
64ab4d6
Modifed init hook to deal with absence of google-ads.yaml
bobhancockg Feb 19, 2026
1a66fb4
Modifed init hook to deal with absence of google-ads.yaml
bobhancockg Feb 19, 2026
f6bfa95
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Feb 19, 2026
cb23e49
Add support for ads.properties.
bobhancockg Feb 19, 2026
993dfee
Cleanup __pycache__
bobhancockg Feb 19, 2026
d5eb124
Added support for service account.
bobhancockg Feb 19, 2026
1d9bf18
Additional rules to context for new errors .
bobhancockg Feb 20, 2026
842ff1a
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Feb 20, 2026
be4125f
Removed startup debug messages.
bobhancockg Feb 20, 2026
e5d852d
Removed matcher from hooks.
bobhancockg Feb 20, 2026
a17ea98
Debugging hooks
bobhancockg Feb 20, 2026
0bda05b
Restore hook structure
bobhancockg Feb 20, 2026
997fcb2
Deduplication logic in cleanup because of system bug
bobhancockg Feb 20, 2026
2419e89
Atomic locking in cleanup_config.py
bobhancockg Feb 20, 2026
a23d104
Removed locking since it does not work
bobhancockg Feb 20, 2026
8c7488d
Added to quirks that the exit hook executes twice.
bobhancockg Feb 23, 2026
290f0a4
README refinements.
bobhancockg Feb 24, 2026
7de2366
Added rules to GEMINI.md
bobhancockg Feb 24, 2026
f26ea4c
conversions/GEMINI.md optimized for AI machine comprehension
bobhancockg Feb 24, 2026
c4e1dc6
Merge branch 'v1.6.0' of https://github.com/googleads/google-ads-api-…
bobhancockg Feb 24, 2026
80f0a72
Modified rules to consolidate support package files.
bobhancockg Feb 24, 2026
b228784
Optimized api_examples
bobhancockg Feb 24, 2026
5cfa872
Optimized api_examples/tests
bobhancockg Feb 24, 2026
f392c77
Added pitfalls to conversions/GEMINI.md
bobhancockg Feb 24, 2026
4d47fe9
Rules and command modifications to create consistent support package.
bobhancockg Feb 24, 2026
663a99d
Command conversions_support_data must write name of output file to
bobhancockg Feb 24, 2026
ddc9041
Update ChangeLog
bobhancockg Feb 24, 2026
80545d2
Vetted api_examples and test for errors
bobhancockg Feb 24, 2026
4c9d883
Added to pitfall to avoid conversion metadata errors.
bobhancockg Feb 25, 2026
3757716
Refinements to conversion troubleshooting output and process.
bobhancockg Feb 25, 2026
0024f83
Added +x to uninstall.sh
bobhancockg Feb 25, 2026
6396744
Delete extraneous file in saved/csv
bobhancockg Feb 25, 2026
dd318c7
Delete extra command file.
bobhancockg Feb 25, 2026
9d4dd21
Delete test_update_logic.sh
bobhancockg Feb 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .gemini/commands/conversions_support_package.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
description = "Collects structured diagnostic data for gTech conversion troubleshooting."

prompt = """
You are a helpful Google Ads API troubleshooting assistant.
The User is experiencing issues with conversions and needs to collect structured diagnostic data for gTech support.

Please execute the following actions:
1. At the top of the output file write "Created by the Google Ads API Developer Assistant"
2. If you have previously completed structured diagnostic analysis, include that text in the file.
3. Locate the current `customer_id` from `customer_id.txt` or context.
4. Run the structured troubleshooting script using the command: `python3 api_examples/collect_conversions_troubleshooting_data.py --customer_id <customer_id>`
5. Include a section "SUMMARY OF FINDINGS" containing the Summary and Error sections from the script's terminal output.
6. Include a section "DETAILED DIAGNOSTIC DATA" containing the **complete verbatim content** of the troubleshooting report generated by the script (found in `saved/data/`).
7. Save this consolidated data into a single file in `saved/data/` (e.g., `conversions_support_package_<epoch>.text`) and print name of file to console.

Here are the details from the user:
{{args}}
"""
39 changes: 39 additions & 0 deletions .gemini/hooks/cleanup_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import os
import shutil
import sys
import datetime

def cleanup():
# Determine paths
script_dir = os.path.dirname(os.path.abspath(__file__))
# .gemini/hooks/ -> project root is 2 levels up
project_root = os.path.abspath(os.path.join(script_dir, "../.."))
config_dir = os.path.join(project_root, "config")

if not os.path.exists(config_dir):
print(f"Config directory {config_dir} does not exist. Nothing to clean.", file=sys.stderr)
return

try:
# User requested to remove *all files* in the config directory.
# We could also remove the directory itself. Let's remove content.
for filename in os.listdir(config_dir):
if filename == ".gitkeep":
continue
file_path = os.path.join(config_dir, filename)
try:
if os.path.isfile(file_path) or os.path.islink(file_path):
os.unlink(file_path)
elif os.path.isdir(file_path):
shutil.rmtree(file_path)
except Exception as e:
print(f"Failed to delete {file_path}. Reason: {e}", file=sys.stderr)

timestamp = datetime.datetime.now()

except Exception as e:
print(f"Error cleaning up config directory: {e}", file=sys.stderr)
sys.exit(1)

if __name__ == "__main__":
cleanup()
196 changes: 196 additions & 0 deletions .gemini/hooks/custom_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import os
import shutil
import subprocess
import json
import sys
import re

def get_version(ext_version_script):
"""Retrieves the extension version."""
try:
result = subprocess.run(
[sys.executable, ext_version_script],
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
except Exception as e:
print(f"Error getting extension version: {e}", file=sys.stderr)
return "2.0.0" # Fallback

def parse_ruby_config(path):
"""Parses a Ruby config file for Google Ads."""
data = {}
patterns = {
"developer_token": r"c\.developer_token\s*=\s*['\"](.*?)['\"]",
"client_id": r"c\.client_id\s*=\s*['\"](.*?)['\"]",
"client_secret": r"c\.client_secret\s*=\s*['\"](.*?)['\"]",
"refresh_token": r"c\.refresh_token\s*=\s*['\"](.*?)['\"]",
"login_customer_id": r"c\.login_customer_id\s*=\s*['\"](.*?)['\"]",
"json_key_file_path": r"c\.json_key_file_path\s*=\s*['\"](.*?)['\"]",
"impersonated_email": r"c\.impersonated_email\s*=\s*['\"](.*?)['\"]",
}
try:
with open(path, "r") as f:
content = f.read()
for key, pattern in patterns.items():
match = re.search(pattern, content)
if match:
data[key] = match.group(1)
except Exception as e:
print(f"Error parsing Ruby config: {e}", file=sys.stderr)
return data

def parse_ini_config(path):
"""Parses a PHP INI config file for Google Ads."""
data = {}
patterns = {
"developer_token": r"developer_token\s*=\s*['\"]?(.*?)['\"]?\s*$",
"client_id": r"client_id\s*=\s*['\"]?(.*?)['\"]?\s*$",
"client_secret": r"client_secret\s*=\s*['\"]?(.*?)['\"]?\s*$",
"refresh_token": r"refresh_token\s*=\s*['\"]?(.*?)['\"]?\s*$",
"login_customer_id": r"login_customer_id\s*=\s*['\"]?(.*?)['\"]?\s*$",
"json_key_file_path": r"json_key_file_path\s*=\s*['\"]?(.*?)['\"]?\s*$",
"impersonated_email": r"impersonated_email\s*=\s*['\"]?(.*?)['\"]?\s*$",
}
try:
with open(path, "r") as f:
for line in f:
for key, pattern in patterns.items():
match = re.search(pattern, line)
if match:
data[key] = match.group(1)
except Exception as e:
print(f"Error parsing INI config: {e}", file=sys.stderr)
return data

def parse_properties_config(path):
"""Parses a Java properties config file for Google Ads."""
data = {}
mapping = {
"api.googleads.developerToken": "developer_token",
"api.googleads.clientId": "client_id",
"api.googleads.clientSecret": "client_secret",
"api.googleads.refreshToken": "refresh_token",
"api.googleads.loginCustomerId": "login_customer_id",
"api.googleads.oAuth2SecretsJsonPath": "json_key_file_path",
"api.googleads.oAuth2PrnEmail": "impersonated_email",
}
try:
with open(path, "r") as f:
for line in f:
if "=" in line:
k, v = line.split("=", 1)
k = k.strip()
if k in mapping:
data[mapping[k]] = v.strip()
except Exception as e:
print(f"Error parsing properties config: {e}", file=sys.stderr)
return data

def write_yaml_config(data, target_path, version):
"""Writes a standard Google Ads YAML config."""
try:
service_account = "json_key_file_path" in data
with open(target_path, "w") as f:
f.write("# Generated by Gemini CLI Assistant\n")
f.write("developer_token: " + data.get("developer_token", "INSERT_DEVELOPER_TOKEN_HERE") + "\n")

if service_account:
f.write("json_key_file_path: " + data["json_key_file_path"] + "\n")
if "impersonated_email" in data:
f.write("impersonated_email: " + data["impersonated_email"] + "\n")
else:
f.write("client_id: " + data.get("client_id", "INSERT_CLIENT_ID_HERE") + "\n")
f.write("client_secret: " + data.get("client_secret", "INSERT_CLIENT_SECRET_HERE") + "\n")
f.write("refresh_token: " + data.get("refresh_token", "INSERT_REFRESH_TOKEN_HERE") + "\n")

if "login_customer_id" in data:
f.write("login_customer_id: " + data["login_customer_id"] + "\n")
f.write("use_proto_plus: True\n")
f.write(f"gaada: \"{version}\"\n")
return True
except Exception as e:
print(f"Error writing YAML config: {e}", file=sys.stderr)
return False

def configure_language(lang_name, home_config, target_config, version, is_python=False):
"""Copies and versions a specific language configuration."""
if not os.path.exists(home_config):
return False

try:
shutil.copy2(home_config, target_config)
with open(target_config, "a", encoding="utf-8") as f:
sep = ":" if is_python else "="
f.write(f"\ngaada{sep} \"{version}\"\n")

return True
except Exception as e:
print(f"Error configuring {lang_name}: {e}", file=sys.stderr)
return False

def main():
script_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.abspath(os.path.join(script_dir, "../.."))
config_dir = os.path.join(project_root, "config")
ext_version_script = os.path.join(project_root, ".gemini/skills/ext_version/scripts/get_extension_version.py")

os.makedirs(config_dir, exist_ok=True)
version = get_version(ext_version_script)

home_dir = os.path.expanduser("~")
python_home = os.path.join(home_dir, "google-ads.yaml")
python_target = os.path.join(config_dir, "google-ads.yaml")

# 1. Try Python YAML first
if configure_language("Python", python_home, python_target, version, is_python=True):
print("Configured Python")
else:
# 2. Try fallbacks
fallbacks = [
("PHP", "google_ads_php.ini", parse_ini_config),
("Ruby", "google_ads_config.rb", parse_ruby_config),
("Java", "ads.properties", parse_properties_config),
]

found_fallback = False
for lang, filename, parser in fallbacks:
path = os.path.join(home_dir, filename)
if os.path.exists(path):
print(f"Found {lang} config at {path}. Converting to YAML...")
data = parser(path)
if write_yaml_config(data, python_target, version):
print(f"Successfully converted {lang} config to {python_target}")
print(f"export GOOGLE_ADS_CONFIGURATION_FILE_PATH=\"{python_target}\"", file=sys.stdout)
found_fallback = True
break

if not found_fallback:
print("Error: No Google Ads configuration found in home directory. Please create ~/google-ads.yaml.", file=sys.stderr)
sys.exit(1)

# 3. Configure other languages if requested by workspace context
languages = [
{"id": "google-ads-php", "name": "PHP", "filename": "google_ads_php.ini", "home": os.path.join(home_dir, "google_ads_php.ini")},
{"id": "google-ads-ruby", "name": "Ruby", "filename": "google_ads_config.rb", "home": os.path.join(home_dir, "google_ads_config.rb")},
{"id": "google-ads-java", "name": "Java", "filename": "ads.properties", "home": os.path.join(home_dir, "ads.properties")},
]

settings_path = os.path.join(project_root, ".gemini/settings.json")
if os.path.exists(settings_path):
try:
with open(settings_path, "r") as f:
settings = json.load(f)
include_dirs = settings.get("context", {}).get("includeDirectories", [])
except Exception:
include_dirs = []

for lang in languages:
if any(lang["id"] in d for d in include_dirs):
target = os.path.join(config_dir, lang["filename"])
configure_language(lang["name"], lang["home"], target, version)

if __name__ == "__main__":
main()
54 changes: 46 additions & 8 deletions .gemini/settings.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,56 @@
{
"ui": {
"accessibility": {
"disableLoadingPhrases": true
"disableLoadingPhrases": true,
"enableLoadingPhrases": false
}
},
"general": {
"checkpointing": {
"enabled": true
},
"sessionRetention": {
"enabled": true,
"maxAge": "30d",
"maxCount": 50
}
},
"context": {
"includeDirectories": [
"/path/to/your/extension/google-ads-api-developer-assistant/api_examples",
"/path/to/your/extension/google-ads-api-developer-assistant/saved_code",
"/path/to/your/library/google-ads-python",
"/path/to/your/library/google-ads-php",
"/path/to/your/library/google-ads-ruby",
"/path/to/your/library/google-ads-java",
"/path/to/your/library/google-ads-dotnet"
"/path/to/project_dir/google-ads-api-developer-assistant/api_examples",
"/path/to/project_dir/google-ads-api-developer-assistant/saved/code",
"/path/to/project_dir/google-ads-api-developer-assistant/client_libs/google-ads-python",
"/path/to/project_dir/google-ads-api-developer-assistant/client_libs/google-ads-php",
"/path/to/project_dir/google-ads-api-developer-assistant/client_libs/google-ads-ruby"
]
},
"tools": {
"enableHooks": true
},
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"name": "init",
"type": "command",
"command": "python3 .gemini/hooks/custom_config.py"
}
]
}
],
"SessionEnd": [
{
"matcher": "exit",
"hooks": [
{
"name": "cleanup",
"type": "command",
"command": "python3 .gemini/hooks/cleanup_config.py"
}
]
}
]
}
}
29 changes: 29 additions & 0 deletions .gemini/settings.json.bak
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"context": {
"fileFiltering": {
"enableRecursiveFileSearch": false
}
},
"ui": {
"theme": "Default Light"
},
"general": {
"preferredEditor": "vim"
},
"useSmartEdit": true,
"tools": {
"allowed": [
"read_file",
"read_many_files",
"list_directory",
"search_file_content",
"glob",
"web_fetch",
"google_web_search",
"save_memory",
"read_document"
],
"enableHooks": true
},
"enableHooks": true
}
Loading