@cardstack/boxel-cli 0.9.0-unstable.10 → 0.9.0-unstable.11

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 (118) hide show
  1. package/bundled-realms/base/command.gts +40 -24
  2. package/bundled-test-harness/assets/{add-workspace-JYBGLgLZ.js → add-workspace-1zRdl8zI.js} +1 -1
  3. package/bundled-test-harness/assets/{add-workspace-test-DRhnr1r8.js → add-workspace-test-D5iVse00.js} +1 -1
  4. package/bundled-test-harness/assets/{ai-module-creation-test-BJJhqeeA.js → ai-module-creation-test-GYiPd87c.js} +1 -1
  5. package/bundled-test-harness/assets/{app-BVQ3DVX4.js → app-CR7E2df0.js} +4 -4
  6. package/bundled-test-harness/assets/{binary-upload-dedupe-test-Bdmk8qpz.js → binary-upload-dedupe-test-mqTMTOGp.js} +1 -1
  7. package/bundled-test-harness/assets/{can-invoke-test-C3_ldZpl.js → can-invoke-test-B3LhMm5-.js} +1 -1
  8. package/bundled-test-harness/assets/{card-chooser-paging-test-C7BNPfWp.js → card-chooser-paging-test-6LwuAKTj.js} +1 -1
  9. package/bundled-test-harness/assets/{card-chooser-test-D8XNwCaW.js → card-chooser-test-CBvHX1BK.js} +1 -1
  10. package/bundled-test-harness/assets/{card-def-field-def-relationships-test-CuatVINU.js → card-def-field-def-relationships-test-DodLozx0.js} +1 -1
  11. package/bundled-test-harness/assets/{card-delete-test-DCmKEZ_Y.js → card-delete-test-BPde9vqy.js} +1 -1
  12. package/bundled-test-harness/assets/{check-correctness-test-B1bGWVSm.js → check-correctness-test-Ct2aB6aD.js} +1 -1
  13. package/bundled-test-harness/assets/{choreo-feature-reel-test-CGhD4996.js → choreo-feature-reel-test-Da7hUrQJ.js} +1 -1
  14. package/bundled-test-harness/assets/{choreo-gallery-BvL1V7P7.js → choreo-gallery-BKVGWFq7.js} +2 -2
  15. package/bundled-test-harness/assets/{choreo-gallery-build-order-test-vqxi2yVx.js → choreo-gallery-build-order-test-CJMITCCe.js} +1 -1
  16. package/bundled-test-harness/assets/{choreo-gallery-compositor-test-DhFRJae-.js → choreo-gallery-compositor-test-BVa9Ak-Q.js} +1 -1
  17. package/bundled-test-harness/assets/{choreo-gallery-cover-test-DmazsR0Z.js → choreo-gallery-cover-test-BFpC-W9i.js} +1 -1
  18. package/bundled-test-harness/assets/{choreo-gallery-crossing-stage-test-BipANF5u.js → choreo-gallery-crossing-stage-test-BpekNEib.js} +1 -1
  19. package/bundled-test-harness/assets/{choreo-gallery-demos-alive-test-CDAsZtl-.js → choreo-gallery-demos-alive-test-IOZo64_Z.js} +1 -1
  20. package/bundled-test-harness/assets/{choreo-gallery-drift-test-EBe6Yaqp.js → choreo-gallery-drift-test-BrPvqI7K.js} +1 -1
  21. package/bundled-test-harness/assets/{choreo-gallery-escort-test-Ceu7TwiV.js → choreo-gallery-escort-test-BgsIKJio.js} +1 -1
  22. package/bundled-test-harness/assets/{choreo-gallery-films-test-DrPpbW3l.js → choreo-gallery-films-test-Cnt3fSqM.js} +1 -1
  23. package/bundled-test-harness/assets/{choreo-gallery-filter-test-CvKk8l6z.js → choreo-gallery-filter-test-uPxHSrLd.js} +1 -1
  24. package/bundled-test-harness/assets/{choreo-gallery-fold-test-D8X4AXxh.js → choreo-gallery-fold-test-DBtUMaRe.js} +1 -1
  25. package/bundled-test-harness/assets/{choreo-gallery-guides-test-DWi_cZtu.js → choreo-gallery-guides-test-BDijbyEJ.js} +2 -2
  26. package/bundled-test-harness/assets/{choreo-gallery-hang-test-Djmr-Jwl.js → choreo-gallery-hang-test-Cfz_f7kq.js} +1 -1
  27. package/bundled-test-harness/assets/{choreo-gallery-highlight-test-hKhRmfbB.js → choreo-gallery-highlight-test-Bey3u2C9.js} +2 -2
  28. package/bundled-test-harness/assets/{choreo-gallery-inline-edit-test-DkjthGzn.js → choreo-gallery-inline-edit-test-DWsf_UFF.js} +1 -1
  29. package/bundled-test-harness/assets/{choreo-gallery-interruption-test-Cy11snG-.js → choreo-gallery-interruption-test-DxgnyIEX.js} +1 -1
  30. package/bundled-test-harness/assets/{choreo-gallery-lone-stack-test-Dv7sHo4c.js → choreo-gallery-lone-stack-test-CFONDgnZ.js} +1 -1
  31. package/bundled-test-harness/assets/{choreo-gallery-navigation-test-rXyVsieC.js → choreo-gallery-navigation-test-C3Gx2Wh7.js} +1 -1
  32. package/bundled-test-harness/assets/{choreo-gallery-playhead-transport-test-DtaCaSLQ.js → choreo-gallery-playhead-transport-test-CH7Jrjhu.js} +1 -1
  33. package/bundled-test-harness/assets/{choreo-gallery-poplayout-subtree-test-Ap4aNR41.js → choreo-gallery-poplayout-subtree-test-D_WzPJ0V.js} +1 -1
  34. package/bundled-test-harness/assets/{choreo-gallery-presence-modes-test-BhlW-Lne.js → choreo-gallery-presence-modes-test-Dg9Rjzo9.js} +1 -1
  35. package/bundled-test-harness/assets/{choreo-gallery-presentation-test-BCkTdHhb.js → choreo-gallery-presentation-test-BwgpOG4i.js} +1 -1
  36. package/bundled-test-harness/assets/{choreo-gallery-reduced-motion-test-C-i6VxrB.js → choreo-gallery-reduced-motion-test-Bb9vbXwc.js} +1 -1
  37. package/bundled-test-harness/assets/{choreo-gallery-sheet-standalone-test-YwYop8yo.js → choreo-gallery-sheet-standalone-test-wBtx6YOK.js} +1 -1
  38. package/bundled-test-harness/assets/{choreo-gallery-sheet-test-BxlTpsW6.js → choreo-gallery-sheet-test-Bgi58xOY.js} +1 -1
  39. package/bundled-test-harness/assets/{choreo-gallery-site-test-NgXU1y_2.js → choreo-gallery-site-test-0k39S_ID.js} +1 -1
  40. package/bundled-test-harness/assets/{choreo-gallery-stage-pQUMuoFC.js → choreo-gallery-stage-ZD1b6EcX.js} +1 -1
  41. package/bundled-test-harness/assets/{choreo-gallery-stage-keyboard-test-BBLXJf7u.js → choreo-gallery-stage-keyboard-test-CwYQ0uLO.js} +1 -1
  42. package/bundled-test-harness/assets/{choreo-gallery-stages-test-Bpt5h0Ci.js → choreo-gallery-stages-test-D9HzyH3W.js} +2 -2
  43. package/bundled-test-harness/assets/{choreo-gallery-subdivision-test-DezdmSrj.js → choreo-gallery-subdivision-test-Bqg4OA3x.js} +1 -1
  44. package/bundled-test-harness/assets/{choreo-gallery-table-plan-test-ClRyzZku.js → choreo-gallery-table-plan-test-kLIQjuGP.js} +1 -1
  45. package/bundled-test-harness/assets/{choreo-gallery-theater-test-eYpVLcPq.js → choreo-gallery-theater-test-hGflkP4v.js} +2 -2
  46. package/bundled-test-harness/assets/{choreo-gallery-tuning-test-BLgbgT-x.js → choreo-gallery-tuning-test-DXxSWyWx.js} +1 -1
  47. package/bundled-test-harness/assets/{choreo-gallery-widget-room-test-7sp5Unfj.js → choreo-gallery-widget-room-test-kDw1KEK8.js} +2 -2
  48. package/bundled-test-harness/assets/{choreo-gallery-wires-test-gaboWsGB.js → choreo-gallery-wires-test-Ci8pZpzw.js} +1 -1
  49. package/bundled-test-harness/assets/{codeblocks-test-BfrtgWFf.js → codeblocks-test-CVdMnOCp.js} +1 -1
  50. package/bundled-test-harness/assets/{codemirror-embed-toolbar-test-Bzke66Ms.js → codemirror-embed-toolbar-test-BssilSQu.js} +1 -1
  51. package/bundled-test-harness/assets/{compaction-test-vJF6rxRc.js → compaction-test-AMNVQVIq.js} +1 -1
  52. package/bundled-test-harness/assets/{container-Cg9tfMT-.js → container-aEzZntti.js} +5 -5
  53. package/bundled-test-harness/assets/{convert-to-class-name-test-GNx5er9G.js → convert-to-class-name-test-D8lm5X8q.js} +1 -1
  54. package/bundled-test-harness/assets/{create-file-modal-qd-wHuoI.js → create-file-modal-YsrXldcb.js} +1 -1
  55. package/bundled-test-harness/assets/{create-listing-modal-test-Dg53cvuG.js → create-listing-modal-test-43xjc0SV.js} +1 -1
  56. package/bundled-test-harness/assets/{credential-fields-test-CUiG7Iez.js → credential-fields-test-BFMDfkwH.js} +1 -1
  57. package/bundled-test-harness/assets/{debug-message-test-B_dvUaEc.js → debug-message-test-CfuhQ3QC.js} +1 -1
  58. package/bundled-test-harness/assets/{externals-BnJo838b.js → externals-wVTxNrdG.js} +1 -1
  59. package/bundled-test-harness/assets/{fallback-banner-live-delete-test-CnZFQWVp.js → fallback-banner-live-delete-test-CLbfPO0a.js} +1 -1
  60. package/bundled-test-harness/assets/{fallback-banner-test-DTlCNXTq.js → fallback-banner-test-9xbINj2Z.js} +1 -1
  61. package/bundled-test-harness/assets/{fallback-models-test-DjdqZpc6.js → fallback-models-test-DVQo2MxM.js} +1 -1
  62. package/bundled-test-harness/assets/{file-attachment-test-CX0j9bZz.js → file-attachment-test-CRKbikdE.js} +1 -1
  63. package/bundled-test-harness/assets/{general-test-7U7FOyZ1.js → general-test-lSgd8Ss0.js} +1 -1
  64. package/bundled-test-harness/assets/{index-query-engine-test-Ds1qxgEC.js → index-query-engine-test-BHRXNeAL.js} +1 -1
  65. package/bundled-test-harness/assets/{inspector-test-Ed3O5tuy.js → inspector-test-C6z8Un9M.js} +1 -1
  66. package/bundled-test-harness/assets/{instance-filter-matcher-test-D5X1y4EE.js → instance-filter-matcher-test-1Yg4VSaZ.js} +1 -1
  67. package/bundled-test-harness/assets/{main-CL6WYORU.js → main-Th6TkaXh.js} +1 -1
  68. package/bundled-test-harness/assets/{markdown-embed-chooser-modal-test-gW6Xfznw.js → markdown-embed-chooser-modal-test-Swm_1E9A.js} +1 -1
  69. package/bundled-test-harness/assets/{message-tool-test-BpSbgQ6j.js → message-tool-test-lzxIVYt5.js} +1 -1
  70. package/bundled-test-harness/assets/{modal-BwTd4bsz.js → modal-66tRI_Y_.js} +1 -1
  71. package/bundled-test-harness/assets/modal-container-CjnDLCtj.js +40 -0
  72. package/bundled-test-harness/assets/{model-cost-tier-test-CvyrJtBe.js → model-cost-tier-test-Dqd1Ww8g.js} +1 -1
  73. package/bundled-test-harness/assets/{operation-lowering-test-Dl-IR159.js → operation-lowering-test-BNFo0ZMa.js} +1 -1
  74. package/bundled-test-harness/assets/{operator-mode-card-chooser-test-DNB9UUeb.js → operator-mode-card-chooser-test-D3hTWkk0.js} +1 -1
  75. package/bundled-test-harness/assets/{operator-mode-links-test-a2ELMv7x.js → operator-mode-links-test-B6yda5il.js} +1 -1
  76. package/bundled-test-harness/assets/{operator-mode-test-BOILKxRt.js → operator-mode-test-wj2uYBuS.js} +1 -1
  77. package/bundled-test-harness/assets/{operator-mode-ui-test-DrYx32gG.js → operator-mode-ui-test-CO7CyFKM.js} +1 -1
  78. package/bundled-test-harness/assets/{overlay-menu-items-test-B15JkLWi.js → overlay-menu-items-test-C2GA814r.js} +1 -1
  79. package/bundled-test-harness/assets/{past-sessions-test-Db7VnDiY.js → past-sessions-test-CJyYYcCJ.js} +1 -1
  80. package/bundled-test-harness/assets/{permission-checking-realm-test-Bf9py4pK.js → permission-checking-realm-test-8dG7pbV2.js} +1 -1
  81. package/bundled-test-harness/assets/{profile-settings-modal-r1hvskUn.js → profile-settings-modal-BItESTfo.js} +1 -1
  82. package/bundled-test-harness/assets/{realm-config-policy-standing-test-BiJAwdmt.js → realm-config-policy-standing-test-CpvCRyQD.js} +1 -1
  83. package/bundled-test-harness/assets/{realm-config-routing-rule-edit-test-CGTMRnZC.js → realm-config-routing-rule-edit-test-hJ-6xFqS.js} +1 -1
  84. package/bundled-test-harness/assets/{realm-config-settings-test-CMvriHQ1.js → realm-config-settings-test-CJsbxe8c.js} +1 -1
  85. package/bundled-test-harness/assets/{reasoning-test-B4_QXPYj.js → reasoning-test-DZdScSB0.js} +1 -1
  86. package/bundled-test-harness/assets/{room-message-test-D0zWbBl2.js → room-message-test-C37ua8-g.js} +1 -1
  87. package/bundled-test-harness/assets/{run-realm-code-BCbAj6ZU.js → run-realm-code-WsTalbNy.js} +3 -1
  88. package/bundled-test-harness/assets/{run-realm-code-test-C1SstTFn.js → run-realm-code-test-BUEmsWe8.js} +9 -1
  89. package/bundled-test-harness/assets/{schema-generation-test-CPSyEnFn.js → schema-generation-test-DDnSXIhb.js} +1 -1
  90. package/bundled-test-harness/assets/{scrolling-test-8RITl4Yy.js → scrolling-test-B2X_cmFO.js} +1 -1
  91. package/bundled-test-harness/assets/{searchable-option-test-wy6MhR17.js → searchable-option-test-D8JVkmy0.js} +1 -1
  92. package/bundled-test-harness/assets/{sending-test-DECmMY11.js → sending-test-Dpz8twhM.js} +1 -1
  93. package/bundled-test-harness/assets/{skills-test-bnEF18f-.js → skills-test-ozjqNJg9.js} +1 -1
  94. package/bundled-test-harness/assets/{store-test-4HQ25OC9.js → store-test-BQ9i9Kr5.js} +39 -4
  95. package/bundled-test-harness/assets/{tests-4eXqEcYs.js → tests-CLX9KyWy.js} +450 -450
  96. package/bundled-test-harness/assets/{text-input-validator-test-DbbmKb6a.js → text-input-validator-test-Bst2QdAc.js} +1 -1
  97. package/bundled-test-harness/assets/{to-device-streaming-test-BFMz5Glx.js → to-device-streaming-test-CRPRebs2.js} +1 -1
  98. package/bundled-test-harness/assets/{token-usage-test-BEZ3LAKu.js → token-usage-test-BhABlxuQ.js} +1 -1
  99. package/bundled-test-harness/assets/{tools-D-uShEr3.js → tools-BIhB57BB.js} +1 -1
  100. package/bundled-test-harness/assets/{tools-test-DKOoqRwb.js → tools-test-26_cJBw_.js} +42 -22
  101. package/bundled-test-harness/assets/{workspace-chooser-Cu2Ytanf.js → workspace-chooser-BoxgLo8E.js} +2 -2
  102. package/bundled-test-harness/assets/{workspace-chooser-catalog-order-test-CvmdsPdO.js → workspace-chooser-catalog-order-test-CVmYeH4c.js} +1 -1
  103. package/bundled-test-harness/assets/{workspace-chooser-unreachable-notice-test-DQU5jnd9.js → workspace-chooser-unreachable-notice-test-CMRlJ4q2.js} +1 -1
  104. package/bundled-test-harness/assets/{workspace-delete-summary-test-CQGv5NBQ.js → workspace-delete-summary-test-Bw91K51c.js} +1 -1
  105. package/bundled-test-harness/assets/{workspace-UMec6T3x.js → workspace-rCACd5qW.js} +1 -1
  106. package/bundled-test-harness/assets/{workspace-test-X4Kzx0qN.js → workspace-test-B1wFPgPU.js} +23 -2
  107. package/bundled-test-harness/assets/{workspace-timestamp-labels-test-CSow2dI6.js → workspace-timestamp-labels-test-CvFFMtct.js} +1 -1
  108. package/bundled-test-harness/index.html +14 -14
  109. package/bundled-test-harness/tests/index.html +14 -14
  110. package/bundled-types/base/command.d.ts +1 -0
  111. package/bundled-types/host-app/components/modal-container.gts +1 -0
  112. package/bundled-types/host-app/tools/run-realm-code.ts +6 -2
  113. package/dist/index.js +269 -269
  114. package/package.json +1 -1
  115. package/src/commands/profile.ts +87 -19
  116. package/src/index.ts +13 -1
  117. package/src/lib/prompt.ts +54 -6
  118. package/bundled-test-harness/assets/modal-container-YpGh-tkA.js +0 -40
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardstack/boxel-cli",
3
- "version": "0.9.0-unstable.10",
3
+ "version": "0.9.0-unstable.11",
4
4
  "license": "MIT",
5
5
  "description": "CLI tools for Boxel workspace management",
6
6
  "main": "./dist/index.js",
@@ -422,16 +422,36 @@ export async function listProfiles(
422
422
  }
423
423
  }
424
424
 
425
+ // What adding a profile reaches beyond this module. Tests stand in for the
426
+ // browser and for the person at the terminal through it.
427
+ export interface AddProfileDeps {
428
+ browserLogin?: typeof browserLogin;
429
+ ensurePersonalRealm?: typeof ensurePersonalRealm;
430
+ prompt?: (question: string) => Promise<string>;
431
+ // Whether a person is at the terminal to answer questions. A coding agent or
432
+ // a script runs the CLI without a TTY, and a question asked there is never
433
+ // answered.
434
+ isTty?: () => boolean;
435
+ }
436
+
437
+ interface AddProfileContext {
438
+ browserLogin: typeof browserLogin;
439
+ ensurePersonalRealm: typeof ensurePersonalRealm;
440
+ prompt: (question: string) => Promise<string>;
441
+ interactive: boolean;
442
+ }
443
+
425
444
  // Returns false when the user declined to replace an existing profile.
426
445
  async function confirmOverwrite(
427
446
  manager: ProfileManager,
428
447
  matrixId: string,
448
+ ctx: AddProfileContext,
429
449
  ): Promise<boolean> {
430
450
  if (!manager.getProfile(matrixId)) {
431
451
  return true;
432
452
  }
433
453
  console.log(`\n${FG_YELLOW}Profile ${matrixId} already exists.${RESET}`);
434
- const overwrite = await prompt('Overwrite? [y/N]: ');
454
+ const overwrite = await ctx.prompt('Overwrite? [y/N]: ');
435
455
  if (overwrite.toLowerCase() !== 'y') {
436
456
  console.log('Cancelled.');
437
457
  return false;
@@ -439,9 +459,12 @@ async function confirmOverwrite(
439
459
  return true;
440
460
  }
441
461
 
442
- async function promptDisplayName(matrixId: string): Promise<string> {
462
+ async function promptDisplayName(
463
+ matrixId: string,
464
+ ctx: AddProfileContext,
465
+ ): Promise<string> {
443
466
  const defaultDisplayName = `${getUsernameFromMatrixId(matrixId)} \u00b7 ${getDomainFromMatrixId(matrixId)}`;
444
- const displayNameInput = await prompt(
467
+ const displayNameInput = await ctx.prompt(
445
468
  `Display name [${defaultDisplayName}]: `,
446
469
  );
447
470
  return displayNameInput || defaultDisplayName;
@@ -458,15 +481,25 @@ type AddProfileOutcome =
458
481
  // Browser sign-in. The authorization page offers both a password form and a
459
482
  // Google button, and reports back whichever account the user signed in as — so
460
483
  // unlike the terminal password path there is nothing to ask for up front.
484
+ //
485
+ // Without a TTY the questions that follow the sign-in are not asked: the
486
+ // person already chose to sign in, in the browser, and a question nobody can
487
+ // answer leaves the profile unsaved — so whoever ran the command runs it
488
+ // again and the person is sent through the browser again. The profile is
489
+ // saved as the defaults would have it, and a profile already stored for the
490
+ // account is replaced, since the browser has just proven it is that account.
491
+ // It keeps its display name. `addProfile` leaves a different active profile
492
+ // active.
461
493
  async function addProfileViaBrowser(
462
494
  manager: ProfileManager,
463
495
  matrixUrl: string,
464
496
  hostUrl: string,
465
497
  realmServerUrl: string,
498
+ ctx: AddProfileContext,
466
499
  ): Promise<AddProfileOutcome> {
467
500
  let auth;
468
501
  try {
469
- auth = await browserLogin({ matrixUrl, hostUrl });
502
+ auth = await ctx.browserLogin({ matrixUrl, hostUrl });
470
503
  } catch (err) {
471
504
  if (err instanceof SsoTimeoutError) {
472
505
  console.log(`\n${FG_YELLOW}${err.message}${RESET}`);
@@ -482,17 +515,23 @@ async function addProfileViaBrowser(
482
515
  console.log(
483
516
  `\n${FG_GREEN}✓${RESET} Signed in as ${formatProfileBadge(auth.userId)}`,
484
517
  );
485
- const proceed = await prompt('Save this profile? [Y/n]: ');
486
- if (proceed.toLowerCase() === 'n') {
487
- console.log('Cancelled.');
488
- return { status: 'cancelled' };
489
- }
518
+ let displayName: string | undefined;
519
+ if (ctx.interactive) {
520
+ const proceed = await ctx.prompt('Save this profile? [Y/n]: ');
521
+ if (proceed.toLowerCase() === 'n') {
522
+ console.log('Cancelled.');
523
+ return { status: 'cancelled' };
524
+ }
490
525
 
491
- if (!(await confirmOverwrite(manager, auth.userId))) {
492
- return { status: 'cancelled' };
493
- }
526
+ if (!(await confirmOverwrite(manager, auth.userId, ctx))) {
527
+ return { status: 'cancelled' };
528
+ }
494
529
 
495
- const displayName = await promptDisplayName(auth.userId);
530
+ displayName = await promptDisplayName(auth.userId, ctx);
531
+ } else {
532
+ // Undefined for a new profile, which addProfileWithAuth names by default.
533
+ displayName = manager.getProfile(auth.userId)?.displayName;
534
+ }
496
535
  await manager.addProfileWithAuth(
497
536
  auth.userId,
498
537
  auth,
@@ -508,7 +547,7 @@ async function addProfileViaBrowser(
508
547
  // and a failure only warns: the profile works without it, and signing in to
509
548
  // the web app offers workspace creation.
510
549
  try {
511
- const result = await ensurePersonalRealm(auth, realmServerUrl);
550
+ const result = await ctx.ensurePersonalRealm(auth, realmServerUrl);
512
551
  if (result.outcome === 'created') {
513
552
  console.log(
514
553
  `${FG_GREEN}✓${RESET} Created your personal workspace: ${FG_CYAN}${result.realmUrl}${RESET}`,
@@ -537,10 +576,11 @@ async function addProfileViaPassword(
537
576
  domain: string,
538
577
  matrixUrl: string,
539
578
  realmServerUrl: string,
579
+ ctx: AddProfileContext,
540
580
  ): Promise<AddProfileOutcome> {
541
581
  console.log(`\nEnter your Boxel username (without @ or domain)`);
542
582
  console.log(`${DIM}Example: ctse, aallen90${RESET}`);
543
- const username = await prompt('Username: ');
583
+ const username = await ctx.prompt('Username: ');
544
584
 
545
585
  if (!username) {
546
586
  console.error(`${FG_RED}Error:${RESET} Username is required.`);
@@ -549,7 +589,7 @@ async function addProfileViaPassword(
549
589
 
550
590
  const matrixId = `@${username}:${domain}`;
551
591
 
552
- if (!(await confirmOverwrite(manager, matrixId))) {
592
+ if (!(await confirmOverwrite(manager, matrixId, ctx))) {
553
593
  return { status: 'cancelled' };
554
594
  }
555
595
 
@@ -560,7 +600,7 @@ async function addProfileViaPassword(
560
600
  process.exit(1);
561
601
  }
562
602
 
563
- const displayName = await promptDisplayName(matrixId);
603
+ const displayName = await promptDisplayName(matrixId, ctx);
564
604
 
565
605
  await manager.addProfile(
566
606
  matrixId,
@@ -572,11 +612,18 @@ async function addProfileViaPassword(
572
612
  return { status: 'added', matrixId };
573
613
  }
574
614
 
575
- async function addProfile(
615
+ export async function addProfile(
576
616
  manager: ProfileManager,
577
617
  environment: EnvironmentDefaults,
578
618
  useBrowser = true,
619
+ deps: AddProfileDeps = {},
579
620
  ): Promise<void> {
621
+ const ctx: AddProfileContext = {
622
+ browserLogin: deps.browserLogin ?? browserLogin,
623
+ ensurePersonalRealm: deps.ensurePersonalRealm ?? ensurePersonalRealm,
624
+ prompt: deps.prompt ?? prompt,
625
+ interactive: (deps.isTty ?? (() => Boolean(process.stdin.isTTY)))(),
626
+ };
580
627
  console.log(`\n${BOLD}Add New Profile${RESET}\n`);
581
628
  // The environment comes from flags rather than a prompt, so name it. The
582
629
  // realm server identifies it — that's the host every later command talks to,
@@ -595,15 +642,28 @@ async function addProfile(
595
642
  environment.matrixUrl,
596
643
  environment.appUrl ?? environment.realmServerUrl,
597
644
  environment.realmServerUrl,
645
+ ctx,
598
646
  )
599
647
  : { status: 'usePassword' };
600
648
 
601
649
  if (outcome.status === 'usePassword') {
650
+ // The terminal password sign-in is a run of questions, so without a TTY
651
+ // it would wait on an answer that never comes.
652
+ if (!ctx.interactive) {
653
+ console.error(
654
+ `${FG_RED}Error:${RESET} Signing in with a password here needs a terminal. ` +
655
+ `Without one, pass the Matrix ID and the password in the environment: ` +
656
+ `BOXEL_PASSWORD=... boxel profile add -u @<username>:${environment.domain}`,
657
+ );
658
+ process.exitCode = 1;
659
+ return;
660
+ }
602
661
  outcome = await addProfileViaPassword(
603
662
  manager,
604
663
  environment.domain,
605
664
  environment.matrixUrl,
606
665
  environment.realmServerUrl,
666
+ ctx,
607
667
  );
608
668
  }
609
669
 
@@ -619,8 +679,16 @@ async function addProfile(
619
679
 
620
680
  if (manager.getActiveProfileId() === matrixId) {
621
681
  console.log(`${DIM}This profile is now active.${RESET}`);
682
+ } else if (!ctx.interactive) {
683
+ // The browser can sign in as an account nobody meant — a Google identity
684
+ // with no matching account gets a new one — and without a TTY nobody has
685
+ // confirmed this one. The profile that was active stays active until
686
+ // someone chooses this one.
687
+ console.log(
688
+ `${DIM}Use 'boxel profile switch ${matrixId}' to switch to this profile.${RESET}`,
689
+ );
622
690
  } else {
623
- const switchNow = await prompt('Switch to this profile now? [Y/n]: ');
691
+ const switchNow = await ctx.prompt('Switch to this profile now? [Y/n]: ');
624
692
  if (switchNow.toLowerCase() !== 'n') {
625
693
  manager.switchProfile(matrixId);
626
694
  console.log(
package/src/index.ts CHANGED
@@ -3,6 +3,8 @@ import { readFileSync } from 'fs';
3
3
  import { resolve } from 'path';
4
4
  import { buildBoxelProgram } from './build-program.ts';
5
5
  import { setQuiet } from './lib/cli-log.ts';
6
+ import { FG_RED, RESET } from './lib/colors.ts';
7
+ import { PromptInputClosedError } from './lib/prompt.ts';
6
8
 
7
9
  const pkg = JSON.parse(
8
10
  readFileSync(resolve(import.meta.dirname, '../package.json'), 'utf-8'),
@@ -22,4 +24,14 @@ if (process.argv.includes('--quiet')) {
22
24
  setQuiet(true);
23
25
  }
24
26
 
25
- buildBoxelProgram(pkg.version).parse();
27
+ buildBoxelProgram(pkg.version)
28
+ .parseAsync()
29
+ .catch((err: unknown) => {
30
+ if (err instanceof PromptInputClosedError) {
31
+ console.error(`${FG_RED}Error:${RESET} ${err.message}`);
32
+ process.exitCode = 1;
33
+ return;
34
+ }
35
+ // Anything else is unexpected, and its stack is what explains it.
36
+ throw err;
37
+ });
package/src/lib/prompt.ts CHANGED
@@ -1,22 +1,70 @@
1
1
  import * as readline from 'readline';
2
2
  import { Writable } from 'stream';
3
3
 
4
- export function prompt(question: string): Promise<string> {
5
- const wasFlowing = process.stdin.readableFlowing;
4
+ // A question whose input ended before it was answered. The CLI's entry point
5
+ // prints its message without a stack, since it describes how the command was
6
+ // run rather than a fault in it.
7
+ export class PromptInputClosedError extends Error {
8
+ constructor(question: string, fromTerminal: boolean) {
9
+ super(
10
+ `No answer to "${question.trim()}".` +
11
+ (fromTerminal
12
+ ? ''
13
+ : ' Input ended before one was given. Run the command in a terminal, ' +
14
+ 'or pass what it asks for as flags or environment variables.'),
15
+ );
16
+ this.name = 'PromptInputClosedError';
17
+ }
18
+ }
19
+
20
+ export interface PromptOptions {
21
+ /** @internal The streams to read from and write to; tests pass their own. */
22
+ input?: NodeJS.ReadableStream & {
23
+ readableFlowing?: boolean | null;
24
+ isTTY?: boolean;
25
+ };
26
+ output?: NodeJS.WritableStream;
27
+ }
28
+
29
+ export function prompt(
30
+ question: string,
31
+ options: PromptOptions = {},
32
+ ): Promise<string> {
33
+ const input = options.input ?? process.stdin;
34
+ const wasFlowing = input.readableFlowing;
6
35
  const rl = readline.createInterface({
7
- input: process.stdin,
8
- output: process.stdout,
36
+ input,
37
+ output: options.output ?? process.stdout,
9
38
  });
10
39
 
11
- return new Promise((resolve) => {
40
+ return new Promise((resolve, reject) => {
41
+ // Input that ends before an answer arrives (stdin from /dev/null, or a pipe
42
+ // that runs dry) closes the interface without ever calling the question's
43
+ // callback. Left alone, the command's promise never settles and the process
44
+ // exits as if it had finished, having done nothing it was asked to.
45
+ let answered = false;
46
+ rl.once('close', () => {
47
+ if (!answered) {
48
+ reject(new PromptInputClosedError(question, Boolean(input.isTTY)));
49
+ }
50
+ });
51
+ // Ctrl+C stops the command, as it does at the no-echo prompt below. Without
52
+ // a listener here readline would close the interface instead, which reads
53
+ // as a question left unanswered.
54
+ rl.on('SIGINT', () => {
55
+ answered = true;
56
+ rl.close();
57
+ process.exit(130);
58
+ });
12
59
  rl.question(question, (answer) => {
60
+ answered = true;
13
61
  rl.close();
14
62
  // Creating the interface resumes stdin, and closing it doesn't undo that —
15
63
  // a resumed stdin keeps the event loop alive, so a command whose last act
16
64
  // is a prompt would sit there having already finished its work. The
17
65
  // no-echo prompt below takes the same care.
18
66
  if (!wasFlowing) {
19
- process.stdin.pause();
67
+ input.pause();
20
68
  }
21
69
  resolve(answer.trim());
22
70
  });
@@ -1,40 +0,0 @@
1
- import "./chunk-Rp0pCe6z.js";
2
- import { wt as setComponentTemplate } from "./cache-DtDz7X5V-Wn8jRDfL.js";
3
- import { H as templateFactory, S as on } from "./index-DaCZcoCY-CgzbilMy.js";
4
- import "./modifier-D0EP-FQ7.js";
5
- import "./component-BJO47HTh.js";
6
- import { t as Component } from "./dist-CkA1rgqO.js";
7
- import "./template-factory-ds9s0u8l.js";
8
- import { $ as CardContainer, D as Header, _ as Modal, nt as Tooltip, pt as IconButton } from "./components-BiwudoSo.js";
9
- import { v as IconComponent } from "./icons-LMEaS73h.js";
10
- //#region app/components/modal-container.gts
11
- var ModalContainer = class extends Component {
12
- get size() {
13
- return this.args.size ?? "large";
14
- }
15
- get isOpen() {
16
- return this.args.isOpen ?? true;
17
- }
18
- get closeButtonLabel() {
19
- return this.args.closeButtonLabel ?? "Close";
20
- }
21
- static {
22
- setComponentTemplate(templateFactory({
23
- "id": "eR3qQR3w",
24
- "block": "[[[8,[32,0],[[17,1],[24,\"data-scopedcss-adeaf99312-d66846fe00\",\"\"]],[[\"@size\",\"@isOpen\",\"@onClose\",\"@centered\",\"@isOverlayDismissalDisabled\",\"@layer\"],[[30,0,[\"size\"]],[30,0,[\"isOpen\"]],[30,2],[30,3],[30,4],[30,5]]],[[\"default\"],[[[[1,\"\\n \"],[8,[32,1],[[16,0,[29,[\"dialog-box\\n \",[30,6],\"\\n \",[52,[48,[30,9]],\"dialog-box--with-sidebar\"]]]],[24,\"data-scopedcss-adeaf99312-d66846fe00\",\"\"]],[[\"@displayBoundaries\"],[true]],[[\"default\"],[[[[1,\"\\n\"],[41,[48,[30,9]],[[[1,\" \"],[10,\"section\"],[14,0,\"dialog-box__sidebar-header\"],[14,\"data-scopedcss-adeaf99312-d66846fe00\",\"\"],[12],[13],[1,\"\\n \"],[10,\"aside\"],[14,0,\"dialog-box__sidebar\"],[14,\"data-scopedcss-adeaf99312-d66846fe00\",\"\"],[12],[1,\"\\n \"],[18,9,null],[1,\"\\n \"],[13],[1,\"\\n\"]],[]],null],[1,\" \"],[8,[32,2],[[24,0,\"dialog-box__header\"],[24,\"data-scopedcss-adeaf99312-d66846fe00\",\"\"]],[[\"@size\",\"@title\"],[\"large\",[30,7]]],[[\"default\"],[[[[1,\"\\n\"],[41,[30,8],[[[1,\" \"],[8,[32,3],[[24,0,\"dialog-box__close-tooltip\"],[24,\"data-scopedcss-adeaf99312-d66846fe00\",\"\"]],[[\"@placement\",\"@disabled\"],[\"bottom\",[30,4]]],[[\"trigger\",\"content\"],[[[[1,\"\\n \"],[8,[32,4],[[24,0,\"dialog-box__close dialog-box__close--in-tooltip\"],[24,\"aria-label\",\"close modal\"],[16,\"disabled\",[30,4]],[24,\"data-test-close-modal\",\"\"],[24,\"data-scopedcss-adeaf99312-d66846fe00\",\"\"],[4,[32,6],[\"click\",[30,2]],null]],[[\"@icon\",\"@width\",\"@height\"],[[32,5],\"12\",\"12\"]],null],[1,\"\\n \"]],[]],[[[1,\"\\n \"],[10,1],[14,0,\"close-tooltip\"],[14,\"data-scopedcss-adeaf99312-d66846fe00\",\"\"],[12],[1,\"\\n \"],[10,1],[14,0,\"close-tooltip__label\"],[14,\"data-scopedcss-adeaf99312-d66846fe00\",\"\"],[12],[1,[30,0,[\"closeButtonLabel\"]]],[13],[1,\"\\n \"],[10,\"kbd\"],[14,0,\"shortcut-key\"],[14,\"data-scopedcss-adeaf99312-d66846fe00\",\"\"],[12],[1,[30,8]],[13],[1,\"\\n \"],[13],[1,\"\\n \"]],[]]]]],[1,\"\\n\"]],[]],[[[1,\" \"],[8,[32,4],[[24,0,\"dialog-box__close\"],[24,\"aria-label\",\"close modal\"],[16,\"disabled\",[30,4]],[24,\"data-test-close-modal\",\"\"],[24,\"data-scopedcss-adeaf99312-d66846fe00\",\"\"],[4,[32,6],[\"click\",[30,2]],null]],[[\"@icon\",\"@width\",\"@height\"],[[32,5],\"12\",\"12\"]],null],[1,\"\\n\"]],[]]],[1,\" \"],[18,10,null],[1,\"\\n \"]],[]]]]],[1,\"\\n \"],[10,0],[14,0,\"dialog-box__content\"],[14,\"data-scopedcss-adeaf99312-d66846fe00\",\"\"],[12],[1,\"\\n \"],[18,11,null],[1,\"\\n \"],[13],[1,\"\\n\"],[41,[48,[30,12]],[[[1,\" \"],[10,\"footer\"],[14,0,\"dialog-box__footer\"],[14,\"data-scopedcss-adeaf99312-d66846fe00\",\"\"],[12],[1,\"\\n \"],[18,12,null],[1,\"\\n \"],[13],[1,\"\\n\"]],[]],null],[1,\" \"]],[]]]]],[1,\"\\n\"]],[]]]]],[1,\"\\n\"]],[\"&attrs\",\"@onClose\",\"@centered\",\"@isCloseDisabled\",\"@layer\",\"@cardContainerClass\",\"@title\",\"@closeButtonShortcut\",\"&sidebar\",\"&header\",\"&content\",\"&footer\"],[\"if\",\"has-block\",\"yield\"]]",
25
- "moduleName": "/home/runner/work/boxel/boxel/packages/host/app/components/modal-container.gts",
26
- "scope": () => [
27
- Modal,
28
- CardContainer,
29
- Header,
30
- Tooltip,
31
- IconButton,
32
- IconComponent,
33
- on
34
- ],
35
- "isStrictMode": true
36
- }), this);
37
- }
38
- };
39
- //#endregion
40
- export { ModalContainer as t };