Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,22 @@ cp .env.example .env
pnpm run spark "what is the current temperature in London?"
```

## Usage

Spark accepts up to three arguments:

```bash
pnpm run spark "<task>" [url] [data]
```

- **task**: Natural language description of what you want to do (required)
- **url**: Starting URL to begin the task from (optional)
- **data**: JSON object with contextual data for the task (optional)

## Examples

### Basic Usage

```bash
# Get current weather
pnpm run spark "is it raining in Tokyo"
Expand All @@ -53,6 +67,45 @@ pnpm run spark "what is the current price of AAPL stock"
pnpm run spark "what is the top headline on Reuters"
```

### With Starting URL

```bash
# Start from a specific website
pnpm run spark "find the latest news" "https://news.ycombinator.com"

# Check weather on a specific weather site
pnpm run spark "get weather for San Francisco" "https://weather.com"

# Book a flight from NYC to LAX on December 25th for 2 passengers
pnpm run spark "book a flight from NYC to LAX on December 25th for 2 passengers" "https://airline.com"
```

### With Contextual Data

You can provide details either in the task description OR in the data object. Using the data object helps keep the task description clean and provides structured information the AI can easily reference.

```bash
# Same task as above, but with structured data instead of in the prompt
pnpm run spark "book a flight" "https://airline.com" '{"departure":"NYC","destination":"LAX","date":"2024-12-25","passengers":2}'

# Fill out a form with provided information
pnpm run spark "submit contact form" "https://example.com/contact" '{"name":"John Doe","email":"john@example.com","message":"Hello world"}'

# Compare: details in prompt vs. data object
pnpm run spark "find hotels in Paris from Dec 20-22 for 2 guests" "https://booking.com"
# vs.
pnpm run spark "find hotels" "https://booking.com" '{"location":"Paris","checkIn":"2024-12-20","checkOut":"2024-12-22","guests":2}'
```

**When to use data objects:**

- Complex information with multiple fields
- Structured data like dates, IDs, or specifications
- When you want to keep the task description simple and focused
- For reusable templates where only the data changes

The data object is passed as JSON and becomes available to the AI throughout the entire task execution, allowing it to reference the information when filling forms, making selections, or completing complex workflows.

## Development

Built with TypeScript, Playwright, and OpenAI's GPT-4.
Expand Down
25 changes: 22 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,44 @@ import { PlaywrightBrowser } from "./browser/playwrightBrowser.js";
// Load environment variables from .env file
config();

// Get the task and optional URL from the args
// Get the task, optional URL, and optional data from the args
const task = process.argv[2];
const startingUrl = process.argv[3];
const dataArg = process.argv[4];

if (!task) {
console.error(chalk.red.bold("❌ Error: Missing task argument"));
console.log("");
console.log(chalk.white.bold("Usage:"));
console.log(` ${chalk.cyan("spark")} ${chalk.green("<task>")} ${chalk.yellow("[url]")}`);
console.log(
` ${chalk.cyan("spark")} ${chalk.green("<task>")} ${chalk.yellow("[url]")} ${chalk.magenta("[data]")}`,
);
console.log("");
console.log(chalk.white.bold("Examples:"));
console.log(` ${chalk.cyan("spark")} ${chalk.green('"search for flights to Paris"')}`);
console.log(
` ${chalk.cyan("spark")} ${chalk.green('"find the latest news"')} ${chalk.yellow("https://news.ycombinator.com")}`,
);
console.log(
` ${chalk.cyan("spark")} ${chalk.green('"book a flight"')} ${chalk.yellow("https://airline.com")} ${chalk.magenta('\'{"departure":"NYC","destination":"LAX","date":"2024-12-25"}\'')}`,
);
console.log("");
process.exit(1);
}

// Parse data argument if provided
let data = null;
if (dataArg) {
try {
data = JSON.parse(dataArg);
} catch (error) {
console.error(chalk.red.bold("❌ Error: Invalid JSON in data argument"));
console.log(chalk.gray(`Data argument: ${dataArg}`));
console.log(chalk.gray(`Error: ${error instanceof Error ? error.message : String(error)}`));
process.exit(1);
}
}

// Debug flag - set to true to see page snapshots
const DEBUG = false;

Expand All @@ -48,7 +67,7 @@ const DEBUG = false;
});

// Execute the task
await webAgent.execute(task, startingUrl);
await webAgent.execute(task, startingUrl, data);

// Close the browser when done
await webAgent.close();
Expand Down
14 changes: 13 additions & 1 deletion src/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,27 @@ Task: {{task}}
Explanation: {{explanation}}
Plan: {{plan}}
Today's Date: {{currentDate}}
{{#if data}}
Input Data:
\`\`\`json
{{data}}
\`\`\`
{{/if}}
`.trim(),
);

export const buildTaskAndPlanPrompt = (task: string, explanation: string, plan: string) =>
export const buildTaskAndPlanPrompt = (
task: string,
explanation: string,
plan: string,
data?: any,
) =>
taskAndPlanTemplate({
task,
explanation,
plan,
currentDate: getCurrentFormattedDate(),
data: data ? JSON.stringify(data, null, 2) : null,
});

const pageSnapshotTemplate = buildPromptTemplate(
Expand Down
11 changes: 9 additions & 2 deletions src/webAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export class WebAgent {
private provider: LanguageModel;
private DEBUG = false;
private taskExplanation: string = "";
private data: any = null;
private readonly FILTERED_PREFIXES = ["/url:"];
private readonly ARIA_TRANSFORMATIONS: Array<[RegExp, string]> = [
[/^listitem/g, "li"],
Expand Down Expand Up @@ -106,7 +107,7 @@ export class WebAgent {
},
{
role: "user",
content: buildTaskAndPlanPrompt(task, this.taskExplanation, this.plan),
content: buildTaskAndPlanPrompt(task, this.taskExplanation, this.plan, this.data),
},
];
return this.messages;
Expand Down Expand Up @@ -377,6 +378,7 @@ export class WebAgent {
this.plan = "";
this.url = "";
this.messages = [];
this.data = null;
this.currentPage = { url: "", title: "" };
}

Expand Down Expand Up @@ -405,14 +407,19 @@ export class WebAgent {
return response.object;
}

async execute(task: string, startingUrl?: string) {
async execute(task: string, startingUrl?: string, data?: any) {
if (!task) {
throw new Error("No task provided.");
}

// Reset state for new task
this.resetState();

// Store the data if provided
if (data) {
this.data = data;
}

// If a starting URL is provided, use it directly
if (startingUrl) {
this.url = startingUrl;
Expand Down
Loading