Skip to content

Commit 2f0897b

Browse files
authored
Merge pull request #395 from elie222/adjust-choose-rule-prompt
Learned patterns
2 parents 2f7c28a + a55681d commit 2f0897b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1051
-151
lines changed

.cursor/rules/gmail-api.mdc

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
description: Guidelines for working with Gmail API
3+
globs:
4+
alwaysApply: false
5+
---
6+
# Gmail API Usage
7+
8+
Guidelines for working with email provider APIs (Gmail, Outlook, etc.) to ensure maintainability and future provider support.
9+
Currently we only support Gmail.
10+
11+
## Core Principles
12+
13+
1. **Never call provider APIs directly from routes or components**
14+
2. **Always use wrapper functions from the utils folder**
15+
3. **Keep provider-specific implementation details isolated**
16+
17+
## Directory Structure
18+
19+
```
20+
apps/web/utils/
21+
├── gmail/ # Gmail-specific implementations
22+
│ ├── message.ts # Message operations (get, list, batch, etc.)
23+
│ ├── thread.ts # Thread operations
24+
│ ├── label.ts # Label operations
25+
│ └── ...
26+
├── outlook/ # Future Outlook implementation
27+
└── ... # Other providers
28+
```
29+
30+
## Usage Patterns
31+
32+
### ✅ DO: Use the abstraction layers
33+
34+
```typescript
35+
// GOOD: Using provided utility functions
36+
import { getMessages, getMessage } from "@/utils/gmail/message";
37+
38+
async function fetchEmails(gmail: gmail_v1.Gmail, query: string) {
39+
// Use the wrapper function that handles implementation details
40+
const messages = await getMessages(gmail, {
41+
query,
42+
maxResults: 10,
43+
});
44+
45+
return messages;
46+
}
47+
```
48+
49+
### ❌ DON'T: Call provider APIs directly
50+
51+
```typescript
52+
// BAD: Direct API calls
53+
async function fetchEmails(gmail: gmail_v1.Gmail, query: string) {
54+
// Direct API calls make future provider support difficult
55+
const response = await gmail.users.messages.list({
56+
userId: "me",
57+
q: query,
58+
maxResults: 10,
59+
});
60+
61+
return response.data;
62+
}
63+
```
64+
65+
## Why This Matters
66+
67+
1. **Future Provider Support**: By isolating provider-specific implementations, we can add support for Outlook, ProtonMail, etc.

.cursor/rules/llm-test.mdc

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ Tests for LLM-related functionality should follow these guidelines to ensure con
3131
// Skip tests unless explicitly running AI tests
3232
const isAiTest = process.env.RUN_AI_TESTS === "true";
3333

34-
describe.skipIf(!isAiTest)("yourFunction", () => {
34+
describe.runIf(isAiTest)("yourFunction", () => {
3535
beforeEach(() => {
3636
vi.clearAllMocks();
3737
});
3838

3939
test("test case description", async () => {
4040
// Test implementation
4141
});
42-
});
42+
}, 15_000);
4343
```
4444

4545
## Helper Functions

.github/workflows/test.yml

+1
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,4 @@ jobs:
5050
GOOGLE_PUBSUB_TOPIC_NAME: "topic"
5151
GOOGLE_ENCRYPT_SECRET: "secret"
5252
GOOGLE_ENCRYPT_SALT: "salt"
53+
INTERNAL_API_KEY: "secret"

apps/web/.env.example

+43-34
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,22 @@ DIRECT_URL="postgresql://postgres:password@localhost:5432/inboxzero?schema=publi
44
NEXTAUTH_SECRET= # Generate a random secret here: https://generate-secret.vercel.app/32
55
NEXTAUTH_URL=http://localhost:3000
66

7+
# Gmail
78
GOOGLE_CLIENT_ID=
89
GOOGLE_CLIENT_SECRET=
910
GOOGLE_ENCRYPT_SECRET= # openssl rand -hex 32
1011
GOOGLE_ENCRYPT_SALT= # openssl rand -hex 16
1112

13+
GOOGLE_PUBSUB_TOPIC_NAME="projects/abc/topics/xyz"
14+
GOOGLE_PUBSUB_VERIFICATION_TOKEN= # Generate a random secret here: https://generate-secret.vercel.app/32
15+
1216
# LLM config
1317
DEFAULT_LLM_PROVIDER=openai
14-
OPENAI_API_KEY=
18+
19+
# Set at least one of the following:
20+
# ANTHROPIC_API_KEY=
21+
# OPENROUTER_API_KEY=
22+
# OPENAI_API_KEY=
1523
# GOOGLE_API_KEY=
1624
# GROQ_API_KEY=
1725
# BEDROCK_ACCESS_KEY=
@@ -20,6 +28,13 @@ OPENAI_API_KEY=
2028
# OLLAMA_BASE_URL=http://localhost:11434/api
2129
# NEXT_PUBLIC_OLLAMA_MODEL=phi3
2230

31+
# Economy LLM configuration (for large context windows where cost efficiency matters)
32+
ECONOMY_LLM_PROVIDER=
33+
ECONOMY_LLM_MODEL=
34+
35+
INTERNAL_API_KEY= # Generate a random secret here: https://generate-secret.vercel.app/32
36+
API_KEY_SALT= # Generate a random secret here: https://generate-secret.vercel.app/32
37+
2338
#redis config
2439
UPSTASH_REDIS_URL="http://localhost:8079"
2540
UPSTASH_REDIS_TOKEN= # Generate a random secret here: https://generate-secret.vercel.app/32
@@ -28,30 +43,47 @@ QSTASH_TOKEN=
2843
QSTASH_CURRENT_SIGNING_KEY=
2944
QSTASH_NEXT_SIGNING_KEY=
3045

46+
# Optional:
47+
48+
NEXT_PUBLIC_APP_HOME_PATH=/automation # If you want the product to default to email client, set this to /mail
49+
LOG_ZOD_ERRORS=true
50+
CRON_SECRET=
51+
52+
# Tinybird
3153
TINYBIRD_TOKEN=
3254
TINYBIRD_BASE_URL=https://api.us-east.tinybird.co/
3355
TINYBIRD_ENCRYPT_SECRET= # openssl rand -hex 32
3456
TINYBIRD_ENCRYPT_SALT= # openssl rand -hex 16
35-
# Set this to true if you haven't set `TINYBIRD_TOKEN`.
36-
# Some of the app's featues will be disabled when this is set.
37-
# Generate a random secret here: https://generate-secret.vercel.app/32
38-
API_KEY_SALT=
39-
40-
LOOPS_API_SECRET=
41-
42-
GOOGLE_PUBSUB_TOPIC_NAME="projects/abc/topics/xyz"
43-
GOOGLE_PUBSUB_VERIFICATION_TOKEN= # Generate a random secret here: https://generate-secret.vercel.app/32
4457

58+
# Sentry (error tracking)
4559
SENTRY_AUTH_TOKEN=
4660
SENTRY_ORGANIZATION=
4761
SENTRY_PROJECT=
4862
NEXT_PUBLIC_SENTRY_DSN=
4963

64+
# Axiom (logging)
5065
NEXT_PUBLIC_AXIOM_DATASET=
5166
NEXT_PUBLIC_AXIOM_TOKEN=
5267

53-
LOG_ZOD_ERRORS=true
68+
# PostHog (analytics)
69+
NEXT_PUBLIC_POSTHOG_KEY=
70+
NEXT_PUBLIC_POSTHOG_HERO_AB=
71+
NEXT_PUBLIC_POSTHOG_ONBOARDING_SURVEY_ID=
72+
POSTHOG_API_SECRET=
73+
POSTHOG_PROJECT_ID=
74+
75+
# Marketing emails
76+
RESEND_API_KEY=
77+
LOOPS_API_SECRET=
78+
79+
# Crisp support chat
80+
NEXT_PUBLIC_CRISP_WEBSITE_ID=
81+
82+
# Sanity config for blog
83+
NEXT_PUBLIC_SANITY_PROJECT_ID=
84+
NEXT_PUBLIC_SANITY_DATASET="production"
5485

86+
# Payments
5587
LEMON_SQUEEZY_SIGNING_SECRET=
5688
LEMON_SQUEEZY_API_KEY=
5789

@@ -75,26 +107,3 @@ NEXT_PUBLIC_LIFETIME_PAYMENT_LINK=#
75107
NEXT_PUBLIC_LIFETIME_VARIANT_ID=123
76108
NEXT_PUBLIC_LIFETIME_EXTRA_SEATS_PAYMENT_LINK=#
77109
NEXT_PUBLIC_LIFETIME_EXTRA_SEATS_VARIANT_ID=123
78-
79-
NEXT_PUBLIC_POSTHOG_KEY=
80-
NEXT_PUBLIC_POSTHOG_HERO_AB=
81-
NEXT_PUBLIC_POSTHOG_ONBOARDING_SURVEY_ID=
82-
POSTHOG_API_SECRET=
83-
POSTHOG_PROJECT_ID=
84-
85-
RESEND_API_KEY=
86-
CRON_SECRET=
87-
88-
NEXT_PUBLIC_CRISP_WEBSITE_ID=
89-
90-
ADMINS=
91-
92-
NEXT_PUBLIC_SANITY_PROJECT_ID=
93-
NEXT_PUBLIC_SANITY_DATASET="production"
94-
95-
# SANITY_STUDIO_PROJECT_ID=
96-
# SANITY_STUDIO_DATASET=production
97-
# SANITY_STUDIO_HOST=
98-
99-
# If you want the product to default to email client, set this to /mail
100-
NEXT_PUBLIC_APP_HOME_PATH=/automation

apps/web/__tests__/ai-categorize-senders.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const testSenders = [
5151
},
5252
];
5353

54-
describe.skipIf(!isAiTest)("AI Sender Categorization", () => {
54+
describe.runIf(isAiTest)("AI Sender Categorization", () => {
5555
describe("Bulk Categorization", () => {
5656
it("should categorize senders with snippets using AI", async () => {
5757
const result = await aiCategorizeSenders({

apps/web/__tests__/ai-choose-args.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const isAiTest = process.env.RUN_AI_TESTS === "true";
99

1010
vi.mock("server-only", () => ({}));
1111

12-
describe.skipIf(!isAiTest)("getActionItemsWithAiArgs", () => {
12+
describe.runIf(isAiTest)("getActionItemsWithAiArgs", () => {
1313
test("should return actions unchanged when no AI args needed", async () => {
1414
const actions = [getAction({})];
1515
const rule = getRule("Test rule", actions);

apps/web/__tests__/ai-choose-rule.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const isAiTest = process.env.RUN_AI_TESTS === "true";
99

1010
vi.mock("server-only", () => ({}));
1111

12-
describe.skipIf(!isAiTest)("aiChooseRule", () => {
12+
describe.runIf(isAiTest)("aiChooseRule", () => {
1313
test("Should return no rule when no rules passed", async () => {
1414
const result = await aiChooseRule({
1515
rules: [],

apps/web/__tests__/ai-create-group.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ vi.mock("@/utils/gmail/message", () => ({
1313
queryBatchMessages: vi.fn(),
1414
}));
1515

16-
describe.skipIf(!isAiTest)("aiGenerateGroupItems", () => {
16+
describe.runIf(isAiTest)("aiGenerateGroupItems", () => {
1717
it("should generate group items based on user prompt", async () => {
1818
const user = {
1919

0 commit comments

Comments
 (0)