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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "traw",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "module": "src/index.ts",
5
5
  "type": "module",
6
6
  "bin": {
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
- git clone https://github.com/zarazaex69/traw
34
- cd traw
35
- bun install
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 execute browser agent with a goal
14
- auth register new account and set as default
15
- upd check for updates
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 (glm-4-flash, no thinking)
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: glm-4.7)
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: "glm-4.7",
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("registering new account...")
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 resp = await fetch(`${moUrl}/auth/register`, {
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; active: boolean }
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 args = process.argv.slice(2)
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 (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
189
+ if (positionals.length === 0 || values.help) {
106
190
  printHelp()
107
191
  return
108
192
  }
109
193
 
110
- const cmd = args[0]
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
- // handle update check command
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
- let moUrl = defaultConfig.moUrl
142
- for (let i = 1; i < args.length; i++) {
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
- await registerAccount(moUrl)
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 config = { ...defaultConfig }
169
- const goalParts: string[] = []
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
- for (let i = 1; i < args.length; i++) {
172
- const arg = args[i]
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
- if (arg === "--headless") {
175
- config.headless = true
176
- continue
177
- }
178
- if (arg === "--no-headless" || arg === "--headed") {
179
- config.headless = false
180
- continue
181
- }
182
- if (arg === "--video") {
183
- config.recordVideo = true
184
- continue
185
- }
186
- if (arg === "--fast") {
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 = goalParts.join(" ")
298
+ const goal = positionals.slice(1).join(" ")
225
299
  if (!goal) {
226
- log.error("provide a goal: bun run traw run \"your 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
- api: config.apiUrl || config.moUrl,
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
  }