From 0576ed5d9b0ba913f2bf96272dcb40c8a80609bd Mon Sep 17 00:00:00 2001 From: Coding Agent Date: Sat, 14 Mar 2026 23:12:34 +0000 Subject: [PATCH] agent: @U0AJM7X8FBR @U0AJM7X8FBR become an expert at this repo https://github.c --- src/artists/getBatchArtistSocials.ts | 13 +- src/artists/isScrapableSocial.ts | 4 +- src/chats/getTaskRoomId.ts | 4 +- src/consts.ts | 2 +- src/content/analyzeClips.ts | 15 +- src/content/defaultPipelineConfig.ts | 13 +- src/content/fetchArtistContext.ts | 9 +- src/content/fetchAudienceContext.ts | 14 +- src/content/fetchGithubFile.ts | 18 +- src/content/generateAudioVideo.ts | 36 ++-- src/content/generateCaption.ts | 23 ++- src/content/generateContentImage.ts | 9 +- src/content/generateContentVideo.ts | 9 +- src/content/listArtistSongs.ts | 20 +- src/content/loadTemplate.ts | 46 +++-- src/content/renderFinalVideo.ts | 106 +++++++--- src/content/selectAudioClip.ts | 4 + src/content/testPipeline.ts | 194 ++++++++++++++---- src/content/transcribeSong.ts | 8 +- src/content/upscaleImage.ts | 4 + src/content/upscaleVideo.ts | 4 + .../__tests__/createOrgGithubRepo.test.ts | 11 +- src/github/createGithubRepo.ts | 29 ++- src/github/createOrgGithubRepo.ts | 31 ++- src/github/getExistingGithubRepo.ts | 21 +- src/polling/pollScraperResults.ts | 12 +- .../__tests__/executePulseInSandbox.test.ts | 6 +- src/pulse/executePulseInSandbox.ts | 3 + src/recoup/__tests__/getAccountOrgs.test.ts | 4 +- src/recoup/createAccountSandbox.ts | 2 +- src/recoup/createChat.ts | 4 +- src/recoup/fetchActivePulses.ts | 2 +- src/recoup/fetchTask.ts | 8 +- src/recoup/generateChat.ts | 2 +- src/recoup/getAccount.ts | 4 +- src/recoup/getAccountOrgs.ts | 8 +- src/recoup/getAccountSandboxes.ts | 6 +- src/recoup/getArtistSocials.ts | 12 +- src/recoup/getProArtists.ts | 3 + src/recoup/getScraperResults.ts | 7 +- src/recoup/scrapeSocial.ts | 6 +- src/recoup/updateAccountSnapshot.ts | 2 +- .../__tests__/addOrgSubmodules.test.ts | 28 +-- .../__tests__/cloneMonorepoViaAgent.test.ts | 1 - .../__tests__/configureGitAuth.test.ts | 8 +- .../__tests__/copyOpenClawToRepo.test.ts | 17 +- .../__tests__/ensureOrgRepos.test.ts | 36 ++-- .../__tests__/getGitHubAuthPrefix.test.ts | 4 +- .../__tests__/getOrCreateSandbox.test.ts | 16 +- .../__tests__/getSandboxHomeDir.test.ts | 4 + .../notifyCodingAgentCallback.test.ts | 4 +- src/sandboxes/__tests__/pushOrgRepos.test.ts | 14 +- .../__tests__/runClaudeCodeAgent.test.ts | 14 +- .../__tests__/runOpenClawAgent.test.ts | 15 +- src/sandboxes/__tests__/setupOpenClaw.test.ts | 84 ++++---- .../__tests__/snapshotAndPersist.test.ts | 3 + .../__tests__/stripGitmodulesTokens.test.ts | 9 +- src/sandboxes/__tests__/syncOrgRepos.test.ts | 15 +- src/sandboxes/cloneMonorepoViaAgent.ts | 4 +- src/sandboxes/configureGitAuth.ts | 9 +- src/sandboxes/ensureGithubRepo.ts | 18 +- src/sandboxes/ensureOrgRepos.ts | 14 +- src/sandboxes/ensureSetupSandbox.ts | 5 +- src/sandboxes/getOrCreateSandbox.ts | 4 +- src/sandboxes/getSandboxEnv.ts | 6 +- src/sandboxes/getVercelSandboxCredentials.ts | 2 +- src/sandboxes/git/addOrgSubmodules.ts | 2 +- src/sandboxes/git/pushOrgRepos.ts | 6 +- src/sandboxes/installSkill.ts | 5 +- src/sandboxes/logStep.ts | 6 +- src/sandboxes/notifyCodingAgentCallback.ts | 5 +- src/sandboxes/onboardOpenClaw.ts | 2 +- src/sandboxes/parsePRUrls.ts | 6 +- src/sandboxes/pushSandboxToGithub.ts | 24 +-- src/sandboxes/runGitCommand.ts | 5 +- src/sandboxes/runOpenClawAgent.ts | 2 +- src/sandboxes/runSetupArtistSkill.ts | 2 +- src/sandboxes/runSetupSandboxSkill.ts | 2 +- src/sandboxes/setupOpenClaw.ts | 5 +- src/sandboxes/snapshotAndPersist.ts | 6 +- src/sandboxes/writeReadme.ts | 2 +- .../__tests__/contentCreationSchema.test.ts | 1 - src/schemas/contentCreationSchema.ts | 1 - src/schemas/sandboxSchema.ts | 4 +- src/socials/filterScrapableSocials.ts | 8 +- src/socials/scrapeAndPollSocials.ts | 24 +-- src/tasks/__tests__/codingAgentTask.test.ts | 7 +- src/tasks/__tests__/sendPulsesTask.test.ts | 5 +- src/tasks/__tests__/setupSandboxTask.test.ts | 18 +- src/tasks/__tests__/updatePRTask.test.ts | 4 +- src/tasks/codingAgentTask.ts | 9 +- src/tasks/createContentTask.ts | 8 +- src/tasks/customerPromptTask.ts | 4 +- src/tasks/proArtistSocialProfilesScrape.ts | 14 +- src/tasks/runSandboxCommandTask.ts | 11 +- src/tasks/sendPulsesTask.ts | 4 +- src/tasks/setupSandboxTask.ts | 8 +- src/tasks/updatePRTask.ts | 2 +- 98 files changed, 714 insertions(+), 570 deletions(-) diff --git a/src/artists/getBatchArtistSocials.ts b/src/artists/getBatchArtistSocials.ts index e23c313..a4fed9b 100644 --- a/src/artists/getBatchArtistSocials.ts +++ b/src/artists/getBatchArtistSocials.ts @@ -4,23 +4,20 @@ import { getArtistSocials } from "../recoup/getArtistSocials"; /** * Fetches socials for all artists in parallel. * Returns a Map of artistId -> socials array. + * + * @param artistIds */ export async function getBatchArtistSocials( - artistIds: string[] + artistIds: string[], ): Promise>>> { logger.log("Fetching socials for all artists", { totalArtists: artistIds.length, }); - const socialsResponses = await Promise.all( - artistIds.map((artistId) => getArtistSocials(artistId)) - ); + const socialsResponses = await Promise.all(artistIds.map(artistId => getArtistSocials(artistId))); // Store socials in map - const artistSocialsMap = new Map< - string, - Awaited> - >(); + const artistSocialsMap = new Map>>(); for (let i = 0; i < artistIds.length; i++) { artistSocialsMap.set(artistIds[i], socialsResponses[i]); diff --git a/src/artists/isScrapableSocial.ts b/src/artists/isScrapableSocial.ts index 119f5bd..248358d 100644 --- a/src/artists/isScrapableSocial.ts +++ b/src/artists/isScrapableSocial.ts @@ -12,6 +12,8 @@ import type { ArtistSocialProfile } from "../recoup/getArtistSocials"; * * Non-scrapable platforms: * - open.spotify.com + * + * @param social */ export function isScrapableSocial(social: ArtistSocialProfile): boolean { const profileUrl = social.profile_url.toLowerCase(); @@ -39,5 +41,5 @@ export function isScrapableSocial(social: ArtistSocialProfile): boolean { "youtube.com", ]; - return scrapableDomains.some((domain) => profileUrl.includes(domain)); + return scrapableDomains.some(domain => profileUrl.includes(domain)); } diff --git a/src/chats/getTaskRoomId.ts b/src/chats/getTaskRoomId.ts index ad92858..7795038 100644 --- a/src/chats/getTaskRoomId.ts +++ b/src/chats/getTaskRoomId.ts @@ -14,9 +14,7 @@ type GetTaskRoomIdParams = { * @param params - Parameters containing optional roomId and artistId * @returns The room ID, or undefined if creation fails */ -export async function getTaskRoomId( - params: GetTaskRoomIdParams -): Promise { +export async function getTaskRoomId(params: GetTaskRoomIdParams): Promise { if (params.roomId) { logger.log("Using existing roomId", { roomId: params.roomId }); return params.roomId; diff --git a/src/consts.ts b/src/consts.ts index 78c714c..5e0a2cc 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -1,4 +1,4 @@ export const NEW_API_BASE_URL = "https://recoup-api.vercel.app"; export const RECOUP_API_KEY = process.env.RECOUP_API_KEY; export const CODING_AGENT_ACCOUNT_ID = "04e3aba9-c130-4fb8-8b92-34e95d43e66b"; -export const OPENCLAW_DEFAULT_MODEL = "vercel-ai-gateway/anthropic/claude-sonnet-4.6"; \ No newline at end of file +export const OPENCLAW_DEFAULT_MODEL = "vercel-ai-gateway/anthropic/claude-sonnet-4.6"; diff --git a/src/content/analyzeClips.ts b/src/content/analyzeClips.ts index a4cc386..8269743 100644 --- a/src/content/analyzeClips.ts +++ b/src/content/analyzeClips.ts @@ -17,10 +17,7 @@ export interface SongClip { * @param lyrics - Timestamped lyrics from transcription * @returns Array of clip recommendations */ -export async function analyzeClips( - songTitle: string, - lyrics: SongLyrics, -): Promise { +export async function analyzeClips(songTitle: string, lyrics: SongLyrics): Promise { const recoupApiKey = process.env.RECOUP_API_KEY; if (!recoupApiKey) { throw new Error("RECOUP_API_KEY is required for clip analysis"); @@ -107,7 +104,10 @@ IMPORTANT: startSeconds must align with actual word timestamps from the lyrics a return [ { startSeconds: 0, - lyrics: lyrics.segments.slice(0, 10).map(s => s.text).join(" "), + lyrics: lyrics.segments + .slice(0, 10) + .map(s => s.text) + .join(" "), reason: "fallback — start of song", mood: "unknown", hasLyrics: true, @@ -123,7 +123,10 @@ IMPORTANT: startSeconds must align with actual word timestamps from the lyrics a return [ { startSeconds: 0, - lyrics: lyrics.segments.slice(0, 10).map(s => s.text).join(" "), + lyrics: lyrics.segments + .slice(0, 10) + .map(s => s.text) + .join(" "), reason: "fallback — JSON parse failed", mood: "unknown", hasLyrics: true, diff --git a/src/content/defaultPipelineConfig.ts b/src/content/defaultPipelineConfig.ts index 144a4cc..9f7f1c0 100644 --- a/src/content/defaultPipelineConfig.ts +++ b/src/content/defaultPipelineConfig.ts @@ -6,7 +6,18 @@ * in their repo, but it's not required. */ -type AspectRatio = "auto" | "21:9" | "16:9" | "3:2" | "4:3" | "5:4" | "1:1" | "4:5" | "3:4" | "2:3" | "9:16"; +type AspectRatio = + | "auto" + | "21:9" + | "16:9" + | "3:2" + | "4:3" + | "5:4" + | "1:1" + | "4:5" + | "3:4" + | "2:3" + | "9:16"; type Resolution = "1K" | "2K" | "4K"; export interface PipelineConfig { diff --git a/src/content/fetchArtistContext.ts b/src/content/fetchArtistContext.ts index ce4c82a..81e7dca 100644 --- a/src/content/fetchArtistContext.ts +++ b/src/content/fetchArtistContext.ts @@ -1,16 +1,17 @@ /** * Fetches artist context (artist.md) from GitHub. * Returns placeholder if not found. + * + * @param githubRepo + * @param artistSlug + * @param fetchFile */ export async function fetchArtistContext( githubRepo: string, artistSlug: string, fetchFile: (repo: string, path: string) => Promise, ): Promise { - const buffer = await fetchFile( - githubRepo, - `artists/${artistSlug}/context/artist.md`, - ); + const buffer = await fetchFile(githubRepo, `artists/${artistSlug}/context/artist.md`); if (!buffer) return "(no artist identity available)"; const content = buffer.toString("utf-8"); // Strip frontmatter if present diff --git a/src/content/fetchAudienceContext.ts b/src/content/fetchAudienceContext.ts index 3f100ec..d39aecc 100644 --- a/src/content/fetchAudienceContext.ts +++ b/src/content/fetchAudienceContext.ts @@ -1,16 +1,20 @@ /** * Fetches audience context (audience.md) from GitHub. * Returns placeholder if not found. + * + * @param githubRepo + * @param artistSlug + * @param fetchFile */ export async function fetchAudienceContext( githubRepo: string, artistSlug: string, fetchFile: (repo: string, path: string) => Promise, ): Promise { - const buffer = await fetchFile( - githubRepo, - `artists/${artistSlug}/context/audience.md`, - ); + const buffer = await fetchFile(githubRepo, `artists/${artistSlug}/context/audience.md`); if (!buffer) return "(no audience context available)"; - return buffer.toString("utf-8").replace(/^---[\s\S]*?---\s*/, "").trim(); + return buffer + .toString("utf-8") + .replace(/^---[\s\S]*?---\s*/, "") + .trim(); } diff --git a/src/content/fetchGithubFile.ts b/src/content/fetchGithubFile.ts index c4f00fe..8253e8f 100644 --- a/src/content/fetchGithubFile.ts +++ b/src/content/fetchGithubFile.ts @@ -4,6 +4,8 @@ import { logger } from "@trigger.dev/sdk/v3"; * Fetches a raw file from a GitHub repo via the API. * If the file isn't found in the main repo, checks submodule org repos. * + * @param githubRepoUrl + * @param filePath * @returns The file as a Buffer, or null if not found anywhere. */ export async function fetchGithubFile( @@ -39,6 +41,10 @@ export async function fetchGithubFile( /** * Fetches a file from a specific GitHub repo. + * + * @param githubRepoUrl + * @param filePath + * @param token */ async function fetchFileFromRepo( githubRepoUrl: string, @@ -67,11 +73,11 @@ async function fetchFileFromRepo( /** * Reads .gitmodules from the main repo and extracts org submodule URLs. + * + * @param githubRepoUrl + * @param token */ -async function getOrgRepoUrls( - githubRepoUrl: string, - token: string, -): Promise { +async function getOrgRepoUrls(githubRepoUrl: string, token: string): Promise { const gitmodules = await fetchFileFromRepo(githubRepoUrl, ".gitmodules", token); if (!gitmodules) return []; @@ -87,6 +93,10 @@ async function getOrgRepoUrls( return urls; } +/** + * + * @param githubRepoUrl + */ function parseRepoUrl(githubRepoUrl: string): { owner: string; repo: string } { const match = githubRepoUrl.match(/github\.com\/([^/]+)\/([^/]+)/); if (!match) { diff --git a/src/content/generateAudioVideo.ts b/src/content/generateAudioVideo.ts index 6040e47..8bfc144 100644 --- a/src/content/generateAudioVideo.ts +++ b/src/content/generateAudioVideo.ts @@ -16,11 +16,16 @@ const execFileAsync = promisify(execFile); * * Matches the content-creation-app's generateAudioVideo.ts behavior. * + * @param imageUrl.imageUrl * @param imageUrl - URL of the AI-generated image * @param songBuffer - Raw mp3 bytes of the song * @param audioStartSeconds - Where to clip the song from * @param audioDurationSeconds - How long the clip should be * @param motionPrompt - Describes how the subject should move + * @param imageUrl.songBuffer + * @param imageUrl.audioStartSeconds + * @param imageUrl.audioDurationSeconds + * @param imageUrl.motionPrompt * @returns URL of the generated video (with audio baked in) */ export async function generateAudioVideo({ @@ -37,19 +42,12 @@ export async function generateAudioVideo({ motionPrompt: string; }): Promise { const config = DEFAULT_PIPELINE_CONFIG; - const durationSeconds = Math.min( - audioDurationSeconds, - config.audioVideoModelMaxSeconds, - ); + const durationSeconds = Math.min(audioDurationSeconds, config.audioVideoModelMaxSeconds); const fps = 25; const numFrames = Math.round(durationSeconds * fps) + 1; // Clip the audio to the right section and upload to fal - const audioUrl = await clipAndUploadAudio( - songBuffer, - audioStartSeconds, - durationSeconds, - ); + const audioUrl = await clipAndUploadAudio(songBuffer, audioStartSeconds, durationSeconds); logger.log("Generating audio-to-video (lipsync)", { model: config.audioVideoModel, @@ -89,6 +87,10 @@ export async function generateAudioVideo({ /** * Clips the song mp3 to the specified range and uploads to fal storage. + * + * @param songBuffer + * @param startSeconds + * @param durationSeconds */ async function clipAndUploadAudio( songBuffer: Buffer, @@ -105,10 +107,14 @@ async function clipAndUploadAudio( await execFileAsync("ffmpeg", [ "-y", - "-i", inputPath, - "-ss", String(startSeconds), - "-t", String(durationSeconds), - "-c", "copy", + "-i", + inputPath, + "-ss", + String(startSeconds), + "-t", + String(durationSeconds), + "-c", + "copy", clippedPath, ]); @@ -128,6 +134,10 @@ async function clipAndUploadAudio( } } +/** + * + * @param data + */ function extractFalUrl(data: Record): string | undefined { for (const key of ["image", "video"]) { if (data[key] && typeof data[key] === "object") { diff --git a/src/content/generateCaption.ts b/src/content/generateCaption.ts index c30f97e..4d8ab74 100644 --- a/src/content/generateCaption.ts +++ b/src/content/generateCaption.ts @@ -4,8 +4,10 @@ import type { SongLyrics } from "./transcribeSong"; import type { CaptionLength } from "../schemas/contentCreationSchema"; const CAPTION_LENGTH_INSTRUCTIONS: Record = { - short: "Write a SHORT caption (max 10 words). Punchy, minimal, like a text message. Think: one phrase that hits.", - medium: "Write a MEDIUM caption (15-30 words). A complete thought with feeling. 1-2 sentences max.", + short: + "Write a SHORT caption (max 10 words). Punchy, minimal, like a text message. Think: one phrase that hits.", + medium: + "Write a MEDIUM caption (15-30 words). A complete thought with feeling. 1-2 sentences max.", long: "Write a LONG caption (40-80 words). A mini-story or stream of consciousness. Vulnerable, raw, the kind of caption people screenshot.", }; @@ -14,6 +16,15 @@ const CAPTION_LENGTH_INSTRUCTIONS: Record = { * Combines template style, artist context, song lyrics, and audience data. * * Matches the content-creation-app's generateCaption.ts behavior. + * + * @param root0 + * @param root0.template + * @param root0.songTitle + * @param root0.fullLyrics + * @param root0.clipLyrics + * @param root0.artistContext + * @param root0.audienceContext + * @param root0.captionLength */ export async function generateCaption({ template, @@ -41,9 +52,10 @@ export async function generateCaption({ ? JSON.stringify(template.captionGuide, null, 2) : "(no caption guide)"; - const examples = template.captionExamples.length > 0 - ? template.captionExamples.map(c => `- "${c}"`).join("\n") - : "(no examples)"; + const examples = + template.captionExamples.length > 0 + ? template.captionExamples.map(c => `- "${c}"`).join("\n") + : "(no examples)"; const lengthInstruction = CAPTION_LENGTH_INSTRUCTIONS[captionLength]; @@ -117,4 +129,3 @@ Generate ONE caption. ${lengthInstruction} Return ONLY the caption text, nothing logger.log("Caption generated", { caption: captionText.slice(0, 80) }); return captionText; } - diff --git a/src/content/generateContentImage.ts b/src/content/generateContentImage.ts index ab7c05a..a54893e 100644 --- a/src/content/generateContentImage.ts +++ b/src/content/generateContentImage.ts @@ -13,9 +13,12 @@ import { DEFAULT_PIPELINE_CONFIG } from "./defaultPipelineConfig"; * The prompt tells the model to replace the person in the reference scene * with the person from the face-guide headshot. * + * @param faceGuideUrl.faceGuideUrl * @param faceGuideUrl - fal storage URL of the artist's face-guide (headshot) * @param referenceImagePath - local path to a template reference image (or null) * @param prompt - Scene/style prompt that instructs the face swap + * @param faceGuideUrl.referenceImagePath + * @param faceGuideUrl.prompt * @returns URL of the generated image */ export async function generateContentImage({ @@ -75,7 +78,11 @@ export async function generateContentImage({ return imageUrl; } -/** Extracts a media URL from various fal.ai response shapes. */ +/** + * Extracts a media URL from various fal.ai response shapes. + * + * @param data + */ function extractFalUrl(data: Record): string | undefined { for (const key of ["image", "video"]) { if (data[key] && typeof data[key] === "object") { diff --git a/src/content/generateContentVideo.ts b/src/content/generateContentVideo.ts index 44f3210..91ba135 100644 --- a/src/content/generateContentVideo.ts +++ b/src/content/generateContentVideo.ts @@ -5,8 +5,10 @@ import { DEFAULT_PIPELINE_CONFIG } from "./defaultPipelineConfig"; /** * Generates a video from an AI-generated image using fal.ai image-to-video. * + * @param imageUrl.imageUrl * @param imageUrl - URL of the source image (from generateContentImage) * @param motionPrompt - Describes how the subject should move + * @param imageUrl.motionPrompt * @returns URL of the generated video */ export async function generateContentVideo({ @@ -17,10 +19,7 @@ export async function generateContentVideo({ motionPrompt: string; }): Promise { const config = DEFAULT_PIPELINE_CONFIG; - const durationSeconds = Math.min( - config.clipDuration, - config.videoModelMaxSeconds, - ); + const durationSeconds = Math.min(config.clipDuration, config.videoModelMaxSeconds); logger.log("Generating video", { model: config.videoModel, @@ -55,6 +54,8 @@ export async function generateContentVideo({ /** * Extracts a media URL from various fal.ai response shapes. + * + * @param data */ function extractFalUrl(data: Record): string | undefined { for (const key of ["image", "video"]) { diff --git a/src/content/listArtistSongs.ts b/src/content/listArtistSongs.ts index 87fca16..dd2357a 100644 --- a/src/content/listArtistSongs.ts +++ b/src/content/listArtistSongs.ts @@ -4,6 +4,8 @@ import { logger } from "@trigger.dev/sdk/v3"; * Lists mp3 files available in an artist's GitHub repo. * Checks main repo first, then submodule org repos. * + * @param githubRepoUrl + * @param artistSlug * @returns Array of file paths relative to the repo root. */ export async function listArtistSongs( @@ -40,6 +42,8 @@ export async function listArtistSongs( /** * Parses an encoded song path to extract the actual repo URL and file path. * Song paths from org repos are encoded as: __ORG_REPO__{url}__{path} + * + * @param encodedPath */ export function parseSongPath(encodedPath: string): { repoUrl: string | null; @@ -52,6 +56,12 @@ export function parseSongPath(encodedPath: string): { return { repoUrl: null, filePath: encodedPath }; } +/** + * + * @param githubRepoUrl + * @param artistSlug + * @param token + */ async function listMp3sInRepo( githubRepoUrl: string, artistSlug: string, @@ -89,10 +99,12 @@ async function listMp3sInRepo( .map(entry => entry.path); } -async function getOrgRepoUrls( - githubRepoUrl: string, - token: string, -): Promise { +/** + * + * @param githubRepoUrl + * @param token + */ +async function getOrgRepoUrls(githubRepoUrl: string, token: string): Promise { const match = githubRepoUrl.match(/github\.com\/([^/]+)\/([^/]+)/); if (!match) return []; const [, owner, repo] = match; diff --git a/src/content/loadTemplate.ts b/src/content/loadTemplate.ts index c01bc3b..e77b5cf 100644 --- a/src/content/loadTemplate.ts +++ b/src/content/loadTemplate.ts @@ -23,6 +23,8 @@ const TEMPLATES_DIR = path.resolve(__dirname, "../content/templates"); /** * Load all template data (style guide, caption guide, moods, movements, reference images). + * + * @param templateName */ export async function loadTemplate(templateName: string): Promise { const templateDir = path.join(TEMPLATES_DIR, templateName); @@ -33,15 +35,14 @@ export async function loadTemplate(templateName: string): Promise const captionGuide = await loadJsonFile>( path.join(templateDir, "caption-guide.json"), ); - const captionExamples = await loadJsonFile( - path.join(templateDir, "references", "captions", "examples.json"), - ) ?? []; - const videoMoods = await loadJsonFile( - path.join(templateDir, "video-moods.json"), - ) ?? []; - const videoMovements = await loadJsonFile( - path.join(templateDir, "video-movements.json"), - ) ?? []; + const captionExamples = + (await loadJsonFile( + path.join(templateDir, "references", "captions", "examples.json"), + )) ?? []; + const videoMoods = + (await loadJsonFile(path.join(templateDir, "video-moods.json"))) ?? []; + const videoMovements = + (await loadJsonFile(path.join(templateDir, "video-movements.json"))) ?? []; // Discover reference images const imagesDir = path.join(templateDir, "references", "images"); @@ -77,6 +78,8 @@ export async function loadTemplate(templateName: string): Promise /** * Pick a random reference image path from the template. + * + * @param template */ export function pickRandomReferenceImage(template: TemplateData): string | null { if (template.referenceImagePaths.length === 0) return null; @@ -87,6 +90,9 @@ export function pickRandomReferenceImage(template: TemplateData): string | null /** * Build the full image prompt by combining the base prompt with the style guide rules. * Matches the logic from the content-creation-app's generateImage.ts. + * + * @param basePrompt + * @param styleGuide */ export function buildImagePrompt( basePrompt: string, @@ -119,20 +125,28 @@ export function buildImagePrompt( /** * Build a motion prompt for video generation using template mood/movement variations. * Matches the logic from the content-creation-app's generateVideo.ts. + * + * @param template */ export function buildMotionPrompt(template: TemplateData): string { - const movement = template.videoMovements.length > 0 - ? template.videoMovements[Math.floor(Math.random() * template.videoMovements.length)] - : "nearly still, only natural breathing"; + const movement = + template.videoMovements.length > 0 + ? template.videoMovements[Math.floor(Math.random() * template.videoMovements.length)] + : "nearly still, only natural breathing"; - const mood = template.videoMoods.length > 0 - ? template.videoMoods[Math.floor(Math.random() * template.videoMoods.length)] - : ""; + const mood = + template.videoMoods.length > 0 + ? template.videoMoods[Math.floor(Math.random() * template.videoMoods.length)] + : ""; return `Completely static camera. The person stares at the camera. Movement: ${movement}.${mood ? ` Energy: ${mood}.` : ""} Shot on phone, low light, grainy.`; } -/** Load a JSON file, returning null if it doesn't exist. */ +/** + * Load a JSON file, returning null if it doesn't exist. + * + * @param filePath + */ async function loadJsonFile(filePath: string): Promise { try { const raw = await fs.readFile(filePath, "utf-8"); diff --git a/src/content/renderFinalVideo.ts b/src/content/renderFinalVideo.ts index c3a43c8..6ed4d2f 100644 --- a/src/content/renderFinalVideo.ts +++ b/src/content/renderFinalVideo.ts @@ -11,6 +11,8 @@ const execFileAsync = promisify(execFile); /** * Strips emoji and other non-ASCII characters that ffmpeg drawtext can't render. + * + * @param text */ function stripEmoji(text: string): string { return text @@ -24,6 +26,9 @@ function stripEmoji(text: string): string { /** * Wraps text to fit within a max character width per line. * Breaks on word boundaries to avoid mid-word splits. + * + * @param text + * @param maxCharsPerLine */ function wrapText(text: string, maxCharsPerLine: number): string[] { const words = text.split(" "); @@ -60,11 +65,13 @@ const BOTTOM_MARGIN = 120; * so captions never get cut off regardless of text length. * * Vertical positioning: - * - Short (1-3 lines): bottom of frame - * - Medium (4-6 lines): vertically centered - * - Long (7+ lines): starts from top area + * - Short (1-3 lines): bottom of frame + * - Medium (4-6 lines): vertically centered + * - Long (7+ lines): starts from top area * * Font auto-shrinks if text doesn't fit within the available space. + * + * @param text */ function calculateCaptionLayout(text: string): { lines: string[]; @@ -80,7 +87,7 @@ function calculateCaptionLayout(text: string): { let chosenLineHeight = MIN_FONT_SIZE + 10; for (let fontSize = MAX_FONT_SIZE; fontSize >= MIN_FONT_SIZE; fontSize -= 2) { - const charsPerLine = Math.floor(FRAME_WIDTH * 0.85 / (fontSize * 0.55)); + const charsPerLine = Math.floor((FRAME_WIDTH * 0.85) / (fontSize * 0.55)); const lineHeight = fontSize + 10; const lines = wrapText(text, charsPerLine); const totalHeight = lines.length * lineHeight; @@ -135,11 +142,13 @@ export interface RenderFinalVideoOutput { /** * Renders the final social post video using ffmpeg: - * 1. Downloads the AI-generated video - * 2. Crops 16:9 → 9:16 (portrait for TikTok/Reels) - * 3. Overlays audio clip from the song (unless lipsync mode) - * 4. Overlays caption text (white with black stroke, bottom center) - * 5. Returns the final video as a data URL + * 1. Downloads the AI-generated video + * 2. Crops 16:9 → 9:16 (portrait for TikTok/Reels) + * 3. Overlays audio clip from the song (unless lipsync mode) + * 4. Overlays caption text (white with black stroke, bottom center) + * 5. Returns the final video as a data URL + * + * @param input */ export async function renderFinalVideo( input: RenderFinalVideoInput, @@ -219,9 +228,22 @@ export async function renderFinalVideo( * Builds the ffmpeg arguments for the final render. * * What it does (matching Remotion SocialPost): - * 1. Center-crop 16:9 → 9:16 portrait - * 2. Overlay audio from song clip (skip if lipsync — audio already in video) - * 3. Overlay caption text (white, black stroke, bottom center) + * 1. Center-crop 16:9 → 9:16 portrait + * 2. Overlay audio from song clip (skip if lipsync — audio already in video) + * 3. Overlay caption text (white, black stroke, bottom center) + * + * @param root0 + * @param root0.videoPath + * @param root0.audioPath + * @param root0.captionLayout + * @param root0.captionLayout.lines + * @param root0.captionLayout.fontSize + * @param root0.captionLayout.lineHeight + * @param root0.captionLayout.position + * @param root0.outputPath + * @param root0.audioStartSeconds + * @param root0.audioDurationSeconds + * @param root0.hasAudio */ function buildFfmpegArgs({ videoPath, @@ -234,7 +256,12 @@ function buildFfmpegArgs({ }: { videoPath: string; audioPath: string; - captionLayout: { lines: string[]; fontSize: number; lineHeight: number; position: "bottom" | "center" | "top" }; + captionLayout: { + lines: string[]; + fontSize: number; + lineHeight: number; + position: "bottom" | "center" | "top"; + }; outputPath: string; audioStartSeconds: number; audioDurationSeconds: number; @@ -275,7 +302,7 @@ function buildFfmpegArgs({ .replace(/\n/g, " ") .replace(/\r/g, ""); - const yPos = blockStartY + (i * lineHeight); + const yPos = blockStartY + i * lineHeight; return [ `drawtext=text='${escaped}'`, @@ -295,29 +322,46 @@ function buildFfmpegArgs({ if (hasAudio) { // Lipsync mode: video already has audio, just crop + caption args.push( - "-i", videoPath, - "-vf", videoFilter, - "-c:v", "libx264", - "-c:a", "aac", - "-pix_fmt", "yuv420p", - "-movflags", "+faststart", + "-i", + videoPath, + "-vf", + videoFilter, + "-c:v", + "libx264", + "-c:a", + "aac", + "-pix_fmt", + "yuv420p", + "-movflags", + "+faststart", "-shortest", outputPath, ); } else { // Normal mode: crop video + overlay song audio clip args.push( - "-i", videoPath, - "-ss", String(audioStartSeconds), - "-t", String(audioDurationSeconds), - "-i", audioPath, - "-vf", videoFilter, - "-c:v", "libx264", - "-c:a", "aac", - "-map", "0:v:0", - "-map", "1:a:0", - "-pix_fmt", "yuv420p", - "-movflags", "+faststart", + "-i", + videoPath, + "-ss", + String(audioStartSeconds), + "-t", + String(audioDurationSeconds), + "-i", + audioPath, + "-vf", + videoFilter, + "-c:v", + "libx264", + "-c:a", + "aac", + "-map", + "0:v:0", + "-map", + "1:a:0", + "-pix_fmt", + "yuv420p", + "-movflags", + "+faststart", "-shortest", outputPath, ); diff --git a/src/content/selectAudioClip.ts b/src/content/selectAudioClip.ts index 6f223d3..990ec30 100644 --- a/src/content/selectAudioClip.ts +++ b/src/content/selectAudioClip.ts @@ -37,10 +37,14 @@ export interface SelectedAudioClip { * 5. Analyze clips with Recoup Chat API * 6. Pick the best clip * + * @param githubRepo.githubRepo * @param githubRepo - GitHub repo URL * @param artistSlug - Artist directory name * @param clipDuration - How long the clip should be (seconds) * @param lipsync - Whether to prefer clips with lyrics (for lipsync mode) + * @param githubRepo.artistSlug + * @param githubRepo.clipDuration + * @param githubRepo.lipsync * @returns Selected audio clip with all metadata */ export async function selectAudioClip({ diff --git a/src/content/testPipeline.ts b/src/content/testPipeline.ts index c6dbf63..8a5b4c7 100644 --- a/src/content/testPipeline.ts +++ b/src/content/testPipeline.ts @@ -25,7 +25,8 @@ dotenv.config({ path: path.resolve(process.cwd(), ".env.local") }); dotenv.config({ path: path.resolve(process.cwd(), ".env") }); const STATE_FILE = path.resolve(process.cwd(), ".pipeline-state.json"); -const GITHUB_REPO = "https://github.com/recoupable/sidney-swift-1ca89eeb-14ab-4a4a-a1c5-2dd41663c039"; +const GITHUB_REPO = + "https://github.com/recoupable/sidney-swift-1ca89eeb-14ab-4a4a-a1c5-2dd41663c039"; const ARTIST_SLUG = "gatsby-grace"; const TEMPLATE_NAME = "artist-caption-bedroom"; @@ -46,6 +47,9 @@ interface PipelineState { finalVideoPath?: string; } +/** + * + */ async function loadState(): Promise { try { return JSON.parse(await fs.readFile(STATE_FILE, "utf-8")); @@ -54,12 +58,19 @@ async function loadState(): Promise { } } +/** + * + * @param state + */ async function saveState(state: PipelineState): Promise { await fs.writeFile(STATE_FILE, JSON.stringify(state, null, 2)); console.log(` 💾 State saved to ${STATE_FILE}`); } // --- Configure fal --- +/** + * + */ function setupFal(): void { if (!process.env.FAL_KEY) { console.error("❌ FAL_KEY not set in .env.local"); @@ -70,11 +81,16 @@ function setupFal(): void { // --- Steps --- +/** + * + */ async function testImage(): Promise { setupFal(); const { fetchGithubFile } = await import("./fetchGithubFile.js"); const { generateContentImage } = await import("./generateContentImage.js"); - const { loadTemplate, pickRandomReferenceImage, buildImagePrompt } = await import("./loadTemplate.js"); + const { loadTemplate, pickRandomReferenceImage, buildImagePrompt } = await import( + "./loadTemplate.js" + ); const { FACE_SWAP_INSTRUCTION } = await import("./contentPrompts.js"); console.log("\n🎨 Testing: Image Generation\n"); @@ -84,7 +100,10 @@ async function testImage(): Promise { // Fetch face-guide console.log(" Fetching face-guide..."); - const faceGuideBuffer = await fetchGithubFile(GITHUB_REPO, `artists/${ARTIST_SLUG}/context/images/face-guide.png`); + const faceGuideBuffer = await fetchGithubFile( + GITHUB_REPO, + `artists/${ARTIST_SLUG}/context/images/face-guide.png`, + ); if (!faceGuideBuffer) throw new Error("face-guide.png not found"); const faceGuideFile = new File([faceGuideBuffer], "face-guide.png", { type: "image/png" }); const faceGuideUrl = await fal.storage.upload(faceGuideFile); @@ -100,7 +119,11 @@ async function testImage(): Promise { console.log(` Prompt: "${prompt.slice(0, 80)}..."`); console.log(" 🔄 Generating image...\n"); - const imageUrl = await generateContentImage({ faceGuideUrl, referenceImagePath: refPath, prompt }); + const imageUrl = await generateContentImage({ + faceGuideUrl, + referenceImagePath: refPath, + prompt, + }); console.log(`\n ✅ Image generated!`); console.log(` 🔗 ${imageUrl}`); @@ -109,6 +132,9 @@ async function testImage(): Promise { await saveState({ ...state, faceGuideUrl, imageUrl }); } +/** + * + */ async function testVideo(): Promise { setupFal(); const { generateContentVideo } = await import("./generateContentVideo.js"); @@ -116,7 +142,10 @@ async function testVideo(): Promise { const state = await loadState(); const imageUrl = state.upscaledImageUrl ?? state.imageUrl; - if (!imageUrl) { console.error("❌ No image URL in state. Run: image first"); process.exit(1); } + if (!imageUrl) { + console.error("❌ No image URL in state. Run: image first"); + process.exit(1); + } console.log("\n🎬 Testing: Video Generation\n"); console.log(` Image: ${imageUrl.slice(0, 60)}...`); @@ -134,12 +163,18 @@ async function testVideo(): Promise { await saveState({ ...state, videoUrl }); } +/** + * + */ async function testUpscaleImage(): Promise { setupFal(); const { upscaleImage } = await import("./upscaleImage.js"); const state = await loadState(); - if (!state.imageUrl) { console.error("❌ No image URL in state. Run: image first"); process.exit(1); } + if (!state.imageUrl) { + console.error("❌ No image URL in state. Run: image first"); + process.exit(1); + } console.log("\n🔍 Testing: Image Upscale\n"); console.log(` Input: ${state.imageUrl.slice(0, 60)}...`); @@ -153,12 +188,18 @@ async function testUpscaleImage(): Promise { await saveState({ ...state, upscaledImageUrl }); } +/** + * + */ async function testUpscaleVideo(): Promise { setupFal(); const { upscaleVideo } = await import("./upscaleVideo.js"); const state = await loadState(); - if (!state.videoUrl) { console.error("❌ No video URL in state. Run: video first"); process.exit(1); } + if (!state.videoUrl) { + console.error("❌ No video URL in state. Run: video first"); + process.exit(1); + } console.log("\n🔍 Testing: Video Upscale\n"); console.log(` Input: ${state.videoUrl.slice(0, 60)}...`); @@ -172,6 +213,9 @@ async function testUpscaleVideo(): Promise { await saveState({ ...state, upscaledVideoUrl }); } +/** + * + */ async function testAudio(): Promise { setupFal(); const { selectAudioClip } = await import("./selectAudioClip.js"); @@ -204,13 +248,21 @@ async function testAudio(): Promise { }); } +/** + * + */ async function testCaption(): Promise { - const { generateCaption, fetchArtistContext, fetchAudienceContext } = await import("./generateCaption.js"); + const { generateCaption, fetchArtistContext, fetchAudienceContext } = await import( + "./generateCaption.js" + ); const { fetchGithubFile } = await import("./fetchGithubFile.js"); const { loadTemplate } = await import("./loadTemplate.js"); const state = await loadState(); - if (!state.songTitle) { console.error("❌ No song in state. Run: audio first"); process.exit(1); } + if (!state.songTitle) { + console.error("❌ No song in state. Run: audio first"); + process.exit(1); + } console.log("\n✍️ Testing: Caption Generation\n"); @@ -236,13 +288,22 @@ async function testCaption(): Promise { await saveState({ ...state, captionText }); } +/** + * + */ async function testRender(): Promise { const { renderFinalVideo } = await import("./renderFinalVideo.js"); const state = await loadState(); const videoUrl = state.upscaledVideoUrl ?? state.videoUrl; - if (!videoUrl) { console.error("❌ No video URL. Run: video first"); process.exit(1); } - if (!state.songBuffer) { console.error("❌ No audio. Run: audio first"); process.exit(1); } + if (!videoUrl) { + console.error("❌ No video URL. Run: video first"); + process.exit(1); + } + if (!state.songBuffer) { + console.error("❌ No audio. Run: audio first"); + process.exit(1); + } console.log("\n🎬 Testing: ffmpeg Final Render\n"); console.log(` Video: ${videoUrl.slice(0, 60)}...`); @@ -270,10 +331,18 @@ async function testRender(): Promise { await saveState({ ...state, finalVideoPath: outPath }); } +/** + * + */ async function testRenderOnly(): Promise { const { execFile } = await import("node:child_process"); const { promisify } = await import("node:util"); - const { writeFile: writeFileFn, readFile: readFileFn, unlink: unlinkFn, mkdir: mkdirFn } = await import("node:fs/promises"); + const { + writeFile: writeFileFn, + readFile: readFileFn, + unlink: unlinkFn, + mkdir: mkdirFn, + } = await import("node:fs/promises"); const { randomUUID } = await import("node:crypto"); const { tmpdir } = await import("node:os"); const execFileAsync = promisify(execFile); @@ -289,26 +358,41 @@ async function testRenderOnly(): Promise { console.log(" Creating test video..."); await execFileAsync("ffmpeg", [ - "-y", "-f", "lavfi", "-i", "testsrc=s=1280x720:d=3", - "-c:v", "libx264", "-pix_fmt", "yuv420p", testVideoPath, + "-y", + "-f", + "lavfi", + "-i", + "testsrc=s=1280x720:d=3", + "-c:v", + "libx264", + "-pix_fmt", + "yuv420p", + testVideoPath, ]); console.log(" Creating test audio..."); await execFileAsync("ffmpeg", [ - "-y", "-f", "lavfi", "-i", "anullsrc=r=44100:cl=stereo", - "-t", "3", silentPath, + "-y", + "-f", + "lavfi", + "-i", + "anullsrc=r=44100:cl=stereo", + "-t", + "3", + silentPath, ]); // Test with a LONG caption to verify adaptive sizing works - const testCaption = process.argv[3] === "long" - ? "sometimes you just gotta sit in the dark and let the playlist do the talking because words ain't gonna fix what's broken inside but at least the bass hits right where it hurts and you wonder if anyone else is sitting in their room right now feeling the exact same thing scrolling through old photos at 3am knowing you should sleep but the music won't let you go" - : process.argv[3] === "medium" - ? "sometimes you just gotta sit in the dark and let the playlist do the talking because words ain't gonna fix what's broken inside" - : "that one drawer holding all the memories you just can't throw away"; + const testCaption = + process.argv[3] === "long" + ? "sometimes you just gotta sit in the dark and let the playlist do the talking because words ain't gonna fix what's broken inside but at least the bass hits right where it hurts and you wonder if anyone else is sitting in their room right now feeling the exact same thing scrolling through old photos at 3am knowing you should sleep but the music won't let you go" + : process.argv[3] === "medium" + ? "sometimes you just gotta sit in the dark and let the playlist do the talking because words ain't gonna fix what's broken inside" + : "that one drawer holding all the memories you just can't throw away"; console.log(` Caption: "${testCaption.slice(0, 60)}${testCaption.length > 60 ? "..." : ""}"`); console.log(" 🔄 Rendering...\n"); - + // Inline the adaptive layout logic for the test const FRAME_W = 720; const FRAME_H = 1280; @@ -317,13 +401,22 @@ async function testRenderOnly(): Promise { const MAX_FS = 42; const BOTTOM_M = 120; + /** + * + * @param text + * @param maxChars + */ function wrapLocal(text: string, maxChars: number): string[] { const words = text.split(" "); const lines: string[] = []; let cur = ""; for (const w of words) { - if (cur.length + w.length + 1 > maxChars && cur.length > 0) { lines.push(cur); cur = w; } - else { cur = cur ? `${cur} ${w}` : w; } + if (cur.length + w.length + 1 > maxChars && cur.length > 0) { + lines.push(cur); + cur = w; + } else { + cur = cur ? `${cur} ${w}` : w; + } } if (cur) lines.push(cur); return lines; @@ -333,13 +426,18 @@ async function testRenderOnly(): Promise { let chosenLh = chosenFs + 10; let chosenLines: string[] = []; for (let fs = MAX_FS; fs >= MIN_FS; fs -= 2) { - const cpl = Math.floor(FRAME_W * 0.85 / (fs * 0.55)); + const cpl = Math.floor((FRAME_W * 0.85) / (fs * 0.55)); const lh = fs + 10; const lines = wrapLocal(testCaption.replace(/'/g, "\u2019"), cpl); if (lines.length * lh <= FRAME_H * MAX_H_RATIO) { - chosenFs = fs; chosenLh = lh; chosenLines = lines; break; + chosenFs = fs; + chosenLh = lh; + chosenLines = lines; + break; } - chosenFs = fs; chosenLh = lh; chosenLines = lines; + chosenFs = fs; + chosenLh = lh; + chosenLines = lines; } // Determine position based on line count @@ -349,15 +447,19 @@ async function testRenderOnly(): Promise { const FH = 1280; const totalTH = chosenLines.length * chosenLh; let blockStartY: number; - if (position === "bottom") { blockStartY = FH - BOTTOM_M - totalTH; } - else if (position === "center") { blockStartY = Math.round((FH - totalTH) / 2); } - else { blockStartY = 180; } + if (position === "bottom") { + blockStartY = FH - BOTTOM_M - totalTH; + } else if (position === "center") { + blockStartY = Math.round((FH - totalTH) / 2); + } else { + blockStartY = 180; + } const cropFilter = "crop=ih*9/16:ih"; const scaleFilter = "scale=720:1280"; const bw = Math.max(2, Math.round(chosenFs / 14)); const captionFilters = chosenLines.map((line, i) => { - const yPos = blockStartY + (i * chosenLh); + const yPos = blockStartY + i * chosenLh; return `drawtext=text='${line}':fontsize=${chosenFs}:fontcolor=white:borderw=${bw}:bordercolor=black:x=(w-tw)/2:y=${yPos}`; }); @@ -369,16 +471,26 @@ async function testRenderOnly(): Promise { try { await execFileAsync("ffmpeg", [ "-y", - "-i", testVideoPath, - "-t", "3", - "-i", silentPath, - "-vf", videoFilter, - "-c:v", "libx264", - "-c:a", "aac", - "-map", "0:v:0", - "-map", "1:a:0", - "-pix_fmt", "yuv420p", - "-movflags", "+faststart", + "-i", + testVideoPath, + "-t", + "3", + "-i", + silentPath, + "-vf", + videoFilter, + "-c:v", + "libx264", + "-c:a", + "aac", + "-map", + "0:v:0", + "-map", + "1:a:0", + "-pix_fmt", + "yuv420p", + "-movflags", + "+faststart", "-shortest", outputPath, ]); diff --git a/src/content/transcribeSong.ts b/src/content/transcribeSong.ts index bb19b51..bf2a6fd 100644 --- a/src/content/transcribeSong.ts +++ b/src/content/transcribeSong.ts @@ -68,15 +68,17 @@ export async function transcribeSong( /** * Gets the lyrics for a specific time range within a song. + * + * @param lyrics + * @param startSeconds + * @param endSeconds */ export function getLyricsForTimeRange( lyrics: SongLyrics, startSeconds: number, endSeconds: number, ): { clipLyrics: string; segments: LyricSegment[] } { - const segments = lyrics.segments.filter( - s => s.start < endSeconds && s.end > startSeconds, - ); + const segments = lyrics.segments.filter(s => s.start < endSeconds && s.end > startSeconds); const clipLyrics = segments.map(s => s.text).join(" "); return { clipLyrics, segments }; } diff --git a/src/content/upscaleImage.ts b/src/content/upscaleImage.ts index 36cb509..879e516 100644 --- a/src/content/upscaleImage.ts +++ b/src/content/upscaleImage.ts @@ -37,6 +37,10 @@ export async function upscaleImage(imageUrl: string): Promise { return url; } +/** + * + * @param data + */ function extractFalUrl(data: Record): string | undefined { for (const key of ["image", "video"]) { if (data[key] && typeof data[key] === "object") { diff --git a/src/content/upscaleVideo.ts b/src/content/upscaleVideo.ts index c07d5ea..f14fae5 100644 --- a/src/content/upscaleVideo.ts +++ b/src/content/upscaleVideo.ts @@ -43,6 +43,10 @@ export async function upscaleVideo(videoUrl: string): Promise { return url; } +/** + * + * @param data + */ function extractFalUrl(data: Record): string | undefined { for (const key of ["image", "video"]) { if (data[key] && typeof data[key] === "object") { diff --git a/src/github/__tests__/createOrgGithubRepo.test.ts b/src/github/__tests__/createOrgGithubRepo.test.ts index b03395b..03f82ef 100644 --- a/src/github/__tests__/createOrgGithubRepo.test.ts +++ b/src/github/__tests__/createOrgGithubRepo.test.ts @@ -26,9 +26,7 @@ describe("createOrgGithubRepo", () => { const result = await createOrgGithubRepo("Test Org", "uuid-123"); - expect(result).toBe( - "https://github.com/Recoupable-Com/org-test-org-uuid-123" - ); + expect(result).toBe("https://github.com/Recoupable-Com/org-test-org-uuid-123"); expect(mockFetch).toHaveBeenCalledOnce(); // Verify the request body has the right repo name and auto_init @@ -57,9 +55,7 @@ describe("createOrgGithubRepo", () => { const result = await createOrgGithubRepo("My Org", "uuid-456"); - expect(result).toBe( - "https://github.com/Recoupable-Com/org-my-org-uuid-456" - ); + expect(result).toBe("https://github.com/Recoupable-Com/org-my-org-uuid-456"); expect(mockFetch).toHaveBeenCalledTimes(2); }); @@ -104,8 +100,7 @@ describe("createOrgGithubRepo", () => { mockFetch.mockResolvedValueOnce({ ok: true, json: async () => ({ - html_url: - "https://github.com/Recoupable-Com/org-my-cool-org-uuid-111", + html_url: "https://github.com/Recoupable-Com/org-my-cool-org-uuid-111", }), }); diff --git a/src/github/createGithubRepo.ts b/src/github/createGithubRepo.ts index 1c28d0e..828c9ca 100644 --- a/src/github/createGithubRepo.ts +++ b/src/github/createGithubRepo.ts @@ -13,7 +13,7 @@ const GITHUB_ORG = "recoupable"; */ export async function createGithubRepo( accountName: string, - accountId: string + accountId: string, ): Promise { const token = process.env.GITHUB_TOKEN; @@ -32,21 +32,18 @@ export async function createGithubRepo( }); try { - const response = await fetch( - `https://api.github.com/orgs/${GITHUB_ORG}/repos`, - { - method: "POST", - headers: { - Accept: "application/vnd.github+json", - Authorization: `Bearer ${token}`, - "X-GitHub-Api-Version": "2022-11-28", - }, - body: JSON.stringify({ - name: repoName, - private: true, - }), - } - ); + const response = await fetch(`https://api.github.com/orgs/${GITHUB_ORG}/repos`, { + method: "POST", + headers: { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${token}`, + "X-GitHub-Api-Version": "2022-11-28", + }, + body: JSON.stringify({ + name: repoName, + private: true, + }), + }); if (!response.ok) { // 422 means repo already exists — fetch existing URL diff --git a/src/github/createOrgGithubRepo.ts b/src/github/createOrgGithubRepo.ts index aa84640..1e43166 100644 --- a/src/github/createOrgGithubRepo.ts +++ b/src/github/createOrgGithubRepo.ts @@ -16,7 +16,7 @@ const GITHUB_ORG = "recoupable"; */ export async function createOrgGithubRepo( orgName: string, - orgId: string + orgId: string, ): Promise { const token = process.env.GITHUB_TOKEN; @@ -35,22 +35,19 @@ export async function createOrgGithubRepo( }); try { - const response = await fetch( - `https://api.github.com/orgs/${GITHUB_ORG}/repos`, - { - method: "POST", - headers: { - Accept: "application/vnd.github+json", - Authorization: `Bearer ${token}`, - "X-GitHub-Api-Version": "2022-11-28", - }, - body: JSON.stringify({ - name: repoName, - private: true, - auto_init: true, - }), - } - ); + const response = await fetch(`https://api.github.com/orgs/${GITHUB_ORG}/repos`, { + method: "POST", + headers: { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${token}`, + "X-GitHub-Api-Version": "2022-11-28", + }, + body: JSON.stringify({ + name: repoName, + private: true, + auto_init: true, + }), + }); if (!response.ok) { // 422 means repo already exists — fetch existing URL diff --git a/src/github/getExistingGithubRepo.ts b/src/github/getExistingGithubRepo.ts index 2c9d060..1b26a25 100644 --- a/src/github/getExistingGithubRepo.ts +++ b/src/github/getExistingGithubRepo.ts @@ -8,9 +8,7 @@ const GITHUB_ORG = "recoupable"; * @param repoName - The full repository name (e.g. "account-name-uuid") * @returns The repository HTML URL, or undefined if not found or on error */ -export async function getExistingGithubRepo( - repoName: string -): Promise { +export async function getExistingGithubRepo(repoName: string): Promise { const token = process.env.GITHUB_TOKEN; if (!token) { @@ -24,16 +22,13 @@ export async function getExistingGithubRepo( }); try { - const response = await fetch( - `https://api.github.com/repos/${GITHUB_ORG}/${repoName}`, - { - headers: { - Accept: "application/vnd.github+json", - Authorization: `Bearer ${token}`, - "X-GitHub-Api-Version": "2022-11-28", - }, - } - ); + const response = await fetch(`https://api.github.com/repos/${GITHUB_ORG}/${repoName}`, { + headers: { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${token}`, + "X-GitHub-Api-Version": "2022-11-28", + }, + }); if (!response.ok) { logger.error("Failed to fetch existing GitHub repo", { diff --git a/src/polling/pollScraperResults.ts b/src/polling/pollScraperResults.ts index ed8af96..2a7f729 100644 --- a/src/polling/pollScraperResults.ts +++ b/src/polling/pollScraperResults.ts @@ -15,18 +15,16 @@ export type PollResult = ScrapeRun & { /** * Polls each scraper run in parallel until all are completed (SUCCEEDED or FAILED). * Returns an array of results for each run. + * + * @param runs */ -export async function pollScraperResults( - runs: ScrapeRun[] -): Promise { +export async function pollScraperResults(runs: ScrapeRun[]): Promise { const results: PollResult[] = []; - const pendingRuns = new Map( - runs.map((run) => [run.runId, run]) - ); + const pendingRuns = new Map(runs.map(run => [run.runId, run])); while (pendingRuns.size > 0) { // Poll all pending runs in parallel - const pollPromises = Array.from(pendingRuns.values()).map(async (run) => { + const pollPromises = Array.from(pendingRuns.values()).map(async run => { const result = await getScraperResults(run.runId); if (!result) { diff --git a/src/pulse/__tests__/executePulseInSandbox.test.ts b/src/pulse/__tests__/executePulseInSandbox.test.ts index 13bc95a..254c4c8 100644 --- a/src/pulse/__tests__/executePulseInSandbox.test.ts +++ b/src/pulse/__tests__/executePulseInSandbox.test.ts @@ -114,9 +114,9 @@ describe("executePulseInSandbox", () => { })); const mod = await import("../executePulseInSandbox"); - await expect( - mod.executePulseInSandbox({ accountId, prompt }) - ).rejects.toThrow("RECOUP_API_KEY not configured"); + await expect(mod.executePulseInSandbox({ accountId, prompt })).rejects.toThrow( + "RECOUP_API_KEY not configured", + ); process.env.RECOUP_API_KEY = original; }); diff --git a/src/pulse/executePulseInSandbox.ts b/src/pulse/executePulseInSandbox.ts index aedb49d..87a863b 100644 --- a/src/pulse/executePulseInSandbox.ts +++ b/src/pulse/executePulseInSandbox.ts @@ -21,6 +21,9 @@ const executePulseResponseSchema = z.object({ * * @param params.accountId - The account ID to execute the pulse for * @param params.prompt - The pulse prompt to execute + * @param root0 + * @param root0.accountId + * @param root0.prompt * @returns The sandbox ID and run ID on success, undefined on error */ export async function executePulseInSandbox({ diff --git a/src/recoup/__tests__/getAccountOrgs.test.ts b/src/recoup/__tests__/getAccountOrgs.test.ts index 5a3c163..8ebcedc 100644 --- a/src/recoup/__tests__/getAccountOrgs.test.ts +++ b/src/recoup/__tests__/getAccountOrgs.test.ts @@ -40,9 +40,7 @@ describe("getAccountOrgs", () => { { organizationId: "org-456", organizationName: "Another Org" }, ]); expect(mockFetch).toHaveBeenCalledOnce(); - expect(mockFetch.mock.calls[0][0]).toContain( - "/api/organizations?account_id=account-1" - ); + expect(mockFetch.mock.calls[0][0]).toContain("/api/organizations?account_id=account-1"); }); it("returns empty array when account has no orgs", async () => { diff --git a/src/recoup/createAccountSandbox.ts b/src/recoup/createAccountSandbox.ts index e9aabe4..8ab7ae5 100644 --- a/src/recoup/createAccountSandbox.ts +++ b/src/recoup/createAccountSandbox.ts @@ -19,7 +19,7 @@ const createSandboxResponseSchema = z.object({ * @returns The created sandbox ID, or undefined on error */ export async function createAccountSandbox( - accountId: string + accountId: string, ): Promise<{ sandboxId: string } | undefined> { const url = `${NEW_API_BASE_URL}/api/sandboxes`; diff --git a/src/recoup/createChat.ts b/src/recoup/createChat.ts index 53e2d2e..5d95cd3 100644 --- a/src/recoup/createChat.ts +++ b/src/recoup/createChat.ts @@ -23,9 +23,7 @@ type CreateChatResponse = { * @param params - Chat creation parameters * @returns Promise that resolves to the created chat, or undefined on error */ -export async function createChat( - params?: CreateChatParams -): Promise { +export async function createChat(params?: CreateChatParams): Promise { if (!RECOUP_API_KEY) { logger.error("Missing RECOUP_API_KEY environment variable"); return undefined; diff --git a/src/recoup/fetchActivePulses.ts b/src/recoup/fetchActivePulses.ts index f468ca8..0a5c088 100644 --- a/src/recoup/fetchActivePulses.ts +++ b/src/recoup/fetchActivePulses.ts @@ -60,7 +60,7 @@ export async function fetchActivePulses(): Promise { logger.log("Fetched active pulses", { count: pulses.length, - pulses: pulses.map((p) => ({ + pulses: pulses.map(p => ({ id: p.id, account_id: p.account_id, active: p.active, diff --git a/src/recoup/fetchTask.ts b/src/recoup/fetchTask.ts index 4799da5..8daab5e 100644 --- a/src/recoup/fetchTask.ts +++ b/src/recoup/fetchTask.ts @@ -16,7 +16,7 @@ const taskResponseSchema = z.object({ artist_account_id: z.string(), enabled: z.boolean().nullable(), model: z.string().nullish(), - }) + }), ), }); @@ -27,10 +27,10 @@ const taskResponseSchema = z.object({ * - Task not found * - Task is disabled * - API error occurs + * + * @param externalId */ -export async function fetchTask( - externalId?: string -): Promise { +export async function fetchTask(externalId?: string): Promise { if (!externalId) { logger.warn("No externalId provided, skipping task fetch"); return undefined; diff --git a/src/recoup/generateChat.ts b/src/recoup/generateChat.ts index 630bfb1..d9dc630 100644 --- a/src/recoup/generateChat.ts +++ b/src/recoup/generateChat.ts @@ -25,7 +25,7 @@ type ChatGenerateResponse = { * @returns Promise that resolves to the parsed response, or undefined on error */ export async function generateChat( - params: ChatGenerateParams + params: ChatGenerateParams, ): Promise { if (!RECOUP_API_KEY) { logger.error("Missing RECOUP_API_KEY environment variable"); diff --git a/src/recoup/getAccount.ts b/src/recoup/getAccount.ts index ba68bce..784fb5f 100644 --- a/src/recoup/getAccount.ts +++ b/src/recoup/getAccount.ts @@ -21,9 +21,7 @@ export interface AccountInfo { * @param accountId - The account ID to look up * @returns The account id and name, or undefined on error */ -export async function getAccount( - accountId: string -): Promise { +export async function getAccount(accountId: string): Promise { const url = `${NEW_API_BASE_URL}/api/accounts/${encodeURIComponent(accountId)}`; logger.log("Fetching account info", { accountId, url }); diff --git a/src/recoup/getAccountOrgs.ts b/src/recoup/getAccountOrgs.ts index 8937ec6..bf60a7d 100644 --- a/src/recoup/getAccountOrgs.ts +++ b/src/recoup/getAccountOrgs.ts @@ -23,9 +23,7 @@ export interface OrgInfo { * @param accountId - The account ID to look up orgs for * @returns Array of org id/name pairs, or undefined on error */ -export async function getAccountOrgs( - accountId: string -): Promise { +export async function getAccountOrgs(accountId: string): Promise { const url = `${NEW_API_BASE_URL}/api/organizations?account_id=${encodeURIComponent(accountId)}`; logger.log("Fetching account organizations", { accountId, url }); @@ -59,8 +57,8 @@ export async function getAccountOrgs( } const orgs = validation.data.organizations - .filter((org) => org.organization_id && org.organization_name) - .map((org) => ({ + .filter(org => org.organization_id && org.organization_name) + .map(org => ({ organizationId: org.organization_id, organizationName: org.organization_name!, })); diff --git a/src/recoup/getAccountSandboxes.ts b/src/recoup/getAccountSandboxes.ts index 5b01af2..425aa73 100644 --- a/src/recoup/getAccountSandboxes.ts +++ b/src/recoup/getAccountSandboxes.ts @@ -20,11 +20,9 @@ export interface AccountSandboxesInfo { * @returns The snapshot ID and github repo URL, or undefined on error */ export async function getAccountSandboxes( - accountId: string + accountId: string, ): Promise { - const url = `${NEW_API_BASE_URL}/api/sandboxes?account_id=${encodeURIComponent( - accountId - )}`; + const url = `${NEW_API_BASE_URL}/api/sandboxes?account_id=${encodeURIComponent(accountId)}`; logger.log("Fetching account sandboxes", { accountId, url }); diff --git a/src/recoup/getArtistSocials.ts b/src/recoup/getArtistSocials.ts index f055c17..1851841 100644 --- a/src/recoup/getArtistSocials.ts +++ b/src/recoup/getArtistSocials.ts @@ -15,7 +15,7 @@ const artistSocialsResponseSchema = z.object({ following_count: z.number().nullable(), region: z.string().nullable(), updated_at: z.string(), - }) + }), ), pagination: z .object({ @@ -27,14 +27,16 @@ const artistSocialsResponseSchema = z.object({ .optional(), }); -export type ArtistSocialProfile = z.infer< - typeof artistSocialsResponseSchema ->["socials"][number]; +export type ArtistSocialProfile = z.infer["socials"][number]; const ARTIST_SOCIALS_API_URL = "https://api.recoupable.com/api/artist/socials"; +/** + * + * @param artistAccountId + */ export async function getArtistSocials( - artistAccountId: string + artistAccountId: string, ): Promise { if (!artistAccountId) { logger.error("getArtistSocials called without artistAccountId"); diff --git a/src/recoup/getProArtists.ts b/src/recoup/getProArtists.ts index 13a361d..51d13fa 100644 --- a/src/recoup/getProArtists.ts +++ b/src/recoup/getProArtists.ts @@ -8,6 +8,9 @@ const proArtistsResponseSchema = z.object({ const PRO_ARTISTS_API_URL = "https://api.recoupable.com/api/artists/pro"; +/** + * + */ export async function getProArtists(): Promise { try { const response = await fetch(PRO_ARTISTS_API_URL, { diff --git a/src/recoup/getScraperResults.ts b/src/recoup/getScraperResults.ts index 7f8eba6..f9c875e 100644 --- a/src/recoup/getScraperResults.ts +++ b/src/recoup/getScraperResults.ts @@ -21,10 +21,10 @@ const APIFY_SCRAPER_API_URL = "https://api.recoupable.com/api/apify/scraper"; /** * Checks the status and retrieves results from an Apify scraper run. * Returns the response with status and data (if completed). + * + * @param runId */ -export async function getScraperResults( - runId: string -): Promise { +export async function getScraperResults(runId: string): Promise { if (!runId) { logger.error("getScraperResults called without runId"); return undefined; @@ -80,4 +80,3 @@ export async function getScraperResults( return undefined; } } - diff --git a/src/recoup/scrapeSocial.ts b/src/recoup/scrapeSocial.ts index e1670af..7d85e42 100644 --- a/src/recoup/scrapeSocial.ts +++ b/src/recoup/scrapeSocial.ts @@ -14,10 +14,10 @@ const SOCIAL_SCRAPE_API_URL = "https://api.recoupable.com/api/social/scrape"; /** * Triggers a social profile scraping job for a given social_id. * Returns Apify run metadata that can be used to poll for status and retrieve results. + * + * @param socialId */ -export async function scrapeSocial( - socialId: string -): Promise { +export async function scrapeSocial(socialId: string): Promise { if (!socialId) { logger.error("scrapeSocial called without socialId"); return undefined; diff --git a/src/recoup/updateAccountSnapshot.ts b/src/recoup/updateAccountSnapshot.ts index 8be278a..2dd1b54 100644 --- a/src/recoup/updateAccountSnapshot.ts +++ b/src/recoup/updateAccountSnapshot.ts @@ -12,7 +12,7 @@ import { NEW_API_BASE_URL, RECOUP_API_KEY } from "../consts"; export async function updateAccountSnapshot( accountId: string, snapshotId?: string, - githubRepo?: string + githubRepo?: string, ): Promise<{ success: boolean; snapshotId: string } | undefined> { const url = `${NEW_API_BASE_URL}/api/sandboxes`; diff --git a/src/sandboxes/__tests__/addOrgSubmodules.test.ts b/src/sandboxes/__tests__/addOrgSubmodules.test.ts index 969e768..d63c32f 100644 --- a/src/sandboxes/__tests__/addOrgSubmodules.test.ts +++ b/src/sandboxes/__tests__/addOrgSubmodules.test.ts @@ -10,6 +10,9 @@ vi.mock("../getSandboxHomeDir", () => ({ const { addOrgSubmodules } = await import("../git/addOrgSubmodules"); +/** + * + */ function createMockSandbox() { const runCommand = vi.fn().mockResolvedValue({ exitCode: 0, @@ -51,9 +54,7 @@ describe("addOrgSubmodules", () => { await addOrgSubmodules(sandbox); const submoduleCalls = sandbox.runCommand.mock.calls.filter( - (call: any[]) => - call[0]?.cmd === "git" && - call[0]?.args?.[0] === "submodule" + (call: any[]) => call[0]?.cmd === "git" && call[0]?.args?.[0] === "submodule", ); expect(submoduleCalls).toHaveLength(0); }); @@ -86,10 +87,7 @@ describe("addOrgSubmodules", () => { }; } } - if ( - opts.cmd === "sh" && - opts.args?.[1]?.includes("git config --file .gitmodules") - ) { + if (opts.cmd === "sh" && opts.args?.[1]?.includes("git config --file .gitmodules")) { return { exitCode: 1, stdout: async () => "", stderr: async () => "" }; } return { exitCode: 0, stdout: async () => "", stderr: async () => "" }; @@ -101,7 +99,7 @@ describe("addOrgSubmodules", () => { (call: any[]) => call[0]?.cmd === "git" && call[0]?.args?.[0] === "submodule" && - call[0]?.args?.[1] === "add" + call[0]?.args?.[1] === "add", ); expect(submoduleCalls).toHaveLength(2); @@ -128,10 +126,7 @@ describe("addOrgSubmodules", () => { stderr: async () => "", }; } - if ( - opts.cmd === "sh" && - opts.args?.[1]?.includes("git config --file .gitmodules") - ) { + if (opts.cmd === "sh" && opts.args?.[1]?.includes("git config --file .gitmodules")) { return { exitCode: 0, stdout: async () => "", stderr: async () => "" }; } return { exitCode: 0, stdout: async () => "", stderr: async () => "" }; @@ -143,7 +138,7 @@ describe("addOrgSubmodules", () => { (call: any[]) => call[0]?.cmd === "git" && call[0]?.args?.[0] === "submodule" && - call[0]?.args?.[1] === "add" + call[0]?.args?.[1] === "add", ); expect(submoduleCalls).toHaveLength(0); }); @@ -166,10 +161,7 @@ describe("addOrgSubmodules", () => { stderr: async () => "", }; } - if ( - opts.cmd === "sh" && - opts.args?.[1]?.includes("git config --file .gitmodules") - ) { + if (opts.cmd === "sh" && opts.args?.[1]?.includes("git config --file .gitmodules")) { return { exitCode: 1, stdout: async () => "", stderr: async () => "" }; } return { exitCode: 0, stdout: async () => "", stderr: async () => "" }; @@ -181,7 +173,7 @@ describe("addOrgSubmodules", () => { (call: any[]) => call[0]?.cmd === "sh" && call[0]?.args?.[1]?.includes("git rm") && - call[0]?.args?.[1]?.includes("--cached") + call[0]?.args?.[1]?.includes("--cached"), ); expect(cleanupCall).toBeDefined(); }); diff --git a/src/sandboxes/__tests__/cloneMonorepoViaAgent.test.ts b/src/sandboxes/__tests__/cloneMonorepoViaAgent.test.ts index 8f338cf..f2971bc 100644 --- a/src/sandboxes/__tests__/cloneMonorepoViaAgent.test.ts +++ b/src/sandboxes/__tests__/cloneMonorepoViaAgent.test.ts @@ -51,5 +51,4 @@ describe("cloneMonorepoViaAgent", () => { const message = vi.mocked(runClaudeCodeAgent).mock.calls[0][1].message; expect(message).not.toContain("git config"); }); - }); diff --git a/src/sandboxes/__tests__/configureGitAuth.test.ts b/src/sandboxes/__tests__/configureGitAuth.test.ts index 33e92a5..ae53a39 100644 --- a/src/sandboxes/__tests__/configureGitAuth.test.ts +++ b/src/sandboxes/__tests__/configureGitAuth.test.ts @@ -6,6 +6,9 @@ vi.mock("@trigger.dev/sdk/v3", () => ({ const { configureGitAuth } = await import("../configureGitAuth"); +/** + * + */ function createMockSandbox() { const runCommand = vi.fn().mockResolvedValue({ exitCode: 0 }); return { runCommand } as any; @@ -61,9 +64,8 @@ describe("configureGitAuth", () => { await configureGitAuth(sandbox); - const insteadOfCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => - call[0]?.args?.some?.((a: string) => a.includes("insteadOf")) + const insteadOfCall = sandbox.runCommand.mock.calls.find((call: any[]) => + call[0]?.args?.some?.((a: string) => a.includes("insteadOf")), ); expect(insteadOfCall).toBeUndefined(); }); diff --git a/src/sandboxes/__tests__/copyOpenClawToRepo.test.ts b/src/sandboxes/__tests__/copyOpenClawToRepo.test.ts index e49264a..5a76367 100644 --- a/src/sandboxes/__tests__/copyOpenClawToRepo.test.ts +++ b/src/sandboxes/__tests__/copyOpenClawToRepo.test.ts @@ -6,6 +6,9 @@ vi.mock("@trigger.dev/sdk/v3", () => ({ const { copyOpenClawToRepo } = await import("../copyOpenClawToRepo"); +/** + * + */ function createMockSandbox() { const runCommand = vi.fn().mockResolvedValue({ exitCode: 0, @@ -29,7 +32,7 @@ describe("copyOpenClawToRepo", () => { (call: any[]) => call[0]?.cmd === "sh" && call[0]?.args?.[1]?.includes("cp -r") && - call[0]?.args?.[1]?.includes(".openclaw") + call[0]?.args?.[1]?.includes(".openclaw"), ); expect(copyCall).toBeDefined(); @@ -43,9 +46,7 @@ describe("copyOpenClawToRepo", () => { await copyOpenClawToRepo(sandbox); const copyCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => - call[0]?.cmd === "sh" && - call[0]?.args?.[1]?.includes("cp -r") + (call: any[]) => call[0]?.cmd === "sh" && call[0]?.args?.[1]?.includes("cp -r"), ); const cmd = copyCall![0].args[1]; expect(cmd).toContain("find /vercel/sandbox/.openclaw -name .git"); @@ -58,9 +59,7 @@ describe("copyOpenClawToRepo", () => { await copyOpenClawToRepo(sandbox); const submoduleCalls = sandbox.runCommand.mock.calls.filter( - (call: any[]) => - call[0]?.cmd === "git" && - call[0]?.args?.[0] === "submodule" + (call: any[]) => call[0]?.cmd === "git" && call[0]?.args?.[0] === "submodule", ); expect(submoduleCalls).toHaveLength(0); }); @@ -71,9 +70,7 @@ describe("copyOpenClawToRepo", () => { await copyOpenClawToRepo(sandbox); const sedCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => - call[0]?.cmd === "sh" && - call[0]?.args?.[1]?.includes("x-access-token") + (call: any[]) => call[0]?.cmd === "sh" && call[0]?.args?.[1]?.includes("x-access-token"), ); expect(sedCall).toBeUndefined(); }); diff --git a/src/sandboxes/__tests__/ensureOrgRepos.test.ts b/src/sandboxes/__tests__/ensureOrgRepos.test.ts index 645d261..386ead6 100644 --- a/src/sandboxes/__tests__/ensureOrgRepos.test.ts +++ b/src/sandboxes/__tests__/ensureOrgRepos.test.ts @@ -12,12 +12,14 @@ vi.mock("../../recoup/getAccountOrgs", () => ({ const mockCreateOrgGithubRepo = vi.fn(); vi.mock("../../github/createOrgGithubRepo", () => ({ - createOrgGithubRepo: (...args: unknown[]) => - mockCreateOrgGithubRepo(...args), + createOrgGithubRepo: (...args: unknown[]) => mockCreateOrgGithubRepo(...args), })); const { ensureOrgRepos } = await import("../ensureOrgRepos"); +/** + * + */ function createMockSandbox() { const runCommand = vi.fn().mockImplementation((opts: any) => { const finished = { @@ -91,7 +93,7 @@ describe("ensureOrgRepos", () => { ]); mockCreateOrgGithubRepo.mockResolvedValueOnce( - "https://github.com/recoupable/org-test-org-org-1" + "https://github.com/recoupable/org-test-org-org-1", ); const sandbox = createMockSandbox(); @@ -100,15 +102,13 @@ describe("ensureOrgRepos", () => { // Should have called openclaw agent with a message about cloning const openclawCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => call[0]?.cmd === "openclaw" + (call: any[]) => call[0]?.cmd === "openclaw", ); expect(openclawCall).toBeDefined(); // The openclaw args should include the repo URL const args = openclawCall![0].args; - const message = args.find( - (a: string, i: number) => args[i - 1] === "--message" - ); + const message = args.find((a: string, i: number) => args[i - 1] === "--message"); expect(message).toContain("org-test-org-org-1"); // GITHUB_TOKEN is injected into openclaw.json by setupOpenClaw, @@ -123,7 +123,7 @@ describe("ensureOrgRepos", () => { ]); mockCreateOrgGithubRepo.mockResolvedValueOnce( - "https://github.com/recoupable/org-test-org-org-1" + "https://github.com/recoupable/org-test-org-org-1", ); const sandbox = createMockSandbox(); @@ -131,9 +131,7 @@ describe("ensureOrgRepos", () => { await ensureOrgRepos(sandbox, "account-1"); const submoduleCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => - call[0]?.cmd === "git" && - call[0]?.args?.[0] === "submodule" + (call: any[]) => call[0]?.cmd === "git" && call[0]?.args?.[0] === "submodule", ); expect(submoduleCall).toBeUndefined(); }); @@ -146,9 +144,7 @@ describe("ensureOrgRepos", () => { mockCreateOrgGithubRepo .mockResolvedValueOnce(undefined) - .mockResolvedValueOnce( - "https://github.com/recoupable/org-working-org-org-2" - ); + .mockResolvedValueOnce("https://github.com/recoupable/org-working-org-org-2"); const sandbox = createMockSandbox(); @@ -158,7 +154,7 @@ describe("ensureOrgRepos", () => { // Openclaw should still be called with the one that succeeded const openclawCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => call[0]?.cmd === "openclaw" + (call: any[]) => call[0]?.cmd === "openclaw", ); expect(openclawCall).toBeDefined(); }); @@ -175,7 +171,7 @@ describe("ensureOrgRepos", () => { ]); mockCreateOrgGithubRepo.mockResolvedValueOnce( - "https://github.com/recoupable/org-test-org-org-1" + "https://github.com/recoupable/org-test-org-org-1", ); const sandbox = createMockSandbox(); @@ -183,12 +179,10 @@ describe("ensureOrgRepos", () => { await ensureOrgRepos(sandbox, "account-1"); const openclawCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => call[0]?.cmd === "openclaw" + (call: any[]) => call[0]?.cmd === "openclaw", ); const args = openclawCall![0].args; - const message = args.find( - (a: string, i: number) => args[i - 1] === "--message" - ); + const message = args.find((a: string, i: number) => args[i - 1] === "--message"); // Message must handle .git as a file (submodule gitlink), not just directory expect(message).toContain(".git file"); @@ -206,7 +200,7 @@ describe("ensureOrgRepos", () => { await ensureOrgRepos(sandbox, "account-1"); const openclawCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => call[0]?.cmd === "openclaw" + (call: any[]) => call[0]?.cmd === "openclaw", ); expect(openclawCall).toBeUndefined(); }); diff --git a/src/sandboxes/__tests__/getGitHubAuthPrefix.test.ts b/src/sandboxes/__tests__/getGitHubAuthPrefix.test.ts index 16fdbed..8b77c53 100644 --- a/src/sandboxes/__tests__/getGitHubAuthPrefix.test.ts +++ b/src/sandboxes/__tests__/getGitHubAuthPrefix.test.ts @@ -10,9 +10,7 @@ describe("getGitHubAuthPrefix", () => { it("returns the auth URL prefix when GITHUB_TOKEN is set", () => { const result = getGitHubAuthPrefix(); - expect(result).toBe( - "https://x-access-token:ghp_test123@github.com/", - ); + expect(result).toBe("https://x-access-token:ghp_test123@github.com/"); }); it("returns null when GITHUB_TOKEN is missing", () => { diff --git a/src/sandboxes/__tests__/getOrCreateSandbox.test.ts b/src/sandboxes/__tests__/getOrCreateSandbox.test.ts index eabcd2a..b76550b 100644 --- a/src/sandboxes/__tests__/getOrCreateSandbox.test.ts +++ b/src/sandboxes/__tests__/getOrCreateSandbox.test.ts @@ -15,9 +15,7 @@ vi.mock("@vercel/sandbox", () => ({ })); vi.mock("../../recoup/createAccountSandbox", () => ({ - createAccountSandbox: vi - .fn() - .mockResolvedValue({ sandboxId: "sbx_123" }), + createAccountSandbox: vi.fn().mockResolvedValue({ sandboxId: "sbx_123" }), })); vi.mock("../getVercelSandboxCredentials", () => ({ @@ -34,9 +32,7 @@ vi.mock("../logStep", () => ({ const { getOrCreateSandbox } = await import("../getOrCreateSandbox"); const { Sandbox } = await import("@vercel/sandbox"); -const { createAccountSandbox } = await import( - "../../recoup/createAccountSandbox" -); +const { createAccountSandbox } = await import("../../recoup/createAccountSandbox"); beforeEach(() => { vi.clearAllMocks(); @@ -52,18 +48,14 @@ describe("getOrCreateSandbox", () => { it("connects to sandbox via Sandbox.get with returned sandboxId", async () => { await getOrCreateSandbox("acc_1"); - expect(Sandbox.get).toHaveBeenCalledWith( - expect.objectContaining({ sandboxId: "sbx_123" }), - ); + expect(Sandbox.get).toHaveBeenCalledWith(expect.objectContaining({ sandboxId: "sbx_123" })); }); it("returns sandboxId and sandbox instance", async () => { const result = await getOrCreateSandbox("acc_1"); expect(result.sandboxId).toBe("sbx_123"); - expect(result.sandbox).toEqual( - expect.objectContaining({ sandboxId: "sbx_123" }), - ); + expect(result.sandbox).toEqual(expect.objectContaining({ sandboxId: "sbx_123" })); }); it("throws when createAccountSandbox returns undefined", async () => { diff --git a/src/sandboxes/__tests__/getSandboxHomeDir.test.ts b/src/sandboxes/__tests__/getSandboxHomeDir.test.ts index f5dcbc0..683f007 100644 --- a/src/sandboxes/__tests__/getSandboxHomeDir.test.ts +++ b/src/sandboxes/__tests__/getSandboxHomeDir.test.ts @@ -1,6 +1,10 @@ import { describe, it, expect, vi } from "vitest"; import { getSandboxHomeDir } from "../getSandboxHomeDir"; +/** + * + * @param stdout + */ function createMockSandbox(stdout = "/home/user") { return { runCommand: vi.fn().mockResolvedValue({ diff --git a/src/sandboxes/__tests__/notifyCodingAgentCallback.test.ts b/src/sandboxes/__tests__/notifyCodingAgentCallback.test.ts index aff093c..59d9373 100644 --- a/src/sandboxes/__tests__/notifyCodingAgentCallback.test.ts +++ b/src/sandboxes/__tests__/notifyCodingAgentCallback.test.ts @@ -52,9 +52,7 @@ describe("notifyCodingAgentCallback", () => { status: "no_changes", }); - expect(logger.error).toHaveBeenCalledWith( - expect.stringContaining("CODING_AGENT_CALLBACK_URL"), - ); + expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("CODING_AGENT_CALLBACK_URL")); expect(mockFetch).not.toHaveBeenCalled(); }); }); diff --git a/src/sandboxes/__tests__/pushOrgRepos.test.ts b/src/sandboxes/__tests__/pushOrgRepos.test.ts index 54231f4..b776c10 100644 --- a/src/sandboxes/__tests__/pushOrgRepos.test.ts +++ b/src/sandboxes/__tests__/pushOrgRepos.test.ts @@ -16,6 +16,9 @@ vi.mock("../logStep", () => ({ const { pushOrgRepos } = await import("../git/pushOrgRepos"); const { logStep } = await import("../logStep"); +/** + * + */ function createMockSandbox() { const runCommand = vi.fn(); return { runCommand } as any; @@ -58,7 +61,7 @@ describe("pushOrgRepos", () => { // Should not call openclaw const openclawCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => call[0]?.cmd === "openclaw" + (call: any[]) => call[0]?.cmd === "openclaw", ); expect(openclawCall).toBeUndefined(); }); @@ -89,7 +92,7 @@ describe("pushOrgRepos", () => { call[0]?.args?.[0] === "agent" && call[0]?.args?.[1] === "--agent" && call[0]?.args?.[2] === "main" && - call[0]?.args?.[3] === "--message" + call[0]?.args?.[3] === "--message", ); expect(openclawCall).toBeDefined(); @@ -124,7 +127,7 @@ describe("pushOrgRepos", () => { await pushOrgRepos(sandbox); const openclawCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => call[0]?.cmd === "openclaw" + (call: any[]) => call[0]?.cmd === "openclaw", ); const message = openclawCall![0].args[4]; @@ -163,8 +166,7 @@ describe("pushOrgRepos", () => { // The find command should use resolved path, not ~ const findCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => - call[0]?.cmd === "sh" && call[0]?.args?.[1]?.includes("find") + (call: any[]) => call[0]?.cmd === "sh" && call[0]?.args?.[1]?.includes("find"), ); expect(findCall![0].args[1]).toContain("/home/sandbox/"); expect(findCall![0].args[1]).not.toContain("~"); @@ -201,7 +203,7 @@ describe("pushOrgRepos", () => { expect(logStep).toHaveBeenCalledWith( expect.stringContaining("failed"), false, - expect.objectContaining({ stderr: expect.any(String) }) + expect.objectContaining({ stderr: expect.any(String) }), ); }); }); diff --git a/src/sandboxes/__tests__/runClaudeCodeAgent.test.ts b/src/sandboxes/__tests__/runClaudeCodeAgent.test.ts index 60fe953..e447a1a 100644 --- a/src/sandboxes/__tests__/runClaudeCodeAgent.test.ts +++ b/src/sandboxes/__tests__/runClaudeCodeAgent.test.ts @@ -14,6 +14,13 @@ const { logStep } = await import("../logStep"); const originalEnv = { ...process.env }; +/** + * + * @param finished + * @param finished.exitCode + * @param finished.stdout + * @param finished.stderr + */ function mockDetachedCommand(finished: { exitCode: number; stdout: () => Promise; @@ -22,6 +29,9 @@ function mockDetachedCommand(finished: { return { wait: vi.fn().mockResolvedValue(finished) }; } +/** + * + */ function createMockSandbox() { return { runCommand: vi.fn() } as any; } @@ -97,9 +107,7 @@ describe("runClaudeCodeAgent", () => { await runClaudeCodeAgent(sandbox, { label: "Test", message: "Do something" }); - expect(sandbox.runCommand).toHaveBeenCalledWith( - expect.objectContaining({ detached: true }), - ); + expect(sandbox.runCommand).toHaveBeenCalledWith(expect.objectContaining({ detached: true })); expect(waitMock).toHaveBeenCalled(); }); diff --git a/src/sandboxes/__tests__/runOpenClawAgent.test.ts b/src/sandboxes/__tests__/runOpenClawAgent.test.ts index 90ffb2a..8ba77a5 100644 --- a/src/sandboxes/__tests__/runOpenClawAgent.test.ts +++ b/src/sandboxes/__tests__/runOpenClawAgent.test.ts @@ -12,6 +12,13 @@ vi.mock("../logStep", () => ({ const { runOpenClawAgent } = await import("../runOpenClawAgent"); const { logStep } = await import("../logStep"); +/** + * + * @param finished + * @param finished.exitCode + * @param finished.stdout + * @param finished.stderr + */ function mockDetachedCommand(finished: { exitCode: number; stdout: () => Promise; @@ -20,6 +27,9 @@ function mockDetachedCommand(finished: { return { wait: vi.fn().mockResolvedValue(finished) }; } +/** + * + */ function createMockSandbox() { const runCommand = vi.fn(); return { runCommand } as any; @@ -153,9 +163,7 @@ describe("runOpenClawAgent", () => { message: "Make changes", }); - expect(sandbox.runCommand).toHaveBeenCalledWith( - expect.objectContaining({ detached: true }), - ); + expect(sandbox.runCommand).toHaveBeenCalledWith(expect.objectContaining({ detached: true })); expect(waitMock).toHaveBeenCalled(); }); @@ -178,5 +186,4 @@ describe("runOpenClawAgent", () => { expect(result.stdout).toBe("output\n"); expect(result.stderr).toBe("warn\n"); }); - }); diff --git a/src/sandboxes/__tests__/setupOpenClaw.test.ts b/src/sandboxes/__tests__/setupOpenClaw.test.ts index acea0be..74ddd42 100644 --- a/src/sandboxes/__tests__/setupOpenClaw.test.ts +++ b/src/sandboxes/__tests__/setupOpenClaw.test.ts @@ -10,6 +10,9 @@ vi.mock("../onboardOpenClaw", () => ({ const { setupOpenClaw } = await import("../setupOpenClaw"); +/** + * + */ function createMockSandbox() { const runCommand = vi.fn().mockResolvedValue({ exitCode: 0, @@ -31,9 +34,7 @@ describe("setupOpenClaw", () => { delete process.env.RECOUP_API_KEY; const sandbox = createMockSandbox(); - await expect(setupOpenClaw(sandbox, "account-1")).rejects.toThrow( - "Missing RECOUP_API_KEY" - ); + await expect(setupOpenClaw(sandbox, "account-1")).rejects.toThrow("Missing RECOUP_API_KEY"); }); it("injects RECOUP_API_KEY and RECOUP_ACCOUNT_ID into openclaw.json", async () => { @@ -42,12 +43,10 @@ describe("setupOpenClaw", () => { await setupOpenClaw(sandbox, "account-1"); // Find the node -e call that injects env vars - const injectCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => { - const args = call[0]?.args; - return args?.[0] === "-c" && args?.[1]?.includes("openclaw.json"); - } - ); + const injectCall = sandbox.runCommand.mock.calls.find((call: any[]) => { + const args = call[0]?.args; + return args?.[0] === "-c" && args?.[1]?.includes("openclaw.json"); + }); expect(injectCall).toBeDefined(); const script = injectCall![0].args[1]; @@ -71,12 +70,10 @@ describe("setupOpenClaw", () => { await setupOpenClaw(sandbox, "account-1"); // Find the node -e call that injects env vars - const injectCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => { - const args = call[0]?.args; - return args?.[0] === "-c" && args?.[1]?.includes("openclaw.json"); - } - ); + const injectCall = sandbox.runCommand.mock.calls.find((call: any[]) => { + const args = call[0]?.args; + return args?.[0] === "-c" && args?.[1]?.includes("openclaw.json"); + }); expect(injectCall).toBeDefined(); const script = injectCall![0].args[1]; @@ -92,12 +89,10 @@ describe("setupOpenClaw", () => { await setupOpenClaw(sandbox, "account-1"); // Env injection call should still run (for RECOUP vars) - const injectCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => { - const args = call[0]?.args; - return args?.[0] === "-c" && args?.[1]?.includes("openclaw.json"); - } - ); + const injectCall = sandbox.runCommand.mock.calls.find((call: any[]) => { + const args = call[0]?.args; + return args?.[0] === "-c" && args?.[1]?.includes("openclaw.json"); + }); expect(injectCall).toBeDefined(); }); @@ -106,12 +101,10 @@ describe("setupOpenClaw", () => { await setupOpenClaw(sandbox, "account-1"); - const injectCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => { - const args = call[0]?.args; - return args?.[0] === "-c" && args?.[1]?.includes("openclaw.json"); - } - ); + const injectCall = sandbox.runCommand.mock.calls.find((call: any[]) => { + const args = call[0]?.args; + return args?.[0] === "-c" && args?.[1]?.includes("openclaw.json"); + }); expect(injectCall).toBeDefined(); const script = injectCall![0].args[1]; @@ -125,12 +118,10 @@ describe("setupOpenClaw", () => { await setupOpenClaw(sandbox, "account-1"); // Find the gateway start call - const gatewayCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => { - const args = call[0]?.args; - return args?.[1]?.includes("gateway"); - } - ); + const gatewayCall = sandbox.runCommand.mock.calls.find((call: any[]) => { + const args = call[0]?.args; + return args?.[1]?.includes("gateway"); + }); expect(gatewayCall).toBeDefined(); }); @@ -140,12 +131,10 @@ describe("setupOpenClaw", () => { await setupOpenClaw(sandbox, "account-1"); - const injectCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => { - const args = call[0]?.args; - return args?.[0] === "-c" && args?.[1]?.includes("openclaw.json"); - } - ); + const injectCall = sandbox.runCommand.mock.calls.find((call: any[]) => { + const args = call[0]?.args; + return args?.[0] === "-c" && args?.[1]?.includes("openclaw.json"); + }); expect(injectCall).toBeDefined(); const script = injectCall![0].args[1]; @@ -161,15 +150,12 @@ describe("setupOpenClaw", () => { // Make the second runCommand call (env injection) fail // First call is onboardOpenClaw (mocked), so first runCommand is env injection - sandbox.runCommand - .mockResolvedValueOnce({ - exitCode: 1, - stdout: async () => "", - stderr: async () => "node script failed", - }); - - await expect(setupOpenClaw(sandbox, "account-1")).rejects.toThrow( - "Failed to inject env vars" - ); + sandbox.runCommand.mockResolvedValueOnce({ + exitCode: 1, + stdout: async () => "", + stderr: async () => "node script failed", + }); + + await expect(setupOpenClaw(sandbox, "account-1")).rejects.toThrow("Failed to inject env vars"); }); }); diff --git a/src/sandboxes/__tests__/snapshotAndPersist.test.ts b/src/sandboxes/__tests__/snapshotAndPersist.test.ts index 8876697..74bf0e9 100644 --- a/src/sandboxes/__tests__/snapshotAndPersist.test.ts +++ b/src/sandboxes/__tests__/snapshotAndPersist.test.ts @@ -12,6 +12,9 @@ const { snapshotAndPersist } = await import("../snapshotAndPersist"); const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000; +/** + * + */ function createMockSandbox() { return { snapshot: vi.fn().mockResolvedValue({ diff --git a/src/sandboxes/__tests__/stripGitmodulesTokens.test.ts b/src/sandboxes/__tests__/stripGitmodulesTokens.test.ts index 4ba7512..c234490 100644 --- a/src/sandboxes/__tests__/stripGitmodulesTokens.test.ts +++ b/src/sandboxes/__tests__/stripGitmodulesTokens.test.ts @@ -6,6 +6,9 @@ vi.mock("@trigger.dev/sdk/v3", () => ({ const { stripGitmodulesTokens } = await import("../git/stripGitmodulesTokens"); +/** + * + */ function createMockSandbox() { const runCommand = vi.fn().mockResolvedValue({ exitCode: 0, @@ -29,7 +32,7 @@ describe("stripGitmodulesTokens", () => { (call: any[]) => call[0]?.cmd === "sh" && call[0]?.args?.[1]?.includes("x-access-token") && - call[0]?.args?.[1]?.includes(".gitmodules") + call[0]?.args?.[1]?.includes(".gitmodules"), ); expect(sedCall).toBeDefined(); @@ -43,9 +46,7 @@ describe("stripGitmodulesTokens", () => { await stripGitmodulesTokens(sandbox); const sedCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => - call[0]?.cmd === "sh" && - call[0]?.args?.[1]?.includes(".gitmodules") + (call: any[]) => call[0]?.cmd === "sh" && call[0]?.args?.[1]?.includes(".gitmodules"), ); const cmd = sedCall![0].args[1]; expect(cmd).toContain("git add .gitmodules"); diff --git a/src/sandboxes/__tests__/syncOrgRepos.test.ts b/src/sandboxes/__tests__/syncOrgRepos.test.ts index 1dc82d5..89f53ec 100644 --- a/src/sandboxes/__tests__/syncOrgRepos.test.ts +++ b/src/sandboxes/__tests__/syncOrgRepos.test.ts @@ -7,6 +7,9 @@ vi.mock("@trigger.dev/sdk/v3", () => ({ const { syncOrgRepos } = await import("../git/syncOrgRepos"); +/** + * + */ function createMockSandbox() { const runCommand = vi.fn().mockResolvedValue({ wait: vi.fn().mockResolvedValue({ @@ -43,14 +46,12 @@ describe("syncOrgRepos", () => { await syncOrgRepos(sandbox); const openclawCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => call[0]?.cmd === "openclaw" + (call: any[]) => call[0]?.cmd === "openclaw", ); expect(openclawCall).toBeDefined(); const args = openclawCall![0].args; - const message = args.find( - (a: string, i: number) => args[i - 1] === "--message" - ); + const message = args.find((a: string, i: number) => args[i - 1] === "--message"); expect(message).toContain("git fetch origin main"); expect(message).toContain("git reset --hard origin/main"); }); @@ -61,12 +62,10 @@ describe("syncOrgRepos", () => { await syncOrgRepos(sandbox); const openclawCall = sandbox.runCommand.mock.calls.find( - (call: any[]) => call[0]?.cmd === "openclaw" + (call: any[]) => call[0]?.cmd === "openclaw", ); const args = openclawCall![0].args; - const message = args.find( - (a: string, i: number) => args[i - 1] === "--message" - ); + const message = args.find((a: string, i: number) => args[i - 1] === "--message"); expect(message).toContain(".git file"); }); }); diff --git a/src/sandboxes/cloneMonorepoViaAgent.ts b/src/sandboxes/cloneMonorepoViaAgent.ts index f24b31a..5134d64 100644 --- a/src/sandboxes/cloneMonorepoViaAgent.ts +++ b/src/sandboxes/cloneMonorepoViaAgent.ts @@ -8,9 +8,7 @@ import { runClaudeCodeAgent } from "./runClaudeCodeAgent"; * * @param sandbox - The Vercel Sandbox instance */ -export async function cloneMonorepoViaAgent( - sandbox: Sandbox, -): Promise { +export async function cloneMonorepoViaAgent(sandbox: Sandbox): Promise { await runClaudeCodeAgent(sandbox, { label: "Clone monorepo via agent", message: [ diff --git a/src/sandboxes/configureGitAuth.ts b/src/sandboxes/configureGitAuth.ts index abf9ebe..aac63fb 100644 --- a/src/sandboxes/configureGitAuth.ts +++ b/src/sandboxes/configureGitAuth.ts @@ -7,6 +7,8 @@ import { getGitHubAuthPrefix } from "./getGitHubAuthPrefix"; * * Sets url.insteadOf so all HTTPS GitHub URLs automatically use the token, * and configures the Recoup Agent git identity. + * + * @param sandbox */ export async function configureGitAuth(sandbox: Sandbox): Promise { const authPrefix = getGitHubAuthPrefix(); @@ -14,12 +16,7 @@ export async function configureGitAuth(sandbox: Sandbox): Promise { if (authPrefix) { await sandbox.runCommand({ cmd: "git", - args: [ - "config", - "--global", - `url.${authPrefix}.insteadOf`, - "https://github.com/", - ], + args: ["config", "--global", `url.${authPrefix}.insteadOf`, "https://github.com/"], }); logger.log("Git auth configured with GITHUB_TOKEN"); } else { diff --git a/src/sandboxes/ensureGithubRepo.ts b/src/sandboxes/ensureGithubRepo.ts index 73b9f27..b3a7564 100644 --- a/src/sandboxes/ensureGithubRepo.ts +++ b/src/sandboxes/ensureGithubRepo.ts @@ -21,7 +21,7 @@ import { getGitHubAuthPrefix } from "./getGitHubAuthPrefix"; */ export async function ensureGithubRepo( sandbox: Sandbox, - accountId: string + accountId: string, ): Promise { const authPrefix = getGitHubAuthPrefix(); @@ -80,13 +80,7 @@ export async function ensureGithubRepo( return undefined; } - if ( - !(await runGitCommand( - sandbox, - ["remote", "add", "origin", repoUrl], - "add remote" - )) - ) { + if (!(await runGitCommand(sandbox, ["remote", "add", "origin", repoUrl], "add remote"))) { return undefined; } @@ -108,7 +102,7 @@ export async function ensureGithubRepo( !(await runGitCommand( sandbox, ["checkout", "-B", "main", "origin/main"], - "checkout main branch" + "checkout main branch", )) ) { return undefined; @@ -117,11 +111,7 @@ export async function ensureGithubRepo( // Set up URL rewriting so submodule clones use auth await sandbox.runCommand({ cmd: "git", - args: [ - "config", - `url.${authPrefix}.insteadOf`, - "https://github.com/", - ], + args: ["config", `url.${authPrefix}.insteadOf`, "https://github.com/"], }); // Initialize submodules if they exist (org repos) diff --git a/src/sandboxes/ensureOrgRepos.ts b/src/sandboxes/ensureOrgRepos.ts index 512c4c7..288e926 100644 --- a/src/sandboxes/ensureOrgRepos.ts +++ b/src/sandboxes/ensureOrgRepos.ts @@ -16,10 +16,7 @@ import { logStep } from "./logStep"; * @param sandbox - The Vercel Sandbox instance * @param accountId - The account ID to look up orgs for */ -export async function ensureOrgRepos( - sandbox: Sandbox, - accountId: string -): Promise { +export async function ensureOrgRepos(sandbox: Sandbox, accountId: string): Promise { const githubToken = process.env.GITHUB_TOKEN; if (!githubToken) { @@ -41,10 +38,7 @@ export async function ensureOrgRepos( const orgRepos: Array<{ name: string; url: string }> = []; for (const org of orgs) { - const repoUrl = await createOrgGithubRepo( - org.organizationName, - org.organizationId - ); + const repoUrl = await createOrgGithubRepo(org.organizationName, org.organizationId); if (!repoUrl) { logger.error("Failed to create org GitHub repo, skipping", { @@ -66,9 +60,7 @@ export async function ensureOrgRepos( } // Build the prompt for OpenClaw to clone the repos - const repoList = orgRepos - .map((r) => `- "${r.name}" → ${r.url}`) - .join("\n"); + const repoList = orgRepos.map(r => `- "${r.name}" → ${r.url}`).join("\n"); const message = [ "Clone the following GitHub repositories into orgs/ in your workspace.", diff --git a/src/sandboxes/ensureSetupSandbox.ts b/src/sandboxes/ensureSetupSandbox.ts index f0ad567..b24a75f 100644 --- a/src/sandboxes/ensureSetupSandbox.ts +++ b/src/sandboxes/ensureSetupSandbox.ts @@ -11,10 +11,7 @@ import { logStep } from "./logStep"; * @param sandbox - The Vercel Sandbox instance * @param accountId - The account ID for the sandbox owner */ -export async function ensureSetupSandbox( - sandbox: Sandbox, - accountId: string -): Promise { +export async function ensureSetupSandbox(sandbox: Sandbox, accountId: string): Promise { logStep("Installing skills"); await installSkill(sandbox, "recoupable/setup-sandbox"); diff --git a/src/sandboxes/getOrCreateSandbox.ts b/src/sandboxes/getOrCreateSandbox.ts index 8eb716b..43b4ccb 100644 --- a/src/sandboxes/getOrCreateSandbox.ts +++ b/src/sandboxes/getOrCreateSandbox.ts @@ -15,9 +15,7 @@ interface GetOrCreateSandboxResult { * @param accountId - The account ID to create a sandbox for * @returns The sandbox ID and connected Sandbox instance */ -export async function getOrCreateSandbox( - accountId: string, -): Promise { +export async function getOrCreateSandbox(accountId: string): Promise { const { token, teamId, projectId } = getVercelSandboxCredentials(); logStep("Creating sandbox via API"); diff --git a/src/sandboxes/getSandboxEnv.ts b/src/sandboxes/getSandboxEnv.ts index 3906254..421a527 100644 --- a/src/sandboxes/getSandboxEnv.ts +++ b/src/sandboxes/getSandboxEnv.ts @@ -1,10 +1,10 @@ /** * Builds the environment variables to inject into sandbox commands. * Shared by runSandboxCommandTask and codingAgentTask. + * + * @param accountId */ -export function getSandboxEnv( - accountId: string -): Record { +export function getSandboxEnv(accountId: string): Record { const apiKey = process.env.RECOUP_API_KEY; if (!apiKey) { throw new Error("Missing RECOUP_API_KEY environment variable"); diff --git a/src/sandboxes/getVercelSandboxCredentials.ts b/src/sandboxes/getVercelSandboxCredentials.ts index ca42463..0e18a2d 100644 --- a/src/sandboxes/getVercelSandboxCredentials.ts +++ b/src/sandboxes/getVercelSandboxCredentials.ts @@ -10,7 +10,7 @@ export function getVercelSandboxCredentials() { if (!token || !teamId || !projectId) { throw new Error( - "Missing Vercel credentials. Set VERCEL_TOKEN, VERCEL_TEAM_ID, and VERCEL_PROJECT_ID." + "Missing Vercel credentials. Set VERCEL_TOKEN, VERCEL_TEAM_ID, and VERCEL_PROJECT_ID.", ); } diff --git a/src/sandboxes/git/addOrgSubmodules.ts b/src/sandboxes/git/addOrgSubmodules.ts index 7770f77..198001d 100644 --- a/src/sandboxes/git/addOrgSubmodules.ts +++ b/src/sandboxes/git/addOrgSubmodules.ts @@ -32,7 +32,7 @@ export async function addOrgSubmodules(sandbox: Sandbox): Promise { const stdout = (await findResult.stdout()) || ""; const orgNames = stdout .split("\n") - .map((s) => s.trim()) + .map(s => s.trim()) .filter(Boolean); if (orgNames.length === 0) { diff --git a/src/sandboxes/git/pushOrgRepos.ts b/src/sandboxes/git/pushOrgRepos.ts index 969a4f6..9451b79 100644 --- a/src/sandboxes/git/pushOrgRepos.ts +++ b/src/sandboxes/git/pushOrgRepos.ts @@ -12,9 +12,7 @@ import { getSandboxHomeDir } from "../getSandboxHomeDir"; * * @param sandbox - The Vercel Sandbox instance */ -export async function pushOrgRepos( - sandbox: Sandbox -): Promise { +export async function pushOrgRepos(sandbox: Sandbox): Promise { const githubToken = process.env.GITHUB_TOKEN; if (!githubToken) { @@ -37,7 +35,7 @@ export async function pushOrgRepos( const stdout = (await findResult.stdout()) || ""; const orgNames = stdout .split("\n") - .map((s) => s.trim()) + .map(s => s.trim()) .filter(Boolean); if (orgNames.length === 0) { diff --git a/src/sandboxes/installSkill.ts b/src/sandboxes/installSkill.ts index b016d14..12ccbce 100644 --- a/src/sandboxes/installSkill.ts +++ b/src/sandboxes/installSkill.ts @@ -10,10 +10,7 @@ import { logger } from "@trigger.dev/sdk/v3"; * @param sandbox - The Vercel Sandbox instance * @param skill - The skills.sh skill identifier (e.g. "recoupable/setup-sandbox") */ -export async function installSkill( - sandbox: Sandbox, - skill: string -): Promise { +export async function installSkill(sandbox: Sandbox, skill: string): Promise { const skillName = skill.split("/").pop()!; logger.log("Installing skill via skills.sh", { skill }); diff --git a/src/sandboxes/logStep.ts b/src/sandboxes/logStep.ts index f166f99..7b8c2d2 100644 --- a/src/sandboxes/logStep.ts +++ b/src/sandboxes/logStep.ts @@ -8,11 +8,7 @@ import { logger, metadata } from "@trigger.dev/sdk/v3"; * @param isStep - If true, also sets currentStep metadata (default: true) * @param extra - Optional JSON object included in logger.log but NOT in metadata */ -export function logStep( - message: string, - isStep = true, - extra?: Record, -): void { +export function logStep(message: string, isStep = true, extra?: Record): void { if (isStep) { metadata.set("currentStep", message); } diff --git a/src/sandboxes/notifyCodingAgentCallback.ts b/src/sandboxes/notifyCodingAgentCallback.ts index b676780..065c64c 100644 --- a/src/sandboxes/notifyCodingAgentCallback.ts +++ b/src/sandboxes/notifyCodingAgentCallback.ts @@ -26,7 +26,10 @@ export async function notifyCodingAgentCallback(payload: CallbackPayload): Promi return; } - logger.log("Sending coding agent callback", { threadId: payload.threadId, status: payload.status }); + logger.log("Sending coding agent callback", { + threadId: payload.threadId, + status: payload.status, + }); try { const response = await fetch(url, { diff --git a/src/sandboxes/onboardOpenClaw.ts b/src/sandboxes/onboardOpenClaw.ts index c450f93..27ba42a 100644 --- a/src/sandboxes/onboardOpenClaw.ts +++ b/src/sandboxes/onboardOpenClaw.ts @@ -35,7 +35,7 @@ export async function onboardOpenClaw(sandbox: Sandbox): Promise { ]; logger.log("Running OpenClaw onboard", { - command: `openclaw ${onboardArgs.map((a) => (a === process.env.VERCEL_AI_GATEWAY_API_KEY ? "[REDACTED]" : a)).join(" ")}`, + command: `openclaw ${onboardArgs.map(a => (a === process.env.VERCEL_AI_GATEWAY_API_KEY ? "[REDACTED]" : a)).join(" ")}`, }); const onboard = await sandbox.runCommand({ diff --git a/src/sandboxes/parsePRUrls.ts b/src/sandboxes/parsePRUrls.ts index 5d0be6c..d08b430 100644 --- a/src/sandboxes/parsePRUrls.ts +++ b/src/sandboxes/parsePRUrls.ts @@ -19,7 +19,9 @@ export function parsePRUrls(stdout: string): ParsedPR[] { const prs: ParsedPR[] = []; for (const line of lines) { - const match = line.match(/^PR_CREATED:\s*(https:\/\/github\.com\/([^/]+\/[^/]+)\/pull\/(\d+))\s*$/); + const match = line.match( + /^PR_CREATED:\s*(https:\/\/github\.com\/([^/]+\/[^/]+)\/pull\/(\d+))\s*$/, + ); if (!match) continue; const url = match[1]; @@ -27,7 +29,7 @@ export function parsePRUrls(stdout: string): ParsedPR[] { const number = parseInt(match[3], 10); // Look up baseBranch from SUBMODULE_CONFIG by matching the repo - const configEntry = Object.values(SUBMODULE_CONFIG).find((c) => c.repo === repo); + const configEntry = Object.values(SUBMODULE_CONFIG).find(c => c.repo === repo); const baseBranch = configEntry?.baseBranch ?? "main"; prs.push({ repo, number, url, baseBranch }); diff --git a/src/sandboxes/pushSandboxToGithub.ts b/src/sandboxes/pushSandboxToGithub.ts index 4f711b6..3defd23 100644 --- a/src/sandboxes/pushSandboxToGithub.ts +++ b/src/sandboxes/pushSandboxToGithub.ts @@ -15,9 +15,7 @@ import { pushOrgRepos } from "./git/pushOrgRepos"; * @param sandbox - The Vercel Sandbox instance * @returns true if push succeeded or there were no changes, false on error */ -export async function pushSandboxToGithub( - sandbox: Sandbox -): Promise { +export async function pushSandboxToGithub(sandbox: Sandbox): Promise { logger.log("Pushing sandbox files to GitHub"); // Configure git user for commits @@ -25,18 +23,14 @@ export async function pushSandboxToGithub( !(await runGitCommand( sandbox, ["config", "user.email", "agent@recoupable.com"], - "configure git email" + "configure git email", )) ) { return false; } if ( - !(await runGitCommand( - sandbox, - ["config", "user.name", "Recoup Agent"], - "configure git name" - )) + !(await runGitCommand(sandbox, ["config", "user.name", "Recoup Agent"], "configure git name")) ) { return false; } @@ -67,11 +61,7 @@ export async function pushSandboxToGithub( if (diffResult.exitCode !== 0) { // There are staged changes — commit them if ( - !(await runGitCommand( - sandbox, - ["commit", "-m", "Update sandbox files"], - "commit changes" - )) + !(await runGitCommand(sandbox, ["commit", "-m", "Update sandbox files"], "commit changes")) ) { return false; } @@ -82,11 +72,7 @@ export async function pushSandboxToGithub( // Force push — sandbox files are the source of truth if ( - !(await runGitCommand( - sandbox, - ["push", "--force", "origin", "HEAD:main"], - "push to remote" - )) + !(await runGitCommand(sandbox, ["push", "--force", "origin", "HEAD:main"], "push to remote")) ) { return false; } diff --git a/src/sandboxes/runGitCommand.ts b/src/sandboxes/runGitCommand.ts index 1e99b6e..21ceb01 100644 --- a/src/sandboxes/runGitCommand.ts +++ b/src/sandboxes/runGitCommand.ts @@ -4,12 +4,15 @@ import { logger } from "@trigger.dev/sdk/v3"; /** * Runs a git command in the sandbox and logs stderr on failure. * + * @param sandbox + * @param args + * @param description * @returns true if the command succeeded, false otherwise */ export async function runGitCommand( sandbox: Sandbox, args: string[], - description: string + description: string, ): Promise { const result = await sandbox.runCommand({ cmd: "git", args }); diff --git a/src/sandboxes/runOpenClawAgent.ts b/src/sandboxes/runOpenClawAgent.ts index 9dc239f..a98d99d 100644 --- a/src/sandboxes/runOpenClawAgent.ts +++ b/src/sandboxes/runOpenClawAgent.ts @@ -22,7 +22,7 @@ interface RunOpenClawAgentResult { */ export async function runOpenClawAgent( sandbox: Sandbox, - options: RunOpenClawAgentOptions + options: RunOpenClawAgentOptions, ): Promise { const { label, message, env } = options; diff --git a/src/sandboxes/runSetupArtistSkill.ts b/src/sandboxes/runSetupArtistSkill.ts index b5956f9..eac7aa5 100644 --- a/src/sandboxes/runSetupArtistSkill.ts +++ b/src/sandboxes/runSetupArtistSkill.ts @@ -10,7 +10,7 @@ import { runOpenClawAgent } from "./runOpenClawAgent"; */ export async function runSetupArtistSkill( sandbox: Sandbox, - env: Record + env: Record, ): Promise { const result = await runOpenClawAgent(sandbox, { label: "Running setup-artist skill", diff --git a/src/sandboxes/runSetupSandboxSkill.ts b/src/sandboxes/runSetupSandboxSkill.ts index 7ea296a..77eacc5 100644 --- a/src/sandboxes/runSetupSandboxSkill.ts +++ b/src/sandboxes/runSetupSandboxSkill.ts @@ -10,7 +10,7 @@ import { runOpenClawAgent } from "./runOpenClawAgent"; */ export async function runSetupSandboxSkill( sandbox: Sandbox, - env: Record + env: Record, ): Promise { const result = await runOpenClawAgent(sandbox, { label: "Running setup-sandbox skill", diff --git a/src/sandboxes/setupOpenClaw.ts b/src/sandboxes/setupOpenClaw.ts index 7f7c480..f5ae752 100644 --- a/src/sandboxes/setupOpenClaw.ts +++ b/src/sandboxes/setupOpenClaw.ts @@ -11,10 +11,7 @@ import { OPENCLAW_DEFAULT_MODEL } from "../consts"; * @param accountId - The account ID for the sandbox owner * @throws Error if env injection or gateway startup fails */ -export async function setupOpenClaw( - sandbox: Sandbox, - accountId: string -): Promise { +export async function setupOpenClaw(sandbox: Sandbox, accountId: string): Promise { await onboardOpenClaw(sandbox); // Inject RECOUP env vars into openclaw.json's env block so they're diff --git a/src/sandboxes/snapshotAndPersist.ts b/src/sandboxes/snapshotAndPersist.ts index 56b36b3..868c81a 100644 --- a/src/sandboxes/snapshotAndPersist.ts +++ b/src/sandboxes/snapshotAndPersist.ts @@ -11,11 +11,7 @@ import { updateAccountSnapshot } from "../recoup/updateAccountSnapshot"; * @param githubRepo - Optional GitHub repo URL to persist alongside the snapshot * @returns The snapshot result with snapshotId and expiresAt */ -export async function snapshotAndPersist( - sandbox: Sandbox, - accountId: string, - githubRepo?: string -) { +export async function snapshotAndPersist(sandbox: Sandbox, accountId: string, githubRepo?: string) { const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000; logger.log("Taking sandbox snapshot"); diff --git a/src/sandboxes/writeReadme.ts b/src/sandboxes/writeReadme.ts index 536f031..4c0b69b 100644 --- a/src/sandboxes/writeReadme.ts +++ b/src/sandboxes/writeReadme.ts @@ -16,7 +16,7 @@ export async function writeReadme( sandbox: Sandbox, sandboxId: string, accountId: string, - githubRepo?: string + githubRepo?: string, ): Promise { // Skip if README.md already has our content const check = await sandbox.runCommand({ diff --git a/src/schemas/__tests__/contentCreationSchema.test.ts b/src/schemas/__tests__/contentCreationSchema.test.ts index 5ed44ca..926f2b4 100644 --- a/src/schemas/__tests__/contentCreationSchema.test.ts +++ b/src/schemas/__tests__/contentCreationSchema.test.ts @@ -39,4 +39,3 @@ describe("createContentPayloadSchema", () => { expect(result.success).toBe(false); }); }); - diff --git a/src/schemas/contentCreationSchema.ts b/src/schemas/contentCreationSchema.ts index b733ca1..35700dd 100644 --- a/src/schemas/contentCreationSchema.ts +++ b/src/schemas/contentCreationSchema.ts @@ -17,4 +17,3 @@ export const createContentPayloadSchema = z.object({ }); export type CreateContentPayload = z.infer; - diff --git a/src/schemas/sandboxSchema.ts b/src/schemas/sandboxSchema.ts index 943eeea..77c6fd6 100644 --- a/src/schemas/sandboxSchema.ts +++ b/src/schemas/sandboxSchema.ts @@ -8,9 +8,7 @@ export const runSandboxCommandPayloadSchema = z.object({ accountId: z.string().min(1, "accountId is required"), }); -export type RunSandboxCommandPayload = z.infer< - typeof runSandboxCommandPayloadSchema ->; +export type RunSandboxCommandPayload = z.infer; export const snapshotSchema = z.object({ id: z.string(), diff --git a/src/socials/filterScrapableSocials.ts b/src/socials/filterScrapableSocials.ts index 610fbee..80b8393 100644 --- a/src/socials/filterScrapableSocials.ts +++ b/src/socials/filterScrapableSocials.ts @@ -12,13 +12,13 @@ export type ScrapableSocial = { /** * Filters and collects all scrapable socials from the artist socials map. * Returns an array of scrapable socials with their associated artist IDs. + * + * @param artistIds + * @param artistSocialsMap */ export function filterScrapableSocials( artistIds: string[], - artistSocialsMap: Map< - string, - Awaited> | undefined - > + artistSocialsMap: Map> | undefined>, ): ScrapableSocial[] { const scrapableSocials: ScrapableSocial[] = []; const nonScrapableSocials: Array<{ diff --git a/src/socials/scrapeAndPollSocials.ts b/src/socials/scrapeAndPollSocials.ts index 860f8f9..f0e98b8 100644 --- a/src/socials/scrapeAndPollSocials.ts +++ b/src/socials/scrapeAndPollSocials.ts @@ -9,10 +9,13 @@ export const SCRAPE_BATCH_SIZE = 10; /** * Scrapes and polls socials in batches, waiting for each batch to complete before starting the next. * Returns an array of poll results for all completed scrapes. + * + * @param socials + * @param batchSize */ export async function scrapeAndPollSocials( socials: ScrapableSocial[], - batchSize: number = SCRAPE_BATCH_SIZE + batchSize: number = SCRAPE_BATCH_SIZE, ): Promise { const allResults: PollResult[] = []; @@ -23,7 +26,7 @@ export async function scrapeAndPollSocials( // Start scrapes for this batch const scrapeResults = await Promise.all( - socialBatch.map((social) => scrapeSocial(social.socialId)) + socialBatch.map(social => scrapeSocial(social.socialId)), ); // Collect valid runs from this batch @@ -75,27 +78,24 @@ export async function scrapeAndPollSocials( // Log all successfully started scrapes for this batch if (startedScrapes.length > 0) { - logger.log( - `Started scrapes for batch ${batchNumber} of ${totalBatches}`, - { - count: startedScrapes.length, - scrapes: startedScrapes, - } - ); + logger.log(`Started scrapes for batch ${batchNumber} of ${totalBatches}`, { + count: startedScrapes.length, + scrapes: startedScrapes, + }); } // Poll this batch to completion before moving to next batch logger.log(`Polling batch ${batchNumber} runs to completion`, { batchRuns: batchRuns.length, - runIds: batchRuns.map((r) => r.runId), + runIds: batchRuns.map(r => r.runId), }); const batchResults = await pollScraperResults(batchRuns); logger.log(`Batch ${batchNumber} completed`, { total: batchResults.length, - succeeded: batchResults.filter((r) => r.status === "SUCCEEDED").length, - failed: batchResults.filter((r) => r.status === "FAILED").length, + succeeded: batchResults.filter(r => r.status === "SUCCEEDED").length, + failed: batchResults.filter(r => r.status === "FAILED").length, results: batchResults, }); diff --git a/src/tasks/__tests__/codingAgentTask.test.ts b/src/tasks/__tests__/codingAgentTask.test.ts index e98f1c4..543d019 100644 --- a/src/tasks/__tests__/codingAgentTask.test.ts +++ b/src/tasks/__tests__/codingAgentTask.test.ts @@ -38,8 +38,10 @@ vi.mock("../../sandboxes/runClaudeCodeAgent", () => ({ vi.mock("../../sandboxes/pushAndCreatePRsViaAgent", () => ({ pushAndCreatePRsViaAgent: vi.fn().mockResolvedValue([ { - repo: "recoupable/api", number: 42, - url: "https://github.com/recoupable/api/pull/42", baseBranch: "test", + repo: "recoupable/api", + number: 42, + url: "https://github.com/recoupable/api/pull/42", + baseBranch: "test", }, ]), })); @@ -150,5 +152,4 @@ describe("codingAgentTask", () => { expect(cloneOrder).toBeLessThan(syncOrder); expect(syncOrder).toBeLessThan(agentOrder); }); - }); diff --git a/src/tasks/__tests__/sendPulsesTask.test.ts b/src/tasks/__tests__/sendPulsesTask.test.ts index 52ff3a7..fae60d6 100644 --- a/src/tasks/__tests__/sendPulsesTask.test.ts +++ b/src/tasks/__tests__/sendPulsesTask.test.ts @@ -20,8 +20,7 @@ vi.mock("../../recoup/fetchActivePulses", () => ({ const mockExecutePulseInSandbox = vi.fn(); vi.mock("../../pulse/executePulseInSandbox", () => ({ - executePulseInSandbox: (...args: unknown[]) => - mockExecutePulseInSandbox(...args), + executePulseInSandbox: (...args: unknown[]) => mockExecutePulseInSandbox(...args), })); // Import after mocks @@ -53,7 +52,7 @@ describe("sendPulsesTask", () => { expect.objectContaining({ accountId: "account-1", prompt: expect.any(String), - }) + }), ); }); diff --git a/src/tasks/__tests__/setupSandboxTask.test.ts b/src/tasks/__tests__/setupSandboxTask.test.ts index f9baed5..323f164 100644 --- a/src/tasks/__tests__/setupSandboxTask.test.ts +++ b/src/tasks/__tests__/setupSandboxTask.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; vi.mock("@trigger.dev/sdk/v3", () => ({ logger: { log: vi.fn(), error: vi.fn() }, - schemaTask: vi.fn((config) => config), + schemaTask: vi.fn(config => config), })); const mockStop = vi.fn().mockResolvedValue(undefined); @@ -23,9 +23,7 @@ vi.mock("../../sandboxes/logStep", () => ({ })); vi.mock("../../sandboxes/provisionSandbox", () => ({ - provisionSandbox: vi - .fn() - .mockResolvedValue({ githubRepo: "https://github.com/org/repo" }), + provisionSandbox: vi.fn().mockResolvedValue({ githubRepo: "https://github.com/org/repo" }), })); vi.mock("../../sandboxes/snapshotAndPersist", () => ({ @@ -36,15 +34,9 @@ vi.mock("../../sandboxes/snapshotAndPersist", () => ({ })); const { setupSandboxTask } = await import("../setupSandboxTask"); -const { getOrCreateSandbox } = await import( - "../../sandboxes/getOrCreateSandbox" -); -const { provisionSandbox } = await import( - "../../sandboxes/provisionSandbox" -); -const { snapshotAndPersist } = await import( - "../../sandboxes/snapshotAndPersist" -); +const { getOrCreateSandbox } = await import("../../sandboxes/getOrCreateSandbox"); +const { provisionSandbox } = await import("../../sandboxes/provisionSandbox"); +const { snapshotAndPersist } = await import("../../sandboxes/snapshotAndPersist"); beforeEach(() => { vi.clearAllMocks(); diff --git a/src/tasks/__tests__/updatePRTask.test.ts b/src/tasks/__tests__/updatePRTask.test.ts index 0b1c9e7..0691fce 100644 --- a/src/tasks/__tests__/updatePRTask.test.ts +++ b/src/tasks/__tests__/updatePRTask.test.ts @@ -23,7 +23,9 @@ vi.mock("@vercel/sandbox", () => ({ vi.mock("../../sandboxes/getVercelSandboxCredentials", () => ({ getVercelSandboxCredentials: vi.fn().mockReturnValue({ - token: "tok", teamId: "team", projectId: "proj", + token: "tok", + teamId: "team", + projectId: "proj", }), })); diff --git a/src/tasks/codingAgentTask.ts b/src/tasks/codingAgentTask.ts index bc31500..a06556f 100644 --- a/src/tasks/codingAgentTask.ts +++ b/src/tasks/codingAgentTask.ts @@ -18,11 +18,11 @@ import { CODING_AGENT_ACCOUNT_ID } from "../consts"; export const codingAgentTask = schemaTask({ id: "coding-agent", schema: codingAgentPayloadSchema, - maxDuration: 60 * 30, + maxDuration: 60 * 15, retry: { maxAttempts: 0, }, - run: async (payload) => { + run: async payload => { const { prompt, callbackThreadId } = payload; const { sandboxId, sandbox } = await getOrCreateSandbox(CODING_AGENT_ACCOUNT_ID); @@ -52,7 +52,10 @@ export const codingAgentTask = schemaTask({ logStep("Creating PRs via agent"); const timestamp = Date.now(); - const slug = prompt.slice(0, 30).replace(/[^a-zA-Z0-9]/g, "-").toLowerCase(); + const slug = prompt + .slice(0, 30) + .replace(/[^a-zA-Z0-9]/g, "-") + .toLowerCase(); const branch = `agent/${slug}-${timestamp}`; const prs = await pushAndCreatePRsViaAgent(sandbox, { prompt, branch }); diff --git a/src/tasks/createContentTask.ts b/src/tasks/createContentTask.ts index 5237ab4..4e4baa0 100644 --- a/src/tasks/createContentTask.ts +++ b/src/tasks/createContentTask.ts @@ -100,10 +100,14 @@ export const createContentTask = schemaTask({ // --- Step 4: Fetch artist/audience context --- logStep("Fetching artist context"); const artistContext = await fetchArtistContext( - payload.githubRepo, payload.artistSlug, fetchGithubFile, + payload.githubRepo, + payload.artistSlug, + fetchGithubFile, ); const audienceContext = await fetchAudienceContext( - payload.githubRepo, payload.artistSlug, fetchGithubFile, + payload.githubRepo, + payload.artistSlug, + fetchGithubFile, ); // --- Step 5: Generate image --- diff --git a/src/tasks/customerPromptTask.ts b/src/tasks/customerPromptTask.ts index 3768930..4b3b49f 100644 --- a/src/tasks/customerPromptTask.ts +++ b/src/tasks/customerPromptTask.ts @@ -48,9 +48,7 @@ export const customerPromptTask = schedules.task({ const accountId = taskConfig?.accountId; const artistId = taskConfig?.artistId; const model = taskConfig?.model; - const prompt = - taskConfig?.prompt ?? - "Draft a friendly check-in message for our customers."; + const prompt = taskConfig?.prompt ?? "Draft a friendly check-in message for our customers."; if (!accountId) { logger.error("Missing required accountId from task"); diff --git a/src/tasks/proArtistSocialProfilesScrape.ts b/src/tasks/proArtistSocialProfilesScrape.ts index ddd5358..4f25034 100644 --- a/src/tasks/proArtistSocialProfilesScrape.ts +++ b/src/tasks/proArtistSocialProfilesScrape.ts @@ -36,11 +36,9 @@ export const proArtistSocialProfilesScrape = schedules.task({ // Log artists missing socials for visibility artistIds .filter( - (artistId) => - !artistSocialsMap.get(artistId) || - artistSocialsMap.get(artistId)?.length === 0 + artistId => !artistSocialsMap.get(artistId) || artistSocialsMap.get(artistId)?.length === 0, ) - .forEach((artistId) => { + .forEach(artistId => { logger.warn("No socials found for artist", { artistId }); }); @@ -62,15 +60,15 @@ export const proArtistSocialProfilesScrape = schedules.task({ logger.log("All scrape batches completed", { totalRuns: allResults.length, totalArtists: artistIds.length, - succeeded: allResults.filter((r) => r.status === "SUCCEEDED").length, - failed: allResults.filter((r) => r.status === "FAILED").length, + succeeded: allResults.filter(r => r.status === "SUCCEEDED").length, + failed: allResults.filter(r => r.status === "FAILED").length, }); return { totalArtists: artistIds.length, totalRuns: allResults.length, - succeeded: allResults.filter((r) => r.status === "SUCCEEDED").length, - failed: allResults.filter((r) => r.status === "FAILED").length, + succeeded: allResults.filter(r => r.status === "SUCCEEDED").length, + failed: allResults.filter(r => r.status === "FAILED").length, }; }, }); diff --git a/src/tasks/runSandboxCommandTask.ts b/src/tasks/runSandboxCommandTask.ts index 857df73..d5ef67f 100644 --- a/src/tasks/runSandboxCommandTask.ts +++ b/src/tasks/runSandboxCommandTask.ts @@ -12,10 +12,7 @@ import { ensureOrgRepos } from "../sandboxes/ensureOrgRepos"; import { ensureSetupSandbox } from "../sandboxes/ensureSetupSandbox"; import { pushSandboxToGithub } from "../sandboxes/pushSandboxToGithub"; import { syncOrgRepos } from "../sandboxes/git/syncOrgRepos"; -import { - runSandboxCommandPayloadSchema, - type SandboxResult, -} from "../schemas/sandboxSchema"; +import { runSandboxCommandPayloadSchema, type SandboxResult } from "../schemas/sandboxSchema"; /** * Background task that connects to an existing Vercel Sandbox, ensures OpenClaw @@ -91,11 +88,7 @@ export const runSandboxCommandTask = schemaTask({ logStep("Pushing to GitHub"); await pushSandboxToGithub(sandbox); - const snapshotResult = await snapshotAndPersist( - sandbox, - accountId, - githubRepo ?? undefined - ); + const snapshotResult = await snapshotAndPersist(sandbox, accountId, githubRepo ?? undefined); const result: SandboxResult = { stdout, diff --git a/src/tasks/sendPulsesTask.ts b/src/tasks/sendPulsesTask.ts index 7704065..1bd7e29 100644 --- a/src/tasks/sendPulsesTask.ts +++ b/src/tasks/sendPulsesTask.ts @@ -111,7 +111,7 @@ Use send_email with the html parameter to deliver.`; export const sendPulsesTask = schedules.task({ id: "send-pulses-task", cron: { pattern: "0 9 * * *", timezone: "America/New_York" }, // Run daily at 9 AM ET - run: async (payload) => { + run: async payload => { logger.log("Starting send pulses task", { timestamp: payload.timestamp, timezone: payload.timezone, @@ -134,7 +134,7 @@ export const sendPulsesTask = schedules.task({ } logger.log("Processing active pulses", { count: activePulses.length }); - accountIds = activePulses.map((pulse) => pulse.account_id); + accountIds = activePulses.map(pulse => pulse.account_id); } let sent = 0; diff --git a/src/tasks/setupSandboxTask.ts b/src/tasks/setupSandboxTask.ts index 71e0d65..63a8aee 100644 --- a/src/tasks/setupSandboxTask.ts +++ b/src/tasks/setupSandboxTask.ts @@ -17,7 +17,7 @@ export const setupSandboxTask = schemaTask({ retry: { maxAttempts: 0, // Zero retries — run once only }, - run: async (payload) => { + run: async payload => { const { accountId } = payload; logStep("Starting sandbox setup", true, { accountId }); @@ -30,11 +30,7 @@ export const setupSandboxTask = schemaTask({ logStep("Provisioning complete", false); logStep("Taking snapshot"); - const snapshotResult = await snapshotAndPersist( - sandbox, - accountId, - githubRepo, - ); + const snapshotResult = await snapshotAndPersist(sandbox, accountId, githubRepo); logStep("Sandbox setup complete", true, { sandboxId: sandbox.sandboxId, diff --git a/src/tasks/updatePRTask.ts b/src/tasks/updatePRTask.ts index d5ac75e..62fb7be 100644 --- a/src/tasks/updatePRTask.ts +++ b/src/tasks/updatePRTask.ts @@ -20,7 +20,7 @@ export const updatePRTask = schemaTask({ retry: { maxAttempts: 0, }, - run: async (payload) => { + run: async payload => { const { feedback, snapshotId, branch, repo, callbackThreadId } = payload; const { token, teamId, projectId } = getVercelSandboxCredentials();