Files
2026-07-13 13:30:30 +08:00

740 lines
36 KiB
Python

#!/usr/bin/env python3
"""
🚀 Gemini Enterprise Migration CLI
This script exposes all capabilities of the Gemini Enterprise Migration tool
directly via a deterministic, rapid Command-Line Interface.
"""
import os
import sys
import json
import argparse
import logging
# Ensure package directory is in sys.path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import core
# ANSI color codes
COLOR_GREEN = "\033[92m"
COLOR_YELLOW = "\033[93m"
COLOR_RED = "\033[91m"
COLOR_BLUE = "\033[94m"
COLOR_BOLD = "\033[1m"
COLOR_RESET = "\033[0m"
def print_color(text, color):
"""Prints text in the specified ANSI color to stdout, or to stderr if outputting JSON."""
file = sys.stderr if "--json" in sys.argv else sys.stdout
if file.isatty():
print(f"{color}{text}{COLOR_RESET}", file=file)
else:
print(text, file=file)
def format_table(headers, rows):
"""Formats and prints rows in a text-based grid table."""
if not rows:
print("No results found.")
return
# Calculate column widths
col_widths = [len(h) for headers_list in [headers] for h in headers_list]
for row in rows:
for i, val in enumerate(row):
str_val = str(val or "")
if len(str_val) > col_widths[i]:
col_widths[i] = len(str_val)
# Build separator
separator = "+" + "+".join("-" * (w + 2) for w in col_widths) + "+"
# Print headers
print_color(separator, COLOR_BOLD)
header_row = "|" + "|".join(f" {h:<{col_widths[i]}} " for i, h in enumerate(headers)) + "|"
print_color(header_row, COLOR_BOLD)
print_color(separator, COLOR_BOLD)
# Print rows
for row in rows:
row_str = "|" + "|".join(f" {str(row[i] or ''):<{col_widths[i]}} " for i in range(len(headers))) + "|"
print(row_str)
print_color(separator, COLOR_BOLD)
def get_env_default(var_names, default=None):
"""Returns the first non-empty environment variable value or default."""
for name in var_names:
val = os.environ.get(name)
if val:
return val
return default
try:
from skills import MigrationConfigSkill
SKILL_CFG = MigrationConfigSkill()
except Exception:
SKILL_CFG = None
def get_skill_env(env_type: str, key: str, default=None):
if SKILL_CFG:
cfg = SKILL_CFG.get_source_env() if env_type == "source" else SKILL_CFG.get_target_env()
return cfg.get(key, default)
return default
def handle_list_notebooks(args):
project = args.project or get_skill_env("source", "project_number") or get_env_default(["GOOGLE_CLOUD_PROJECT", "GEMINI_API_PROJECT"])
location = args.location or get_skill_env("source", "region") or get_env_default(["GOOGLE_CLOUD_LOCATION", "GEMINI_API_LOCATION"], "global")
if not project:
print_color("Error: Google Cloud project number must be provided via --project or environment variables.", COLOR_RED)
sys.exit(1)
print_color(f"Listing recently viewed notebooks in project {project} ({location})...", COLOR_BLUE)
try:
notebooks = core.list_notebooks(project, location)
if args.json:
print(json.dumps(notebooks, indent=2))
else:
headers = ["ID", "Title", "Create Time", "Update Time"]
rows = []
for nb in notebooks:
nb_id = nb.get("name", "").split("/")[-1]
rows.append([
nb_id,
nb.get("title", ""),
nb.get("createTime", "N/A"),
nb.get("updateTime", "N/A")
])
format_table(headers, rows)
except Exception as e:
print_color(f"Error: {e}", COLOR_RED)
sys.exit(1)
def handle_list_sources(args):
project = args.project or get_skill_env("source", "project_number") or get_env_default(["GOOGLE_CLOUD_PROJECT", "GEMINI_API_PROJECT"])
location = args.location or get_skill_env("source", "region") or get_env_default(["GOOGLE_CLOUD_LOCATION", "GEMINI_API_LOCATION"], "global")
if not project:
print_color("Error: Google Cloud project number must be provided via --project or environment variables.", COLOR_RED)
sys.exit(1)
print_color(f"Listing sources for notebook '{args.notebook_id}' in project {project}...", COLOR_BLUE)
try:
sources = core.list_sources_and_types(args.notebook_id, project, location)
if args.json:
# Strip raw_data from print to keep it clean, unless requested explicitly
for s in sources:
if not args.include_raw and "raw_data" in s:
s.pop("raw_data")
print(json.dumps(sources, indent=2))
else:
headers = ["Title", "ID", "Type", "Location"]
rows = []
for src in sources:
rows.append([
src.get("title", ""),
src.get("id", ""),
src.get("type", ""),
src.get("location", "")
])
format_table(headers, rows)
except Exception as e:
print_color(f"Error: {e}", COLOR_RED)
sys.exit(1)
def handle_migrate_notebook(args):
source_project = args.source_project or get_skill_env("source", "project_number") or get_env_default(["GOOGLE_CLOUD_PROJECT", "GEMINI_API_PROJECT"])
target_project = args.target_project or get_skill_env("target", "project_number") or get_env_default(["GOOGLE_CLOUD_PROJECT", "GEMINI_API_PROJECT"])
source_location = args.source_location or get_skill_env("source", "region") or get_env_default(["GOOGLE_CLOUD_LOCATION", "GEMINI_API_LOCATION"], "global")
target_location = args.target_location or get_skill_env("target", "region") or get_env_default(["GOOGLE_CLOUD_LOCATION", "GEMINI_API_LOCATION"], "global")
bucket = get_env_default(["GCS_BUCKET_NAME"])
if not source_project:
print_color("Error: Source project must be specified via --source-project or environment.", COLOR_RED)
sys.exit(1)
if not target_project:
print_color("Error: Target project must be specified via --target-project or environment.", COLOR_RED)
sys.exit(1)
print_color(f"Migrating notebook '{args.notebook}' from {source_project} ({source_location}) to {target_project} ({target_location})...", COLOR_BLUE)
try:
res = core.migrate_notebook_pipeline(
args.notebook,
target_project,
target_location,
source_project,
source_location,
bucket
)
if args.json:
print(json.dumps(res, indent=2))
else:
if res.get("success"):
print_color(f"\n🎉 Successfully migrated notebook '{res['source_notebook_title']}'!", COLOR_GREEN)
print(f"Target Notebook ID: {res['target_notebook_id']}")
print(f"Migrated Sources: {res['migrated_sources_count']}")
else:
print_color("\n⚠️ Notebook migrated with partial success.", COLOR_YELLOW)
print(f"Target Notebook ID: {res['target_notebook_id']}")
print(f"Migrated Sources: {res['migrated_sources_count']}")
print_color(f"Failed Sources: {res['failed_sources_count']}", COLOR_RED)
for f in res.get("failed", []):
print_color(f" - {f['title']}: {f['error']}", COLOR_RED)
except Exception as e:
print_color(f"Error during migration: {e}", COLOR_RED)
sys.exit(1)
def handle_list_agents(args):
project = args.project or get_skill_env("source", "project_number") or get_env_default(["GOOGLE_CLOUD_PROJECT", "GEMINI_API_PROJECT"])
location = args.location or get_skill_env("source", "region") or get_env_default(["GOOGLE_CLOUD_LOCATION", "GEMINI_API_LOCATION"], "global")
engine_id = args.engine_id or get_skill_env("source", "engine_id")
if not project:
print_color("Error: Google Cloud project number must be specified.", COLOR_RED)
sys.exit(1)
if not engine_id:
print_color("Error: Discovery Engine engine ID must be specified.", COLOR_RED)
sys.exit(1)
print_color(f"Listing employee-made agents in project {project} (Engine: {engine_id})...", COLOR_BLUE)
try:
agents = core.list_employee_agents(project, location, engine_id, basic=True)
if args.json:
for a in agents:
if "raw_agent" in a:
a.pop("raw_agent")
print(json.dumps(agents, indent=2))
else:
headers = ["Display Name", "Description", "Primary Tools"]
rows = []
for agent in agents:
tools_str = ", ".join(agent.get("connectors_and_tools", []))
if len(tools_str) > 40:
tools_str = tools_str[:37] + "..."
rows.append([
agent.get("displayName", ""),
agent.get("description", "No description"),
tools_str
])
format_table(headers, rows)
except Exception as e:
print_color(f"Error: {e}", COLOR_RED)
sys.exit(1)
def handle_get_agent_details(args):
project = args.project or get_skill_env("source", "project_number") or get_env_default(["GOOGLE_CLOUD_PROJECT", "GEMINI_API_PROJECT"])
location = args.location or get_skill_env("source", "region") or get_env_default(["GOOGLE_CLOUD_LOCATION", "GEMINI_API_LOCATION"], "global")
engine_id = args.engine_id or get_skill_env("source", "engine_id")
if not project:
print_color("Error: Google Cloud project number must be specified.", COLOR_RED)
sys.exit(1)
if not engine_id:
print_color("Error: Discovery Engine engine ID must be specified.", COLOR_RED)
sys.exit(1)
print_color(f"Retrieving details for employee agent '{args.agent_name}'...", COLOR_BLUE)
try:
agents = core.list_employee_agents(project, location, engine_id, basic=False)
for agent in agents:
if agent.get("displayName", "").lower() == args.agent_name.lower():
if args.json:
if "raw_agent" in agent:
agent.pop("raw_agent")
print(json.dumps(agent, indent=2))
else:
print_color(f"\nAgent: {agent['displayName']}", COLOR_BOLD)
print(f"Description: {agent.get('description', 'No description')}")
print(f"Instructions:\n{agent.get('instructions', 'No instructions')}")
print(f"Connectors & Tools: {', '.join(agent.get('connectors_and_tools', []))}")
print(f"Knowledge: {', '.join(agent.get('knowledge', [])) or 'None'}")
sub_agents_list = []
for sub in agent.get("sub_agents", []):
sub_agents_list.append(f"{sub.get('displayName')} ({sub.get('model')})")
print(f"Sub-Agents: {', '.join(sub_agents_list) or 'None'}")
return
print_color(f"Error: Agent '{args.agent_name}' not found.", COLOR_RED)
sys.exit(1)
except Exception as e:
print_color(f"Error: {e}", COLOR_RED)
sys.exit(1)
def handle_extract_datastores(args):
project = args.project or get_skill_env("source", "project_number") or get_env_default(["GOOGLE_CLOUD_PROJECT", "GEMINI_API_PROJECT"])
location = args.location or get_skill_env("source", "region") or get_env_default(["GOOGLE_CLOUD_LOCATION", "GEMINI_API_LOCATION"], "global")
engine_id = args.engine_id or get_skill_env("source", "engine_id")
if not project:
print_color("Error: Source project must be specified.", COLOR_RED)
sys.exit(1)
if not engine_id:
print_color("Error: Discovery Engine engine ID must be specified.", COLOR_RED)
sys.exit(1)
print_color(f"Extracting datastores for agent '{args.agent_name}'...", COLOR_BLUE)
try:
report = core.extract_agent_datastores(
args.agent_name,
project,
location,
engine_id
)
if args.json:
print(json.dumps(report, indent=2))
else:
print_color(f"\nDatastore dependencies for agent: {report['agent_name']}", COLOR_BOLD)
datastores = report.get("datastores", {})
if not datastores:
print("No datastores found.")
for node, ds_list in datastores.items():
print(f"Node '{node}':")
for ds in ds_list:
print(f" - {ds}")
except Exception as e:
print_color(f"Error: {e}", COLOR_RED)
sys.exit(1)
def handle_migrate_agent(args):
source_project = args.source_project or get_skill_env("source", "project_number") or get_env_default(["GOOGLE_CLOUD_PROJECT", "GEMINI_API_PROJECT"])
target_project = args.target_project or get_skill_env("target", "project_number") or get_env_default(["GOOGLE_CLOUD_PROJECT", "GEMINI_API_PROJECT"])
source_location = args.source_location or get_skill_env("source", "region") or get_env_default(["GOOGLE_CLOUD_LOCATION", "GEMINI_API_LOCATION"], "global")
target_location = args.target_location or get_skill_env("target", "region") or get_env_default(["GOOGLE_CLOUD_LOCATION", "GEMINI_API_LOCATION"], "global")
source_engine = args.source_engine or get_skill_env("source", "engine_id")
target_engine = args.target_engine or get_skill_env("target", "engine_id")
bucket = args.backup_bucket or get_env_default(["GCS_BUCKET_NAME"])
mapping_str = args.connector_mapping
if not mapping_str and SKILL_CFG:
mapping = SKILL_CFG.get_connectors_mapping()
mapping.update(SKILL_CFG.get_datastores_mapping())
if mapping:
mapping_str = json.dumps(mapping)
if not source_project or not target_project:
print_color("Error: Source and Target projects must be specified.", COLOR_RED)
sys.exit(1)
print_color(f"Migrating employee agent '{args.agent_name}' from project {source_project} to target project {target_project}...", COLOR_BLUE)
try:
res = core.migrate_employee_agent(
args.agent_name,
target_project,
target_location,
target_engine,
source_project,
source_location,
source_engine,
args.force,
bucket,
mapping_str
)
if args.json:
print(json.dumps(res, indent=2))
else:
if res.get("success"):
print_color(f"\n🎉 Successfully migrated agent '{args.agent_name}'!", COLOR_GREEN)
print(res.get("message"))
else:
# Handle pre-flight validation failure
if "warning" in res:
print_color(f"\n⚠️ Pre-flight Validation Failed: {res['warning']}", COLOR_YELLOW)
print(res.get("message"))
report = res.get("report", {})
print(f"Missing Connectors/Datastores: {report.get('missing_connectors', [])}")
print("\nTo bypass this check and proceed anyway, run the command again with --force.")
else:
print_color(f"Migration failed: {res.get('message')}", COLOR_RED)
except Exception as e:
print_color(f"Error: {e}", COLOR_RED)
sys.exit(1)
def handle_lookup_and_map_connectors(args):
source_project = args.source_project or get_skill_env("source", "project_number") or get_env_default(["GOOGLE_CLOUD_PROJECT", "GEMINI_API_PROJECT"])
target_project = args.target_project or get_skill_env("target", "project_number") or get_env_default(["GOOGLE_CLOUD_PROJECT", "GEMINI_API_PROJECT"])
source_location = args.source_location or get_skill_env("source", "region") or get_env_default(["GOOGLE_CLOUD_LOCATION", "GEMINI_API_LOCATION"], "global")
target_location = args.target_location or get_skill_env("target", "region") or get_env_default(["GOOGLE_CLOUD_LOCATION", "GEMINI_API_LOCATION"], "global")
source_engine = args.source_engine or get_skill_env("source", "engine_id")
target_engine = args.target_engine or get_skill_env("target", "engine_id")
try:
res = core.lookup_and_map_connectors(
args.agent_name,
target_project,
target_location,
target_engine,
source_project,
source_location,
source_engine
)
if args.json:
print(json.dumps(res, indent=2))
else:
headers = ["Source Connector / DataStore", "Target Mapping", "Status"]
mapping = res.get("proposed_mapping", {})
missing = res.get("missing_connectors", [])
rows = []
for k, v in mapping.items():
status = "❌ Missing" if k in missing else "✅ Mapped"
rows.append([k, v, status])
format_table(headers, rows)
if missing:
print_color(f"\n⚠️ Missing Connectors/DataStores in Target: {missing}", COLOR_YELLOW)
except Exception as e:
print_color(f"Error: {e}", COLOR_RED)
sys.exit(1)
def handle_list_datastores(args):
project = args.project or get_env_default(["GOOGLE_CLOUD_PROJECT", "GEMINI_API_PROJECT"])
location = args.location or get_env_default(["GOOGLE_CLOUD_LOCATION", "GEMINI_API_LOCATION"], "global")
if not project:
print_color("Error: Google Cloud project number must be specified.", COLOR_RED)
sys.exit(1)
print_color(f"Listing DataStores in project {project} ({location})...", COLOR_BLUE)
try:
datastores = core.list_datastores(project, location)
if args.json:
print(json.dumps(datastores, indent=2))
else:
headers = ["DataStore ID", "Display Name", "Full Resource Name"]
rows = [[ds["id"], ds["displayName"], ds["name"]] for ds in datastores]
format_table(headers, rows)
except Exception as e:
print_color(f"Error: {e}", COLOR_RED)
sys.exit(1)
def handle_export_agent_gcs(args):
project = args.project or get_env_default(["GOOGLE_CLOUD_PROJECT", "GEMINI_API_PROJECT"])
location = args.location or get_env_default(["GOOGLE_CLOUD_LOCATION", "GEMINI_API_LOCATION"], "global")
bucket = args.bucket or get_env_default(["GCS_BUCKET_NAME"])
if not project:
print_color("Error: Source project must be specified.", COLOR_RED)
sys.exit(1)
if not bucket:
print_color("Error: GCS bucket name must be specified via --bucket or environment.", COLOR_RED)
sys.exit(1)
print_color(f"Exporting agent '{args.agent_name}' definition to gs://{bucket}/{args.object_name}...", COLOR_BLUE)
try:
res = core.export_agent_to_gcs(
args.agent_name,
args.object_name,
bucket,
project,
location,
args.engine_id
)
if args.json:
print(json.dumps(res, indent=2))
else:
print_color(f"\n🎉 {res.get('message')}", COLOR_GREEN)
except Exception as e:
print_color(f"Error during export: {e}", COLOR_RED)
sys.exit(1)
def handle_import_agent_gcs(args):
project = args.target_project or get_env_default(["GOOGLE_CLOUD_PROJECT", "GEMINI_API_PROJECT"])
location = args.target_location or get_env_default(["GOOGLE_CLOUD_LOCATION", "GEMINI_API_LOCATION"], "global")
bucket = args.bucket or get_env_default(["GCS_BUCKET_NAME"])
mapping_str = ""
if SKILL_CFG:
mapping = SKILL_CFG.get_connectors_mapping()
mapping.update(SKILL_CFG.get_datastores_mapping())
if mapping:
mapping_str = json.dumps(mapping)
if not project:
print_color("Error: Target project must be specified.", COLOR_RED)
sys.exit(1)
if not bucket:
print_color("Error: GCS bucket name must be specified via --bucket or environment.", COLOR_RED)
sys.exit(1)
print_color(f"Importing agent definition from gs://{bucket}/{args.object_name} to project {project}...", COLOR_BLUE)
try:
res = core.import_agent_from_gcs(
args.object_name,
project,
location,
args.target_engine,
bucket,
mapping_str
)
if args.json:
print(json.dumps(res, indent=2))
else:
print_color(f"\n🎉 {res.get('message')}", COLOR_GREEN)
except Exception as e:
print_color(f"Error during import: {e}", COLOR_RED)
sys.exit(1)
def handle_export_notebook_gcs(args):
project = args.project or get_skill_env("source", "project_number") or get_env_default(["GOOGLE_CLOUD_PROJECT", "GEMINI_API_PROJECT"])
location = args.location or get_skill_env("source", "region") or get_env_default(["GOOGLE_CLOUD_LOCATION", "GEMINI_API_LOCATION"], "global")
bucket = args.bucket or get_env_default(["GCS_BUCKET_NAME"])
if not project:
print_color("Error: Source project must be specified.", COLOR_RED)
sys.exit(1)
if not bucket:
print_color("Error: GCS bucket name must be specified via --bucket or environment.", COLOR_RED)
sys.exit(1)
print_color(f"Exporting notebook '{args.notebook_name}' definition to gs://{bucket}/{args.object_name}...", COLOR_BLUE)
try:
res = core.export_notebook_to_gcs(
args.notebook_name,
args.object_name,
bucket,
project,
location
)
if args.json:
print(json.dumps(res, indent=2))
else:
print_color(f"\n🎉 {res.get('message')}", COLOR_GREEN)
except Exception as e:
print_color(f"Error during export: {e}", COLOR_RED)
sys.exit(1)
def handle_import_notebook_gcs(args):
project = args.target_project or get_skill_env("target", "project_number") or get_env_default(["GOOGLE_CLOUD_PROJECT", "GEMINI_API_PROJECT"])
location = args.target_location or get_skill_env("target", "region") or get_env_default(["GOOGLE_CLOUD_LOCATION", "GEMINI_API_LOCATION"], "global")
bucket = args.bucket or get_env_default(["GCS_BUCKET_NAME"])
if not project:
print_color("Error: Target project must be specified.", COLOR_RED)
sys.exit(1)
if not bucket:
print_color("Error: GCS bucket name must be specified via --bucket or environment.", COLOR_RED)
sys.exit(1)
print_color(f"Importing notebook definition from gs://{bucket}/{args.object_name} to project {project}...", COLOR_BLUE)
try:
res = core.import_notebook_from_gcs(
args.object_name,
project,
location,
bucket
)
if args.json:
print(json.dumps(res, indent=2))
else:
if res.get("success"):
print_color(f"\n🎉 Successfully imported notebook '{res['target_notebook_title']}'!", COLOR_GREEN)
print(f"Target Notebook ID: {res['target_notebook_id']}")
print(f"Migrated Sources: {res['migrated_sources_count']}")
else:
print_color("\n⚠️ Notebook imported with partial success.", COLOR_YELLOW)
print(f"Target Notebook ID: {res['target_notebook_id']}")
print(f"Migrated Sources: {res['migrated_sources_count']}")
print_color(f"Failed Sources: {res['failed_sources_count']}", COLOR_RED)
for f in res.get("failed", []):
print_color(f" - {f['title']}: {f['error']}", COLOR_RED)
except Exception as e:
print_color(f"Error during import: {e}", COLOR_RED)
sys.exit(1)
def handle_create_notebook(args):
target_project = args.target_project or get_env_default(["GOOGLE_CLOUD_PROJECT", "GEMINI_API_PROJECT"])
target_location = args.target_location or get_env_default(["GOOGLE_CLOUD_LOCATION", "GEMINI_API_LOCATION"], "global")
if not target_project:
print_color("Error: Target project must be specified.", COLOR_RED)
sys.exit(1)
print_color(f"Creating notebook '{args.title}' in project {target_project}...", COLOR_BLUE)
try:
res = core.create_notebook(target_project, target_location, args.title)
if args.json:
print(json.dumps(res, indent=2))
else:
print_color(f"🎉 Successfully created notebook '{args.title}'!", COLOR_GREEN)
print(f"ID: {res.get('name', '').split('/')[-1]}")
except Exception as e:
print_color(f"Error: {e}", COLOR_RED)
sys.exit(1)
def handle_add_source_to_notebook(args):
target_project = args.target_project or get_env_default(["GOOGLE_CLOUD_PROJECT", "GEMINI_API_PROJECT"])
target_location = args.target_location or get_env_default(["GOOGLE_CLOUD_LOCATION", "GEMINI_API_LOCATION"], "global")
if not target_project:
print_color("Error: Target project must be specified.", COLOR_RED)
sys.exit(1)
print_color(f"Adding source to notebook '{args.notebook_id}' in project {target_project}...", COLOR_BLUE)
try:
content_obj = json.loads(args.source_content)
res = core.add_source_to_notebook(target_project, target_location, args.notebook_id, content_obj)
if args.json:
print(json.dumps(res, indent=2))
else:
print_color(f"🎉 Successfully added source to notebook '{args.notebook_id}'!", COLOR_GREEN)
except Exception as e:
print_color(f"Error: {e}", COLOR_RED)
sys.exit(1)
def main():
parser = argparse.ArgumentParser(
description="🚀 Gemini Enterprise Migration CLI - Migrate low-code agents and notebooks deterministically."
)
parser.add_argument("--json", action="store_true", help="Output raw JSON results instead of user-friendly text tables")
subparsers = parser.add_subparsers(dest="command", help="Migration command to execute")
# List notebooks
p_list_nb = subparsers.add_parser("list-notebooks", help="List recently viewed notebooks in source project")
p_list_nb.add_argument("--project", help="Google Cloud project number (defaults to GOOGLE_CLOUD_PROJECT env)")
p_list_nb.add_argument("--location", help="Geographic location (defaults to GOOGLE_CLOUD_LOCATION env or 'global')")
p_list_nb.set_defaults(func=handle_list_notebooks)
# List sources
p_list_src = subparsers.add_parser("list-sources", help="List sources and their types in a specific notebook")
p_list_src.add_argument("notebook_id", help="The source notebook ID")
p_list_src.add_argument("--project", help="Google Cloud project number (defaults to GOOGLE_CLOUD_PROJECT env)")
p_list_src.add_argument("--location", help="Geographic location (defaults to GOOGLE_CLOUD_LOCATION env or 'global')")
p_list_src.add_argument("--include-raw", action="store_true", help="Include raw source metadata in JSON output")
p_list_src.set_defaults(func=handle_list_sources)
# Migrate notebook
p_mig_nb = subparsers.add_parser("migrate-notebook", help="Migrate a notebook and all its sources in a fast python loop")
p_mig_nb.add_argument("notebook", help="The source notebook ID or title to migrate")
p_mig_nb.add_argument("--source-project", help="Source project number (defaults to GOOGLE_CLOUD_PROJECT env)")
p_mig_nb.add_argument("--target-project", help="Target project number (defaults to GOOGLE_CLOUD_PROJECT env)")
p_mig_nb.add_argument("--source-location", help="Source geographic location (defaults to 'global')")
p_mig_nb.add_argument("--target-location", help="Target geographic location (defaults to 'global')")
p_mig_nb.set_defaults(func=handle_migrate_notebook)
# List agents
p_list_ag = subparsers.add_parser("list-agents", help="List all employee-made low-code agents")
p_list_ag.add_argument("--project", help="Source project number (defaults to GOOGLE_CLOUD_PROJECT env or SKILL.md)")
p_list_ag.add_argument("--engine-id", help="Discovery Engine engine ID containing the agents (defaults to SKILL.md)")
p_list_ag.add_argument("--location", help="Geographic location (defaults to 'global')")
p_list_ag.set_defaults(func=handle_list_agents)
# Get agent details
p_get_det = subparsers.add_parser("get-agent-details", help="Get details of a specific agent")
p_get_det.add_argument("agent_name", help="Exact display name of low-code agent")
p_get_det.add_argument("--project", help="Source project number (defaults to GOOGLE_CLOUD_PROJECT env or SKILL.md)")
p_get_det.add_argument("--engine-id", help="Discovery Engine engine ID containing the agent (defaults to SKILL.md)")
p_get_det.add_argument("--location", help="Geographic location (defaults to 'global')")
p_get_det.set_defaults(func=handle_get_agent_details)
# Extract datastores
p_ext_ds = subparsers.add_parser("extract-datastores", help="Extract datastores used by an agent and its subagents")
p_ext_ds.add_argument("agent_name", help="Exact display name of source low-code agent")
p_ext_ds.add_argument("--project", help="Source project number (defaults to GOOGLE_CLOUD_PROJECT env or SKILL.md)")
p_ext_ds.add_argument("--engine-id", help="Discovery Engine engine ID containing the agent (defaults to SKILL.md)")
p_ext_ds.add_argument("--location", help="Geographic location (defaults to 'global')")
p_ext_ds.set_defaults(func=handle_extract_datastores)
# Migrate agent
p_mig_ag = subparsers.add_parser("migrate-agent", help="Migrate a low-code agent from source to target")
p_mig_ag.add_argument("agent_name", help="Exact display name of source low-code agent to migrate")
p_mig_ag.add_argument("--source-project", help="Source project number (defaults to GOOGLE_CLOUD_PROJECT env)")
p_mig_ag.add_argument("--target-project", help="Target project number (defaults to GOOGLE_CLOUD_PROJECT env)")
p_mig_ag.add_argument("--source-engine", help="Source Discovery Engine engine ID")
p_mig_ag.add_argument("--target-engine", help="Target Discovery Engine engine ID")
p_mig_ag.add_argument("--source-location", help="Source geographic location (defaults to 'global')")
p_mig_ag.add_argument("--target-location", help="Target geographic location (defaults to 'global')")
p_mig_ag.add_argument("--force", action="store_true", help="Bypass connector pre-flight validation check")
p_mig_ag.add_argument("--backup-bucket", help="GCS bucket name for implicit backup (defaults to GCS_BUCKET_NAME env)")
p_mig_ag.add_argument("--connector-mapping", help="Optional JSON dict or comma-separated k:v mapping string", default="")
p_mig_ag.set_defaults(func=handle_migrate_agent)
# Lookup and Map Connectors
p_lookup_map = subparsers.add_parser("lookup-map-connectors", help="Lookup and intelligently map source connectors to target connectors")
p_lookup_map.add_argument("agent_name", help="Exact display name of source agent")
p_lookup_map.add_argument("--source-project", help="Source project number")
p_lookup_map.add_argument("--target-project", help="Target project number")
p_lookup_map.add_argument("--source-engine", help="Source engine ID")
p_lookup_map.add_argument("--target-engine", help="Target engine ID")
p_lookup_map.add_argument("--source-location", help="Source location", default="global")
p_lookup_map.add_argument("--target-location", help="Target location", default="global")
p_lookup_map.set_defaults(func=handle_lookup_and_map_connectors)
# List DataStores
p_list_ds = subparsers.add_parser("list-datastores", help="List available DataStores in a project")
p_list_ds.add_argument("--project", help="Google Cloud project number")
p_list_ds.add_argument("--location", help="Location (defaults to 'global')", default="global")
p_list_ds.set_defaults(func=handle_list_datastores)
# Export Agent GCS
p_exp_gcs = subparsers.add_parser("export-agent-gcs", help="Export an employee-made low-code agent to GCS")
p_exp_gcs.add_argument("agent_name", help="Exact display name of source low-code agent")
p_exp_gcs.add_argument("object_name", help="GCS object name (e.g. exports/my_agent.json)")
p_exp_gcs.add_argument("--bucket", help="GCS bucket name (defaults to GCS_BUCKET_NAME env)")
p_exp_gcs.add_argument("--project", help="Source project number (defaults to GOOGLE_CLOUD_PROJECT env)")
p_exp_gcs.add_argument("--engine-id", required=True, help="Source Discovery Engine engine ID")
p_exp_gcs.add_argument("--location", help="Source geographic location (defaults to 'global')")
p_exp_gcs.set_defaults(func=handle_export_agent_gcs)
# Import Agent GCS
p_imp_gcs = subparsers.add_parser("import-agent-gcs", help="Import an employee-made low-code agent definition from GCS")
p_imp_gcs.add_argument("object_name", help="GCS object name of stored definition JSON")
p_imp_gcs.add_argument("--bucket", help="GCS bucket name (defaults to GCS_BUCKET_NAME env)")
p_imp_gcs.add_argument("--target-project", help="Target project number (defaults to GOOGLE_CLOUD_PROJECT env)")
p_imp_gcs.add_argument("--target-engine", required=True, help="Target Discovery Engine engine ID")
p_imp_gcs.add_argument("--target-location", help="Target geographic location (defaults to 'global')")
p_imp_gcs.set_defaults(func=handle_import_agent_gcs)
# Export Notebook GCS
p_exp_nb_gcs = subparsers.add_parser("export-notebook-gcs", help="Export a NotebookLM notebook definition to GCS")
p_exp_nb_gcs.add_argument("notebook_name", help="Exact title or ID of source notebook")
p_exp_nb_gcs.add_argument("object_name", help="GCS object name (e.g. exports/my_notebook.json)")
p_exp_nb_gcs.add_argument("--bucket", help="GCS bucket name (defaults to GCS_BUCKET_NAME env)")
p_exp_nb_gcs.add_argument("--project", help="Source project number (defaults to GOOGLE_CLOUD_PROJECT env)")
p_exp_nb_gcs.add_argument("--location", help="Source geographic location (defaults to 'global')")
p_exp_nb_gcs.set_defaults(func=handle_export_notebook_gcs)
# Import Notebook GCS
p_imp_nb_gcs = subparsers.add_parser("import-notebook-gcs", help="Import a NotebookLM notebook definition from GCS")
p_imp_nb_gcs.add_argument("object_name", help="GCS object name of stored definition JSON")
p_imp_nb_gcs.add_argument("--bucket", help="GCS bucket name (defaults to GCS_BUCKET_NAME env)")
p_imp_nb_gcs.add_argument("--target-project", help="Target project number (defaults to GOOGLE_CLOUD_PROJECT env)")
p_imp_nb_gcs.add_argument("--target-location", help="Target geographic location (defaults to 'global')")
p_imp_nb_gcs.set_defaults(func=handle_import_notebook_gcs)
# Create notebook
p_create_nb = subparsers.add_parser("create-notebook", help="Create a new empty notebook in the target project")
p_create_nb.add_argument("title", help="The title of the notebook to create")
p_create_nb.add_argument("--target-project", help="Target project number (defaults to GOOGLE_CLOUD_PROJECT env)")
p_create_nb.add_argument("--target-location", help="Target geographic location (defaults to 'global')")
p_create_nb.set_defaults(func=handle_create_notebook)
# Add source to notebook
p_add_src = subparsers.add_parser("add-source-to-notebook", help="Add a single source to a notebook in the target project")
p_add_src.add_argument("notebook_id", help="The ID of the notebook to add the source to")
p_add_src.add_argument("source_content", help="The JSON string of the userContent to add")
p_add_src.add_argument("--target-project", help="Target project number (defaults to GOOGLE_CLOUD_PROJECT env)")
p_add_src.add_argument("--target-location", help="Target geographic location (defaults to 'global')")
p_add_src.set_defaults(func=handle_add_source_to_notebook)
args = parser.parse_args()
# Set up basic logging format
logging.basicConfig(level=logging.WARNING, format="%(asctime)s - %(levelname)s - %(message)s")
if not args.command:
parser.print_help()
sys.exit(0)
args.func(args)
if __name__ == "__main__":
main()