uniweb 0.13.17 → 0.14.0

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 (65) hide show
  1. package/README.md +1 -1
  2. package/package.json +7 -7
  3. package/partials/agents.md +51 -0
  4. package/src/backend/client.js +173 -40
  5. package/src/backend/data-bundle.js +15 -3
  6. package/src/backend/foundation-bring-along.js +76 -21
  7. package/src/backend/payment-handoff.js +28 -9
  8. package/src/backend/site-media.js +147 -19
  9. package/src/backend/site-sync.js +362 -40
  10. package/src/commands/add.js +575 -273
  11. package/src/commands/build.js +160 -57
  12. package/src/commands/clone.js +79 -26
  13. package/src/commands/content.js +11 -7
  14. package/src/commands/deploy.js +80 -32
  15. package/src/commands/dev.js +39 -17
  16. package/src/commands/docs.js +44 -16
  17. package/src/commands/doctor.js +338 -82
  18. package/src/commands/export.js +15 -7
  19. package/src/commands/handoff.js +6 -2
  20. package/src/commands/i18n.js +248 -99
  21. package/src/commands/inspect.js +48 -19
  22. package/src/commands/invite.js +9 -3
  23. package/src/commands/org.js +19 -5
  24. package/src/commands/publish.js +229 -60
  25. package/src/commands/pull.js +157 -48
  26. package/src/commands/push.js +144 -42
  27. package/src/commands/refresh.js +37 -10
  28. package/src/commands/register.js +235 -64
  29. package/src/commands/rename.js +126 -46
  30. package/src/commands/runtime.js +74 -24
  31. package/src/commands/status.js +48 -15
  32. package/src/commands/sync.js +7 -2
  33. package/src/commands/template.js +12 -4
  34. package/src/commands/update.js +263 -87
  35. package/src/commands/validate.js +115 -32
  36. package/src/framework-index.json +15 -13
  37. package/src/index.js +298 -145
  38. package/src/templates/fetchers/github.js +7 -7
  39. package/src/templates/fetchers/npm.js +3 -3
  40. package/src/templates/fetchers/release.js +21 -18
  41. package/src/templates/index.js +34 -12
  42. package/src/templates/processor.js +44 -12
  43. package/src/templates/resolver.js +42 -15
  44. package/src/templates/validator.js +14 -7
  45. package/src/utils/asset-upload.js +90 -22
  46. package/src/utils/code-upload.js +62 -37
  47. package/src/utils/config.js +31 -10
  48. package/src/utils/dep-survey.js +33 -7
  49. package/src/utils/destination-prompt.js +27 -17
  50. package/src/utils/git.js +66 -22
  51. package/src/utils/host-prompt.js +4 -3
  52. package/src/utils/install-integrity.js +8 -4
  53. package/src/utils/interactive.js +3 -3
  54. package/src/utils/json-file.js +6 -2
  55. package/src/utils/names.js +15 -6
  56. package/src/utils/placement.js +16 -8
  57. package/src/utils/registry-auth.js +185 -57
  58. package/src/utils/registry-orgs.js +142 -53
  59. package/src/utils/runtime-upload.js +52 -14
  60. package/src/utils/scaffold.js +55 -14
  61. package/src/utils/site-content-refs.js +3 -1
  62. package/src/utils/update-check.js +43 -17
  63. package/src/utils/workspace.js +13 -7
  64. package/src/versions.js +18 -7
  65. package/templates/site/_gitignore +5 -0
@@ -37,7 +37,11 @@ export function getAuthDir() {
37
37
 
38
38
  /** Normalize a backend URL to a bare origin (for stamping on the session). */
39
39
  function normOrigin(u) {
40
- try { return new URL(u).origin } catch { return u }
40
+ try {
41
+ return new URL(u).origin
42
+ } catch {
43
+ return u
44
+ }
41
45
  }
42
46
 
43
47
  /** True when a stored session's `expiresAt` is in the past (absent → never expires). */
@@ -109,22 +113,31 @@ export async function loginToRegistry({ apiBase, username, password } = {}) {
109
113
  res = await fetch(url, {
110
114
  method: 'POST',
111
115
  headers: { 'Content-Type': 'application/json' },
112
- body: JSON.stringify({ username, password }),
116
+ body: JSON.stringify({ username, password })
113
117
  })
114
118
  } catch (err) {
115
- throw new Error(`Could not reach the login endpoint at ${url}: ${err.message}`)
119
+ throw new Error(
120
+ `Could not reach the login endpoint at ${url}: ${err.message}`
121
+ )
116
122
  }
117
123
 
118
124
  if (!res.ok) {
119
125
  let detail = ''
120
- try { detail = (await res.text()).slice(0, 300) } catch { /* ignore */ }
121
- const e = new Error(`Login failed: HTTP ${res.status} ${res.statusText}${detail ? ` — ${detail}` : ''}`)
126
+ try {
127
+ detail = (await res.text()).slice(0, 300)
128
+ } catch {
129
+ /* ignore */
130
+ }
131
+ const e = new Error(
132
+ `Login failed: HTTP ${res.status} ${res.statusText}${detail ? ` — ${detail}` : ''}`
133
+ )
122
134
  e.status = res.status
123
135
  throw e
124
136
  }
125
137
 
126
138
  const data = await res.json().catch(() => null)
127
- if (!data?.token) throw new Error('Login succeeded but the response carried no token.')
139
+ if (!data?.token)
140
+ throw new Error('Login succeeded but the response carried no token.')
128
141
 
129
142
  // New-backend login body (agreed with backend, 2026-05-26):
130
143
  // { token, expires_at, account: { uuid, username, handle } }
@@ -157,7 +170,11 @@ export async function loginToRegistry({ apiBase, username, password } = {}) {
157
170
  * @param {string[]} [options.args] - argv slice; checked for --non-interactive
158
171
  * @returns {Promise<string>} bearer token
159
172
  */
160
- export async function ensureRegistryAuth({ apiBase, command = 'This command', args = [] } = {}) {
173
+ export async function ensureRegistryAuth({
174
+ apiBase,
175
+ command = 'This command',
176
+ args = []
177
+ } = {}) {
161
178
  if (process.env.UNIWEB_TOKEN) return process.env.UNIWEB_TOKEN
162
179
 
163
180
  const stored = await readRegistryAuth()
@@ -167,24 +184,37 @@ export async function ensureRegistryAuth({ apiBase, command = 'This command', ar
167
184
  const envUser = process.env.UNIWEB_USERNAME
168
185
  const envPass = process.env.UNIWEB_PASSWORD
169
186
  if (envUser && envPass) {
170
- const record = await loginToRegistry({ apiBase, username: envUser, password: envPass })
187
+ const record = await loginToRegistry({
188
+ apiBase,
189
+ username: envUser,
190
+ password: envPass
191
+ })
171
192
  return record.token
172
193
  }
173
194
 
174
195
  const { isNonInteractive, getCliPrefix } = await import('./interactive.js')
175
196
  if (isNonInteractive(args)) {
176
197
  const prefix = getCliPrefix()
177
- const reason = stored && isExpired(stored) ? 'Session expired.' : 'Not logged in.'
178
- console.error(`\x1b[31m✗\x1b[0m ${reason} ${command} requires a Uniweb account, and the CLI is non-interactive (CI / no TTY / --non-interactive).`)
198
+ const reason =
199
+ stored && isExpired(stored) ? 'Session expired.' : 'Not logged in.'
200
+ console.error(
201
+ `\x1b[31m✗\x1b[0m ${reason} ${command} requires a Uniweb account, and the CLI is non-interactive (CI / no TTY / --non-interactive).`
202
+ )
179
203
  console.error(' Options:')
180
204
  console.error(` • Set UNIWEB_TOKEN to a bearer token.`)
181
- console.error(` • Set UNIWEB_USERNAME + UNIWEB_PASSWORD to log in non-interactively.`)
182
- console.error(` • Run \`${prefix} login\` interactively first, then re-run.`)
205
+ console.error(
206
+ ` • Set UNIWEB_USERNAME + UNIWEB_PASSWORD to log in non-interactively.`
207
+ )
208
+ console.error(
209
+ ` • Run \`${prefix} login\` interactively first, then re-run.`
210
+ )
183
211
  process.exit(1)
184
212
  }
185
213
 
186
214
  if (stored && isExpired(stored)) {
187
- console.log(`\x1b[33mSession expired.\x1b[0m ${command} requires a Uniweb account.\n`)
215
+ console.log(
216
+ `\x1b[33mSession expired.\x1b[0m ${command} requires a Uniweb account.\n`
217
+ )
188
218
  } else {
189
219
  console.log(`${command} requires a Uniweb account.\n`)
190
220
  }
@@ -207,10 +237,12 @@ const BROWSER_AVAILABLE = false
207
237
  */
208
238
  export async function fetchMe({ apiBase, token }) {
209
239
  const res = await fetch(`${apiBase.replace(/\/$/, '')}/dev/auth/me`, {
210
- headers: { Authorization: `Bearer ${token}` },
240
+ headers: { Authorization: `Bearer ${token}` }
211
241
  })
212
242
  if (!res.ok) {
213
- const e = new Error(`token check failed: HTTP ${res.status} ${res.statusText}`)
243
+ const e = new Error(
244
+ `token check failed: HTTP ${res.status} ${res.statusText}`
245
+ )
214
246
  e.status = res.status
215
247
  throw e
216
248
  }
@@ -224,13 +256,33 @@ async function loginViaPassword({ apiBase, nonInteractive }) {
224
256
  let password = process.env.UNIWEB_PASSWORD
225
257
  if (!username || !password) {
226
258
  if (nonInteractive) {
227
- throw new Error('username/password login needs a terminal — set UNIWEB_USERNAME + UNIWEB_PASSWORD (or UNIWEB_TOKEN).')
259
+ throw new Error(
260
+ 'username/password login needs a terminal — set UNIWEB_USERNAME + UNIWEB_PASSWORD (or UNIWEB_TOKEN).'
261
+ )
228
262
  }
229
263
  const prompts = (await import('prompts')).default
230
- const resp = await prompts([
231
- { type: 'text', name: 'username', message: 'Username:', validate: (v) => (v ? true : 'Username is required') },
232
- { type: 'password', name: 'password', message: 'Password:', validate: (v) => (v ? true : 'Password is required') },
233
- ], { onCancel: () => { console.log('\nLogin cancelled.'); process.exit(0) } })
264
+ const resp = await prompts(
265
+ [
266
+ {
267
+ type: 'text',
268
+ name: 'username',
269
+ message: 'Username:',
270
+ validate: (v) => (v ? true : 'Username is required')
271
+ },
272
+ {
273
+ type: 'password',
274
+ name: 'password',
275
+ message: 'Password:',
276
+ validate: (v) => (v ? true : 'Password is required')
277
+ }
278
+ ],
279
+ {
280
+ onCancel: () => {
281
+ console.log('\nLogin cancelled.')
282
+ process.exit(0)
283
+ }
284
+ }
285
+ )
234
286
  username = resp.username
235
287
  password = resp.password
236
288
  if (!username || !password) process.exit(1)
@@ -244,9 +296,20 @@ async function loginViaTokenPaste({ apiBase, nonInteractive }) {
244
296
  throw new Error('token paste needs a terminal — set UNIWEB_TOKEN instead.')
245
297
  }
246
298
  const prompts = (await import('prompts')).default
247
- const { token } = await prompts({
248
- type: 'password', name: 'token', message: 'Paste your token:', validate: (v) => (v ? true : 'Token is required'),
249
- }, { onCancel: () => { console.log('\nLogin cancelled.'); process.exit(0) } })
299
+ const { token } = await prompts(
300
+ {
301
+ type: 'password',
302
+ name: 'token',
303
+ message: 'Paste your token:',
304
+ validate: (v) => (v ? true : 'Token is required')
305
+ },
306
+ {
307
+ onCancel: () => {
308
+ console.log('\nLogin cancelled.')
309
+ process.exit(0)
310
+ }
311
+ }
312
+ )
250
313
  if (!token) process.exit(1)
251
314
  const account = await fetchMe({ apiBase, token }) // throws if the token is invalid
252
315
  const record = { token, origin: normOrigin(apiBase) }
@@ -261,9 +324,12 @@ async function loginViaTokenPaste({ apiBase, nonInteractive }) {
261
324
  async function openBrowser(url) {
262
325
  try {
263
326
  const { exec } = await import('node:child_process')
264
- const cmd = process.platform === 'darwin' ? `open "${url}"`
265
- : process.platform === 'win32' ? `start "" "${url}"`
266
- : `xdg-open "${url}"`
327
+ const cmd =
328
+ process.platform === 'darwin'
329
+ ? `open "${url}"`
330
+ : process.platform === 'win32'
331
+ ? `start "" "${url}"`
332
+ : `xdg-open "${url}"`
267
333
  return await new Promise((resolve) => exec(cmd, (err) => resolve(!err)))
268
334
  } catch {
269
335
  return false
@@ -300,24 +366,37 @@ export async function awaitBrowserCallback({
300
366
  openingLabel = 'Opening your browser…',
301
367
  waitingLabel,
302
368
  okTitle = 'Done',
303
- errTitle = 'Something went wrong',
369
+ errTitle = 'Something went wrong'
304
370
  } = {}) {
305
371
  const result = await new Promise((resolve) => {
306
372
  const server = createServer((req, res) => {
307
373
  const u = new URL(req.url, 'http://127.0.0.1')
308
- if (u.pathname !== '/callback') { res.writeHead(404); res.end('Not found'); return }
309
- const verdict = validate(u.searchParams) || { error: 'no result from the callback.' }
374
+ if (u.pathname !== '/callback') {
375
+ res.writeHead(404)
376
+ res.end('Not found')
377
+ return
378
+ }
379
+ const verdict = validate(u.searchParams) || {
380
+ error: 'no result from the callback.'
381
+ }
310
382
  const failed = !!verdict.error
311
383
  res.writeHead(failed ? 400 : 200, { 'Content-Type': 'text/html' })
312
- res.end(`<!doctype html><html><body style="font-family:system-ui,sans-serif;text-align:center;padding:60px">`
313
- + `<h2 style="color:${failed ? '#dc2626' : '#16a34a'}">${failed ? errTitle : okTitle}</h2>`
314
- + `<p>You can close this tab and return to your terminal.</p></body></html>`)
384
+ res.end(
385
+ `<!doctype html><html><body style="font-family:system-ui,sans-serif;text-align:center;padding:60px">` +
386
+ `<h2 style="color:${failed ? '#dc2626' : '#16a34a'}">${failed ? errTitle : okTitle}</h2>` +
387
+ `<p>You can close this tab and return to your terminal.</p></body></html>`
388
+ )
315
389
  cleanup()
316
390
  resolve(failed ? { error: verdict.error } : { value: verdict.value })
317
391
  })
318
392
  let timer
319
- function cleanup() { clearTimeout(timer); server.close() }
320
- server.on('error', (e) => resolve({ error: `loopback server error: ${e.message}` }))
393
+ function cleanup() {
394
+ clearTimeout(timer)
395
+ server.close()
396
+ }
397
+ server.on('error', (e) =>
398
+ resolve({ error: `loopback server error: ${e.message}` })
399
+ )
321
400
  server.listen(0, '127.0.0.1', async () => {
322
401
  const { port } = server.address()
323
402
  const redirectUri = `http://127.0.0.1:${port}/callback`
@@ -325,10 +404,18 @@ export async function awaitBrowserCallback({
325
404
  console.log(`\x1b[36m→\x1b[0m ${openingLabel}`)
326
405
  console.log(` \x1b[2m${url}\x1b[0m`)
327
406
  const opened = await openBrowser(url)
328
- if (!opened) console.log('\x1b[33m⚠\x1b[0m Could not open a browser automatically — open the URL above.')
329
- console.log(`\x1b[2m${waitingLabel || `Waiting (${Math.round(timeoutMs / 1000)}s)…`}\x1b[0m`)
407
+ if (!opened)
408
+ console.log(
409
+ '\x1b[33m⚠\x1b[0m Could not open a browser automatically — open the URL above.'
410
+ )
411
+ console.log(
412
+ `\x1b[2m${waitingLabel || `Waiting (${Math.round(timeoutMs / 1000)}s)…`}\x1b[0m`
413
+ )
330
414
  })
331
- timer = setTimeout(() => { server.close(); resolve({ error: `timed out (${Math.round(timeoutMs / 1000)}s).` }) }, timeoutMs)
415
+ timer = setTimeout(() => {
416
+ server.close()
417
+ resolve({ error: `timed out (${Math.round(timeoutMs / 1000)}s).` })
418
+ }, timeoutMs)
332
419
  })
333
420
  if (result.error) throw new Error(result.error)
334
421
  return result.value
@@ -342,7 +429,9 @@ export async function awaitBrowserCallback({
342
429
  // BROWSER_AVAILABLE until the endpoint is live.
343
430
  async function loginViaBrowser({ apiBase }) {
344
431
  if (!BROWSER_AVAILABLE) {
345
- throw new Error('browser/social login for the new backend isn’t available yet — use --password or --token-paste.')
432
+ throw new Error(
433
+ 'browser/social login for the new backend isn’t available yet — use --password or --token-paste.'
434
+ )
346
435
  }
347
436
  const base = apiBase.replace(/\/$/, '')
348
437
  const state = randomBytes(16).toString('hex')
@@ -352,7 +441,8 @@ async function loginViaBrowser({ apiBase }) {
352
441
  `${base}/dev/auth/authorize?redirect_uri=${encodeURIComponent(redirectUri)}&state=${state}`,
353
442
  validate: (params) => {
354
443
  if (params.get('error')) return { error: params.get('error') }
355
- if (params.get('state') !== state) return { error: 'state mismatch — please try again.' }
444
+ if (params.get('state') !== state)
445
+ return { error: 'state mismatch — please try again.' }
356
446
  const tok = params.get('token')
357
447
  if (!tok) return { error: 'no token returned by the callback.' }
358
448
  return { value: tok }
@@ -360,11 +450,15 @@ async function loginViaBrowser({ apiBase }) {
360
450
  openingLabel: 'Opening your browser to sign in…',
361
451
  waitingLabel: 'Waiting for sign-in to complete (120s)…',
362
452
  okTitle: 'Login successful',
363
- errTitle: 'Login failed',
453
+ errTitle: 'Login failed'
364
454
  })
365
455
 
366
456
  let account = null
367
- try { account = await fetchMe({ apiBase, token }) } catch { /* identity optional; token is valid */ }
457
+ try {
458
+ account = await fetchMe({ apiBase, token })
459
+ } catch {
460
+ /* identity optional; token is valid */
461
+ }
368
462
  const record = { token, origin: normOrigin(apiBase) }
369
463
  if (account?.uuid) record.uuid = account.uuid
370
464
  if (account?.username) record.username = account.username
@@ -388,8 +482,13 @@ async function loginViaBrowser({ apiBase }) {
388
482
  export async function runRegistryLogin({ apiBase, args = [] } = {}) {
389
483
  const existing = await readRegistryAuth()
390
484
  if (existing?.token && !isExpired(existing)) {
391
- const who = existing.username || existing.handle || (existing.uuid ? `account ${existing.uuid}` : '')
392
- console.log(`Already logged in${who ? ` as \x1b[1m${who}\x1b[0m` : ''}${apiBase ? ` (${apiBase})` : ''}.`)
485
+ const who =
486
+ existing.username ||
487
+ existing.handle ||
488
+ (existing.uuid ? `account ${existing.uuid}` : '')
489
+ console.log(
490
+ `Already logged in${who ? ` as \x1b[1m${who}\x1b[0m` : ''}${apiBase ? ` (${apiBase})` : ''}.`
491
+ )
393
492
  console.log('\x1b[2mContinuing will replace the existing session.\x1b[0m\n')
394
493
  }
395
494
 
@@ -407,7 +506,9 @@ export async function runRegistryLogin({ apiBase, args = [] } = {}) {
407
506
  try {
408
507
  account = await fetchMe({ apiBase, token: tokenFlag })
409
508
  } catch (err) {
410
- console.error(`\x1b[31m✗\x1b[0m Token rejected by ${apiBase}: ${err.message}`)
509
+ console.error(
510
+ `\x1b[31m✗\x1b[0m Token rejected by ${apiBase}: ${err.message}`
511
+ )
411
512
  process.exit(1)
412
513
  }
413
514
  const record = { token: tokenFlag, origin: normOrigin(apiBase) }
@@ -415,34 +516,58 @@ export async function runRegistryLogin({ apiBase, args = [] } = {}) {
415
516
  if (account?.username) record.username = account.username
416
517
  if (account?.handle) record.handle = account.handle
417
518
  await writeRegistryAuth(record)
418
- console.log(`\x1b[32m✓\x1b[0m Logged in${account?.username ? ` as \x1b[1m${account.username}\x1b[0m` : ''}${apiBase ? ` (${apiBase})` : ''}`)
519
+ console.log(
520
+ `\x1b[32m✓\x1b[0m Logged in${account?.username ? ` as \x1b[1m${account.username}\x1b[0m` : ''}${apiBase ? ` (${apiBase})` : ''}`
521
+ )
419
522
  return record
420
523
  }
421
524
 
422
- let method = args.includes('--browser') ? 'browser'
423
- : args.includes('--password') ? 'password'
424
- : args.includes('--token-paste') ? 'token-paste'
425
- : null
525
+ let method = args.includes('--browser')
526
+ ? 'browser'
527
+ : args.includes('--password')
528
+ ? 'password'
529
+ : args.includes('--token-paste')
530
+ ? 'token-paste'
531
+ : null
426
532
 
427
533
  if (!method) {
428
534
  if (nonInteractive) {
429
535
  if (process.env.UNIWEB_USERNAME && process.env.UNIWEB_PASSWORD) {
430
536
  method = 'password'
431
537
  } else {
432
- console.error('\x1b[31m✗\x1b[0m Cannot log in non-interactively without a method.')
433
- console.error(' Set UNIWEB_USERNAME + UNIWEB_PASSWORD, set UNIWEB_TOKEN, or run `uniweb login` in a terminal.')
538
+ console.error(
539
+ '\x1b[31m✗\x1b[0m Cannot log in non-interactively without a method.'
540
+ )
541
+ console.error(
542
+ ' Set UNIWEB_USERNAME + UNIWEB_PASSWORD, set UNIWEB_TOKEN, or run `uniweb login` in a terminal.'
543
+ )
434
544
  console.error(' Or force one: --password / --token-paste.')
435
545
  process.exit(1)
436
546
  }
437
547
  } else {
438
548
  const prompts = (await import('prompts')).default
439
549
  const choices = []
440
- if (BROWSER_AVAILABLE) choices.push({ title: 'Browser / social (Google, etc.)', value: 'browser' })
550
+ if (BROWSER_AVAILABLE)
551
+ choices.push({
552
+ title: 'Browser / social (Google, etc.)',
553
+ value: 'browser'
554
+ })
441
555
  choices.push({ title: 'Username and password', value: 'password' })
442
556
  choices.push({ title: 'Paste a token', value: 'token-paste' })
443
- const { picked } = await prompts({
444
- type: 'select', name: 'picked', message: 'How do you want to log in?', choices,
445
- }, { onCancel: () => { console.log('\nLogin cancelled.'); process.exit(0) } })
557
+ const { picked } = await prompts(
558
+ {
559
+ type: 'select',
560
+ name: 'picked',
561
+ message: 'How do you want to log in?',
562
+ choices
563
+ },
564
+ {
565
+ onCancel: () => {
566
+ console.log('\nLogin cancelled.')
567
+ process.exit(0)
568
+ }
569
+ }
570
+ )
446
571
  if (!picked) process.exit(0)
447
572
  method = picked
448
573
  }
@@ -451,7 +576,8 @@ export async function runRegistryLogin({ apiBase, args = [] } = {}) {
451
576
  let record
452
577
  try {
453
578
  if (method === 'browser') record = await loginViaBrowser({ apiBase })
454
- else if (method === 'token-paste') record = await loginViaTokenPaste({ apiBase, nonInteractive })
579
+ else if (method === 'token-paste')
580
+ record = await loginViaTokenPaste({ apiBase, nonInteractive })
455
581
  else record = await loginViaPassword({ apiBase, nonInteractive })
456
582
  } catch (err) {
457
583
  console.error(`\x1b[31m✗\x1b[0m ${err.message}`)
@@ -459,7 +585,9 @@ export async function runRegistryLogin({ apiBase, args = [] } = {}) {
459
585
  }
460
586
 
461
587
  if (record?.token) {
462
- console.log(`\x1b[32m✓\x1b[0m Logged in${record.username ? ` as \x1b[1m${record.username}\x1b[0m` : ''}${apiBase ? ` (${apiBase})` : ''}`)
588
+ console.log(
589
+ `\x1b[32m✓\x1b[0m Logged in${record.username ? ` as \x1b[1m${record.username}\x1b[0m` : ''}${apiBase ? ` (${apiBase})` : ''}`
590
+ )
463
591
  }
464
592
  return record
465
593
  }