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.
- package/README.md +5 -0
- package/cli.js +92 -1
- 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 (
|
|
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:' })
|