Compare commits
2 commits
main
...
cheapest-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
733982d29a | ||
|
|
4a9cdf08c8 |
11 changed files with 138 additions and 43 deletions
|
|
@ -7,7 +7,7 @@
|
|||
# shell que o chamou.
|
||||
#
|
||||
# No terminal (esta função), o script também imprime a diretiva `launch
|
||||
# opencode <dir> --model deepseek/deepseek-v4-flash --auto [--prompt "…"]` (OPS26):
|
||||
# opencode <dir> --model cheapestinference/deepseek-v4-flash --auto [--prompt "…"]` (OPS26):
|
||||
# a função executa o cd e então a linha (tokenizada por xargs — honra as aspas
|
||||
# do prompt, nunca eval), e o TUI do opencode abre no worktree — `next` com
|
||||
# `/work-issue --issue <N>` já enviado (OPS33: a Issue claimada vai no prompt),
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ No opencode, isso é o comando **`/worktree next`** / **`/worktree kill`** (`.op
|
|||
|
||||
**Default-go:** `next`, `plan` e `new` imprimem `cd <dir>` na última linha **por padrão**; `--stay` suprime; `--go` explícito continua aceito como no-op. No terminal interativo, use a função `worktree()` de **`.agents/shell/worktree.sh`** (uma linha de `source` no profile) para o `cd` ser aplicado de verdade no shell que te chamou — node não consegue mudar o cwd do shell pai. `kill` também imprime `cd <main>` no fim (a sessão nunca fica num diretório destruído).
|
||||
|
||||
**Launch do opencode (OPS26 + OPS33 + OPS31):** só a função shell marca `TEQO_WORKTREE_TERMINAL=1`; com o marcador, `next`/`plan`/`new` imprimem também a diretiva `launch opencode <dir> --model deepseek/deepseek-v4-flash --auto [--prompt "/work-issue --issue <N>"]` **antes** do `cd`, e a função a executa depois do cd — `next` abre o TUI com `/work-issue --issue <N>` já enviado (a Issue claimada vai no prompt; a skill lê o resto do Forgejo); `plan` abre com `/plan-issue` já enviado (OPS31: o TUI já cai no fluxo de planejamento); `new` abre sem `--prompt` ("apenas conversar"). Sem `exec`: sair do opencode volta ao shell no worktree. Presets são constantes em `scripts/lib/worktree.mjs`. **O comando `/worktree` do opencode (e qualquer automação sem o marcador) nunca lança o TUI.**
|
||||
**Launch do opencode (OPS26 + OPS33 + OPS31):** só a função shell marca `TEQO_WORKTREE_TERMINAL=1`; com o marcador, `next`/`plan`/`new` imprimem também a diretiva `launch opencode <dir> --model cheapestinference/deepseek-v4-flash --auto [--prompt "/work-issue --issue <N>"]` **antes** do `cd`, e a função a executa depois do cd — `next` abre o TUI com `/work-issue --issue <N>` já enviado (a Issue claimada vai no prompt; a skill lê o resto do Forgejo); `plan` abre com `/plan-issue` já enviado (OPS31: o TUI já cai no fluxo de planejamento); `new` abre sem `--prompt` ("apenas conversar"). Sem `exec`: sair do opencode volta ao shell no worktree. Presets são constantes em `scripts/lib/worktree.mjs`. **O comando `/worktree` do opencode (e qualquer automação sem o marcador) nunca lança o TUI.**
|
||||
|
||||
## Fluxo quando invocado como skill (agentes que não têm opencode)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
description: Transforma ideias humanas em Issues GitHub + planos de intenção em docs/plans/ (com gate e registro)
|
||||
model: deepseek/deepseek-v4-flash
|
||||
model: cheapestinference/deepseek-v4-flash
|
||||
---
|
||||
|
||||
Carregue a skill `plan-issue` (ferramenta de skills, nome exato) e siga o fluxo dela de ponta a ponta. A fonte canônica é `.agents/skills/plan-issue/SKILL.md` — não a transcreva nem recrie o fluxo.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
description: Executa uma Issue já claimada de ponta a ponta com supervisão humana (plano de implementação → pausa → execução → simplify → débitos → PR)
|
||||
model: deepseek/deepseek-v4-flash
|
||||
model: cheapestinference/deepseek-v4-flash
|
||||
---
|
||||
|
||||
Carregue a skill `work-issue` (ferramenta de skills, nome exato) e siga o fluxo dela de ponta a ponta. A fonte canônica é `.agents/skills/work-issue/SKILL.md` — não a transcreva nem recrie o fluxo.
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
112
docs/ops/instagram-feed-testing-guide.md
Normal file
112
docs/ops/instagram-feed-testing-guide.md
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# Guide — Testar o feed do Instagram na home pública (S3)
|
||||
|
||||
Como o feed do Instagram chega à home e como validar cada peça do quebra-cabeça
|
||||
quando "os posts não aparecem".
|
||||
|
||||
## Como funciona (o caminho que um post percorre)
|
||||
|
||||
1. A home de campanha renderiza o board **"Acompanhe de perto"**
|
||||
(`CampaignContentSection`). Ele só aparece se houver **algum** card visível
|
||||
(artigos + YouTube + Instagram).
|
||||
2. O render chama `getInstagramFeed()` (`src/utilities/socialFeed/instagramFeedView.ts`),
|
||||
cacheado por 5 min na tag `social-feed`.
|
||||
3. Ele lê a global **Configurações → Feed de redes sociais**
|
||||
(`/admin/globals/social-feed-settings`). Se `enabled` **e** `instagramEnabled`
|
||||
**e** token **e** user id estiverem preenchidos, chama a Instagram Graph API
|
||||
(`GET /{userId}` para o username e `GET /{userId}/media` para os posts).
|
||||
4. Sucesso → grava o snapshot cru na global e o status de sincronização; o board
|
||||
mostra os posts elegíveis (menos os marcados em "Itens excluídos").
|
||||
5. Falha → grava o **motivo** no status e o board cai no último snapshot (ou fica
|
||||
sem cards IG). **A home nunca quebra** — o erro é invisível na home, ele vive
|
||||
no painel de status do admin.
|
||||
|
||||
## O sinal mais rápido: o painel de status (admin)
|
||||
|
||||
Em `/admin` → **Configurações → Feed de redes sociais**, o bloco **"Instagram —
|
||||
estado da sincronização"** é o diagnóstico em 1 clique:
|
||||
|
||||
| Painel mostra | Significado | Ação |
|
||||
| --------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------ |
|
||||
| **Sincronizado · há X min · N posts** | Credenciais aceitas; cards devem estar na home | Conferir a home (cache de 5 min) |
|
||||
| **Falha na última sincronização** | A Graph API recusou; o texto diz o motivo | Seguir a correção do texto e clicar **Tentar sincronizar de novo** |
|
||||
| **Instagram ainda não configurado** | Falta token ou user id | Preencher os dois campos |
|
||||
| **Aguardando a primeira sincronização** | Configurado, nunca tentou | Clicar **Tentar sincronizar de novo** |
|
||||
|
||||
O status é atualizado: no salvar da global (quando as credenciais mudam), em cada
|
||||
render da home e no botão **Tentar sincronizar de novo**.
|
||||
|
||||
## Validar o token fora do admin
|
||||
|
||||
```bash
|
||||
# Se responder com username + user_id, o token é válido para a Graph API do IG:
|
||||
curl -s "https://graph.instagram.com/me?fields=user_id,username&access_token=SEU_TOKEN"
|
||||
|
||||
# Testa o refresh (só token emitido pelo Instagram Login passa):
|
||||
curl -s "https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token=SEU_TOKEN"
|
||||
```
|
||||
|
||||
- `/me` com `OAuthException` → token inválido/expirado **ou emitido via Facebook
|
||||
Login** (a Graph API do Instagram só aceita tokens do Instagram Login).
|
||||
- `/me` ok mas o `user_id` devolvido não bate com o campo **"ID do usuário"** →
|
||||
corrigir o campo.
|
||||
|
||||
## Testar a home pública
|
||||
|
||||
1. Conferir o painel de status primeiro (acima).
|
||||
2. Abrir a home e procurar o board "Acompanhe de perto". Os cards do Instagram
|
||||
têm badge **Instagram**, capa quadrada (1:1), legenda e data, e abrem o post
|
||||
em nova aba (`instagram.com`).
|
||||
3. **Lembrete de cache:** o feed é cacheado por 5 min. Depois de salvar a global
|
||||
no admin o cache é invalidado sozinho; se a mudança foi feita direto no banco
|
||||
ou via seed, invalidar manualmente:
|
||||
```bash
|
||||
curl -X POST "https://<dominio>/api/revalidate?tag=social-feed" \
|
||||
-H "x-revalidate-secret: $REVALIDATE_SECRET"
|
||||
```
|
||||
4. **A seção inteira pode estar ausente** por outro motivo: o board só renderiza
|
||||
se houver pelo menos um card (artigo visível, YouTube ou IG). Um feed IG vazio
|
||||
com artigos e YouTube desligados esconde a seção inteira — não é bug do IG.
|
||||
|
||||
## Testar localmente (stub determinístico)
|
||||
|
||||
Os e2e rodam contra um stub local da Graph API (`tests/e2e/instagram-stub.mjs`)
|
||||
que responde `/media`, `/user`, `/refresh_access_token` e thumbnails com um
|
||||
fixture fixo, com estados alternáveis:
|
||||
|
||||
```bash
|
||||
pnpm test:e2e -- frontend.e2e.spec.ts # inclui "renders the Instagram feed with exclusions"
|
||||
```
|
||||
|
||||
O stub permite alternar estados (`ok | fail | invalid-token`) via
|
||||
`POST /__stub/state` — útil para reproduzir fail-closed e snapshot sem rede.
|
||||
Em dev manual, aponte `INSTAGRAM_API_BASE_URL` para o stub para exercitar o
|
||||
fluxo sem tocar na API real.
|
||||
|
||||
## Checklist de "configurei e não aparece"
|
||||
|
||||
- [ ] Checkbox **Feed ativo** (`enabled`) marcado.
|
||||
- [ ] Checkbox **Instagram ativo** (`instagramEnabled`) marcado.
|
||||
- [ ] **Token de acesso** preenchido e emitido pelo **Instagram Login** (Basic
|
||||
Display), NÃO pela Facebook Login (page token) — este é o erro mais comum; o
|
||||
endpoint de refresh também recusa tokens do Facebook.
|
||||
- [ ] **ID do usuário** é o numérico da conta Business/Creator (o `/me` acima
|
||||
confirma o `user_id` certo).
|
||||
- [ ] O servidor alcança `graph.instagram.com` (em produção o container precisa
|
||||
de saída para a internet; localmente, proxy/firewall bloqueando também dá
|
||||
"Não foi possível falar com a API").
|
||||
- [ ] O painel de status mostra **Sincronizado** (não "Falha" nem "Não configurado").
|
||||
- [ ] Home publicada há mais de 5 min (ou cache invalidado via `?tag=social-feed`).
|
||||
- [ ] Há ao menos um card visível no board (a seção inteira some com tudo vazio).
|
||||
- [ ] Os posts não estão marcados em **Itens excluídos** (post de grade etc.).
|
||||
|
||||
Qualquer estado de falha, o painel de status mostra o motivo em linguagem de
|
||||
produto — é por onde começar antes de culpar a configuração.
|
||||
|
||||
## Referências
|
||||
|
||||
- Runbook de token: `docs/ops/instagram-feed-token-runbook.md`
|
||||
- Código: `src/utilities/socialFeed/instagramFeed.ts` (fetch/parse/erros),
|
||||
`instagramFeedView.ts` (loader cacheado), `instagramSync.ts` (sync do hook),
|
||||
`src/globals/SocialFeedSettings.ts` (campos do admin),
|
||||
`src/components/admin/InstagramSyncStatusPanel.tsx` (painel de status)
|
||||
- Stub e2e: `tests/e2e/instagram-stub.mjs`, `tests/helpers/socialStub.ts`
|
||||
|
|
@ -6,30 +6,5 @@
|
|||
"url": "https://design.penpot.app/mcp/stream?userToken={file:.opencode/secrets/penpot-token}",
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"provider": {
|
||||
"cheapest-inference": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"name": "Cheapest Inference",
|
||||
"options": {
|
||||
"baseURL": "https://api.cheapestinference.com/v1"
|
||||
},
|
||||
"models": {
|
||||
"deepseek-v4-flash": {
|
||||
"name": "DeepSeek V4 Flash",
|
||||
"limit": {
|
||||
"context": 1000000,
|
||||
"output": 384000
|
||||
}
|
||||
},
|
||||
"mimo-v2.5": {
|
||||
"name": "MiMo v2.5",
|
||||
"limit": {
|
||||
"context": 1000000,
|
||||
"output": 128000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,16 @@ export const PLAN_BRANCH_PREFIX = 'plans/plan-issue'
|
|||
/** Env var the interactive-terminal `worktree()` function sets to request the opencode launch. */
|
||||
export const WORKTREE_TERMINAL_ENV = 'TEQO_WORKTREE_TERMINAL'
|
||||
|
||||
/** Preset model for the opencode launch — change the preset by editing this constant. */
|
||||
export const OPENCODE_PRESET_MODEL = 'deepseek/deepseek-v4-flash'
|
||||
/**
|
||||
* Preset model for the opencode launch — change the preset by editing this
|
||||
* constant. Provider `cheapestinference` (Cheapest Inference, global config
|
||||
* in ~/.config/opencode/opencode.jsonc; auth key via `opencode auth login
|
||||
* cheapestinference`). The `max` variant is the default because opencode's
|
||||
* TUI has no `--variant` flag — the per-model variant is persisted in
|
||||
* ~/.local/state/opencode/model.json (`variant["cheapestinference/deepseek-v4-flash"]
|
||||
* = "max"`), applied automatically when the TUI opens with this model.
|
||||
*/
|
||||
export const OPENCODE_PRESET_MODEL = 'cheapestinference/deepseek-v4-flash'
|
||||
|
||||
/**
|
||||
* Skill command sent as the launch's initial message per purpose. `next`
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
* Chamado do terminal interativo (com
|
||||
* `TEQO_WORKTREE_TERMINAL=1`, que só a função shell
|
||||
* seta), imprime também a diretiva `launch
|
||||
* opencode <dir> --model deepseek/deepseek-v4-flash
|
||||
* opencode <dir> --model cheapestinference/deepseek-v4-flash
|
||||
* --auto --prompt "/work-issue --issue <N>"` ANTES
|
||||
* do `cd` — a função shell executa o cd e então a
|
||||
* linha, e o TUI do opencode abre no worktree com
|
||||
|
|
@ -775,7 +775,7 @@ if (!subcommand) {
|
|||
console.log(' opencode command, ou a função `worktree()` de .agents/shell/worktree.sh);')
|
||||
console.log(' no terminal (TEQO_WORKTREE_TERMINAL=1) imprime também a diretiva')
|
||||
console.log(' `launch opencode … --prompt "/work-issue --issue <N>"` (OPS26+OPS33:')
|
||||
console.log(' abre o TUI com deepseek/deepseek-v4-flash + auto + a Issue claimada já')
|
||||
console.log(' abre o TUI com cheapestinference/deepseek-v4-flash + auto + a Issue claimada já')
|
||||
console.log(' informada); --stay suprime cd e launch (o claim ainda acontece); --go')
|
||||
console.log(' explícito continua aceito como no-op; --no-migrate pula migrations e o')
|
||||
console.log(' seed mínimo (db:seed:minimal) nos bancos novos (OPS28: paridade com a CI)')
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ describe('opencode commands couple to their skill by exact name', () => {
|
|||
/^description: .+$/m,
|
||||
)
|
||||
expect(frontmatter![1], 'frontmatter must pin the preference model').toMatch(
|
||||
/^model: deepseek\/deepseek-v4-flash$/m,
|
||||
/^model: cheapestinference\/deepseek-v4-flash$/m,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ describe('opencodeLaunchDirective (terminal-only opencode launch, OPS26 + OPS33)
|
|||
|
||||
it('next launches with the preset model, --auto and the /work-issue command sent (value always quoted)', () => {
|
||||
expect(opencodeLaunchDirective({ dir, purpose: 'next', terminal: true })).toBe(
|
||||
`launch opencode ${dir} --model deepseek/deepseek-v4-flash --auto --prompt "/work-issue"`,
|
||||
`launch opencode ${dir} --model cheapestinference/deepseek-v4-flash --auto --prompt "/work-issue"`,
|
||||
)
|
||||
})
|
||||
|
||||
|
|
@ -156,33 +156,33 @@ describe('opencodeLaunchDirective (terminal-only opencode launch, OPS26 + OPS33)
|
|||
expect(
|
||||
opencodeLaunchDirective({ dir, purpose: 'next', terminal: true, issueNumber: 595 }),
|
||||
).toBe(
|
||||
`launch opencode ${dir} --model deepseek/deepseek-v4-flash --auto --prompt "/work-issue --issue 595"`,
|
||||
`launch opencode ${dir} --model cheapestinference/deepseek-v4-flash --auto --prompt "/work-issue --issue 595"`,
|
||||
)
|
||||
})
|
||||
|
||||
it('plan launches with the same presets and /plan-issue sent (OPS31, value quoted)', () => {
|
||||
expect(opencodeLaunchDirective({ dir, purpose: 'plan', terminal: true })).toBe(
|
||||
`launch opencode ${dir} --model deepseek/deepseek-v4-flash --auto --prompt "/plan-issue"`,
|
||||
`launch opencode ${dir} --model cheapestinference/deepseek-v4-flash --auto --prompt "/plan-issue"`,
|
||||
)
|
||||
})
|
||||
|
||||
it('new launches prompt-less too — "apenas conversar", no skill sent', () => {
|
||||
expect(opencodeLaunchDirective({ dir, purpose: 'new', terminal: true })).toBe(
|
||||
`launch opencode ${dir} --model deepseek/deepseek-v4-flash --auto`,
|
||||
`launch opencode ${dir} --model cheapestinference/deepseek-v4-flash --auto`,
|
||||
)
|
||||
})
|
||||
|
||||
it('plan/new ignore the issueNumber — only next carries the claimed issue', () => {
|
||||
expect(opencodeLaunchDirective({ dir, purpose: 'plan', terminal: true, issueNumber: 7 })).toBe(
|
||||
`launch opencode ${dir} --model deepseek/deepseek-v4-flash --auto --prompt "/plan-issue"`,
|
||||
`launch opencode ${dir} --model cheapestinference/deepseek-v4-flash --auto --prompt "/plan-issue"`,
|
||||
)
|
||||
expect(opencodeLaunchDirective({ dir, purpose: 'new', terminal: true, issueNumber: 7 })).toBe(
|
||||
`launch opencode ${dir} --model deepseek/deepseek-v4-flash --auto`,
|
||||
`launch opencode ${dir} --model cheapestinference/deepseek-v4-flash --auto`,
|
||||
)
|
||||
})
|
||||
|
||||
it('pins the preset constants — changing the model is editing a constant', () => {
|
||||
expect(OPENCODE_PRESET_MODEL).toBe('deepseek/deepseek-v4-flash')
|
||||
expect(OPENCODE_PRESET_MODEL).toBe('cheapestinference/deepseek-v4-flash')
|
||||
expect(WORKTREE_TERMINAL_ENV).toBe('TEQO_WORKTREE_TERMINAL')
|
||||
expect(OPENCODE_SKILL_COMMAND_BY_PURPOSE).toEqual({
|
||||
next: '/work-issue',
|
||||
|
|
@ -193,7 +193,7 @@ describe('opencodeLaunchDirective (terminal-only opencode launch, OPS26 + OPS33)
|
|||
|
||||
it('an unknown purpose degrades to a prompt-less launch (fail-safe direction)', () => {
|
||||
expect(opencodeLaunchDirective({ dir, purpose: 'bogus', terminal: true })).toBe(
|
||||
`launch opencode ${dir} --model deepseek/deepseek-v4-flash --auto`,
|
||||
`launch opencode ${dir} --model cheapestinference/deepseek-v4-flash --auto`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue