cody-slack: initial implementation (#50470)

Cody Slack bot experimental implementation. 
- Take a look at README.md for technical details. 
- Introduces new client package `cody-slack`.
- No changes made to other client packages.

## Test plan

CI and talk to @cody_dev in Slack 😜
This commit is contained in:
Valery Bugakov 2023-04-13 19:31:12 -07:00 committed by GitHub
parent ba4cbedb6b
commit ea74d8d784
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1100 additions and 60 deletions

View File

@ -8,6 +8,7 @@ client/client-api/node_modules
client/codeintellify/node_modules
client/cody/node_modules
client/cody-shared/node_modules
client/cody-slack/node_modules
client/cody-ui/node_modules
client/cody-web/node_modules
client/common/node_modules

View File

@ -0,0 +1,2 @@
/dist/
/out/

View File

@ -0,0 +1,24 @@
// @ts-check
const baseConfig = require('../../.eslintrc')
module.exports = {
extends: '../../.eslintrc.js',
parserOptions: {
...baseConfig.parserOptions,
project: [__dirname + '/tsconfig.json'],
},
overrides: baseConfig.overrides,
rules: {
'id-length': 'off',
'no-console': 'off',
'no-restricted-imports': [
'error',
{
patterns: ['!@sourcegraph/cody-shared/*'], // allow any imports from the @sourcegraph/cody-shared package
},
],
'unicorn/filename-case': 'off',
'arrow-body-style': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
},
}

3
client/cody-slack/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules/
out/
dist/

52
client/cody-slack/BUILD.bazel generated Normal file
View File

@ -0,0 +1,52 @@
load("@aspect_rules_js//npm:defs.bzl", "npm_package")
load("@aspect_rules_ts//ts:defs.bzl", "ts_config")
load("@npm//:defs.bzl", "npm_link_all_packages")
load("//dev:defs.bzl", "ts_project")
npm_link_all_packages(name = "node_modules")
ts_config(
name = "tsconfig",
src = "tsconfig.json",
visibility = ["//client:__subpackages__"],
deps = [
"//:tsconfig",
"//client/cody-shared:tsconfig",
"//client/common:tsconfig",
],
)
ts_project(
name = "cody-slack",
srcs = [
"src/app.ts",
"src/constants.ts",
"src/mention-handler.ts",
"src/services/codebase-context.ts",
"src/services/openai-completions-client.ts",
"src/services/sourcegraph-client.ts",
"src/services/stream-completions.ts",
"src/slack/helpers.ts",
"src/slack/init.ts",
"src/slack/message-interaction.ts",
"src/slack/preamble.ts",
],
tsconfig = ":tsconfig",
deps = [
":node_modules/@slack/bolt",
":node_modules/@slack/web-api",
":node_modules/@sourcegraph/cody-shared",
":node_modules/openai",
"//:node_modules/@types/lodash",
"//:node_modules/envalid",
"//:node_modules/lodash",
],
)
npm_package(
name = "cody-slack_pkg",
srcs = [
"package.json",
":cody-slack",
],
)

201
client/cody-slack/LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2022 Sourcegraph, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,23 @@
## Cody Slack (experimental)
Cody-Slack is an experimental Slack bot designed to bring the power of Cody to your Slack workspace. The main goal is to provide a useful developer experience tool that shares knowledge directly within Slack, interacts with different data stores (Sourcegraph repo, handbook, docs website), uses tools like GitHub CLI to do things and ultimately boosts productivity.
## Architecture
Cody-Slack's architecture is similar to the VSCode extension, as it's built on top of the `cody-shared` package. The Slack bot responds only to `app_mention` messages and uses all messages from a Slack thread where it's mentioned as a prompt context to improve responses.
Currently, key settings like repo (`sourcegraph/sourcegraph`), embeddings type (`blended`), and serverEndpoint (`s2`) are hardcoded. The Slack bot leverages Sourcegraph embeddings endpoint and Slack thread messages to create prompts. It then streams results back to the Slack thread, throttling message updates to avoid bumping into the Slack rate limiter.
By default, the Slack bot uses the Claude model, but it also has an implemented OpenAI completion client for future testing.
## Local Development
Use the `sg run cody-slack` command and provide all required environment variables via `sg.config.overwrite.yaml`. You can use `ngrok` to expose the local port to the internet and set the URL as [the Events Request URL](https://api.slack.com/apis/connections/events-api#request-urls) in the Slack configuration. To avoid affecting the production version of the Slack bot, create your own application for local development purposes.
An alternative option is to use [Socket Mode](https://api.slack.com/apis/connections/socket), which automatically connects to the Slack backend. However, Socket Mode has proven unreliable, as it regularly loses events, making local development challenging. Several GitHub issues are related to this problem.
Is there a better way to approach the local development of the Slack bot with the Events API? File a PR to update this section if you know about it!
## Deployment
The production deployment is rather simple: all code is bundled together using esbuild into a single JavaScript file (`pnpm build`), which is then uploaded to a Heroku eco dyno (`pnpm release`). The second command works only for @valerybugakov locally since the deployment is configured for his account. While this approach is suitable for prototyping with a 5-second re-deployment time, it would be great to host the Slack bot on Sourcegraph infrastructure (help wanted 👋).

View File

@ -0,0 +1,11 @@
// @ts-check
/** @type {import('@jest/types').Config.InitialOptions} */
const config = require('../../jest.config.base')
/** @type {import('@jest/types').Config.InitialOptions} */
module.exports = {
...config,
displayName: 'cody-slack',
rootDir: __dirname,
}

View File

@ -0,0 +1,23 @@
{
"name": "@sourcegraph/cody-slack",
"private": true,
"displayName": "Sourcegraph Cody Slack",
"version": "0.0.1",
"license": "Apache-2.0",
"description": "Your programming sidekick powered by AI and Sourcegraph's code search and intelligence.",
"scripts": {
"start": "ts-node-transpile-only ./src/app.ts",
"lint": "pnpm run lint:js",
"lint:js": "eslint --cache '**/*.[tj]s?(x)'",
"build": "esbuild ./src/app.ts --bundle --outfile=dist/app.js --format=cjs --platform=node",
"build-ts": "tsc -b --emitDeclarationOnly",
"release": "pnpm run build && cd dist && git add . && git commit -m wip && git push heroku master"
},
"dependencies": {
"@slack/bolt": "^3.12.2",
"@slack/web-api": "^6.8.1",
"@sourcegraph/cody-shared": "workspace:*",
"@sourcegraph/common": "workspace:*",
"openai": "^3.2.1"
}
}

View File

@ -0,0 +1,36 @@
import { ENVIRONMENT_CONFIG, DEFAULT_APP_SETTINGS } from './constants'
import { handleHumanMessage } from './mention-handler'
import { createCodebaseContext } from './services/codebase-context'
import { isBotEvent } from './slack/helpers'
import { app } from './slack/init'
const { PORT } = ENVIRONMENT_CONFIG
// Main function to start the bot
async function startBot() {
// Create a context for the codebase using the default app settings
const codebaseContext = await createCodebaseContext(DEFAULT_APP_SETTINGS.codebase, DEFAULT_APP_SETTINGS.contextType)
// Listen for mentions in the Slack app
app.event<'app_mention'>('app_mention', async ({ event }) => {
// Ignore events generated by bots
if (isBotEvent(event)) {
return
}
console.log('APP_MENTION', event.text)
// Process the mention event generated by a human user
await handleHumanMessage(event, codebaseContext)
})
// Start the Slack app on the specified port
return app.start(PORT)
}
// Start the bot and log the status
startBot()
.then(() => console.log(`⚡️ Cody Slack-bot is running on port ${PORT}!`))
.catch(error => {
console.error('Error starting the bot:', error)
process.exit(1)
})

View File

@ -0,0 +1,19 @@
import { cleanEnv, str, num } from 'envalid'
export const ENVIRONMENT_CONFIG = cleanEnv(process.env, {
PORT: num({ default: 3000 }),
// OPENAI_API_KEY: str(),
SOURCEGRAPH_ACCESS_TOKEN: str(),
SLACK_APP_TOKEN: str(),
SLACK_BOT_TOKEN: str(),
SLACK_SIGNING_SECRET: str(),
})
export const DEFAULT_APP_SETTINGS = {
codebase: 'github.com/sourcegraph/sourcegraph',
serverEndpoint: 'https://sourcegraph.sourcegraph.com',
contextType: 'blended',
debug: 'development',
} as const

View File

@ -0,0 +1,131 @@
import { AppMentionEvent } from '@slack/bolt'
import { Message as SlackReplyMessage } from '@slack/web-api/dist/response/ChannelsRepliesResponse'
import { throttle } from 'lodash'
import { Transcript } from '@sourcegraph/cody-shared/src/chat/transcript'
import { reformatBotMessage } from '@sourcegraph/cody-shared/src/chat/viewHelpers'
import { CodebaseContext } from '@sourcegraph/cody-shared/src/codebase-context'
import { Message as PromptMessage } from '@sourcegraph/cody-shared/src/sourcegraph-api'
import { intentDetector } from './services/sourcegraph-client'
import { streamCompletions } from './services/stream-completions'
import * as slackHelpers from './slack/helpers'
import { interactionFromMessage } from './slack/message-interaction'
import { SLACK_PREAMBLE } from './slack/preamble'
const IN_PROGRESS_MESSAGE = '...✍️'
/**
* Handles human-generated messages in a Slack bot application.
* Processes the messages, generates a prompt, and streams completions.
*/
export async function handleHumanMessage(event: AppMentionEvent, codebaseContext: CodebaseContext): Promise<void> {
const channel = event.channel
const thread_ts = slackHelpers.getEventTs(event)
// Restore transcript from the Slack thread
const messages = await slackHelpers.getThreadMessages(channel, thread_ts)
const transcript = await restoreTranscriptFromSlackThread(codebaseContext, messages)
// Send an in-progress message
const response = await slackHelpers.postMessage(IN_PROGRESS_MESSAGE, channel, thread_ts)
// Generate a prompt and start completion streaming
const prompt = await transcript.toPrompt(SLACK_PREAMBLE)
console.log('PROMPT', prompt)
startCompletionStreaming(prompt, channel, response?.ts)
}
/**
* Restores a transcript from the given Slack thread messages.
*/
async function restoreTranscriptFromSlackThread(codebaseContext: CodebaseContext, messages: SlackReplyMessage[]) {
const transcript = new Transcript()
const mergedMessages = mergeSequentialUserMessages(messages)
for (const [index, message] of mergedMessages.entries()) {
const interaction = await interactionFromMessage(
message.human,
intentDetector,
// Fetch codebase context only for the last message
index === mergedMessages.length - 1 ? codebaseContext : null
)
transcript.addInteraction(interaction)
if (message.assistant?.text) {
transcript.addAssistantResponse(message.assistant?.text)
}
}
return transcript
}
/**
* Starts streaming completions for the given prompt.
*/
function startCompletionStreaming(
promptMessages: PromptMessage[],
channel: string,
inProgressMessageTs?: string
): void {
streamCompletions(promptMessages, {
onChange: text => {
onBotMessageChange(reformatBotMessage(text, ''), channel, inProgressMessageTs)?.catch(console.error)
},
onComplete: () => {
console.log('Streaming complete!')
},
onError: err => {
console.error(err)
},
})
}
/**
* Throttled function to update the bot message when there is a change.
* Ensures message updates are throttled to avoid exceeding Slack API rate limits.
*/
const onBotMessageChange = throttle(async (text: string, channel, inProgressMessageTs?: string) => {
if (inProgressMessageTs) {
await slackHelpers.updateMessage(channel, inProgressMessageTs, text)
} else {
console.error('The in-progress mesasge is not found!')
}
// Throttle message updates to keep Slack API rate limiter happy.
}, 1000)
interface SlackInteraction {
human: SlackReplyMessage
assistant?: SlackReplyMessage
}
/**
* Merges sequential user messages in a Slack thread to avoid missing important context.
*/
function mergeSequentialUserMessages(messages: SlackReplyMessage[]) {
const mergedMessages: SlackInteraction[] = []
for (const message of messages) {
const text = message.text?.replace(/<@[\dA-Z]+>/gm, '').trim()
const lastInteraction = mergedMessages[mergedMessages.length - 1]
const updatedMessage = { ...message, blocks: undefined, text }
if (!lastInteraction) {
mergedMessages.push({ human: updatedMessage })
continue
}
if (message.bot_id) {
if (!lastInteraction.assistant) {
lastInteraction.assistant = updatedMessage
}
} else if (!lastInteraction.assistant) {
lastInteraction.human.text = `${lastInteraction.human.text || ''}; ${text}`
} else {
mergedMessages.push({ human: updatedMessage })
}
}
return mergedMessages
}

View File

@ -0,0 +1,43 @@
import { memoize } from 'lodash'
import { CodebaseContext } from '@sourcegraph/cody-shared/src/codebase-context'
import { SourcegraphEmbeddingsSearchClient } from '@sourcegraph/cody-shared/src/embeddings/client'
import { KeywordContextFetcher } from '@sourcegraph/cody-shared/src/keyword-context'
import { isError } from '@sourcegraph/cody-shared/src/utils'
import { sourcegraphClient } from './sourcegraph-client'
/**
* Memoized function to get the repository ID for a given codebase.
*/
const getRepoId = memoize(async (codebase: string) => {
const repoId = codebase ? await sourcegraphClient.getRepoId(codebase) : null
if (isError(repoId)) {
const errorMessage =
`Cody could not find the '${codebase}' repository on your Sourcegraph instance.\n` +
'Please check that the repository exists and is entered correctly in the cody.codebase setting.'
console.error(errorMessage)
}
return repoId
})
export async function createCodebaseContext(
codebase: string,
contextType: 'embeddings' | 'keyword' | 'none' | 'blended'
) {
const repoId = await getRepoId(codebase)
const embeddingsSearch =
repoId && !isError(repoId) ? new SourcegraphEmbeddingsSearchClient(sourcegraphClient, repoId) : null
const codebaseContext = new CodebaseContext(contextType, embeddingsSearch, new LocalKeywordContextFetcherMock())
return codebaseContext
}
class LocalKeywordContextFetcherMock implements KeywordContextFetcher {
public getContext() {
return Promise.resolve([])
}
}

View File

@ -0,0 +1,85 @@
import { IncomingMessage } from 'http'
import { Configuration, OpenAIApi } from 'openai'
import { SourcegraphCompletionsClient } from '@sourcegraph/cody-shared/src/sourcegraph-api/completions/client'
import {
CompletionCallbacks,
CompletionParameters,
} from '@sourcegraph/cody-shared/src/sourcegraph-api/completions/types'
export class OpenAICompletionsClient extends SourcegraphCompletionsClient {
private openai: OpenAIApi
constructor(
protected apiKey: string,
instanceUrl: string = '',
protected accessToken: string = '',
protected mode: 'development' | 'production' = 'production'
) {
super(instanceUrl, accessToken, mode)
const configuration = new Configuration({
apiKey: this.apiKey,
})
this.openai = new OpenAIApi(configuration)
}
public stream(params: CompletionParameters, cb: CompletionCallbacks) {
this.openai
.createChatCompletion(
{
model: 'gpt-3.5-turbo',
messages: params.messages.map(message => {
return {
role: message.speaker === 'human' ? 'user' : 'assistant',
content: message.text,
}
}),
stream: true,
},
{ responseType: 'stream' }
)
.then(response => {
const stream = response.data as unknown as IncomingMessage
let modelResponseText = ''
stream.on('data', (chunk: Buffer) => {
// Split messames in the event stream.
const payloads = chunk.toString().split('\n\n')
for (const payload of payloads) {
if (payload.includes('[DONE]')) {
return
}
if (payload.startsWith('data:')) {
const data = payload.replaceAll(/(\n)?^data:\s*/g, '') // in case there's multiline data event
try {
const delta = JSON.parse(data.trim())
const newTextChunk = delta.choices[0].delta?.content
if (newTextChunk) {
modelResponseText += newTextChunk
cb.onChange(modelResponseText)
}
} catch (error) {
console.log(
`Error with JSON.parse: ${chunk.toString()}\nPayload: ${payload};\nError: ${error}`
)
cb.onError(error)
}
}
}
})
stream.on('error', e => cb.onError(e.message))
stream.on('end', () => cb.onComplete())
})
.catch(console.error)
return () => {}
}
}

View File

@ -0,0 +1,13 @@
import { SourcegraphIntentDetectorClient } from '@sourcegraph/cody-shared/src/intent-detector/client'
import { SourcegraphGraphQLAPIClient } from '@sourcegraph/cody-shared/src/sourcegraph-api/graphql'
import { DEFAULT_APP_SETTINGS, ENVIRONMENT_CONFIG } from '../constants'
const { SOURCEGRAPH_ACCESS_TOKEN } = ENVIRONMENT_CONFIG
export const sourcegraphClient = new SourcegraphGraphQLAPIClient(
DEFAULT_APP_SETTINGS.serverEndpoint,
SOURCEGRAPH_ACCESS_TOKEN
)
export const intentDetector = new SourcegraphIntentDetectorClient(sourcegraphClient)

View File

@ -0,0 +1,40 @@
import { SOLUTION_TOKEN_LENGTH } from '@sourcegraph/cody-shared/src/prompt/constants'
import { SourcegraphNodeCompletionsClient } from '@sourcegraph/cody-shared/src/sourcegraph-api/completions/nodeClient'
import {
CompletionParameters,
CompletionCallbacks,
Message,
} from '@sourcegraph/cody-shared/src/sourcegraph-api/completions/types'
import { DEFAULT_APP_SETTINGS, ENVIRONMENT_CONFIG } from '../constants'
import { OpenAICompletionsClient } from './openai-completions-client'
const { SOURCEGRAPH_ACCESS_TOKEN } = ENVIRONMENT_CONFIG
const DEFAULT_CHAT_COMPLETION_PARAMETERS: Omit<CompletionParameters, 'messages'> = {
temperature: 0.2,
maxTokensToSample: SOLUTION_TOKEN_LENGTH,
topK: -1,
topP: -1,
}
const completionsClient = getCompletionsClient()
export function streamCompletions(messages: Message[], cb: CompletionCallbacks) {
return completionsClient.stream({ messages, ...DEFAULT_CHAT_COMPLETION_PARAMETERS }, cb)
}
function getCompletionsClient() {
const { OPENAI_API_KEY } = process.env
if (OPENAI_API_KEY) {
return new OpenAICompletionsClient(OPENAI_API_KEY)
}
return new SourcegraphNodeCompletionsClient(
DEFAULT_APP_SETTINGS.serverEndpoint,
SOURCEGRAPH_ACCESS_TOKEN,
DEFAULT_APP_SETTINGS.debug
)
}

View File

@ -0,0 +1,55 @@
import { KnownEventFromType, SlackEvent } from '@slack/bolt'
import { ChatPostMessageResponse } from '@slack/web-api'
import { webClient } from './init'
export async function getThreadMessages(channel: string, thread_ts: string) {
const result = await webClient.conversations.replies({
channel,
ts: thread_ts,
})
return result?.messages || []
}
export function getEventTs(event: KnownEventFromType<'app_mention'> | KnownEventFromType<'message'>) {
if ('thread_ts' in event && event.thread_ts !== event.ts) {
return event.thread_ts || event.ts
}
return event.ts
}
export const isBotEvent = (event: SlackEvent) => {
return 'subtype' in event && event.subtype !== 'bot_message'
}
export async function updateMessage(channel: string, messageTs: string, newText: string): Promise<void> {
const response = await webClient.chat.update({
channel,
ts: messageTs, // The timestamp of the message you want to update.
text: newText, // The new text for the updated message.
})
if (!response.ok) {
throw new Error(`Error updating message: ${response.error}`)
}
}
export async function postMessage(
message: string,
channel: string,
thread_ts: string
): Promise<ChatPostMessageResponse | undefined> {
const response = await webClient.chat.postMessage({
channel,
text: message,
thread_ts, // Use the timestamp of the parent message to reply in the thread.
})
if (!response.ok) {
throw new Error(`Error sending message: ${response.error}`)
}
return response
}

View File

@ -0,0 +1,14 @@
import { App } from '@slack/bolt'
import { WebClient, LogLevel } from '@slack/web-api'
import { ENVIRONMENT_CONFIG } from '../constants'
// Initialize the Bolt.js app and the WebClient using environment variables.
export const app = new App({
appToken: ENVIRONMENT_CONFIG.SLACK_APP_TOKEN,
token: ENVIRONMENT_CONFIG.SLACK_BOT_TOKEN,
signingSecret: ENVIRONMENT_CONFIG.SLACK_SIGNING_SECRET,
logLevel: LogLevel.INFO,
})
export const webClient = new WebClient(ENVIRONMENT_CONFIG.SLACK_BOT_TOKEN)

View File

@ -0,0 +1,55 @@
import { Message } from '@slack/web-api/dist/response/ChannelsRepliesResponse'
import { Interaction } from '@sourcegraph/cody-shared/src/chat/transcript/interaction'
import { CodebaseContext } from '@sourcegraph/cody-shared/src/codebase-context'
import { ContextMessage } from '@sourcegraph/cody-shared/src/codebase-context/messages'
import { IntentDetector } from '@sourcegraph/cody-shared/src/intent-detector'
import { MAX_HUMAN_INPUT_TOKENS } from '@sourcegraph/cody-shared/src/prompt/constants'
import { truncateText } from '@sourcegraph/cody-shared/src/prompt/truncation'
import { getShortTimestamp } from '@sourcegraph/cody-shared/src/timestamp'
export async function interactionFromMessage(
message: Message,
intentDetector: IntentDetector,
codebaseContext: CodebaseContext | null
): Promise<Interaction | null> {
if (!message.text) {
return Promise.resolve(null)
}
const timestamp = getShortTimestamp()
const textWithoutMentions = message.text?.replace(/<@[\dA-Z]+>/gm, '').trim()
const text = truncateText(textWithoutMentions, MAX_HUMAN_INPUT_TOKENS)
const contextMessages =
codebaseContext === null ? Promise.resolve([]) : getContextMessages(text, intentDetector, codebaseContext)
return Promise.resolve(
new Interaction(
{ speaker: 'human', text, displayText: text, timestamp },
{ speaker: 'assistant', text: '', displayText: '', timestamp },
contextMessages
)
)
}
export async function getContextMessages(
text: string,
intentDetector: IntentDetector,
codebaseContext: CodebaseContext
): Promise<ContextMessage[]> {
const contextMessages: ContextMessage[] = []
const isCodebaseContextRequired = await intentDetector.isCodebaseContextRequired(text)
if (isCodebaseContextRequired) {
const codebaseContextMessages = await codebaseContext.getContextMessages(text, {
numCodeResults: 8,
numTextResults: 2,
})
contextMessages.push(...codebaseContextMessages)
}
return contextMessages
}

View File

@ -0,0 +1,52 @@
import { Message } from '@sourcegraph/cody-shared/src/sourcegraph-api'
import { DEFAULT_APP_SETTINGS } from '../constants'
const actions = `You are Cody, an AI-powered coding assistant created by Sourcegraph. You work inside a Slack workspace. You have access to the Slack thread conversation with all the replies. You perform the following actions:
- Answer general programming questions.
- Answer general questions about the Slack thread you're in.
- Answer questions about the code that I have provided to you.
- Generate code that matches a written description.
- Explain what a section of code does.`
const rules = `In your responses, obey the following rules:
- Be as brief and concise as possible without losing clarity.
- The current Slack thread is the same as the conversation you're having with the user. Use this information to answer questions.
- All code snippets have to be markdown-formatted without that language specifier, and placed in-between triple backticks like this \`\`\`.
- Answer questions only if you know the answer or can make a well-informed guess. Otherwise, tell me you don't know and what context I need to provide you for you to answer the question.
- Only reference file names or URLs if you are sure they exist.`
const answer = `Understood. I am Cody, an AI assistant made by Sourcegraph to help with programming tasks and assist in Slack conversations.
I work inside a Slack workspace. I have access have access to the currently active Slack thread conversation with all the replies.
I will answer questions, explain code, and generate code as concisely and clearly as possible.
My responses will be formatted using Markdown syntax for code blocks without language specifiers.
I will acknowledge when I don't know an answer or need more context. I will use the Slack thread conversation history to answer your questions.`
function getSlackPreamble(codebase: string): Message[] {
const preamble = [actions, rules]
const preambleResponse = [answer]
if (codebase) {
const codebasePreamble =
`You have access to the \`${codebase}\` repository. You are able to answer questions about the \`${codebase}\` repository. ` +
`I will provide the relevant code snippets from the \`${codebase}\` repository when necessary to answer my questions.`
preamble.push(codebasePreamble)
preambleResponse.push(
`I have access to the \`${codebase}\` repository and can answer questions about its files.`
)
}
return [
{
speaker: 'human',
text: preamble.join('\n\n'),
},
{
speaker: 'assistant',
text: preambleResponse.join('\n'),
},
]
}
export const SLACK_PREAMBLE = getSlackPreamble(DEFAULT_APP_SETTINGS.codebase)

View File

@ -0,0 +1,20 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"sourceRoot": "src",
"rootDir": ".",
"outDir": "./out",
"baseUrl": "./src",
},
"include": ["src", "package.json", ".eslintrc.js", "jest.config.js"],
"exclude": ["out", "dist"],
"references": [
{
"path": "../cody-shared",
},
{
"path": "../common",
},
],
}

View File

@ -100,6 +100,7 @@ Available commands in `sg.config.yaml`:
* codeintel-executor
* codeintel-executor-firecracker
* codeintel-worker
* cody-slack: Start Cody-Slack locally server locally
* debug-env: Debug env vars
* docsite: Docsite instance serving the docs
* embeddings

View File

@ -941,6 +941,20 @@ importers:
dependencies:
'@sourcegraph/common': link:../common
client/cody-slack:
specifiers:
'@slack/bolt': ^3.12.2
'@slack/web-api': ^6.8.1
'@sourcegraph/cody-shared': workspace:*
'@sourcegraph/common': workspace:*
openai: ^3.2.1
dependencies:
'@slack/bolt': 3.12.2
'@slack/web-api': 6.8.1
'@sourcegraph/cody-shared': link:../cody-shared
'@sourcegraph/common': link:../common
openai: 3.2.1
client/cody-ui:
specifiers:
'@sourcegraph/cody-shared': workspace:*
@ -3231,7 +3245,6 @@ packages:
cpu: [arm]
os: [android]
requiresBuild: true
dev: false
optional: true
/@esbuild/android-arm64/0.16.17:
@ -3257,7 +3270,6 @@ packages:
cpu: [arm64]
os: [android]
requiresBuild: true
dev: false
optional: true
/@esbuild/android-x64/0.16.17:
@ -3283,7 +3295,6 @@ packages:
cpu: [x64]
os: [android]
requiresBuild: true
dev: false
optional: true
/@esbuild/darwin-arm64/0.16.17:
@ -3309,7 +3320,6 @@ packages:
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: false
optional: true
/@esbuild/darwin-x64/0.16.17:
@ -3335,7 +3345,6 @@ packages:
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: false
optional: true
/@esbuild/freebsd-arm64/0.16.17:
@ -3361,7 +3370,6 @@ packages:
cpu: [arm64]
os: [freebsd]
requiresBuild: true
dev: false
optional: true
/@esbuild/freebsd-x64/0.16.17:
@ -3387,7 +3395,6 @@ packages:
cpu: [x64]
os: [freebsd]
requiresBuild: true
dev: false
optional: true
/@esbuild/linux-arm/0.16.17:
@ -3413,7 +3420,6 @@ packages:
cpu: [arm]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@esbuild/linux-arm64/0.16.17:
@ -3439,7 +3445,6 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@esbuild/linux-ia32/0.16.17:
@ -3465,7 +3470,6 @@ packages:
cpu: [ia32]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@esbuild/linux-loong64/0.16.17:
@ -3491,7 +3495,6 @@ packages:
cpu: [loong64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@esbuild/linux-mips64el/0.16.17:
@ -3517,7 +3520,6 @@ packages:
cpu: [mips64el]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@esbuild/linux-ppc64/0.16.17:
@ -3543,7 +3545,6 @@ packages:
cpu: [ppc64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@esbuild/linux-riscv64/0.16.17:
@ -3569,7 +3570,6 @@ packages:
cpu: [riscv64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@esbuild/linux-s390x/0.16.17:
@ -3595,7 +3595,6 @@ packages:
cpu: [s390x]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@esbuild/linux-x64/0.16.17:
@ -3621,7 +3620,6 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@esbuild/netbsd-x64/0.16.17:
@ -3647,7 +3645,6 @@ packages:
cpu: [x64]
os: [netbsd]
requiresBuild: true
dev: false
optional: true
/@esbuild/openbsd-x64/0.16.17:
@ -3673,7 +3670,6 @@ packages:
cpu: [x64]
os: [openbsd]
requiresBuild: true
dev: false
optional: true
/@esbuild/sunos-x64/0.16.17:
@ -3699,7 +3695,6 @@ packages:
cpu: [x64]
os: [sunos]
requiresBuild: true
dev: false
optional: true
/@esbuild/win32-arm64/0.16.17:
@ -3725,7 +3720,6 @@ packages:
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@esbuild/win32-ia32/0.16.17:
@ -3751,7 +3745,6 @@ packages:
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@esbuild/win32-x64/0.16.17:
@ -3777,7 +3770,6 @@ packages:
cpu: [x64]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@eslint/eslintrc/1.4.1:
@ -6828,6 +6820,32 @@ packages:
resolution: {integrity: sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==}
dev: true
/@slack/bolt/3.12.2:
resolution: {integrity: sha512-Rv5apx14Nx25ho7MHigZcmYG+P/TzKB4MEdY/UDM7ntCCmTBdRd5d+teERmGPNalFjz/tEfQ5bw+Z8zZjHIOXA==}
engines: {node: '>=12.13.0', npm: '>=6.12.0'}
dependencies:
'@slack/logger': 3.0.0
'@slack/oauth': 2.6.0
'@slack/socket-mode': 1.3.2
'@slack/types': 2.8.0
'@slack/web-api': 6.8.1
'@types/express': 4.17.11
'@types/node': 18.11.8
'@types/promise.allsettled': 1.0.3
'@types/tsscmp': 1.0.0
axios: 0.26.1
express: 4.18.2
please-upgrade-node: 3.2.0
promise.allsettled: 1.0.2
raw-body: 2.5.1
tsscmp: 1.0.6
transitivePeerDependencies:
- bufferutil
- debug
- supports-color
- utf-8-validate
dev: false
/@slack/logger/2.0.0:
resolution: {integrity: sha512-OkIJpiU2fz6HOJujhlhfIGrc8hB4ibqtf7nnbJQDerG0BqwZCfmgtK5sWzZ0TkXVRBKD5MpLrTmCYyMxoMCgPw==}
engines: {node: '>= 8.9.0', npm: '>= 5.5.1'}
@ -6835,11 +6853,57 @@ packages:
'@types/node': 13.13.5
dev: true
/@slack/logger/3.0.0:
resolution: {integrity: sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA==}
engines: {node: '>= 12.13.0', npm: '>= 6.12.0'}
dependencies:
'@types/node': 13.13.5
dev: false
/@slack/oauth/2.6.0:
resolution: {integrity: sha512-t75jfYUoVVq4x9TnJrNn5VQRfr9n/3Fvuq3M6gf3URmKCvm/kQyZGp0Ff8AK/dzdYYWKDfSVD9GQImhF578MZA==}
engines: {node: '>=12.13.0', npm: '>=6.12.0'}
dependencies:
'@slack/logger': 3.0.0
'@slack/web-api': 6.8.1
'@types/jsonwebtoken': 8.5.9
'@types/node': 13.13.5
jsonwebtoken: 9.0.0
lodash.isstring: 4.0.1
transitivePeerDependencies:
- debug
dev: false
/@slack/socket-mode/1.3.2:
resolution: {integrity: sha512-6LiwYE6k4DNbnctZZSLfERiOzWngAvXogxQEYzUkxeZgh2GC6EdmRq6OEbZXOBe71/K66YVx05VfR7B4b1ScTQ==}
engines: {node: '>=12.13.0', npm: '>=6.12.0'}
dependencies:
'@slack/logger': 3.0.0
'@slack/web-api': 6.8.1
'@types/node': 13.13.5
'@types/p-queue': 2.3.2
'@types/ws': 7.4.7
eventemitter3: 3.1.2
finity: 0.5.4
p-cancelable: 1.1.0
p-queue: 2.4.2
ws: 7.5.9
transitivePeerDependencies:
- bufferutil
- debug
- utf-8-validate
dev: false
/@slack/types/1.7.0:
resolution: {integrity: sha512-aigLPmTO513JxeFyeII/74y+S5jU39tabDWPsZyMHJWCYqK3vCkRvV73NL+Ay+Tq5RC2NgSmkedk1wvQJ6oXLg==}
engines: {node: '>= 8.9.0', npm: '>= 5.5.1'}
dev: true
/@slack/types/2.8.0:
resolution: {integrity: sha512-ghdfZSF0b4NC9ckBA8QnQgC9DJw2ZceDq0BIjjRSv6XAZBXJdWgxIsYz0TYnWSiqsKZGH2ZXbj9jYABZdH3OSQ==}
engines: {node: '>= 12.13.0', npm: '>= 6.12.0'}
dev: false
/@slack/web-api/5.15.0:
resolution: {integrity: sha512-tjQ8Zqv/Fmj9SOL9yIEd7IpTiKfKHi9DKAkfRVeotoX0clMr3SqQtBqO+KZMX27gm7dmgJsQaDKlILyzdCO+IA==}
engines: {node: '>= 8.9.0', npm: '>= 5.5.1'}
@ -6858,6 +6922,25 @@ packages:
- debug
dev: true
/@slack/web-api/6.8.1:
resolution: {integrity: sha512-eMPk2S99S613gcu7odSw/LV+Qxr8A+RXvBD0GYW510wJuTERiTjP5TgCsH8X09+lxSumbDE88wvWbuFuvGa74g==}
engines: {node: '>= 12.13.0', npm: '>= 6.12.0'}
dependencies:
'@slack/logger': 3.0.0
'@slack/types': 2.8.0
'@types/is-stream': 1.1.0
'@types/node': 13.13.5
axios: 0.27.2
eventemitter3: 3.1.2
form-data: 2.5.1
is-electron: 2.2.0
is-stream: 1.1.0
p-queue: 6.6.2
p-retry: 4.6.0
transitivePeerDependencies:
- debug
dev: false
/@snyk/dep-graph/2.6.0:
resolution: {integrity: sha512-9NPk7cTvDNA90NyNQvh87LYKgkCoD67i+FGdRpwA0/CL59RRY7cG37RTFe++Y3ZALxpYK1sLNzU+yeB7vzVhqQ==}
engines: {node: '>=10'}
@ -9083,7 +9166,6 @@ packages:
resolution: {integrity: sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg==}
dependencies:
'@types/node': 13.13.5
dev: true
/@types/isomorphic-fetch/0.0.36:
resolution: {integrity: sha512-ulw4d+vW1HKn4oErSmNN2HYEcHGq0N1C5exlrMM0CRqX1UUpFhGb5lwiom5j9KN3LBJJDLRmYIZz1ghm7FIzZw==}
@ -9182,7 +9264,6 @@ packages:
resolution: {integrity: sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==}
dependencies:
'@types/node': 13.13.5
dev: true
/@types/keyv/3.1.4:
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
@ -9270,6 +9351,10 @@ packages:
/@types/node/16.18.11:
resolution: {integrity: sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==}
/@types/node/18.11.8:
resolution: {integrity: sha512-uGwPWlE0Hj972KkHtCDVwZ8O39GmyjfMane1Z3GUBGGnkZ2USDq7SxLpVIiIHpweY9DS0QTDH0Nw7RNBsAAZ5A==}
dev: false
/@types/normalize-package-data/2.4.0:
resolution: {integrity: sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==}
dev: true
@ -9277,6 +9362,10 @@ packages:
/@types/npmlog/4.1.2:
resolution: {integrity: sha512-4QQmOF5KlwfxJ5IGXFIudkeLCdMABz03RcUXu+LCb24zmln8QW6aDjuGl4d4XPVLf2j+FnjelHTP7dvceAFbhA==}
/@types/p-queue/2.3.2:
resolution: {integrity: sha512-eKAv5Ql6k78dh3ULCsSBxX6bFNuGjTmof5Q/T6PiECDq0Yf8IIn46jCyp3RJvCi8owaEmm3DZH1PEImjBMd/vQ==}
dev: false
/@types/parse-json/4.0.0:
resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==}
@ -9328,6 +9417,10 @@ packages:
resolution: {integrity: sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==}
dev: true
/@types/promise.allsettled/1.0.3:
resolution: {integrity: sha512-b/IFHHTkYkTqu41IH9UtpICwqrpKj2oNlb4KHPzFQDMiz+h1BgAeATeO0/XTph4+UkH9W2U0E4B4j64KWOovag==}
dev: false
/@types/prop-types/15.7.5:
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
@ -9568,6 +9661,10 @@ packages:
resolution: {integrity: sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==}
dev: true
/@types/tsscmp/1.0.0:
resolution: {integrity: sha512-rj18XR6c4Ohds86Lq8MI1NMRrXes4eLo4H06e5bJyKucE1rXGsfBBbFGD2oDC+DSufQCpnU3TTW7QAiwLx+7Yw==}
dev: false
/@types/undertaker-registry/1.0.1:
resolution: {integrity: sha512-Z4TYuEKn9+RbNVk1Ll2SS4x1JeLHecolIbM/a8gveaHsW0Hr+RQMraZACwTO2VD7JvepgA6UO1A1VrbktQrIbQ==}
dev: true
@ -9646,6 +9743,12 @@ packages:
- webpack-cli
dev: true
/@types/ws/7.4.7:
resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==}
dependencies:
'@types/node': 13.13.5
dev: false
/@types/ws/8.5.3:
resolution: {integrity: sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==}
dependencies:
@ -11028,7 +11131,6 @@ packages:
es-abstract: 1.21.1
es-array-method-boxes-properly: 1.0.0
is-string: 1.0.7
dev: true
/array.prototype.reduce/1.0.4:
resolution: {integrity: sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==}
@ -11207,6 +11309,15 @@ packages:
- debug
dev: false
/axios/0.27.2:
resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
dependencies:
follow-redirects: 1.15.1
form-data: 4.0.0
transitivePeerDependencies:
- debug
dev: false
/axobject-query/2.2.0:
resolution: {integrity: sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==}
dev: true
@ -11775,8 +11886,7 @@ packages:
dev: true
/buffer-equal-constant-time/1.0.1:
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
dev: true
resolution: {integrity: sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=}
/buffer-equal/1.0.0:
resolution: {integrity: sha512-tcBWO2Dl4e7Asr9hTGcpVrCe+F7DubpmqWCTbj4FHLmjqO2hIaC383acQubWtRJhdceqs5uBHs6Es+Sk//RKiQ==}
@ -13210,7 +13320,7 @@ packages:
postcss-modules-values: 4.0.0_postcss@8.4.21
postcss-value-parser: 4.2.0
semver: 7.3.8
webpack: 5.75.0_pdcrf7mb3dfag2zju4x4octu4a
webpack: 5.75.0_esbuild@0.17.8
/css-minimizer-webpack-plugin/4.2.2_zj7shrtzhjuywytipisjis56au:
resolution: {integrity: sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA==}
@ -14610,10 +14720,9 @@ packages:
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
dependencies:
safe-buffer: 5.2.1
dev: true
/ee-first/1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=}
/electron-to-chromium/1.4.284:
resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==}
@ -14815,7 +14924,6 @@ packages:
/es-array-method-boxes-properly/1.0.0:
resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==}
dev: true
/es-get-iterator/1.1.0:
resolution: {integrity: sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==}
@ -14827,7 +14935,6 @@ packages:
is-set: 2.0.1
is-string: 1.0.7
isarray: 2.0.5
dev: true
/es-module-lexer/0.9.3:
resolution: {integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==}
@ -14999,7 +15106,6 @@ packages:
'@esbuild/win32-arm64': 0.17.8
'@esbuild/win32-ia32': 0.17.8
'@esbuild/win32-x64': 0.17.8
dev: false
/escalade/3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
@ -15508,7 +15614,6 @@ packages:
/eventemitter3/3.1.2:
resolution: {integrity: sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==}
dev: true
/eventemitter3/4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
@ -16079,6 +16184,10 @@ packages:
parse-filepath: 1.0.2
dev: true
/finity/0.5.4:
resolution: {integrity: sha512-3l+5/1tuw616Lgb0QBimxfdd2TqaDGpfCBpfX6EqtFmqUV3FtQnVEX4Aa62DagYEqnsTIjZcTfbq9msDbXYgyA==}
dev: false
/flagged-respawn/1.0.0:
resolution: {integrity: sha512-uEIeQj2Z4a7Jzpb682FYsKc4YWHKaykTndikWrRtoWhOc1w3/97An5RcLhwBSq7phxokzK5kFHEKRKT0raGgTg==}
engines: {node: '>= 0.8.0'}
@ -16225,7 +16334,6 @@ packages:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: true
/form-data/3.0.0:
resolution: {integrity: sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==}
@ -16376,7 +16484,7 @@ packages:
dependencies:
graceful-fs: 4.2.11
inherits: 2.0.4
mkdirp: 0.5.5
mkdirp: 0.5.6
rimraf: 2.6.3
dev: true
@ -18169,6 +18277,10 @@ packages:
is-window: 1.0.2
dev: true
/is-electron/2.2.0:
resolution: {integrity: sha512-SpMppC2XR3YdxSzczXReBjqs2zGscWQpBIKqwXYBFic0ERaxNVgwLCHwOLZeESfdJQjX0RDvrJ1lBXX2ij+G1Q==}
dev: false
/is-extendable/0.1.1:
resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
engines: {node: '>=0.10.0'}
@ -18265,7 +18377,6 @@ packages:
/is-map/2.0.1:
resolution: {integrity: sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==}
dev: true
/is-negated-glob/1.0.0:
resolution: {integrity: sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==}
@ -18402,7 +18513,6 @@ packages:
/is-set/2.0.1:
resolution: {integrity: sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==}
dev: true
/is-shared-array-buffer/1.0.2:
resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==}
@ -18517,7 +18627,6 @@ packages:
/isarray/2.0.5:
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
dev: true
/isexe/2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
@ -18650,14 +18759,12 @@ packages:
/iterate-iterator/1.0.1:
resolution: {integrity: sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==}
dev: true
/iterate-value/1.0.2:
resolution: {integrity: sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==}
dependencies:
es-get-iterator: 1.1.0
iterate-iterator: 1.0.1
dev: true
/its-fine/1.1.0_react@18.1.0:
resolution: {integrity: sha512-nEoEt5EYSed1mmvwCRv3l1+6T7pyu4ltyBihzPjUtaSWhFhUPU/c7xkPDIutTh8FeIv0F1F5wOFYI8a2s5rlBA==}
@ -18735,7 +18842,7 @@ packages:
jest-util: 28.1.3
jest-validate: 28.1.3
prompts: 2.4.2
yargs: 17.6.2
yargs: 17.7.1
transitivePeerDependencies:
- '@types/node'
- supports-color
@ -19801,6 +19908,16 @@ packages:
semver: 5.7.1
dev: true
/jsonwebtoken/9.0.0:
resolution: {integrity: sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==}
engines: {node: '>=12', npm: '>=6'}
dependencies:
jws: 3.2.2
lodash: 4.17.21
ms: 2.1.3
semver: 7.3.8
dev: false
/jsx-ast-utils/3.3.0:
resolution: {integrity: sha512-XzO9luP6L0xkxwhIJMTJQpZo/eeN60K08jHdexfD569AGxeNug6UketeHXEhROoM8aR7EcUoOQmIhcJQjcuq8Q==}
engines: {node: '>=4.0'}
@ -19832,14 +19949,12 @@ packages:
buffer-equal-constant-time: 1.0.1
ecdsa-sig-formatter: 1.0.11
safe-buffer: 5.2.1
dev: true
/jws/3.2.2:
resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==}
dependencies:
jwa: 1.4.1
safe-buffer: 5.2.1
dev: true
/keytar/7.9.0:
resolution: {integrity: sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==}
@ -20309,7 +20424,6 @@ packages:
/lodash.isstring/4.0.1:
resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
dev: true
/lodash.isundefined/3.0.1:
resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==}
@ -20687,7 +20801,7 @@ packages:
resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==}
/media-typer/0.3.0:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
resolution: {integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=}
engines: {node: '>= 0.6'}
/mem/4.3.0:
@ -21371,6 +21485,7 @@ packages:
/minimist/1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
dev: true
/minipass-collect/1.0.2:
resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==}
@ -21437,7 +21552,7 @@ packages:
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
hasBin: true
dependencies:
minimist: 1.2.8
minimist: 1.2.6
/mkdirp/1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
@ -22305,7 +22420,6 @@ packages:
/p-cancelable/1.1.0:
resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==}
engines: {node: '>=6'}
dev: true
/p-cancelable/2.0.0:
resolution: {integrity: sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==}
@ -22400,13 +22514,17 @@ packages:
aggregate-error: 3.1.0
dev: true
/p-queue/2.4.2:
resolution: {integrity: sha512-n8/y+yDJwBjoLQe1GSJbbaYQLTI7QHNZI2+rpmCDbe++WLf9HC3gf6iqj5yfPAV71W4UF3ql5W1+UBPXoXTxng==}
engines: {node: '>=4'}
dev: false
/p-queue/6.6.2:
resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==}
engines: {node: '>=8'}
dependencies:
eventemitter3: 4.0.7
p-timeout: 3.2.0
dev: true
/p-retry/4.6.0:
resolution: {integrity: sha512-SAHbQEwg3X5DRNaLmWjT+DlGc93ba5i+aP3QLfVNDncQEQO4xjbYW4N/lcVTSuP0aJietGfx2t94dJLzfBMpXw==}
@ -22420,7 +22538,6 @@ packages:
engines: {node: '>=8'}
dependencies:
p-finally: 1.0.0
dev: true
/p-timeout/4.1.0:
resolution: {integrity: sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==}
@ -22784,6 +22901,12 @@ packages:
hasBin: true
dev: true
/please-upgrade-node/3.2.0:
resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==}
dependencies:
semver-compare: 1.0.0
dev: false
/plur/4.0.0:
resolution: {integrity: sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==}
engines: {node: '>=10'}
@ -23029,7 +23152,7 @@ packages:
klona: 2.0.5
postcss: 8.4.21
semver: 7.3.8
webpack: 5.75.0_pdcrf7mb3dfag2zju4x4octu4a
webpack: 5.75.0_esbuild@0.17.8
/postcss-media-query-parser/0.2.3:
resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==}
@ -23623,7 +23746,6 @@ packages:
es-abstract: 1.21.1
function-bind: 1.1.1
iterate-value: 1.0.2
dev: true
/promise.prototype.finally/3.1.0:
resolution: {integrity: sha512-7p/K2f6dI+dM8yjRQEGrTQs5hTQixUAdOGpMEA3+pVxpX5oHKRSKAXyLw9Q9HUWDTdwtoo39dSHGQtN90HcEwQ==}
@ -25263,7 +25385,7 @@ packages:
klona: 2.0.5
neo-async: 2.6.2
sass: 1.32.4
webpack: 5.75.0_pdcrf7mb3dfag2zju4x4octu4a
webpack: 5.75.0_esbuild@0.17.8
/sass/1.32.4:
resolution: {integrity: sha512-N0BT0PI/t3+gD8jKa83zJJUb7ssfQnRRfqN+GIErokW6U4guBpfYl8qYB+OFLEho+QvnV5ZH1R9qhUC/Z2Ch9w==}
@ -25358,6 +25480,10 @@ packages:
dependencies:
node-forge: 1.3.1
/semver-compare/1.0.0:
resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==}
dev: false
/semver-diff/2.1.0:
resolution: {integrity: sha512-gL8F8L4ORwsS0+iQ34yCYv///jsOq0ZL7WP55d1HnJ32o7tyFYEFQZQA22mrLIacZdU6xecaBBZ+uEiffGNyXw==}
engines: {node: '>=0.10.0'}
@ -26461,7 +26587,7 @@ packages:
peerDependencies:
webpack: ^5.0.0
dependencies:
webpack: 5.75.0_pdcrf7mb3dfag2zju4x4octu4a
webpack: 5.75.0_esbuild@0.17.8
/style-mod/4.0.0:
resolution: {integrity: sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==}
@ -27006,7 +27132,6 @@ packages:
serialize-javascript: 6.0.0
terser: 5.16.1
webpack: 5.75.0_esbuild@0.17.8
dev: false
/terser-webpack-plugin/5.3.6_zj7shrtzhjuywytipisjis56au:
resolution: {integrity: sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==}
@ -27429,6 +27554,11 @@ packages:
/tslib/2.1.0:
resolution: {integrity: sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==}
/tsscmp/1.0.6:
resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==}
engines: {node: '>=0.6.x'}
dev: false
/tsutils-etc/1.4.1_o4lkiqkygxao4n37kchtvzdmeq:
resolution: {integrity: sha512-6UPYgc7OXcIW5tFxlsZF3OVSBvDInl/BkS3Xsu64YITXk7WrnWTVByKWPCThFDBp5gl5IGHOzGMdQuDCE7OL4g==}
hasBin: true
@ -27439,7 +27569,7 @@ packages:
'@types/yargs': 17.0.16
tsutils: 3.21.0_typescript@5.0.2
typescript: 5.0.2
yargs: 17.6.2
yargs: 17.7.1
dev: true
/tsutils/3.21.0_typescript@5.0.2:
@ -28625,7 +28755,7 @@ packages:
dependencies:
memory-fs: 0.4.1
mime: 2.6.0
mkdirp: 0.5.5
mkdirp: 0.5.6
range-parser: 1.2.1
webpack: 5.75.0_pdcrf7mb3dfag2zju4x4octu4a
webpack-log: 2.0.0
@ -28824,7 +28954,6 @@ packages:
- '@swc/core'
- esbuild
- uglify-js
dev: false
/webpack/5.75.0_pdcrf7mb3dfag2zju4x4octu4a:
resolution: {integrity: sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==}
@ -29323,7 +29452,6 @@ packages:
/y18n/5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
dev: false
/yallist/2.1.2:
resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==}
@ -29479,7 +29607,6 @@ packages:
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 21.1.1
dev: false
/yargs/7.1.0:
resolution: {integrity: sha512-JHLTJJ5uqdt0peYp5mHzmSNV4uHXWphgSlKk5jg3sY5XYPTBw0hzw0SDNnYISn7pAXeAv5pKT4CNY+EcCTptBg==}

View File

@ -452,6 +452,15 @@ commands:
INTEGRATION_TESTS: true
NODE_ENV: production
cody-slack:
description: Start Cody-Slack locally server locally
cmd: pnpm --filter @sourcegraph/cody-slack run start
env:
SLACK_APP_TOKEN: XXX
SLACK_SIGNING_SECRET: XXX
SLACK_BOT_TOKEN: XXX
SOURCEGRAPH_ACCESS_TOKEN: XXX
docsite:
description: Docsite instance serving the docs
cmd: .bin/docsite_${DOCSITE_VERSION} -config doc/docsite.json serve -http=localhost:5080