diff --git a/.github/sync-env-vars.sh b/.github/sync-env-vars.sh new file mode 100644 index 00000000..9a8dc3c9 --- /dev/null +++ b/.github/sync-env-vars.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# Check if required environment variables are set +if [ -z "$CLOUDFLARE_ACCOUNT_ID" ] || [ -z "$CLOUDFLARE_API_TOKEN" ] || [ -z "$GITHUB_REPOSITORY" ] || \ + [ -z "$VOYAGEAI_API_KEY" ] || [ -z "$SUPABASE_URL" ] || [ -z "$SUPABASE_KEY" ]; then + echo "Error: Required environment variables are not set" + exit 1 +fi + +# Extract just the repository name from org/repo format +REPOSITORY_NAME=$(echo "$GITHUB_REPOSITORY" | cut -d'/' -f2) +# Replace dots with hyphens +REPOSITORY_NAME=${REPOSITORY_NAME//./-} + +# Echo the repository name +echo "Repository name: $REPOSITORY_NAME" + +# Make the API call to Cloudflare +curl -X PATCH \ + "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/${REPOSITORY_NAME}/deployment_configs" \ + -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ + -H "Content-Type: application/json" \ + --data '{ + "deployment_configs": { + "production": { + "env_vars": { + "VOYAGEAI_API_KEY": { + "value": "'"${VOYAGEAI_API_KEY}"'", + "type": "secret_text" + }, + "SUPABASE_URL": { + "value": "'"${SUPABASE_URL}"'", + "type": "secret_text" + }, + "SUPABASE_KEY": { + "value": "'"${SUPABASE_KEY}"'", + "type": "secret_text" + } + } + } + } + }' \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00d8dfb4..76c12021 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,4 +35,8 @@ jobs: uses: actions/upload-artifact@v4 with: name: static - path: static + path: | + static + functions + package.json + yarn.lock diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 90f09123..72ed0194 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -25,5 +25,25 @@ jobs: cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }} commit_sha: ${{ github.event.workflow_run.head_sha }} workflow_run_id: ${{ github.event.workflow_run.id }} - app_id: ${{ secrets.APP_ID }} - app_private_key: ${{ secrets.APP_PRIVATE_KEY }} + statics_directory: "static" + + update_env_secrets_in_cloudflare: + name: Update environment secrets in Cloudflare + runs-on: ubuntu-22.04 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Make script executable + run: chmod +x .github/sync-env-vars.sh + + - name: Run sync environment variables script + env: + GITHUB_REPOSITORY: ${{ github.repository }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + VOYAGEAI_API_KEY: ${{ secrets.VOYAGEAI_API_KEY }} + SUPABASE_URL: ${{ secrets.SUPABASE_URL }} + SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} + run: bash .github/sync-env-vars.sh + diff --git a/bun.lockb b/bun.lockb deleted file mode 100755 index 8b8774f5..00000000 Binary files a/bun.lockb and /dev/null differ diff --git a/functions/issue-scraper.ts b/functions/issue-scraper.ts new file mode 100644 index 00000000..9c8a09f3 --- /dev/null +++ b/functions/issue-scraper.ts @@ -0,0 +1,362 @@ +import { Context } from "./types"; +import { SupabaseClient } from "@supabase/supabase-js"; +import { VoyageAIClient } from "voyageai"; +import { Octokit } from "@octokit/rest"; +import markdownit from "markdown-it"; +import plainTextPlugin from "markdown-it-plain-text"; +import { validatePOST } from "./validators"; + +const VECTOR_SIZE = 1024; + +interface MarkdownItWithPlainText extends markdownit { + plainText: string; +} + +interface PayloadType { + issue: { + nodeId: string; + number: number; + title: string; + body: string; + state: string; + stateReason: string | null; + repositoryName: string; + repositoryId: number; + assignees: string[]; + createdAt: string; + closedAt: string | null; + updatedAt: string; + }; + action: string; + sender: { + login: string; + }; + repository: { + id: number; + node_id: string; + name: string; + full_name: string; + owner: { + login: string; + id: number; + type: string; + site_admin: boolean; + }; + }; +} + +interface IssueNode { + id: string; + number: number; + title: string; + body: string; + state: string; + stateReason: string | null; + createdAt: string; + updatedAt: string; + closedAt: string | null; + author: { + login: string; + } | null; + assignees: { + nodes: Array<{ + login: string; + }>; + }; + repository: { + id: string; + name: string; + owner: { + login: string; + }; + }; +} + +interface GraphQlSearchResponse { + search: { + pageInfo: { + hasNextPage: boolean; + endCursor: string | null; + }; + nodes: Array; + }; +} + +export const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET", + "Access-Control-Allow-Headers": "Content-Type", +}; + +export async function onRequest(ctx: Context): Promise { + const { request, env } = ctx; + try { + switch (request.method) { + case "POST": { + const result = await validatePOST(request); + if (!result.isValid || !result.gitHubUser) { + return new Response("Unauthorized", { + headers: corsHeaders, + status: 400, + }); + } + const githubUserName = result.gitHubUser.login; + const timestamp = result.timestamp; // Unix timestamp in milliseconds + + try { + const supabase = new SupabaseClient(env.SUPABASE_URL, env.SUPABASE_KEY); + const response = await issueScraper(githubUserName, supabase, env.VOYAGEAI_API_KEY, result.authToken, timestamp); + return new Response(response, { + headers: corsHeaders, + status: 200, + }); + } catch (error) { + console.error("Error processing request:", error); + return new Response("Internal Server Error", { + headers: corsHeaders, + status: 500, + }); + } + } + + default: + return new Response("Method Not Allowed", { + headers: corsHeaders, + status: 405, + }); + } + } catch (error) { + console.error("Error processing request:", error); + return new Response("Internal Server Error", { + headers: corsHeaders, + status: 500, + }); + } +} + +function markdownToPlainText(markdown: string | null): string | null { + if (!markdown) return markdown; + const md = markdownit() as MarkdownItWithPlainText; + md.use(plainTextPlugin); + md.render(markdown); + return md.plainText; +} + +const SEARCH_ISSUES_QUERY = /* GraphQL */ ` + query SearchIssues($searchText: String!, $after: String) { + search(query: $searchText, type: ISSUE, first: 100, after: $after) { + pageInfo { + hasNextPage + endCursor + } + nodes { + ... on Issue { + id + number + title + body + state + stateReason + createdAt + updatedAt + closedAt + author { + login + } + assignees(first: 10) { + nodes { + login + } + } + repository { + id + name + owner { + login + } + } + } + } + } + } +`; + +async function fetchUserIssuesBatch(octokit: InstanceType, username: string, lastScraped?: number): Promise { + const allIssues: IssueNode[] = []; + let hasNextPage = true; + let cursor: string | null = null; + + // Construct the query with the lastScraped timestamp + const searchText = `assignee:${username} is:issue is:closed reason:completed ${lastScraped ? `closed:>${new Date(lastScraped).toISOString()}` : ""}`; + + while (hasNextPage) { + const variables: { searchText: string; after?: string } = { searchText }; + if (cursor) { + variables.after = cursor; + } + + const response: GraphQlSearchResponse = await octokit.graphql(SEARCH_ISSUES_QUERY, variables); + + allIssues.push(...response.search.nodes); + + hasNextPage = response.search.pageInfo.hasNextPage; + cursor = response.search.pageInfo.endCursor; + } + + return allIssues; +} + +async function batchEmbeddings(voyageClient: VoyageAIClient, texts: string[]): Promise<(number[] | undefined)[]> { + try { + const embeddingResponse = await voyageClient.embed({ + input: texts, + model: "voyage-large-2-instruct", + inputType: "document", + }); + return embeddingResponse.data?.map((item) => item.embedding) || []; + } catch (error) { + console.error("Error batching embeddings:", error); + throw error; + } +} + +async function batchUpsertIssues( + supabase: SupabaseClient, + issues: Array<{ + id: string; + markdown: string; + plaintext: string; + embedding: string; + author_id: number; + payload: PayloadType; + }> +): Promise { + const { error } = await supabase.from("issues").upsert(issues); + if (error) { + throw new Error(`Error during batch upsert: ${error.message}`); + } +} + +async function batchFetchAuthorIds(octokit: InstanceType, logins: string[]): Promise> { + const authorIdMap: Record = {}; + const BATCH_SIZE = 20; + for (let i = 0; i < logins.length; i += BATCH_SIZE) { + const batch = logins.slice(i, i + BATCH_SIZE); + const promises = batch.map(async (login) => { + try { + const response = await octokit.rest.users.getByUsername({ username: login }); + return { login, id: response.data.id }; + } catch (error) { + console.error(`Error fetching author ID for ${login}:`, error); + return { login, id: -1 }; + } + }); + const results = await Promise.all(promises); + results.forEach(({ login, id }) => { + authorIdMap[login] = id; + }); + } + return authorIdMap; +} + +async function issueScraper(username: string, supabase: SupabaseClient, voyageApiKey: string, token?: string, timestamp?: number): Promise { + try { + if (!username) { + throw new Error("Username is required"); + } + + const storageFailed = []; + + const octokit = new Octokit(token ? { auth: token } : {}); + const voyageClient = new VoyageAIClient({ apiKey: voyageApiKey }); + + let issues = await fetchUserIssuesBatch(octokit, username, timestamp); + + const uniqueAuthors = Array.from(new Set(issues.map((issue) => issue.author?.login).filter((login): login is string => !!login))); + + const authorIdMap = await batchFetchAuthorIds(octokit, uniqueAuthors); + + // Filter the issues to include only those with an valid title + issues = issues.filter((issue) => { + if (!issue.title) { + storageFailed.push({ + id: issue.id, + reason: "Issue does not have a title", + }); + return false; + } + }); + + const markdowns = issues.map((issue) => { + return `${issue.body || ""} ${issue.title}`; + }); + const plainTexts = markdowns.map(markdownToPlainText); + const embeddings = await batchEmbeddings(voyageClient, markdowns); + + const upsertData = issues.map((issue, index) => { + const authorId = issue.author?.login ? authorIdMap[issue.author.login] || -1 : -1; + const repoOwner = issue.repository.owner.login; + + return { + id: issue.id, + markdown: markdowns[index], + plaintext: plainTexts[index] ?? "", + embedding: JSON.stringify(embeddings[index] || Array(VECTOR_SIZE).fill(0)), + author_id: authorId, + payload: { + issue: { + nodeId: issue.id, + number: issue.number, + title: issue.title, + body: issue.body || "", + state: issue.state, + stateReason: issue.stateReason, + repositoryName: issue.repository.name, + repositoryId: parseInt(issue.repository.id), + assignees: (issue.assignees?.nodes || []).map((a) => a.login), + createdAt: issue.createdAt, + closedAt: issue.closedAt, + updatedAt: issue.updatedAt, + }, + action: "created", + sender: { login: username }, + repository: { + id: parseInt(issue.repository.id), + node_id: issue.repository.id, + name: issue.repository.name, + full_name: `${repoOwner}/${issue.repository.name}`, + owner: { + login: repoOwner, + id: authorId, + type: "User", + site_admin: false, + }, + }, + }, + }; + }); + + await batchUpsertIssues(supabase, upsertData); + + return JSON.stringify( + { + success: true, + stats: { + storageSuccessful: upsertData.length, + storageFailed: storageFailed.length, + }, + issues: upsertData.map((issue) => ({ + id: issue.id, + markdown: issue.markdown, + plaintext: issue.plaintext, + })), + storageFailed: storageFailed, + }, + null, + 2 + ); + } catch (error) { + console.error("Error in issueScraper:", error); + throw error; + } +} diff --git a/functions/referral-manager.ts b/functions/referral-manager.ts index f83ddd4b..0996173b 100644 --- a/functions/referral-manager.ts +++ b/functions/referral-manager.ts @@ -50,14 +50,16 @@ export async function onRequest(ctx: Context): Promise { async function handleSet(env: Env, request: Request): Promise { const result = await validatePOST(request); - if (!result.isValid || !result.gitHubUserId || !result.referralCode) { + if (!result.isValid || !result.gitHubUser || !result.referralCode) { return new Response("Unauthorized", { headers: corsHeaders, status: 400, }); } - const { gitHubUserId, referralCode } = result; + const { gitHubUser, referralCode } = result; + + const gitHubUserId = gitHubUser.id.toString(); const oldRefCode = await env.KVNamespace.get(gitHubUserId); diff --git a/functions/tsconfig.json b/functions/tsconfig.json new file mode 100644 index 00000000..80d76d53 --- /dev/null +++ b/functions/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "lib": ["esnext"], + "types": ["@cloudflare/workers-types"], + "moduleResolution": "Node", + "esModuleInterop": true, + } + } \ No newline at end of file diff --git a/functions/types.ts b/functions/types.ts index acafc2e9..4be640b3 100644 --- a/functions/types.ts +++ b/functions/types.ts @@ -1,18 +1,26 @@ import { EventContext, KVNamespace } from "@cloudflare/workers-types"; +import { GitHubUser } from "../src/home/github-types"; export interface Env { KVNamespace: KVNamespace; + SUPABASE_URL: string; + SUPABASE_ANON_KEY: string; + SUPABASE_KEY: string; + VOYAGEAI_API_KEY: string; } export interface POSTRequestBody { authToken: string; referralCode: string; + timestamp: number; } export interface ValidationResult { isValid: boolean; - gitHubUserId?: string; + gitHubUser?: GitHubUser; referralCode?: string; + authToken?: string; + timestamp?: number; } export type Context = EventContext>; diff --git a/functions/validators.ts b/functions/validators.ts index 51335482..ca54ff8c 100644 --- a/functions/validators.ts +++ b/functions/validators.ts @@ -6,7 +6,7 @@ import { Octokit } from "@octokit/rest"; export async function validatePOST(request: Request): Promise { const jsonData: POSTRequestBody = await request.json(); - const { authToken, referralCode } = jsonData; + const { authToken, referralCode, timestamp } = jsonData; const octokit = new Octokit({ auth: authToken }); @@ -15,7 +15,7 @@ export async function validatePOST(request: Request): Promise const gitHubUser = response.data; - return { isValid: true, gitHubUserId: gitHubUser.id.toString(), referralCode: referralCode }; + return { isValid: true, gitHubUser: gitHubUser, referralCode: referralCode, authToken: authToken, timestamp }; } catch (error) { console.error("User is not logged in"); return { isValid: false }; diff --git a/package.json b/package.json index b7a653ca..375d03d4 100644 --- a/package.json +++ b/package.json @@ -38,12 +38,16 @@ "@supabase/supabase-js": "^2.39.0", "dotenv": "^16.3.1", "esbuild-plugin-env": "^1.0.8", + "markdown-it": "^14.1.0", + "markdown-it-plain-text": "^0.3.0", "marked": "^11.0.0", "marked-footnote": "^1.2.4", + "voyageai": "^0.0.3-1", "wrangler": "^3.83.0" }, "devDependencies": { "@commitlint/cli": "^18.4.3", + "@types/markdown-it": "^14.1.2", "@commitlint/config-conventional": "^18.4.3", "@types/node": "^20.10.0", "@typescript-eslint/eslint-plugin": "^6.13.1", diff --git a/src/home/authentication.ts b/src/home/authentication.ts index c110f705..ea090b9f 100644 --- a/src/home/authentication.ts +++ b/src/home/authentication.ts @@ -4,6 +4,7 @@ import { GitHubUser } from "./github-types"; import { trackReferralCode } from "./register-referral"; import { displayGitHubUserInformation } from "./rendering/display-github-user-information"; import { renderGitHubLoginButton } from "./rendering/render-github-login-button"; +import { startIssueScraper } from "./scraper/issue-scraper"; export async function authentication() { if (!navigator.onLine) { @@ -20,5 +21,7 @@ export async function authentication() { if (gitHubUser) { await trackReferralCode(); await displayGitHubUserInformation(gitHubUser); + const githubUserName = gitHubUser.login; + await startIssueScraper(githubUserName); } } diff --git a/src/home/scraper/issue-scraper.ts b/src/home/scraper/issue-scraper.ts new file mode 100644 index 00000000..8c287f81 --- /dev/null +++ b/src/home/scraper/issue-scraper.ts @@ -0,0 +1,49 @@ +import { checkSupabaseSession } from "../rendering/render-github-login-button"; + +export async function startIssueScraper(username: string) { + const supabaseAuth = await checkSupabaseSession(); + + // Get the last fetch timestamp + const lastFetchKey = `lastFetch_${username}`; + const lastFetch = localStorage.getItem(lastFetchKey); + const now = Date.now(); + + // Prepare the request payload + const requestBody: { authToken: string; timestamp?: number } = { + authToken: supabaseAuth.provider_token, + }; + + if (lastFetch) { + const lastFetchTimestamp = Number(lastFetch); + if (now - lastFetchTimestamp < 24 * 60 * 60 * 1000) { + return JSON.stringify({ + success: true, + message: "Skipping fetch - last fetch was less than 24 hours ago", + }); + } + requestBody.timestamp = lastFetchTimestamp; + } + + // Send the request to the issue scraper endpoint + const response = await fetch("/issue-scraper", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(requestBody), + }); + + if (response.status === 200) { + // Update the last fetch timestamp in local storage + localStorage.setItem(lastFetchKey, now.toString()); + return JSON.stringify({ + success: true, + message: "Successfully fetched issues", + }); + } else { + return JSON.stringify({ + success: false, + message: `Failed to fetch issues. Status: ${response.status}`, + }); + } +} diff --git a/wrangler.toml b/wrangler.toml index 5b777f80..771f2580 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -10,8 +10,8 @@ id = "0a6aaf0a6edb428189606b116da58ef7" [vars] YARN_VERSION = "1.22.22" - -# These secrets need to be configured via Cloudflare dashboard: -# SUPABASE_URL = "" -# SUPABASE_ANON_KEY = "" +SUPABASE_URL="" +SUPABASE_ANON_KEY="" +VOYAGEAI_API_KEY="" +SUPABASE_KEY="" diff --git a/yarn.lock b/yarn.lock index e3cfb1c0..4a20caf6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1134,6 +1134,24 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/linkify-it@^5": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-5.0.0.tgz#21413001973106cda1c3a9b91eedd4ccd5469d76" + integrity sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q== + +"@types/markdown-it@^14.1.2": + version "14.1.2" + resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-14.1.2.tgz#57f2532a0800067d9b934f3521429a2e8bfb4c61" + integrity sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog== + dependencies: + "@types/linkify-it" "^5" + "@types/mdurl" "^2" + +"@types/mdurl@^2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-2.0.0.tgz#d43878b5b20222682163ae6f897b20447233bdfd" + integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg== + "@types/minimist@^1.2.0": version "1.2.5" resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e" @@ -1322,6 +1340,13 @@ JSONStream@^1.3.5: jsonparse "^1.2.0" through ">=2.2.7 <3" +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -1621,6 +1646,14 @@ buffer@^5.7.1: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + builtins@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.1.0.tgz#6d85eeb360c4ebc166c3fdef922a15aa7316a5e8" @@ -2218,6 +2251,11 @@ enquirer@^2.3.6: ansi-colors "^4.1.1" strip-ansi "^6.0.1" +entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + environment@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" @@ -2541,6 +2579,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + eventemitter2@6.4.7: version "6.4.7" resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d" @@ -2551,6 +2594,11 @@ eventemitter3@^5.0.1: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + execa@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" @@ -2775,7 +2823,7 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== -form-data@~4.0.0: +form-data@^4.0.0, form-data@~4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== @@ -2784,6 +2832,11 @@ form-data@~4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +formdata-node@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-6.0.3.tgz#48f8e2206ae2befded82af621ef015f08168dc6d" + integrity sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg== + fs-extra@10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" @@ -3149,7 +3202,7 @@ identity-function@^1.0.0: resolved "https://registry.yarnpkg.com/identity-function/-/identity-function-1.0.0.tgz#bea1159f0985239be3ca348edf40ce2f0dd2c21d" integrity sha512-kNrgUK0qI+9qLTBidsH85HjDLpZfrrS0ElquKKe/fJFdB3D7VeKdXXEvOPDUHSHOzdZKCAAaQIWWyp0l2yq6pw== -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -3544,6 +3597,11 @@ jiti@^1.21.6: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.7.tgz#9dd81043424a3d28458b193d965f0d18a2300ba9" integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A== +js-base64@3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.2.tgz#816d11d81a8aff241603d19ce5761e13e41d7745" + integrity sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ== + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -3696,6 +3754,13 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +linkify-it@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-5.0.0.tgz#9ef238bfa6dc70bd8e7f9572b52d369af569b421" + integrity sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ== + dependencies: + uc.micro "^2.0.0" + lint-staged@^15.1.0: version "15.2.11" resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.11.tgz#e88440982f4a4c1d55a9a7a839259ec3806bd81b" @@ -3924,6 +3989,23 @@ map-obj@^4.0.0: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== +markdown-it-plain-text@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/markdown-it-plain-text/-/markdown-it-plain-text-0.3.0.tgz#374abfcc1c95ae3d7a5e09365558d43184e1e7db" + integrity sha512-JT5x7oXZaTY6lwxsnxaIAT4hm5Q2Mi8dZoCaCNg3s7JXmDxY84D90OtV0US436UvN4zOUHoac4ExceTogydOLw== + +markdown-it@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-14.1.0.tgz#3c3c5992883c633db4714ccb4d7b5935d98b7d45" + integrity sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg== + dependencies: + argparse "^2.0.1" + entities "^4.4.0" + linkify-it "^5.0.0" + mdurl "^2.0.0" + punycode.js "^2.3.1" + uc.micro "^2.1.0" + marked-footnote@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/marked-footnote/-/marked-footnote-1.2.4.tgz#fcf534467fc9e0fb5a83bce44747cf3849561998" @@ -3939,6 +4021,11 @@ math-intrinsics@^1.0.0: resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== +mdurl@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-2.0.0.tgz#80676ec0433025dd3e17ee983d0fe8de5a2237e0" + integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w== + mem@^6.0.1: version "6.1.1" resolved "https://registry.yarnpkg.com/mem/-/mem-6.1.1.tgz#ea110c2ebc079eca3022e6b08c85a795e77f6318" @@ -4144,6 +4231,13 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +node-fetch@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-fetch@3.0.0-beta.9: version "3.0.0-beta.9" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.0.0-beta.9.tgz#0a7554cfb824380dd6812864389923c783c80d9b" @@ -4626,11 +4720,23 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" +punycode.js@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7" + integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA== + punycode@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== +qs@6.11.2: + version "6.11.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" + integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== + dependencies: + side-channel "^1.0.4" + qs@6.13.1: version "6.13.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.1.tgz#3ce5fc72bd3a8171b85c99b93c65dd20b7d1b16e" @@ -4698,6 +4804,17 @@ readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.6.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-stream@^4.5.2: + version "4.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.6.0.tgz#ce412dfb19c04efde1c5936d99c27f37a1ff94c9" + integrity sha512-cbAdYt0VcnpN2Bekq7PU+k363ZRsPwJoEEJOEtSJQlJXzwaxt3FIo/uL+KeDSGIjJqtkwyge4KQgD2S2kd+CQw== + dependencies: + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + string_decoder "^1.3.0" + readdirp@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.0.2.tgz#388fccb8b75665da3abffe2d8f8ed59fe74c230a" @@ -5016,7 +5133,7 @@ side-channel-weakmap@^1.0.2: object-inspect "^1.13.3" side-channel-map "^1.0.1" -side-channel@^1.0.6, side-channel@^1.1.0: +side-channel@^1.0.4, side-channel@^1.0.6, side-channel@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== @@ -5252,7 +5369,7 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -string_decoder@^1.1.1: +string_decoder@^1.1.1, string_decoder@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== @@ -5571,6 +5688,11 @@ typescript@^5.3.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6" integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg== +uc.micro@^2.0.0, uc.micro@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee" + integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A== + ufo@^1.5.4: version "1.5.4" resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.4.tgz#16d6949674ca0c9e0fbbae1fa20a71d7b1ded754" @@ -5654,6 +5776,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +url-join@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" + integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== + util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -5705,6 +5832,19 @@ vlq@^0.2.1: resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26" integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow== +voyageai@^0.0.3-1: + version "0.0.3-1" + resolved "https://registry.yarnpkg.com/voyageai/-/voyageai-0.0.3-1.tgz#a9ccf3d157b67945e08b79d4f3ddb62d3e39161f" + integrity sha512-R3jN/xnILWoMBL3jPY61Ydm1JbpK3J+VmXBoHvlNg1Xz8h0xdX7sEffXeSu+sAEKQaPyWXVQDmM/jpBhPXw58g== + dependencies: + form-data "^4.0.0" + formdata-node "^6.0.3" + js-base64 "3.7.2" + node-fetch "2.7.0" + qs "6.11.2" + readable-stream "^4.5.2" + url-join "4.0.1" + wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"