traw 0.2.6 → 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.6",
3
+ "version": "0.2.7",
4
4
  "module": "src/index.ts",
5
5
  "type": "module",
6
6
  "bin": {
package/readme.md CHANGED
@@ -35,7 +35,7 @@ bun add -g traw
35
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
@@ -26,11 +26,18 @@ const cliOptions = {
26
26
  model: { type: "string" as const },
27
27
  }
28
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
+
29
36
  const defaultConfig: AgentConfig = {
30
37
  moUrl: `http://localhost:${DEFAULT_MO_PORT}`,
31
38
  apiUrl: undefined,
32
39
  apiKey: process.env.OPENAI_API_KEY || process.env.API_KEY,
33
- model: "glm-4.7",
40
+ model: "coder-model",
34
41
  thinking: true,
35
42
  headless: true,
36
43
  recordVideo: false,
@@ -48,12 +55,10 @@ async function prompt(question: string): Promise<string> {
48
55
  }
49
56
 
50
57
  async function ensureMo(moUrl: string): Promise<boolean> {
51
- // check if mo is already running
52
58
  if (await pingMo(moUrl)) {
53
59
  return true
54
60
  }
55
61
 
56
- // mo not running, check if installed
57
62
  const installed = await isMoInstalled()
58
63
 
59
64
  if (!installed) {
@@ -71,7 +76,6 @@ async function ensureMo(moUrl: string): Promise<boolean> {
71
76
  }
72
77
  }
73
78
 
74
- // start mo
75
79
  try {
76
80
  const port = parseInt(new URL(moUrl).port) || DEFAULT_MO_PORT
77
81
  await startMo(port)
@@ -82,11 +86,13 @@ async function ensureMo(moUrl: string): Promise<boolean> {
82
86
  }
83
87
  }
84
88
 
85
- async function registerAccount(moUrl: string): Promise<void> {
86
- log.info("registering new account...")
89
+ async function registerAccount(moUrl: string, provider: Provider): Promise<void> {
90
+ log.info(`registering new ${provider} account...`)
87
91
  log.info("browser will open for captcha solving")
88
92
 
89
- 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}`, {
90
96
  method: "POST",
91
97
  headers: { "Content-Type": "application/json" },
92
98
  })
@@ -98,7 +104,7 @@ async function registerAccount(moUrl: string): Promise<void> {
98
104
 
99
105
  const data = await resp.json() as {
100
106
  success: boolean
101
- token: { id: string; email: string; active: boolean }
107
+ token: { id: string; email: string; is_active: boolean }
102
108
  }
103
109
 
104
110
  if (!data.success) {
@@ -107,9 +113,66 @@ async function registerAccount(moUrl: string): Promise<void> {
107
113
 
108
114
  log.success(`registered: ${data.token.email}`)
109
115
  log.info(`token id: ${data.token.id}`)
116
+ log.info(`provider: ${provider}`)
110
117
  log.info("token is now active and ready to use")
111
118
  }
112
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
+
113
176
  async function main() {
114
177
  const { values, positionals } = parseArgs({
115
178
  args: Bun.argv.slice(2),
@@ -157,13 +220,40 @@ async function main() {
157
220
 
158
221
  if (cmd === "auth") {
159
222
  const moUrl = typeof values.mo === "string" ? values.mo : defaultConfig.moUrl
223
+ const subCmd = positionals[1]
160
224
 
161
225
  if (!await ensureMo(moUrl)) {
162
226
  process.exit(1)
163
227
  }
164
228
 
165
229
  try {
166
- 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
+ }
167
257
  } catch (err: any) {
168
258
  log.error(err.message)
169
259
  process.exit(1)
@@ -181,7 +271,15 @@ async function main() {
181
271
  const moUrl = typeof values.mo === "string" ? values.mo : defaultConfig.moUrl
182
272
  const apiUrl = typeof values.api === "string" ? values.api : undefined
183
273
  const apiKey = typeof values["api-key"] === "string" ? values["api-key"] : defaultConfig.apiKey
184
- const model = typeof values.model === "string" ? values.model : (values.fast ? "0727-106B-API" : defaultConfig.model)
274
+
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
+ }
282
+
185
283
  const steps = typeof values.steps === "string" ? parseInt(values.steps) : defaultConfig.maxSteps
186
284
 
187
285
  const config: AgentConfig = {
@@ -199,7 +297,7 @@ async function main() {
199
297
 
200
298
  const goal = positionals.slice(1).join(" ")
201
299
  if (!goal) {
202
- log.error("provide a goal: bun run traw run \"your goal\"")
300
+ log.error("provide a goal: traw run \"your goal\"")
203
301
  process.exit(1)
204
302
  }
205
303
 
@@ -217,7 +315,6 @@ async function main() {
217
315
  setSilent(true)
218
316
  }
219
317
 
220
- // skip mo setup if using custom api
221
318
  if (!config.apiUrl && !await ensureMo(config.moUrl)) {
222
319
  process.exit(1)
223
320
  }