diff --git a/packages/core/README.md b/packages/core/README.md index 48c4673..5081e99 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -4,6 +4,12 @@ Orchestrate complex, long-running coding tasks to an ephemeral cloud environment integrated with a GitHub repo. +## Examples + +- [Advanced Session](./examples/advanced-session/README.md) +- [Webhook Integration](./examples/webhook/README.md) +- [GitHub Actions](./examples/github-actions/README.md) + ## Send work to a Cloud based session ```ts diff --git a/packages/core/examples/advanced-session/README.md b/packages/core/examples/advanced-session/README.md new file mode 100644 index 0000000..6d7cbe3 --- /dev/null +++ b/packages/core/examples/advanced-session/README.md @@ -0,0 +1,14 @@ +# Advanced Session Example + +This example demonstrates how to use the Jules SDK to run a session, interact with it, wait for plan approvals, and monitor stream progress while parsing the output for artifacts. + +## Running + +Ensure you have your `JULES_API_KEY` set. +You can run the script via: + +```sh +bun run index.ts +``` + +This will run the advanced session and stream activities. diff --git a/packages/core/examples/advanced-session/index.ts b/packages/core/examples/advanced-session/index.ts new file mode 100644 index 0000000..cbfaeff --- /dev/null +++ b/packages/core/examples/advanced-session/index.ts @@ -0,0 +1,81 @@ +import { jules, JulesError } from '@google/jules-sdk'; + +/** + * Advanced Session Example + * + * Demonstrates: + * - Interactive session creation + * - Waiting for plan approval + * - Monitoring progress via reactive streams + * - Handling code diffs (changeSet) + */ +async function runAdvancedSession() { + try { + console.log('Starting advanced session...'); + + // Interactive Sessions allow you to provide feedback and guide the process + const session = await jules.session({ + prompt: `Create a simple python script that prints 'Hello Advanced Session!' and test it.`, + // For a repoless session, we don't provide a source + }); + + console.log(`Session created: ${session.id}`); + + // Wait for the plan to be generated and ready for approval + console.log('Waiting for plan approval...'); + await session.waitFor('awaitingPlanApproval'); + + console.log('Plan is ready. Approving it now.'); + await session.approve(); + + // Stream activities and artifacts + for await (const activity of session.stream()) { + switch (activity.type) { + case 'planGenerated': + console.log( + 'Plan:', + activity.plan?.steps.map((s) => s.title) + ); + break; + case 'agentMessaged': + console.log('Agent says:', activity.message); + break; + case 'progressUpdated': + console.log(`Progress: ${activity.title}`); + break; + case 'sessionCompleted': + console.log('Session complete!'); + break; + } + + // Check artifacts + for (const artifact of activity.artifacts ?? []) { + if (artifact.type === 'bashOutput') { + console.log(`[BASH] ${artifact.toString()}`); + } + if (artifact.type === 'changeSet') { + const parsed = artifact.parsed(); + for (const file of parsed.files) { + console.log( + `[DIFF] ${file.path}: +${file.additions} -${file.deletions}` + ); + } + } + } + } + + const outcome = await session.result(); + console.log(`Session finished with state: ${outcome.state}`); + + } catch (error) { + if (error instanceof JulesError) { + console.error(`SDK error: ${error.message}`); + } else { + console.error('Unknown error:', error); + } + } +} + +if (require.main === module) { + runAdvancedSession(); +} diff --git a/packages/core/examples/github-actions/README.md b/packages/core/examples/github-actions/README.md new file mode 100644 index 0000000..c963b2a --- /dev/null +++ b/packages/core/examples/github-actions/README.md @@ -0,0 +1,59 @@ +# GitHub Actions Example + +This example demonstrates how to use the Jules SDK within a GitHub Action to automate workflows. + +## Overview + +This example creates a custom GitHub Action that: +1. Takes a `prompt` input. +2. Reads the `JULES_API_KEY` from the environment. +3. Automatically uses the current repository and branch as context. +4. Starts a Jules session to perform the task. +5. Monitors the progress and outputs the resulting Pull Request URL. + +## Files + +- `index.ts`: The main action logic using the Jules SDK. +- `action.yml`: The metadata file that defines the action inputs, outputs, and runtime. +- `package.json`: Dependencies for the action (`@actions/core`, `@actions/github`, `@google/jules-sdk`). + +## Usage + +You can use this action in your own GitHub workflows by referencing its path or publishing it. + +Here is an example workflow file (`.github/workflows/jules.yml`) that triggers the agent whenever an issue is labeled `agent-fix`: + +```yaml +name: Jules Agent Workflow + +on: + issues: + types: [labeled] + +jobs: + run-jules: + if: github.event.label.name == 'agent-fix' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Run Jules Agent + uses: ./packages/core/examples/github-actions # Path to where this action is located + env: + JULES_API_KEY: ${{ secrets.JULES_API_KEY }} + with: + prompt: "Fix the following issue: ${{ github.event.issue.title }}\n\n${{ github.event.issue.body }}" +``` + +## Running the Example Locally + +To compile the TypeScript action: + +```bash +cd packages/core/examples/github-actions +npm install +npm run build +``` + +The compiled JavaScript will be placed in `dist/index.js`, which is what GitHub Actions will execute. diff --git a/packages/core/examples/github-actions/action.yml b/packages/core/examples/github-actions/action.yml new file mode 100644 index 0000000..3b233b9 --- /dev/null +++ b/packages/core/examples/github-actions/action.yml @@ -0,0 +1,13 @@ +name: 'Jules Agent Action' +description: 'Run a Jules coding agent session on your repository' +author: 'Google' +inputs: + prompt: + description: 'The instructions for the Jules agent' + required: true +outputs: + pr-url: + description: 'The URL of the Pull Request created by the agent, if any' +runs: + using: 'node20' + main: 'dist/index.js' diff --git a/packages/core/examples/github-actions/index.ts b/packages/core/examples/github-actions/index.ts new file mode 100644 index 0000000..a12dcdf --- /dev/null +++ b/packages/core/examples/github-actions/index.ts @@ -0,0 +1,89 @@ +import * as core from '@actions/core'; +import * as github from '@actions/github'; +import { jules } from '@google/jules-sdk'; + +async function run() { + try { + // 1. Read inputs defined in action.yml + const prompt = core.getInput('prompt', { required: true }); + + // 2. The Jules SDK requires JULES_API_KEY environment variable. + // GitHub Actions can pass this via `env:` or `with:` which sets an input. + // If you prefer not to use the environment variable, you can initialize + // the client specifically: + // const julesClient = jules.with({ apiKey: core.getInput('api-key') }); + + // We will assume JULES_API_KEY is available in the environment, + // which is the default behavior of the `jules` instance. + if (!process.env.JULES_API_KEY) { + throw new Error('JULES_API_KEY environment variable is missing.'); + } + + // 3. Get context about the current repository from the GitHub Action payload + const context = github.context; + const owner = context.repo.owner; + const repo = context.repo.repo; + const ref = context.ref; + + // Convert refs/heads/main to main + let baseBranch = 'main'; + if (ref.startsWith('refs/heads/')) { + baseBranch = ref.replace('refs/heads/', ''); + } + + core.info(`Starting Jules session for ${owner}/${repo} on branch ${baseBranch}`); + core.info(`Prompt: ${prompt}`); + + // 4. Create a new Jules session + const session = await jules.session({ + prompt: prompt, + source: { + github: `${owner}/${repo}`, + baseBranch: baseBranch, + }, + // Automatically create a PR when the session is complete + autoPr: true, + }); + + core.info(`Session created successfully. ID: ${session.id}`); + + // 5. Monitor the progress + for await (const activity of session.stream()) { + switch (activity.type) { + case 'planGenerated': + core.info(`[Plan Generated] ${activity.plan.steps.length} steps.`); + break; + case 'progressUpdated': + core.info(`[Progress Updated] ${activity.title}`); + break; + case 'sessionCompleted': + core.info(`[Session Completed]`); + break; + } + } + + // 6. Wait for the final outcome + const outcome = await session.result(); + + if (outcome.state === 'failed') { + core.setFailed(`Session failed.`); + return; + } + + core.info(`Session finished successfully.`); + + if (outcome.pullRequest) { + core.info(`Pull Request created: ${outcome.pullRequest.url}`); + // Set an output that other steps in the workflow can use + core.setOutput('pr-url', outcome.pullRequest.url); + } + } catch (error) { + if (error instanceof Error) { + core.setFailed(`Action failed with error: ${error.message}`); + } else { + core.setFailed(`Action failed with an unknown error.`); + } + } +} + +run(); diff --git a/packages/core/examples/github-actions/package.json b/packages/core/examples/github-actions/package.json new file mode 100644 index 0000000..d4a944d --- /dev/null +++ b/packages/core/examples/github-actions/package.json @@ -0,0 +1,18 @@ +{ + "name": "jules-github-actions-example", + "version": "1.0.0", + "description": "An example of using the Jules SDK in a GitHub Action", + "main": "dist/index.js", + "scripts": { + "build": "tsc" + }, + "dependencies": { + "@actions/core": "^1.10.1", + "@actions/github": "^6.0.0", + "@google/jules-sdk": "workspace:*" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.0.0" + } +} diff --git a/packages/core/examples/github-actions/tsconfig.json b/packages/core/examples/github-actions/tsconfig.json new file mode 100644 index 0000000..487a581 --- /dev/null +++ b/packages/core/examples/github-actions/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "CommonJS", + "moduleResolution": "node", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist" + }, + "include": ["index.ts"] +} diff --git a/packages/core/examples/webhook/README.md b/packages/core/examples/webhook/README.md new file mode 100644 index 0000000..2b40959 --- /dev/null +++ b/packages/core/examples/webhook/README.md @@ -0,0 +1,36 @@ +# Webhook Integration Example + +This example demonstrates how to create a simple webhook server using [Hono](https://hono.dev/) that listens for incoming HTTP `POST` requests and automatically creates a new Jules session. + +This is particularly useful for event-driven workflows, such as automatically running an agent when a specific event occurs in another system (e.g., a GitHub issue being created, a new row added to a database, or a custom application event). + +## Prerequisites + +- Ensure you have [Bun](https://bun.sh/) installed, or another compatible runtime like Node.js. +- Ensure you have your `JULES_API_KEY` set as an environment variable. + +## Running the Example + +1. Start the webhook server: + + ```bash + bun install + bun run index.ts + ``` + +2. The server will start and listen on port `3000`. + +3. Send a test webhook payload using `curl` (or your preferred tool like Postman): + + ```bash + curl -X POST http://localhost:3000/webhook \ + -H "Content-Type: application/json" \ + -d '{"event": "bug_report", "description": "Fix typo in README"}' + ``` + +4. Check the server console logs. You should see the incoming payload and the ID of the newly created Jules session. + +## Notes + +- In a real-world scenario, you should validate the incoming webhook payload to ensure it comes from a trusted source (e.g., verifying a signature or secret token). +- You can customize the `prompt` and `source` in `index.ts` to match your specific requirements and target repository. diff --git a/packages/core/examples/webhook/index.ts b/packages/core/examples/webhook/index.ts new file mode 100644 index 0000000..3e7782f --- /dev/null +++ b/packages/core/examples/webhook/index.ts @@ -0,0 +1,42 @@ +import { serve } from '@hono/node-server'; +import { Hono } from 'hono'; +import { jules } from '@google/jules-sdk'; + +const app = new Hono(); + +app.post('/webhook', async (c) => { + try { + const payload = await c.req.json(); + console.log('Received webhook payload:', payload); + + // Create a new Jules session triggered by the webhook + const session = await jules.session({ + prompt: `Process this webhook payload: ${JSON.stringify(payload)}`, + // Provide a default repository or adapt to your needs + source: { github: 'davideast/dataprompt', baseBranch: 'main' }, + }); + + console.log(`Created Jules session: ${session.id}`); + + return c.json({ + success: true, + message: 'Webhook processed and session created successfully', + sessionId: session.id, + }, 200); + + } catch (error) { + console.error('Error processing webhook:', error); + return c.json({ + success: false, + message: 'Failed to process webhook', + }, 500); + } +}); + +const port = 3000; +console.log(`Server is running on port ${port}`); + +serve({ + fetch: app.fetch, + port, +}); diff --git a/packages/core/examples/webhook/package.json b/packages/core/examples/webhook/package.json new file mode 100644 index 0000000..fe3e326 --- /dev/null +++ b/packages/core/examples/webhook/package.json @@ -0,0 +1,17 @@ +{ + "name": "webhook", + "version": "1.0.0", + "description": "Example demonstrating how to trigger a Jules session via a webhook.", + "main": "index.ts", + "scripts": { + "start": "bun run index.ts" + }, + "dependencies": { + "@google/jules-sdk": "workspace:*", + "@hono/node-server": "^1.11.1", + "hono": "^4.3.11" + }, + "devDependencies": { + "bun-types": "^1.1.8" + } +}