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
29 changes: 29 additions & 0 deletions .codex/skills/cate-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,35 @@ panel. If a selected panel was closed, select another panel before continuing.

## Browser workflow

### Jev mode

Give Jev a natural-language task for the selected browser panel:

```bash
cate browser jev 'Set the Greeting field to Hi and click Save' --panel <id>
cate browser jev 'Open the Settings tab' --max-steps 10 --json
```

Save your OpenRouter API key in **Cate Settings → CLI → OpenRouter API key**.
No terminal export is needed. Enable Browser Read and Control in the same settings.
This mode sends the prompt and
page accessibility text to Jev (`typesafe/jev-1.13`) through OpenRouter's Decisions
API. Cate makes provider requests using the saved key; the key is not sent to the CLI.
Clearing the setting disables Jev. No other model is used: Jev selects browser
operations, elements, and complete destination URLs supplied in the prompt.
For text input, the prompt is split on whitespace and Jev selects one word as the
entire field value. Duplicate words are offered once; punctuation is preserved.
Words cannot be combined and new text cannot be generated. Include the exact field
value as a single word and supply absolute HTTP/HTTPS destination URLs explicitly.

Supported operations are HTTP/HTTPS navigation, clicks, field replacement, keys, page scrolling, and
waits. Runs pin the panel/tab and stop on user takeover, uncertainty, errors,
180 seconds, or the step limit (20 by default, up to 100). `--json` returns
the status, action trace, model-call count, and final URL; only `done` exits zero.
Completion is a model judgment based on the page; verify important outcomes.

### JavaScript mode

Browser control uses persistent JavaScript with the `cua` tab API. The old argv
actions, selectors, page evaluation, and revisioned string refs have been removed.
Start by binding a tab to get its accessibility state, then request a screenshot
Expand Down
99 changes: 99 additions & 0 deletions e2e/browser-jev.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { expect, test } from '@playwright/test'
import type { ElectronApplication, Page } from 'playwright'
import { closeApp, launchApp, seedTerminal } from './fixtures/electron-app'
import { fixtureEvaluate, target } from './fixtures/browser-control'

async function terminalCommand(page: Page, nodeId: string, command: string, marker: string) {
const wrapped = process.platform === 'win32'
? `${command}; Write-Output ("${marker}:{0}" -f $LASTEXITCODE)\r`
: `${command}; cate_status=$?; printf '\\n${marker}:%s\\n' "$cate_status"\r`
expect(await page.evaluate(({ nodeId, wrapped }) => window.__cateE2E!.writeTerminal(nodeId, wrapped), { nodeId, wrapped })).toBe(true)
await expect.poll(() => page.evaluate(({ nodeId, marker }) =>
new RegExp(`${marker}:\\d+`).test(window.__cateE2E!.terminalText(nodeId) ?? ''), { nodeId, marker }), { timeout: 200_000 }).toBe(true)
const text = await page.evaluate(nodeId => window.__cateE2E!.terminalText(nodeId), nodeId)
return { code: Number(text!.match(new RegExp(`${marker}:(\\d+)`))![1]), text }
}

async function readyTerminal(page: Page) {
const nodeId = await seedTerminal(page, { x: 100, y: 100 })
await expect.poll(() => page.evaluate(id => window.__cateE2E!.terminalPtyId(id), nodeId), { timeout: 60_000 }).not.toBeNull()
return nodeId
}

/** Only replace the external provider. CLI, host permissions, settings, and browser actions are real. */
async function mockDecisions(app: ElectronApplication, decisions: string[]) {
await app.evaluate((_electron, decisions) => {
const originalFetch = globalThis.fetch
globalThis.fetch = async (input, init) => {
if (String(input) !== 'https://openrouter.ai/api/alpha/decisions') return originalFetch(input, init)
if (new Headers(init?.headers).get('Authorization') !== 'Bearer test-saved-key') throw new Error('Saved key was not used')
const request = JSON.parse(String(init?.body))
const criteria = request.questions.decision.criteria
const next = decisions.shift()!
const choice = Object.hasOwn(criteria, next) ? next : Object.keys(criteria).find(key => criteria[key] === next)!
return new Response(JSON.stringify({ answers: { decision: { type: 'choice', choice, confidence: 1, probabilities: { [choice]: 1 } } } }))
}
}, decisions)
}

for (const word of ['Hi', 'Hello!']) test(`Jev saves ${word} using the Cate setting without an exported key`, async () => {
test.setTimeout(240_000)
const live = process.env.CATE_LIVE_JEV === '1'
if (live) expect(process.env.OPENROUTER_API_KEY).toBeTruthy()
const { electronApp: app, mainWindow: page } = await launchApp({ env: { OPENROUTER_API_KEY: '' } })
try {
if (live) {
await page.evaluate(key => window.electronAPI.settingsSet('cliOpenRouterApiKey', key), process.env.OPENROUTER_API_KEY!)
} else {
await page.evaluate(() => window.__cateE2E!.openSettings('cli'))
const keyInput = page.getByLabel('OpenRouter API key', { exact: true })
await expect(keyInput).toHaveAttribute('type', 'password')
await keyInput.fill(' test-saved-key ')
await keyInput.press('Enter')
await expect.poll(() => page.evaluate(() => window.electronAPI.settingsGet('cliOpenRouterApiKey'))).toBe('test-saved-key')
await page.keyboard.press('Escape')
await mockDecisions(app, ['setValue', 'c0', word, 'click', 'c0', 'done'])
}
const html = `<title>Greeting form</title><label>Greeting <input id="greeting"></label>
<button onclick="document.getElementById('result').textContent='Saved greeting: '+document.getElementById('greeting').value">Save</button><p id="result">Not saved yet</p>`
const browser = await page.evaluate(url => window.__cateE2E!.createBrowser(url, { x: 120, y: 120 }), `data:text/html,${encodeURIComponent(html)}`)
await target(page, browser, 'Greeting')
const terminal = await readyTerminal(page)
const result = await terminalCommand(page, terminal,
`cate browser jev 'Set Greeting to ${word} then click Save. Finish when the saved greeting shows ${word}' --panel ${browser.panelId} --max-steps 5 --json`, '__JEV_SAVED')
expect(result.code, result.text ?? '').toBe(0)
expect(result.text).toContain('"status":"done"')
expect(result.text).not.toContain('test-saved-key')
expect(await fixtureEvaluate(app, page, browser, 'document.getElementById("greeting").value')).toBe(word)
expect(await fixtureEvaluate(app, page, browser, 'document.getElementById("result").textContent')).toBe(`Saved greeting: ${word}`)
await page.evaluate(() => window.electronAPI.settingsSet('cliOpenRouterApiKey', ''))
const missing = await terminalCommand(page, terminal,
`cate browser jev 'Verify the saved greeting' --panel ${browser.panelId} --json`, '__JEV_CLEARED')
expect(missing.code).toBe(1)
expect(missing.text).toContain('Set the OpenRouter API key in Cate Settings')
} finally {
await page.evaluate(() => window.electronAPI.settingsSet('cliOpenRouterApiKey', '')).catch(() => {})
await closeApp(app)
}
})

test('Jev completes Wikipedia search through the CLI with a saved key', async () => {
test.skip(process.env.CATE_LIVE_JEV !== '1', 'Requires OpenRouter and public Wikipedia')
test.setTimeout(240_000)
expect(process.env.OPENROUTER_API_KEY).toBeTruthy()
const { electronApp: app, mainWindow: page } = await launchApp({ env: { OPENROUTER_API_KEY: '' } })
try {
await page.evaluate(key => window.electronAPI.settingsSet('cliOpenRouterApiKey', key), process.env.OPENROUTER_API_KEY!)
const browser = await page.evaluate(() => window.__cateE2E!.createBrowser('about:blank', { x: 120, y: 120 }))
const terminal = await readyTerminal(page)
const result = await terminalCommand(page, terminal,
`cate browser jev 'Go to https://www.wikipedia.org and search for Berlin then submit the search. Finish when the Berlin article heading is visible.' --panel ${browser.panelId} --max-steps 15 --json`, '__JEV_WIKIPEDIA')
expect(result.code, result.text ?? '').toBe(0)
expect(result.text).toContain('"status":"done"')
expect(await fixtureEvaluate(app, page, browser, 'document.querySelector("h1").textContent')).toBe('Berlin')
expect(await fixtureEvaluate(app, page, browser, 'location.pathname')).toBe('/wiki/Berlin')
} finally {
await page.evaluate(() => window.electronAPI.settingsSet('cliOpenRouterApiKey', '')).catch(() => {})
await closeApp(app)
}
})
29 changes: 29 additions & 0 deletions skills/cate-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,35 @@ panel. If a selected panel was closed, select another panel before continuing.

## Browser workflow

### Jev mode

Give Jev a natural-language task for the selected browser panel:

```bash
cate browser jev 'Set the Greeting field to Hi and click Save' --panel <id>
cate browser jev 'Open the Settings tab' --max-steps 10 --json
```

Save your OpenRouter API key in **Cate Settings → CLI → OpenRouter API key**.
No terminal export is needed. Enable Browser Read and Control in the same settings.
This mode sends the prompt and
page accessibility text to Jev (`typesafe/jev-1.13`) through OpenRouter's Decisions
API. Cate makes provider requests using the saved key; the key is not sent to the CLI.
Clearing the setting disables Jev. No other model is used: Jev selects browser
operations, elements, and complete destination URLs supplied in the prompt.
For text input, the prompt is split on whitespace and Jev selects one word as the
entire field value. Duplicate words are offered once; punctuation is preserved.
Words cannot be combined and new text cannot be generated. Include the exact field
value as a single word and supply absolute HTTP/HTTPS destination URLs explicitly.

Supported operations are HTTP/HTTPS navigation, clicks, field replacement, keys, page scrolling, and
waits. Runs pin the panel/tab and stop on user takeover, uncertainty, errors,
180 seconds, or the step limit (20 by default, up to 100). `--json` returns
the status, action trace, model-call count, and final URL; only `done` exits zero.
Completion is a model judgment based on the page; verify important outcomes.

### JavaScript mode

Browser control uses persistent JavaScript with the `cua` tab API. The old argv
actions, selectors, page evaluation, and revisioned string refs have been removed.
Start by binding a tab to get its accessibility state, then request a screenshot
Expand Down
Loading
Loading