traw 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,63 @@
1
+ name: Release Traw
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ permissions:
9
+ contents: write
10
+
11
+ jobs:
12
+ publish-npm:
13
+ name: Publish to NPM
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v4
18
+
19
+ - name: Setup Node
20
+ uses: actions/setup-node@v4
21
+ with:
22
+ node-version: '20'
23
+ registry-url: 'https://registry.npmjs.org'
24
+
25
+ - name: Get version
26
+ id: version
27
+ run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
28
+
29
+ - name: Update package version
30
+ run: |
31
+ npm version ${{ steps.version.outputs.VERSION }} --no-git-tag-version --allow-same-version
32
+
33
+ - name: Publish to NPM
34
+ run: npm publish --access public
35
+ env:
36
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
37
+
38
+ - name: Create GitHub Release
39
+ uses: softprops/action-gh-release@v1
40
+ with:
41
+ name: Traw v${{ steps.version.outputs.VERSION }}
42
+ body: |
43
+ ## Traw v${{ steps.version.outputs.VERSION }}
44
+
45
+ ### Installation
46
+
47
+ ```bash
48
+ # npm
49
+ npm install traw
50
+
51
+ # bun
52
+ bun add traw
53
+ ```
54
+
55
+ ### Usage
56
+
57
+ ```bash
58
+ bun run traw
59
+ ```
60
+ draft: false
61
+ prerelease: false
62
+ env:
63
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
package/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "mo"]
2
+ path = mo
3
+ url = https://github.com/zarazaex69/mo.git
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ <div align="center">
2
+ <img src="assets/logo.png" alt="Traw" width="300"/>
3
+ </div>
4
+
5
+ <div align="center">
6
+
7
+ ![Bun](https://img.shields.io/badge/-Bun-0D1117?style=flat-square&logo=Bun&logoColor=F3E6D8)
8
+ ![License](https://img.shields.io/badge/license-BSD--2--Clause-0D1117?style=flat-square&logo=open-source-initiative&logoColor=green&labelColor=0D1117)
9
+
10
+ </div>
11
+
12
+ ## About
13
+
14
+ Traw is a simple and fast neuro agent that browses the internet instead of you
15
+
16
+ ## Fast Start
17
+
18
+ ```bash
19
+ # start the traw
20
+ bun run traw run "your goal" // re.. goal -> you real goal... not mock... you get the idea...
21
+ ```
22
+
23
+ <div align="center">
24
+
25
+ ---
26
+
27
+ ### Contact
28
+
29
+ Telegram: [zarazaex](https://t.me/zarazaexe)
30
+ <br>
31
+ Email: [zarazaex@tuta.io](mailto:zarazaex@tuta.io)
32
+ <br>
33
+ Site: [zarazaex.xyz](https://zarazaex.xyz)
34
+ <br>
35
+
36
+ </div>
Binary file
package/bun.lock ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "traw",
7
+ "dependencies": {
8
+ "playwright": "^1.40.0",
9
+ },
10
+ "devDependencies": {
11
+ "@types/bun": "latest",
12
+ },
13
+ "peerDependencies": {
14
+ "typescript": "^5",
15
+ },
16
+ },
17
+ },
18
+ "packages": {
19
+ "@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
20
+
21
+ "@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
22
+
23
+ "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
24
+
25
+ "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
26
+
27
+ "playwright": ["playwright@1.57.0", "", { "dependencies": { "playwright-core": "1.57.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw=="],
28
+
29
+ "playwright-core": ["playwright-core@1.57.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ=="],
30
+
31
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
32
+
33
+ "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
34
+ }
35
+ }
package/index.ts ADDED
@@ -0,0 +1 @@
1
+ console.log("Hello via Bun!");
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "traw",
3
+ "version": "0.1.0",
4
+ "module": "src/index.ts",
5
+ "type": "module",
6
+ "scripts": {
7
+ "traw": "bun run src/index.ts",
8
+ "postinstall": "bunx playwright install firefox"
9
+ },
10
+ "dependencies": {
11
+ "playwright": "^1.40.0"
12
+ },
13
+ "devDependencies": {
14
+ "@types/bun": "latest"
15
+ },
16
+ "peerDependencies": {
17
+ "typescript": "^5"
18
+ }
19
+ }
package/src/agent.ts ADDED
@@ -0,0 +1,173 @@
1
+ import type { Action, AgentConfig, AgentStep, ChatMessage, PageState } from "./types"
2
+ import { BrowserController } from "./browser"
3
+ import { MoClient } from "./mo-client"
4
+
5
+ const systemPrompt = `You are a browser automation agent. You see the page state and decide what to do next.
6
+
7
+ ACTIONS:
8
+ - goto: navigate to URL
9
+ - click: click element by CSS selector
10
+ - type: type text into input (selector + text)
11
+ - scroll: scroll up/down
12
+ - wait: wait 1s
13
+ - done: task complete, report results
14
+
15
+ CSS SELECTOR RULES (CRITICAL):
16
+ - Use ONLY valid CSS selectors
17
+ - IDs: #search_form_input
18
+ - Classes: .result__a or a.result__a
19
+ - Attributes: a[href*="github.com"] or input[name="q"]
20
+ - Tag + class: button.search-btn
21
+ - NEVER use parentheses in selectors like a(something) - this is INVALID
22
+ - NEVER invent class names - use only what you see in the DOM
23
+
24
+ IMPORTANT RULES:
25
+ - Follow your plan step by step
26
+ - Do NOT use "done" until you have FULLY completed ALL steps in your plan
27
+ - If you encounter an error, try alternative approach
28
+ - Actually visit pages and read content, don't guess from search results
29
+
30
+ Respond with JSON only:
31
+ {
32
+ "thought": "brief reasoning",
33
+ "action": {
34
+ "type": "click|type|goto|scroll|wait|done",
35
+ "selector": "#valid-css-selector",
36
+ "text": "for type/goto",
37
+ "direction": "up|down",
38
+ "reason": "why"
39
+ }
40
+ }
41
+
42
+ When ALL steps complete, use "done" with full results in "reason".`
43
+
44
+ const planningPrompt = `You are a planning agent. Create a step-by-step plan to accomplish the user's goal using a web browser.
45
+
46
+ Rules:
47
+ - Be specific about what to search, click, or navigate to
48
+ - Number each step
49
+ - Keep it concise (max 10 steps)
50
+ - Start from DuckDuckGo search page
51
+
52
+ Respond with a numbered plan only, no JSON.`
53
+
54
+ export class Agent {
55
+ private browser: BrowserController
56
+ private mo: MoClient
57
+ private config: AgentConfig
58
+ private history: AgentStep[] = []
59
+ private messages: ChatMessage[] = []
60
+ private plan = ""
61
+
62
+ constructor(config: AgentConfig) {
63
+ this.config = config
64
+ this.browser = new BrowserController(config)
65
+ this.mo = new MoClient(config.moUrl, config.model)
66
+ }
67
+
68
+ async run(goal: string): Promise<AgentStep[]> {
69
+ console.log(`\n[goal] ${goal}\n`)
70
+
71
+ console.log("[planning]...")
72
+ this.plan = await this.createPlan(goal)
73
+ console.log("\n" + this.plan + "\n")
74
+
75
+ await this.browser.launch()
76
+ await this.browser.execute({
77
+ type: "goto",
78
+ text: "https://html.duckduckgo.com/html/",
79
+ reason: "start page",
80
+ })
81
+
82
+ this.messages.push({ role: "system", content: systemPrompt })
83
+ this.messages.push({
84
+ role: "user",
85
+ content: `Your task: ${goal}\n\nYour plan:\n${this.plan}\n\nFollow this plan step by step. You are now on DuckDuckGo search.`,
86
+ })
87
+
88
+ try {
89
+ for (let step = 0; step < this.config.maxSteps; step++) {
90
+ const state = await this.browser.getState()
91
+
92
+ console.log(`\n--- step ${step + 1} ---`)
93
+ console.log(`url: ${state.url}`)
94
+ console.log(`title: ${state.title}`)
95
+
96
+ const decision = await this.think(state)
97
+
98
+ console.log(`thought: ${decision.thought}`)
99
+ console.log(`action: ${decision.action.type} - ${decision.action.reason}`)
100
+
101
+ const result = await this.browser.execute(decision.action)
102
+ console.log(`result: ${result}`)
103
+
104
+ this.history.push({
105
+ timestamp: Date.now(),
106
+ thought: decision.thought,
107
+ action: decision.action,
108
+ result,
109
+ })
110
+
111
+ if (decision.action.type === "done") {
112
+ console.log(`\n[complete]`)
113
+ break
114
+ }
115
+
116
+ await new Promise((r) => setTimeout(r, 500))
117
+ }
118
+ } finally {
119
+ const videoPath = await this.browser.close()
120
+ if (videoPath) {
121
+ console.log(`[video] ${videoPath}`)
122
+ }
123
+ }
124
+
125
+ return this.history
126
+ }
127
+
128
+ private async createPlan(goal: string): Promise<string> {
129
+ return this.mo.chat([
130
+ { role: "system", content: planningPrompt },
131
+ { role: "user", content: `Goal: ${goal}` },
132
+ ])
133
+ }
134
+
135
+ private async think(state: PageState): Promise<{ thought: string; action: Action }> {
136
+ const stateMsg = `Current page:
137
+ URL: ${state.url}
138
+ Title: ${state.title}
139
+
140
+ Interactive elements:
141
+ ${state.dom}
142
+
143
+ What's your next action?`
144
+
145
+ this.messages.push({ role: "user", content: stateMsg })
146
+
147
+ const response = await this.mo.chat(this.messages)
148
+
149
+ try {
150
+ let jsonStr = response
151
+
152
+ // extract json from markdown code block if present
153
+ const match = response.match(/```(?:json)?\s*([\s\S]*?)```/)
154
+ if (match) {
155
+ jsonStr = match[1]
156
+ }
157
+
158
+ const parsed = JSON.parse(jsonStr.trim())
159
+ this.messages.push({ role: "assistant", content: response })
160
+
161
+ return {
162
+ thought: parsed.thought || "thinking...",
163
+ action: parsed.action,
164
+ }
165
+ } catch {
166
+ console.error("failed to parse AI response:", response)
167
+ return {
168
+ thought: "couldn't parse response, waiting...",
169
+ action: { type: "wait", reason: "parse error" },
170
+ }
171
+ }
172
+ }
173
+ }
package/src/browser.ts ADDED
@@ -0,0 +1,197 @@
1
+ import { chromium, type Browser, type Page, type BrowserContext } from "playwright"
2
+ import { mkdir } from "node:fs/promises"
3
+ import type { Action, PageState, AgentConfig } from "./types"
4
+
5
+ export class BrowserController {
6
+ private browser: Browser | null = null
7
+ private context: BrowserContext | null = null
8
+ private page: Page | null = null
9
+ private config: AgentConfig
10
+
11
+ constructor(config: AgentConfig) {
12
+ this.config = config
13
+ }
14
+
15
+ async launch(): Promise<void> {
16
+ this.browser = await chromium.launch({
17
+ headless: this.config.headless,
18
+ })
19
+
20
+ const contextOpts: any = {
21
+ viewport: { width: 1280, height: 720 },
22
+ }
23
+
24
+ if (this.config.recordVideo) {
25
+ contextOpts.recordVideo = {
26
+ dir: "./traw-recordings",
27
+ size: { width: 1280, height: 720 },
28
+ }
29
+ }
30
+
31
+ this.context = await this.browser.newContext(contextOpts)
32
+ this.page = await this.context.newPage()
33
+ }
34
+
35
+ async close(): Promise<string | null> {
36
+ let videoPath: string | null = null
37
+
38
+ try {
39
+ if (this.page && this.config.recordVideo) {
40
+ await this.page.waitForTimeout(1000)
41
+
42
+ const video = this.page.video()
43
+ if (video) {
44
+ await this.page.close()
45
+ this.page = null
46
+
47
+ await mkdir("./traw-recordings", { recursive: true })
48
+
49
+ const filename = `traw-${Date.now()}.webm`
50
+ const savePath = `./traw-recordings/${filename}`
51
+
52
+ await video.saveAs(savePath)
53
+ videoPath = savePath
54
+ }
55
+ }
56
+ } catch (e: any) {
57
+ console.error("[video error]", e.message)
58
+ }
59
+
60
+ if (this.page) await this.page.close().catch(() => {})
61
+ if (this.context) await this.context.close().catch(() => {})
62
+ if (this.browser) await this.browser.close().catch(() => {})
63
+
64
+ return videoPath
65
+ }
66
+
67
+ async getState(): Promise<PageState> {
68
+ if (!this.page) throw new Error("browser not launched")
69
+
70
+ const url = this.page.url()
71
+ const title = await this.page.title()
72
+ const dom = await this.extractDom()
73
+
74
+ return { url, title, dom }
75
+ }
76
+
77
+ async execute(action: Action): Promise<string> {
78
+ if (!this.page) throw new Error("browser not launched")
79
+
80
+ try {
81
+ switch (action.type) {
82
+ case "goto":
83
+ await this.page.goto(action.text!, { waitUntil: "networkidle", timeout: 15000 })
84
+ return `navigated to ${action.text}`
85
+
86
+ case "click":
87
+ await this.page.click(action.selector!, { timeout: 10000 })
88
+ await this.page.waitForLoadState("networkidle", { timeout: 10000 }).catch(() => {})
89
+ return `clicked ${action.selector}`
90
+
91
+ case "type":
92
+ await this.page.fill(action.selector!, action.text!)
93
+ return `typed "${action.text}" into ${action.selector}`
94
+
95
+ case "scroll":
96
+ const delta = action.direction === "down" ? 500 : -500
97
+ await this.page.mouse.wheel(0, delta)
98
+ await this.page.waitForTimeout(300)
99
+ return `scrolled ${action.direction}`
100
+
101
+ case "wait":
102
+ await this.page.waitForTimeout(2000)
103
+ return "waited 2s"
104
+
105
+ case "screenshot":
106
+ const buf = await this.page.screenshot()
107
+ return `screenshot taken (${buf.length} bytes)`
108
+
109
+ case "done":
110
+ return "agent finished"
111
+
112
+ default:
113
+ return `unknown action: ${action.type}`
114
+ }
115
+ } catch (err: any) {
116
+ return `error: ${err.message}`
117
+ }
118
+ }
119
+
120
+ async screenshot(): Promise<Buffer> {
121
+ if (!this.page) throw new Error("browser not launched")
122
+ return this.page.screenshot()
123
+ }
124
+
125
+ // extract only interactive elements with valid css selectors
126
+ private async extractDom(): Promise<string> {
127
+ if (!this.page) return ""
128
+
129
+ return this.page.evaluate(() => {
130
+ const selectors = [
131
+ "a",
132
+ "button",
133
+ "input",
134
+ "textarea",
135
+ "select",
136
+ "[role='button']",
137
+ "[type='submit']",
138
+ ]
139
+ const elements: string[] = []
140
+
141
+ selectors.forEach((sel) => {
142
+ document.querySelectorAll(sel).forEach((el) => {
143
+ const tag = el.tagName.toLowerCase()
144
+ const text = (el as HTMLElement).innerText?.slice(0, 40)?.trim() || ""
145
+ const href = (el as HTMLAnchorElement).href || ""
146
+ const placeholder = (el as HTMLInputElement).placeholder || ""
147
+ const type = (el as HTMLInputElement).type || ""
148
+ const name = (el as HTMLInputElement).name || ""
149
+ const value = (el as HTMLInputElement).value || ""
150
+ const title = el.getAttribute("title") || ""
151
+ const ariaLabel = el.getAttribute("aria-label") || ""
152
+
153
+ // build css selector - try multiple strategies
154
+ let selector = ""
155
+
156
+ if (el.id) {
157
+ selector = `#${el.id}`
158
+ } else if (name) {
159
+ selector = `${tag}[name="${name}"]`
160
+ } else if (title) {
161
+ selector = `${tag}[title="${title}"]`
162
+ } else if (ariaLabel) {
163
+ selector = `${tag}[aria-label="${ariaLabel}"]`
164
+ } else if (type && tag === "input") {
165
+ selector = `input[type="${type}"]`
166
+ } else if (el.className && typeof el.className === "string") {
167
+ const cls = el.className.split(" ").filter((c) => c && !c.includes(":"))[0]
168
+ if (cls) selector = `${tag}.${cls}`
169
+ }
170
+
171
+ // fallback: nth-of-type
172
+ if (!selector && (tag === "button" || tag === "a")) {
173
+ const siblings = Array.from(document.querySelectorAll(tag))
174
+ const index = siblings.findIndex((s) => s === el) + 1
175
+ if (index > 0) selector = `${tag}:nth-of-type(${index})`
176
+ }
177
+
178
+ if (!selector) return
179
+
180
+ let desc = `[${selector}]`
181
+ if (text) desc += ` "${text}"`
182
+ if (value && !text) desc += ` value="${value}"`
183
+ if (href && !href.startsWith("javascript:")) desc += ` -> ${href.slice(0, 50)}`
184
+ if (placeholder) desc += ` placeholder="${placeholder}"`
185
+ if (title && !text) desc += ` title="${title}"`
186
+ if (ariaLabel && !text && !title) desc += ` aria="${ariaLabel}"`
187
+ if (type && type !== "submit" && type !== "text") desc += ` type=${type}`
188
+
189
+ elements.push(desc)
190
+ })
191
+ })
192
+
193
+ // dedupe and limit
194
+ return [...new Set(elements)].slice(0, 60).join("\n")
195
+ })
196
+ }
197
+ }
package/src/index.ts ADDED
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env bun
2
+ import { Agent } from "./agent"
3
+ import type { AgentConfig } from "./types"
4
+
5
+ const defaultConfig: AgentConfig = {
6
+ moUrl: "http://localhost:8080",
7
+ model: "glm-4.7",
8
+ headless: false,
9
+ recordVideo: true,
10
+ maxSteps: 20,
11
+ }
12
+
13
+ async function main() {
14
+ const args = process.argv.slice(2)
15
+
16
+ if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
17
+ printHelp()
18
+ return
19
+ }
20
+
21
+ const cmd = args[0]
22
+ if (cmd !== "run") {
23
+ console.error(`unknown command: ${cmd}`)
24
+ process.exit(1)
25
+ }
26
+
27
+ const config = { ...defaultConfig }
28
+ const goalParts: string[] = []
29
+
30
+ for (let i = 1; i < args.length; i++) {
31
+ const arg = args[i]
32
+
33
+ if (arg === "--headless") {
34
+ config.headless = true
35
+ continue
36
+ }
37
+ if (arg === "--no-video") {
38
+ config.recordVideo = false
39
+ continue
40
+ }
41
+ if (arg.startsWith("--steps=")) {
42
+ config.maxSteps = parseInt(arg.split("=")[1])
43
+ continue
44
+ }
45
+ if (arg.startsWith("--mo=")) {
46
+ config.moUrl = arg.split("=")[1]
47
+ continue
48
+ }
49
+ if (!arg.startsWith("--")) {
50
+ goalParts.push(arg)
51
+ }
52
+ }
53
+
54
+ const goal = goalParts.join(" ")
55
+ if (!goal) {
56
+ console.error("[error] provide a goal: bun run traw run \"your goal\"")
57
+ process.exit(1)
58
+ }
59
+
60
+ console.log("[traw] starting agent...")
61
+ console.log(` mo: ${config.moUrl}`)
62
+ console.log(` headless: ${config.headless}`)
63
+ console.log(` video: ${config.recordVideo}`)
64
+ console.log(` max steps: ${config.maxSteps}`)
65
+
66
+ const agent = new Agent(config)
67
+
68
+ try {
69
+ const history = await agent.run(goal)
70
+
71
+ console.log("\n[done] steps:", history.length)
72
+ if (history.length > 0) {
73
+ const last = history[history.length - 1]
74
+ console.log(" final:", last.action.type, "-", last.action.reason)
75
+ }
76
+ } catch (err: any) {
77
+ console.error("\n[error]", err.message)
78
+ process.exit(1)
79
+ }
80
+ }
81
+
82
+ function printHelp() {
83
+ console.log(`
84
+ 🌐 traw - AI browser agent
85
+
86
+ Usage:
87
+ traw run "your goal here"
88
+ traw run # interactive mode (coming soon)
89
+
90
+ Options:
91
+ --headless run without visible browser
92
+ --no-video disable video recording
93
+ --steps=N max steps (default: 20)
94
+ --mo=URL mo server url (default: http://localhost:8080)
95
+
96
+ Examples:
97
+ traw run "find the weather in Moscow"
98
+ traw run "search for bun.js documentation"
99
+ `)
100
+ }
101
+
102
+ main()
@@ -0,0 +1,35 @@
1
+ import type { ChatMessage } from "./types"
2
+
3
+ export class MoClient {
4
+ private url: string
5
+ private model: string
6
+
7
+ constructor(url: string, model: string) {
8
+ this.url = url
9
+ this.model = model
10
+ }
11
+
12
+ async chat(messages: ChatMessage[]): Promise<string> {
13
+ const resp = await fetch(`${this.url}/v1/chat/completions`, {
14
+ method: "POST",
15
+ headers: { "Content-Type": "application/json" },
16
+ body: JSON.stringify({
17
+ model: this.model,
18
+ messages,
19
+ stream: false,
20
+ thinking: false,
21
+ }),
22
+ })
23
+
24
+ if (!resp.ok) {
25
+ const body = await resp.text()
26
+ throw new Error(`mo error: ${resp.status} ${body}`)
27
+ }
28
+
29
+ const data = (await resp.json()) as {
30
+ choices: { message: { content: string } }[]
31
+ }
32
+
33
+ return data.choices[0]?.message?.content ?? ""
34
+ }
35
+ }
package/src/types.ts ADDED
@@ -0,0 +1,43 @@
1
+ export type ActionType =
2
+ | "click"
3
+ | "type"
4
+ | "scroll"
5
+ | "goto"
6
+ | "wait"
7
+ | "screenshot"
8
+ | "done"
9
+
10
+ export interface Action {
11
+ type: ActionType
12
+ selector?: string
13
+ text?: string
14
+ direction?: "up" | "down"
15
+ reason: string
16
+ }
17
+
18
+ export interface PageState {
19
+ url: string
20
+ title: string
21
+ dom: string
22
+ screenshot?: string
23
+ }
24
+
25
+ export interface AgentStep {
26
+ timestamp: number
27
+ thought: string
28
+ action: Action
29
+ result?: string
30
+ }
31
+
32
+ export interface AgentConfig {
33
+ moUrl: string
34
+ model: string
35
+ headless: boolean
36
+ recordVideo: boolean
37
+ maxSteps: number
38
+ }
39
+
40
+ export interface ChatMessage {
41
+ role: "system" | "user" | "assistant"
42
+ content: string
43
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext", "DOM"],
5
+ "target": "ESNext",
6
+ "module": "Preserve",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ // Bundler mode
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "noEmit": true,
16
+
17
+ // Best practices
18
+ "strict": true,
19
+ "skipLibCheck": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "noUncheckedIndexedAccess": false,
22
+ "noImplicitOverride": true,
23
+
24
+ // Some stricter flags (disabled by default)
25
+ "noUnusedLocals": false,
26
+ "noUnusedParameters": false,
27
+ "noPropertyAccessFromIndexSignature": false
28
+ }
29
+ }