create-director-app
Version:
NPX package to clone director scripts from script generation service
288 lines (269 loc) • 9.34 kB
JavaScript
import fetch from "node-fetch";
function stripContextIdBlocks(script) {
const regex = /(\s*)if\s*\(\s*!contextId\s*\)\s*\{([\s\S]*?)\n\1\}/g;
return script.replace(regex, (match, indent, content) => {
const dedentedContent = content
.split("\n")
.map((line) => {
if (line.startsWith(indent + " ")) {
return indent + line.substring(indent.length + 2);
}
return line;
})
.join("\n");
return dedentedContent;
});
}
function replaceStagehandConfig(script) {
const configFunctionRegex = /const stagehandConfig = \(\)[^{]*\{[\s\S]*?\n\};\n/g;
let modifiedScript = script.replace(configFunctionRegex, "");
modifiedScript = modifiedScript.replace(/stagehandConfig\(\)/g, "StagehandConfig");
if (!modifiedScript.includes("import StagehandConfig")) {
const importRegex = /^import[\s\S]*?from[\s\S]*?;$/gm;
const imports = modifiedScript.match(importRegex);
if (imports) {
const lastImport = imports[imports.length - 1];
const lastImportIndex = modifiedScript.lastIndexOf(lastImport);
modifiedScript =
modifiedScript.slice(0, lastImportIndex + lastImport.length) +
'\nimport StagehandConfig from "./stagehand.config.js";' +
modifiedScript.slice(lastImportIndex + lastImport.length);
}
else {
modifiedScript =
'import StagehandConfig from "./stagehand.config.js";\n' +
modifiedScript;
}
}
return modifiedScript;
}
/**
* Cleans up imports and removes contextId variable
*/
function cleanupImportsAndVariables(script) {
let modifiedScript = script.replace(/import\s*\{([^}]*)\}\s*from\s*["']@browserbasehq\/stagehand["'];?/, (match, imports) => {
const importList = imports
.split(",")
.map((imp) => imp.trim())
.filter((imp) => {
const cleaned = imp.replace(/type\s+/, "");
return cleaned !== "ConstructorParams" && cleaned !== "LogLine";
});
return `import { ${importList.join(", ")} } from "@browserbasehq/stagehand";`;
});
// Remove contextId variable declaration
modifiedScript = modifiedScript.replace(/const\s+contextId\s*=\s*["'][^"']*["'];\s*\n?/g, "");
// Replace 'export default runWorkflow;' with 'runWorkflow();'
modifiedScript = modifiedScript.replace(/export\s+default\s+runWorkflow\s*;?\s*$/m, "runWorkflow();");
return modifiedScript;
}
export async function fetchTemplate(jwtToken, endpoint) {
try {
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${jwtToken}`,
},
body: JSON.stringify({
template: jwtToken,
}),
});
if (!response.ok) {
if (response.status === 403) {
throw new Error("Token expired. Please regenerate the command and try again.");
}
else if (response.status === 404) {
throw new Error("Script not found. Token may be invalid or expired.");
}
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = (await response.json());
if (!result.success || !result.data || !result.data.script) {
throw new Error(result.error || "Failed to fetch script");
}
let processedScript = stripContextIdBlocks(result.data.script);
processedScript = replaceStagehandConfig(processedScript);
processedScript = cleanupImportsAndVariables(processedScript);
const templateData = {
name: result.data.name,
script: processedScript,
files: createProjectFiles(processedScript, result.data.name),
packageJson: createPackageJson(result.data.name),
instructions: result.data.instructions || createDefaultInstructions(result.data.name),
};
return templateData;
}
catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to fetch template: ${error.message}`);
}
throw new Error("Failed to fetch template: Unknown error");
}
}
function createProjectFiles(scriptContent, templateId) {
return [
{
path: "index.ts",
content: scriptContent,
type: "file",
},
{
path: "stagehand.config.ts",
content: createStagehandConfig(),
type: "file",
},
{
path: "tsconfig.json",
content: createTsConfig(),
type: "file",
},
{
path: ".cursorrules",
content: createCursorRules(),
type: "file",
},
{
path: ".gitignore",
content: createGitignore(),
type: "file",
},
{
path: ".env.example",
content: createEnvFile(),
type: "file",
},
];
}
function createPackageJson(templateId) {
return {
name: templateId,
version: "1.0.0",
description: "Stagehand workflow project",
type: "module",
main: "index.js",
scripts: {
start: "tsx index.ts",
build: "tsc",
dev: "tsx watch index.ts",
postinstall: "playwright install chromium",
},
dependencies: {
"@browserbasehq/stagehand": "^2.2.1",
zod: "^3.24.3",
dotenv: "^16.4.7",
},
devDependencies: {
"@types/node": "^22.5.5",
tsx: "^4.19.2",
typescript: "^5.7.2",
},
};
}
function createStagehandConfig() {
return `import type { ConstructorParams } from "@browserbasehq/stagehand";
import dotenv from "dotenv";
dotenv.config();
const StagehandConfig: ConstructorParams = {
verbose: 1 /* Verbosity level for logging: 0 = silent, 1 = info, 2 = all */,
domSettleTimeoutMs: 30_000 /* Timeout for DOM to settle in milliseconds */,
modelName: "gemini-2.0-flash" /* Name of the model to use */,
modelClientOptions: {
apiKey: process.env.GOOGLE_API_KEY,
} /* Configuration options for the model client */,
env: "LOCAL" /* Environment to run in: LOCAL or BROWSERBASE */,
localBrowserLaunchOptions: {
viewport: {
width: 1024,
height: 768,
},
} /* Configuration options for the local browser */,
};
export default StagehandConfig;
`;
}
function createTsConfig() {
return `{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022", "DOM"],
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"outDir": "./dist",
"rootDir": "./",
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["./**/*.ts"],
"exclude": ["node_modules", "dist"]
}`;
}
function createCursorRules() {
return `# Stagehand Cursor Rules
You are an expert TypeScript and Playwright developer working with Stagehand.
## Key Guidelines:
- Always use Stagehand's act(), observe(), and extract() methods
- Prefer observe() before act() for better reliability
- Follow the existing code patterns and style
- Use proper error handling with try/catch blocks
- Always close Stagehand instances in finally blocks
## Stagehand Methods:
- page.act(instruction) - Perform actions on the page
- page.observe(instruction) - Plan actions before executing
- page.extract({instruction, schema}) - Extract structured data
- page.goto(url) - Navigate to a page
## Environment Variables:
- GOOGLE_API_KEY - For Google AI models
- BROWSERBASE_API_KEY - For Browserbase
- BROWSERBASE_PROJECT_ID - Project ID
## Best Practices:
- Use descriptive action descriptions for better debugging
- Log important steps in the workflow
- Handle errors gracefully
- Test workflows in isolation when possible
Always ensure proper async/await usage and clean resource management.`;
}
function createGitignore() {
return `node_modules/
dist/
.env
*.log
.DS_Store`;
}
function createEnvFile() {
return `# Google AI configuration
GOOGLE_API_KEY=your_google_api_key_here`;
}
function createDefaultInstructions(templateId) {
return `# ${templateId}
This is a Stagehand workflow project.
## Setup
1. Install dependencies:
\`\`\`bash
npm install
\`\`\`
2. Configure your environment variables:
- Copy \`.env.example\` to \`.env\`
- Add your Browserbase API key and project ID
- Add your Google API key
3. Run the workflow:
\`\`\`bash
npm start
\`\`\`
## Project Structure
- \`index.ts\` - Your workflow implementation
- \`package.json\` - Node.js dependencies
- \`.env\` - Environment variables
## Environment Variables
- \`BROWSERBASE_API_KEY\` - Your Browserbase API key
- \`BROWSERBASE_PROJECT_ID\` - Your Browserbase project ID
- \`GOOGLE_API_KEY\` - Your Google API key for the AI model`;
}
//# sourceMappingURL=template-fetcher.js.map