-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Add slack-summarizer extension #18959
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Congratulations on your new Raycast extension! 🚀 You can expect an initial review within five business days. Once the PR is approved and merged, the extension will be available on our Store. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Summary
This PR introduces a comprehensive Slack summarizer extension that leverages OpenAI to generate concise summaries of Slack channels and threads directly within Raycast.
- Implements robust rate limiting and pagination handling in
slackApi.js
with exponential backoff for large channel histories - Uses PKCE OAuth flow for secure Slack authentication with proper scoping and token management
- Includes intelligent caching strategies for channel lists (24h) and user data (14 days) to minimize API calls
- Provides customizable OpenAI prompts through preferences while maintaining sensible defaults
- Properly handles thread URL parsing and message formatting with user mention replacements
Note: The implementation follows Raycast's best practices and includes all necessary documentation and configuration files. The CHANGELOG.md correctly uses the {PR_MERGE_DATE} template.
💡 (2/5) Greptile learns from your feedback when you react with 👍/👎!
13 file(s) reviewed, 13 comment(s)
Edit PR Review Bot Settings | Greptile
@@ -0,0 +1,3 @@ | |||
# Slack Summaries Changelog |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Title 'Slack Summaries' in CHANGELOG.md doesn't match 'Slack Summarizer' in package.json title field
# Slack Summaries Changelog | |
# Slack Summarizer Changelog |
"name": "summarize-channel", | ||
"title": "Summarize Slack Channel", | ||
"subtitle": "Slack", | ||
"description": "Summarize a Slack channel threads", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
syntax: Grammar issue in description: 'Summarize a Slack channel threads' should be 'Summarize Slack channel threads' or 'Summarize a Slack channel's threads'
"description": "Summarize a Slack channel threads", | |
"description": "Summarize Slack channel threads", |
"name": "openaiPrompt", | ||
"type": "textfield", | ||
"title": "OpenAI prompt", | ||
"description": "Custom prompt used when summarising a channel", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
syntax: Inconsistent spelling: 'summarising' should be 'summarizing' to match American English used elsewhere
"description": "Custom prompt used when summarising a channel", | |
"description": "Custom prompt used when summarizing a channel", |
"placeholder": "gpt-4.1", | ||
"default": "gpt-4.1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: gpt-4.1 is not a valid OpenAI model. Should use gpt-4 or gpt-3.5-turbo as placeholder and default
"placeholder": "gpt-4.1", | |
"default": "gpt-4.1", | |
"placeholder": "gpt-4", | |
"default": "gpt-4", |
} catch (error) { | ||
toast.style = Toast.Style.Failure; | ||
toast.title = "Couldn't generate summary"; | ||
if (error instanceof Error) { | ||
toast.message = error.message; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Consider using showFailureToast from @raycast/utils instead of manually setting toast properties
} catch (error) { | |
toast.style = Toast.Style.Failure; | |
toast.title = "Couldn't generate summary"; | |
if (error instanceof Error) { | |
toast.message = error.message; | |
} | |
} catch (error) { | |
showFailureToast(error, { title: "Couldn't generate summary" }); | |
if (error instanceof Error) { | |
toast.message = error.message; | |
} |
].join(","), | ||
}); | ||
|
||
let cachedApp; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: cachedApp should be declared as let cachedApp: App | undefined to ensure proper typing
let cachedApp; | |
let cachedApp: App | undefined; |
export async function getSlackApp() { | ||
if (cachedApp) return cachedApp; | ||
|
||
const accessToken = await slackOAuth.authorize(); // PKCE flow |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: slackOAuth.authorize() should be wrapped in try/catch to handle potential auth failures gracefully
const accessToken = await slackOAuth.authorize(); // PKCE flow | |
try { | |
const accessToken = await slackOAuth.authorize(); // PKCE flow | |
} catch (error) { | |
showFailureToast(error, { title: "Could not authorize with Slack" }); | |
throw error; | |
} |
async function callOpenAIChatCompletion(messages, model = defaultModel) { | ||
const res = await openai.chat.completions.create({ | ||
model, | ||
temperature: 0.2, | ||
messages, | ||
}); | ||
return res.choices?.[0]?.message?.content?.trim() || "[No summary]"; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: API call not wrapped in try-catch block. Could fail silently if API errors occur.
async function callOpenAIChatCompletion(messages, model = defaultModel) { | |
const res = await openai.chat.completions.create({ | |
model, | |
temperature: 0.2, | |
messages, | |
}); | |
return res.choices?.[0]?.message?.content?.trim() || "[No summary]"; | |
} | |
async function callOpenAIChatCompletion(messages, model = defaultModel) { | |
try { | |
const res = await openai.chat.completions.create({ | |
model, | |
temperature: 0.2, | |
messages, | |
}); | |
return res.choices?.[0]?.message?.content?.trim() || "[No summary]"; | |
} catch (error) { | |
showFailureToast(error, { title: "Could not get OpenAI completion" }); | |
return "[Error getting summary]"; | |
} | |
} |
import { callOpenAIChannel, callOpenAIThread } from "./openaiApi"; | ||
import { getChannelIdByName, getAllUsers, fetchFullThread, getThreadsForChannel } from "./slackApi"; | ||
|
||
let userMap = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Using a global mutable userMap could cause race conditions if multiple summarizations run concurrently. Consider making this scoped to each summarization request.
const oldestTs = Math.floor(Date.now() / 1000) - days * 24 * 60 * 60; | ||
const result = await getThreadsForChannel(channelId, oldestTs); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Verify that days is a positive number to avoid invalid timestamp calculations
const oldestTs = Math.floor(Date.now() / 1000) - days * 24 * 60 * 60; | |
const result = await getThreadsForChannel(channelId, oldestTs); | |
if (days <= 0) throw new Error("Days must be a positive number"); | |
const oldestTs = Math.floor(Date.now() / 1000) - days * 24 * 60 * 60; | |
const result = await getThreadsForChannel(channelId, oldestTs); |
Description
Summarize any Slack channel, thread, or message directly from Raycast using OpenAI.
Perfect for catching-up after vacations, keeping stakeholders in the loop, or turning noisy discussions into succinct, bullet-point briefs.
Screencast
Raycast.extension.mp4
Checklist
npm run build
and tested this distribution build in Raycastassets
folder are used by the extension itselfREADME
are placed outside of themetadata
folder