ego-browser
ego-browser는 CLI로 접근 가능한 Node.js 런타임을 통해 실제 Chromium 브라우저를 노출합니다. 미리 로드된 page, page.locator(...), browser 및 taskSpaces 퍼사드는 Playwright 스타일의 이름과 호출 형태를 따릅니다. taskSpaces, site, fetch, cdp는 ego-browser 고유의 기능을 제공합니다.
설치, 설정 또는 연결 문제는 references/install.md를 참조하세요.
Bash 도구를 사용하여 ego-browser nodejs <<'EOF' ... EOF 형태로 브라우저 작업을 실행합니다. JavaScript를 heredoc 안에 직접 넣으세요. .js 파일을 생성하거나, Playwright를 임포트하거나, 다른 브라우저를 실행하거나, 헬퍼 이름을 임의로 만들지 마세요.
heredoc은 JavaScript 컨테이너일 뿐입니다. Bash 호출 자체가 실행 단위입니다. 기본적으로 전체 브라우저 작업에 대해 하나의 Bash 호출을 사용합니다. 각 await는 내부 작업일 뿐, 단계 경계가 아닙니다. 실행 전에 예측 가능한 모든 관찰, 동작, 대기, 추출, 검증 및 제한된 대안을 스크립트에 인코딩하세요. 브라우저 결과를 JavaScript에서 즉시 사용하고, 작업이 완료될 때까지 프로세스 내에서 계속 적응하세요. 중간 출력을 검사하거나 다음 동작을 계획하기 위해 종료하지 마세요. 필수 사용자 또는 외부 제어, 프로세스 내에서 수행할 수 없는 시각적 검사, 또는 스크립트가 복구할 수 없는 프로세스 수준 실패가 있는 경우에만 다른 Bash 명령을 시작하세요.
페이지 컨트롤을 검사하기 전에 가장 상태 비저장적인(least-stateful) 신뢰할 수 있는 경로를 선택하세요. 작업이 결과나 제약 조건을 지정하지만 필수 상호 작용을 지정하지 않는 경우, 이미 올바른 상태나 직접 인코딩하는 알려진 안정적인 URL 또는 사이트 경로를 선호하세요. UI를 통해 동등한 필터, 정렬 또는 탐색을 재생하는 대신 결과 목표 상태를 검증하세요. 사용자가 해당 상호 작용을 요청했거나, 상호 작용 자체가 테스트 중이거나, 신뢰할 수 있는 동등 항목이 알려지지 않은 경우에만 페이지 컨트롤을 사용하세요. 취약한 경로를 절대 임의로 만들지 마세요.
이미 충족된 사후 조건을 완료된 작업으로 처리하세요. 필요한 값이 이미 보일 수 있는 컨트롤을 조작하기 전에, 해당 상태를 결정하는 데 필요한 최소한의 읽기만 수행하세요. 일치하면 편집기를 열거나, 상호 작용을 재생하거나, 다시 읽지 마세요. 충족되지 않은 나머지 결과로 직접 계속 진행하세요. "설정", "선택" 또는 "확인"과 같은 단어는 사용자가 명시적으로 전환을 요구하거나 상호 작용 자체가 테스트 중이지 않는 한 필요한 최종 상태를 설명합니다.
브라우저 작업을 터미널 완료와 분리하세요. useOrCreate는 하나의 사용자 목표를 시작하거나 재개합니다. 반환된 task.id를 반환하고, 해당 목표가 종료될 때까지 동일한 id 또는 정확히 동일한 이름을 재사용하세요. 모든 예측 가능한 관찰, 동작, 대기, 추출 및 검증을 간결하게 유지하되, 목표가 충족되었는지 여부를 아직 결정 중인 Bash 호출에서 taskSpaces.complete(...)을 호출하지 마세요. 먼저 브라우저 작업을 완료하고 요청된 모든 결과와 필요한 범위 또는 적용 범위 경계가 입증되었음을 보여주는 증거를 출력하세요. 이전 출력을 검토한 후에만 전용 최종 Bash 호출이 원래 작업 공간을 완료할 수 있습니다. 이 호출은 page 또는 browser 작업을 수행하지 않습니다. 이 단일 수명 주기 커밋은 하나의 호출 기본값에 대한 예외이며, 브라우저 단계나 라운드가 아닙니다. 비어 있지 않거나 그럴듯한 부분 결과, 중단된 페이지, 소진된 재시도 또는 대체 시도는 완료 증거가 아닙니다. keep: true는 사용자를 위해 최종 결과를 보존합니다. 완료되지 않은 작업을 유지하지 않습니다.
현재 또는 상대 날짜 작업에 대한 시간 창을 고정하세요. 레코드를 수집하기 전에 사용자/작업 환경에서 "오늘/현재/최신"을 한 번 설정하거나 현재 페이지 상태를 명시적으로 확인하세요. 콘텐츠 타임스탬프를 시계가 아닌 데이터로 취급하세요. 스크롤, 가상화, 새로고침, 캐시 또는 변경된 결과 배치로 인해 드러난 오래된 레코드는 해당 앵커를 대체하지 않아야 합니다. 원래 창에 대해 레코드를 계속 평가하고, 관찰된 가장 최신 콘텐츠 날짜로 작업을 자동으로 재조정하지 마세요.
빠른 시작
이 스킬의 모든 예제는 의도적으로 복합적입니다. URL, 선택자 및 데이터를 사용자 작업에 맞게 조정하세요.
ego-browser nodejs <<'EOF'
const task = await taskSpaces.useOrCreate('inspect example page')
await browser.openOrReuseTab('https://example.com', { wait: true, timeout: 20000 })
const heading = await page.getByRole('heading').first().innerText()
const info = await page.info()
if (!heading || !('url' in info)) throw new Error('Example page was not ready')
const result = { taskSpaceId: task.id, heading, url: info.url }
console.log(JSON.stringify(result, null, 2))
EOF
작업이 완료될 때까지 모든 예측 가능한 작업을 스크립트 내에서 유지하세요. console.log(...)로 최종 결과를 출력합니다.
복합 패턴
추출, 선택, 탐색, 확인
목록 또는 검색 페이지에서 선택하기 전에 구조화된 후보를 추출하세요. 추출, 선택, 동작, 대기, 확인 및 정리를 하나의 Bash 호출에 유지하세요.
ego-browser nodejs <<'EOF'
const task = await taskSpaces.useOrCreate('compare search results')
await browser.openOrReuseTab('https://example.com/search?q=browser+automation', {
wait: true,
timeout: 20000,
})
const cards = page.locator('article')
const items = await cards.evaluateAll((nodes) =>
nodes.map((node) => ({
title: node.querySelector('h2')?.textContent?.trim(),
href: node.querySelector('a')?.href,
})),
)
const chosenIndex = items.findIndex((item) => item.title && item.href)
if (chosenIndex < 0) throw new Error('No usable result: ' + JSON.stringify(items))
const before = await page.url()
const navigation = page.waitForURL((url) => url.href !== before, { timeout: 15000 })
await cards.nth(chosenIndex).getByRole('link').first().click()
if (!(await navigation)) throw new Error('Chosen result did not navigate')
const info = await page.info()
if (!('url' in info) || info.url === before) throw new Error('Navigation was not verified')
const result = { chosen: items[chosenIndex], opened: info.url }
console.log(JSON.stringify(result, null, 2))
EOF
입력, 트리거, 대기, 읽어오기
트리거하는 동작 전에 요청/응답 대기를 등록한 다음 클릭을 성공으로 간주하지 않고 결과 페이지 상태를 확인하세요.
ego-browser nodejs <<'EOF'
const task = await taskSpaces.useOrCreate('search orders')
await browser.openOrReuseTab('https://example.com/orders', { wait: true, timeout: 20000 })
const responsePromise = page.waitForResponse(
(response) => response.url().includes('/api/orders') && response.ok(),
{ timeout: 15000 },
)
await page.getByLabel('Search orders').fill('pending')
await page.getByRole('button', { name: /search/i }).click()
const response = await responsePromise
const rows = await page.locator('table tbody tr').allInnerTexts()
if (!rows.length) throw new Error('Search completed but returned no visible rows')
const result = { status: response.status(), rows }
console.log(JSON.stringify(result, null, 2))
EOF
탭 핸들 새로고침, 전환, 검사
targetId를 수명이 짧은 핸들로 취급하세요. 동일한 Bash 호출 내에서 이를 발견, 검증 및 사용하세요.
ego-browser nodejs <<'EOF'
const task = await taskSpaces.useOrCreate('review generated report')
const tabs = await browser.listTabs({ includeChrome: false })
const reportTab = tabs.find((tab) => tab.url.includes('/reports/'))
if (!reportTab?.targetId) throw new Error('Report tab not found: ' + JSON.stringify(tabs))
await browser.switchTab(reportTab.targetId)
const info = await page.info()
const heading = await page.getByRole('heading').first().innerText()
if (!('url' in info) || !info.url.includes('/reports/')) throw new Error('Wrong tab selected')
const result = { taskSpaceId: task.id, heading, url: info.url }
console.log(JSON.stringify(result, null, 2))
EOF
런타임 맵
page: 탐색 및 상태(goto,reload,url,title,info), 시맨틱 로케이터, 대기,snapshot,screenshot,screencast,evaluate,keyboard,mouse, 다운로드 및 이벤트 드레이닝.page.locator(selector): 체이닝 및 필터링;first/nth/last; 클릭, 호버,dragTo,scrollIntoViewIfNeeded, 폼, 키보드, 업로드, 상태 읽기, 컬렉션, 요소 평가, 스크린샷 및 대기 메서드.browser:listTabs,currentTab,switchTab,openOrReuseTab,closeTab,ensureRealTab,iframeTarget.taskSpaces:list,switch,new,useOrCreate,claim,complete,handOff,takeOver,waitForAgentControl.fetch.server는 Node 측 요청을 수행합니다.fetch.browser는 현재 페이지 오리진에서 요청을 수행합니다.cdp는 비상구로만 사용하세요.console.log는 출력 채널입니다. 정확한 시그니처가 명확하지 않은 경우console.log(help('page')),console.log(help('locator'))또는 다른help(name)호출을 사용하세요.
실행 규칙
page.url()은 ego-browser에서 비동기입니다. 항상await page.url()을 사용하세요.page.waitForURL(...)조건자는URL객체를 수신하므로url.href,url.pathname또는url.searchParams를 검사하세요. 기본적으로load를 기다립니다. 로드 전에 의도적으로 진행하는 경우에만waitUntil: 'commit'을 사용하세요.page.waitForURL,page.waitForLoadState,page.waitForSelector, 로케이터waitFor및page.waitForFunction은 시간 초과 시 falsy 값을 반환합니다. 계속 진행하기 전에 결과를 확인하거나 필요한 상태를 즉시 확인하세요.- 트리거하는 동작 전에 요청, 응답 또는 탐색 대기를 등록하세요. 상태 기반 대기를 선호하세요.
page.waitForTimeout(...)은 간단한 시각적 안정화를 위해서만 사용하고 2000ms 이하로 유지하세요. - 안정적인 시맨틱 로케이터를 선호하세요. 페이지 구조를 알 수 없는 경우
evaluateAll,allInnerTexts또는 다른 제한된 읽기로 관련 컨트롤 또는 후보를 한 번 수집하고, JavaScript에서 다음 동작을 파생한 다음 명령 전체에서 선택자 추측을 열거하는 대신 동일한 heredoc에서 계속 진행하세요. - 단일 요소 동작 및 필수 읽기(원시 CSS 및 원시
xpath=로케이터 포함)는 엄격하며 자동 대기합니다. 일치 항목이 0개인 경우 로케이터를 수정하기 전에 로드, 활성 탭 및 모달/오버레이 상태를 확인하세요. 여러 일치 항목이 있는 경우count()/allInnerTexts()를 검사하고, 시맨틱하게 또는filter(...)로 범위를 좁힌 다음 중복이 정당한지 확인한 후에만first()/nth()를 사용하세요. 성공적인 동작이 스크립트를 앞으로 나아가게 하세요. 분기를 결정할 때와 작업에 필요한 최종 사후 조건에 대해 한 번 상태를 읽고, 모든 동작 후에는 읽지 마세요. 이미 충족된 필수 상태는 재생할 필요가 없습니다. - 실패 시 전략을 실질적으로 변경하기 위해 한 번의 목표 지향적 관찰을 사용하세요. 거의 동일한 로케이터나 명령을 반복하지 마세요. 증거를 기반으로 안정적인 시맨틱, DOM 또는 시각적 경로로 전환하세요.
- 명시적으로 요청된 사용자에게 보이는 전환 및 중지 경계를 유지하세요. 필수 클릭이 현재 탭을 탐색하거나 다른 탭을 열 수 있는 경우, 한 번 클릭하고 동일한 스크립트에서
await page.url()과 새로고침된browser.listTabs()로 결과를 확인하세요. 대상이 알려져 있다는 이유만으로 클릭을 직접 탐색으로 대체하지 마세요. 필수 동작의 실패를 무시하지 마세요.
작업 공간 (Task Spaces)
작업 공간은 사용자의 로그인 상태를 상속하는 자체 탭이 있는 격리된 브라우징 컨텍스트입니다. 첫 번째 Bash 스크립트 시작 부분에서 taskSpaces.useOrCreate(nameOrId)로 한 번 선택하세요. 외부 종속성으로 인해 나중에 명령이 불가피한 경우, 계속하기 전에 반환된 동일한 숫자 task.id 또는 정확히 동일한 짧은 목표 이름을 선택하세요. 별도의 사용자 목표에 대해서만 새 공간을 만드세요. 설정을 다시 시작하는 대신 명령 전체에서 이미 확인된 사실을 유지하세요.
useOrCreate는 에이전트 소유 공간을 재사용하거나 생성합니다. 일치하는 공간이 사용자 소유인 경우, 공간을 클레임하지 않고 선택하므로 브라우저 작업이 사용자 제어 하드 스톱에 도달합니다. 거기서 작업하라는 명시적인 사용자 확인 후에 taskSpaces.list() → taskSpaces.claim(id) → browser.listTabs() → browser.switchTab(targetId)를 사용하세요.
각 공간에는 ownership: 'agent' | 'agentDelegatedToUser' | 'user'가 있습니다.
| 사용자 소유 공간에 대한 작업 | 동작 |
|---|---|
taskSpaces.switch | 에이전트 소유 공간만 전환하므로 오류 발생 |
taskSpaces.claim | 소유권을 에이전트로 이전하고 공간 선택 |
taskSpaces.handOff / complete(..., { keep: true }) | { done: false, skipped: 'user-owned' }와 함께 건너뜀 |
taskSpaces.complete(..., { keep: false }) | 클레임한 후 공간을 닫음 |
taskSpaces.takeOver / waitForAgentControl | 소유권 확인을 수행하지 않음 |
성공을 주장하기 전에 handOff 및 complete의 done 결과를 확인하세요.
완료를 브라우저 실행과 별개의 최종 커밋으로 취급하세요. 최종 URL, 값 및 기타 증거를 캡처하고 출력한 후 완료 없이 작업 Bash 호출을 종료하세요. 해당 출력을 검토하세요. 요청된 모든 사후 조건과 필요한 범위 또는 적용 범위 경계는 단지 그럴듯할 뿐만 아니라 입증되어야 합니다. 입증된 경우, 원래 id 또는 정확한 이름에 대해 taskSpaces.complete(nameOrId, { keep })을 최대 한 번 호출하고 done을 확인하며 page 또는 browser 작업을 수행하지 않는 전용 최종 Bash 호출을 한 번 실행하세요. 충족되지 않거나 입증되지 않은 것이 있으면 대신 동일한 원래 작업 공간에서 계속 진행하세요. 수정, 재시도 또는 이후 단계는 새로운 목표가 아닙니다. keep은 필수입니다. 기본값은 false입니다. 사용자가 완료된 페이지를 유지하도록 요청했거나, 해당 페이지에서 수동으로 작업해야 하거나, 결과를 URL, 파일, 아티팩트 또는 요약으로 전달할 수 없는 경우에만 true를 사용하세요. 임시 탭은 작업하면서 닫고 사용자에게 필요한 탭만 유지하세요.
targetId를 하드코딩하거나, 수동으로 복사하거나, id로 이름을 바꾸지 마세요. 현재 Bash 호출 내에서 이를 획득하고 사용하세요. 다른 명령이 정말로 필요한 경우, 전환하거나 닫기 전에 browser.listTabs()를 새로고침하고 find(...) 결과를 검증하세요. browser.iframeTarget(...)은 객체가 아닌 대상 ID 문자열 또는 null을 반환합니다.
제어 핸드오프
"사용자가 제어 중", "비활성" 또는 "할당되지 않음" 오류는 전체 작업에 대한 하드 스톱입니다. 재시도하거나, 우회하거나, taskSpaces.takeOver를 자동으로 호출하지 마세요. 사용자에게 물어보고 기다리세요.
로그인, 캡차 또는 기타 수동 단계의 경우, ego-lite 브라우저가 있는 에이전트 공간에서 작업을 완료하세요. 헬퍼 이름을 발명하지 마세요. page.getByRole, page.getByLabel, page.getByText, page.getByPlaceholder, page.getByTitle, page.getByAltText, page.locator('data-testid=...')와 같은 Playwright 스타일의 시맨틱 로케이터를 사용하세요. 자리 표시자 또는 텍스트가 없는 빈 아이콘 버튼과 같은 엣지 케이스의 경우, page.locator('button:has(svg)') 또는 page.locator('[aria-label="..."]')로 전환하세요. 가능한 경우 일치하는 레이블을 선호하세요.
taskSpaces.complete("...", { keep: true })는 작업 공간을 보존하고, 소유권을 agentDelegatedToUser로 설정한 다음 사용자에게 알리고 반환합니다. 새 브라우저 관련 명령이 없습니다. 완료 확인 후 프롬프트를 종료하세요. keep: false는 공간을 닫고 모든 탭을 해제합니다.
알려진 한계
- 로그인 상태는
taskSpaces를 통해 안정적으로 유지됩니다. 사용자가 ego-browser가 시작된 동일한 브라우저의 다른 공간에서 수동으로 로그인하는 경우 공유됩니다. - ego-lite는
help('cdp')로 접근 가능한 제한된cdp노출을 지원하며,Page.printToPDF와 같은 작업을 위한 비상구입니다. page.info()는url,title,viewport및readyState를 포함하는 객체를 반환합니다.console.log는 ego-browser에서 유일한 출력 채널입니다.- ego-lite는 항상 현재 사용 가능한
ego-browser버전(최신 Chromium 포함)을 실행합니다. 버전 고정은 지원되지 않습니다. timeout값은 ms 단위입니다. 기본값은 구성 가능하지만 일반적으로 30초입니다.browser.closeTab은page의 현재 탭을 닫습니다. 특정 탭을 닫으려면 먼저browser.switchTab(targetId)를 사용하세요.
ego-browser nodejs <<'EOF'
const task = await taskSpaces.useOrCreate('check state')
await browser.openOrReuseTab('about:blank')
const info = await page.info()
console.log('info:', JSON.stringify(info))
// 도움말
console.log(help('page'))
EOF