<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Hiroshi Toyama</title>
    <description>The latest articles on Forem by Hiroshi Toyama (@toyama0919).</description>
    <link>https://forem.com/toyama0919</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F41804%2F7c54b968-6837-4276-9b8a-ac8f060c6f18.png</url>
      <title>Forem: Hiroshi Toyama</title>
      <link>https://forem.com/toyama0919</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/toyama0919"/>
    <language>en</language>
    <item>
      <title>Managing AI Agent Skills with `npx skills`: A Practical Guide</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Sat, 11 Apr 2026 08:04:45 +0000</pubDate>
      <link>https://forem.com/toyama0919/managing-ai-agent-skills-with-npx-skills-a-practical-guide-2an8</link>
      <guid>https://forem.com/toyama0919/managing-ai-agent-skills-with-npx-skills-a-practical-guide-2an8</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;AI agents like Claude Code, Cursor, and GitHub Copilot don't inherently know how to use every tool in your stack. You need a way to teach them. That's what &lt;code&gt;npx skills&lt;/code&gt; does — it's a package manager for AI agent behaviors, built by Vercel Labs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add microsoft/playwright-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command fetches a &lt;code&gt;SKILL.md&lt;/code&gt; from the specified GitHub repository and installs it into your agent's config directory (&lt;code&gt;.agents/skills/&lt;/code&gt; or &lt;code&gt;.claude/skills/&lt;/code&gt; depending on the agent).&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub as the Registry
&lt;/h3&gt;

&lt;p&gt;Unlike npm which uses npmjs.com, &lt;code&gt;skills&lt;/code&gt; uses GitHub as its registry. The &lt;code&gt;microsoft/playwright-cli&lt;/code&gt; argument maps directly to &lt;code&gt;https://github.com/microsoft/playwright-cli&lt;/code&gt;. Any public GitHub repo with a &lt;code&gt;SKILL.md&lt;/code&gt; at root is a valid skill source.&lt;/p&gt;

&lt;p&gt;You can also install by full URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add https://github.com/microsoft/playwright-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SKILL.md as the Package Entry Point
&lt;/h3&gt;

&lt;p&gt;Each skill repo contains a &lt;code&gt;SKILL.md&lt;/code&gt; — the equivalent of &lt;code&gt;index.js&lt;/code&gt; in an npm package. It contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Metadata&lt;/strong&gt;: name and description of the skill&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool definitions&lt;/strong&gt;: commands the AI can invoke (e.g. &lt;code&gt;playwright test&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt instructions&lt;/strong&gt;: when and how the AI should use the tool&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  .skills.json + skills-lock.json = package.json + package-lock.json
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;npm&lt;/th&gt;
&lt;th&gt;skills CLI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dependency manifest&lt;/td&gt;
&lt;td&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.skills.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lock file&lt;/td&gt;
&lt;td&gt;&lt;code&gt;package-lock.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;skills-lock.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Install directory&lt;/td&gt;
&lt;td&gt;&lt;code&gt;node_modules/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.agents/skills/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Registry&lt;/td&gt;
&lt;td&gt;npmjs.com&lt;/td&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Install command&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npm install&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx skills experimental_install&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After &lt;code&gt;npx skills add&lt;/code&gt;, your &lt;code&gt;.skills.json&lt;/code&gt; will look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"skills"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"playwright-cli"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"remote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"microsoft/playwright-cli"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"latest"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add a skill&lt;/span&gt;
npx skills add vercel-labs/agent-skills

&lt;span class="c"&gt;# Add globally (user-level, not project-level)&lt;/span&gt;
npx skills add vercel-labs/agent-skills &lt;span class="nt"&gt;-g&lt;/span&gt;

&lt;span class="c"&gt;# Target specific agents&lt;/span&gt;
npx skills add vercel-labs/agent-skills &lt;span class="nt"&gt;--agent&lt;/span&gt; claude-code cursor

&lt;span class="c"&gt;# List installed skills&lt;/span&gt;
npx skills list
npx skills &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt;           &lt;span class="c"&gt;# global skills&lt;/span&gt;
npx skills &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; cursor    &lt;span class="c"&gt;# filter by agent&lt;/span&gt;

&lt;span class="c"&gt;# Search the registry&lt;/span&gt;
npx skills find typescript

&lt;span class="c"&gt;# Update all skills&lt;/span&gt;
npx skills update

&lt;span class="c"&gt;# Restore from lock file (equivalent of npm ci)&lt;/span&gt;
npx skills experimental_install

&lt;span class="c"&gt;# Sync from node_modules to agent directories&lt;/span&gt;
npx skills experimental_sync

&lt;span class="c"&gt;# Scaffold a new skill&lt;/span&gt;
npx skills init my-skill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;remove&lt;/code&gt; Doesn't Update the Lock File
&lt;/h3&gt;

&lt;p&gt;This is the biggest footgun:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills &lt;span class="nb"&gt;rm &lt;/span&gt;microsoft/playwright-cli &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This removes the skill files from your agent directories, but &lt;strong&gt;leaves the entry in &lt;code&gt;skills-lock.json&lt;/code&gt;&lt;/strong&gt;. The next time someone runs &lt;code&gt;experimental_install&lt;/code&gt;, the skill comes back.&lt;/p&gt;

&lt;p&gt;Workaround:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;npx skills remove&lt;/code&gt; as usual&lt;/li&gt;
&lt;li&gt;Manually edit &lt;code&gt;.skills.json&lt;/code&gt; to remove the entry&lt;/li&gt;
&lt;li&gt;Delete &lt;code&gt;skills-lock.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;npx skills update&lt;/code&gt; or &lt;code&gt;add&lt;/code&gt; remaining skills to regenerate a clean lock file&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;experimental_&lt;/code&gt; Prefix is Real
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;experimental_install&lt;/code&gt; and &lt;code&gt;experimental_sync&lt;/code&gt; are genuinely experimental. The &lt;code&gt;sync&lt;/code&gt; command in the current version is not &lt;code&gt;npx skills sync&lt;/code&gt; — it's &lt;code&gt;npx skills experimental_install&lt;/code&gt; to restore from lock file, and &lt;code&gt;npx skills experimental_sync&lt;/code&gt; to sync from node_modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Behavior with npx
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;npx skills&lt;/code&gt; may run a cached older version. Force latest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills@latest add &amp;lt;repo&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For projects where everyone needs the same CLI version, add it as a devDependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CI/CD Integration
&lt;/h2&gt;

&lt;p&gt;Add to your CI setup to restore skills on each run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restore AI agent skills&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx skills experimental_install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures every developer and CI environment uses exactly the same skill versions as defined in &lt;code&gt;skills-lock.json&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Your Own Skill
&lt;/h2&gt;

&lt;p&gt;Any GitHub repo with a &lt;code&gt;SKILL.md&lt;/code&gt; is installable. Create one with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills init my-skill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This scaffolds a &lt;code&gt;SKILL.md&lt;/code&gt; that you push to GitHub. Anyone can then install it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add yourusername/my-skill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Browse existing skills at &lt;a href="https://skills.sh/" rel="noopener noreferrer"&gt;skills.sh&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;npx skills&lt;/code&gt; is npm for AI agent capabilities. The mental model maps cleanly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SKILL.md&lt;/code&gt; = &lt;code&gt;index.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.skills.json&lt;/code&gt; = &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;skills-lock.json&lt;/code&gt; = &lt;code&gt;package-lock.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;experimental_install&lt;/code&gt; = &lt;code&gt;npm ci&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;GitHub = npm registry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tooling is still experimental — particularly the lock file management on remove — but it's already useful for ensuring consistent AI behavior across team environments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devtools</category>
      <category>cli</category>
      <category>agents</category>
    </item>
    <item>
      <title>Deploying a Google ADK Agent to Vertex AI Agent Engine with Terraform</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Mon, 30 Mar 2026 12:45:33 +0000</pubDate>
      <link>https://forem.com/toyama0919/deploying-a-google-adk-agent-to-vertex-ai-agent-engine-with-terraform-83b</link>
      <guid>https://forem.com/toyama0919/deploying-a-google-adk-agent-to-vertex-ai-agent-engine-with-terraform-83b</guid>
      <description>&lt;p&gt;Most documentation for Vertex AI Agent Engine focuses on the Python SDK (&lt;code&gt;vertexai.agent_engines.create&lt;/code&gt;). That works fine for one-off deployments, but if you want your agent infrastructure managed declaratively alongside the rest of your GCP resources, Terraform is the right tool.&lt;/p&gt;

&lt;p&gt;This post walks through a complete Terraform setup for deploying a Google ADK agent to Vertex AI Agent Engine using &lt;code&gt;google_vertex_ai_reasoning_engine&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Terraform &amp;gt;= 1.5&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;google&lt;/code&gt; or &lt;code&gt;google-beta&lt;/code&gt; provider&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aiplatform.googleapis.com&lt;/code&gt; enabled&lt;/li&gt;
&lt;li&gt;A Google ADK agent wrapped in &lt;code&gt;AdkApp&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Agent Engine Deployment Works
&lt;/h2&gt;

&lt;p&gt;The deployment model is straightforward: &lt;strong&gt;tar.gz your source code, base64-encode it, and pass it to the API via &lt;code&gt;inline_source&lt;/code&gt;&lt;/strong&gt;. The runtime handles dependency installation, session management, and streaming — you just provide the entrypoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Agent Entrypoint
&lt;/h2&gt;

&lt;p&gt;The key requirement is an &lt;code&gt;AdkApp&lt;/code&gt; instance at the module level. This is what Terraform's &lt;code&gt;entrypoint_object&lt;/code&gt; points to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/myagent/agent.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.adk.agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;vertexai.agent_engines&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AdkApp&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Returns weather for a given city.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weather&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sunny&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;root_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weather_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Answers weather questions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Answer the user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s weather question using the get_weather tool.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# This is what entrypoint_object references
&lt;/span&gt;&lt;span class="n"&gt;agent_engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AdkApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;root_agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wrapping in &lt;code&gt;AdkApp&lt;/code&gt; automatically exposes &lt;code&gt;create_session&lt;/code&gt;, &lt;code&gt;stream_query&lt;/code&gt;, and other ADK methods as callable endpoints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Source Archive
&lt;/h2&gt;

&lt;p&gt;Agent Engine expects a base64-encoded tar.gz containing your source files and a &lt;code&gt;requirements.txt&lt;/code&gt;. Here's a minimal build script that uses only the Python standard library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# scripts/build_source.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tarfile&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;project_root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project_root&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;src_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project_root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;src&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;requirements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project_root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requirements.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tarfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileobj&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w:gz&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src_dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rglob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="n"&gt;arcname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;relative_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arcname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arcname&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requirements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arcname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requirements.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;b64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getvalue&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base64&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;b64&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;requirements.txt&lt;/code&gt; lists PyPI package names — the Agent Engine runtime installs them at deploy time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;google-adk&amp;gt;=1.0.0&lt;/span&gt;
&lt;span class="err"&gt;google-cloud-aiplatform[agent_engines]&amp;gt;=1.93.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Terraform Configuration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;provider.tf&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;google&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/google"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 6.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;google-beta&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/google-beta"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 6.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"google-beta"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"asia-northeast1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Wiring the Archive Build
&lt;/h3&gt;

&lt;p&gt;Use the &lt;code&gt;external&lt;/code&gt; data source to invoke the build script during &lt;code&gt;terraform plan&lt;/code&gt;/&lt;code&gt;apply&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"external"&lt;/span&gt; &lt;span class="s2"&gt;"agent_source"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;program&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"python3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/scripts/build_source.py"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;project_root&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/../.."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every &lt;code&gt;apply&lt;/code&gt; picks up the latest source automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Agent Engine Resource
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_vertex_ai_reasoning_engine"&lt;/span&gt; &lt;span class="s2"&gt;"my_agent"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google-beta&lt;/span&gt;
  &lt;span class="nx"&gt;display_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-agent-${var.env}"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ADK weather agent"&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"asia-northeast1"&lt;/span&gt;

  &lt;span class="nx"&gt;spec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;agent_framework&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"google-adk"&lt;/span&gt;
    &lt;span class="nx"&gt;service_account&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service_account_email&lt;/span&gt;

    &lt;span class="nx"&gt;class_methods&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"create_session"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;api_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"get_session"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="nx"&gt;api_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"list_sessions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="nx"&gt;api_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"delete_session"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;api_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"stream_query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="nx"&gt;api_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"stream"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="nx"&gt;source_code_spec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;inline_source&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;source_archive&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;external&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agent_source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base64&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;python_spec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;entrypoint_module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"src.myagent.agent"&lt;/span&gt;
        &lt;span class="nx"&gt;entrypoint_object&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"agent_engine"&lt;/span&gt;
        &lt;span class="nx"&gt;requirements_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"requirements.txt"&lt;/span&gt;
        &lt;span class="nx"&gt;version&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"3.12"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;deployment_spec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"LOG_LEVEL"&lt;/span&gt;
        &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INFO"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;secret_env&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"API_TOKEN"&lt;/span&gt;
        &lt;span class="nx"&gt;secret_ref&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;secret&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-api-token"&lt;/span&gt;
          &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"latest"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Block Reference
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Block&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;agent_framework&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Must be &lt;code&gt;"google-adk"&lt;/code&gt; — tells the runtime which framework to use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;class_methods&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enumerates callable methods; &lt;code&gt;api_mode = "stream"&lt;/code&gt; enables SSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inline_source&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Embeds the base64 tar.gz directly — no GCS bucket needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;python_spec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Specifies the entrypoint and Python version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deployment_spec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Injects env vars and Secret Manager secrets at runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;class_methods&lt;/code&gt; in Detail
&lt;/h3&gt;

&lt;p&gt;Every ADK method you want to expose must be declared explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;class_methods&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"create_session"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;api_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"get_session"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="nx"&gt;api_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"list_sessions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="nx"&gt;api_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"delete_session"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;api_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"stream_query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="nx"&gt;api_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"stream"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;api_mode = "stream"&lt;/code&gt; makes the method return a Server-Sent Events stream. Only &lt;code&gt;stream_query&lt;/code&gt; needs this — the rest are standard request/response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Provider choice matters.&lt;/strong&gt; &lt;code&gt;google_vertex_ai_reasoning_engine&lt;/code&gt; is available in both &lt;code&gt;google&lt;/code&gt; and &lt;code&gt;google-beta&lt;/code&gt; providers. Make sure the &lt;code&gt;provider&lt;/code&gt; attribute matches whichever you configure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;external&lt;/code&gt; data source re-runs on every plan.&lt;/strong&gt; This is by design — you always get the latest source. If your build script is slow, consider caching or only calling it on &lt;code&gt;apply&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;entrypoint_module&lt;/code&gt; uses dot notation, not file paths.&lt;/strong&gt; &lt;code&gt;src.myagent.agent&lt;/code&gt; maps to &lt;code&gt;src/myagent/agent.py&lt;/code&gt; in the archive. Match this to your actual directory structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secret Manager secrets must already exist.&lt;/strong&gt; Terraform reads existing secrets via &lt;code&gt;data&lt;/code&gt; sources — it doesn't create them. Provision secrets separately before running &lt;code&gt;apply&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy and Verify
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
terraform plan
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Export the resource name to call the agent from Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"agent_engine_resource_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_vertex_ai_reasoning_engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;vertexai&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;vertexai.agent_engines&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AdkApp&lt;/span&gt;

&lt;span class="n"&gt;vertexai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-project&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;asia-northeast1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AdkApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_resource_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;projects/my-project/locations/asia-northeast1/reasoningEngines/&amp;lt;ID&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s the weather in Tokyo?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;google_vertex_ai_reasoning_engine&lt;/code&gt; is available in both &lt;code&gt;google&lt;/code&gt; and &lt;code&gt;google-beta&lt;/code&gt; providers&lt;/li&gt;
&lt;li&gt;Source code is delivered as a base64 tar.gz via &lt;code&gt;inline_source&lt;/code&gt; — no GCS required&lt;/li&gt;
&lt;li&gt;A minimal Python script using only stdlib is enough to produce the archive&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;class_methods&lt;/code&gt; must explicitly enumerate every ADK method you want to expose&lt;/li&gt;
&lt;li&gt;Secret Manager integration is declarative via &lt;code&gt;secret_env&lt;/code&gt; blocks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Terraforming Agent Engine makes cleanup (&lt;code&gt;terraform destroy&lt;/code&gt;), environment promotion, and drift detection straightforward. The ADK + Terraform combination has sparse documentation, so hopefully this fills the gap.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>gcp</category>
      <category>googlecloud</category>
      <category>ai</category>
    </item>
    <item>
      <title>AI-Driven Chrome Extension Development with WXT and Chrome DevTools MCP</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Sun, 29 Mar 2026 12:21:39 +0000</pubDate>
      <link>https://forem.com/toyama0919/ai-driven-chrome-extension-development-with-wxt-and-chrome-devtools-mcp-4109</link>
      <guid>https://forem.com/toyama0919/ai-driven-chrome-extension-development-with-wxt-and-chrome-devtools-mcp-4109</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Building a Chrome extension that modifies a third-party web app is a unique challenge. The DOM structure is opaque, class names are minified and change between deployments, and there's no official API to hook into. Traditional extension development looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inspect the DOM manually in DevTools&lt;/li&gt;
&lt;li&gt;Write selectors and content scripts&lt;/li&gt;
&lt;li&gt;Reload the extension&lt;/li&gt;
&lt;li&gt;Check if it works&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This cycle is slow. I wanted an AI coding agent that could &lt;strong&gt;see the actual browser state&lt;/strong&gt; and &lt;strong&gt;verify its own changes&lt;/strong&gt; — not just generate code blindly.&lt;/p&gt;

&lt;p&gt;That's how I arrived at this stack: &lt;strong&gt;WXT&lt;/strong&gt; for the extension framework, &lt;strong&gt;Chrome DevTools MCP&lt;/strong&gt; for giving the AI agent browser access, and &lt;strong&gt;Cursor&lt;/strong&gt; as the IDE tying it all together.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://wxt.dev" rel="noopener noreferrer"&gt;WXT&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Chrome extension framework (TypeScript, hot reload, Manifest V3)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/nichochar/chrome-devtools-mcp" rel="noopener noreferrer"&gt;Chrome DevTools MCP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;MCP server that exposes Chrome DevTools Protocol to AI agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cursor.sh" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;AI-powered IDE with native MCP support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 1: WXT with a Fixed CDP Port
&lt;/h2&gt;

&lt;p&gt;WXT is a framework that wraps Chrome extension development with file-based routing, hot reload, and TypeScript support out of the box. The key insight is that WXT's runner can launch Chrome with &lt;strong&gt;custom Chromium args&lt;/strong&gt; — including &lt;code&gt;--remote-debugging-port&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// wxt.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wxt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My Extension&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;extensionApi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chrome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;chromiumArgs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--remote-debugging-port=9222&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;`--user-data-dir=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;/.chrome-debug-profile`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--exclude-switches=enable-automation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;startUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things to note:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--remote-debugging-port=9222&lt;/code&gt;&lt;/strong&gt; — Exposes the Chrome DevTools Protocol on a fixed port. The MCP server connects here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--user-data-dir&lt;/code&gt;&lt;/strong&gt; — A dedicated profile directory, separate from your daily Chrome. Login sessions persist across dev restarts. &lt;strong&gt;Add this to &lt;code&gt;.gitignore&lt;/code&gt;&lt;/strong&gt; — it contains cookies and session tokens that must not be pushed to a repository.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--exclude-switches=enable-automation&lt;/code&gt;&lt;/strong&gt; — Without this, some sites detect the "automated" browser and block sign-in.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When you run &lt;code&gt;wxt&lt;/code&gt; (or &lt;code&gt;npm run dev&lt;/code&gt;), WXT launches Chrome with these args, loads your extension, and watches for file changes — all in one command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Chrome DevTools MCP Configuration
&lt;/h2&gt;

&lt;p&gt;MCP (Model Context Protocol) lets AI agents call external tools. Chrome DevTools MCP is an MCP server that wraps the Chrome DevTools Protocol — giving your AI agent the ability to navigate pages, evaluate JavaScript, take screenshots, and inspect the DOM.&lt;/p&gt;

&lt;p&gt;Configuration lives in &lt;code&gt;.cursor/mcp.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"chrome-devtools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"chrome-devtools-mcp@latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--browserUrl=http://127.0.0.1:9222"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. When Cursor starts, it spins up the MCP server, which connects to Chrome on port 9222. The AI agent can now see and interact with your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: The Dev Script (Optional but Recommended)
&lt;/h2&gt;

&lt;p&gt;A small shell script wrapping the two main workflows keeps things ergonomic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./dev.sh dev      &lt;span class="c"&gt;# WXT dev server + Chrome (hot reload + MCP)&lt;/span&gt;
./dev.sh start    &lt;span class="c"&gt;# Load built extension (no hot reload, MCP only)&lt;/span&gt;
./dev.sh stop     &lt;span class="c"&gt;# Kill debug Chrome&lt;/span&gt;
./dev.sh status   &lt;span class="c"&gt;# Check CDP connection&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;dev&lt;/code&gt; is the primary mode — WXT handles everything and you get hot reload. &lt;code&gt;start&lt;/code&gt; is for testing production builds with MCP inspection. The script handles edge cases like port conflicts, PID management, and connection verification via &lt;code&gt;curl http://localhost:9222/json/version&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Workflow in Practice
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Start the environment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev   &lt;span class="c"&gt;# or ./dev.sh dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Chrome opens automatically with the extension loaded. WXT watches for file changes. The MCP server connects to port 9222. Cursor's AI agent can now see the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. AI inspects current state
&lt;/h3&gt;

&lt;p&gt;The AI agent evaluates JavaScript in the browser context to understand the current DOM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The agent runs this via MCP&lt;/span&gt;
&lt;span class="nf"&gt;evaluate_script&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`() =&amp;gt; ({
    injectedStyle: !!document.getElementById('my-extension-styles'),
    buttonCount: document.querySelectorAll('.my-custom-button').length,
    panelVisible: !!document.getElementById('my-panel'),
  })`&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent can &lt;strong&gt;verify its own changes without you switching context&lt;/strong&gt;. It writes code, WXT hot-reloads, and the agent checks if the DOM updated correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. AI verifies changes through the browser
&lt;/h3&gt;

&lt;p&gt;Here's the key difference from normal AI-assisted coding. Instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I've added the panel. Please refresh and check if it works."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI does this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I've added the panel. Let me verify... [evaluates script via MCP] ... The &lt;code&gt;#my-panel&lt;/code&gt; element exists, has 5 child entries, and is positioned correctly. Rendering looks good."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Text-based DOM verification is preferred over screenshots — it's faster, cheaper, and more precise:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Good: structured verification&lt;/span&gt;
&lt;span class="nf"&gt;evaluate_script&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buttons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.my-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;firstButton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;outerHTML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Screenshots only when you need visual layout confirmation&lt;/span&gt;
&lt;span class="nf"&gt;take_screenshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tips and Gotchas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Content Script Isolation
&lt;/h3&gt;

&lt;p&gt;Chrome extension content scripts run in an &lt;strong&gt;isolated world&lt;/strong&gt;. Variables set on &lt;code&gt;window&lt;/code&gt; in the content script are invisible to &lt;code&gt;evaluate_script&lt;/code&gt; via MCP, because MCP evaluates in the &lt;strong&gt;page context&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The workaround: verify through DOM side effects, not global variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Won't work: window globals are in the isolated world&lt;/span&gt;
&lt;span class="nf"&gt;evaluate_script&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;myExtensionState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// → undefined&lt;/span&gt;

&lt;span class="c1"&gt;// Works: check the DOM changes the extension made&lt;/span&gt;
&lt;span class="nf"&gt;evaluate_script&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;styleInjected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-extension-styles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;panelExists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-panel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Selector Strategy for Third-Party UIs
&lt;/h3&gt;

&lt;p&gt;When building extensions for sites you don't control, selectors break frequently. A fallback chain helps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[aria-label*="Submit"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-test-id="submit"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.submit-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Priority:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;ARIA attributes&lt;/strong&gt; (&lt;code&gt;aria-label&lt;/code&gt;, &lt;code&gt;role&lt;/code&gt;) — most stable across updates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic attributes&lt;/strong&gt; (&lt;code&gt;data-test-id&lt;/code&gt;) — moderately stable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Class names&lt;/strong&gt; — last resort, always provide as fallback&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can even build a DOM analyzer shortcut (&lt;code&gt;Ctrl+Shift+D&lt;/code&gt;) that exports the page structure in a format the AI agent can consume. When selectors break, press the shortcut, paste the output into Cursor, and the agent updates the fallback selectors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Async DOM Waiting
&lt;/h3&gt;

&lt;p&gt;SPA elements appear asynchronously. Rather than fragile &lt;code&gt;setTimeout&lt;/code&gt; chains, use polling with bounded retries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the element never appears, fail silently — no console spam.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google Login Gotcha
&lt;/h3&gt;

&lt;p&gt;When Chrome launches with &lt;code&gt;--remote-debugging-port&lt;/code&gt;, Google sometimes detects it as an "unsafe browser" and blocks sign-in. The &lt;code&gt;--exclude-switches=enable-automation&lt;/code&gt; flag helps, but if it's not enough:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Launch Chrome with the dedicated profile (without WXT)&lt;/li&gt;
&lt;li&gt;Sign in manually&lt;/li&gt;
&lt;li&gt;Close Chrome&lt;/li&gt;
&lt;li&gt;Now run &lt;code&gt;npm run dev&lt;/code&gt; — WXT reuses the same profile with the valid session&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The dedicated &lt;code&gt;--user-data-dir&lt;/code&gt; persists your login across dev sessions.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;--user-data-dir&lt;/code&gt; and Security
&lt;/h3&gt;

&lt;p&gt;The dedicated profile serves two purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Isolation from your daily Chrome&lt;/strong&gt;: The dev browser doesn't touch your bookmarks, extensions, or sessions — and your personal credentials don't leak into the dev environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal credentials&lt;/strong&gt;: Only log into what you need for development. Don't sign into personal Gmail or other unrelated accounts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always add the profile directory to &lt;code&gt;.gitignore&lt;/code&gt;&lt;/strong&gt;. It contains cookies, session tokens, and LocalStorage.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.chrome-debug-profile/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CDP port 9222 is accessible from localhost&lt;/strong&gt;. &lt;code&gt;--remote-debugging-port&lt;/code&gt; binds to &lt;code&gt;127.0.0.1&lt;/code&gt; by default, but any process on your machine can access all open tabs. Only run it during active development.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't use this on shared machines&lt;/strong&gt;. While CDP is open, anyone on the same machine can control the browser session.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;If you want to try this workflow:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create a WXT project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create wxt@latest my-extension
&lt;span class="nb"&gt;cd &lt;/span&gt;my-extension
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Add CDP port to &lt;code&gt;wxt.config.ts&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;chromiumArgs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--remote-debugging-port=9222&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`--user-data-dir=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;/.chrome-debug-profile`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--exclude-switches=enable-automation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Create &lt;code&gt;.cursor/mcp.json&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"chrome-devtools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chrome-devtools-mcp@latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--browserUrl=http://127.0.0.1:9222"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Run
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Chrome launches with CDP enabled, Cursor's agent connects. Your AI agent can now see your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Workflow Matters
&lt;/h2&gt;

&lt;p&gt;The traditional Chrome extension development loop is &lt;strong&gt;write → reload → manually check → repeat&lt;/strong&gt;. With WXT + Chrome DevTools MCP, it becomes &lt;strong&gt;write → auto-reload → AI verifies → iterate&lt;/strong&gt; — and the AI agent can do the first and last steps too.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Debugging&lt;/strong&gt; goes from "read console logs, set breakpoints, manually reproduce" to "AI evaluates scripts in the live browser and reports what's happening."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selector maintenance&lt;/strong&gt; goes from "open DevTools, inspect element, copy selector, paste into code" to "AI reads the DOM and updates fallback selectors."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature development&lt;/strong&gt; goes from "code blind, test manually" to "AI writes code, checks DOM state, fixes issues — all in one turn."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This doesn't replace understanding your own extension. But it dramatically shortens the feedback loop, especially for the tedious parts of third-party DOM manipulation.&lt;/p&gt;

</description>
      <category>chromeextension</category>
      <category>ai</category>
      <category>webdev</category>
      <category>cursor</category>
    </item>
    <item>
      <title>BigQuery Global Queries: Join Data Across Regions Without ETL</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Sun, 22 Mar 2026 12:38:06 +0000</pubDate>
      <link>https://forem.com/toyama0919/bigquery-global-queries-join-data-across-regions-without-etl-1ho1</link>
      <guid>https://forem.com/toyama0919/bigquery-global-queries-join-data-across-regions-without-etl-1ho1</guid>
      <description>&lt;p&gt;As of February 2026, Google released &lt;strong&gt;BigQuery Global Queries&lt;/strong&gt; in Preview. It lets you join tables from completely different geographic regions — say, &lt;code&gt;asia-northeast1&lt;/code&gt; (Tokyo) and &lt;code&gt;us-central1&lt;/code&gt; (Iowa) — in a &lt;strong&gt;single SQL statement&lt;/strong&gt;. No ETL, no data movement pipelines, no manual copying.&lt;/p&gt;

&lt;p&gt;This post covers how it actually works under the hood, what it costs, and the gotchas you need to know before using it in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Old Problem
&lt;/h2&gt;

&lt;p&gt;BigQuery historically required all datasets referenced in a single query to live in the same location. If your sales data was in Tokyo and your user master was in the US, you had two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy one dataset to the other region (ETL pipeline, operational overhead).&lt;/li&gt;
&lt;li&gt;Run two separate queries and join the results in application code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Global Queries eliminates this constraint.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works: 4-Stage Execution
&lt;/h2&gt;

&lt;p&gt;When you run a global query, BigQuery orchestrates the execution across regions transparently:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Distributed Execution
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Query Optimizer&lt;/strong&gt; analyzes the query, identifies which tables live in which regions, and assigns the querying region as the &lt;strong&gt;Primary Region&lt;/strong&gt; (the "leader"). Workers in each remote region receive their execution assignments in parallel.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Data Pushdown
&lt;/h3&gt;

&lt;p&gt;This is the most critical stage — and the one that makes global queries economically viable.&lt;/p&gt;

&lt;p&gt;Before any data crosses the network, BigQuery applies three types of pushdown to minimize transfer size:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Predicate Pushdown&lt;/strong&gt;: &lt;code&gt;WHERE&lt;/code&gt; clause filters run &lt;em&gt;in the remote region&lt;/em&gt;, before the data moves. A 100M-row table filtered to 100 rows transfers 100 rows — not 100M.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Projection Pushdown&lt;/strong&gt;: Only the columns named in &lt;code&gt;SELECT&lt;/code&gt; are read from remote storage. BigQuery's columnar storage (Capacitor) makes this efficient.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggregation Pushdown&lt;/strong&gt;: &lt;code&gt;GROUP BY&lt;/code&gt;/&lt;code&gt;SUM&lt;/code&gt;/&lt;code&gt;COUNT&lt;/code&gt; operations run as partial aggregations in the remote region. A billion-row transaction table can be summarized to 365 rows (daily totals) before transfer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Data Transfer
&lt;/h3&gt;

&lt;p&gt;Filtered, minimized results travel over Google's internal network to the Primary Region, where they're stored in &lt;strong&gt;temporary internal tables&lt;/strong&gt; for up to 8 hours. This is where cross-region &lt;strong&gt;egress charges&lt;/strong&gt; are incurred.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Final Join
&lt;/h3&gt;

&lt;p&gt;The Primary Region merges local data with the temporary remote data, as if everything were in one place. The query result returned to the user looks like any normal BigQuery result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Executed from asia-northeast1 (Tokyo)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sales&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sales&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_global_sales&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`project.japan_dataset.sales`&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt;   &lt;span class="c1"&gt;-- local&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="nv"&gt;`project.us_dataset.sales`&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t2&lt;/span&gt;      &lt;span class="c1"&gt;-- remote (auto-transferred)&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-03-01'&lt;/span&gt;               &lt;span class="c1"&gt;-- pushed down to both regions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  IAM Permissions
&lt;/h2&gt;

&lt;p&gt;Global Queries require two layers of setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project-level opt-in (admin task)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Enable execution from the primary region&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;PROJECT&lt;/span&gt; &lt;span class="nv"&gt;`your-project-id`&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;OPTIONS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;`region-asia-northeast1.enable_global_queries_execution`&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Enable data access from the remote region&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;PROJECT&lt;/span&gt; &lt;span class="nv"&gt;`your-project-id`&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;OPTIONS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;`region-us-central1.enable_global_queries_data_access`&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  User-level permissions
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bigquery.jobs.createGlobalQuery&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Required to initiate a global query. Currently only included in &lt;code&gt;roles/bigquery.admin&lt;/code&gt; — create a custom role for regular users.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;roles/bigquery.dataViewer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Required on every dataset being referenced, in every region.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Cost Structure
&lt;/h2&gt;

&lt;p&gt;Global queries have &lt;strong&gt;three billing components&lt;/strong&gt; instead of the usual one:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;th&gt;Approximate Price (2026)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compute&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bytes scanned across all regions&lt;/td&gt;
&lt;td&gt;$6.25 / 1 TB (on-demand)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Egress&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Data transferred from remote to primary region&lt;/td&gt;
&lt;td&gt;~$0.08–$0.12 / 1 GB (intercontinental)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Temporary Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Intermediate data stored for up to 8 hours&lt;/td&gt;
&lt;td&gt;~$0.02/GB-month (prorated)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Cost simulation
&lt;/h3&gt;

&lt;p&gt;Scenario: Query from Tokyo, scanning a 1 TB table in &lt;code&gt;us-central1&lt;/code&gt;, with a &lt;code&gt;WHERE&lt;/code&gt; clause that reduces the data transferred to 1 GB.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compute: 1 TB × $6.25 = &lt;strong&gt;$6.25&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Egress: 1 GB × $0.12 = &lt;strong&gt;$0.12&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total: ~$6.37&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you skip the &lt;code&gt;WHERE&lt;/code&gt; clause and transfer the full 1 TB: egress alone exceeds &lt;strong&gt;$100&lt;/strong&gt;. Pushdown is not optional — it's the entire cost model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dry run before executing
&lt;/h3&gt;

&lt;p&gt;Use the BigQuery Console (it shows estimated bytes scanned before you click Run) or the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bq query &lt;span class="nt"&gt;--dry_run&lt;/span&gt; &lt;span class="nt"&gt;--use_legacy_sql&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="s1"&gt;'SELECT ...'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: As of the current preview, dry runs may not accurately estimate egress (only compute bytes). Budget conservatively.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Key Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Latency
&lt;/h3&gt;

&lt;p&gt;Cross-region queries are &lt;strong&gt;always slower&lt;/strong&gt; than single-region queries. Physical distance adds hundreds of milliseconds of network latency, plus multi-region orchestration overhead. Expect a minimum of &lt;strong&gt;5–10 seconds&lt;/strong&gt; even for modest cross-region joins. Real-time dashboards are not a good fit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Residency
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Primary Region is where remote data lands temporarily&lt;/strong&gt;. If GDPR or local privacy laws prohibit data from Region A leaving Region A, you must run the query &lt;em&gt;from&lt;/em&gt; Region A as the primary — not from a region outside it. VPC Service Controls perimeters are also respected.&lt;/p&gt;




&lt;h2&gt;
  
  
  Current Limitations (Preview, March 2026)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  No Query Cache
&lt;/h3&gt;

&lt;p&gt;Global queries never use the query cache. Since data can change in any remote region at any time, BigQuery always reads fresh data. Every execution incurs full compute and egress costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workaround&lt;/strong&gt;: For frequently-used cross-region joins, materialize results into a local table using &lt;code&gt;CREATE TABLE AS SELECT&lt;/code&gt; and query that instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  No INFORMATION_SCHEMA from Remote Regions
&lt;/h3&gt;

&lt;p&gt;You cannot query &lt;code&gt;INFORMATION_SCHEMA&lt;/code&gt; views from a remote region within a global query. Joining metadata across regions requires first exporting that metadata into regular tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unsupported Table Types
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BigLake Apache Iceberg tables&lt;/strong&gt; in remote regions are not supported as remote sources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partition pseudo-columns&lt;/strong&gt; (&lt;code&gt;_PARTITIONTIME&lt;/code&gt;, &lt;code&gt;_PARTITIONDATE&lt;/code&gt;) may not pushdown correctly (more on this below).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  No Sandbox Support
&lt;/h3&gt;

&lt;p&gt;Billing Account required. The Sandbox (free tier) does not support Global Queries because egress charges can exceed the free quota.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Partition Pseudo-Column Trap
&lt;/h2&gt;

&lt;p&gt;This is the most dangerous limitation in production, and deserves its own section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Background: Pseudo-columns vs. Physical columns
&lt;/h3&gt;

&lt;p&gt;BigQuery offers two partitioning strategies:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Partition Key&lt;/th&gt;
&lt;th&gt;Access&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ingestion-time partitioned&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Arrival timestamp, managed by BigQuery&lt;/td&gt;
&lt;td&gt;Via &lt;code&gt;_PARTITIONTIME&lt;/code&gt; / &lt;code&gt;_PARTITIONDATE&lt;/code&gt; (pseudo-columns)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Column-based partitioned&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An actual column in your table schema (e.g., &lt;code&gt;event_date&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Via the column name directly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pseudo-columns are not part of the formal table schema. They're metadata-level constructs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why pushdown fails for pseudo-columns
&lt;/h3&gt;

&lt;p&gt;When the Query Optimizer sends execution instructions to a remote region, it works from the table's schema definition. Pseudo-columns aren't in that definition, so the optimizer can't reliably communicate partition pruning constraints to the remote worker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worst case&lt;/strong&gt;: A filter like &lt;code&gt;WHERE _PARTITIONDATE = '2026-03-01'&lt;/code&gt; is silently ignored in the remote region. The remote worker scans the entire table across all partitions and begins transferring everything to the primary region. Your query either times out or generates a very large bill.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix: Migrate to column-based partitioning
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create a new table with an explicit physical partition column&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`project.dataset.new_table`&lt;/span&gt;
&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;event_date&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_PARTITIONDATE&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;event_date&lt;/span&gt;  &lt;span class="c1"&gt;-- materialize the pseudo-column&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`project.dataset.old_table`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a physical column, the optimizer sees it in the schema, understands the partition structure, and confidently applies pushdown in the remote region.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workaround B: Aliasing via Views (use with caution)
&lt;/h3&gt;

&lt;p&gt;If migrating the table isn't possible, you can create a view in the remote region that aliases the pseudo-column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- View in us-central1&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="nv"&gt;`project.us_dataset.v_sales`&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;_PARTITIONDATE&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;partition_date_col&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`project.us_dataset.ingestion_time_partitioned_table`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then query the view from the primary region:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`project.us_dataset.v_sales`&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;partition_date_col&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-03-01'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;em&gt;sometimes&lt;/em&gt; works for simple queries, but pushdown is not guaranteed. In complex queries with JOINs or aggregations, the optimizer often loses the connection between the aliased column and the underlying partition structure, falls back to full-scan, and transfers everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Always verify&lt;/strong&gt; that pushdown is working by checking the Query Execution Plan and confirming the remote &lt;code&gt;READ&lt;/code&gt; stage shows filtered row counts — not the full table row count.&lt;/p&gt;




&lt;h2&gt;
  
  
  Operational Best Practices
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No query cache&lt;/td&gt;
&lt;td&gt;Materialize frequent cross-region joins into local intermediate tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need metadata across regions&lt;/td&gt;
&lt;td&gt;Export metadata to regular tables on a schedule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ingestion-time partitioned tables&lt;/td&gt;
&lt;td&gt;Migrate to column-based partitioning before using as remote sources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unclear cost pre-execution&lt;/td&gt;
&lt;td&gt;Use dry run + estimate egress separately; add a buffer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;BigQuery Global Queries is a genuinely useful feature that eliminates an entire category of ETL pipelines. The execution model is well-designed — pushdown at the predicate, projection, and aggregation levels means you're typically only transferring the data you actually need.&lt;/p&gt;

&lt;p&gt;The key things to internalize:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pushdown is the cost model.&lt;/strong&gt; Filter early, select only the columns you need, push aggregations to the remote side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ingestion-time partitioned tables are a liability&lt;/strong&gt; in global queries. Migrate to column-based partitioning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's Preview&lt;/strong&gt; — no query cache, no &lt;code&gt;INFORMATION_SCHEMA&lt;/code&gt; cross-region, no BigLake Iceberg remotes. Design your architecture around these constraints.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Check the &lt;a href="https://cloud.google.com/bigquery/docs/global-queries" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; for the latest changes as this feature moves toward GA.&lt;/p&gt;

</description>
      <category>bigquery</category>
      <category>gcp</category>
      <category>dataengineering</category>
      <category>sql</category>
    </item>
    <item>
      <title>The Cloud is No Longer Virtual: The Harsh Physical Reality of AI Infra in 2026</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Mon, 23 Feb 2026 08:42:02 +0000</pubDate>
      <link>https://forem.com/toyama0919/the-cloud-is-no-longer-virtual-the-harsh-physical-reality-of-ai-infra-in-2026-27ba</link>
      <guid>https://forem.com/toyama0919/the-cloud-is-no-longer-virtual-the-harsh-physical-reality-of-ai-infra-in-2026-27ba</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;The "Virtual" in Cloud is fading. In 2026, AI infrastructure is dominated by three physical constraints: &lt;strong&gt;power grid capacity&lt;/strong&gt;, &lt;strong&gt;tax legislations&lt;/strong&gt;, and &lt;strong&gt;liquid cooling&lt;/strong&gt;. If you are still picking regions based solely on latency, you are overpaying by at least 20%.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Death of the "Sales Tax Holiday"
&lt;/h2&gt;

&lt;p&gt;For a decade, states like Virginia attracted data centers with massive sales tax exemptions. That era ended in February 2026 with &lt;strong&gt;Virginia HB 897&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this matters for your bill:
&lt;/h3&gt;

&lt;p&gt;In the US, "Sales Tax" works differently from Japan's VAT or Europe's VAT. It is a &lt;strong&gt;sunken cost&lt;/strong&gt; with no tax credit for businesses. When a state removes a 6-10% tax exemption on hardware:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An NVIDIA B200 cluster worth $100M suddenly costs &lt;strong&gt;$110M&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;This extra Capex is directly passed to you as higher hourly instance rates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Move:&lt;/strong&gt; We are seeing a "Great Migration" to the &lt;strong&gt;Midwest AI Belt&lt;/strong&gt; (Indiana, Ohio, Iowa), where 20-30 year tax holidays are still guaranteed. &lt;/p&gt;




&lt;h2&gt;
  
  
  2. Why "Power" is the New "Latency"
&lt;/h2&gt;

&lt;p&gt;We used to care about milliseconds. Now, we care about Megawatts. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Virginia Gridlock
&lt;/h3&gt;

&lt;p&gt;In North Virginia (&lt;code&gt;us-east-1&lt;/code&gt;), data centers now consume over &lt;strong&gt;25% of the total state power&lt;/strong&gt;. The grid is saturated. To build new AI capacity, AWS and Google are now forced to become &lt;strong&gt;Energy Producers&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nuclear is the New "Default Gateway"
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SMRs (Small Modular Reactors):&lt;/strong&gt; AWS is deploying SMRs as "Microservices for Energy"—factory-built reactors that can be dropped next to a data center.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direct-to-Plant:&lt;/strong&gt; Microsoft and Azure are restarting decommissioned plants (like Three Mile Island) just to keep their GPUs humming.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. The "Jevons Paradox" of NVIDIA GPUs
&lt;/h2&gt;

&lt;p&gt;People often ask: &lt;em&gt;"Why doesn't NVIDIA make low-power GPUs?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The answer is &lt;strong&gt;Tokens per Watt&lt;/strong&gt;. NVIDIA's Blackwell (B200) consumes a massive &lt;strong&gt;1,200W&lt;/strong&gt;, but it is &lt;strong&gt;25x more efficient&lt;/strong&gt; at generating tokens than the previous generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Thermal Wall
&lt;/h3&gt;

&lt;p&gt;Because one rack now pulls &lt;strong&gt;120kW+&lt;/strong&gt;, traditional air cooling is dead. 2026 is the year of &lt;strong&gt;Liquid Cooling&lt;/strong&gt;. If your DC doesn't have pipes, it can't run the latest AI models. This creates a "Performance Gap" between old regions and new AI-native regions.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The Tokyo Context: Why so expensive?
&lt;/h2&gt;

&lt;p&gt;Many Japanese developers wonder why &lt;code&gt;ap-northeast-1&lt;/code&gt; costs more than &lt;code&gt;us-east-1&lt;/code&gt; despite Japan's "cheaper" cost of living.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Imported Energy:&lt;/strong&gt; Japan's industrial electricity is &lt;strong&gt;2-3x more expensive&lt;/strong&gt; than the US.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Dollar-Denominated Silicon:&lt;/strong&gt; Everything from the GPU to the fuel for the power plant is priced in USD. The weak Yen makes these "imported" cloud resources luxury items.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Humidity:&lt;/strong&gt; Tokyo’s humid summers make PUE (Power Usage Effectiveness) worse than the dry, flat plains of Ohio.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  5. FinOps 2026: Actions for Engineers
&lt;/h2&gt;

&lt;p&gt;"Turning off idle instances" is FinOps 101. To be a Senior Infrastructure Engineer in 2026, you need &lt;strong&gt;Regional Arbitrage&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Move Training to the Midwest:&lt;/strong&gt; Shift non-latency-sensitive training jobs from &lt;code&gt;us-east-1&lt;/code&gt; to &lt;code&gt;us-west-2&lt;/code&gt; (Oregon) or the new Indiana regions to save &lt;strong&gt;10-15%&lt;/strong&gt; on tax and power alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Token-Specific Hardware:&lt;/strong&gt; Evaluate &lt;strong&gt;TPU v7&lt;/strong&gt; (Google Cloud) or &lt;strong&gt;Trainium 2&lt;/strong&gt; (AWS). In 2026, specialized ASICs are often &lt;strong&gt;3x more cost-effective&lt;/strong&gt; than general-purpose GPUs for specific LLM workloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure as Code (IaC) for Regions:&lt;/strong&gt; Don't hardcode regions. Use variables that allow you to follow the "Tax-Free Energy" across the globe.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The cloud is no longer an invisible layer of abstraction. It is a physical plant that breathes energy and exhales heat. The best engineers in 2026 will be those who understand the &lt;strong&gt;physics and economics&lt;/strong&gt; behind the API call.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What are your thoughts?&lt;/strong&gt; Are you planning to migrate your workloads out of Virginia? Let's discuss in the comments!&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>ai</category>
      <category>aws</category>
      <category>finops</category>
    </item>
    <item>
      <title>Track Your Azure OpenAI Costs in Seconds, Not Minutes</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Mon, 26 Jan 2026 12:32:19 +0000</pubDate>
      <link>https://forem.com/toyama0919/track-your-azure-openai-costs-in-seconds-not-minutes-2fnb</link>
      <guid>https://forem.com/toyama0919/track-your-azure-openai-costs-in-seconds-not-minutes-2fnb</guid>
      <description>&lt;p&gt;If you're building AI applications with Azure OpenAI, you know the drill: costs can spiral fast. One experimental feature using o1-preview, a few hundred test runs, and suddenly your bill looks very different from last month.&lt;/p&gt;

&lt;p&gt;The Azure portal shows you the numbers eventually, but when you're iterating quickly on AI features, you need real-time visibility. That's exactly what &lt;code&gt;azurecost&lt;/code&gt; delivers - instant Azure OpenAI cost tracking from your terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Azure OpenAI Cost Challenge
&lt;/h2&gt;

&lt;p&gt;Unlike traditional cloud services with predictable pricing, Azure OpenAI costs vary wildly based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Model choice (GPT-4o-mini vs o1-preview is a 5-15x difference)&lt;/li&gt;
&lt;li&gt;Token usage (both prompt and completion tokens)&lt;/li&gt;
&lt;li&gt;Deployment scaling and throughput&lt;/li&gt;
&lt;li&gt;Testing and development cycles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The questions you need answered daily:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"How much did my o1-preview deployment cost yesterday?"&lt;/li&gt;
&lt;li&gt;"Which resource group is burning through credits?"&lt;/li&gt;
&lt;li&gt;"Did that new feature spike my OpenAI spend?"&lt;/li&gt;
&lt;li&gt;"How does dev environment cost compare to production?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Checking this through the Azure portal means multiple clicks, page loads, and waiting. When you're checking costs multiple times a day during active development, this friction adds up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;azurecost&lt;/code&gt; is a Python CLI tool built specifically for developers who need fast answers about their Azure spending. For Azure OpenAI users, it's the fastest way to track Cognitive Services costs without touching the Azure portal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why It Works for Azure OpenAI
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Instant visibility&lt;/strong&gt;: See your OpenAI costs in 2 seconds, not 2 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daily granularity&lt;/strong&gt;: Catch cost spikes the day they happen, not at month-end&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource-level tracking&lt;/strong&gt;: Monitor individual Azure OpenAI accounts separately&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource group isolation&lt;/strong&gt;: Separate dev, staging, and production costs effortlessly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-dimensional views&lt;/strong&gt;: Break down by service, location, resource group, or resource ID&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation-ready&lt;/strong&gt;: Python API for integrating into your CI/CD or daily reports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-currency&lt;/strong&gt;: Works with any billing currency&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Installation takes one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;azurecost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Log in with Azure CLI (if you haven't already):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check your Azure OpenAI costs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; your-subscription-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;USD&lt;span class="o"&gt;)&lt;/span&gt;                 2025-11    2025-12
&lt;span class="nt"&gt;------------------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;
total                 1247.83    2891.45
Cognitive Services    1247.83    2891.45
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's your Azure OpenAI spend right there - Cognitive Services is the billing category for Azure OpenAI. Notice the spike in December? Now you can investigate what changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Azure OpenAI Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Scenario 1: Daily Cost Monitoring During Development
&lt;/h3&gt;

&lt;p&gt;You're building a new reasoning agent with o1-preview. Check costs every morning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; prod-subscription &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;USD&lt;span class="o"&gt;)&lt;/span&gt;           2025-12-15  2025-12-16  2025-12-17  2025-12-18
&lt;span class="nt"&gt;------------&lt;/span&gt;  &lt;span class="nt"&gt;-----------&lt;/span&gt;  &lt;span class="nt"&gt;-----------&lt;/span&gt;  &lt;span class="nt"&gt;-----------&lt;/span&gt;  &lt;span class="nt"&gt;-----------&lt;/span&gt;
total                45.23        52.18       178.45        51.20
Cognitive Services   45.23        52.18       178.45        51.20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whoa, December 17th spiked to $178. That's the day you started load testing with o1-preview. Now you know exactly when and how much it costs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 2: Environment-Based Cost Breakdown
&lt;/h3&gt;

&lt;p&gt;You have separate resource groups for dev, staging, and production. See costs side by side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; ai-subscription &lt;span class="nt"&gt;-d&lt;/span&gt; ResourceGroup &lt;span class="nt"&gt;-d&lt;/span&gt; ServiceName
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;USD&lt;span class="o"&gt;)&lt;/span&gt;                                        2025-11    2025-12
&lt;span class="nt"&gt;-----------------------------------------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;
total                                        1247.83    2891.45
ai-dev-rg/Cognitive Services                  342.15     456.32
ai-staging-rg/Cognitive Services              198.42     287.89
ai-prod-rg/Cognitive Services                 707.26    2147.24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production jumped from $707 to $2147. Time to optimize those prompts or consider GPT-4o-mini for some use cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 3: Focused Investigation on Production
&lt;/h3&gt;

&lt;p&gt;Something's wrong with production costs. Drill down to just that resource group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; ai-subscription &lt;span class="nt"&gt;-r&lt;/span&gt; ai-prod-rg &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 14
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See two weeks of daily costs for production only. Spot the pattern, correlate with deployments or feature releases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 4: Multi-Region Cost Analysis
&lt;/h3&gt;

&lt;p&gt;Running Azure OpenAI deployments in multiple regions? Group by location:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; global-ai-sub &lt;span class="nt"&gt;-d&lt;/span&gt; Location &lt;span class="nt"&gt;-d&lt;/span&gt; ServiceName
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;USD&lt;span class="o"&gt;)&lt;/span&gt;                                   2025-11    2025-12
&lt;span class="nt"&gt;------------------------------------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;
total                                   1247.83    2891.45
East US/Cognitive Services               823.14    1923.87
West Europe/Cognitive Services           424.69     967.58
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;East US is handling most of the load. Maybe redistribute traffic or consider regional pricing differences.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 5: Resource-Level Cost Analysis
&lt;/h3&gt;

&lt;p&gt;Running multiple Azure OpenAI accounts for different teams or use cases? Track costs at the individual resource level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; ai-subscription &lt;span class="nt"&gt;-d&lt;/span&gt; ResourceId
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;USD&lt;span class="o"&gt;)&lt;/span&gt;                                                                                     2025-12    2026-01
&lt;span class="nt"&gt;--------------------------------------------------------------------------------------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;
total                                                                                     5741.44   16571.60
/resourcegroups/ai/providers/microsoft.cognitiveservices/accounts/chatbot-prod           3401.80   16390.44
/resourcegroups/ai/providers/microsoft.cognitiveservices/accounts/analytics-engine       2194.17     131.82
/resourcegroups/ai/providers/microsoft.cognitiveservices/accounts/internal-tools          145.47      49.34
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows exactly which Azure OpenAI account is consuming credits. Perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost attribution&lt;/strong&gt;: Charge back costs to specific teams or projects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identifying cost anomalies&lt;/strong&gt;: Spot which deployment suddenly increased spend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-tenant environments&lt;/strong&gt;: Track costs per customer or tenant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget allocation&lt;/strong&gt;: Distribute budget based on actual usage patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combine with daily granularity to investigate when a specific resource started costing more:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; ai-subscription &lt;span class="nt"&gt;-d&lt;/span&gt; ResourceId &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why This Matters for Azure OpenAI Users
&lt;/h2&gt;

&lt;p&gt;Building with Azure OpenAI is different from traditional cloud infrastructure. With VMs or databases, costs are fairly predictable. But with modern language models like GPT-4o and o1-preview, costs depend on how users interact with your application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Long conversations = more tokens = higher costs&lt;/li&gt;
&lt;li&gt;Complex reasoning tasks = more input tokens&lt;/li&gt;
&lt;li&gt;Detailed responses = more completion tokens&lt;/li&gt;
&lt;li&gt;Testing and iteration = multiplied costs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;During active development, you need to check costs frequently. Not once a month when the bill arrives, but daily or even multiple times a day.&lt;/p&gt;

&lt;p&gt;The Azure portal workflow kills this feedback loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open browser&lt;/li&gt;
&lt;li&gt;Navigate to Azure portal (wait for load)&lt;/li&gt;
&lt;li&gt;Find the right subscription&lt;/li&gt;
&lt;li&gt;Click through to Cost Management&lt;/li&gt;
&lt;li&gt;Configure time range and filters&lt;/li&gt;
&lt;li&gt;Wait for data to render&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By the time you see the numbers, you've burned 2-3 minutes. When you're doing this multiple times daily, the friction discourages you from checking at all.&lt;/p&gt;

&lt;p&gt;Then you're surprised at month-end when the bill is 3x what you expected.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;azurecost&lt;/code&gt; fixes this. Checking costs becomes as fast as checking git status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; ai-subscription &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two seconds. Real-time feedback. No context switching.&lt;/p&gt;

&lt;p&gt;This speed changes behavior. When checking costs is instant, you actually do it. You catch issues early. You experiment with confidence because you're monitoring the impact.&lt;/p&gt;

&lt;p&gt;The tool emerged from my own need while building AI features. I was spending too much time in the portal doing the same query repeatedly. I wanted something terminal-based that integrated into my development workflow.&lt;/p&gt;

&lt;p&gt;What started as a personal script evolved into a proper tool that my team adopted, then others in the community found useful. The philosophy is simple: do one thing well - show Azure costs fast and clearly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automate Azure OpenAI Cost Monitoring
&lt;/h2&gt;

&lt;p&gt;The Python API lets you integrate cost tracking into your workflows. Send daily Azure OpenAI cost reports to Slack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azurecost&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Azurecost&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="c1"&gt;# Get yesterday's costs
&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Azurecost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;granularity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DAILY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dimensions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ResourceGroup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ServiceName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;subscription_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai-production&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;total_results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ago&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convert_tabulate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Send to Slack
&lt;/span&gt;&lt;span class="n"&gt;webhook_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SLACK_WEBHOOK_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🤖 Azure OpenAI Cost Report (Last 7 Days)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;```
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
```&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webhook_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this as a daily cron job or GitHub Action. Your team gets automatic cost visibility without anyone checking the portal.&lt;/p&gt;

&lt;p&gt;Other use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Budget alerts&lt;/strong&gt;: Trigger warnings when daily costs exceed thresholds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost attribution&lt;/strong&gt;: Track which team or feature is using OpenAI credits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-deployment checks&lt;/strong&gt;: Validate costs before promoting to production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finance automation&lt;/strong&gt;: Generate reports for accounting without manual exports&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuration Tips for AI Workloads
&lt;/h2&gt;

&lt;p&gt;Set environment variables to streamline your daily checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add to your ~/.zshrc or ~/.bashrc&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AZURE_SUBSCRIPTION_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-ai-subscription-id
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AZURE_RESOURCE_GROUP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ai-prod-rg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run &lt;code&gt;azurecost&lt;/code&gt; with no arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create shell aliases for common queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Daily OpenAI costs&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;aicosts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'azurecost -s ai-subscription -g DAILY -a 7'&lt;/span&gt;

&lt;span class="c"&gt;# Production environment only&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;prodcosts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'azurecost -s ai-subscription -r ai-prod-rg -g DAILY -a 7'&lt;/span&gt;

&lt;span class="c"&gt;# Compare all environments&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;envcosts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'azurecost -s ai-subscription -d ResourceGroup'&lt;/span&gt;

&lt;span class="c"&gt;# Resource-level breakdown&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;resourcecosts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'azurecost -s ai-subscription -d ResourceId'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;aicosts&lt;/code&gt; each morning as part of your routine. Takes 2 seconds, keeps you informed. Use &lt;code&gt;resourcecosts&lt;/code&gt; when you need to drill down to individual Azure OpenAI accounts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices for Azure OpenAI Cost Management
&lt;/h2&gt;

&lt;p&gt;Based on using &lt;code&gt;azurecost&lt;/code&gt; with AI workloads, here are patterns that work:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Daily morning check&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; ai-sub &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Catch anomalies before they compound. One day of unexpected costs is manageable. A month is not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Before and after feature releases&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before deployment&lt;/span&gt;
azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; ai-sub &lt;span class="nt"&gt;-r&lt;/span&gt; ai-prod-rg &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 3

&lt;span class="c"&gt;# Deploy new feature&lt;/span&gt;

&lt;span class="c"&gt;# After 24 hours&lt;/span&gt;
azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; ai-sub &lt;span class="nt"&gt;-r&lt;/span&gt; ai-prod-rg &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Measure the cost impact of new features. Make data-driven decisions about o1-preview vs GPT-4o-mini.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Set up automated alerts&lt;/strong&gt;&lt;br&gt;
Use the Python API to send daily reports. Don't rely on remembering to check manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Isolate environments&lt;/strong&gt;&lt;br&gt;
Use separate resource groups for dev, staging, production. Makes cost attribution trivial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Monitor during load testing&lt;/strong&gt;&lt;br&gt;
Run &lt;code&gt;azurecost&lt;/code&gt; before and after load tests. Understand your cost-per-request at scale before going live.&lt;/p&gt;
&lt;h2&gt;
  
  
  Start Tracking Your Azure OpenAI Costs Now
&lt;/h2&gt;

&lt;p&gt;Install the tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;azurecost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check your costs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; your-subscription &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. You now have instant visibility into your Azure OpenAI spending.&lt;/p&gt;

&lt;p&gt;Check the &lt;a href="https://github.com/toyama0919/azurecost" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; for full documentation, API examples, and to report issues or contribute.&lt;/p&gt;

&lt;p&gt;Building AI features is expensive enough. Don't let invisible costs surprise you. Make cost visibility effortless.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Quick Links&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/toyama0919/azurecost" rel="noopener noreferrer"&gt;toyama0919/azurecost&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PyPI: &lt;a href="https://pypi.org/project/azurecost/" rel="noopener noreferrer"&gt;azurecost&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Python 3.8+ required&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building with Azure OpenAI? Drop a comment on how you're managing costs or share your use case. Always looking for feedback and ideas.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>openai</category>
      <category>cli</category>
      <category>python</category>
    </item>
    <item>
      <title>ty: The Blazingly Fast Python Type Checker from Astral (Ruff &amp; uv Creators)</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Wed, 07 Jan 2026 04:44:52 +0000</pubDate>
      <link>https://forem.com/toyama0919/ty-the-blazingly-fast-python-type-checker-from-astral-ruff-uv-creators-5bd</link>
      <guid>https://forem.com/toyama0919/ty-the-blazingly-fast-python-type-checker-from-astral-ruff-uv-creators-5bd</guid>
      <description>&lt;h1&gt;
  
  
  ty: The Blazingly Fast Python Type Checker from Astral
&lt;/h1&gt;

&lt;p&gt;Astral, the company behind popular Python tools like &lt;a href="https://github.com/astral-sh/ruff" rel="noopener noreferrer"&gt;Ruff&lt;/a&gt; and &lt;a href="https://github.com/astral-sh/uv" rel="noopener noreferrer"&gt;uv&lt;/a&gt;, has released a new type checker called &lt;strong&gt;ty&lt;/strong&gt;. Built in Rust, it promises to revolutionize Python type checking with incredible speed and powerful features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;⚡ Blazingly Fast&lt;/strong&gt;: 10-100x faster than mypy and Pyright&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔍 Rich Diagnostics&lt;/strong&gt;: Detailed error messages with multi-file context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🧠 Smart Type Inference&lt;/strong&gt;: Catches bugs even without type annotations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🎯 Advanced Type System&lt;/strong&gt;: Intersection types, advanced narrowing, reachability analysis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🛠️ Language Server&lt;/strong&gt;: Built-in support for IDE integration (VS Code extension available)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Using uv (recommended)&lt;/span&gt;
uv tool &lt;span class="nb"&gt;install &lt;/span&gt;ty@latest

&lt;span class="c"&gt;# Using pip&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;ty
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Basic Usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check a file&lt;/span&gt;
ty check file.py

&lt;span class="c"&gt;# Check directories&lt;/span&gt;
ty check src tests

&lt;span class="c"&gt;# Verbose mode&lt;/span&gt;
ty check src &lt;span class="nt"&gt;--verbose&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Makes ty Special?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Speed That Matters
&lt;/h3&gt;

&lt;p&gt;ty can type-check the entire Home Assistant project in &lt;strong&gt;~2.19 seconds&lt;/strong&gt;, compared to mypy's &lt;strong&gt;45.66 seconds&lt;/strong&gt;. That's over &lt;strong&gt;20x faster&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;For large codebases, this speed difference transforms the developer experience:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No more waiting for CI to finish&lt;/li&gt;
&lt;li&gt;Instant feedback in your IDE&lt;/li&gt;
&lt;li&gt;Practical to run on every save&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Powerful Type Inference
&lt;/h3&gt;

&lt;p&gt;Here's the magic: &lt;strong&gt;ty can find bugs even without type annotations&lt;/strong&gt;. Let's look at some examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Bug Detection
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Example 1: Type Mismatches
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Oops! Assigning int to str
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ty's output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[invalid-assignment]: Object of type `int` is not assignable to `str`
 --&amp;gt; example.py:4:9
  |
4 | result: str = add(1, 2)
  |         ---   ^^^^^^^^^ Incompatible value of type `int`
  |         |
  |         Declared type
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example 2: Missing Attributes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;  &lt;span class="c1"&gt;# email was never defined!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ty catches this:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[unresolved-attribute]: Object of type `Self@get_email` has no attribute `email`
 --&amp;gt; example.py:6:16
  |
6 |         return self.email
  |                ^^^^^^^^^^
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works even for complex cases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DatabaseClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;connection_string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Typo: should be .execute(), not .exec()
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ty will warn you that &lt;code&gt;.exec()&lt;/code&gt; doesn't exist on the connection object!&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 3: Incorrect Function Arguments
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;four&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# String in int list!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ty's error:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[invalid-argument-type]: Argument to bound method `append` is incorrect
 --&amp;gt; example.py:2:16
  |
2 | numbers.append("four")
  |                ^^^^^^ Expected `int`, found `Literal["four"]`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example 4: Null Safety
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# What if value is None?
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ty spots the issue:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[invalid-argument-type]: Expected `Sized`, found `str | None`
 --&amp;gt; example.py:2:16
  |
2 |     return len(value)
  |                ^^^^^
  |
info: Element `None` of this union is not assignable to `Sized`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example 5: Implicit None Returns
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="c1"&gt;# Missing return statement - implicit None!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ty catches this too:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[invalid-return-type]: Function can implicitly return `None`,
which is not assignable to return type `str`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;ty uses &lt;code&gt;pyproject.toml&lt;/code&gt; for configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tool.ty]&lt;/span&gt;
&lt;span class="nn"&gt;[tool.ty.environment]&lt;/span&gt;
&lt;span class="py"&gt;python-version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"3.10"&lt;/span&gt;

&lt;span class="nn"&gt;[tool.ty.src]&lt;/span&gt;
&lt;span class="py"&gt;include&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"src/**/*.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tests/**/*.py"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;exclude&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;".tox/**"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"build/**"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"dist/**"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c"&gt;# Per-directory overrides&lt;/span&gt;
&lt;span class="nn"&gt;[[tool.ty.overrides]]&lt;/span&gt;
&lt;span class="py"&gt;include&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"tests/**/*.py"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nn"&gt;[tool.ty.overrides.rules]&lt;/span&gt;
&lt;span class="py"&gt;invalid-assignment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ignore"&lt;/span&gt;  &lt;span class="c"&gt;# More lenient for tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CI/CD Integration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub Actions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Type Check&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;typecheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.10'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install ty&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run type checker&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ty check src tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  tox Integration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[testenv:ty]&lt;/span&gt;
&lt;span class="py"&gt;deps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="err"&gt;ty&lt;/span&gt;
    &lt;span class="err"&gt;-e&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;
&lt;span class="py"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="err"&gt;ty&lt;/span&gt; &lt;span class="err"&gt;check&lt;/span&gt; &lt;span class="err"&gt;src&lt;/span&gt; &lt;span class="err"&gt;tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  pre-commit Hook
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ty&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ty type checker&lt;/span&gt;
        &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ty check src tests&lt;/span&gt;
        &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;system&lt;/span&gt;
        &lt;span class="na"&gt;pass_filenames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Editor Integration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  VS Code
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Install the "ty" extension from VS Code Marketplace&lt;/li&gt;
&lt;li&gt;Enjoy automatic:

&lt;ul&gt;
&lt;li&gt;Type checking as you type&lt;/li&gt;
&lt;li&gt;Inlay hints for inferred types&lt;/li&gt;
&lt;li&gt;Code actions and quick fixes&lt;/li&gt;
&lt;li&gt;Jump to definition with type awareness&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  ty vs mypy vs Pyright
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;ty&lt;/th&gt;
&lt;th&gt;mypy&lt;/th&gt;
&lt;th&gt;Pyright&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Speed&lt;/td&gt;
&lt;td&gt;⚡⚡⚡ Ultra-fast&lt;/td&gt;
&lt;td&gt;🐢 Moderate&lt;/td&gt;
&lt;td&gt;🚀 Fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error Messages&lt;/td&gt;
&lt;td&gt;📝 Very detailed&lt;/td&gt;
&lt;td&gt;📄 Standard&lt;/td&gt;
&lt;td&gt;📝 Detailed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type Inference&lt;/td&gt;
&lt;td&gt;🧠 Powerful&lt;/td&gt;
&lt;td&gt;📚 Standard&lt;/td&gt;
&lt;td&gt;🧠 Powerful&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maturity&lt;/td&gt;
&lt;td&gt;🆕 Beta&lt;/td&gt;
&lt;td&gt;✅ Stable&lt;/td&gt;
&lt;td&gt;✅ Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diagnostics&lt;/td&gt;
&lt;td&gt;🔍 Multi-file context&lt;/td&gt;
&lt;td&gt;📊 Single-file&lt;/td&gt;
&lt;td&gt;🔍 Cross-file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language Server&lt;/td&gt;
&lt;td&gt;✅ Built-in&lt;/td&gt;
&lt;td&gt;⚠️ Via dmypy&lt;/td&gt;
&lt;td&gt;✅ Built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Current Limitations
&lt;/h2&gt;

&lt;p&gt;As of v0.0.9 (Beta):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Limited rule set&lt;/strong&gt;: Main rules implemented, more coming in 2026&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No strict mode yet&lt;/strong&gt;: Won't complain about missing type annotations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Beta status&lt;/strong&gt;: API may change before stable release&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, even in beta, ty is already catching real bugs and providing value in production codebases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with ty
&lt;/h2&gt;

&lt;p&gt;Here's a practical workflow for adopting ty in your project:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Install and Run
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv tool &lt;span class="nb"&gt;install &lt;/span&gt;ty@latest
ty check src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Review Results
&lt;/h3&gt;

&lt;p&gt;ty will show you actual bugs in your code, even without type annotations!&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Add to CI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add to your test script&lt;/span&gt;
tox &lt;span class="nt"&gt;-e&lt;/span&gt; ty

&lt;span class="c"&gt;# Or run directly&lt;/span&gt;
ty check src tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Gradual Type Annotation
&lt;/h3&gt;

&lt;p&gt;As you maintain your code, gradually add type annotations where it makes sense:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# After
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With annotations, ty becomes even more powerful!&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Example: FastAPI Application
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;connect_database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
        &lt;span class="c1"&gt;# Bug: FastAPI expects HTTPException, but we're returning None!
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/users/{user_id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DB_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Bug: user could be None, but we're accessing .dict()
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ty will catch both bugs:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;get_user&lt;/code&gt; can return &lt;code&gt;None&lt;/code&gt;, but FastAPI expects an exception or a valid user&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user.dict()&lt;/code&gt; could fail if user is &lt;code&gt;None&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Performance Tips
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use file exclusion&lt;/strong&gt;: Skip generated files and vendor directories&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable caching&lt;/strong&gt;: ty caches results for unchanged files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel processing&lt;/strong&gt;: ty automatically uses multiple cores&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incremental mode&lt;/strong&gt;: In your editor, only changed files are re-checked&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Roadmap
&lt;/h2&gt;

&lt;p&gt;According to Astral, ty is targeting a stable 1.0 release in 2026, with plans for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ More comprehensive rule coverage&lt;/li&gt;
&lt;li&gt;✅ Strict mode (enforce type annotations)&lt;/li&gt;
&lt;li&gt;✅ Better integration with popular frameworks&lt;/li&gt;
&lt;li&gt;✅ Plugin system for custom rules&lt;/li&gt;
&lt;li&gt;✅ Performance optimizations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;ty represents the next evolution in Python type checking. Its combination of blazing speed, powerful inference, and excellent diagnostics makes it a compelling choice for Python developers.&lt;/p&gt;

&lt;p&gt;While still in beta, ty is already proving valuable for catching real bugs and improving code quality. If you're using mypy or Pyright and frustrated by slow check times, ty is worth trying.&lt;/p&gt;

&lt;p&gt;The Astral team has an excellent track record with Ruff and uv, and ty looks set to be another game-changer in the Python tooling ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🌐 Official Website: &lt;a href="https://astral.sh/ty" rel="noopener noreferrer"&gt;astral.sh/ty&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 Documentation: &lt;a href="https://docs.astral.sh/ty/" rel="noopener noreferrer"&gt;docs.astral.sh/ty&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 GitHub: &lt;a href="https://github.com/astral-sh/ty" rel="noopener noreferrer"&gt;github.com/astral-sh/ty&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 Discord: &lt;a href="https://astral.sh/discord" rel="noopener noreferrer"&gt;Astral Discord&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Have you tried ty yet? What's your experience with Python type checkers? Let me know in the comments! 👇&lt;/p&gt;

</description>
      <category>python</category>
      <category>typechecker</category>
      <category>tooling</category>
      <category>performance</category>
    </item>
    <item>
      <title>Introducing azurecost: Azure Cost Management Made Simple</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Tue, 16 Dec 2025 12:35:15 +0000</pubDate>
      <link>https://forem.com/toyama0919/introducing-azurecost-azure-cost-management-made-simple-1o3k</link>
      <guid>https://forem.com/toyama0919/introducing-azurecost-azure-cost-management-made-simple-1o3k</guid>
      <description>&lt;p&gt;If you've ever struggled to understand your Azure costs through the portal's cluttered interface, you're not alone. That's why I created &lt;code&gt;azurecost&lt;/code&gt; - a straightforward command-line tool that gives you instant visibility into your Azure spending.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Azure's Cost Management portal is powerful, but sometimes you just want quick answers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"How much did I spend last month?"&lt;/li&gt;
&lt;li&gt;"Which services are costing me the most?"&lt;/li&gt;
&lt;li&gt;"What's the cost breakdown by resource group?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Navigating through the Azure portal for these simple questions feels like overkill. You need something faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;azurecost&lt;/code&gt; is a Python CLI tool that brings Azure cost data directly to your terminal. No clicking through menus, no waiting for the portal to load - just type a command and get your answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simple by default&lt;/strong&gt;: Just run &lt;code&gt;azurecost&lt;/code&gt; and see your monthly costs by service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible dimensions&lt;/strong&gt;: Group costs by resource group, service name, location, or any combination&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple time ranges&lt;/strong&gt;: View daily or monthly costs for any time period&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-currency detection&lt;/strong&gt;: Displays costs in your subscription's billing currency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python API&lt;/strong&gt;: Use it programmatically in your scripts and automation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Installation is one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;azurecost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure you're logged in with Azure CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; my-subscription
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;JPY&lt;span class="o"&gt;)&lt;/span&gt;                 2023-08    2023-09
&lt;span class="nt"&gt;------------------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;
total                  492.77      80.28
Cognitive Services     492.77      80.28
Bandwidth                0          0
Storage                  0          0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean, readable, instant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Multi-dimensional Analysis
&lt;/h3&gt;

&lt;p&gt;Want to see costs broken down by both resource group and service? Easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; my-subscription &lt;span class="nt"&gt;-d&lt;/span&gt; ResourceGroup &lt;span class="nt"&gt;-d&lt;/span&gt; ServiceName
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Daily Monitoring
&lt;/h3&gt;

&lt;p&gt;Tracking costs during a migration or deployment? Switch to daily granularity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; my-subscription &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows you the last 7 days of costs, broken down by day.&lt;/p&gt;

&lt;h3&gt;
  
  
  Focused Investigation
&lt;/h3&gt;

&lt;p&gt;Need to check costs for a specific resource group?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; my-subscription &lt;span class="nt"&gt;-r&lt;/span&gt; production-rg &lt;span class="nt"&gt;-a&lt;/span&gt; 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shows 3 months of costs for just that resource group.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built This
&lt;/h2&gt;

&lt;p&gt;The idea for &lt;code&gt;azurecost&lt;/code&gt; came from a recurring frustration in my daily work as a cloud engineer. Every morning, I wanted to do a quick cost check - nothing fancy, just a sanity check to make sure nothing was unexpectedly spiking. But this simple task meant:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Opening a browser&lt;/li&gt;
&lt;li&gt;Navigating to the Azure portal (and waiting for it to load)&lt;/li&gt;
&lt;li&gt;Finding the right subscription&lt;/li&gt;
&lt;li&gt;Clicking through to Cost Management&lt;/li&gt;
&lt;li&gt;Waiting for the cost analysis page to render&lt;/li&gt;
&lt;li&gt;Configuring the time range and dimensions I wanted&lt;/li&gt;
&lt;li&gt;Waiting again for the data to load&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By the time I got my answer, 2-3 minutes had passed. Multiply that by every time someone on the team needed to check costs, and we're talking about hours of wasted time per week.&lt;/p&gt;

&lt;p&gt;I realized I was doing the same exact query every time. I didn't need the portal's full power - I needed something as simple as running &lt;code&gt;git status&lt;/code&gt; or &lt;code&gt;docker ps&lt;/code&gt;. Something that gave me the information I needed in 2 seconds, not 2 minutes.&lt;/p&gt;

&lt;p&gt;Then there were the times when I was SSH'd into a server or working in a restricted environment where opening a browser wasn't convenient. Or when I wanted to pipe cost data into a script for automated reporting. The portal simply wasn't designed for these workflows.&lt;/p&gt;

&lt;p&gt;So I built &lt;code&gt;azurecost&lt;/code&gt;. The goal was simple: make checking Azure costs feel as natural as any other command-line operation. No ceremony, no waiting, no clicking.&lt;/p&gt;

&lt;p&gt;What started as a personal productivity hack turned into something my team adopted, then colleagues at other companies started using it, and eventually it made sense to release it as an open-source tool.&lt;/p&gt;

&lt;p&gt;The philosophy behind &lt;code&gt;azurecost&lt;/code&gt; is that most of the time, you don't need 100 different options and visualizations. You need a fast, reliable answer to a simple question. The tool does one thing well: show you your Azure costs quickly and clearly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using It in Your Scripts
&lt;/h2&gt;

&lt;p&gt;The Python API makes it easy to integrate cost monitoring into your automation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azurecost&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Azurecost&lt;/span&gt;

&lt;span class="n"&gt;core&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Azurecost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;granularity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DAILY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dimensions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ServiceName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ResourceGroup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;subscription_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-subscription&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;total_results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ago&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convert_tabulate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Daily cost reports sent to Slack&lt;/li&gt;
&lt;li&gt;Budget alerts in your CI/CD pipeline&lt;/li&gt;
&lt;li&gt;Cost attribution in team dashboards&lt;/li&gt;
&lt;li&gt;Custom reporting for finance teams&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuration Tips
&lt;/h2&gt;

&lt;p&gt;Set environment variables to avoid typing the same options repeatedly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AZURE_SUBSCRIPTION_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AZURE_RESOURCE_GROUP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production-rg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now just run &lt;code&gt;azurecost&lt;/code&gt; with no arguments. It'll use your configured subscription and resource group automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The tool is actively maintained and open to contributions. Some ideas I'm exploring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cost anomaly detection&lt;/li&gt;
&lt;li&gt;Budget tracking and alerts&lt;/li&gt;
&lt;li&gt;Cost forecasting based on historical trends&lt;/li&gt;
&lt;li&gt;Integration with other cloud providers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;The tool is open source and available on PyPI. Give it a try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;azurecost
azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; your-subscription
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out the &lt;a href="https://github.com/toyama0919/azurecost" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; for full documentation, examples, and to report issues or contribute.&lt;/p&gt;

&lt;p&gt;Azure cost management doesn't have to be complicated. Sometimes the best tool is the simplest one.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔗 GitHub: &lt;a href="https://github.com/toyama0919/azurecost" rel="noopener noreferrer"&gt;toyama0919/azurecost&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 PyPI: &lt;a href="https://pypi.org/project/azurecost/" rel="noopener noreferrer"&gt;azurecost&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐍 Python: 3.8+&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have questions or feedback? Open an issue on GitHub or drop a comment below!&lt;/p&gt;

</description>
      <category>azure</category>
      <category>cli</category>
      <category>python</category>
      <category>devops</category>
    </item>
    <item>
      <title>Auto-generate Pull Requests with Claude API and Shell Function</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Mon, 15 Dec 2025 12:01:39 +0000</pubDate>
      <link>https://forem.com/toyama0919/auto-generate-pull-requests-with-claude-api-and-shell-function-fp2</link>
      <guid>https://forem.com/toyama0919/auto-generate-pull-requests-with-claude-api-and-shell-function-fp2</guid>
      <description>&lt;h2&gt;
  
  
  Created git-pr-auto
&lt;/h2&gt;

&lt;p&gt;I built a shell function that sends git diff to Claude to auto-generate Pull Request titles and descriptions. It includes practical features like automatic GitHub account switching and remote branch checking.&lt;/p&gt;

&lt;p&gt;Key features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude automatically generates PR title and body from git diff&lt;/li&gt;
&lt;li&gt;Auto-detects master and main branches&lt;/li&gt;
&lt;li&gt;Checks remote branch existence before proceeding&lt;/li&gt;
&lt;li&gt;Automatic GitHub account switching (personal/work)&lt;/li&gt;
&lt;li&gt;Language switching based on account type&lt;/li&gt;
&lt;li&gt;Confirmation step before creating PR&lt;/li&gt;
&lt;li&gt;Automatically opens PR in browser after creation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why I Built This
&lt;/h2&gt;

&lt;p&gt;Writing Pull Requests was tedious. I faced several challenges:&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Creation Takes Too Much Time
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Recalling changes to write a title&lt;/li&gt;
&lt;li&gt;Organizing and describing changes&lt;/li&gt;
&lt;li&gt;Formatting the description&lt;/li&gt;
&lt;li&gt;These tasks took 5-10 minutes every time&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Switching Between Multiple Accounts is Annoying
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Using separate GitHub accounts for work and personal projects&lt;/li&gt;
&lt;li&gt;Need to configure git config for each repository&lt;/li&gt;
&lt;li&gt;GitHub CLI authentication needs switching too&lt;/li&gt;
&lt;li&gt;Wrong settings lead to PRs from the wrong account&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Inconsistent PR Quality
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Descriptions become sloppy when tired&lt;/li&gt;
&lt;li&gt;Information gets insufficient when rushed&lt;/li&gt;
&lt;li&gt;Sometimes fail to accurately convey changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;git-pr-auto solves these issues. Claude API analyzes the diff and generates appropriate titles and descriptions. Automatic account switching prevents configuration mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You need the following setup to use this function.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;gh

&lt;span class="c"&gt;# Authentication (personal)&lt;/span&gt;
gh auth login

&lt;span class="c"&gt;# Authentication (work)&lt;/span&gt;
&lt;span class="nv"&gt;GH_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;github.example.com gh auth login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Claude CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# For macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;anthropic/claude/claude

&lt;span class="c"&gt;# Authentication&lt;/span&gt;
claude auth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the &lt;a href="https://docs.anthropic.com/en/docs/build-with-claude/claude-cli" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; for Claude CLI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment Variables
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add to .zshrc or .bashrc&lt;/span&gt;

&lt;span class="c"&gt;# Pattern for personal account repositories (regex)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_PERSONAL_PATTERN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"github.com/(your-username|your-org)"&lt;/span&gt;

&lt;span class="c"&gt;# Work account settings&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_WORK_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"work.user@company.com"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_WORK_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"github.example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;Here's the complete implementation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git-pr-auto&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;# Auto-configure account&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; git-auth-setup&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    return &lt;/span&gt;1
  &lt;span class="k"&gt;fi&lt;/span&gt;

  &lt;span class="c"&gt;# Check GitHub CLI authentication&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; gh auth status &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: GitHub CLI is not authenticated"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GH_HOST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Please run: GH_HOST=&lt;/span&gt;&lt;span class="nv"&gt;$GH_HOST&lt;/span&gt;&lt;span class="s2"&gt; gh auth login"&lt;/span&gt;
    &lt;span class="k"&gt;else
      &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Please run: gh auth login"&lt;/span&gt;
    &lt;span class="k"&gt;fi
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Current directory: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Remote URL: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git remote get-url origin 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;1
  &lt;span class="k"&gt;fi&lt;/span&gt;

  &lt;span class="c"&gt;# Get current branch&lt;/span&gt;
  &lt;span class="nv"&gt;CURRENT_BRANCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--abbrev-ref&lt;/span&gt; HEAD&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="c"&gt;# Detect master or main branch&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;git branch &lt;span class="nt"&gt;-a&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"^&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s2"&gt;*master&lt;/span&gt;&lt;span class="nv"&gt;$\&lt;/span&gt;&lt;span class="s2"&gt;|^&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\*\s&lt;/span&gt;&lt;span class="s2"&gt;*master&lt;/span&gt;&lt;span class="nv"&gt;$\&lt;/span&gt;&lt;span class="s2"&gt;|remotes/origin/master$"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;BASE_BRANCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;master
  &lt;span class="k"&gt;elif &lt;/span&gt;git branch &lt;span class="nt"&gt;-a&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"^&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s2"&gt;*main&lt;/span&gt;&lt;span class="nv"&gt;$\&lt;/span&gt;&lt;span class="s2"&gt;|^&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\*\s&lt;/span&gt;&lt;span class="s2"&gt;*main&lt;/span&gt;&lt;span class="nv"&gt;$\&lt;/span&gt;&lt;span class="s2"&gt;|remotes/origin/main$"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;BASE_BRANCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: Neither master nor main branch found"&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;1
  &lt;span class="k"&gt;fi&lt;/span&gt;

  &lt;span class="c"&gt;# Error if current branch is base branch&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CURRENT_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: Cannot create PR from &lt;/span&gt;&lt;span class="nv"&gt;$BASE_BRANCH&lt;/span&gt;&lt;span class="s2"&gt; branch"&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;1
  &lt;span class="k"&gt;fi&lt;/span&gt;

  &lt;span class="c"&gt;# Determine whether to use local or remote branch&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--verify&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;COMPARE_BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;elif &lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--verify&lt;/span&gt; &lt;span class="s2"&gt;"origin/&lt;/span&gt;&lt;span class="nv"&gt;$BASE_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;COMPARE_BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"origin/&lt;/span&gt;&lt;span class="nv"&gt;$BASE_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: Cannot find &lt;/span&gt;&lt;span class="nv"&gt;$BASE_BRANCH&lt;/span&gt;&lt;span class="s2"&gt; branch locally or remotely"&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;1
  &lt;span class="k"&gt;fi

  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"📊 Checking diff between &lt;/span&gt;&lt;span class="nv"&gt;$CURRENT_BRANCH&lt;/span&gt;&lt;span class="s2"&gt; and &lt;/span&gt;&lt;span class="nv"&gt;$COMPARE_BASE&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"----------------------------------------"&lt;/span&gt;

  &lt;span class="c"&gt;# Get diff&lt;/span&gt;
  &lt;span class="nv"&gt;DIFF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git diff &lt;span class="nv"&gt;$COMPARE_BASE&lt;/span&gt;..HEAD&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DIFF&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: No differences found between &lt;/span&gt;&lt;span class="nv"&gt;$CURRENT_BRANCH&lt;/span&gt;&lt;span class="s2"&gt; and &lt;/span&gt;&lt;span class="nv"&gt;$COMPARE_BASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;1
  &lt;span class="k"&gt;fi&lt;/span&gt;

  &lt;span class="c"&gt;# Show diff summary&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Files changed:"&lt;/span&gt;
  &lt;span class="nv"&gt;STAT_OUTPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git diff &lt;span class="nt"&gt;--stat&lt;/span&gt; &lt;span class="nv"&gt;$COMPARE_BASE&lt;/span&gt;..HEAD&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STAT_OUTPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: No file changes detected"&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;1
  &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STAT_OUTPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"----------------------------------------"&lt;/span&gt;

  &lt;span class="c"&gt;# Check remote branch existence&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; git ls-remote &lt;span class="nt"&gt;--exit-code&lt;/span&gt; &lt;span class="nt"&gt;--heads&lt;/span&gt; origin &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CURRENT_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"❌ Error: Remote branch 'origin/&lt;/span&gt;&lt;span class="nv"&gt;$CURRENT_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;' does not exist"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Please push your branch to remote first:"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  git push -u origin &lt;/span&gt;&lt;span class="nv"&gt;$CURRENT_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;1
  &lt;span class="k"&gt;fi&lt;/span&gt;

  &lt;span class="c"&gt;# Generate PR content with claude command&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🤖 Analyzing diff with Claude..."&lt;/span&gt;

  &lt;span class="c"&gt;# Change Body language based on account type&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_ACCOUNT_TYPE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"personal"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;BODY_LANG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"English"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nv"&gt;BODY_LANG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Japanese"&lt;/span&gt;
  &lt;span class="k"&gt;fi

  &lt;/span&gt;&lt;span class="nv"&gt;PR_CONTENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;claude &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Generate a Pull Request title and description from the following git diff.

Please output in the following format only (do not output any other text):

TITLE: [Concise title (English, within 50 characters)]

BODY:
[Description of changes (in &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BODY_LANG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; in detail)]
- Change 1
- Change 2
...

Below is the git diff:
&lt;/span&gt;&lt;span class="nv"&gt;$DIFF&lt;/span&gt;&lt;span class="s2"&gt;
"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="c"&gt;# Debug: Check Claude output&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PR_CONTENT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: Claude returned empty response"&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;1
  &lt;span class="k"&gt;fi&lt;/span&gt;

  &lt;span class="c"&gt;# Extract title and body&lt;/span&gt;
  &lt;span class="nv"&gt;PR_TITLE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PR_CONTENT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"^TITLE:"&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1 | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/^TITLE:[[:space:]]*//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;PR_BODY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PR_CONTENT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'/^BODY:/,$ p'&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; +2&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="c"&gt;# Error if title is empty&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PR_TITLE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: Failed to extract PR title from Claude response"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Claude output:"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PR_CONTENT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;1
  &lt;span class="k"&gt;fi

  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"----------------------------------------"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"📝 Generated PR Content:"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Title: &lt;/span&gt;&lt;span class="nv"&gt;$PR_TITLE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Body:"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PR_BODY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"----------------------------------------"&lt;/span&gt;

  &lt;span class="c"&gt;# Create PR&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
  &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="s2"&gt;"?Create PR? (y/N): "&lt;/span&gt; CONFIRM
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONFIRM&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"y"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONFIRM&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Y"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🚀 Creating PR..."&lt;/span&gt;
    &lt;span class="nv"&gt;PR_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gh &lt;span class="nb"&gt;pr &lt;/span&gt;create &lt;span class="nt"&gt;--base&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PR_TITLE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PR_BODY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'https://github.com/[^[:space:]]*'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PR_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
      &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✅ PR created successfully!"&lt;/span&gt;
      &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Opening PR in browser..."&lt;/span&gt;
      open &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PR_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else
      &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
      &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✅ PR created successfully!"&lt;/span&gt;
      gh &lt;span class="nb"&gt;pr &lt;/span&gt;view &lt;span class="nt"&gt;--web&lt;/span&gt;
    &lt;span class="k"&gt;fi
  else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"PR creation cancelled"&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Points
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Remote Branch Check
&lt;/h4&gt;

&lt;p&gt;Before calling Claude API, it checks if the remote branch exists. This prevents wasting API costs and time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; git ls-remote &lt;span class="nt"&gt;--exit-code&lt;/span&gt; &lt;span class="nt"&gt;--heads&lt;/span&gt; origin &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CURRENT_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"❌ Error: Remote branch 'origin/&lt;/span&gt;&lt;span class="nv"&gt;$CURRENT_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;' does not exist"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Please push your branch to remote first:"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  git push -u origin &lt;/span&gt;&lt;span class="nv"&gt;$CURRENT_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Language Switching Based on Account Type
&lt;/h4&gt;

&lt;p&gt;Generates PR body in English for personal accounts and Japanese for work accounts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_ACCOUNT_TYPE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"personal"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;BODY_LANG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"English"&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nv"&gt;BODY_LANG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Japanese"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Prompt for Claude
&lt;/h4&gt;

&lt;p&gt;Specifying a clear format for Claude ensures parseable output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PR_CONTENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;claude &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Generate a Pull Request title and description from the following git diff.

Please output in the following format only (do not output any other text):

TITLE: [Concise title (English, within 50 characters)]

BODY:
[Description of changes (in &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BODY_LANG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; in detail)]
- Change 1
- Change 2
...

Below is the git diff:
&lt;/span&gt;&lt;span class="nv"&gt;$DIFF&lt;/span&gt;&lt;span class="s2"&gt;
"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Commit changes on feature branch&lt;/span&gt;
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add new feature"&lt;/span&gt;

&lt;span class="c"&gt;# Push to remote&lt;/span&gt;
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin feature-branch

&lt;span class="c"&gt;# Auto-create PR&lt;/span&gt;
git-pr-auto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The execution flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Automatic account configuration&lt;/li&gt;
&lt;li&gt;GitHub CLI authentication check&lt;/li&gt;
&lt;li&gt;Base branch detection&lt;/li&gt;
&lt;li&gt;Diff verification and display&lt;/li&gt;
&lt;li&gt;Remote branch check&lt;/li&gt;
&lt;li&gt;PR content generation with Claude API&lt;/li&gt;
&lt;li&gt;Review generated content&lt;/li&gt;
&lt;li&gt;Confirm PR creation&lt;/li&gt;
&lt;li&gt;Create PR and open in browser&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Example Execution
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git-pr-auto

📊 Checking diff between feature-branch and main...
&lt;span class="nt"&gt;----------------------------------------&lt;/span&gt;
Files changed:
 functions/git.sh | 12 ++++++++++--
 1 file changed, 10 insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;, 2 deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nt"&gt;----------------------------------------&lt;/span&gt;

🤖 Analyzing diff with Claude...
&lt;span class="nt"&gt;----------------------------------------&lt;/span&gt;
📝 Generated PR Content:
Title: Add remote branch check before PR creation

Body:
Added remote branch existence check &lt;span class="k"&gt;for &lt;/span&gt;more robust PR creation flow.

Changes:
- Check remote branch existence before Claude API call
- Display appropriate error message and push &lt;span class="nb"&gt;command &lt;/span&gt;when branch doesn&lt;span class="s1"&gt;'t exist
- Reduce API cost and time waste
----------------------------------------

Create PR? (y/N): y
🚀 Creating PR...

✅ PR created successfully!
Opening PR in browser...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automatic GitHub Account Switching
&lt;/h2&gt;

&lt;p&gt;This function calls another function called &lt;code&gt;git-auth-setup&lt;/code&gt; to automatically configure accounts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git-auth-setup&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;# Get remote URL&lt;/span&gt;
  &lt;span class="nv"&gt;REMOTE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git remote get-url origin 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REMOTE_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: Not a git repository or no remote configured"&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;1
  &lt;span class="k"&gt;fi&lt;/span&gt;

  &lt;span class="c"&gt;# Determine if personal or work account&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REMOTE_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_PERSONAL_PATTERN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# Personal account settings&lt;/span&gt;
    &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_ACCOUNT_TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"personal"&lt;/span&gt;
    &lt;span class="nb"&gt;unset &lt;/span&gt;GH_HOST
    git config user.name &lt;span class="s2"&gt;"Your Name"&lt;/span&gt;
    git config user.email &lt;span class="s2"&gt;"personal@example.com"&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c"&gt;# Work account settings&lt;/span&gt;
    &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_ACCOUNT_TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"work"&lt;/span&gt;
    &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GH_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_WORK_HOST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    git config user.name &lt;span class="s2"&gt;"Your Work Name"&lt;/span&gt;
    git config user.email &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_WORK_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This mechanism automatically switches to the appropriate account settings based on the repository URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Claude CLI Not Installed
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;command not found: claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install Claude CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;anthropic/claude/claude
claude auth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  GitHub CLI Not Authenticated
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: GitHub CLI is not authenticated
Please run: gh auth login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Authenticate with the displayed command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Personal account&lt;/span&gt;
gh auth login

&lt;span class="c"&gt;# Work account&lt;/span&gt;
&lt;span class="nv"&gt;GH_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;github.example.com gh auth login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Remote Branch Doesn't Exist
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Error: Remote branch 'origin/feature-branch' does not exist
Please push your branch to remote first:
  git push -u origin feature-branch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Push with the displayed command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin feature-branch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Empty Claude Response
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Claude returned empty response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Possible causes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude API authentication expired → Re-authenticate with &lt;code&gt;claude auth&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Diff is too large → Split commits to make them smaller&lt;/li&gt;
&lt;li&gt;Network issues → Check connection&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Can't Extract PR Title
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Failed to extract PR title from Claude response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This occurs when Claude's output format differs from expectations. Check the debug output and adjust the prompt if needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Improvements
&lt;/h2&gt;

&lt;p&gt;While the current implementation is practical, there's room for improvement:&lt;/p&gt;

&lt;h3&gt;
  
  
  Draft PR Support
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh &lt;span class="nb"&gt;pr &lt;/span&gt;create &lt;span class="nt"&gt;--draft&lt;/span&gt; &lt;span class="nt"&gt;--base&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PR_TITLE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PR_BODY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add an option to create as a draft PR.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto-assign Reviewers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh &lt;span class="nb"&gt;pr &lt;/span&gt;create &lt;span class="nt"&gt;--base&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PR_TITLE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PR_BODY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--reviewer&lt;/span&gt; &lt;span class="s2"&gt;"reviewer1,reviewer2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Automatically assign team members as reviewers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto-label
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh &lt;span class="nb"&gt;pr &lt;/span&gt;create &lt;span class="nt"&gt;--base&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PR_TITLE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PR_BODY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--label&lt;/span&gt; &lt;span class="s2"&gt;"enhancement,needs-review"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Automatically add labels based on the changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Template Support
&lt;/h3&gt;

&lt;p&gt;Read GitHub's PR template and pass it to Claude to generate PRs that follow the template.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;git-pr-auto makes PR creation dramatically easier. It's particularly effective in these scenarios:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Managing multiple GitHub accounts&lt;/li&gt;
&lt;li&gt;Finding PR descriptions tedious to write&lt;/li&gt;
&lt;li&gt;Spending too much time organizing and summarizing changes&lt;/li&gt;
&lt;li&gt;Wanting to automate the PR creation process&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Claude API's accuracy is high, generating content that's usable as-is in most cases. Occasional adjustments are needed, but it's much faster than writing from scratch.&lt;/p&gt;

&lt;p&gt;Since using this function, time spent on PR creation has significantly decreased. Give it a try if you face similar challenges.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cli.github.com/" rel="noopener noreferrer"&gt;GitHub CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.anthropic.com/en/docs/build-with-claude/claude-cli" rel="noopener noreferrer"&gt;Claude CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/book/en/v2/Git-Branching-Branching-Workflows" rel="noopener noreferrer"&gt;Git Branching Strategy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>shell</category>
      <category>claude</category>
    </item>
    <item>
      <title>Easy Deployment of Vertex AI Agent Engine with vaiae</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Sat, 29 Nov 2025 08:21:21 +0000</pubDate>
      <link>https://forem.com/toyama0919/easy-deployment-of-vertex-ai-agent-engine-with-vaiae-m84</link>
      <guid>https://forem.com/toyama0919/easy-deployment-of-vertex-ai-agent-engine-with-vaiae-m84</guid>
      <description>&lt;h2&gt;
  
  
  Introducing vaiae
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/toyama0919/vaiae" rel="noopener noreferrer"&gt;https://github.com/toyama0919/vaiae&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;vaiae is a tool that makes it easy to deploy and manage Google Cloud Vertex AI Agent Engine from the command line. Define your configuration in a YAML file and deploy with a single command.&lt;/p&gt;

&lt;p&gt;Key features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Declarative deployment with YAML configuration files&lt;/li&gt;
&lt;li&gt;Profile management for multiple environments (dev/prod, etc.)&lt;/li&gt;
&lt;li&gt;Interactive messaging with AI Agents&lt;/li&gt;
&lt;li&gt;Safe validation with dry run mode&lt;/li&gt;
&lt;li&gt;Available as both a Python library and CLI&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why I Built This
&lt;/h2&gt;

&lt;p&gt;Deploying and managing Vertex AI Agent Engine traditionally involved tedious manual work. There were several challenges:&lt;/p&gt;

&lt;h3&gt;
  
  
  Too Much Manual Work
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Need to configure AI Agents through the GCP console every time&lt;/li&gt;
&lt;li&gt;Complex dependency and package version management&lt;/li&gt;
&lt;li&gt;Manual configuration of environment variables and secrets&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Difficult Environment Management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tedious to separate development and production environment settings&lt;/li&gt;
&lt;li&gt;Need to manually switch between different configurations for each environment&lt;/li&gt;
&lt;li&gt;No way to track configuration history or version control&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Team Development Challenges
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Need to document and share configuration details&lt;/li&gt;
&lt;li&gt;Configuration drift between team members&lt;/li&gt;
&lt;li&gt;Deployment procedures become person-dependent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;vaiae solves these challenges with YAML-based configuration management and a simple CLI. Since configurations are managed as code, they can be version-controlled with Git and shared with the team. The profile feature also makes managing multiple environments easy.&lt;/p&gt;

&lt;p&gt;This article covers everything from basic usage to practical applications of vaiae.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Easy installation with pip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;vaiae
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python 3.10 or higher is required.&lt;/p&gt;

&lt;h2&gt;
  
  
  GCP Authentication Setup
&lt;/h2&gt;

&lt;p&gt;Before using vaiae, you need to configure Google Cloud authentication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Using Application Default Credentials&lt;/span&gt;
gcloud auth application-default login

&lt;span class="c"&gt;# Using service account key&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_APPLICATION_CREDENTIALS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/path/to/service-account-key.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, make sure the Vertex AI API is enabled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Creating a Configuration File
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;.agent-engine.yml&lt;/code&gt; in your project root. This is the Agent Engine definition file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Vertex AI configuration&lt;/span&gt;
  &lt;span class="na"&gt;vertex_ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-gcp-project"&lt;/span&gt;
    &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;asia-northeast1"&lt;/span&gt;
    &lt;span class="na"&gt;staging_bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-staging-bucket"&lt;/span&gt;

  &lt;span class="na"&gt;display_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-agent-engine"&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Basic&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Agent&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Engine"&lt;/span&gt;
  &lt;span class="na"&gt;gcs_dir_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-agent/1.0.0"&lt;/span&gt;

  &lt;span class="c1"&gt;# AI Agent configuration&lt;/span&gt;
  &lt;span class="na"&gt;agent_engine&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;instance_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my_package.agents.main_agent"&lt;/span&gt;

  &lt;span class="c1"&gt;# Environment variables&lt;/span&gt;
  &lt;span class="na"&gt;env_vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-api-key"&lt;/span&gt;
    &lt;span class="na"&gt;SLACK_WEBHOOK_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;slack-webhook-url"&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latest"&lt;/span&gt;

  &lt;span class="c1"&gt;# Dependencies&lt;/span&gt;
  &lt;span class="na"&gt;requirements&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google-cloud-aiplatform[adk,agent_engines]==1.96.0"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google-adk"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requests"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important points:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;display_name&lt;/code&gt; serves as a unique identifier for the Agent Engine&lt;/li&gt;
&lt;li&gt;Redeploying with the same &lt;code&gt;display_name&lt;/code&gt; will update instead of creating a new one&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;instance_path&lt;/code&gt; dynamically imports an existing AI Agent instance&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Agent Implementation Example
&lt;/h3&gt;

&lt;p&gt;Here's how to create an AI Agent using &lt;code&gt;google-adk&lt;/code&gt; that can be referenced by &lt;code&gt;instance_path&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# my_package/agents.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.adk.agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;

&lt;span class="c1"&gt;# Define tools as regular functions
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get weather information for a location.

    Args:
        location: City name

    Returns:
        Weather information
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The weather in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is sunny, 22°C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Calculate the sum of two numbers.

    Args:
        a: First number
        b: Second number

    Returns:
        Sum of a and b
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;

&lt;span class="c1"&gt;# Create the root agent
&lt;/span&gt;&lt;span class="n"&gt;main_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-2.0-flash-exp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A helpful assistant that can check weather and perform calculations.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    You are a helpful assistant with the following capabilities:
    - Check weather information using get_weather tool
    - Perform calculations using calculate_sum tool

    Always be polite and provide clear responses.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;calculate_sum&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This agent can be referenced in your &lt;code&gt;.agent-engine.yml&lt;/code&gt; using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;agent_engine&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;instance_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my_package.agents.main_agent"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;instance_path&lt;/code&gt; points to the &lt;code&gt;main_agent&lt;/code&gt; variable defined in the &lt;code&gt;my_package.agents&lt;/code&gt; module. vaiae will dynamically import this agent instance when deploying.&lt;/p&gt;

&lt;p&gt;For more complex agent examples, refer to the &lt;a href="https://cloud.google.com/vertex-ai/docs" rel="noopener noreferrer"&gt;Vertex AI documentation&lt;/a&gt; and &lt;a href="https://pypi.org/project/google-adk/" rel="noopener noreferrer"&gt;google-adk PyPI package&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment
&lt;/h3&gt;

&lt;p&gt;Once you've created the configuration file, deployment is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Preview with dry run&lt;/span&gt;
vaiae deploy &lt;span class="nt"&gt;--dry-run&lt;/span&gt;

&lt;span class="c"&gt;# Actually deploy&lt;/span&gt;
vaiae deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using dry run lets you verify the configuration before actually deploying. This is especially important in production environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checking Deployed AI Agents
&lt;/h3&gt;

&lt;p&gt;You can list deployed Agent Engines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vaiae list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results are displayed in an easy-to-read table format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Found 2 agent engine(s):

Display Name         Resource Name                                        Create Time                  Update Time
-------------------  ---------------------------------------------------  ---------------------------  ---------------------------
my-agent-dev         projects/.../agentEngines/123...                     2024-11-20 10:30:00          2024-11-25 14:20:00
my-agent-prod        projects/.../agentEngines/456...                     2024-11-15 09:00:00          2024-11-26 16:45:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sending Messages to AI Agents
&lt;/h3&gt;

&lt;p&gt;You can interact with your deployed AI Agents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Basic message sending&lt;/span&gt;
vaiae send &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Please analyze the data"&lt;/span&gt;

&lt;span class="c"&gt;# Continue conversation with session ID&lt;/span&gt;
vaiae send &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"What's the next step?"&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"session-123"&lt;/span&gt;

&lt;span class="c"&gt;# Specify user ID&lt;/span&gt;
vaiae send &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Create a report"&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"user-456"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using session IDs allows you to continue conversations while maintaining context from previous interactions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deleting AI Agents
&lt;/h3&gt;

&lt;p&gt;You can delete Agent Engines when they're no longer needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Verify with dry run&lt;/span&gt;
vaiae delete &lt;span class="nt"&gt;--dry-run&lt;/span&gt;

&lt;span class="c"&gt;# Actually delete&lt;/span&gt;
vaiae delete

&lt;span class="c"&gt;# Delete by name&lt;/span&gt;
vaiae delete &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"my-agent-engine"&lt;/span&gt;

&lt;span class="c"&gt;# Force delete (including child resources)&lt;/span&gt;
vaiae delete &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"my-agent-engine"&lt;/span&gt; &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Practical Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Managing Multiple Environments
&lt;/h3&gt;

&lt;p&gt;Using different configurations for development and production is essential in real development. vaiae accomplishes this with profile functionality.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Default configuration (common settings)&lt;/span&gt;
&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vertex_ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;asia-northeast1"&lt;/span&gt;
    &lt;span class="na"&gt;staging_bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;common-staging-bucket"&lt;/span&gt;

  &lt;span class="na"&gt;agent_engine&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;instance_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my_package.agents.main_agent"&lt;/span&gt;

  &lt;span class="na"&gt;requirements&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google-cloud-aiplatform[adk,agent_engines]==1.96.0"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google-adk"&lt;/span&gt;

&lt;span class="c1"&gt;# Development environment&lt;/span&gt;
&lt;span class="na"&gt;development&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vertex_ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dev-project"&lt;/span&gt;
  &lt;span class="na"&gt;display_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-agent-dev"&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Development&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Agent"&lt;/span&gt;
  &lt;span class="na"&gt;env_vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;DEBUG_MODE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
    &lt;span class="na"&gt;API_ENDPOINT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api-dev.example.com"&lt;/span&gt;

&lt;span class="c1"&gt;# Production environment&lt;/span&gt;
&lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vertex_ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prod-project"&lt;/span&gt;
  &lt;span class="na"&gt;display_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-agent-prod"&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Production&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Agent"&lt;/span&gt;
  &lt;span class="na"&gt;env_vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;DEBUG_MODE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
    &lt;span class="na"&gt;API_ENDPOINT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy with specific profiles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Deploy to development environment&lt;/span&gt;
vaiae &lt;span class="nt"&gt;--profile&lt;/span&gt; development deploy

&lt;span class="c"&gt;# Deploy to production environment&lt;/span&gt;
vaiae &lt;span class="nt"&gt;--profile&lt;/span&gt; production deploy

&lt;span class="c"&gt;# Delete development AI Agent&lt;/span&gt;
vaiae &lt;span class="nt"&gt;--profile&lt;/span&gt; development delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using profiles makes it easy to switch between different project IDs, display_names, and environment variables per environment. It's efficient to write common settings in &lt;code&gt;default&lt;/code&gt; and only override differences in each environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debug Mode
&lt;/h3&gt;

&lt;p&gt;When issues occur, you can check detailed logs with debug mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vaiae &lt;span class="nt"&gt;--debug&lt;/span&gt; deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Detailed logging makes it easier to identify problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Custom Configuration Files
&lt;/h3&gt;

&lt;p&gt;To use a configuration file other than the default &lt;code&gt;.agent-engine.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vaiae &lt;span class="nt"&gt;--yaml-file&lt;/span&gt; custom-config.yml deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful when you want different configuration files per project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using as a Python API
&lt;/h2&gt;

&lt;p&gt;You can use vaiae not just as a CLI, but also from Python code. This is useful for integrating with CI/CD pipelines or for more complex automation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;vaiae.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Core&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize Core instance
&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Core&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;yaml_file_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.agent-engine.yml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Deploy
&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_or_update_from_yaml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dry_run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Send message
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Please run the analysis&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Overriding Configuration
&lt;/h3&gt;

&lt;p&gt;You can partially override YAML configuration. This is useful when you want to dynamically change settings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;vaiae.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Core&lt;/span&gt;

&lt;span class="n"&gt;core&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Core&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yaml_file_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.agent-engine.yml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;development&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Partially override YAML configuration
&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_or_update_from_yaml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;dry_run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dynamically generated description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;env_vars&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CUSTOM_VAR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;custom_value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API_ENDPOINT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;requirements&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;additional-package==1.0.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Managing Agent Engines
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;vaiae.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Core&lt;/span&gt;

&lt;span class="n"&gt;core&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Core&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yaml_file_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.agent-engine.yml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# List agent engines
&lt;/span&gt;&lt;span class="n"&gt;agent_engines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_agent_engine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;agent_engines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Name: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;display_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Resource: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Delete
&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete_agent_engine_from_yaml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;force&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dry_run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Integrating with CI/CD Pipelines
&lt;/h3&gt;

&lt;p&gt;Example of integrating with CI/CD pipelines like GitHub Actions or Cloud Build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;vaiae.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Core&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deploy_agent&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Deploy by reading configuration from environment variables&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEPLOY_ENV&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;development&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;core&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Core&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;yaml_file_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.agent-engine.yml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Validate before deployment
&lt;/span&gt;    &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_or_update_from_yaml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dry_run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Actually deploy
&lt;/span&gt;    &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_or_update_from_yaml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dry_run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Successfully deployed to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; environment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;deploy_agent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Authentication Error
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Could not automatically determine credentials
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This occurs when GCP authentication is not configured. Set up authentication with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud auth application-default login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use a service account key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_APPLICATION_CREDENTIALS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/path/to/service-account-key.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Permission Error
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Permission denied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service account or user being used needs the following permissions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;aiplatform.agentEngines.create&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;aiplatform.agentEngines.update&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;aiplatform.agentEngines.delete&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;aiplatform.agentEngines.list&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Grant the appropriate permissions in the GCP IAM console.&lt;/p&gt;

&lt;h3&gt;
  
  
  YAML Configuration Error
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Invalid YAML configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the YAML file syntax:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is indentation correct (consistent with 2 or 4 spaces)?&lt;/li&gt;
&lt;li&gt;Are all required fields configured?&lt;/li&gt;
&lt;li&gt;Are special characters escaped?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With vaiae, deploying and managing Vertex AI Agent Engine becomes dramatically easier. Managing configurations with YAML files and easily switching between multiple environments is a huge advantage.&lt;/p&gt;

&lt;p&gt;Main use cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Separate management of development and production environments&lt;/li&gt;
&lt;li&gt;Integration with CI/CD pipelines&lt;/li&gt;
&lt;li&gt;Sharing AI Agent configurations within teams&lt;/li&gt;
&lt;li&gt;Version control and rollback&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In particular, combining the profile feature with dry run enables safe and efficient deployment.&lt;/p&gt;

&lt;p&gt;vaiae has more features planned for the future. I recommend checking the GitHub repository for the latest information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/vaiae/" rel="noopener noreferrer"&gt;PyPI Package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/toyama0919/vaiae" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vertex-ai" rel="noopener noreferrer"&gt;Google Cloud Vertex AI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vertex-ai/docs/agent-builder" rel="noopener noreferrer"&gt;Vertex AI Agent Builder&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gcp</category>
      <category>vertexai</category>
      <category>python</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
