open-wadah 1.0.2 → 1.0.3

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.
Files changed (3) hide show
  1. package/README.md +5 -0
  2. package/cli.js +92 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -20,6 +20,9 @@ wadah signup
20
20
  wadah login
21
21
  ```
22
22
 
23
+ `wadah login` uses browser sign-in by default.
24
+ Use `wadah login --password` for terminal email/password.
25
+
23
26
  3. Use the CLI:
24
27
 
25
28
  ```bash
@@ -50,3 +53,5 @@ Or pass per command:
50
53
  ```bash
51
54
  wadah --api http://127.0.0.1:3001 open
52
55
  ```
56
+
57
+ For production, use your real API URL (for example `https://api.openwadah.com`) instead of localhost.
package/cli.js CHANGED
@@ -5,6 +5,7 @@ import fetch from 'node-fetch'
5
5
  import Conf from 'conf'
6
6
  import { input, password as promptPassword } from '@inquirer/prompts'
7
7
  import { webcrypto } from 'node:crypto'
8
+ import { spawn } from 'node:child_process'
8
9
 
9
10
  const randomUUID = () => webcrypto.randomUUID()
10
11
  let runtimeApiBase = null
@@ -188,6 +189,31 @@ function fail(message) {
188
189
  process.exitCode = 1
189
190
  }
190
191
 
192
+ function sleep(ms) {
193
+ return new Promise((resolve) => setTimeout(resolve, ms))
194
+ }
195
+
196
+ function openBrowser(url) {
197
+ let cmd
198
+ let args
199
+ if (process.platform === 'darwin') {
200
+ cmd = 'open'
201
+ args = [url]
202
+ } else if (process.platform === 'win32') {
203
+ cmd = 'cmd'
204
+ args = ['/c', 'start', '', url]
205
+ } else {
206
+ cmd = 'xdg-open'
207
+ args = [url]
208
+ }
209
+ return new Promise((resolve) => {
210
+ const child = spawn(cmd, args, { stdio: 'ignore', detached: true })
211
+ child.on('error', () => resolve(false))
212
+ child.unref()
213
+ resolve(true)
214
+ })
215
+ }
216
+
191
217
  // ── program ───────────────────────────────────────────────────────────────────
192
218
 
193
219
  program
@@ -205,11 +231,76 @@ program.hook('preAction', (_, actionCommand) => {
205
231
 
206
232
  program
207
233
  .command('login')
208
- .description('Sign in as a human user (email + password)')
234
+ .description('Sign in as a human user (browser flow by default)')
209
235
  .option('--api-url <url>', 'Override API base URL and save it')
236
+ .option('--password', 'Use terminal email/password instead of browser login')
237
+ .option('--no-browser', "Don't auto-open browser; print URL only")
210
238
  .action(async (opts) => {
211
239
  if (opts.apiUrl) conf.set('api_url', opts.apiUrl.trim().replace(/\/$/, ''))
212
240
 
241
+ // Browser-based login (device flow) is default so password managers can autofill.
242
+ if (!opts.password) {
243
+ try {
244
+ const startRes = await fetch(`${getApiBase()}/api/auth/device/start`, {
245
+ method: 'POST',
246
+ headers: { 'Content-Type': 'application/json' },
247
+ body: JSON.stringify({ client: 'open-wadah-cli' }),
248
+ })
249
+ const startBody = await startRes.json().catch(() => ({}))
250
+ if (!startRes.ok) throw new Error(startBody.error ?? 'Could not start browser login')
251
+
252
+ const verificationUrl = startBody.verification_uri_complete ?? startBody.verification_uri
253
+ const pollEveryMs = Math.max(1000, Number(startBody.interval ?? 2) * 1000)
254
+ const deadline = Date.now() + Math.max(60, Number(startBody.expires_in ?? 600)) * 1000
255
+
256
+ console.log(chalk.bold('\nOpen Wadah — Browser sign in\n'))
257
+ console.log(chalk.gray(` 1) Open this URL and sign in:`))
258
+ console.log(` ${chalk.bold(verificationUrl)}`)
259
+ if (startBody.user_code) console.log(chalk.gray(` 2) If asked, enter code: ${startBody.user_code}`))
260
+ console.log(chalk.gray(' 3) Return here — login completes automatically.\n'))
261
+
262
+ if (opts.browser) {
263
+ const opened = await openBrowser(verificationUrl)
264
+ if (opened) console.log(chalk.gray(' Browser opened.\n'))
265
+ else console.log(chalk.yellow(' Could not open browser automatically. Open the URL manually.\n'))
266
+ }
267
+
268
+ while (Date.now() < deadline) {
269
+ const pollRes = await fetch(`${getApiBase()}/api/auth/device/poll`, {
270
+ method: 'POST',
271
+ headers: { 'Content-Type': 'application/json' },
272
+ body: JSON.stringify({ device_code: startBody.device_code }),
273
+ })
274
+ const pollBody = await pollRes.json().catch(() => ({}))
275
+
276
+ if (pollRes.ok && pollBody.access_token) {
277
+ conf.set('access_token', pollBody.access_token)
278
+ conf.set('refresh_token', pollBody.refresh_token)
279
+ conf.set('token_expires', pollBody.expires_at)
280
+ conf.set('user_email', pollBody.user?.email ?? null)
281
+ conf.set('user_id', pollBody.user?.id ?? null)
282
+ console.log(chalk.green(`\n✓ Signed in${pollBody.user?.email ? ` as ${pollBody.user.email}` : ''}\n`))
283
+ return
284
+ }
285
+
286
+ const pending = pollRes.status === 428 || pollBody.error === 'authorization_pending'
287
+ if (!pending) {
288
+ if (pollRes.status === 410 || pollBody.error === 'expired_token') {
289
+ throw new Error('Browser login expired. Please run wadah login again.')
290
+ }
291
+ throw new Error(pollBody.error ?? 'Browser login failed')
292
+ }
293
+ await sleep(pollEveryMs)
294
+ }
295
+
296
+ throw new Error('Browser login timed out. Please run wadah login again.')
297
+ } catch (err) {
298
+ console.error(chalk.red(`\n✗ ${err.message}\n`))
299
+ process.exit(1)
300
+ }
301
+ return
302
+ }
303
+
213
304
  console.log(chalk.bold('\nOpen Wadah — Sign in\n'))
214
305
  const email = await input({ message: 'Email:' })
215
306
  const pass = await promptPassword({ message: 'Password:' })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-wadah",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Open Wadah CLI — shared task board for humans and agents",
5
5
  "type": "module",
6
6
  "bin": {