Add slack and github connectors

This commit is contained in:
mudler
2024-04-09 18:24:47 +02:00
parent 414a973282
commit 4453f43bec
11 changed files with 423 additions and 34 deletions

54
README.md Normal file
View File

@@ -0,0 +1,54 @@
## Connectors
### Github (issues)
Create an user and a PAT token:
```json
{
"token": "PAT_TOKEN",
"repository": "testrepo",
"owner": "ci-forks",
"botUserName": "localai-bot"
}
```
### Discord
Follow the steps in: https://discordpy.readthedocs.io/en/stable/discord.html to create a discord bot.
The token of the bot is in the "Bot" tab. Also enable " Message Content Intent " in the Bot tab!
```json
{
"token": "Bot DISCORDTOKENHERE",
"defaultChannel": "OPTIONALCHANNELINT"
}
```
### Slack
See slack.yaml
- Create a new App from a manifest (copy-paste from `slack.yaml`)
- Create Oauth token bot token from "OAuth & Permissions" -> "OAuth Tokens for Your Workspace"
- Create App level token (from "Basic Information" -> "App-Level Tokens" ( `scope connections:writeRoute authorizations:read` ))
In the UI, when configuring the connector:
```json
{
"botToken": "xoxb-...",
"appToken": "xapp-1-..."
}
```
### Telegram
Ask a token to @botfather
In the UI, when configuring the connector:
```json
{ "token": "botfathertoken" }
```

View File

@@ -557,21 +557,26 @@ func (a *Agent) Run() error {
// Expose a REST API to interact with the agent to ask it things // Expose a REST API to interact with the agent to ask it things
todoTimer := time.NewTicker(a.options.periodicRuns) //todoTimer := time.NewTicker(a.options.periodicRuns)
timer := time.NewTimer(a.options.periodicRuns)
for { for {
select { select {
case job := <-a.jobQueue: case job := <-a.jobQueue:
// Consume the job and generate a response // Consume the job and generate a response
// TODO: Give a short-term memory to the agent // TODO: Give a short-term memory to the agent
a.consumeJob(job, UserRole) a.consumeJob(job, UserRole)
timer.Reset(a.options.periodicRuns)
case <-a.context.Done(): case <-a.context.Done():
// Agent has been canceled, return error // Agent has been canceled, return error
return ErrContextCanceled return ErrContextCanceled
case <-todoTimer.C: case <-timer.C:
if !a.options.standaloneJob { if !a.options.standaloneJob {
continue continue
} }
// TODO: We should also do not immediately fire this timer but
// instead have a cool-off timer starting after we received the last job
a.periodicallyRun() a.periodicallyRun()
timer.Reset(a.options.periodicRuns)
} }
} }
} }

View File

@@ -121,6 +121,8 @@ func (a *AgentPool) List() []string {
const ( const (
ConnectorTelegram = "telegram" ConnectorTelegram = "telegram"
ConnectorSlack = "slack" ConnectorSlack = "slack"
ConnectorDiscord = "discord"
ConnectorGithubIssues = "github-issues"
ActionSearch = "search" ActionSearch = "search"
) )
@@ -154,7 +156,12 @@ type Connector interface {
Start(a *Agent) Start(a *Agent)
} }
var AvailableConnectors = []string{ConnectorTelegram, ConnectorSlack} var AvailableConnectors = []string{
ConnectorTelegram,
ConnectorSlack,
ConnectorDiscord,
ConnectorGithubIssues,
}
func (a *AgentConfig) availableConnectors() []Connector { func (a *AgentConfig) availableConnectors() []Connector {
connectors := []Connector{} connectors := []Connector{}
@@ -180,6 +187,10 @@ func (a *AgentConfig) availableConnectors() []Connector {
connectors = append(connectors, cc) connectors = append(connectors, cc)
case ConnectorSlack: case ConnectorSlack:
connectors = append(connectors, connector.NewSlack(config)) connectors = append(connectors, connector.NewSlack(config))
case ConnectorDiscord:
connectors = append(connectors, connector.NewDiscord(config))
case ConnectorGithubIssues:
connectors = append(connectors, connector.NewGithub(config))
} }
} }
return connectors return connectors

View File

@@ -0,0 +1,105 @@
package connector
import (
"fmt"
"strings"
"github.com/bwmarrin/discordgo"
"github.com/mudler/local-agent-framework/agent"
)
type Discord struct {
token string
defaultChannel string
}
func NewDiscord(config map[string]string) *Discord {
return &Discord{
token: config["token"],
defaultChannel: config["defaultChannel"],
}
}
func (d *Discord) AgentResultCallback() func(state agent.ActionState) {
return func(state agent.ActionState) {
// Send the result to the bot
}
}
func (d *Discord) AgentReasoningCallback() func(state agent.ActionCurrentState) bool {
return func(state agent.ActionCurrentState) bool {
// Send the reasoning to the bot
return true
}
}
func (d *Discord) Start(a *agent.Agent) {
Token := d.token
// Create a new Discord session using the provided bot token.
dg, err := discordgo.New(Token)
if err != nil {
fmt.Println("error creating Discord session,", err)
return
}
// Register the messageCreate func as a callback for MessageCreate events.
dg.AddHandler(d.messageCreate(a))
// In this example, we only care about receiving message events.
dg.Identify.Intents = discordgo.IntentsGuildMessages | discordgo.IntentsDirectMessages | discordgo.IntentMessageContent
// Open a websocket connection to Discord and begin listening.
err = dg.Open()
if err != nil {
fmt.Println("error opening connection,", err)
return
}
go func() {
<-a.Context().Done()
dg.Close()
}()
}
// This function will be called (due to AddHandler above) every time a new
// message is created on any channel that the authenticated bot has access to.
func (d *Discord) messageCreate(a *agent.Agent) func(s *discordgo.Session, m *discordgo.MessageCreate) {
return func(s *discordgo.Session, m *discordgo.MessageCreate) {
// Ignore all messages created by the bot itself
// This isn't required in this specific example but it's a good practice.
if m.Author.ID == s.State.User.ID {
return
}
interact := func() {
//m := m.ContentWithMentionsReplaced()
content := m.Content
content = strings.ReplaceAll(content, "<@"+s.State.User.ID+"> ", "")
job := a.Ask(
agent.WithText(
content,
),
)
_, err := s.ChannelMessageSend(m.ChannelID, job.Response)
if err != nil {
fmt.Println("error sending message,", err)
}
}
// Interact if we are mentioned
for _, mention := range m.Mentions {
if mention.ID == s.State.User.ID {
interact()
return
}
}
// Or we are in the default channel (if one is set!)
if d.defaultChannel != "" && m.ChannelID == d.defaultChannel {
interact()
return
}
}
}

View File

@@ -0,0 +1,153 @@
package connector
import (
"fmt"
"strings"
"time"
"github.com/google/go-github/v61/github"
"github.com/mudler/local-agent-framework/agent"
"github.com/sashabaranov/go-openai"
)
type GithubIssues struct {
token string
repository string
owner string
agent *agent.Agent
pollInterval time.Duration
client *github.Client
}
func NewGithub(config map[string]string) *GithubIssues {
client := github.NewClient(nil).WithAuthToken(config["token"])
interval, err := time.ParseDuration(config["pollInterval"])
if err != nil {
interval = 1 * time.Minute
}
return &GithubIssues{
client: client,
token: config["token"],
repository: config["repository"],
owner: config["owner"],
pollInterval: interval,
}
}
func (g *GithubIssues) AgentResultCallback() func(state agent.ActionState) {
return func(state agent.ActionState) {
// Send the result to the bot
}
}
func (g *GithubIssues) AgentReasoningCallback() func(state agent.ActionCurrentState) bool {
return func(state agent.ActionCurrentState) bool {
// Send the reasoning to the bot
return true
}
}
func (g *GithubIssues) Start(a *agent.Agent) {
// Start the connector
g.agent = a
go func() {
ticker := time.NewTicker(g.pollInterval)
for {
select {
case <-ticker.C:
fmt.Println("Fire in da hole!")
g.issuesService()
case <-a.Context().Done():
return
}
}
}()
}
func (g *GithubIssues) issuesService() {
user, _, err := g.client.Users.Get(g.agent.Context(), "")
if err != nil {
fmt.Printf("\nerror: %v\n", err)
return
}
issues, _, err := g.client.Issues.ListByRepo(
g.agent.Context(),
g.owner,
g.repository,
&github.IssueListByRepoOptions{})
if err != nil {
fmt.Println("Error listing issues", err)
}
for _, issue := range issues {
// Do something with the issue
if issue.IsPullRequest() {
continue
}
messages := []openai.ChatCompletionMessage{
{
Role: "system",
Content: fmt.Sprintf("This is a conversation with an user that opened a Github issue with title '%s'.", issue.GetTitle()),
},
{
Role: "user",
Content: issue.GetBody(),
},
}
comments, _, _ := g.client.Issues.ListComments(g.agent.Context(), g.owner, g.repository, issue.GetNumber(),
&github.IssueListCommentsOptions{})
mustAnswer := false
botAnsweredAlready := false
for i, comment := range comments {
role := "user"
if comment.GetUser().GetLogin() == user.GetLogin() {
botAnsweredAlready = true
role = "assistant"
}
messages = append(messages, openai.ChatCompletionMessage{
Role: role,
Content: comment.GetBody(),
})
// if last comment is from the user and mentions the bot username, we must answer
if comment.User.GetName() != user.GetLogin() && len(comments)-1 == i {
if strings.Contains(comment.GetBody(), fmt.Sprintf("@%s", user.GetLogin())) {
fmt.Println("Bot was mentioned in the last comment")
mustAnswer = true
}
}
}
if len(comments) == 0 || !botAnsweredAlready {
// if no comments, or bot didn't answer yet, we must answer
fmt.Println("No comments, or bot didn't answer yet")
fmt.Println("Comments:", len(comments))
fmt.Println("Bot answered already", botAnsweredAlready)
mustAnswer = true
}
if !mustAnswer {
continue
}
res := g.agent.Ask(
agent.WithConversationHistory(messages),
)
_, _, err := g.client.Issues.CreateComment(
g.agent.Context(),
g.owner, g.repository,
issue.GetNumber(), &github.IssueComment{
Body: github.String(res.Response),
},
)
if err != nil {
fmt.Println("Error creating comment", err)
}
}
}

View File

@@ -41,15 +41,15 @@ func (t *Slack) AgentReasoningCallback() func(state agent.ActionCurrentState) bo
func (t *Slack) Start(a *agent.Agent) { func (t *Slack) Start(a *agent.Agent) {
api := slack.New( api := slack.New(
t.botToken, t.botToken,
slack.OptionDebug(true), // slack.OptionDebug(true),
slack.OptionLog(log.New(os.Stdout, "api: ", log.Lshortfile|log.LstdFlags)), slack.OptionLog(log.New(os.Stdout, "api: ", log.Lshortfile|log.LstdFlags)),
slack.OptionAppLevelToken(t.appToken), slack.OptionAppLevelToken(t.appToken),
) )
client := socketmode.New( client := socketmode.New(
api, api,
socketmode.OptionDebug(true), // socketmode.OptionDebug(true),
socketmode.OptionLog(log.New(os.Stdout, "socketmode: ", log.Lshortfile|log.LstdFlags)), //socketmode.OptionLog(log.New(os.Stdout, "socketmode: ", log.Lshortfile|log.LstdFlags)),
) )
go func() { go func() {
for evt := range client.Events { for evt := range client.Events {

View File

@@ -14,22 +14,29 @@ import (
type Telegram struct { type Telegram struct {
Token string Token string
lastChatID int64 lastChatID int64
bot *bot.Bot
agent *agent.Agent
} }
// Send any text message to the bot after the bot has been started // Send any text message to the bot after the bot has been started
func (t *Telegram) AgentResultCallback() func(state agent.ActionState) { func (t *Telegram) AgentResultCallback() func(state agent.ActionState) {
return func(state agent.ActionState) { return func(state agent.ActionState) {
// Send the result to the bot t.bot.SetMyDescription(t.agent.Context(), &bot.SetMyDescriptionParams{
Description: state.Reasoning,
})
} }
} }
func (t *Telegram) AgentReasoningCallback() func(state agent.ActionCurrentState) bool {
return func(state agent.ActionCurrentState) bool {
// Send the reasoning to the bot
return true
}
func (t *Telegram) AgentReasoningCallback() func(state agent.ActionCurrentState) bool {
return func(state agent.ActionCurrentState) bool {
t.bot.SetMyDescription(t.agent.Context(), &bot.SetMyDescriptionParams{
Description: state.Reasoning,
})
return true
} }
}
func (t *Telegram) Start(a *agent.Agent) { func (t *Telegram) Start(a *agent.Agent) {
ctx, cancel := signal.NotifyContext(a.Context(), os.Interrupt) ctx, cancel := signal.NotifyContext(a.Context(), os.Interrupt)
defer cancel() defer cancel()
@@ -54,6 +61,9 @@ func (t *Telegram) Start(a *agent.Agent) {
panic(err) panic(err)
} }
t.bot = b
t.agent = a
go func() { go func() {
for m := range a.ConversationChannel() { for m := range a.ConversationChannel() {
if t.lastChatID == 0 { if t.lastChatID == 0 {

View File

@@ -19,28 +19,25 @@
<input type="text" name="name" id="name" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white" placeholder="Name"> <input type="text" name="name" id="name" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white" placeholder="Name">
</div> </div>
<div id="connectorsSection"> <div id="connectorsSection">
<div class="connector mb-4">
</div>
</div> </div>
<button type="button" id="addConnectorButton" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-500 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"> <button type="button" id="addConnectorButton" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-500 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
Add Connector Add Connector
</button> </button>
<script> <script>
const actions = `{{ range .Actions }}<option value="{{.}}">{{.}}</option>{{ end }}`;
const connectors = `{{ range .Connectors }}<option value="{{.}}">{{.}}</option>{{ end }}`;
document.getElementById('addConnectorButton').addEventListener('click', function() { document.getElementById('addConnectorButton').addEventListener('click', function() {
const connectorsSection = document.getElementById('connectorsSection'); const connectorsSection = document.getElementById('connectorsSection');
const newConnectorIndex = connectorsSection.getElementsByClassName('connector').length; const newConnectorIndex = connectorsSection.getElementsByClassName('connector').length;
const newConnectorHTML = ` const newConnectorHTML = `
<div class="connector mb-4"> <div class="connector mb-4">
<div class="mb-4"> <div class="mb-4">
<label for="connectorType${newConnectorIndex}" class="block text-lg font-medium text-gray-400">Connector Type</label> <label for="connectorType${newConnectorIndex}" class="block text-lg font-medium text-gray-400">Connector Type</label>
<select name="connectors[${newConnectorIndex}].type" id="connectorType${newConnectorIndex}" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white"> <select name="connectors[${newConnectorIndex}].type" id="connectorType${newConnectorIndex}" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white">
{{ range .Connectors }} `+connectors+` </select>
<option value="{{.}}">{{.}}</option>
{{ end }}
</select>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label for="connectorConfig${newConnectorIndex}" class="block text-lg font-medium text-gray-400">Connector Config (JSON)</label> <label for="connectorConfig${newConnectorIndex}" class="block text-lg font-medium text-gray-400">Connector Config (JSON)</label>
@@ -54,31 +51,28 @@
</script> </script>
<div class="mb-4" id="action_box"> <div class="mb-4" id="action_box">
<div class="action mb-4">
</div>
</div> </div>
<button id="action_button" type="button" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-500 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">Add action</button> <button id="action_button" type="button" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-500 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">Add action</button>
<script> <script>
document.getElementById('action_button').addEventListener('click', function() { document.getElementById('action_button').addEventListener('click', function() {
const actionsSection = document.getElementById('action_box'); const actionsSection = document.getElementById('action_box');
const ii = actionsSection.getElementsByClassName('connector').length; const ii = actionsSection.getElementsByClassName('action').length;
const newConnectorHTML = ` const newActionHTML = `
<div class="action mb-4"> <div class="action mb-4">
<label for="actionsName${ii}" class="block text-lg font-medium text-gray-400">Connector Type</label> <label for="actionsName${ii}" class="block text-lg font-medium text-gray-400">Action</label>
<select name="actions[${ii}].name" id="actionsName${ii}" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white"> <select name="actions[${ii}].name" id="actionsName${ii}" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white">
{{ range .Actions }} `+actions+`</select>
<option value="{{.}}">{{.}}</option>
{{ end }}
</select>
<div class="mb-4"> <div class="mb-4">
<label for="actionsConfig${ii}" class="block text-lg font-medium text-gray-400">Connector Config (JSON)</label> <label for="actionsConfig${ii}" class="block text-lg font-medium text-gray-400">Action Config (JSON)</label>
<textarea id="actionsConfig${ii}" name="actions[${ii}].config" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white" placeholder='{"results":"5"}'></textarea> <textarea id="actionsConfig${ii}" name="actions[${ii}].config" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white" placeholder='{"results":"5"}'></textarea>
</div> </div>
</div> </div>
`; `;
actionsSection.insertAdjacentHTML('beforeend', newConnectorHTML); actionsSection.insertAdjacentHTML('beforeend', newActionHTML);
}); });
</script> </script>

4
go.mod
View File

@@ -5,9 +5,11 @@ go 1.22
toolchain go1.22.2 toolchain go1.22.2
require ( require (
github.com/bwmarrin/discordgo v0.28.1
github.com/donseba/go-htmx v1.8.0 github.com/donseba/go-htmx v1.8.0
github.com/go-telegram/bot v1.2.1 github.com/go-telegram/bot v1.2.1
github.com/gofiber/fiber/v2 v2.52.4 github.com/gofiber/fiber/v2 v2.52.4
github.com/google/go-github/v61 v61.0.0
github.com/onsi/ginkgo/v2 v2.15.0 github.com/onsi/ginkgo/v2 v2.15.0
github.com/onsi/gomega v1.31.1 github.com/onsi/gomega v1.31.1
github.com/sap-nocops/duckduckgogo v0.0.0-20201102135645-176990152850 github.com/sap-nocops/duckduckgogo v0.0.0-20201102135645-176990152850
@@ -23,6 +25,7 @@ require (
github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/logr v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect github.com/gorilla/websocket v1.4.2 // indirect
@@ -34,6 +37,7 @@ require (
github.com/stretchr/testify v1.9.0 // indirect github.com/stretchr/testify v1.9.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/net v0.21.0 // indirect golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.19.0 // indirect golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect

15
go.sum
View File

@@ -4,6 +4,8 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo= github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4=
github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -24,9 +26,14 @@ github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84Egg
github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v61 v61.0.0 h1:VwQCBwhyE9JclCI+22/7mLB1PuU9eowCXKY5pNlu1go=
github.com/google/go-github/v61 v61.0.0/go.mod h1:0WR+KmsWX75G2EbpyGsGmradjo3IiciuI4BmdVCobQY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -69,19 +76,27 @@ github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHY
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

38
slack.yaml Normal file
View File

@@ -0,0 +1,38 @@
## To install:
## Create an app from this manifest.
## Generate tokens:
## appLevel token from "Basic Information" -> "App-Level Tokens" ( scope connections:writeRoute authorizations:read )
## bot token from "OAuth & Permissions" -> "OAuth Tokens for Your Workspace"
## When configuring the connector, supply the tokens with:
## { "botToken": "OAuth Tokens for Your Workspace", "appToken": "App-Level Tokens" }
display_information:
name: LocalAgent
description: LocalAgent bot
background_color: "#0040ff"
features:
bot_user:
display_name: LocalAgent
always_online: true
oauth_config:
scopes:
bot:
- app_mentions:read
- channels:history
- channels:read
- chat:write
- commands
- im:history
- im:read
- im:write
- users:read
- users:read.email
settings:
event_subscriptions:
bot_events:
- app_mention
- message.im
interactivity:
is_enabled: true
org_deploy_enabled: false
socket_mode_enabled: true
token_rotation_enabled: false