Enable spam email processing and improve label management workflow (#1948)

# Improved Email Labeling System with DEV_PROXY Support

## Description

This PR enhances the email labeling workflow with a more sophisticated approach to label management. It replaces the previous labeling system with a new implementation that better handles existing labels and user-defined topics.

Key improvements:
- Added DEV_PROXY environment variable to support local development
- Implemented a more robust label suggestion system that prioritizes existing account labels
- Added ability to create missing labels when appropriate
- Modified thread workflow to reload inbox after syncing
- Enabled processing of messages marked as spam (commented out spam filtering)
- Added a test:cron script for local testing of scheduled handlers

## Type of Change

- [x]  New feature (non-breaking change which adds functionality)
- [x] 🐛 Bug fix (non-breaking change which fixes an issue)
- [x]  Performance improvement

## Areas Affected

- [x] Email Integration (Gmail, IMAP, etc.)
- [x] Development Workflow

## Testing Done

- [x] Manual testing performed

## Checklist

- [x] I have performed a self-review of my code
- [x] My changes generate no new warnings
- [x] I have updated the documentation

## Additional Notes

The new labeling system now follows a three-step process:
1. Retrieves existing user account labels
2. Gets user-defined topics for potential new labels
3. Intelligently suggests and applies labels, prioritizing existing ones

The DEV_PROXY environment variable allows for easier local development by redirecting notification requests through a local proxy when configured.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **New Features**
  * Improved label suggestion and synchronization, now incorporating user topics and existing labels for more accurate email organization.
  * Added spam detection to prevent intent analysis on spam-tagged messages.
  * Enhanced workflow steps for label management, including new steps for user topic retrieval and label suggestion generation.

* **Bug Fixes**
  * Messages labeled as spam are now properly excluded from certain processing steps.

* **Chores**
  * Updated environment variable defaults to enable workflows in local and staging environments.
  * Added a new script for testing scheduled tasks via a local endpoint.
  * Disabled the "seed-style" CLI command.

* **Other Improvements**
  * Inbox folder now reloads automatically after thread updates.
  * Improved logging for thread processing and label synchronization.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Adam
2025-08-07 14:23:01 -07:00
committed by GitHub
parent fae9457e88
commit 18314cd088
10 changed files with 363 additions and 128 deletions

View File

@@ -12,7 +12,8 @@
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio",
"eval": "evalite",
"eval:dev": "evalite watch"
"eval:dev": "evalite watch",
"test:cron": "curl 'http://localhost:8787/cdn-cgi/handler/scheduled'"
},
"exports": {
"./trpc": "./src/trpc/index.ts",

View File

@@ -90,6 +90,7 @@ export type ZeroEnv = {
thread_queue: Queue;
VECTORIZE: VectorizeIndex;
VECTORIZE_MESSAGE: VectorizeIndex;
DEV_PROXY: string;
};
const env = _env as ZeroEnv;

View File

@@ -23,7 +23,9 @@ export const c = {
} as unknown as AppContext;
export const getNotificationsUrl = (provider: EProviders) => {
return env.VITE_PUBLIC_BACKEND_URL + '/a8n/notify/' + provider;
return env.DEV_PROXY
? `${env.DEV_PROXY}/a8n/notify/${provider}`
: env.VITE_PUBLIC_BACKEND_URL + '/a8n/notify/' + provider;
};
export async function setSubscribedState(

View File

@@ -354,7 +354,7 @@ export class WorkflowRunner extends DurableObject<ZeroEnv> {
// Extract thread IDs from messages
historyItem.messagesAdded?.forEach((msg) => {
if (msg.message?.labelIds?.includes('DRAFT')) return;
if (msg.message?.labelIds?.includes('SPAM')) return;
// if (msg.message?.labelIds?.includes('SPAM')) return;
if (msg.message?.threadId) {
threadsAdded.add(msg.message.threadId);
}
@@ -465,6 +465,8 @@ export class WorkflowRunner extends DurableObject<ZeroEnv> {
);
}
}
} else {
yield* Console.log('[ZERO_WORKFLOW] No new threads to process');
}
// Process label changes for threads
@@ -1070,7 +1072,7 @@ export class WorkflowRunner extends DurableObject<ZeroEnv> {
history.forEach((historyItem) => {
historyItem.messagesAdded?.forEach((msg) => {
if (msg.message?.labelIds?.includes('DRAFT')) return;
if (msg.message?.labelIds?.includes('SPAM')) return;
// if (msg.message?.labelIds?.includes('SPAM')) return;
if (msg.message?.threadId) {
threadsAdded.add(msg.message.threadId);
}

View File

@@ -941,6 +941,7 @@ export class ZeroDriver extends DurableObject<ZeroEnv> {
Effect.tap(() =>
Effect.sync(() => console.log(`[syncThread] Updated database for ${threadId}`)),
),
Effect.tap(() => Effect.sync(() => this.reloadFolder('inbox'))),
Effect.catchAll((error) => {
console.error(`[syncThread] Failed to update database for ${threadId}:`, error);
return Effect.succeed(undefined);
@@ -966,18 +967,6 @@ export class ZeroDriver extends DurableObject<ZeroEnv> {
return Effect.succeed(undefined);
}),
);
// yield* Effect.tryPromise(() => sendDoState(this.name)).pipe(
// Effect.tap(() =>
// Effect.sync(() => {
// result.broadcastSent = true;
// console.log(`[syncThread] Broadcasted do state for ${threadId}`);
// }),
// ),
// Effect.catchAll((error) => {
// console.warn(`[syncThread] Failed to broadcast do state for ${threadId}:`, error);
// return Effect.succeed(undefined);
// }),
// );
} else {
console.log(`[syncThread] No agent available for broadcasting ${threadId}`);
}

View File

@@ -280,24 +280,31 @@ export const createDefaultWorkflows = (): WorkflowEngine => {
{
id: 'get-user-labels',
name: 'Get User Labels',
description: 'Retrieves user-defined labels',
description: 'Retrieves existing labels from user account',
enabled: true,
action: workflowFunctions.getUserLabels,
},
{
id: 'generate-labels',
name: 'Generate Labels',
description: 'Generates appropriate labels for the thread',
id: 'get-user-topics',
name: 'Get User Topics',
description: 'Retrieves user-defined topics for potential new labels',
enabled: true,
action: workflowFunctions.generateLabels,
action: workflowFunctions.getUserTopics,
},
{
id: 'generate-label-suggestions',
name: 'Generate Label Suggestions',
description: 'Generates appropriate label suggestions for the thread',
enabled: true,
action: workflowFunctions.generateLabelSuggestions,
errorHandling: 'continue',
},
{
id: 'apply-labels',
name: 'Apply Labels',
description: 'Applies generated labels to the thread',
id: 'sync-labels',
name: 'Sync Labels',
description: 'Creates missing labels and applies them to the thread',
enabled: true,
action: workflowFunctions.applyLabels,
action: workflowFunctions.syncLabels,
errorHandling: 'continue',
},
{

View File

@@ -1,6 +1,5 @@
import {
SummarizeMessage,
ThreadLabels,
ReSummarizeThread,
SummarizeThread,
} from '../lib/brain.fallback.prompts';
@@ -22,7 +21,18 @@ export const workflowFunctions: Record<string, WorkflowFunction> = {
if (!context.thread.messages || context.thread.messages.length === 0) {
throw new Error('Cannot analyze email intent: No messages in thread');
}
const latestMessage = context.thread.messages[context.thread.messages.length - 1];
const latestMessage = context.thread.latest!;
if (latestMessage.tags.some((tag) => tag.name.toLowerCase() === 'spam')) {
console.log('[WORKFLOW_FUNCTIONS] Skipping analysis for spam message');
return {
isQuestion: false,
isRequest: false,
isMeeting: false,
isUrgent: false,
};
}
const emailIntent = analyzeEmailIntent(latestMessage);
console.log('[WORKFLOW_FUNCTIONS] Analyzed email intent:', {
@@ -392,132 +402,212 @@ export const workflowFunctions: Record<string, WorkflowFunction> = {
}
},
generateLabels: async (context) => {
const summaryResult = context.results?.get('generate-thread-summary');
console.log(summaryResult, context.results);
if (!summaryResult?.summary) {
console.log('[WORKFLOW_FUNCTIONS] No summary available for label generation');
return { labels: [] };
}
getUserTopics: async (context) => {
console.log('[WORKFLOW_FUNCTIONS] Getting user topics for connection:', context.connectionId);
let userLabels: { name: string; usecase: string }[] = [];
try {
const { stub: agent } = await getZeroAgent(context.connectionId);
const userTopics = await agent.getUserTopics();
if (userTopics.length > 0) {
userLabels = userTopics.map((topic: any) => ({
const formattedTopics = userTopics.map((topic: any) => ({
name: topic.topic,
usecase: topic.usecase,
}));
console.log('[WORKFLOW_FUNCTIONS] Using user topics as labels:', userLabels);
console.log('[WORKFLOW_FUNCTIONS] Using user topics:', formattedTopics);
return { userTopics: formattedTopics };
} else {
console.log('[WORKFLOW_FUNCTIONS] No user topics found, using defaults');
userLabels = defaultLabels;
return { userTopics: defaultLabels };
}
} catch (error) {
console.log('[WORKFLOW_FUNCTIONS] Failed to get user topics, using defaults:', error);
userLabels = defaultLabels;
}
console.log('[WORKFLOW_FUNCTIONS] Generating labels for thread:', {
userLabels,
threadId: context.threadId,
threadLabels: context.thread.labels,
});
const labelsResponse = await env.AI.run('@cf/meta/llama-4-scout-17b-16e-instruct', {
messages: [
{ role: 'system', content: ThreadLabels(userLabels, context.thread.labels) },
{ role: 'user', content: summaryResult.summary },
],
});
if (labelsResponse?.response?.replaceAll('!', '').trim()?.length) {
console.log('[WORKFLOW_FUNCTIONS] Labels generated:', labelsResponse.response);
const labels: string[] = labelsResponse?.response
?.split(',')
.map((e: string) => e.trim())
.filter((e: string) => e.length > 0)
.filter((e: string) =>
userLabels.find((label) => label.name.toLowerCase() === e.toLowerCase()),
);
return { labels, userLabelsUsed: userLabels };
} else {
console.log('[WORKFLOW_FUNCTIONS] No labels generated');
return { labels: [], userLabelsUsed: userLabels };
return { userTopics: defaultLabels };
}
},
applyLabels: async (context) => {
const labelsResult = context.results?.get('generate-labels');
generateLabelSuggestions: async (context) => {
const summaryResult = context.results?.get('generate-thread-summary');
const userLabelsResult = context.results?.get('get-user-labels');
const userTopicsResult = context.results?.get('get-user-topics');
if (!summaryResult?.summary) {
console.log('[WORKFLOW_FUNCTIONS] No summary available for label generation');
return { suggestions: [], accountLabelsMap: {} };
}
const accountLabels = userLabelsResult?.userAccountLabels || [];
const userTopics = userTopicsResult?.userTopics || defaultLabels;
const currentThreadLabels = context.thread.labels?.map((l: { name: string }) => l.name) || [];
// Create normalized map for quick lookups
const accountLabelsMap: Record<string, any> = {};
accountLabels.forEach((label: any) => {
const key = label.name.toLowerCase().trim();
accountLabelsMap[key] = label;
});
console.log('[WORKFLOW_FUNCTIONS] Generating label suggestions for thread:', {
threadId: context.threadId,
accountLabelsCount: accountLabels.length,
userTopicsCount: userTopics.length,
currentLabelsCount: currentThreadLabels.length,
});
// Create a comprehensive prompt with all available options
const accountCandidates = accountLabels.map((l: { name: string; description?: string }) => ({
name: l.name,
usecase: l.description || 'General purpose label',
}));
const promptContent = `
EXISTING ACCOUNT LABELS:
${accountCandidates.map((l: { name: string; usecase: string }) => `- ${l.name}: ${l.usecase}`).join('\n')}
USER TOPICS (potential new labels):
${userTopics.map((t: { name: string; usecase: string }) => `- ${t.name}: ${t.usecase}`).join('\n')}
CURRENT THREAD LABELS: ${currentThreadLabels.join(', ') || 'None'}
Instructions:
1. Return 1 label that best match this thread summary
2. PREFER existing account labels if they fit the usecase
3. If no existing labels fit, choose from user topics
4. Only suggest NEW labels if neither existing nor topics match
5. Return as JSON array: [{"name": "label name", "source": "existing|topic|new"}]
Thread Summary: ${summaryResult.summary}`;
const labelsResponse = await env.AI.run('@cf/meta/llama-4-scout-17b-16e-instruct', {
messages: [
{
role: 'system',
content:
'You are an AI that helps organize emails by suggesting appropriate labels. Always respond with valid JSON.',
},
{ role: 'user', content: promptContent },
],
});
const suggestions: { name: string; source: string }[] = labelsResponse.response;
console.log('[WORKFLOW_FUNCTIONS] Generated label suggestions:', suggestions);
return { suggestions, accountLabelsMap };
},
syncLabels: async (context) => {
const suggestionsResult: {
suggestions: { name: string; source: string }[];
accountLabelsMap: Record<string, any>;
} = context.results?.get('generate-label-suggestions') || { suggestions: [] };
const userLabelsResult = context.results?.get('get-user-labels');
if (!labelsResult?.labels || labelsResult.labels.length === 0) {
console.log('[WORKFLOW_FUNCTIONS] No labels to apply');
if (!suggestionsResult?.suggestions || suggestionsResult.suggestions.length === 0) {
console.log('[WORKFLOW_FUNCTIONS] No label suggestions to sync');
return { applied: false };
}
if (!userLabelsResult?.userAccountLabels) {
console.log('[WORKFLOW_FUNCTIONS] No user account labels available');
return { applied: false };
}
const { suggestions, accountLabelsMap } = suggestionsResult;
const userAccountLabels = userLabelsResult?.userAccountLabels || [];
const userAccountLabels = userLabelsResult.userAccountLabels;
const generatedLabels = labelsResult.labels;
console.log('[WORKFLOW_FUNCTIONS] Syncing thread labels:', {
threadId: context.threadId,
suggestions: suggestions.map((s: any) => `${s.name} (${s.source})`),
});
console.log('[WORKFLOW_FUNCTIONS] Modifying thread labels:', generatedLabels);
const { stub: agent } = await getZeroAgent(context.connectionId);
const finalLabelIds: string[] = [];
const createdLabels: any[] = [];
const validLabelIds = generatedLabels
.map((name: string) => {
const foundLabel = userAccountLabels.find(
(label: { name: string; id: string }) => label.name.toLowerCase() === name.toLowerCase(),
);
return foundLabel?.id;
})
.filter((id: string | undefined): id is string => id !== undefined && id !== '');
// Process each suggestion: create if needed, collect IDs
for (const suggestion of suggestions) {
const normalizedName = suggestion.name.toLowerCase().trim();
if (validLabelIds.length > 0) {
const currentLabelIds = context.thread.labels?.map((l: { id: string }) => l.id) || [];
const labelsToAdd = validLabelIds.filter((id: string) => !currentLabelIds.includes(id));
const aiManagedLabelNames = new Set(
(labelsResult.userLabelsUsed || []).map((topic: { name: string }) =>
topic.name.toLowerCase(),
),
);
const aiManagedLabelIds = new Set(
userAccountLabels
.filter((label: { name: string }) => aiManagedLabelNames.has(label.name.toLowerCase()))
.map((label: { id: string }) => label.id),
);
const labelsToRemove = currentLabelIds.filter(
(id: string) => aiManagedLabelIds.has(id) && !validLabelIds.includes(id),
);
if (labelsToAdd.length > 0 || labelsToRemove.length > 0) {
console.log('[WORKFLOW_FUNCTIONS] Applying label changes:', {
add: labelsToAdd,
remove: labelsToRemove,
});
await modifyThreadLabelsInDB(
context.connectionId,
context.threadId.toString(),
labelsToAdd,
labelsToRemove,
);
console.log('[WORKFLOW_FUNCTIONS] Successfully modified thread labels');
return { applied: true, added: labelsToAdd.length, removed: labelsToRemove.length };
if (accountLabelsMap[normalizedName]) {
// Label already exists
finalLabelIds.push(accountLabelsMap[normalizedName].id);
console.log('[WORKFLOW_FUNCTIONS] Using existing label:', suggestion.name);
} else {
console.log('[WORKFLOW_FUNCTIONS] No label changes needed - labels already match');
return { applied: false };
// Need to create label
try {
console.log('[WORKFLOW_FUNCTIONS] Creating new label:', suggestion.name);
const created = (await agent.createLabel({
name: suggestion.name,
})) as any; // Type assertion since agent interface may return void but implementation returns Label
if (created?.id) {
finalLabelIds.push(created.id);
createdLabels.push(created);
// Update accountLabelsMap for subsequent lookups
accountLabelsMap[normalizedName] = created;
console.log('[WORKFLOW_FUNCTIONS] Successfully created label:', created);
} else {
console.log(
'[WORKFLOW_FUNCTIONS] Failed to create label - no ID returned for:',
suggestion.name,
);
}
} catch (error) {
console.error('[WORKFLOW_FUNCTIONS] Error creating label:', {
name: suggestion.name,
error: error instanceof Error ? error.message : String(error),
});
}
}
}
console.log('[WORKFLOW_FUNCTIONS] No valid labels found in user account');
return { applied: false };
if (finalLabelIds.length === 0) {
console.log('[WORKFLOW_FUNCTIONS] No valid label IDs to apply');
return { applied: false, created: createdLabels.length };
}
// Calculate which labels to add/remove
const currentLabelIds = context.thread.labels?.map((l: { id: string }) => l.id) || [];
const labelsToAdd = finalLabelIds.filter((id: string) => !currentLabelIds.includes(id));
// Determine AI-managed labels for removal logic
const userTopicsResult = context.results?.get('get-user-topics');
const userTopics = userTopicsResult?.userTopics || [];
const aiManagedLabelNames = new Set([
...userTopics.map((topic: { name: string; usecase: string }) => topic.name.toLowerCase()),
...defaultLabels.map((label: { name: string; usecase: string }) => label.name.toLowerCase()),
]);
const aiManagedLabelIds = new Set(
userAccountLabels
.filter((label: { name: string }) => aiManagedLabelNames.has(label.name.toLowerCase()))
.map((label: { id: string }) => label.id),
);
const labelsToRemove = currentLabelIds.filter(
(id: string) => aiManagedLabelIds.has(id) && !finalLabelIds.includes(id),
);
// Apply changes if needed
if (labelsToAdd.length > 0 || labelsToRemove.length > 0) {
console.log('[WORKFLOW_FUNCTIONS] Applying label changes:', {
add: labelsToAdd,
remove: labelsToRemove,
created: createdLabels.length,
});
await modifyThreadLabelsInDB(
context.connectionId,
context.threadId.toString(),
labelsToAdd,
labelsToRemove,
);
console.log('[WORKFLOW_FUNCTIONS] Successfully synced thread labels');
return {
applied: true,
added: labelsToAdd.length,
removed: labelsToRemove.length,
created: createdLabels.length,
};
} else {
console.log('[WORKFLOW_FUNCTIONS] No label changes needed - labels already match');
return { applied: false, created: createdLabels.length };
}
},
};

View File

@@ -172,7 +172,7 @@
"DROP_AGENT_TABLES": "false",
"THREAD_SYNC_MAX_COUNT": "60",
"THREAD_SYNC_LOOP": "false",
"DISABLE_WORKFLOWS": "true",
"DISABLE_WORKFLOWS": "false",
"AUTORAG_ID": "",
"USE_OPENAI": "true",
"CLOUDFLARE_ACCOUNT_ID": "",
@@ -390,7 +390,7 @@
"DROP_AGENT_TABLES": "false",
"THREAD_SYNC_MAX_COUNT": "60",
"THREAD_SYNC_LOOP": "true",
"DISABLE_WORKFLOWS": "true",
"DISABLE_WORKFLOWS": "false",
},
"kv_namespaces": [
{

143
scripts/bun.lock Normal file
View File

@@ -0,0 +1,143 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "scripts",
"dependencies": {
"@faker-js/faker": "9.8.0",
"@inquirer/prompts": "7.5.1",
"cmd-ts": "^0.13.0",
"resend": "4.5.1",
},
},
},
"packages": {
"@faker-js/faker": ["@faker-js/faker@9.8.0", "", {}, "sha512-U9wpuSrJC93jZBxx/Qq2wPjCuYISBueyVUGK7qqdmj7r/nxaxwW8AQDCLeRO7wZnjj94sh3p246cAYjUKuqgfg=="],
"@inquirer/checkbox": ["@inquirer/checkbox@4.2.0", "", { "dependencies": { "@inquirer/core": "^10.1.15", "@inquirer/figures": "^1.0.13", "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-fdSw07FLJEU5vbpOPzXo5c6xmMGDzbZE2+niuDHX5N6mc6V0Ebso/q3xiHra4D73+PMsC8MJmcaZKuAAoaQsSA=="],
"@inquirer/confirm": ["@inquirer/confirm@5.1.14", "", { "dependencies": { "@inquirer/core": "^10.1.15", "@inquirer/type": "^3.0.8" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q=="],
"@inquirer/core": ["@inquirer/core@10.1.15", "", { "dependencies": { "@inquirer/figures": "^1.0.13", "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA=="],
"@inquirer/editor": ["@inquirer/editor@4.2.15", "", { "dependencies": { "@inquirer/core": "^10.1.15", "@inquirer/type": "^3.0.8", "external-editor": "^3.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-wst31XT8DnGOSS4nNJDIklGKnf+8shuauVrWzgKegWUe28zfCftcWZ2vktGdzJgcylWSS2SrDnYUb6alZcwnCQ=="],
"@inquirer/expand": ["@inquirer/expand@4.0.17", "", { "dependencies": { "@inquirer/core": "^10.1.15", "@inquirer/type": "^3.0.8", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-PSqy9VmJx/VbE3CT453yOfNa+PykpKg/0SYP7odez1/NWBGuDXgPhp4AeGYYKjhLn5lUUavVS/JbeYMPdH50Mw=="],
"@inquirer/figures": ["@inquirer/figures@1.0.13", "", {}, "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw=="],
"@inquirer/input": ["@inquirer/input@4.2.1", "", { "dependencies": { "@inquirer/core": "^10.1.15", "@inquirer/type": "^3.0.8" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-tVC+O1rBl0lJpoUZv4xY+WGWY8V5b0zxU1XDsMsIHYregdh7bN5X5QnIONNBAl0K765FYlAfNHS2Bhn7SSOVow=="],
"@inquirer/number": ["@inquirer/number@3.0.17", "", { "dependencies": { "@inquirer/core": "^10.1.15", "@inquirer/type": "^3.0.8" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-GcvGHkyIgfZgVnnimURdOueMk0CztycfC8NZTiIY9arIAkeOgt6zG57G+7vC59Jns3UX27LMkPKnKWAOF5xEYg=="],
"@inquirer/password": ["@inquirer/password@4.0.17", "", { "dependencies": { "@inquirer/core": "^10.1.15", "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-DJolTnNeZ00E1+1TW+8614F7rOJJCM4y4BAGQ3Gq6kQIG+OJ4zr3GLjIjVVJCbKsk2jmkmv6v2kQuN/vriHdZA=="],
"@inquirer/prompts": ["@inquirer/prompts@7.5.1", "", { "dependencies": { "@inquirer/checkbox": "^4.1.6", "@inquirer/confirm": "^5.1.10", "@inquirer/editor": "^4.2.11", "@inquirer/expand": "^4.0.13", "@inquirer/input": "^4.1.10", "@inquirer/number": "^3.0.13", "@inquirer/password": "^4.0.13", "@inquirer/rawlist": "^4.1.1", "@inquirer/search": "^3.0.13", "@inquirer/select": "^4.2.1" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-5AOrZPf2/GxZ+SDRZ5WFplCA2TAQgK3OYrXCYmJL5NaTu4ECcoWFlfUZuw7Es++6Njv7iu/8vpYJhuzxUH76Vg=="],
"@inquirer/rawlist": ["@inquirer/rawlist@4.1.5", "", { "dependencies": { "@inquirer/core": "^10.1.15", "@inquirer/type": "^3.0.8", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-R5qMyGJqtDdi4Ht521iAkNqyB6p2UPuZUbMifakg1sWtu24gc2Z8CJuw8rP081OckNDMgtDCuLe42Q2Kr3BolA=="],
"@inquirer/search": ["@inquirer/search@3.1.0", "", { "dependencies": { "@inquirer/core": "^10.1.15", "@inquirer/figures": "^1.0.13", "@inquirer/type": "^3.0.8", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-PMk1+O/WBcYJDq2H7foV0aAZSmDdkzZB9Mw2v/DmONRJopwA/128cS9M/TXWLKKdEQKZnKwBzqu2G4x/2Nqx8Q=="],
"@inquirer/select": ["@inquirer/select@4.3.1", "", { "dependencies": { "@inquirer/core": "^10.1.15", "@inquirer/figures": "^1.0.13", "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Gfl/5sqOF5vS/LIrSndFgOh7jgoe0UXEizDqahFRkq5aJBLegZ6WjuMh/hVEJwlFQjyLq1z9fRtvUMkb7jM1LA=="],
"@inquirer/type": ["@inquirer/type@3.0.8", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw=="],
"@react-email/render": ["@react-email/render@1.0.6", "", { "dependencies": { "html-to-text": "9.0.5", "prettier": "3.5.3", "react-promise-suspense": "0.3.4" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-zNueW5Wn/4jNC1c5LFgXzbUdv5Lhms+FWjOvWAhal7gx5YVf0q6dPJ0dnR70+ifo59gcMLwCZEaTS9EEuUhKvQ=="],
"@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="],
"ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"chardet": ["chardet@0.7.0", "", {}, "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="],
"cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="],
"cmd-ts": ["cmd-ts@0.13.0", "", { "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", "didyoumean": "^1.2.2", "strip-ansi": "^6.0.0" } }, "sha512-nsnxf6wNIM/JAS7T/x/1JmbEsjH0a8tezXqqpaL0O6+eV0/aDEnRxwjxpu0VzDdRcaC1ixGSbRlUuf/IU59I4g=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
"didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="],
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"external-editor": ["external-editor@3.1.0", "", { "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", "tmp": "^0.0.33" } }, "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew=="],
"fast-deep-equal": ["fast-deep-equal@2.0.1", "", {}, "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"html-to-text": ["html-to-text@9.0.5", "", { "dependencies": { "@selderee/plugin-htmlparser2": "^0.11.0", "deepmerge": "^4.3.1", "dom-serializer": "^2.0.0", "htmlparser2": "^8.0.2", "selderee": "^0.11.0" } }, "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg=="],
"htmlparser2": ["htmlparser2@8.0.2", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1", "entities": "^4.4.0" } }, "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA=="],
"iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"mute-stream": ["mute-stream@2.0.0", "", {}, "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA=="],
"os-tmpdir": ["os-tmpdir@1.0.2", "", {}, "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="],
"parseley": ["parseley@0.12.1", "", { "dependencies": { "leac": "^0.6.0", "peberminta": "^0.9.0" } }, "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw=="],
"peberminta": ["peberminta@0.9.0", "", {}, "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ=="],
"prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
"react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="],
"react-dom": ["react-dom@19.1.1", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.1" } }, "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw=="],
"react-promise-suspense": ["react-promise-suspense@0.3.4", "", { "dependencies": { "fast-deep-equal": "^2.0.1" } }, "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ=="],
"resend": ["resend@4.5.1", "", { "dependencies": { "@react-email/render": "1.0.6" } }, "sha512-ryhHpZqCBmuVyzM19IO8Egtc2hkWI4JOL5lf5F3P7Dydu3rFeX6lHNpGqG0tjWoZ63rw0l731JEmuJZBdDm3og=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
"selderee": ["selderee@0.11.0", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="],
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"tmp": ["tmp@0.0.33", "", { "dependencies": { "os-tmpdir": "~1.0.2" } }, "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw=="],
"type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
"wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
"yoctocolors-cjs": ["yoctocolors-cjs@2.1.2", "", {}, "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA=="],
}
}

View File

@@ -1,11 +1,11 @@
import { sendEmailsCommand } from './send-emails/index';
import { seedStyleCommand } from './seed-style/seeder';
// import { seedStyleCommand } from './seed-style/seeder';
import { subcommands, run } from 'cmd-ts';
const app = subcommands({
name: 'scripts',
cmds: {
'seed-style': seedStyleCommand,
// 'seed-style': seedStyleCommand,
'send-emails': sendEmailsCommand,
},
});