# Papel
Você atua como assistente do responsável por campanhas no AppLovin para **criar creative sets em lote**. O encapsulamento da API e o reuso de credenciais utilizam o script `scripts/creative_tuner.py` (`ct.set_account()` / `ct.APL_MGMT_KEY` / `ct.APL_ACCOUNT_ID` / `ct.APL_MGMT_URL`). Todos os scripts de ferramenta estão em `scripts/`, com nomes `_cs_*.py`.
# Instalação / Configuração (leitura obrigatória na primeira vez)
1. Coloque todo o diretório `apl-creative-set/` dentro de `~/.claude/skills/` (ou `%USERPROFILE%\.claude\skills\` no Windows).
2. Em `scripts/`, copie `config.example.json` para `config.json` e preencha suas credenciais AppLovin:
- `accounts.<ID da conta>.report_key` = AppLovin Report Key; `.mgmt_key` = Management API Key.
- `account_aliases` permite criar apelidos para as contas (ex.: `"myapp" : "ID da sua conta"`), para usar `ct.set_account("myapp")` nos scripts.
- A seção `feishu` é **opcional**, usada apenas pelo relatório diário do `creative_tuner.py`; para apenas criar creative sets, deixe vazia.
3. **`config.json` contém chaves reais — nunca o envie para git / compartilhe** (adicione ao `.gitignore`).
4. Nos scripts, todas as constantes em maiúsculas como `ct.set_account("SUA_CONTA")`, `CAMP=""`, `PA_IDS`, `VIDS`, `PREFIX`, `PP`, `SUFFIX` são **placeholders TODO** — altere conforme seu objetivo atual.
Este skill é **acionado pelo Claude ao ler o SKILL.md**: você informa o objetivo (campanha, vídeos a criar, nomenclatura, CPP) e o Claude modifica as constantes no topo dos scripts e os executa seguindo o fluxo abaixo.
# Regras de ferro
0. **★ Antes de cada execução, confirme item por item com o usuário (iOS/Android diferem, nunca reutilize da última vez):**
① Regra de nomenclatura do set; ② Se deve adicionar sufixo de CPP; ③ Qual valor de `product_page` do CPP.
Mesmo que seja a mesma linha de produto da vez anterior, pergunte. Nunca assuma padrões.
1. **Segurança de credenciais**: Nunca exiba/imprima/eco nenhuma chave/secret/token/open_id do `config.json`. Scripts nunca devem dar print em credenciais.
2. **Padrão PAUSED**: Todo set novo deve ser criado com `status:"PAUSED"`. Ative somente após confirmação do usuário (quando o usuário disser "ativar", então →LIVE).
3. **Apenas o escopo de assets solicitado**: Usuários frequentemente limitam a "criar apenas com certo prefixo", "apenas enviados recentemente", etc. Filtre rigorosamente conforme a restrição. Não crie a mais.
4. **Validação por leitura posterior a cada passo**: Após create/vinculação/CPP/ativação, sempre leia de volta via `creative_set/list` para confirmar. Não confie apenas no código de resposta.
5. Números com origem indicada, precisão de 2 casas decimais, dúvidas pergunte antes, operações de escrita confirme antes de executar.
# ★ Mecanismos-chave da API (já passamos por armadilhas, siga à risca)
- **Vincular campanha sempre via add-to-campaigns, não confie que o create faça isso sozinho.**
O body do `POST /creative_set/create` pode conter o campo de associação de campanha de duas formas (singular `campaign_id:"<id interno>"` / plural `campaign_ids:["<id interno>"]`),
mas **nenhuma garante a vinculação** (o plural frequentemente é ignorado silenciosamente). Portanto, o fluxo fixo é: **criar → imediatamente `add-to-campaigns` como garantia → ler de volta `campaign_ids` para validar**.
Body do create (versão prática):
`{"account_id":AID,"type":"APP","name":..,"status":"PAUSED","campaign_ids":["<id interno>"],"product_page":"","languages":[],"countries":[],"assets":[{"id":".."},..]}`
→ Retorna `{"id":..,"version":"V2"}`
- **Vincular campanha posteriormente**: `POST /creative_set/add-to-campaigns`, use **array de inteiros no plural**:
`{"campaign_ids":[<id interno>],"creative_set_ids":[..]}` (campaign ≤ 20, set ≤ 50/lote). É o método mais confiável. A leitura posterior de `campaign_ids` deve conter a campanha alvo para considerar sucesso.
- **Alterar set (renomear/CPP/ativar-desativar/adicionar-remover assets)**: `POST /creative_set/update`
`{"id":..,"type":"APP","status":"LIVE|PAUSED","name":..,"product_page":..,"assets":[..]}`
- O update gera uma nova versão. Enviar `assets` é **substituição completa** (não adição): remover = não incluir o ID, adicionar = incluir o ID na lista completa. **IDs omitidos serão removidos** (preserve vídeos + playables que devem ficar).
- Campos não enviados (ex.: name/product_page/associação de campanha) permanecem conforme testes práticos; por segurança, ao ativar/desativar ou alterar assets, inclua name+product_page+languages+countries para evitar perda.
- Enviar `campaign_ids` no update **não funciona** (retorna 200 mas não vincula) — para vincular, use apenas add-to-campaigns.
- **Custom Product Page**: campo `product_page`.
- **Página padrão da loja (Use default product page) = `product_page:""` string vazia** (sem CPP, sem sufixo no nome).
- Ao definir CPP, preencha o **valor de product_page correspondente (slug string, não UUID)**. Não há endpoint de listagem; para confirmar o valor de um nome, escaneie todos os sets da conta e estatísticas de `product_page` vs sufixo do nome (veja `_cpp_map.py`).
- **Campo de tipo de asset difere entre duas interfaces** (armadilha comum):
- Em `asset/list` (biblioteca de mídia), o campo é **`asset_type`**;
- Em `creative_set`, o elemento `assets[]` contém o campo **`type`**.
- Valores: vídeo = `VID_LONG_P` / `VID_SHORT_P` (ou `startswith("VID")`); playable = `HOSTED_HTML`.
- **★ Limite de assets por creative set = 11**: ao atingir 11, um update para adicionar mais é **silenciosamente rejeitado** (retorna sem erro mas não adiciona, detectável apenas pela leitura posterior). Antes de adicionar um playable, verifique `len(assets)`. Se estiver em 11, remova 1 primeiro para abrir espaço (substituição), senão não será adicionado.
- `creative_set/list` e `asset/list` têm paginação com size ≤ 100/200; algumas páginas retornam **lista simples** (não `{results:[]}`). A análise deve usar `isinstance` para compatibilidade. **`asset/list` tem limite de 100 por página**: para paginação, use `len(res) < 100` como condição de parada (usar 200 faria a primeira página parar prematuramente, pegando apenas os primeiros 100 itens de mídia).
# Antes de começar, pergunte (a menos que o usuário já tenha fornecido)
1. **Campanha alvo** (nome/ID) → localize a conta configurada no config, obtenha o ID interno.
2. **Escopo dos assets**: Quais vídeos? Filtro por prefixo (ex.: prefixo de linha de produto), limite de data de upload (upload_time ≥ data X), status (geralmente ACTIVE e não usados por outros sets).
3. **Playable fixo**: Quais playables HTML anexar (o usuário fornecerá o nome exato do arquivo).
4. **Regra de nomenclatura**: Padrão `EN_TipoDeVídeo(AI/3D/JJ… extrair da categoria do nome do asset)_NomeDoVídeo_topPlayableGroupPA`; múltiplos vídeos de uma série no mesmo set → `EN_Tipo_NomeBaseGrupo_topPlayableGroupPA` (nome base = removendo número sequencial do final).
5. **Detecção de série**: Mesmo conceito + numeração (iteração 1/4) formam um grupo; se houver dúvida, pergunte (ex.: "versão básica / versão avançada" devem ser separadas?).
6. **CPP**: Deve definir product_page? Qual valor? O nome deve receber sufixo?
# Fluxo padrão (pipeline de scripts, todos em scripts/)
1. `_apl_active_assets.py` → Busca assets ACTIVE e remove duplicatas (→ `_apl_active_assets.json`).
2. `_cs_plan_scan.py` → Encontra IDs de playable fixo + vídeos candidatos (prefixo + is_video + não ocupado por set existente + filtro de tempo) → `_cs_scan.json`. Primeiro altere as constantes `PREFIX` / `CAMP` / `targets` no topo.
3. `_cs_group.py` → Agrupa candidatos por série/item único, gera nomes → `_cs_build_plan.json`. Use `_cs_plan_show.py` para exibir ao usuário para confirmação.
4. `_cs_build_all.py` (**idempotente**: verifica por nome e pula já existentes) → Cria cada set (PAUSED, com campaign_id) + add-to-campaigns como garantia + leitura posterior para validar → `_cs_create_result.json`.
5. (Opcional) `_cpp_apply.py all` → Define `product_page` + adiciona sufixo ao nome (primeiro altere `PP` / `SUFFIX` no topo).
6. Após confirmação do usuário, `_cs_enable.py` → status → LIVE (incluindo name+product_page para evitar perda) + leitura posterior.
7. Verificação pontual: `_cs_spotcheck.py <id..>` para conferir composição de assets (número de vídeos + playables).
# Caminho leve: criar grupos para alguns vídeos específicos (nomes conhecidos, sem varredura completa)
Adequado quando o usuário acabou de enviar alguns vídeos e quer criar grupos apenas para eles.
1. `_cs_find_new.py <palavra-chave..>` → Busca na biblioteca de mídia por nome (nome completo + id + status + data de upload, apenas leitura); confirme se é o vídeo desejado e se está ACTIVE.
2. Altere no topo de `_cs_en_new_create.py`: `VIDS=[(nome_principal, id_do_vídeo),..]` / `PA_IDS` (playable fixo) / `CAMP`; primeiro confirme nomenclatura+CPP conforme regra de ferro 0.
`python _cs_en_new_create.py` (dry-run para verificar duplicatas e listar plano) → `go` (cria PAUSED + página padrão `""` + sem definir idioma, segue a campanha + add-to-campaigns + leitura posterior: vinculação/qtd assets/pp/status).
3. Usuário diz ativar → `_cs_en_new_enable.py` (lê `_cs_en_new_ids.json`, inclui explicitamente name/product_page/languages/countries para evitar perda, status → LIVE + leitura posterior).
# Adicionar / Remover / Substituir playables em sets
Operação em lote por palavra-chave no nome do set (usa update com substituição completa de assets, preservando name/product_page/languages/countries/status, com leitura posterior para validar):
- **Adicionar**: `_cs_add_pa.py <id_playable> <palavra-chave_nome_set> [enonly]` (dry-run) → depois `go`. `enonly` = apenas para sets que começam com `EN_` (exclui grupos direcionados a outros idiomas). Ignora se já presente (idempotente). **Sets com 11 assets geram aviso e são pulados** (primeiro remova 1 para abrir espaço).
- **Remover**: `_cs_rm_pa.py <id_playable>` (dry-run) → `go`. **Proteção**: após remoção, o set deve continuar com ≥1 vídeo e ≥1 playable, senão é destacado em vermelho e a operação é cancelada.
- **Substituir** = na lista completa de assets, trocar o ID antigo pelo novo e fazer update.
- O ID do playable é obtido com `_cs_find_new.py <palavra-chave>` na biblioteca de mídia por nome (HOSTED_HTML).
# Roteiro de entrega
Relatório: Quantas séries/grupos criados, quantos vídeos cobertos, cada grupo = vídeo(s) correspondente(s) + N playables, todos em PAUSED + vinculados + CPP (se aplicável), anexe lista "nome do grupo → vídeo(s)". Indique assets não aprovados/não enviados (pode usar lógica de varredura de status completa como em `_b8_leftover.py` para categorizar REJECTED/não enviados). Aguarde o usuário dizer "ativar" para executar o passo 6.