diff --git a/.claude/skills/add-keys/SKILL.md b/.claude/skills/add-keys/SKILL.md index 5e8ef8d..fe86ee8 100644 --- a/.claude/skills/add-keys/SKILL.md +++ b/.claude/skills/add-keys/SKILL.md @@ -216,3 +216,5 @@ Each plugin has its own skill with the exact script to run: | Discord | `api_key` | `/add-keys/discord` | | Google Calendar | `oauth_2` | `/add-keys/google` | | Google Drive | `oauth_2` | `/add-keys/google` (shares credentials with Calendar) | +| Todoist | `api_key` | `/add-keys/todoist` | +| Notion | `api_key` | `/add-keys/notion` | diff --git a/.claude/skills/add-keys/notion/SKILL.md b/.claude/skills/add-keys/notion/SKILL.md new file mode 100644 index 0000000..127778a --- /dev/null +++ b/.claude/skills/add-keys/notion/SKILL.md @@ -0,0 +1,126 @@ +--- +name: add-keys/notion +description: Set up Notion credentials for Corsair. Use when the user wants to connect Notion to their agent. +--- + +# Notion Key Setup + +Read `/add-keys` first if you haven't — it explains the key model. + +Auth type: **`api_key`** +- Integration level: no credentials (api_key plugins have no shared provider secrets) +- Account level: `api_key` (Notion internal integration token), `webhook_signature` (webhook signing secret) + +--- + +## 1. Get credentials from Notion + +### Integration token (`api_key`) +1. Go to https://www.notion.so/profile/integrations +2. Click **New integration** (or select an existing one) +3. Give it a name and select the associated workspace +4. Under **Capabilities**, enable the permissions your agent needs (read/update/insert content) +5. Click **Save** and copy the **Internal Integration Secret** (starts with `secret_`) +6. Make sure to share the relevant Notion pages/databases with your integration (open a page → **...** menu → **Add connections** → select your integration) + +### Webhook signing secret (`webhook_signature`) — only needed if using webhooks +1. In your Notion integration settings, find the **Webhooks** section +2. Add a webhook endpoint pointing to your `WEBHOOK_URL` (from your `.env`) +3. Copy the signing secret used to verify `x-notion-signature` on incoming requests + +--- + +## 2. Write and run the setup script + +Ask the user to provide the values, then write `scripts/setup-notion.ts`: + +```typescript +import 'dotenv/config'; +import { and, eq } from 'drizzle-orm'; +import { corsair } from '../server/corsair'; +import { db } from '../server/db'; +import { corsairAccounts, corsairIntegrations } from '../server/db/schema'; + +const PLUGIN = 'notion'; +const TENANT_ID = 'default'; +const INTEGRATION_TOKEN = 'secret_...'; // fill in — from Notion integration settings +const WEBHOOK_SIGNATURE = ''; // fill in — leave empty string if not using webhooks + +async function main() { + let [integration] = await db + .select() + .from(corsairIntegrations) + .where(eq(corsairIntegrations.name, PLUGIN)); + + if (!integration) { + [integration] = await db + .insert(corsairIntegrations) + .values({ id: crypto.randomUUID(), name: PLUGIN }) + .returning(); + console.log(`✓ Created integration: ${PLUGIN}`); + } + + await corsair.keys.notion.issue_new_dek(); + console.log('✓ Integration DEK ready'); + + const [existing] = await db + .select() + .from(corsairAccounts) + .where( + and( + eq(corsairAccounts.tenantId, TENANT_ID), + eq(corsairAccounts.integrationId, integration!.id), + ), + ); + + if (!existing) { + await db.insert(corsairAccounts).values({ + id: crypto.randomUUID(), + tenantId: TENANT_ID, + integrationId: integration!.id, + }); + console.log('✓ Created account'); + } + + await corsair.notion.keys.issue_new_dek(); + console.log('✓ Account DEK ready'); + + await corsair.notion.keys.set_api_key(INTEGRATION_TOKEN); + if (WEBHOOK_SIGNATURE) { + await corsair.notion.keys.set_webhook_signature(WEBHOOK_SIGNATURE); + } + + const stored = await corsair.notion.keys.get_api_key(); + console.log(`✓ Notion configured. Token starts with: ${stored?.slice(0, 8)}...`); + + process.exit(0); +} + +main().catch((e) => { console.error(e); process.exit(1); }); +``` + +Run it: + +```bash +docker compose exec agent pnpm tsx scripts/setup-notion.ts +``` + +Then delete the script: + +```bash +rm scripts/setup-notion.ts +``` + +--- + +## 3. Verify + +```bash +docker compose exec agent pnpm tsx -e " +import 'dotenv/config'; +import { corsair } from './server/corsair'; +corsair.notion.keys.get_api_key().then(k => console.log('key:', k?.slice(0,8) + '...')).then(() => process.exit(0)); +" +``` + +No restart needed — the agent reads from the DB on every request. diff --git a/.claude/skills/add-keys/todoist/SKILL.md b/.claude/skills/add-keys/todoist/SKILL.md new file mode 100644 index 0000000..b523125 --- /dev/null +++ b/.claude/skills/add-keys/todoist/SKILL.md @@ -0,0 +1,122 @@ +--- +name: add-keys/todoist +description: Set up Todoist credentials for Corsair. Use when the user wants to connect Todoist to their agent. +--- + +# Todoist Key Setup + +Read `/add-keys` first if you haven't — it explains the key model. + +Auth type: **`api_key`** +- Integration level: no credentials (api_key plugins have no shared provider secrets) +- Account level: `api_key` (Todoist API token), `webhook_signature` (webhook signing secret) + +--- + +## 1. Get credentials from Todoist + +### API token (`api_key`) +1. Go to https://app.todoist.com/app/settings/integrations/developer +2. Scroll to **API token** and copy it + +### Webhook signing secret (`webhook_signature`) — only needed if using webhooks +1. In your Todoist app integration settings, find the webhook configuration +2. Copy the signing secret (used to verify `x-todoist-signature` header on incoming webhooks) +3. The webhook URL to register is stored in your `.env` as `WEBHOOK_URL` + +--- + +## 2. Write and run the setup script + +Ask the user to provide the values, then write `scripts/setup-todoist.ts`: + +```typescript +import 'dotenv/config'; +import { and, eq } from 'drizzle-orm'; +import { corsair } from '../server/corsair'; +import { db } from '../server/db'; +import { corsairAccounts, corsairIntegrations } from '../server/db/schema'; + +const PLUGIN = 'todoist'; +const TENANT_ID = 'default'; +const API_TOKEN = '...'; // fill in — from Todoist settings → Integrations → Developer +const WEBHOOK_SIGNATURE = '...'; // fill in — leave empty string if not using webhooks + +async function main() { + let [integration] = await db + .select() + .from(corsairIntegrations) + .where(eq(corsairIntegrations.name, PLUGIN)); + + if (!integration) { + [integration] = await db + .insert(corsairIntegrations) + .values({ id: crypto.randomUUID(), name: PLUGIN }) + .returning(); + console.log(`✓ Created integration: ${PLUGIN}`); + } + + await corsair.keys.todoist.issue_new_dek(); + console.log('✓ Integration DEK ready'); + + const [existing] = await db + .select() + .from(corsairAccounts) + .where( + and( + eq(corsairAccounts.tenantId, TENANT_ID), + eq(corsairAccounts.integrationId, integration!.id), + ), + ); + + if (!existing) { + await db.insert(corsairAccounts).values({ + id: crypto.randomUUID(), + tenantId: TENANT_ID, + integrationId: integration!.id, + }); + console.log('✓ Created account'); + } + + await corsair.todoist.keys.issue_new_dek(); + console.log('✓ Account DEK ready'); + + await corsair.todoist.keys.set_api_key(API_TOKEN); + if (WEBHOOK_SIGNATURE) { + await corsair.todoist.keys.set_webhook_signature(WEBHOOK_SIGNATURE); + } + + const stored = await corsair.todoist.keys.get_api_key(); + console.log(`✓ Todoist configured. Token starts with: ${stored?.slice(0, 8)}...`); + + process.exit(0); +} + +main().catch((e) => { console.error(e); process.exit(1); }); +``` + +Run it: + +```bash +docker compose exec agent pnpm tsx scripts/setup-todoist.ts +``` + +Then delete the script: + +```bash +rm scripts/setup-todoist.ts +``` + +--- + +## 3. Verify + +```bash +docker compose exec agent pnpm tsx -e " +import 'dotenv/config'; +import { corsair } from './server/corsair'; +corsair.todoist.keys.get_api_key().then(k => console.log('key:', k?.slice(0,8) + '...')).then(() => process.exit(0)); +" +``` + +No restart needed — the agent reads from the DB on every request.