From d7ec39ddda990a93b225065aab57cfdab709ca47 Mon Sep 17 00:00:00 2001 From: Mihir Yavalkar Date: Wed, 10 Jun 2026 10:13:27 -0400 Subject: [PATCH 1/2] Add Gemini CLI extension support as a third platform Problem The plugin supports Codex and Claude Code from a single shared markdown source, but users on Google's Gemini CLI have no way to install or use it. Solution Add Gemini CLI as a third target while keeping the single-source philosophy. Gemini natively supports the Agent Skills standard, so the existing skills/ tree is consumed as-is with no duplication. New files: a root gemini-extension.json manifest, a lean GEMINI.md context file, and a /configure custom command as the onboarding entry point. The token-refresh hook is split into per-platform configs (hooks/hooks.json for Gemini's BeforeTool event, hooks/claude-hooks.json for Claude/Codex PreToolUse) because each platform warns on the other's event names; check-token.sh detects the platform from hook_event_name and emits Gemini's tool_input output schema when rewriting commands. Settings lookup becomes a three-way fallback across .codex/, .claude/, and .gemini/, and the SDK tracking header gains a gemini-cli-extension product. Docs, test scenarios, and the PR template are updated, and the version is bumped to 1.5.0 across all three manifests. Result Gemini CLI users can install the plugin with gemini extensions install https://github.com/spotify/ads-agentic-tools (or gemini extensions link for local development), with skills auto-activating from natural language and OAuth tokens refreshing automatically. Codex and Claude Code behavior is unchanged. Co-Authored-By: Claude Fable 5 --- .claude-plugin/plugin.json | 5 +- .codex-plugin/plugin.json | 4 +- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .gitignore | 1 + AGENTS.md | 13 +-- CHANGELOG.md | 13 +++ CONTRIBUTING.md | 11 ++- GEMINI.md | 31 +++++++ README.md | 29 ++++++- agents/spotify-ads-request-builder.md | 11 +-- commands/configure.toml | 8 ++ gemini-extension.json | 6 ++ hooks/check-token.sh | 87 +++++++++++++------ hooks/claude-hooks.json | 17 ++++ hooks/hooks.json | 11 +-- skills/ads/SKILL.md | 11 +-- skills/api-reference/SKILL.md | 12 +-- .../examples/aggregate-report.md | 2 +- .../examples/full-campaign-flow.md | 2 +- skills/assets/SKILL.md | 11 +-- skills/build-campaign/SKILL.md | 11 +-- skills/bulk/SKILL.md | 11 +-- skills/campaign-strategy/SKILL.md | 5 +- skills/campaigns/SKILL.md | 11 +-- skills/clone/SKILL.md | 11 +-- skills/configure/SKILL.md | 15 ++-- skills/dashboard/SKILL.md | 11 +-- skills/export/SKILL.md | 11 +-- skills/monitor/SKILL.md | 11 +-- skills/report/SKILL.md | 11 +-- templates/settings-template.md | 4 +- tests/test-scenarios.md | 6 +- 32 files changed, 281 insertions(+), 124 deletions(-) create mode 100644 GEMINI.md create mode 100644 commands/configure.toml create mode 100644 gemini-extension.json create mode 100644 hooks/claude-hooks.json diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index dde61e3..fb7d563 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "spotify-ads-api", - "version": "1.4.0", + "version": "1.5.0", "description": "Manage Spotify ad campaigns with natural language. Create campaigns, ad sets, ads, pull reports, and handle OAuth — all through conversation.", "author": { "name": "Alex Murphy" @@ -9,5 +9,6 @@ "repository": "https://github.com/spotify/ads-agentic-tools", "license": "Apache-2.0", "keywords": ["spotify", "ads", "api", "campaigns", "advertising", "oauth", "reporting"], - "skills": "./skills/" + "skills": "./skills/", + "hooks": "./hooks/claude-hooks.json" } diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 032d0b7..471b166 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "spotify-ads-api", - "version": "1.4.0", + "version": "1.5.0", "description": "Manage Spotify ad campaigns with natural language. Create campaigns, ad sets, ads, pull reports, and handle OAuth — all through conversation.", "author": { "name": "Alex Murphy" @@ -10,7 +10,7 @@ "license": "Apache-2.0", "keywords": ["spotify", "ads", "api", "campaigns", "advertising", "oauth", "reporting"], "skills": "./skills/", - "hooks": "./hooks/hooks.json", + "hooks": "./hooks/claude-hooks.json", "interface": { "displayName": "Spotify Ads API", "shortDescription": "Manage Spotify ad campaigns with natural language", diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 221b5e0..6da44de 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,7 +4,7 @@ #### Checklist -- [ ] Tested against the Spotify Ads API with Codex or Claude Code +- [ ] Tested against the Spotify Ads API with Codex, Claude Code, or Gemini CLI - [ ] Existing skills still work as expected - [ ] SKILL.md frontmatter is valid (name, description, allowed-tools) - [ ] README or CHANGELOG updated (if user-facing change) diff --git a/.gitignore b/.gitignore index fe123fc..ce21cdb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .claude/*.local.md .codex/*.local.md +.gemini/*.local.md .DS_Store *.log internal-v3.yaml diff --git a/AGENTS.md b/AGENTS.md index d7fe074..a801840 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ This is the canonical instruction file for agents working in this repository. Co ## What This Is -A Codex and Claude Code plugin package for the Spotify Ads API v3. All source files are markdown — there is no compiled code, no package manager, no build step, no tests. The plugin translates natural language into REST API calls for managing campaigns, ad sets, ads, assets, audiences, and reporting. +A Codex, Claude Code, and Gemini CLI plugin package for the Spotify Ads API v3. All source files are markdown — there is no compiled code, no package manager, no build step, no tests. The plugin translates natural language into REST API calls for managing campaigns, ad sets, ads, assets, audiences, and reporting. ## Architecture @@ -24,8 +24,9 @@ The plugin follows the agent plugin structure with four component types: - `skills/clone/` — Clone campaigns or ad sets by reading the source hierarchy and recreating entities with modifications - `skills/api-reference/` — Comprehensive API v3 reference documentation with `references/` (endpoints, schemas, enums) and `examples/` (full flows). Activates automatically when the Spotify Ads API is mentioned. - **Agent** (`agents/spotify-ads-request-builder.md`) — A natural language agent that triggers automatically when users describe advertising tasks conversationally. Handles multi-step operations (campaign -> ad set -> ad) by chaining API calls and passing IDs between steps. -- **Hooks** (`hooks/hooks.json`) — A `PreToolUse` hook that automatically refreshes expired OAuth tokens before Spotify API calls. -- **Settings** (`.codex/spotify-ads-api.local.md`, with fallback to `.claude/spotify-ads-api.local.md`) — Per-user local config with YAML frontmatter storing OAuth credentials (access_token, refresh_token, client_id, token_expires_at), ad_account_id, and auto_execute. The client_secret is stored in the macOS Keychain (service: `spotify-ads-api-client-secret`, account: `spotify-ads-api`), not in this file. Template lives in `templates/settings-template.md`. These files are gitignored. +- **Hooks** (`hooks/`) — Two per-platform hook configs invoking the same `hooks/check-token.sh` to automatically refresh expired OAuth tokens before Spotify API calls: `hooks/hooks.json` is Gemini-only (`BeforeTool` — Gemini auto-discovers this fixed path and warns on unknown event names, so it must not contain `PreToolUse`), and `hooks/claude-hooks.json` is for Claude/Codex (`PreToolUse`), declared explicitly via the `hooks` field in both `.claude-plugin/plugin.json` and `.codex-plugin/plugin.json`. The script detects the platform from the `hook_event_name` in the hook payload. Note the timeout units differ: seconds for `PreToolUse`, milliseconds for `BeforeTool`. +- **Commands** (`commands/configure.toml`) — A Gemini CLI custom command exposing `/configure` as an explicit entry point to the configure skill. Other skills auto-activate on Gemini via its native Agent Skills support. +- **Settings** (`.codex/spotify-ads-api.local.md`, `.claude/spotify-ads-api.local.md`, or `.gemini/spotify-ads-api.local.md`, with each platform preferring its own file and falling back to the other two) — Per-user local config with YAML frontmatter storing OAuth credentials (access_token, refresh_token, client_id, token_expires_at), ad_account_id, and auto_execute. The client_secret is stored in the macOS Keychain (service: `spotify-ads-api-client-secret`, account: `spotify-ads-api`), not in this file. Template lives in `templates/settings-template.md`. These files are gitignored. ## Marketplace Compatibility @@ -34,7 +35,9 @@ Keep both marketplace files intentional and in sync: - `.agents/plugins/marketplace.json` is the Codex-facing catalog used by `codex plugin marketplace add spotify/ads-agentic-tools`. It must include Codex's `interface.displayName`, `policy.installation`, `policy.authentication`, and `category` metadata. Because this plugin lives at the repository root, use a Git-backed root plugin source (`"source": "url"` with the repository URL) instead of a local `source.path` of `"./"`; Codex treats that local root path as empty and skips the plugin. - `.claude-plugin/marketplace.json` is the Claude Code-compatible catalog. Claude Code requires marketplace metadata at that path and requires a top-level `owner`. To keep the file portable, use the stricter Claude-valid schema: local plugin sources should use the relative string form (`"source": "./"` for this repo-root plugin), and Codex-only marketplace fields such as top-level `interface` or plugin `policy` should not be added to this file. The Claude plugin manifest `.claude-plugin/plugin.json` must also avoid Codex-only manifest fields such as `interface`; keep display metadata in `.codex-plugin/plugin.json` and the Codex marketplace. -When updating marketplace metadata, keep the plugin name, source path, category, description, and user-facing display name aligned wherever each schema supports those fields. +- Gemini CLI has no marketplace file. The root `gemini-extension.json` is the Gemini manifest; `gemini extensions install ` reads it directly, and Gemini auto-discovers the `skills/`, `commands/`, and `hooks/` directories at the extension root. Keep its `name`, `version`, and `description` in sync with the other two manifests, and do not add Gemini-only fields (such as `contextFileName`) to the Claude or Codex manifests. + +When updating marketplace metadata, keep the plugin name, source path, category, description, and user-facing display name aligned wherever each schema supports those fields. The plugin `version` must be bumped in all three manifests together: `.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, and `gemini-extension.json`. ## API Conventions to Know @@ -51,7 +54,7 @@ These non-obvious API quirks were discovered through real testing and are critic - **Report field name** is `fields`, not `report_fields`. - **No DELETE** on campaigns/ad sets/ads — use status changes (ARCHIVED, PAUSED). - **Base URL**: `https://api-partner.spotify.com/ads/v3`. -- **Tracking header**: Every API request must include the SDK tracking header. On Codex, read the `version` from `.codex-plugin/plugin.json` and set `SDK_HEADER="X-Spotify-Ads-Sdk: codex-plugin/$PLUGIN_VERSION"`. On Claude, read `.claude-plugin/plugin.json` and use `claude-code-plugin/$PLUGIN_VERSION`. Include `-H "$SDK_HEADER"` on all curl commands. +- **Tracking header**: Every API request must include the SDK tracking header. On Codex, read the `version` from `.codex-plugin/plugin.json` and set `SDK_HEADER="X-Spotify-Ads-Sdk: codex-plugin/$PLUGIN_VERSION"`. On Claude, read `.claude-plugin/plugin.json` and use `claude-code-plugin/$PLUGIN_VERSION`. On Gemini, read `gemini-extension.json` (extension root) and use `gemini-cli-extension/$PLUGIN_VERSION`. Include `-H "$SDK_HEADER"` on all curl commands. - **`entity_status_type` must match `entity_type`** in `aggregate_reports` queries. For example, use `entity_status_type=AD_SET` when `entity_type=AD_SET` — using `entity_status_type=CAMPAIGN` with `entity_type=AD_SET` causes a filter validation error. - **Audience estimates**: The build-campaign and ads skills run `POST /estimates/audience` before creating ad sets to validate targeting. This catches "min audience threshold" errors before they happen. diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d98efc..91b4fef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [1.5.0] - 2026-06-10 + +### Added +- Gemini CLI extension support: root `gemini-extension.json` manifest, `GEMINI.md` context file, and a `/configure` custom command; skills load through Gemini's native Agent Skills support with no content duplication +- OAuth token auto-refresh on Gemini CLI via a `BeforeTool` hook; hook configs are now split per platform (`hooks/hooks.json` for Gemini, `hooks/claude-hooks.json` for Claude/Codex) since each platform rejects the other's event names +- `.gemini/spotify-ads-api.local.md` settings path (gitignored) + +### Changed +- Settings lookup is now a three-way fallback across `.codex/`, `.claude/`, and `.gemini/` in all skills, the agent, and the token-refresh hook +- `check-token.sh` detects the platform from the hook payload's `hook_event_name` and emits Gemini's `tool_input` output schema when rewriting commands +- SDK tracking header gains a third product: `gemini-cli-extension/$PLUGIN_VERSION` on Gemini +- Plugin version is now synced across three manifests (`.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, `gemini-extension.json`), all bumped to 1.5.0 + ## [1.4.0] - 2026-05-20 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b815133..4f8d6bb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ Look for issues labeled [`good first issue`](https://github.com/spotify/ads-agen ## Building the Project -This is a Codex and Claude Code plugin package made mostly of markdown files. There is no build step, no package manager, and no compiled code. +This is a Codex, Claude Code, and Gemini CLI plugin package made mostly of markdown files. There is no build step, no package manager, and no compiled code. To run the plugin locally: @@ -29,10 +29,16 @@ To run the plugin locally: claude --plugin-dir /path/to/ads-agentic-tools ``` + For Gemini CLI: + ```bash + gemini extensions link /path/to/ads-agentic-tools + ``` + 3. Configure credentials: ``` /spotify-ads-api:configure ``` + (`/configure` on Gemini CLI) ## Workflow @@ -51,9 +57,10 @@ We follow the [GitHub Flow Workflow](https://guides.github.com/introduction/flow There is no automated test suite. Before submitting a pull request: -- Verify your changes work against the Spotify Ads API +- Verify your changes work against the Spotify Ads API on at least one supported platform (Codex, Claude Code, or Gemini CLI) - Confirm that existing skills (`/spotify-ads-api:campaigns`, `/spotify-ads-api:ads`, etc.) still function correctly - If adding a new skill, include a `SKILL.md` following the patterns in existing skill directories +- If touching `hooks/`, note there are two per-platform configs: `hooks/hooks.json` (Gemini, `BeforeTool`) and `hooks/claude-hooks.json` (Claude/Codex, `PreToolUse`), both calling `check-token.sh` — test the token-refresh hook on all three platforms ## Style diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..ee2bec9 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,31 @@ +# Spotify Ads API Extension + +Manage Spotify ad campaigns with natural language. Capabilities are packaged as Agent Skills (campaigns, ads, assets, reporting, dashboards, monitoring, bulk operations, cloning, exports, and a full campaign builder). Describe what you want — e.g. "show my campaign dashboard" or "create a campaign for my podcast" — or run `/skills list` to see everything available. + +## Settings + +Read and write per-user configuration in `.gemini/spotify-ads-api.local.md` (YAML frontmatter: `access_token`, `refresh_token`, `token_expires_at`, `client_id`, `ad_account_id`, `environment`, `auto_execute`). If that file does not exist, fall back to `.claude/spotify-ads-api.local.md`, then `.codex/spotify-ads-api.local.md`. + +Never commit these files. The `client_secret` is stored in the macOS Keychain (service: `spotify-ads-api-client-secret`, account: `spotify-ads-api`), not in the settings file. + +## First-Time Setup + +Run `/configure` (or ask to "configure Spotify Ads API credentials") to set up OAuth 2.0 authentication and select an ad account. Documentation elsewhere may reference `/spotify-ads-api:configure` — that is the Claude Code/Codex name for the same configure skill. + +## SDK Tracking Header + +Every Spotify Ads API request must include the SDK tracking header. Read the `version` from this extension's `gemini-extension.json` and set: + +```bash +SDK_HEADER="X-Spotify-Ads-Sdk: gemini-cli-extension/$PLUGIN_VERSION" +``` + +Include `-H "$SDK_HEADER"` on all curl commands to `api-partner.spotify.com`. + +## Token Expiry + +A BeforeTool hook refreshes expired OAuth tokens automatically before API calls. If a request still returns 401, refresh the token per the configure skill's instructions or re-run `/configure`. + +## Contributors + +If you are editing this repository itself (not using the extension), `AGENTS.md` is the canonical instruction file — read it before making changes. diff --git a/README.md b/README.md index 56825f4..b11f7c3 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Spotify Ads Agentic Tools -A Codex and Claude Code plugin package that lets you manage Spotify advertising campaigns through natural language. Create campaigns, target audiences, launch ads, and pull performance reports — all by describing what you want in plain English. +A Codex, Claude Code, and Gemini CLI plugin package that lets you manage Spotify advertising campaigns through natural language. Create campaigns, target audiences, launch ads, and pull performance reports — all by describing what you want in plain English. Check out our post on the [Spotify Engineering Blog](https://engineering.atspotify.com/2026/5/spotify-ads-api-claude-plugins). ## Prerequisites -- Codex or [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) +- Codex, [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code), or [Gemini CLI](https://geminicli.com/) - A [Spotify Developer](https://developer.spotify.com/) account with an ads-enabled app - A Spotify Ads ad account ID - Python 3.8+ (for automated OAuth flow; optional — manual flow available as fallback) @@ -31,6 +31,14 @@ Restart Codex after adding the marketplace. Then open the plugin directory in th Use `codex plugin marketplace upgrade` later to refresh installed marketplace sources. +### Gemini CLI + +```bash +gemini extensions install https://github.com/spotify/ads-agentic-tools +``` + +Restart Gemini CLI, then verify with `/extensions`. On Gemini, skills activate automatically from natural language (or browse them with `/skills list`); run `/configure` for first-time setup instead of `/spotify-ads-api:configure`. Note: automatic OAuth token refresh uses the macOS Keychain, so auto-refresh is macOS-only. + ## Install from source Use a source checkout for local development or testing unreleased changes. @@ -58,7 +66,14 @@ Use a source checkout for local development or testing unreleased changes. alias claude-ads='claude --plugin-dir /path/to/ads-agentic-tools' ``` -The repository includes platform-specific marketplace metadata: `.agents/plugins/marketplace.json` for Codex and `.claude-plugin/marketplace.json` for Claude Code. Both point at the repo-root plugin, so keep them in sync when changing plugin metadata. +4. For Gemini CLI, link the checkout as a local extension: + ```bash + gemini extensions link "$(pwd)" + ``` + + The link is a symlink, so source changes are picked up on the next Gemini CLI restart. + +The repository includes platform-specific marketplace metadata: `.agents/plugins/marketplace.json` for Codex and `.claude-plugin/marketplace.json` for Claude Code. Gemini CLI has no marketplace file — it installs directly from the repository using the root `gemini-extension.json` manifest. Keep all three manifests in sync when changing plugin metadata. ## Configure @@ -72,6 +87,7 @@ The repository includes platform-specific marketplace metadata: `.agents/plugins ``` /spotify-ads-api:configure ``` + (On Gemini CLI, run `/configure` instead — the skill names below all apply, but the slash-command prefix is Claude Code/Codex syntax.) This opens your browser for Spotify authorization, then saves your tokens locally with automatic refresh. 3. Create your first campaign: @@ -94,6 +110,8 @@ Run `/spotify-ads-api:configure token `. Accepts a pre-obtained acce ## Available Skills +Skill names below use Claude Code/Codex slash-command syntax. On Gemini CLI, the same skills activate automatically from natural language (browse them with `/skills list`), and setup is `/configure`. + | Skill | Description | |-------|-------------| | `/spotify-ads-api:configure` | Set up OAuth credentials, ad account, and preferences | @@ -130,7 +148,7 @@ The plugin includes an agent that interprets natural language requests automatic ## Configuration Reference -Settings are stored in `.codex/spotify-ads-api.local.md` on Codex and `.claude/spotify-ads-api.local.md` on Claude. Each platform falls back to the other settings file if its preferred file does not exist. Both paths are gitignored. +Settings are stored in `.codex/spotify-ads-api.local.md` on Codex, `.claude/spotify-ads-api.local.md` on Claude, and `.gemini/spotify-ads-api.local.md` on Gemini. Each platform falls back to the other settings files if its preferred file does not exist. All three paths are gitignored. | Field | Description | Default | |-------|-------------|---------| @@ -160,6 +178,9 @@ Your targeting is too narrow for the selected ad format. Try broadening the age **"Asset stuck in PROCESSING"** Large files may take longer to transcode. Check status with `/spotify-ads-api:assets get `. If status is REJECTED, the file may not meet format requirements. +**Skill not activating on Gemini CLI** +Run `/skills list` to confirm the extension's skills loaded, and `/extensions` to confirm the extension is enabled. Restart Gemini CLI after installing or linking. + ## License Copyright 2026 Spotify, Inc. diff --git a/agents/spotify-ads-request-builder.md b/agents/spotify-ads-request-builder.md index 660809d..0755ad5 100644 --- a/agents/spotify-ads-request-builder.md +++ b/agents/spotify-ads-request-builder.md @@ -53,11 +53,12 @@ You are a Spotify Ads API specialist that translates natural language advertisin **Startup Process:** 1. Read `access_token`, `ad_account_id`, and `auto_execute` from the active platform settings file: - - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`. - - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`. -2. If neither settings file exists, inform the user to run `/spotify-ads-api:configure` first and stop + - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Gemini: prefer `.gemini/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.codex/spotify-ads-api.local.md`. +2. If no settings file exists, inform the user to run the configure skill first (`/spotify-ads-api:configure` on Claude/Codex, `/configure` on Gemini) and stop 3. Base URL: `https://api-partner.spotify.com/ads/v3` -4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex or `.claude-plugin/plugin.json` on Claude. Set `SDK_PRODUCT` to `codex-plugin` on Codex or `claude-code-plugin` on Claude, then set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"`. Include `-H "$SDK_HEADER"` on all API requests +4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex, `.claude-plugin/plugin.json` on Claude, or `gemini-extension.json` (extension root) on Gemini. Set `SDK_PRODUCT` to `codex-plugin` on Codex, `claude-code-plugin` on Claude, or `gemini-cli-extension` on Gemini, then set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"`. Include `-H "$SDK_HEADER"` on all API requests **Request Building Process:** 1. Analyze the user's natural language request @@ -170,7 +171,7 @@ curl -s -w "\nHTTP_STATUS:%{http_code}" -H "Authorization: Bearer $TOKEN" \ Always check the `HTTP_STATUS:` line first before interpreting the response. **Error Handling:** -- If the API returns a **401 Unauthorized**, the token is likely expired. If the plugin has OAuth credentials configured (refresh_token, client_id in settings, client_secret in keychain), the pre-tool hook should auto-refresh. If auto-refresh didn't occur, suggest running `/spotify-ads-api:configure` to re-authenticate. +- If the API returns a **401 Unauthorized**, the token is likely expired. If the plugin has OAuth credentials configured (refresh_token, client_id in settings, client_secret in keychain), the pre-tool hook should auto-refresh. If auto-refresh didn't occur, suggest running the configure skill (`/spotify-ads-api:configure` on Claude/Codex, `/configure` on Gemini) to re-authenticate. - If the API returns other errors, read the error message and explain what went wrong in plain language - Suggest fixes for common errors (missing fields, budget too low, targeting too narrow, etc.) - Never retry automatically on 4xx errors — explain the issue to the user diff --git a/commands/configure.toml b/commands/configure.toml new file mode 100644 index 0000000..767c429 --- /dev/null +++ b/commands/configure.toml @@ -0,0 +1,8 @@ +description = "Configure Spotify Ads API credentials (OAuth 2.0, manual, or direct token)" +prompt = """ +Activate the spotify-ads-api `configure` Agent Skill and follow its instructions +exactly. If skill activation is unavailable, read and follow skills/configure/SKILL.md +from this extension's directory. + +User arguments (mode selection: oauth | manual | token ): {{args}} +""" diff --git a/gemini-extension.json b/gemini-extension.json new file mode 100644 index 0000000..a09878a --- /dev/null +++ b/gemini-extension.json @@ -0,0 +1,6 @@ +{ + "name": "spotify-ads-api", + "version": "1.5.0", + "description": "Manage Spotify ad campaigns with natural language. Create campaigns, ad sets, and ads, pull reports, upload creative assets, and handle OAuth — all through conversation.", + "contextFileName": "GEMINI.md" +} diff --git a/hooks/check-token.sh b/hooks/check-token.sh index dd5c27e..6b0570c 100755 --- a/hooks/check-token.sh +++ b/hooks/check-token.sh @@ -1,7 +1,7 @@ #!/bin/bash set -uo pipefail -# Spotify Ads API PreToolUse hook +# Spotify Ads API pre-tool hook (PreToolUse on Claude/Codex, BeforeTool on Gemini) # # Auto-refreshes expired OAuth tokens before API calls @@ -14,29 +14,6 @@ elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -d "${CLAUDE_PLUGIN_ROOT:-}" ]; then PLUGIN_ROOT="$CLAUDE_PLUGIN_ROOT" fi -PROJECT_DIR="${CODEX_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-$PWD}}" - -find_settings_file() { - local first_path second_path - - if [ -n "${CODEX_PROJECT_DIR:-}" ]; then - first_path="$PROJECT_DIR/.codex/spotify-ads-api.local.md" - second_path="$PROJECT_DIR/.claude/spotify-ads-api.local.md" - elif [ -n "${CLAUDE_PROJECT_DIR:-}" ]; then - first_path="$PROJECT_DIR/.claude/spotify-ads-api.local.md" - second_path="$PROJECT_DIR/.codex/spotify-ads-api.local.md" - else - first_path="$PROJECT_DIR/.codex/spotify-ads-api.local.md" - second_path="$PROJECT_DIR/.claude/spotify-ads-api.local.md" - fi - - if [ -f "$first_path" ]; then - printf '%s\n' "$first_path" - elif [ -f "$second_path" ]; then - printf '%s\n' "$second_path" - fi -} - # Read all stdin (hook input JSON) input=$(cat) @@ -50,6 +27,45 @@ if ! command -v jq &>/dev/null; then exit 0 fi +# Detect platform: Gemini fires this as a BeforeTool hook (and sets no +# *_PROJECT_DIR env vars); Claude/Codex fire it as PreToolUse +hook_event=$(printf '%s' "$input" | jq -r '.hook_event_name // ""') +if [ "$hook_event" = "BeforeTool" ]; then + PLATFORM="gemini" +elif [ -n "${CODEX_PROJECT_DIR:-}" ]; then + PLATFORM="codex" +elif [ -n "${CLAUDE_PROJECT_DIR:-}" ]; then + PLATFORM="claude" +else + PLATFORM="codex" +fi + +PROJECT_DIR="${CODEX_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-$PWD}}" +if [ "$PLATFORM" = "gemini" ]; then + stdin_cwd=$(printf '%s' "$input" | jq -r '.cwd // ""') + if [ -n "$stdin_cwd" ]; then + PROJECT_DIR="$stdin_cwd" + fi +fi + +find_settings_file() { + local order dir candidate + + case "$PLATFORM" in + gemini) order=".gemini .claude .codex" ;; + claude) order=".claude .codex .gemini" ;; + *) order=".codex .claude .gemini" ;; + esac + + for dir in $order; do + candidate="$PROJECT_DIR/$dir/spotify-ads-api.local.md" + if [ -f "$candidate" ]; then + printf '%s\n' "$candidate" + return + fi + done +} + # Extract the bash command from tool input command=$(printf '%s' "$input" | jq -r '.tool_input.command // .tool_input.cmd // .input.command // .input.cmd // ""') if [[ -z "$command" ]] || [[ "$command" != *"api-partner.spotify.com"* ]]; then @@ -92,7 +108,7 @@ if [ -n "$SETTINGS_FILE" ] && [ -f "$SETTINGS_FILE" ]; then if [ "$needs_refresh" = true ]; then if [ -z "$refresh_token" ] || [ -z "$client_id" ] || [ -z "$client_secret" ]; then - system_message="Spotify API token may be expired but no refresh credentials are configured. Run /spotify-ads-api:configure to set up OAuth." + system_message="Spotify API token may be expired but no refresh credentials are configured. Run the configure skill (/spotify-ads-api:configure on Claude/Codex, /configure on Gemini) to set up OAuth." else REFRESH_SCRIPT="${PLUGIN_ROOT}/skills/configure/scripts/refresh-token.py" if refresh_result=$(python3 "$REFRESH_SCRIPT" \ @@ -126,15 +142,32 @@ if [ -n "$SETTINGS_FILE" ] && [ -f "$SETTINGS_FILE" ]; then system_message="Spotify API token was expired and has been refreshed automatically." fi else - system_message="Failed to refresh Spotify API token. Run /spotify-ads-api:configure to re-authenticate." + system_message="Failed to refresh Spotify API token. Run the configure skill (/spotify-ads-api:configure on Claude/Codex, /configure on Gemini) to re-authenticate." fi fi fi fi # --- Emit output --- +# Gemini merges hookSpecificOutput.tool_input into the model's tool args; +# Claude/Codex expect permissionDecision/updatedInput instead. if [[ "$modified_command" != "$command" ]]; then - if [ -n "$system_message" ]; then + if [ "$PLATFORM" = "gemini" ]; then + if [ -n "$system_message" ]; then + jq -n --arg cmd "$modified_command" --arg msg "$system_message" '{ + "hookSpecificOutput": { + "tool_input": {"command": $cmd} + }, + "systemMessage": $msg + }' 2>/dev/null + else + jq -n --arg cmd "$modified_command" '{ + "hookSpecificOutput": { + "tool_input": {"command": $cmd} + } + }' 2>/dev/null + fi + elif [ -n "$system_message" ]; then jq -n --arg cmd "$modified_command" --arg msg "$system_message" '{ "hookSpecificOutput": { "permissionDecision": "allow", diff --git a/hooks/claude-hooks.json b/hooks/claude-hooks.json new file mode 100644 index 0000000..9a6542b --- /dev/null +++ b/hooks/claude-hooks.json @@ -0,0 +1,17 @@ +{ + "description": "Auto-refresh expired Spotify OAuth tokens before API calls (Claude Code and Codex; Gemini uses hooks/hooks.json)", + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "bash \"${CODEX_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-.}}/hooks/check-token.sh\"", + "timeout": 30 + } + ] + } + ] + } +} diff --git a/hooks/hooks.json b/hooks/hooks.json index 0e9fd79..2f01da4 100644 --- a/hooks/hooks.json +++ b/hooks/hooks.json @@ -1,14 +1,15 @@ { - "description": "Auto-refresh expired Spotify OAuth tokens before API calls", + "description": "Auto-refresh expired Spotify OAuth tokens before API calls (Gemini CLI; Claude Code and Codex use hooks/claude-hooks.json)", "hooks": { - "PreToolUse": [ + "BeforeTool": [ { - "matcher": "Bash", + "matcher": "run_shell_command", "hooks": [ { "type": "command", - "command": "bash \"${CODEX_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-.}}/hooks/check-token.sh\"", - "timeout": 30 + "name": "spotify-ads-token-refresh", + "command": "bash \"${extensionPath}${/}hooks${/}check-token.sh\"", + "timeout": 30000 } ] } diff --git a/skills/ads/SKILL.md b/skills/ads/SKILL.md index 56f86fd..c523877 100644 --- a/skills/ads/SKILL.md +++ b/skills/ads/SKILL.md @@ -12,12 +12,13 @@ Manage ad sets and ads via the Spotify Ads API. Read settings from the active pl ## Setup 1. Read `access_token`, `ad_account_id`, and `auto_execute` from the active platform settings file: - - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`. - - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`. + - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Gemini: prefer `.gemini/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.codex/spotify-ads-api.local.md`. 2. Base URL: `https://api-partner.spotify.com/ads/v3` -3. If neither settings file exists, instruct the user to run `/spotify-ads-api:configure` first. -4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex or `.claude-plugin/plugin.json` on Claude. -5. Set `SDK_PRODUCT` to `codex-plugin` on Codex or `claude-code-plugin` on Claude. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. +3. If no settings file exists, instruct the user to run the configure skill first (`/spotify-ads-api:configure` on Claude/Codex, `/configure` on Gemini). +4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex, `.claude-plugin/plugin.json` on Claude, or `gemini-extension.json` (extension root) on Gemini. +5. Set `SDK_PRODUCT` to `codex-plugin` on Codex, `claude-code-plugin` on Claude, or `gemini-cli-extension` on Gemini. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. ## Parsing Arguments diff --git a/skills/api-reference/SKILL.md b/skills/api-reference/SKILL.md index 702897e..bb444ef 100644 --- a/skills/api-reference/SKILL.md +++ b/skills/api-reference/SKILL.md @@ -25,10 +25,11 @@ X-Spotify-Ads-Sdk: / Use the active platform SDK product and plugin version: - Codex: read `.codex-plugin/plugin.json`, set `SDK_PRODUCT="codex-plugin"`. - Claude: read `.claude-plugin/plugin.json`, set `SDK_PRODUCT="claude-code-plugin"`. +- Gemini: read `gemini-extension.json` (extension root), set `SDK_PRODUCT="gemini-cli-extension"`. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. -To set up authentication, run `/spotify-ads-api:configure` which supports OAuth 2.0 with automatic token refresh, manual OAuth, or direct token input. +To set up authentication, run the configure skill (`/spotify-ads-api:configure` on Claude/Codex, `/configure` on Gemini), which supports OAuth 2.0 with automatic token refresh, manual OAuth, or direct token input. ## Resource Hierarchy @@ -103,16 +104,17 @@ Every CRUD operation on campaigns, ad sets, ads, assets, and audiences is scoped ## Making API Calls -Read the user's plugin settings from the active platform settings file created by `/spotify-ads-api:configure`: -- Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`. -- Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`. +Read the user's plugin settings from the active platform settings file created by the configure skill: +- Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. +- Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. +- Gemini: prefer `.gemini/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.codex/spotify-ads-api.local.md`. Use the settings file to get: - `access_token` — Bearer token for authentication - `ad_account_id` — Default ad account ID - `auto_execute` — Whether to execute API calls automatically or present them first (default: false) -If the settings file does not exist, instruct the user to run `/spotify-ads-api:configure` first. +If the settings file does not exist, instruct the user to run the configure skill first (`/spotify-ads-api:configure` on Claude/Codex, `/configure` on Gemini). Construct curl commands using the appropriate base URL. Example: diff --git a/skills/api-reference/examples/aggregate-report.md b/skills/api-reference/examples/aggregate-report.md index 6514fd5..2380a29 100644 --- a/skills/api-reference/examples/aggregate-report.md +++ b/skills/api-reference/examples/aggregate-report.md @@ -1,6 +1,6 @@ # Example: Pulling an Aggregate Report -**Note:** All curl examples below assume `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"`, where `SDK_PRODUCT` is `codex-plugin` on Codex and `claude-code-plugin` on Claude. +**Note:** All curl examples below assume `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"`, where `SDK_PRODUCT` is `codex-plugin` on Codex, `claude-code-plugin` on Claude, and `gemini-cli-extension` on Gemini. This example shows how to pull aggregated campaign performance metrics. diff --git a/skills/api-reference/examples/full-campaign-flow.md b/skills/api-reference/examples/full-campaign-flow.md index 845b1f4..d945023 100644 --- a/skills/api-reference/examples/full-campaign-flow.md +++ b/skills/api-reference/examples/full-campaign-flow.md @@ -1,6 +1,6 @@ # Example: Full Campaign Setup Flow -**Note:** All curl examples below assume `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"`, where `SDK_PRODUCT` is `codex-plugin` on Codex and `claude-code-plugin` on Claude. +**Note:** All curl examples below assume `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"`, where `SDK_PRODUCT` is `codex-plugin` on Codex, `claude-code-plugin` on Claude, and `gemini-cli-extension` on Gemini. This example shows the complete sequence of API calls to create a campaign, ad set, and ad. diff --git a/skills/assets/SKILL.md b/skills/assets/SKILL.md index e39ccd0..eee86c1 100644 --- a/skills/assets/SKILL.md +++ b/skills/assets/SKILL.md @@ -12,12 +12,13 @@ Upload, list, retrieve, and archive creative assets (audio, video, images) for u ## Setup 1. Read `access_token`, `ad_account_id`, and `auto_execute` from the active platform settings file: - - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`. - - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`. + - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Gemini: prefer `.gemini/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.codex/spotify-ads-api.local.md`. 2. Base URL: `https://api-partner.spotify.com/ads/v3` -3. If neither settings file exists, instruct the user to run `/spotify-ads-api:configure` first. -4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex or `.claude-plugin/plugin.json` on Claude. -5. Set `SDK_PRODUCT` to `codex-plugin` on Codex or `claude-code-plugin` on Claude. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. +3. If no settings file exists, instruct the user to run the configure skill first (`/spotify-ads-api:configure` on Claude/Codex, `/configure` on Gemini). +4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex, `.claude-plugin/plugin.json` on Claude, or `gemini-extension.json` (extension root) on Gemini. +5. Set `SDK_PRODUCT` to `codex-plugin` on Codex, `claude-code-plugin` on Claude, or `gemini-cli-extension` on Gemini. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. ## Parsing Arguments diff --git a/skills/build-campaign/SKILL.md b/skills/build-campaign/SKILL.md index 2376210..064c44f 100644 --- a/skills/build-campaign/SKILL.md +++ b/skills/build-campaign/SKILL.md @@ -13,12 +13,13 @@ calls and create the full campaign hierarchy: Campaign → Ad Sets → Ads. ## Setup 1. Read `access_token`, `ad_account_id`, and `auto_execute` from the active platform settings file: - - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`. - - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`. + - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Gemini: prefer `.gemini/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.codex/spotify-ads-api.local.md`. 2. Base URL: `https://api-partner.spotify.com/ads/v3` -3. If neither settings file exists, instruct the user to run `/spotify-ads-api:configure` first. -4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex or `.claude-plugin/plugin.json` on Claude. -5. Set `SDK_PRODUCT` to `codex-plugin` on Codex or `claude-code-plugin` on Claude. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. +3. If no settings file exists, instruct the user to run the configure skill first (`/spotify-ads-api:configure` on Claude/Codex, `/configure` on Gemini). +4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex, `.claude-plugin/plugin.json` on Claude, or `gemini-extension.json` (extension root) on Gemini. +5. Set `SDK_PRODUCT` to `codex-plugin` on Codex, `claude-code-plugin` on Claude, or `gemini-cli-extension` on Gemini. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. ## Step 1: Parse the Campaign Description diff --git a/skills/bulk/SKILL.md b/skills/bulk/SKILL.md index 49650ac..a8c003f 100644 --- a/skills/bulk/SKILL.md +++ b/skills/bulk/SKILL.md @@ -12,12 +12,13 @@ Apply batch changes to multiple entities in a single workflow. All operations fo ## Setup 1. Read `access_token`, `ad_account_id`, and `auto_execute` from the active platform settings file: - - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`. - - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`. + - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Gemini: prefer `.gemini/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.codex/spotify-ads-api.local.md`. 2. Base URL: `https://api-partner.spotify.com/ads/v3` -3. If neither settings file exists, instruct the user to run `/spotify-ads-api:configure` first. -4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex or `.claude-plugin/plugin.json` on Claude. -5. Set `SDK_PRODUCT` to `codex-plugin` on Codex or `claude-code-plugin` on Claude. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. +3. If no settings file exists, instruct the user to run the configure skill first (`/spotify-ads-api:configure` on Claude/Codex, `/configure` on Gemini). +4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex, `.claude-plugin/plugin.json` on Claude, or `gemini-extension.json` (extension root) on Gemini. +5. Set `SDK_PRODUCT` to `codex-plugin` on Codex, `claude-code-plugin` on Claude, or `gemini-cli-extension` on Gemini. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. ## Parsing Arguments diff --git a/skills/campaign-strategy/SKILL.md b/skills/campaign-strategy/SKILL.md index d2e3a31..7241df3 100644 --- a/skills/campaign-strategy/SKILL.md +++ b/skills/campaign-strategy/SKILL.md @@ -47,8 +47,9 @@ If budget, dates, or market are missing, make a conservative recommendation and 5. Apply execution conventions. - Read settings from the active platform settings file: - - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`. - - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`. + - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Gemini: prefer `.gemini/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.codex/spotify-ads-api.local.md`. - Base URL: `https://api-partner.spotify.com/ads/v3`. - Include `Authorization: Bearer $TOKEN` and `X-Spotify-Ads-Sdk: /` on API calls. - Include `-w "\nHTTP_STATUS:%{http_code}"` on all API curl commands except file uploads. diff --git a/skills/campaigns/SKILL.md b/skills/campaigns/SKILL.md index 794c410..8818b7d 100644 --- a/skills/campaigns/SKILL.md +++ b/skills/campaigns/SKILL.md @@ -12,12 +12,13 @@ Manage campaigns via the Spotify Ads API. Read settings from the active platform ## Setup 1. Read `access_token`, `ad_account_id`, and `auto_execute` from the active platform settings file: - - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`. - - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`. + - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Gemini: prefer `.gemini/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.codex/spotify-ads-api.local.md`. 2. Base URL: `https://api-partner.spotify.com/ads/v3` -3. If neither settings file exists, instruct the user to run `/spotify-ads-api:configure` first. -4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex or `.claude-plugin/plugin.json` on Claude. -5. Set `SDK_PRODUCT` to `codex-plugin` on Codex or `claude-code-plugin` on Claude. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. +3. If no settings file exists, instruct the user to run the configure skill first (`/spotify-ads-api:configure` on Claude/Codex, `/configure` on Gemini). +4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex, `.claude-plugin/plugin.json` on Claude, or `gemini-extension.json` (extension root) on Gemini. +5. Set `SDK_PRODUCT` to `codex-plugin` on Codex, `claude-code-plugin` on Claude, or `gemini-cli-extension` on Gemini. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. ## Operations diff --git a/skills/clone/SKILL.md b/skills/clone/SKILL.md index c6c5f2f..6c11dae 100644 --- a/skills/clone/SKILL.md +++ b/skills/clone/SKILL.md @@ -12,12 +12,13 @@ Clone an existing campaign or ad set by reading its full hierarchy and recreatin ## Setup 1. Read `access_token`, `ad_account_id`, and `auto_execute` from the active platform settings file: - - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`. - - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`. + - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Gemini: prefer `.gemini/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.codex/spotify-ads-api.local.md`. 2. Base URL: `https://api-partner.spotify.com/ads/v3` -3. If neither settings file exists, instruct the user to run `/spotify-ads-api:configure` first. -4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex or `.claude-plugin/plugin.json` on Claude. -5. Set `SDK_PRODUCT` to `codex-plugin` on Codex or `claude-code-plugin` on Claude. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. +3. If no settings file exists, instruct the user to run the configure skill first (`/spotify-ads-api:configure` on Claude/Codex, `/configure` on Gemini). +4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex, `.claude-plugin/plugin.json` on Claude, or `gemini-extension.json` (extension root) on Gemini. +5. Set `SDK_PRODUCT` to `codex-plugin` on Codex, `claude-code-plugin` on Claude, or `gemini-cli-extension` on Gemini. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. ## Parsing Arguments diff --git a/skills/configure/SKILL.md b/skills/configure/SKILL.md index f12971f..4c343aa 100644 --- a/skills/configure/SKILL.md +++ b/skills/configure/SKILL.md @@ -22,7 +22,8 @@ Full OAuth 2.0 authorization flow with automatic token refresh. 1. Choose the active settings file: - Codex: write `.codex/spotify-ads-api.local.md`. - Claude: write `.claude/spotify-ads-api.local.md`. - Read that file if it exists. If it does not exist, read the other platform's settings file as defaults, but do not overwrite it unless the user asks. + - Gemini: write `.gemini/spotify-ads-api.local.md`. + Read that file if it exists. If it does not exist, read another platform's settings file as defaults, but do not overwrite it unless the user asks. 2. Prompt the user for OAuth credentials using AskUserQuestion: - **client_id** (required) — Spotify app client ID from the developer dashboard @@ -36,7 +37,7 @@ security add-generic-password -a "spotify-ads-api" -s "spotify-ads-api-client-se **Do NOT write client_secret to the settings file.** It must only be stored in the keychain. -4. Attempt the automated OAuth flow by running the helper script: +4. Attempt the automated OAuth flow by running the helper script. On Gemini, no plugin-root env var is set — this skill's files live at `/skills/configure/`, so set `PLUGIN_ROOT` to the extension root (two directories up from this skill's directory) instead of using the snippet below. ```bash PLUGIN_ROOT="${CODEX_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$PWD}}" @@ -75,7 +76,7 @@ uv run "${PLUGIN_ROOT}/skills/configure/scripts/oauth-flow.py" \ 9. Write the active platform settings file (see Settings File Format below). -10. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex or `.claude-plugin/plugin.json` on Claude. Set `SDK_PRODUCT` to `codex-plugin` on Codex or `claude-code-plugin` on Claude, then set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"`. +10. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex, `.claude-plugin/plugin.json` on Claude, or `gemini-extension.json` (extension root) on Gemini. Set `SDK_PRODUCT` to `codex-plugin` on Codex, `claude-code-plugin` on Claude, or `gemini-cli-extension` on Gemini, then set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"`. 11. Verify with a test API call: ```bash @@ -132,7 +133,7 @@ Legacy direct token mode for users who already have an access token. 1. Accept the access token from the argument. -2. Warn the user: "Direct token mode — this token will expire in ~1 hour with no automatic refresh. For auto-refresh, re-run with `/spotify-ads-api:configure oauth` using your client credentials." +2. Warn the user: "Direct token mode — this token will expire in ~1 hour with no automatic refresh. For auto-refresh, re-run the configure skill in oauth mode (`/spotify-ads-api:configure oauth` on Claude/Codex, `/configure oauth` on Gemini) using your client credentials." 3. Read existing settings or prompt for: - **ad_account_id** (required) — Use the same businesses → ad accounts discovery flow as the oauth mode (`GET /businesses` then `GET /businesses/{business_id}/ad_accounts`), or ask the user to paste it. @@ -144,7 +145,7 @@ Legacy direct token mode for users who already have an access token. ## Settings File Format -Write the active platform settings file in this exact format (`.codex/spotify-ads-api.local.md` on Codex, `.claude/spotify-ads-api.local.md` on Claude): +Write the active platform settings file in this exact format (`.codex/spotify-ads-api.local.md` on Codex, `.claude/spotify-ads-api.local.md` on Claude, `.gemini/spotify-ads-api.local.md` on Gemini): ```markdown --- @@ -178,8 +179,8 @@ Report the test API call result: ## Security Notes -- The settings file is gitignored via `.codex/*.local.md` and `.claude/*.local.md`. -- If the active settings directory (`.codex/` or `.claude/`) doesn't exist, create it. +- The settings file is gitignored via `.codex/*.local.md`, `.claude/*.local.md`, and `.gemini/*.local.md`. +- If the active settings directory (`.codex/`, `.claude/`, or `.gemini/`) doesn't exist, create it. - **client_secret is stored in the macOS Keychain**, not in the settings file. Use `security find-generic-password -a "spotify-ads-api" -s "spotify-ads-api-client-secret" -w` to retrieve it when needed. - Never log or display the full access token or client_secret — show only the last 8 characters for confirmation. - Never write client_secret to the settings file or any other plaintext file. diff --git a/skills/dashboard/SKILL.md b/skills/dashboard/SKILL.md index f722e08..8550db9 100644 --- a/skills/dashboard/SKILL.md +++ b/skills/dashboard/SKILL.md @@ -12,12 +12,13 @@ Quick performance overview with metrics, spend, and pacing for active campaigns. ## Setup 1. Read `access_token`, `ad_account_id`, and `auto_execute` from the active platform settings file: - - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`. - - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`. + - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Gemini: prefer `.gemini/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.codex/spotify-ads-api.local.md`. 2. Base URL: `https://api-partner.spotify.com/ads/v3` -3. If neither settings file exists, instruct the user to run `/spotify-ads-api:configure` first. -4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex or `.claude-plugin/plugin.json` on Claude. -5. Set `SDK_PRODUCT` to `codex-plugin` on Codex or `claude-code-plugin` on Claude. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. +3. If no settings file exists, instruct the user to run the configure skill first (`/spotify-ads-api:configure` on Claude/Codex, `/configure` on Gemini). +4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex, `.claude-plugin/plugin.json` on Claude, or `gemini-extension.json` (extension root) on Gemini. +5. Set `SDK_PRODUCT` to `codex-plugin` on Codex, `claude-code-plugin` on Claude, or `gemini-cli-extension` on Gemini. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. ## Parsing Arguments diff --git a/skills/export/SKILL.md b/skills/export/SKILL.md index 996b97c..c8ef622 100644 --- a/skills/export/SKILL.md +++ b/skills/export/SKILL.md @@ -12,12 +12,13 @@ Export campaign hierarchies to CSV for offline review, combining entity data wit ## Setup 1. Read `access_token`, `ad_account_id`, and `auto_execute` from the active platform settings file: - - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`. - - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`. + - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Gemini: prefer `.gemini/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.codex/spotify-ads-api.local.md`. 2. Base URL: `https://api-partner.spotify.com/ads/v3` -3. If neither settings file exists, instruct the user to run `/spotify-ads-api:configure` first. -4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex or `.claude-plugin/plugin.json` on Claude. -5. Set `SDK_PRODUCT` to `codex-plugin` on Codex or `claude-code-plugin` on Claude. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. +3. If no settings file exists, instruct the user to run the configure skill first (`/spotify-ads-api:configure` on Claude/Codex, `/configure` on Gemini). +4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex, `.claude-plugin/plugin.json` on Claude, or `gemini-extension.json` (extension root) on Gemini. +5. Set `SDK_PRODUCT` to `codex-plugin` on Codex, `claude-code-plugin` on Claude, or `gemini-cli-extension` on Gemini. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. ## Parsing Arguments diff --git a/skills/monitor/SKILL.md b/skills/monitor/SKILL.md index df50cd4..c811a69 100644 --- a/skills/monitor/SKILL.md +++ b/skills/monitor/SKILL.md @@ -12,12 +12,13 @@ Diagnose delivery problems across active campaigns. Goes beyond the dashboard by ## Setup 1. Read `access_token`, `ad_account_id`, and `auto_execute` from the active platform settings file: - - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`. - - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`. + - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Gemini: prefer `.gemini/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.codex/spotify-ads-api.local.md`. 2. Base URL: `https://api-partner.spotify.com/ads/v3` -3. If neither settings file exists, instruct the user to run `/spotify-ads-api:configure` first. -4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex or `.claude-plugin/plugin.json` on Claude. -5. Set `SDK_PRODUCT` to `codex-plugin` on Codex or `claude-code-plugin` on Claude. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. +3. If no settings file exists, instruct the user to run the configure skill first (`/spotify-ads-api:configure` on Claude/Codex, `/configure` on Gemini). +4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex, `.claude-plugin/plugin.json` on Claude, or `gemini-extension.json` (extension root) on Gemini. +5. Set `SDK_PRODUCT` to `codex-plugin` on Codex, `claude-code-plugin` on Claude, or `gemini-cli-extension` on Gemini. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. ## Parsing Arguments diff --git a/skills/report/SKILL.md b/skills/report/SKILL.md index d4410dd..7de437b 100644 --- a/skills/report/SKILL.md +++ b/skills/report/SKILL.md @@ -12,12 +12,13 @@ Pull reporting data from the Spotify Ads API. Read settings from the active plat ## Setup 1. Read `access_token`, `ad_account_id`, and `auto_execute` from the active platform settings file: - - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`. - - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`. + - Codex: prefer `.codex/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Claude: prefer `.claude/spotify-ads-api.local.md`, then fall back to `.codex/spotify-ads-api.local.md`, then `.gemini/spotify-ads-api.local.md`. + - Gemini: prefer `.gemini/spotify-ads-api.local.md`, then fall back to `.claude/spotify-ads-api.local.md`, then `.codex/spotify-ads-api.local.md`. 2. Base URL: `https://api-partner.spotify.com/ads/v3` -3. If neither settings file exists, instruct the user to run `/spotify-ads-api:configure` first. -4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex or `.claude-plugin/plugin.json` on Claude. -5. Set `SDK_PRODUCT` to `codex-plugin` on Codex or `claude-code-plugin` on Claude. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. +3. If no settings file exists, instruct the user to run the configure skill first (`/spotify-ads-api:configure` on Claude/Codex, `/configure` on Gemini). +4. Read the active platform manifest for the plugin `version`: `.codex-plugin/plugin.json` on Codex, `.claude-plugin/plugin.json` on Claude, or `gemini-extension.json` (extension root) on Gemini. +5. Set `SDK_PRODUCT` to `codex-plugin` on Codex, `claude-code-plugin` on Claude, or `gemini-cli-extension` on Gemini. Set `SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION"` and include `-H "$SDK_HEADER"` on all API requests. ## Operations diff --git a/templates/settings-template.md b/templates/settings-template.md index 5c2dda5..e01f40c 100644 --- a/templates/settings-template.md +++ b/templates/settings-template.md @@ -11,8 +11,8 @@ auto_execute: false # Spotify Ads API Settings Local configuration for the spotify-ads-api plugin. Store this file at -`.codex/spotify-ads-api.local.md` on Codex or `.claude/spotify-ads-api.local.md` -on Claude. +`.codex/spotify-ads-api.local.md` on Codex, `.claude/spotify-ads-api.local.md` +on Claude, or `.gemini/spotify-ads-api.local.md` on Gemini. Do not commit this file to version control. Client secret is stored in the macOS Keychain, not in this file. diff --git a/tests/test-scenarios.md b/tests/test-scenarios.md index c63e9cb..eddb01e 100644 --- a/tests/test-scenarios.md +++ b/tests/test-scenarios.md @@ -9,13 +9,13 @@ **Variables used in curl examples below:** - `$TOKEN` — OAuth access token from settings - `$BASE_URL` — `https://api-partner.spotify.com/ads/v3` -- `$SDK_HEADER` — `X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION`, where `SDK_PRODUCT` is `codex-plugin` on Codex and `claude-code-plugin` on Claude +- `$SDK_HEADER` — `X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION`, where `SDK_PRODUCT` is `codex-plugin` on Codex, `claude-code-plugin` on Claude, and `gemini-cli-extension` on Gemini --- ## Scenario 1: Configure OAuth -**Prompt:** `/spotify-ads-api:configure` +**Prompt:** `/spotify-ads-api:configure` (`/configure` on Gemini) **Quirks tested:** OAuth flow, settings file creation, token validation @@ -24,7 +24,7 @@ 2. Runs `oauth-flow.py` to open browser and complete authorization 3. Parses JSON output with `access_token`, `refresh_token`, `expires_in` 4. Prompts for `ad_account_id`, `auto_execute` -5. Writes the active platform settings file (`.codex/spotify-ads-api.local.md` on Codex, `.claude/spotify-ads-api.local.md` on Claude) with all fields +5. Writes the active platform settings file (`.codex/spotify-ads-api.local.md` on Codex, `.claude/spotify-ads-api.local.md` on Claude, `.gemini/spotify-ads-api.local.md` on Gemini) with all fields 6. Verifies token with test API call **Success criteria:** From 665fc1aee6fd9b14f2d7d3101aafd93f2dd252ab Mon Sep 17 00:00:00 2001 From: Mihir Yavalkar Date: Wed, 10 Jun 2026 12:16:47 -0400 Subject: [PATCH 2/2] Add Gemini paths to token-refresh test scenario Scenario 10's setup listed only the Codex and Claude settings files and named the hook event as PreToolUse, which does not exist on Gemini. Add the .gemini settings path and platform-aware hook event naming so the scenario is runnable on all three platforms. Co-Authored-By: Claude Fable 5 --- tests/test-scenarios.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test-scenarios.md b/tests/test-scenarios.md index eddb01e..9afb68b 100644 --- a/tests/test-scenarios.md +++ b/tests/test-scenarios.md @@ -318,11 +318,11 @@ curl -s -w "\nHTTP_STATUS:%{http_code}" -X POST -H "Authorization: Bearer