diff --git a/.agent-marketplace.json b/.agent-marketplace.json index 622385259e4..5759ebb7a12 100644 --- a/.agent-marketplace.json +++ b/.agent-marketplace.json @@ -196,6 +196,16 @@ "category": "data & apis", "source": "./plugins/peec", "version": "1.0.0" + }, + { + "name": "sazabi", + "description": "Investigate production with Sazabi: ask questions about your logs and traces, triage issues, check deploys, build dashboards, and send app and coding-agent telemetry to Sazabi.", + "author": { + "name": "Paradev" + }, + "category": "development", + "source": "./plugins/sazabi", + "version": "1.0.0" } ], "featured": ["github", "linear"], diff --git a/apps/desktop/src/renderer/components/PluginIcon/PluginIcon.tsx b/apps/desktop/src/renderer/components/PluginIcon/PluginIcon.tsx index 05d2e0cc122..aa13da8cef0 100644 --- a/apps/desktop/src/renderer/components/PluginIcon/PluginIcon.tsx +++ b/apps/desktop/src/renderer/components/PluginIcon/PluginIcon.tsx @@ -32,6 +32,7 @@ import ynabIconUrl from "renderer/assets/icons/ynab-icon.png"; import { AutumnIcon } from "./components/AutumnIcon"; import { PeecIcon } from "./components/PeecIcon"; import { ResendIcon } from "./components/ResendIcon"; +import { SazabiIcon } from "./components/SazabiIcon"; import { SuperhumanIcon } from "./components/SuperhumanIcon"; /** @@ -68,6 +69,7 @@ const PLUGIN_ICONS: Record = { sentry: { icon: SiSentry }, posthog: { icon: SiPosthog }, resend: { icon: ResendIcon }, + sazabi: { icon: SazabiIcon, color: "#FF0027" }, linear: { icon: SiLinear }, supabase: { icon: SiSupabase, color: "#3ECF8E" }, context7: { icon: LuBookOpen }, diff --git a/apps/desktop/src/renderer/components/PluginIcon/components/SazabiIcon/SazabiIcon.ts b/apps/desktop/src/renderer/components/PluginIcon/components/SazabiIcon/SazabiIcon.ts new file mode 100644 index 00000000000..0f4ff344327 --- /dev/null +++ b/apps/desktop/src/renderer/components/PluginIcon/components/SazabiIcon/SazabiIcon.ts @@ -0,0 +1,22 @@ +import { GenIcon } from "react-icons"; + +export const SazabiIcon = GenIcon({ + tag: "svg", + attr: { viewBox: "145 190 740 650", fill: "currentColor" }, + child: [ + { + tag: "path", + attr: { + d: "M255.246 552.975H479.158L398.608 422.361C386.206 402.145 400.875 376.285 424.88 376.285H773.751C784.553 376.285 794.421 370.771 800.022 361.714L874.437 241.076C886.84 220.86 872.17 195 848.166 195H491.294C480.491 195 470.623 200.513 465.022 209.571L374.07 356.988C368.469 366.046 358.467 371.559 347.798 371.559H180.831C156.826 371.559 142.023 397.419 154.559 417.635L228.974 538.272C234.575 547.33 244.577 552.844 255.246 552.844V552.975Z", + }, + child: [], + }, + { + tag: "path", + attr: { + d: "M773.754 478.157H549.842L630.392 608.771C642.794 628.986 628.125 654.847 604.12 654.847H255.25C244.447 654.847 234.579 660.36 228.978 669.418L154.563 789.924C142.16 810.14 156.83 836 180.835 836H537.706C548.509 836 558.377 830.487 563.978 821.429L654.93 674.012C660.531 664.955 670.533 659.441 681.202 659.441H848.169C872.174 659.441 886.977 633.581 874.441 613.365L800.026 492.728C794.425 483.67 784.423 478.157 773.754 478.157Z", + }, + child: [], + }, + ], +}); diff --git a/apps/desktop/src/renderer/components/PluginIcon/components/SazabiIcon/index.ts b/apps/desktop/src/renderer/components/PluginIcon/components/SazabiIcon/index.ts new file mode 100644 index 00000000000..a3f52c4421f --- /dev/null +++ b/apps/desktop/src/renderer/components/PluginIcon/components/SazabiIcon/index.ts @@ -0,0 +1 @@ +export { SazabiIcon } from "./SazabiIcon"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatSession/utils/linkUnfurl/linkUnfurl.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatSession/utils/linkUnfurl/linkUnfurl.ts index 32e354dbacc..b209fe6417f 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatSession/utils/linkUnfurl/linkUnfurl.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatSession/utils/linkUnfurl/linkUnfurl.ts @@ -33,6 +33,7 @@ const SERVICE_HOSTS: [RegExp, string][] = [ [/(^|\.)superhuman\.com$/, "superhuman"], [/(^|\.)circleback\.ai$/, "circleback"], [/(^|\.)peec\.ai$/, "peec"], + [/(^|\.)sazabi\.com$/, "sazabi"], [/(^|\.)supabase\.(com|co)$/, "supabase"], ]; diff --git a/apps/docs/content/docs/plugins/available/index.mdx b/apps/docs/content/docs/plugins/available/index.mdx index 34786a7bac6..242bb3e17f7 100644 --- a/apps/docs/content/docs/plugins/available/index.mdx +++ b/apps/docs/content/docs/plugins/available/index.mdx @@ -10,6 +10,7 @@ Install these from **Plugins** in the sidebar. Each one adds MCP tools to [suppo | [GitHub](/plugins/available/github) | Developer tools | Your GitHub account | | [Sentry](/plugins/available/sentry) | Developer tools | Your Sentry organization | | [Resend](/plugins/available/resend) | Developer tools | Your Resend team | +| [Sazabi](/plugins/available/sazabi) | Developer tools | Your Sazabi organization | | [Stripe](/plugins/available/stripe) | Data & APIs | Your Stripe account | | [Peec AI](/plugins/available/peec) | Data & APIs | Your Peec AI account | | [Linear](/plugins/available/linear) | Productivity | Your Linear workspace | diff --git a/apps/docs/content/docs/plugins/available/meta.json b/apps/docs/content/docs/plugins/available/meta.json index d79f032cf75..54fe06243e9 100644 --- a/apps/docs/content/docs/plugins/available/meta.json +++ b/apps/docs/content/docs/plugins/available/meta.json @@ -7,6 +7,7 @@ "stripe", "peec", "resend", + "sazabi", "linear", "notion", "granola", diff --git a/apps/docs/content/docs/plugins/available/sazabi.mdx b/apps/docs/content/docs/plugins/available/sazabi.mdx new file mode 100644 index 00000000000..1e378405993 --- /dev/null +++ b/apps/docs/content/docs/plugins/available/sazabi.mdx @@ -0,0 +1,34 @@ +--- +title: Sazabi +description: Investigate production from your agent with Sazabi's logs, traces and issues, and check a deploy before you call it done +--- + +Investigate production from your agent. Ask Sazabi questions about your logs, traces and metrics in plain language, work through the issues Sazabi opened on its own, check that a deploy did what it was meant to, and get your app's and your coding agents' telemetry into Sazabi. + +## Skills + +| Skill | What it does | +|:------|:-------------| +| `investigate-production` | Starts from known issues and past investigations, asks the logs precise questions, and reports what fails, why, and who it hits before it changes code | +| `verify-a-deploy` | Compares the error or behavior a change targets before and after the deploy, and checks for new errors, before it calls the task done | +| `triage-issues` | Ranks the issues Sazabi opened by impact, finds each one's cause in the repository, and proposes one fix per issue, leaving issues open until the fix ships | +| `instrument-with-sazabi` | Adds OpenTelemetry to an app, sends logs and traces to a Sazabi intake URL kept in configuration, and confirms the first records arrive | +| `send-agent-telemetry` | Streams Claude Code or Codex session telemetry (tool calls, model requests, tokens, cost) to Sazabi, with prompt export off unless you opt in | +| `add-logging` | Adds structured log lines to the code path a change touches, matching the repository's logger and leaving out secrets | +| `use-project-memory` | Reads Sazabi project memory (runbooks, past incidents) before investigating, and writes back verified findings | +| `build-a-dashboard` | Turns a recurring question into a saved Sazabi dashboard, validates it, and checks every panel returns data | + +Skills are named `sazabi-`. In Claude Code they appear as `superset:sazabi-`. + +## MCP Server + +Tools come from Sazabi's own MCP server at `https://mcp.sazabi.com/mcp`. Paradev proxies each call and adds your credentials. The server lists two tools, `search` and `execute`: the agent searches for an operation (log questions, issues, threads, project memory) and then runs it. See [Sazabi's docs](https://docs.sazabi.com/get-started/coding-agents) for what it can do. + +## Authentication + +| Method | Details | +|:-------|:--------| +| OAuth 2.0 | You sign in to Sazabi in your browser. Paradev registers itself with Sazabi when you connect, so there is no app to set up first. | +| Secret key | Paste a key from Sazabi's **Settings → Secret keys** page. Use it when you cannot sign in through a browser. A key scoped to one project limits the agent to that project. Connecting another key for the same Sazabi organization replaces your existing connection; a key connected by another Paradev user is a separate connection. To reach several projects, use OAuth or an organization-wide key. | + +Connect from the plugin's page in **Plugins**. Paradev stores the credentials on its side and adds them to each call, so they never go into your agent's configuration. diff --git a/apps/marketing/src/app/[lang]/plugins/components/PluginIcon/components/SazabiIcon/SazabiIcon.ts b/apps/marketing/src/app/[lang]/plugins/components/PluginIcon/components/SazabiIcon/SazabiIcon.ts new file mode 100644 index 00000000000..0f4ff344327 --- /dev/null +++ b/apps/marketing/src/app/[lang]/plugins/components/PluginIcon/components/SazabiIcon/SazabiIcon.ts @@ -0,0 +1,22 @@ +import { GenIcon } from "react-icons"; + +export const SazabiIcon = GenIcon({ + tag: "svg", + attr: { viewBox: "145 190 740 650", fill: "currentColor" }, + child: [ + { + tag: "path", + attr: { + d: "M255.246 552.975H479.158L398.608 422.361C386.206 402.145 400.875 376.285 424.88 376.285H773.751C784.553 376.285 794.421 370.771 800.022 361.714L874.437 241.076C886.84 220.86 872.17 195 848.166 195H491.294C480.491 195 470.623 200.513 465.022 209.571L374.07 356.988C368.469 366.046 358.467 371.559 347.798 371.559H180.831C156.826 371.559 142.023 397.419 154.559 417.635L228.974 538.272C234.575 547.33 244.577 552.844 255.246 552.844V552.975Z", + }, + child: [], + }, + { + tag: "path", + attr: { + d: "M773.754 478.157H549.842L630.392 608.771C642.794 628.986 628.125 654.847 604.12 654.847H255.25C244.447 654.847 234.579 660.36 228.978 669.418L154.563 789.924C142.16 810.14 156.83 836 180.835 836H537.706C548.509 836 558.377 830.487 563.978 821.429L654.93 674.012C660.531 664.955 670.533 659.441 681.202 659.441H848.169C872.174 659.441 886.977 633.581 874.441 613.365L800.026 492.728C794.425 483.67 784.423 478.157 773.754 478.157Z", + }, + child: [], + }, + ], +}); diff --git a/apps/marketing/src/app/[lang]/plugins/components/PluginIcon/components/SazabiIcon/index.ts b/apps/marketing/src/app/[lang]/plugins/components/PluginIcon/components/SazabiIcon/index.ts new file mode 100644 index 00000000000..a3f52c4421f --- /dev/null +++ b/apps/marketing/src/app/[lang]/plugins/components/PluginIcon/components/SazabiIcon/index.ts @@ -0,0 +1 @@ +export { SazabiIcon } from "./SazabiIcon"; diff --git a/apps/marketing/src/app/[lang]/plugins/components/PluginIcon/constants.ts b/apps/marketing/src/app/[lang]/plugins/components/PluginIcon/constants.ts index c7dcb58476b..7ccfb985ba6 100644 --- a/apps/marketing/src/app/[lang]/plugins/components/PluginIcon/constants.ts +++ b/apps/marketing/src/app/[lang]/plugins/components/PluginIcon/constants.ts @@ -10,6 +10,7 @@ import { import { AutumnIcon } from "./components/AutumnIcon"; import { PeecIcon } from "./components/PeecIcon"; import { ResendIcon } from "./components/ResendIcon"; +import { SazabiIcon } from "./components/SazabiIcon"; import { SuperhumanIcon } from "./components/SuperhumanIcon"; export const IMAGE_ICONS: Record = { @@ -32,6 +33,7 @@ export const GLYPH_ICONS: Record = { peec: { icon: PeecIcon }, posthog: { icon: SiPosthog }, resend: { icon: ResendIcon }, + sazabi: { icon: SazabiIcon, color: "#FF0027" }, sentry: { icon: SiSentry }, superhuman: { icon: SuperhumanIcon }, vercel: { icon: SiVercel }, diff --git a/apps/marketing/src/app/[lang]/plugins/constants.ts b/apps/marketing/src/app/[lang]/plugins/constants.ts index 6fea20206bb..22e87cdf387 100644 --- a/apps/marketing/src/app/[lang]/plugins/constants.ts +++ b/apps/marketing/src/app/[lang]/plugins/constants.ts @@ -111,6 +111,11 @@ export const CATALOG: { displayName: "Resend", description: msg({ message: "Send email and debug delivery" }), }, + { + name: "sazabi", + displayName: "Sazabi", + description: msg({ message: "Investigate production logs and issues" }), + }, ], }, { diff --git a/packages/i18n/locales/cs/messages.po b/packages/i18n/locales/cs/messages.po index 1881f0b4c74..6c813358615 100644 --- a/packages/i18n/locales/cs/messages.po +++ b/packages/i18n/locales/cs/messages.po @@ -9966,6 +9966,9 @@ msgstr "Neplatný požadavek" msgid "Invalid state parameter. Please try again." msgstr "Neplatný parametr state. Zkuste to prosím znovu." +msgid "Investigate production logs and issues" +msgstr "Prošetřujte produkční logy a problémy" + msgid "Invitation canceled" msgstr "Pozvánka zrušena" diff --git a/packages/i18n/locales/de/messages.po b/packages/i18n/locales/de/messages.po index a807e402f0d..0b9d1c27e0a 100644 --- a/packages/i18n/locales/de/messages.po +++ b/packages/i18n/locales/de/messages.po @@ -9966,6 +9966,9 @@ msgstr "Ungültige Anfrage" msgid "Invalid state parameter. Please try again." msgstr "Ungültiger state-Parameter. Bitte versuche es erneut." +msgid "Investigate production logs and issues" +msgstr "Produktionslogs und Issues untersuchen" + msgid "Invitation canceled" msgstr "Einladung zurückgezogen" diff --git a/packages/i18n/locales/en/messages.po b/packages/i18n/locales/en/messages.po index 712f7a7602d..11de7b454d6 100644 --- a/packages/i18n/locales/en/messages.po +++ b/packages/i18n/locales/en/messages.po @@ -9966,6 +9966,9 @@ msgstr "Invalid Request" msgid "Invalid state parameter. Please try again." msgstr "Invalid state parameter. Please try again." +msgid "Investigate production logs and issues" +msgstr "Investigate production logs and issues" + msgid "Invitation canceled" msgstr "Invitation canceled" diff --git a/packages/i18n/locales/es/messages.po b/packages/i18n/locales/es/messages.po index cf038bcdd92..1ba4efedfbc 100644 --- a/packages/i18n/locales/es/messages.po +++ b/packages/i18n/locales/es/messages.po @@ -9966,6 +9966,9 @@ msgstr "Solicitud no válida" msgid "Invalid state parameter. Please try again." msgstr "Parámetro de estado no válido. Inténtalo de nuevo." +msgid "Investigate production logs and issues" +msgstr "Investiga logs e incidencias de producción" + msgid "Invitation canceled" msgstr "Invitación cancelada" diff --git a/packages/i18n/locales/fr/messages.po b/packages/i18n/locales/fr/messages.po index f1526c63765..b9c588aa74c 100644 --- a/packages/i18n/locales/fr/messages.po +++ b/packages/i18n/locales/fr/messages.po @@ -9966,6 +9966,9 @@ msgstr "Requête invalide" msgid "Invalid state parameter. Please try again." msgstr "Paramètre state invalide. Veuillez réessayer." +msgid "Investigate production logs and issues" +msgstr "Analysez les logs et incidents de production" + msgid "Invitation canceled" msgstr "Invitation annulée" diff --git a/packages/i18n/locales/id/messages.po b/packages/i18n/locales/id/messages.po index 43af48d7e13..0a1916e016d 100644 --- a/packages/i18n/locales/id/messages.po +++ b/packages/i18n/locales/id/messages.po @@ -9966,6 +9966,9 @@ msgstr "Permintaan Tidak Valid" msgid "Invalid state parameter. Please try again." msgstr "Parameter state tidak valid. Silakan coba lagi." +msgid "Investigate production logs and issues" +msgstr "Selidiki log dan masalah produksi" + msgid "Invitation canceled" msgstr "Undangan dibatalkan" diff --git a/packages/i18n/locales/it/messages.po b/packages/i18n/locales/it/messages.po index 3528a0834c9..4b10d55c847 100644 --- a/packages/i18n/locales/it/messages.po +++ b/packages/i18n/locales/it/messages.po @@ -9966,6 +9966,9 @@ msgstr "Richiesta non valida" msgid "Invalid state parameter. Please try again." msgstr "Parametro di stato non valido. Riprova." +msgid "Investigate production logs and issues" +msgstr "Indaga su log e problemi in produzione" + msgid "Invitation canceled" msgstr "Invito annullato" diff --git a/packages/i18n/locales/ja/messages.po b/packages/i18n/locales/ja/messages.po index 7342ae62240..bc730f5ef45 100644 --- a/packages/i18n/locales/ja/messages.po +++ b/packages/i18n/locales/ja/messages.po @@ -9966,6 +9966,9 @@ msgstr "無効なリクエスト" msgid "Invalid state parameter. Please try again." msgstr "stateパラメーターが無効です。もう一度お試しください。" +msgid "Investigate production logs and issues" +msgstr "本番環境のログと問題を調査" + msgid "Invitation canceled" msgstr "招待をキャンセルしました" diff --git a/packages/i18n/locales/ko/messages.po b/packages/i18n/locales/ko/messages.po index ce1d846f9c2..3777907b966 100644 --- a/packages/i18n/locales/ko/messages.po +++ b/packages/i18n/locales/ko/messages.po @@ -9966,6 +9966,9 @@ msgstr "잘못된 요청" msgid "Invalid state parameter. Please try again." msgstr "state 매개변수가 올바르지 않습니다. 다시 시도해 주세요." +msgid "Investigate production logs and issues" +msgstr "프로덕션 로그와 이슈 조사" + msgid "Invitation canceled" msgstr "초대를 취소했습니다" diff --git a/packages/i18n/locales/nl/messages.po b/packages/i18n/locales/nl/messages.po index 694042e79fd..b99317fb672 100644 --- a/packages/i18n/locales/nl/messages.po +++ b/packages/i18n/locales/nl/messages.po @@ -9966,6 +9966,9 @@ msgstr "Ongeldig verzoek" msgid "Invalid state parameter. Please try again." msgstr "Ongeldige state-parameter. Probeer het opnieuw." +msgid "Investigate production logs and issues" +msgstr "Onderzoek productielogs en issues" + msgid "Invitation canceled" msgstr "Uitnodiging geannuleerd" diff --git a/packages/i18n/locales/pl/messages.po b/packages/i18n/locales/pl/messages.po index 576277091c3..fe4a545e621 100644 --- a/packages/i18n/locales/pl/messages.po +++ b/packages/i18n/locales/pl/messages.po @@ -9966,6 +9966,9 @@ msgstr "Nieprawidłowe żądanie" msgid "Invalid state parameter. Please try again." msgstr "Nieprawidłowy parametr state. Spróbuj ponownie." +msgid "Investigate production logs and issues" +msgstr "Badaj logi i problemy produkcyjne" + msgid "Invitation canceled" msgstr "Zaproszenie anulowane" diff --git a/packages/i18n/locales/pt-BR/messages.po b/packages/i18n/locales/pt-BR/messages.po index 23deb99f29b..7727fbfb5e9 100644 --- a/packages/i18n/locales/pt-BR/messages.po +++ b/packages/i18n/locales/pt-BR/messages.po @@ -9966,6 +9966,9 @@ msgstr "Requisição inválida" msgid "Invalid state parameter. Please try again." msgstr "Parâmetro de estado inválido. Tente novamente." +msgid "Investigate production logs and issues" +msgstr "Investigue logs e problemas de produção" + msgid "Invitation canceled" msgstr "Convite cancelado" diff --git a/packages/i18n/locales/ru/messages.po b/packages/i18n/locales/ru/messages.po index 4e5b469abb7..3721a6d0306 100644 --- a/packages/i18n/locales/ru/messages.po +++ b/packages/i18n/locales/ru/messages.po @@ -9966,6 +9966,9 @@ msgstr "Некорректный запрос" msgid "Invalid state parameter. Please try again." msgstr "Некорректный параметр state. Попробуйте ещё раз." +msgid "Investigate production logs and issues" +msgstr "Исследуйте логи и проблемы в продакшене" + msgid "Invitation canceled" msgstr "Приглашение отменено" diff --git a/packages/i18n/locales/tr/messages.po b/packages/i18n/locales/tr/messages.po index ae3331cf194..0856011e0a1 100644 --- a/packages/i18n/locales/tr/messages.po +++ b/packages/i18n/locales/tr/messages.po @@ -9966,6 +9966,9 @@ msgstr "Geçersiz istek" msgid "Invalid state parameter. Please try again." msgstr "Geçersiz state parametresi. Lütfen yeniden deneyin." +msgid "Investigate production logs and issues" +msgstr "Canlı ortam loglarını ve sorunlarını inceleyin" + msgid "Invitation canceled" msgstr "Davet iptal edildi" diff --git a/packages/i18n/locales/vi/messages.po b/packages/i18n/locales/vi/messages.po index f397ffedccd..4e3bec077bb 100644 --- a/packages/i18n/locales/vi/messages.po +++ b/packages/i18n/locales/vi/messages.po @@ -9966,6 +9966,9 @@ msgstr "Yêu cầu không hợp lệ" msgid "Invalid state parameter. Please try again." msgstr "Tham số state không hợp lệ. Vui lòng thử lại." +msgid "Investigate production logs and issues" +msgstr "Điều tra log và sự cố production" + msgid "Invitation canceled" msgstr "Đã hủy lời mời" diff --git a/packages/i18n/locales/zh-CN/messages.po b/packages/i18n/locales/zh-CN/messages.po index 051477dfeb0..6c2563d4b42 100644 --- a/packages/i18n/locales/zh-CN/messages.po +++ b/packages/i18n/locales/zh-CN/messages.po @@ -9966,6 +9966,9 @@ msgstr "无效请求" msgid "Invalid state parameter. Please try again." msgstr "state参数无效。请重试。" +msgid "Investigate production logs and issues" +msgstr "调查生产环境日志和问题" + msgid "Invitation canceled" msgstr "邀请已取消" diff --git a/packages/i18n/locales/zh-TW/messages.po b/packages/i18n/locales/zh-TW/messages.po index 74017ecd659..dd11673b01f 100644 --- a/packages/i18n/locales/zh-TW/messages.po +++ b/packages/i18n/locales/zh-TW/messages.po @@ -9966,6 +9966,9 @@ msgstr "無效請求" msgid "Invalid state parameter. Please try again." msgstr "state引數無效。請重試。" +msgid "Investigate production logs and issues" +msgstr "調查正式環境日誌與問題" + msgid "Invitation canceled" msgstr "邀請已取消" diff --git a/packages/shared/src/connectors/connectors.json b/packages/shared/src/connectors/connectors.json index b0cc5dd7e9d..443d03f6bc3 100644 --- a/packages/shared/src/connectors/connectors.json +++ b/packages/shared/src/connectors/connectors.json @@ -945,6 +945,75 @@ } ] }, + "sazabi_mcp": { + "displayName": "Sazabi", + "icon": "sazabi", + "category": "Developer tools", + "scope": "user", + "methods": [ + { + "type": "oauth2", + "label": "OAuth 2.0", + "client": "dynamic", + "authorization_url": "https://mcp.sazabi.com/mcp", + "scopes": ["openid", "profile", "email", "offline_access", "mcp"], + "identity": { + "url": "https://api.platform.sazabi.com/api/auth/oauth2/userinfo", + "method": "GET", + "headers": { + "Authorization": "Bearer ${config.access_token}" + }, + "account": { + "id": "$.sub", + "label": "$.email" + }, + "user": { + "id": "$.sub", + "label": "$.email" + } + }, + "bind": { + "headers": { + "Authorization": "Bearer ${config.access_token}" + } + } + }, + { + "type": "api_key", + "label": "Secret key", + "credential_input": "api_key", + "inputs": [ + { + "name": "api_key", + "label": "Sazabi secret key", + "description": "Sazabi \u2192 Settings \u2192 Secret keys \u2192 Create. A key scoped to one project limits the agent to that project.", + "required": true, + "secret": true + } + ], + "identity": { + "url": "https://api.sazabi.com/v1/me", + "method": "GET", + "headers": { + "Authorization": "Bearer ${config.access_token}" + }, + "account": { + "id": "$.organization.id", + "label": "$.organization.name" + }, + "user": { + "id": "$.organization.id", + "label": "$.keyName" + } + }, + "bind": { + "headers": { + "X-Sazabi-Secret-Key": "${config.access_token}" + } + } + } + ] + }, "vercel_mcp": { "displayName": "Vercel", "icon": "vercel", diff --git a/packages/shared/src/plugins/index.ts b/packages/shared/src/plugins/index.ts index bde58e03294..a18879637e7 100644 --- a/packages/shared/src/plugins/index.ts +++ b/packages/shared/src/plugins/index.ts @@ -512,6 +512,15 @@ export const PLUGIN_CATALOG: readonly PluginCatalogEntry[] = [ peec: { type: "http", url: "https://api.peec.ai/mcp" }, }, }, + { + name: "sazabi", + version: "1.0.0", + description: "Investigate production logs and issues", + interface: { displayName: "Sazabi", category: "Developer tools" }, + mcpServers: { + sazabi: { type: "http", url: "https://mcp.sazabi.com/mcp" }, + }, + }, { name: "superhuman", version: "1.0.0", diff --git a/packages/shared/src/plugins/manifests.generated.ts b/packages/shared/src/plugins/manifests.generated.ts index 2f600c9b704..4d2f29358af 100644 --- a/packages/shared/src/plugins/manifests.generated.ts +++ b/packages/shared/src/plugins/manifests.generated.ts @@ -819,6 +819,77 @@ export const FIRST_PARTY_MANIFESTS = { } ] } as const, + "sazabi": { + "$schema": "https://superset.sh/schemas/plugin/1.0.0.json", + "name": "sazabi", + "version": "1.0.0", + "description": "Investigate production with Sazabi: ask questions about your logs and traces, triage issues, check deploys, build dashboards, and send app and coding-agent telemetry to Sazabi.", + "author": { + "name": "Paradev", + "url": "https://paradev.com" + }, + "homepage": "https://docs.paradev.com", + "repository": "https://github.com/paradevai/paradev", + "license": "MIT", + "keywords": [ + "sazabi", + "logs", + "observability", + "opentelemetry", + "debugging", + "incidents" + ], + "extensions": { + "superset": { + "interface": { + "displayName": "Sazabi", + "category": "Developer tools", + "icon": "sazabi" + }, + "connector": { + "slug": "sazabi_mcp" + }, + "mcp": { + "type": "streamable-http", + "url": "https://mcp.sazabi.com/mcp" + } + } + }, + "skills": [ + { + "name": "add-logging", + "description": "Before shipping a change, add structured log lines to the code path it touches so the next investigation has evidence — one line per decision and failure, with stable event names and the IDs needed to join requests, at the right level and without secrets. Use when writing or reviewing a feature or fix, when a bug was hard to diagnose because the logs were silent, or when the user asks to improve logging or observability in code." + }, + { + "name": "build-a-dashboard", + "description": "Turn a question the user keeps asking about production (error rates, latency, signups, cost) into a saved Sazabi dashboard built from live log queries — check the fields exist, validate before saving, and confirm every panel returns data. Use when the user asks for a dashboard, a recurring view or report, or says they keep checking the same numbers." + }, + { + "name": "instrument-with-sazabi", + "description": "Get an application's logs and traces into Sazabi with OpenTelemetry — pick the SDK for the runtime, export over OTLP/HTTP to a Sazabi intake URL kept in an environment variable, and prove data arrives before calling it done. Use when the user wants to connect an app or service to Sazabi, add OpenTelemetry, set up logging or tracing, or asks why Sazabi shows no data for a service." + }, + { + "name": "investigate-production", + "description": "Find out what is actually happening in production from Sazabi's logs, traces and issues before touching code — ask the logs a precise question, read the lines behind the answer, and separate the symptom from its cause. Use when the user reports a production error, a slow or failing endpoint, a customer complaint, an alert, or asks what is broken, what changed, or why something stopped working." + }, + { + "name": "send-agent-telemetry", + "description": "Stream coding-agent session telemetry (prompts, tool calls, model requests, tokens and cost) from Claude Code or Codex into Sazabi over OpenTelemetry, with privacy settings chosen on purpose. Use when the user wants observability for their coding agents or agent fleet, to track agent token usage or cost, to audit what agents ran, or to send agent telemetry to Sazabi." + }, + { + "name": "triage-issues", + "description": "Work through the issues Sazabi opened on its own — rank them by severity and blast radius, find each one's cause in this repository with log evidence, and propose or draft one fix per issue, leaving issues open until a fix ships. Use when the user asks what is broken, what to fix first, to clean up or triage Sazabi issues, or for an on-call or morning review." + }, + { + "name": "use-project-memory", + "description": "Read Sazabi project memory (how the system is laid out, what healthy looks like, runbooks, past incidents) before investigating or changing production behavior, and write back durable findings once they are verified. Use at the start of an investigation or on-call task, when the user mentions a runbook or a past incident, or after resolving something worth remembering." + }, + { + "name": "verify-a-deploy", + "description": "Check in Sazabi that a change you shipped works in production before you call the task done — compare error rates and new kinds of log lines before and after the deploy, confirm the fixed error stopped, and catch regressions early. Use after merging or deploying a fix or feature, when the user asks whether a deploy is healthy, did the fix work, or is it safe to move on." + } + ] + } as const, } as const; export type FirstPartyPluginName = keyof typeof FIRST_PARTY_MANIFESTS; diff --git a/packages/trpc/src/lib/connectors/connectors.test.ts b/packages/trpc/src/lib/connectors/connectors.test.ts index ebba7510413..caec4e4569b 100644 --- a/packages/trpc/src/lib/connectors/connectors.test.ts +++ b/packages/trpc/src/lib/connectors/connectors.test.ts @@ -420,6 +420,51 @@ describe("probeIdentity", () => { expect(identity.user).toEqual({ id: "user_01", label: "h@tegon.ai" }); }); + test("sazabi_mcp asks Sazabi's userinfo endpoint after OAuth", async () => { + const calls = respond({ sub: "usr_01", email: "h@tegon.ai" }); + + const identity = await probeIdentity( + "sazabi_mcp", + connectorMethod(requireConnector("sazabi_mcp")), + "oauth-test", + ); + + expect(calls).toEqual([ + { + url: "https://api.platform.sazabi.com/api/auth/oauth2/userinfo", + method: "GET", + auth: "Bearer oauth-test", + }, + ]); + expect(identity.account).toEqual({ id: "usr_01", label: "h@tegon.ai" }); + expect(identity.user).toEqual({ id: "usr_01", label: "h@tegon.ai" }); + }); + + test("sazabi_mcp reads a secret key's organization from /v1/me", async () => { + const calls = respond({ + credentialType: "secret", + organization: { id: "org_01", name: "Tegon", slug: "tegon" }, + keyName: "paradev", + projectId: null, + }); + + const identity = await probeIdentity( + "sazabi_mcp", + connectorMethod(requireConnector("sazabi_mcp"), "api_key"), + "sk-test", + ); + + expect(calls).toEqual([ + { + url: "https://api.sazabi.com/v1/me", + method: "GET", + auth: "Bearer sk-test", + }, + ]); + expect(identity.account).toEqual({ id: "org_01", label: "Tegon" }); + expect(identity.user).toEqual({ id: "org_01", label: "paradev" }); + }); + test("stripe survives an account with no name", async () => { globalThis.fetch = (async (_url: string, init: RequestInit) => { const body = init.body diff --git a/plugins/sazabi/plugin.json b/plugins/sazabi/plugin.json new file mode 100644 index 00000000000..ca61312e8f4 --- /dev/null +++ b/plugins/sazabi/plugin.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://superset.sh/schemas/plugin/1.0.0.json", + "name": "sazabi", + "version": "1.0.0", + "description": "Investigate production with Sazabi: ask questions about your logs and traces, triage issues, check deploys, build dashboards, and send app and coding-agent telemetry to Sazabi.", + "author": { + "name": "Paradev", + "url": "https://paradev.com" + }, + "homepage": "https://docs.paradev.com", + "repository": "https://github.com/paradevai/paradev", + "license": "MIT", + "keywords": [ + "sazabi", + "logs", + "observability", + "opentelemetry", + "debugging", + "incidents" + ], + "extensions": { + "superset": { + "interface": { + "displayName": "Sazabi", + "category": "Developer tools", + "icon": "sazabi" + }, + "connector": { + "slug": "sazabi_mcp" + }, + "mcp": { + "type": "streamable-http", + "url": "https://mcp.sazabi.com/mcp" + } + } + } +} diff --git a/plugins/sazabi/skills/add-logging/SKILL.md b/plugins/sazabi/skills/add-logging/SKILL.md new file mode 100644 index 00000000000..3720c63abec --- /dev/null +++ b/plugins/sazabi/skills/add-logging/SKILL.md @@ -0,0 +1,66 @@ +--- +name: add-logging +description: Before shipping a change, add structured log lines to the code path it touches so the next investigation has evidence — one line per decision and failure, with stable event names and the IDs needed to join requests, at the right level and without secrets. Use when writing or reviewing a feature or fix, when a bug was hard to diagnose because the logs were silent, or when the user asks to improve logging or observability in code. +argument-hint: the change, file, or code path to cover +allowed-tools: mcp__sazabi__search, mcp__sazabi__execute +--- + +# Log what you will need to ask about + +Every investigation in Sazabi is limited by what the code logged. The cheapest +time to add the right lines is while the change is fresh. The job is a small +number of structured lines that answer "what happened to this request, and +why". + +## 0. How the tools work + +The Sazabi MCP server lists only two tools, `search` and `execute`. Browse +Sazabi's operations with `search` and `namespace: "sazabi"` (an empty query +without a namespace lists connected connectors, not Sazabi operations). Before +calling one, repeat `search` with `namespace: "sazabi"`, the hit's full name as +the query, and `includeInputSchema: true` to load its arguments. Then run it +with `execute` as `{ name, arguments }`, the name copied from the hit (for +example `sazabi/logs.ask`). + +## 1. Match what is already there + +Read how this repository logs: which logger, which field names, how request and +trace IDs are attached. Ask `sazabi/logs.ask` for a sample of this service's +recent lines to see the real shape. New lines must look like their neighbors; +an investigation that has to join two naming schemes is slower. + +## 2. Pick the lines + +On the changed path, log: + +- **Failures** with the error, the input that caused it (IDs, not payloads), + and what the code did next (retry, fallback, give up). +- **Decisions** that change behavior: a feature flag, a fallback, a skipped + step, a limit hit. +- **Boundaries**: calls to other services or queues, with target, outcome and + duration. + +Skip lines that restate the obvious ("entering function"). + +## 3. Shape each line + +- A stable event name (`checkout.payment_declined`), so a question can count + it. +- Structured fields, not values glued into the message string. +- IDs that join the story: request or trace ID, user or tenant ID, order ID. +- The right level: ERROR for failures someone must act on, WARN for degraded + but handled, INFO for decisions, DEBUG for detail that is off in production. +- Never secrets, tokens, passwords or full personal data. Redact or omit. + +## 4. Check it + +Run the path locally or in tests and read the lines it emits. After deploy, ask +Sazabi for the new event name to confirm the lines arrive with their fields. + +## Anti-patterns + +- **Logging payloads.** They leak data and cost money; log IDs and sizes. +- **ERROR for expected outcomes.** A declined card is INFO; an ERROR should + mean a broken system. +- **Many lines per request in a hot loop.** Log the summary, not every + iteration. diff --git a/plugins/sazabi/skills/build-a-dashboard/SKILL.md b/plugins/sazabi/skills/build-a-dashboard/SKILL.md new file mode 100644 index 00000000000..688990df6be --- /dev/null +++ b/plugins/sazabi/skills/build-a-dashboard/SKILL.md @@ -0,0 +1,67 @@ +--- +name: build-a-dashboard +description: Turn a question the user keeps asking about production (error rates, latency, signups, cost) into a saved Sazabi dashboard built from live log queries — check the fields exist, validate before saving, and confirm every panel returns data. Use when the user asks for a dashboard, a recurring view or report, or says they keep checking the same numbers. +argument-hint: what the dashboard should show, and for which service or project +allowed-tools: mcp__sazabi__search, mcp__sazabi__execute +--- + +# A dashboard is a saved question + +A Sazabi dashboard is a page of live panels, each running a log query when the +dashboard opens. Build one for questions that come back every day or every +deploy; answer one-off questions with `sazabi/logs.ask` instead. + +## 0. How the tools work + +The Sazabi MCP server lists only two tools, `search` and `execute`. Browse +Sazabi's operations with `search` and `namespace: "sazabi"` (an empty query +without a namespace lists connected connectors, not Sazabi operations). Before +calling one, repeat `search` with `namespace: "sazabi"`, the hit's full name as +the query, and `includeInputSchema: true` to load its arguments. Then run it +with `execute` as `{ name, arguments }`, the name copied from the hit (for +example `sazabi/logs.ask`). +Dashboard operations: `sazabi/dashboards.list`, `.get`, `.validate`, `.create`, `.update`, `.render`, `.delete`. + +## 1. Check what exists + +Run `sazabi/dashboards.list`. Every project has built-in dashboards (issues and +alerts, log health) that can't be edited but can be copied. If a close match +exists, propose editing or copying it rather than adding another. + +## 2. Find the real fields + +Pin down each panel as a question with a number: "error lines by service per +15 minutes", "p95 request duration by route". Use `sazabi/logs.ask` to confirm +the logs actually carry the fields each panel needs (service, route, duration, +event name). If a signal isn't in the logs, leave that panel out and say what +would provide it, for example the `add-logging` skill. Never invent a field. + +## 3. Write, validate, save + +A dashboard is one MDX document: a `DashboardMeta` block (title, description, +default time range) and a grid of panels, each with an inline log query. A +panel's query never names a time range; the dashboard supplies it. Copy the +document shape from `dashboards.validate` and from an existing dashboard via +`dashboards.get`. + +Run `sazabi/dashboards.validate` and fix every diagnostic (each names its line +and a repair hint), then `sazabi/dashboards.create`. Generate the dashboard id +yourself and reuse it if you retry. To change a dashboard, `dashboards.get` it, +edit the whole document, and `dashboards.update` with the edit base it returned. +If the update is rejected because someone saved a newer revision, re-read and +re-apply; don't overwrite. + +## 4. Prove it renders + +Run `sazabi/dashboards.render` over the default window and check every panel +returns ok, not empty or rejected. Fix or drop panels that don't. Reply with the +dashboard link and one line per panel. + +## Anti-patterns + +- **Twenty panels.** Five panels that answer the question beat twenty that + don't. +- **Deleting shared dashboards.** A delete removes it for the whole project and + can't be undone; ask first. +- **Shipping an empty panel.** A panel that renders "No data" on day one + teaches people to ignore the dashboard. diff --git a/plugins/sazabi/skills/instrument-with-sazabi/SKILL.md b/plugins/sazabi/skills/instrument-with-sazabi/SKILL.md new file mode 100644 index 00000000000..503d1b1a981 --- /dev/null +++ b/plugins/sazabi/skills/instrument-with-sazabi/SKILL.md @@ -0,0 +1,80 @@ +--- +name: instrument-with-sazabi +description: Get an application's logs and traces into Sazabi with OpenTelemetry — pick the SDK for the runtime, export over OTLP/HTTP to a Sazabi intake URL kept in an environment variable, and prove data arrives before calling it done. Use when the user wants to connect an app or service to Sazabi, add OpenTelemetry, set up logging or tracing, or asks why Sazabi shows no data for a service. +argument-hint: the service or app to instrument, and where it runs +allowed-tools: mcp__sazabi__search, mcp__sazabi__execute +--- + +# Data in, proven + +Sazabi can only answer questions about telemetry it receives. The job is to +make the app export logs and traces over OpenTelemetry to the user's Sazabi +project, with no secrets in the repo, and to show the first records arriving. + +## 0. How the tools work + +The Sazabi MCP server lists only two tools, `search` and `execute`. Browse +Sazabi's operations with `search` and `namespace: "sazabi"` (an empty query +without a namespace lists connected connectors, not Sazabi operations). Before +calling one, repeat `search` with `namespace: "sazabi"`, the hit's full name as +the query, and `includeInputSchema: true` to load its arguments. Then run it +with `execute` as `{ name, arguments }`, the name copied from the hit (for +example `sazabi/logs.ask`). + +## 1. Get an intake URL + +Run `sazabi/logSources.list` first; an OpenTelemetry source may already exist +for this project. If not, `sazabi/logSources.create` in manual mode for an +OpenTelemetry source returns the intake endpoint to point the sender at. The +intake URL embeds a write-only public key, so it needs no auth header, but it +still belongs in an environment variable or secret store, not in committed +code. Metrics go to the separate OpenTelemetry Metrics source, which has its +own intake path. + +## 2. Wire the SDK + +Use the official OpenTelemetry SDK for the runtime and initialize it before the +app handles requests: + +- **Node.js:** `@opentelemetry/sdk-node`, `@opentelemetry/auto-instrumentations-node`, + `@opentelemetry/exporter-trace-otlp-proto`, `@opentelemetry/exporter-logs-otlp-proto` + and `@opentelemetry/sdk-logs`. Auto-instrumentation exports traces but not + logs, so add a `BatchLogRecordProcessor` with an `OTLPLogExporter`, and + bridge the app's existing logger (pino, winston) instead of replacing it. +- **Python:** `opentelemetry-distro` and `opentelemetry-exporter-otlp-proto-http`, + then `opentelemetry-bootstrap -a install`, and enable the logging handler. +- **Other runtimes:** the standard OTLP/HTTP exporter for that SDK. + +Configure through the standard environment variables, set in the process that +runs the app (not only at build time or in CI): + +```bash +OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf +OTEL_EXPORTER_OTLP_ENDPOINT= +OTEL_SERVICE_NAME= +``` + +Set `OTEL_SERVICE_NAME` and `deployment.environment` so every record says +which service and environment it came from. Add the variable names to +`.env.example` or the deploy config, with the value left to the user. + +## 3. Prove it + +Run the app (or deploy it), make a request, then ask Sazabi with +`sazabi/logs.ask`: "how many records from service `` in the last 15 +minutes". Records usually show up within a minute or two. If none arrive, +check that the variables are set in the running process, that the URL has its +`https://` scheme, and the app's output for SDK initialization errors. + +## 4. Hand off + +Commit on a branch and open a pull request. Say which signals are flowing +(logs, traces), which service name to search for, and which variables the +user must set in each environment. + +## Anti-patterns + +- **Committing the intake URL.** It is a write key; keep it in configuration. +- **Traces only.** Most debugging starts from log lines; export both. +- **Calling it done before a record arrives.** Configuration that has never + delivered a record is a guess. diff --git a/plugins/sazabi/skills/investigate-production/SKILL.md b/plugins/sazabi/skills/investigate-production/SKILL.md new file mode 100644 index 00000000000..d5e1dfef746 --- /dev/null +++ b/plugins/sazabi/skills/investigate-production/SKILL.md @@ -0,0 +1,80 @@ +--- +name: investigate-production +description: Find out what is actually happening in production from Sazabi's logs, traces and issues before touching code — ask the logs a precise question, read the lines behind the answer, and separate the symptom from its cause. Use when the user reports a production error, a slow or failing endpoint, a customer complaint, an alert, or asks what is broken, what changed, or why something stopped working. +argument-hint: the symptom, endpoint, error message, or Sazabi link to investigate +allowed-tools: mcp__sazabi__search, mcp__sazabi__execute +--- + +# Investigate with the logs, not the guess + +Sazabi stores your logs, traces and metrics and answers questions about them. +The job is to turn "something is broken" into a claim about *what* fails, *why*, +and *who* it hits, backed by real log lines, before anyone edits code. + +## 0. How the tools work + +The Sazabi MCP server lists only two tools, `search` and `execute`. Browse +Sazabi's operations with `search` and `namespace: "sazabi"` (an empty query +without a namespace lists connected connectors, not Sazabi operations). Before +calling one, repeat `search` with `namespace: "sazabi"`, the hit's full name as +the query, and `includeInputSchema: true` to load its arguments. Then run it +with `execute` as `{ name, arguments }`, the name copied from the hit (for +example `sazabi/logs.ask`). + +Most operations work on one project. If the user did not name one and more +than one exists, ask which, or pick the one whose name matches this repository. + +## 1. Start from what is already known + +Run `sazabi/issues.search` for the symptom. Sazabi opens issues on its own when +it sees new or growing errors, so the bug may already have a title, a severity, +and a first-seen time. Then run `sazabi/search.threads` for the same words: a +teammate or the Sazabi agent may already have investigated it. + +Treat search hits as candidates. Match on the error and the code path, not on +title similarity. + +## 2. Ask the logs one precise question at a time + +`sazabi/logs.ask` takes a question in plain language and returns an `answer`, +an `explanation` of what it measured, the `window`, and a `queryId`. + +- **Open with an overview** when you do not know where to look yet: "what is + broken in the last hour", "what changed since yesterday's deploy". It returns + the kinds of log lines in the window, with counts against the window before + and example lines. +- **Then narrow** to questions with one answer: "how many 5xx did + POST /api/checkout return per 5 minutes over the last 6 hours", "which + customer IDs saw `PaymentTimeout` today", "show the 20 most recent lines for + trace id abc123". +- Read `explanation` and `window` every time. An answer over the wrong service, + window or field is confidently wrong. + +## 3. Read the lines, then the code + +Pull the actual log lines for the failure, including the requests just before +it. Find the first line this repository emits on the failing path, then open +that code in the working tree. Production runs a release: check the version or +commit in the logs against the branch before you trust line numbers. + +Before you propose a cause, check what the failures have in common: one +endpoint, one tenant, one region, one release, one upstream? A failure scoped +to a dimension names its own trigger. A failure that starts at a deploy points +at that deploy's diff. + +## 4. Report cause, scope, and blast radius + +Write three sentences: what fails, why, and who it hits (volume, affected +customers, since when). Quote the log lines and the question you asked, so the +user can rerun it. Only then, if asked, fix the code you found. + +## Anti-patterns + +- **Fixing the first error you see.** The loudest line is often a downstream + retry, not the cause. +- **One overview, no follow-up.** An overview finds where to look; it is not the + evidence. +- **Trusting an answer without its window.** "No errors" over the last 5 + minutes says nothing about the outage an hour ago. +- **Pasting secrets from logs.** Log lines can hold tokens and personal data. + Quote only what the finding needs. diff --git a/plugins/sazabi/skills/send-agent-telemetry/SKILL.md b/plugins/sazabi/skills/send-agent-telemetry/SKILL.md new file mode 100644 index 00000000000..535e89c6e8a --- /dev/null +++ b/plugins/sazabi/skills/send-agent-telemetry/SKILL.md @@ -0,0 +1,88 @@ +--- +name: send-agent-telemetry +description: Stream coding-agent session telemetry (prompts, tool calls, model requests, tokens and cost) from Claude Code or Codex into Sazabi over OpenTelemetry, with privacy settings chosen on purpose. Use when the user wants observability for their coding agents or agent fleet, to track agent token usage or cost, to audit what agents ran, or to send agent telemetry to Sazabi. +argument-hint: which agents (Claude Code, Codex) and whether prompt text may be exported +allowed-tools: mcp__sazabi__search, mcp__sazabi__execute +--- + +# See what your agents did + +Claude Code and Codex have built-in OpenTelemetry exporters. Pointed at Sazabi, +every session becomes searchable: prompts, tool calls and results, model +requests with token counts and cost. Then "what did the agents run last night" +and "which sessions cost the most" are questions for `sazabi/logs.ask`. + +## 0. How the tools work + +The Sazabi MCP server lists only two tools, `search` and `execute`. Browse +Sazabi's operations with `search` and `namespace: "sazabi"` (an empty query +without a namespace lists connected connectors, not Sazabi operations). Before +calling one, repeat `search` with `namespace: "sazabi"`, the hit's full name as +the query, and `includeInputSchema: true` to load its arguments. Then run it +with `execute` as `{ name, arguments }`, the name copied from the hit (for +example `sazabi/logs.ask`). + +## 1. Decide what may leave the machine + +Ask before exporting prompt text. Prompts and tool inputs can contain code, +customer data and credentials. Codex always exports tool-call arguments and +truncated results, whatever the prompt setting, so say that plainly when the +user picks Codex. Default to prompts off unless the user opts in. + +## 2. Get an intake URL per agent + +Run `sazabi/logSources.list`. If there is no source for this agent yet, create +one with `sazabi/logSources.create` in manual mode (one source per agent: +Claude Code, Codex) and use the intake endpoint it returns. Keep the URL out of +any committed file. + +## 3. Configure the agent + +**Claude Code** reads the `env` block of `~/.claude/settings.json` (or a +project's `.claude/settings.local.json`, which must stay untracked): + +```json +{ + "env": { + "CLAUDE_CODE_ENABLE_TELEMETRY": "1", + "OTEL_LOGS_EXPORTER": "otlp", + "OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf", + "OTEL_EXPORTER_OTLP_ENDPOINT": "", + "OTEL_LOG_USER_PROMPTS": "0", + "OTEL_LOG_ASSISTANT_RESPONSES": "0" + } +} +``` + +Set `OTEL_LOG_ASSISTANT_RESPONSES` explicitly: unset, it follows the prompts +setting. Do not set a metrics exporter; Sazabi does not store Claude Code +metrics. + +**Codex** reads `~/.codex/config.toml`. Use one `[otel]` table with an inline +`exporter`, and the full logs URL including `/v1/logs` (Codex does not add it): + +```toml +[otel] +log_user_prompt = false +exporter = { otlp-http = { endpoint = "/v1/logs", protocol = "binary" } } +``` + +If `[otel]` already exists, edit it in place. A second `[otel]` header is a +TOML error that stops Codex from starting. + +Merge into existing files; never overwrite a user's settings. + +## 4. Prove it + +Agents load settings at startup, so start a new session and run one prompt. +Then ask `sazabi/logs.ask` for `claude_code.api_request` or `codex.api_request` +events in the last 15 minutes. Report what is flowing, what is excluded, and a +first useful question, such as cost per session today. + +## Anti-patterns + +- **Turning on prompt export without asking.** It is the user's data, and + sometimes their customers'. +- **Committing the settings file.** It holds the intake URL. +- **Assuming it works.** A session that started before the change exports + nothing new. diff --git a/plugins/sazabi/skills/triage-issues/SKILL.md b/plugins/sazabi/skills/triage-issues/SKILL.md new file mode 100644 index 00000000000..efda531b8ae --- /dev/null +++ b/plugins/sazabi/skills/triage-issues/SKILL.md @@ -0,0 +1,67 @@ +--- +name: triage-issues +description: Work through the issues Sazabi opened on its own — rank them by severity and blast radius, find each one's cause in this repository with log evidence, and propose or draft one fix per issue, leaving issues open until a fix ships. Use when the user asks what is broken, what to fix first, to clean up or triage Sazabi issues, or for an on-call or morning review. +argument-hint: optional project, service, or severity to focus on +allowed-tools: mcp__sazabi__search, mcp__sazabi__execute +--- + +# Fix the right issue first + +Sazabi opens issues when it sees new or growing errors. A long list is not a +plan. The job is to pick the issues that matter, tie each to code in this +repository with evidence, and leave the user with a short ranked list of fixes. + +## 0. How the tools work + +The Sazabi MCP server lists only two tools, `search` and `execute`. Browse +Sazabi's operations with `search` and `namespace: "sazabi"` (an empty query +without a namespace lists connected connectors, not Sazabi operations). Before +calling one, repeat `search` with `namespace: "sazabi"`, the hit's full name as +the query, and `includeInputSchema: true` to load its arguments. Then run it +with `execute` as `{ name, arguments }`, the name copied from the hit (for +example `sazabi/logs.ask`). +List open issues with `sazabi/issues.search`; search for other issue operations (reading one issue, changing its status) before assuming they exist. + +## 1. Rank before you read + +Pull open issues, then rank by impact, not by age or title: + +1. Severity Sazabi assigned. +2. Volume now against before: is it growing? +3. Who it hits: customers, a paying tenant, a critical path (sign-in, + checkout), or an internal job. +4. Whether this repository owns the failing code. Issues in services you don't + own get a one-line note and a pointer, not an investigation. + +Group issues that share a cause; three issues from one bad deploy are one +item. + +## 2. One issue at a time, with evidence + +For the top few, ask `sazabi/logs.ask` for the failing lines and their +neighbors, find the first frame or log line this repository emits on that +path, and read that code. Check `sazabi/search.threads` for an earlier +investigation before starting over. + +Name one cause per issue, and say how sure you are. "Unknown, here is what +would tell us" is a valid finding. + +## 3. Propose fixes, don't hide issues + +For each issue write: cause, fix, risk, and how to verify it after deploy. Draft +the code change if the user asked. Leave issues open until the fix is live and +the errors have stopped; closing an issue to make the list shorter erases the +signal that would catch a regression. + +## 4. Report + +A ranked list: issue, impact, cause, proposed fix, confidence. Put anything +that needs a human decision (data repair, customer contact) at the top. + +## Anti-patterns + +- **Triage by title.** Two issues with one message can be two bugs; two + messages can be one bug. +- **Fixing the noisiest issue.** Volume is not impact; a quiet failure in + checkout beats a loud retry warning. +- **Resolving without a shipped fix.** diff --git a/plugins/sazabi/skills/use-project-memory/SKILL.md b/plugins/sazabi/skills/use-project-memory/SKILL.md new file mode 100644 index 00000000000..8347372e01b --- /dev/null +++ b/plugins/sazabi/skills/use-project-memory/SKILL.md @@ -0,0 +1,60 @@ +--- +name: use-project-memory +description: Read Sazabi project memory (how the system is laid out, what healthy looks like, runbooks, past incidents) before investigating or changing production behavior, and write back durable findings once they are verified. Use at the start of an investigation or on-call task, when the user mentions a runbook or a past incident, or after resolving something worth remembering. +argument-hint: the system, symptom, or finding to look up or record +allowed-tools: mcp__sazabi__search, mcp__sazabi__execute +--- + +# Start from what the team already knows + +Project memory is curated knowledge the Sazabi agent and your teammates rely +on: architecture notes, what normal looks like, runbooks for recurring +problems, and write-ups of past incidents. Reading it first saves rediscovering +known causes. Writing to it keeps the next person from starting over. + +## 0. How the tools work + +The Sazabi MCP server lists only two tools, `search` and `execute`. Browse +Sazabi's operations with `search` and `namespace: "sazabi"` (an empty query +without a namespace lists connected connectors, not Sazabi operations). Before +calling one, repeat `search` with `namespace: "sazabi"`, the hit's full name as +the query, and `includeInputSchema: true` to load its arguments. Then run it +with `execute` as `{ name, arguments }`, the name copied from the hit (for +example `sazabi/logs.ask`). +Memory operations: `sazabi/memory.search`, `sazabi/memory.list`, `sazabi/memory.create`, `sazabi/memory.update`. + +## 1. Read first + +Before investigating, run `sazabi/memory.search` for the service, the symptom +and the error. Open matching runbooks and incident notes. Follow a runbook's +steps when it fits, but check it against the current code and logs: memory can +be stale, and a runbook for last year's architecture is a lead, not an order. + +## 2. Write only what lasts + +After the work is verified, decide if anything is durable: + +- **Worth writing:** a root cause that could recur, a runbook for a procedure + you just did by hand, a fact about the system that took effort to find. +- **Not worth writing:** raw logs, guesses, one-off findings, anything that + will be wrong next week, secrets. + +Use clear paths: `runbooks/.md` with kind `runbook`, or +`notes/investigations/.md` with kind `note`. + +## 3. Create or update, never clobber + +Search for an existing document on the topic first. If one exists, update it +with `sazabi/memory.update` and keep what is still true. `memory.create` fails +when the path is taken, and `memory.update` fails when it is empty, so a +failure means you picked the wrong one, not that you should force it. + +Show the user the document before you write it to a shared project. + +## Anti-patterns + +- **Treating memory as truth.** Verify against code and logs before acting. +- **Writing a transcript.** Memory should be a few paragraphs a teammate + can act on. +- **Duplicates.** Two runbooks for one problem drift apart; update the one + that exists. diff --git a/plugins/sazabi/skills/verify-a-deploy/SKILL.md b/plugins/sazabi/skills/verify-a-deploy/SKILL.md new file mode 100644 index 00000000000..f600173c615 --- /dev/null +++ b/plugins/sazabi/skills/verify-a-deploy/SKILL.md @@ -0,0 +1,61 @@ +--- +name: verify-a-deploy +description: Check in Sazabi that a change you shipped works in production before you call the task done — compare error rates and new kinds of log lines before and after the deploy, confirm the fixed error stopped, and catch regressions early. Use after merging or deploying a fix or feature, when the user asks whether a deploy is healthy, did the fix work, or is it safe to move on. +argument-hint: the service, deploy time or commit, and the error or behavior the change should affect +allowed-tools: mcp__sazabi__search, mcp__sazabi__execute +--- + +# A task is done when production says so + +Tests pass on the code you wrote. Production runs it against real traffic, +real data and real dependencies. Before you tell the user a fix shipped, check +that it changed what it was meant to change and nothing else. + +## 0. How the tools work + +The Sazabi MCP server lists only two tools, `search` and `execute`. Browse +Sazabi's operations with `search` and `namespace: "sazabi"` (an empty query +without a namespace lists connected connectors, not Sazabi operations). Before +calling one, repeat `search` with `namespace: "sazabi"`, the hit's full name as +the query, and `includeInputSchema: true` to load its arguments. Then run it +with `execute` as `{ name, arguments }`, the name copied from the hit (for +example `sazabi/logs.ask`). +Questions about logs go to `sazabi/logs.ask`. + +## 1. Pin down the deploy + +Find when the change went live: the deploy time, or the first log line that +carries the new version or commit. Every comparison below is "before that +moment" against "after it". If the change is not live yet, say so and stop. + +## 2. Did the change do its job? + +Ask one question for the thing the change should affect, over a window on each +side of the deploy: + +- A fix: "how many times did `` occur per 10 minutes, 3 hours before + and after ". It should drop to zero, not just fall. +- A feature: "how many requests reached `` since + , and how many failed". Zero traffic means it is not wired up yet. +- A performance change: ask for the latency or duration the logs record, per + interval, on both sides. + +## 3. Did it break anything else? + +Ask for an overview of what is new or growing since the deploy. Look at each +new kind of error line and decide if this change could cause it. Check +`sazabi/issues.search` for issues first seen after the deploy. + +## 4. Report + +Say whether it is verified, how (the questions you asked and the numbers on +each side), and what is still unknown, for example a code path no traffic has +hit yet. If something regressed, say so first, with the log lines. + +## Anti-patterns + +- **Calling it done because CI passed.** CI never saw production traffic. +- **Checking too soon.** Five minutes of low traffic can hide a 1% error rate. + Say how much traffic the window covered. +- **Comparing unequal windows.** Compare the same length of time, at a similar + time of day.