traw 0.2.5 → 0.2.7
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.
- package/package.json +1 -1
- package/readme.md +4 -4
- package/src/cli/help.ts +18 -9
- package/src/cli/index.ts +154 -80
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -30,12 +30,12 @@ Traw is a simple and fast neuro agent that browses the internet instead of you
|
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
32
|
# install the traw
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
bun add -g traw
|
|
34
|
+
# or
|
|
35
|
+
npm i -g traw
|
|
36
36
|
|
|
37
37
|
# auth the traw
|
|
38
|
-
bun run traw auth
|
|
38
|
+
bun run traw auth glm
|
|
39
39
|
# mo server not found. install mo? [Y/n] Y
|
|
40
40
|
```
|
|
41
41
|
<div align="center">
|
package/src/cli/help.ts
CHANGED
|
@@ -6,32 +6,41 @@ traw v${VERSION} - AI browser agent
|
|
|
6
6
|
|
|
7
7
|
Usage:
|
|
8
8
|
traw run "your goal here"
|
|
9
|
-
traw auth
|
|
9
|
+
traw auth <provider>
|
|
10
10
|
traw upd
|
|
11
11
|
|
|
12
12
|
Commands:
|
|
13
|
-
run
|
|
14
|
-
auth
|
|
15
|
-
|
|
13
|
+
run execute browser agent with a goal
|
|
14
|
+
auth qwen register qwen account (chat.qwen.ai)
|
|
15
|
+
auth glm register glm account (z.ai)
|
|
16
|
+
auth list [provider] list tokens (all or by provider)
|
|
17
|
+
auth activate <id> activate token by id
|
|
18
|
+
upd check for updates
|
|
19
|
+
|
|
20
|
+
Models:
|
|
21
|
+
qwen: coder-model (default), vision-model (--fast)
|
|
22
|
+
glm: GLM-4-Plus, GLM-4-Flash, GLM-4-Air, GLM-4-6-API-V1
|
|
16
23
|
|
|
17
24
|
Options:
|
|
18
|
-
--fast use fast model (
|
|
25
|
+
--fast use fast model (vision-model for qwen, GLM-4-Flash for glm)
|
|
19
26
|
--headless run without visible browser (default)
|
|
20
27
|
--headed show browser window
|
|
21
28
|
--video enable video recording
|
|
22
|
-
--vision send screenshots to AI (visual mode)
|
|
23
29
|
--steps=N max steps (default: 20)
|
|
24
30
|
--mo=URL mo server url (default: http://localhost:8804)
|
|
25
31
|
--api=URL custom OpenAI-compatible API url (bypasses mo)
|
|
26
32
|
--api-key=KEY API key for custom endpoint (or use OPENAI_API_KEY env)
|
|
27
|
-
--model=NAME model name (default:
|
|
33
|
+
--model=NAME model name (default: coder-model)
|
|
28
34
|
-v, --version show version
|
|
29
35
|
|
|
30
36
|
Examples:
|
|
31
|
-
traw auth
|
|
37
|
+
traw auth qwen
|
|
38
|
+
traw auth glm
|
|
39
|
+
traw auth list
|
|
40
|
+
traw auth activate abc123
|
|
32
41
|
traw run "find the weather in Moscow"
|
|
42
|
+
traw run --model=GLM-4-Plus "search for news"
|
|
33
43
|
traw run --fast "quick search for bun.js"
|
|
34
44
|
traw run --video "search for documentation"
|
|
35
|
-
traw run --api=https://api.openai.com --model=gpt-4o "search for news"
|
|
36
45
|
`)
|
|
37
46
|
}
|
package/src/cli/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
import { parseArgs } from "util"
|
|
2
3
|
import { Agent } from "../agent/agent"
|
|
3
4
|
import type { AgentConfig } from "../types"
|
|
4
5
|
import { log, setSilent } from "../utils/log"
|
|
@@ -9,11 +10,34 @@ import { checkFirstRun, markFirstRunDone, showStarBanner } from "../utils/first-
|
|
|
9
10
|
|
|
10
11
|
const DEFAULT_MO_PORT = 8804
|
|
11
12
|
|
|
13
|
+
const cliOptions = {
|
|
14
|
+
help: { type: "boolean" as const, short: "h" as const },
|
|
15
|
+
version: { type: "boolean" as const, short: "v" as const },
|
|
16
|
+
headless: { type: "boolean" as const },
|
|
17
|
+
headed: { type: "boolean" as const },
|
|
18
|
+
video: { type: "boolean" as const },
|
|
19
|
+
fast: { type: "boolean" as const },
|
|
20
|
+
debug: { type: "boolean" as const },
|
|
21
|
+
json: { type: "boolean" as const },
|
|
22
|
+
steps: { type: "string" as const },
|
|
23
|
+
mo: { type: "string" as const },
|
|
24
|
+
api: { type: "string" as const },
|
|
25
|
+
"api-key": { type: "string" as const },
|
|
26
|
+
model: { type: "string" as const },
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type Provider = "qwen" | "glm"
|
|
30
|
+
|
|
31
|
+
const providerModels: Record<Provider, { default: string; fast: string }> = {
|
|
32
|
+
qwen: { default: "coder-model", fast: "vision-model" },
|
|
33
|
+
glm: { default: "GLM-4-Plus", fast: "GLM-4-Flash" },
|
|
34
|
+
}
|
|
35
|
+
|
|
12
36
|
const defaultConfig: AgentConfig = {
|
|
13
37
|
moUrl: `http://localhost:${DEFAULT_MO_PORT}`,
|
|
14
38
|
apiUrl: undefined,
|
|
15
39
|
apiKey: process.env.OPENAI_API_KEY || process.env.API_KEY,
|
|
16
|
-
model: "
|
|
40
|
+
model: "coder-model",
|
|
17
41
|
thinking: true,
|
|
18
42
|
headless: true,
|
|
19
43
|
recordVideo: false,
|
|
@@ -31,12 +55,10 @@ async function prompt(question: string): Promise<string> {
|
|
|
31
55
|
}
|
|
32
56
|
|
|
33
57
|
async function ensureMo(moUrl: string): Promise<boolean> {
|
|
34
|
-
// check if mo is already running
|
|
35
58
|
if (await pingMo(moUrl)) {
|
|
36
59
|
return true
|
|
37
60
|
}
|
|
38
61
|
|
|
39
|
-
// mo not running, check if installed
|
|
40
62
|
const installed = await isMoInstalled()
|
|
41
63
|
|
|
42
64
|
if (!installed) {
|
|
@@ -54,7 +76,6 @@ async function ensureMo(moUrl: string): Promise<boolean> {
|
|
|
54
76
|
}
|
|
55
77
|
}
|
|
56
78
|
|
|
57
|
-
// start mo
|
|
58
79
|
try {
|
|
59
80
|
const port = parseInt(new URL(moUrl).port) || DEFAULT_MO_PORT
|
|
60
81
|
await startMo(port)
|
|
@@ -65,11 +86,13 @@ async function ensureMo(moUrl: string): Promise<boolean> {
|
|
|
65
86
|
}
|
|
66
87
|
}
|
|
67
88
|
|
|
68
|
-
async function registerAccount(moUrl: string): Promise<void> {
|
|
69
|
-
log.info(
|
|
89
|
+
async function registerAccount(moUrl: string, provider: Provider): Promise<void> {
|
|
90
|
+
log.info(`registering new ${provider} account...`)
|
|
70
91
|
log.info("browser will open for captcha solving")
|
|
71
92
|
|
|
72
|
-
const
|
|
93
|
+
const endpoint = provider === "qwen" ? "/auth/qwen/register" : "/auth/glm/register"
|
|
94
|
+
|
|
95
|
+
const resp = await fetch(`${moUrl}${endpoint}`, {
|
|
73
96
|
method: "POST",
|
|
74
97
|
headers: { "Content-Type": "application/json" },
|
|
75
98
|
})
|
|
@@ -81,7 +104,7 @@ async function registerAccount(moUrl: string): Promise<void> {
|
|
|
81
104
|
|
|
82
105
|
const data = await resp.json() as {
|
|
83
106
|
success: boolean
|
|
84
|
-
token: { id: string; email: string;
|
|
107
|
+
token: { id: string; email: string; is_active: boolean }
|
|
85
108
|
}
|
|
86
109
|
|
|
87
110
|
if (!data.success) {
|
|
@@ -90,32 +113,91 @@ async function registerAccount(moUrl: string): Promise<void> {
|
|
|
90
113
|
|
|
91
114
|
log.success(`registered: ${data.token.email}`)
|
|
92
115
|
log.info(`token id: ${data.token.id}`)
|
|
116
|
+
log.info(`provider: ${provider}`)
|
|
93
117
|
log.info("token is now active and ready to use")
|
|
94
118
|
}
|
|
95
119
|
|
|
120
|
+
async function listTokens(moUrl: string, provider: Provider): Promise<void> {
|
|
121
|
+
const endpoint = `/auth/${provider}/tokens`
|
|
122
|
+
|
|
123
|
+
const resp = await fetch(`${moUrl}${endpoint}`)
|
|
124
|
+
if (!resp.ok) {
|
|
125
|
+
throw new Error(`failed to list tokens: ${resp.status}`)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const data = await resp.json() as {
|
|
129
|
+
tokens: Array<{
|
|
130
|
+
id: string
|
|
131
|
+
email: string
|
|
132
|
+
provider: string
|
|
133
|
+
is_active: boolean
|
|
134
|
+
created_at: string
|
|
135
|
+
}>
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!data.tokens || data.tokens.length === 0) {
|
|
139
|
+
log.info(`no ${provider} tokens found`)
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
console.log()
|
|
144
|
+
log.info(`${provider} tokens:`)
|
|
145
|
+
for (const t of data.tokens) {
|
|
146
|
+
const active = t.is_active ? " [active]" : ""
|
|
147
|
+
console.log(` ${t.id} - ${t.email}${active}`)
|
|
148
|
+
}
|
|
149
|
+
console.log()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function activateToken(moUrl: string, tokenId: string): Promise<void> {
|
|
153
|
+
const providers: Provider[] = ["qwen", "glm"]
|
|
154
|
+
|
|
155
|
+
for (const provider of providers) {
|
|
156
|
+
const resp = await fetch(`${moUrl}/auth/${provider}/tokens/${tokenId}/activate`, {
|
|
157
|
+
method: "POST",
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
if (resp.ok) {
|
|
161
|
+
log.success(`token ${tokenId} activated`)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
throw new Error(`token ${tokenId} not found`)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function detectProviderFromModel(model: string): Provider {
|
|
170
|
+
if (model.startsWith("coder-") || model.startsWith("vision-")) {
|
|
171
|
+
return "qwen"
|
|
172
|
+
}
|
|
173
|
+
return "glm"
|
|
174
|
+
}
|
|
175
|
+
|
|
96
176
|
async function main() {
|
|
97
|
-
const
|
|
177
|
+
const { values, positionals } = parseArgs({
|
|
178
|
+
args: Bun.argv.slice(2),
|
|
179
|
+
options: cliOptions,
|
|
180
|
+
allowPositionals: true,
|
|
181
|
+
strict: false,
|
|
182
|
+
})
|
|
98
183
|
|
|
99
|
-
// show star banner on first run (non-blocking)
|
|
100
184
|
if (await checkFirstRun()) {
|
|
101
185
|
showStarBanner()
|
|
102
186
|
await markFirstRunDone()
|
|
103
187
|
}
|
|
104
188
|
|
|
105
|
-
if (
|
|
189
|
+
if (positionals.length === 0 || values.help) {
|
|
106
190
|
printHelp()
|
|
107
191
|
return
|
|
108
192
|
}
|
|
109
193
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// handle version command
|
|
113
|
-
if (cmd === "--version" || cmd === "-v") {
|
|
194
|
+
if (values.version) {
|
|
114
195
|
console.log(`traw ${VERSION}`)
|
|
115
196
|
return
|
|
116
197
|
}
|
|
117
198
|
|
|
118
|
-
|
|
199
|
+
const cmd = positionals[0]
|
|
200
|
+
|
|
119
201
|
if (cmd === "upd" || cmd === "update") {
|
|
120
202
|
log.info(`current version: ${VERSION}`)
|
|
121
203
|
log.info("checking for updates...")
|
|
@@ -136,21 +218,42 @@ async function main() {
|
|
|
136
218
|
return
|
|
137
219
|
}
|
|
138
220
|
|
|
139
|
-
// handle auth command
|
|
140
221
|
if (cmd === "auth") {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (args[i].startsWith("--mo=")) {
|
|
144
|
-
moUrl = args[i].split("=")[1]
|
|
145
|
-
}
|
|
146
|
-
}
|
|
222
|
+
const moUrl = typeof values.mo === "string" ? values.mo : defaultConfig.moUrl
|
|
223
|
+
const subCmd = positionals[1]
|
|
147
224
|
|
|
148
225
|
if (!await ensureMo(moUrl)) {
|
|
149
226
|
process.exit(1)
|
|
150
227
|
}
|
|
151
228
|
|
|
152
229
|
try {
|
|
153
|
-
|
|
230
|
+
if (subCmd === "qwen") {
|
|
231
|
+
await registerAccount(moUrl, "qwen")
|
|
232
|
+
} else if (subCmd === "glm") {
|
|
233
|
+
await registerAccount(moUrl, "glm")
|
|
234
|
+
} else if (subCmd === "list") {
|
|
235
|
+
const provider = positionals[2] as Provider | undefined
|
|
236
|
+
if (provider && (provider === "qwen" || provider === "glm")) {
|
|
237
|
+
await listTokens(moUrl, provider)
|
|
238
|
+
} else {
|
|
239
|
+
await listTokens(moUrl, "qwen")
|
|
240
|
+
await listTokens(moUrl, "glm")
|
|
241
|
+
}
|
|
242
|
+
} else if (subCmd === "activate") {
|
|
243
|
+
const tokenId = positionals[2]
|
|
244
|
+
if (!tokenId) {
|
|
245
|
+
log.error("provide token id: traw auth activate <id>")
|
|
246
|
+
process.exit(1)
|
|
247
|
+
}
|
|
248
|
+
await activateToken(moUrl, tokenId)
|
|
249
|
+
} else {
|
|
250
|
+
log.error("usage: traw auth <qwen|glm|list|activate>")
|
|
251
|
+
log.info(" traw auth qwen - register qwen account")
|
|
252
|
+
log.info(" traw auth glm - register glm/z.ai account")
|
|
253
|
+
log.info(" traw auth list - list all tokens")
|
|
254
|
+
log.info(" traw auth activate <id> - activate token")
|
|
255
|
+
process.exit(1)
|
|
256
|
+
}
|
|
154
257
|
} catch (err: any) {
|
|
155
258
|
log.error(err.message)
|
|
156
259
|
process.exit(1)
|
|
@@ -165,82 +268,53 @@ async function main() {
|
|
|
165
268
|
process.exit(1)
|
|
166
269
|
}
|
|
167
270
|
|
|
168
|
-
const
|
|
169
|
-
const
|
|
271
|
+
const moUrl = typeof values.mo === "string" ? values.mo : defaultConfig.moUrl
|
|
272
|
+
const apiUrl = typeof values.api === "string" ? values.api : undefined
|
|
273
|
+
const apiKey = typeof values["api-key"] === "string" ? values["api-key"] : defaultConfig.apiKey
|
|
170
274
|
|
|
171
|
-
|
|
172
|
-
|
|
275
|
+
let model: string
|
|
276
|
+
if (typeof values.model === "string") {
|
|
277
|
+
model = values.model
|
|
278
|
+
} else {
|
|
279
|
+
const provider: Provider = "qwen"
|
|
280
|
+
model = values.fast ? providerModels[provider].fast : providerModels[provider].default
|
|
281
|
+
}
|
|
173
282
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
config.model = "0727-106B-API"
|
|
188
|
-
config.thinking = false
|
|
189
|
-
continue
|
|
190
|
-
}
|
|
191
|
-
if (arg === "--debug") {
|
|
192
|
-
config.debug = true
|
|
193
|
-
continue
|
|
194
|
-
}
|
|
195
|
-
if (arg === "--json") {
|
|
196
|
-
config.jsonOutput = true
|
|
197
|
-
continue
|
|
198
|
-
}
|
|
199
|
-
if (arg.startsWith("--steps=")) {
|
|
200
|
-
config.maxSteps = parseInt(arg.split("=")[1])
|
|
201
|
-
continue
|
|
202
|
-
}
|
|
203
|
-
if (arg.startsWith("--mo=")) {
|
|
204
|
-
config.moUrl = arg.split("=")[1]
|
|
205
|
-
continue
|
|
206
|
-
}
|
|
207
|
-
if (arg.startsWith("--api=")) {
|
|
208
|
-
config.apiUrl = arg.split("=")[1]
|
|
209
|
-
continue
|
|
210
|
-
}
|
|
211
|
-
if (arg.startsWith("--api-key=")) {
|
|
212
|
-
config.apiKey = arg.split("=")[1]
|
|
213
|
-
continue
|
|
214
|
-
}
|
|
215
|
-
if (arg.startsWith("--model=")) {
|
|
216
|
-
config.model = arg.split("=")[1]
|
|
217
|
-
continue
|
|
218
|
-
}
|
|
219
|
-
if (!arg.startsWith("--")) {
|
|
220
|
-
goalParts.push(arg)
|
|
221
|
-
}
|
|
283
|
+
const steps = typeof values.steps === "string" ? parseInt(values.steps) : defaultConfig.maxSteps
|
|
284
|
+
|
|
285
|
+
const config: AgentConfig = {
|
|
286
|
+
moUrl,
|
|
287
|
+
apiUrl,
|
|
288
|
+
apiKey,
|
|
289
|
+
model,
|
|
290
|
+
thinking: values.fast !== true,
|
|
291
|
+
headless: values.headed === true ? false : (values.headless === true || defaultConfig.headless),
|
|
292
|
+
recordVideo: values.video === true,
|
|
293
|
+
maxSteps: steps,
|
|
294
|
+
debug: values.debug === true,
|
|
295
|
+
jsonOutput: values.json === true,
|
|
222
296
|
}
|
|
223
297
|
|
|
224
|
-
const goal =
|
|
298
|
+
const goal = positionals.slice(1).join(" ")
|
|
225
299
|
if (!goal) {
|
|
226
|
-
log.error("provide a goal:
|
|
300
|
+
log.error("provide a goal: traw run \"your goal\"")
|
|
227
301
|
process.exit(1)
|
|
228
302
|
}
|
|
229
303
|
|
|
230
304
|
if (!config.jsonOutput) {
|
|
231
305
|
log.header(goal)
|
|
232
306
|
log.config({
|
|
233
|
-
|
|
307
|
+
mo: config.apiUrl || config.moUrl,
|
|
234
308
|
model: config.model,
|
|
235
309
|
headless: config.headless,
|
|
236
310
|
video: config.recordVideo,
|
|
311
|
+
vision: false,
|
|
237
312
|
steps: config.maxSteps,
|
|
238
313
|
})
|
|
239
314
|
} else {
|
|
240
315
|
setSilent(true)
|
|
241
316
|
}
|
|
242
317
|
|
|
243
|
-
// skip mo setup if using custom api
|
|
244
318
|
if (!config.apiUrl && !await ensureMo(config.moUrl)) {
|
|
245
319
|
process.exit(1)
|
|
246
320
|
}
|