@capuchoo/cli 0.8.0 → 0.9.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 (74) hide show
  1. package/README.md +211 -23
  2. package/dist/cli/members.d.ts +23 -0
  3. package/dist/cli/members.d.ts.map +1 -0
  4. package/dist/cli/members.js +38 -0
  5. package/dist/cli/members.js.map +1 -0
  6. package/dist/cli/onboarding.d.ts +41 -0
  7. package/dist/cli/onboarding.d.ts.map +1 -0
  8. package/dist/cli/onboarding.js +94 -0
  9. package/dist/cli/onboarding.js.map +1 -0
  10. package/dist/cli/team.d.ts +16 -0
  11. package/dist/cli/team.d.ts.map +1 -0
  12. package/dist/cli/team.js +32 -0
  13. package/dist/cli/team.js.map +1 -0
  14. package/dist/commands/app/grant.d.ts +11 -0
  15. package/dist/commands/app/grant.d.ts.map +1 -0
  16. package/dist/commands/app/grant.js +31 -0
  17. package/dist/commands/app/grant.js.map +1 -0
  18. package/dist/commands/app/revoke.d.ts +13 -0
  19. package/dist/commands/app/revoke.d.ts.map +1 -0
  20. package/dist/commands/app/revoke.js +40 -0
  21. package/dist/commands/app/revoke.js.map +1 -0
  22. package/dist/commands/app/roles.d.ts +7 -0
  23. package/dist/commands/app/roles.d.ts.map +1 -0
  24. package/dist/commands/app/roles.js +30 -0
  25. package/dist/commands/app/roles.js.map +1 -0
  26. package/dist/commands/auth/issue.d.ts +12 -0
  27. package/dist/commands/auth/issue.d.ts.map +1 -0
  28. package/dist/commands/auth/issue.js +52 -0
  29. package/dist/commands/auth/issue.js.map +1 -0
  30. package/dist/commands/auth/keys.d.ts +7 -0
  31. package/dist/commands/auth/keys.d.ts.map +1 -0
  32. package/dist/commands/auth/keys.js +39 -0
  33. package/dist/commands/auth/keys.js.map +1 -0
  34. package/dist/commands/auth/login.d.ts +20 -0
  35. package/dist/commands/auth/login.d.ts.map +1 -1
  36. package/dist/commands/auth/login.js +90 -11
  37. package/dist/commands/auth/login.js.map +1 -1
  38. package/dist/commands/auth/revoke.d.ts +13 -0
  39. package/dist/commands/auth/revoke.d.ts.map +1 -0
  40. package/dist/commands/auth/revoke.js +48 -0
  41. package/dist/commands/auth/revoke.js.map +1 -0
  42. package/dist/commands/doctor.d.ts.map +1 -1
  43. package/dist/commands/doctor.js +1 -1
  44. package/dist/commands/doctor.js.map +1 -1
  45. package/dist/commands/init.d.ts +13 -0
  46. package/dist/commands/init.d.ts.map +1 -1
  47. package/dist/commands/init.js +69 -11
  48. package/dist/commands/init.js.map +1 -1
  49. package/dist/commands/menu.d.ts +3 -0
  50. package/dist/commands/menu.d.ts.map +1 -1
  51. package/dist/commands/menu.js +70 -15
  52. package/dist/commands/menu.js.map +1 -1
  53. package/dist/commands/org/invite.d.ts +14 -0
  54. package/dist/commands/org/invite.d.ts.map +1 -0
  55. package/dist/commands/org/invite.js +52 -0
  56. package/dist/commands/org/invite.js.map +1 -0
  57. package/dist/commands/org/members.d.ts +10 -0
  58. package/dist/commands/org/members.d.ts.map +1 -0
  59. package/dist/commands/org/members.js +49 -0
  60. package/dist/commands/org/members.js.map +1 -0
  61. package/dist/pipeline/app-identity.d.ts +35 -0
  62. package/dist/pipeline/app-identity.d.ts.map +1 -0
  63. package/dist/pipeline/app-identity.js +109 -0
  64. package/dist/pipeline/app-identity.js.map +1 -0
  65. package/dist/services/cloud.d.ts +69 -1
  66. package/dist/services/cloud.d.ts.map +1 -1
  67. package/dist/services/cloud.js +67 -1
  68. package/dist/services/cloud.js.map +1 -1
  69. package/dist/utils/http.d.ts +5 -0
  70. package/dist/utils/http.d.ts.map +1 -1
  71. package/dist/utils/http.js +13 -2
  72. package/dist/utils/http.js.map +1 -1
  73. package/oclif.manifest.json +374 -84
  74. package/package.json +5 -5
package/README.md CHANGED
@@ -29,7 +29,7 @@ $ npm install -g @capuchoo/cli
29
29
  $ capuchoo COMMAND
30
30
  running command...
31
31
  $ capuchoo (--version)
32
- @capuchoo/cli/0.7.0 win32-x64 node-v24.19.0
32
+ @capuchoo/cli/0.9.0 win32-x64 node-v24.20.0
33
33
  $ capuchoo --help [COMMAND]
34
34
  USAGE
35
35
  $ capuchoo COMMAND
@@ -43,9 +43,15 @@ USAGE
43
43
  <!-- commands -->
44
44
 
45
45
  - [`capuchoo app delete [APPID]`](#capuchoo-app-delete-appid)
46
+ - [`capuchoo app grant EMAIL ROLE`](#capuchoo-app-grant-email-role)
46
47
  - [`capuchoo app list`](#capuchoo-app-list)
48
+ - [`capuchoo app revoke EMAIL`](#capuchoo-app-revoke-email)
49
+ - [`capuchoo app roles`](#capuchoo-app-roles)
50
+ - [`capuchoo auth issue`](#capuchoo-auth-issue)
51
+ - [`capuchoo auth keys`](#capuchoo-auth-keys)
47
52
  - [`capuchoo auth login`](#capuchoo-auth-login)
48
53
  - [`capuchoo auth logout`](#capuchoo-auth-logout)
54
+ - [`capuchoo auth revoke ID`](#capuchoo-auth-revoke-id)
49
55
  - [`capuchoo auth whoami`](#capuchoo-auth-whoami)
50
56
  - [`capuchoo channel create [NAME]`](#capuchoo-channel-create-name)
51
57
  - [`capuchoo channel delete [NAME]`](#capuchoo-channel-delete-name)
@@ -59,7 +65,9 @@ USAGE
59
65
  - [`capuchoo init`](#capuchoo-init)
60
66
  - [`capuchoo menu`](#capuchoo-menu)
61
67
  - [`capuchoo org create [NAME]`](#capuchoo-org-create-name)
68
+ - [`capuchoo org invite EMAIL ROLE`](#capuchoo-org-invite-email-role)
62
69
  - [`capuchoo org list`](#capuchoo-org-list)
70
+ - [`capuchoo org members`](#capuchoo-org-members)
63
71
  - [`capuchoo setup`](#capuchoo-setup)
64
72
  - [`capuchoo version bump TYPE`](#capuchoo-version-bump-type)
65
73
  - [`capuchoo version sync`](#capuchoo-version-sync)
@@ -86,7 +94,31 @@ EXAMPLES
86
94
  ```
87
95
 
88
96
  _See code:
89
- [src/commands/app/delete.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/app/delete.ts)_
97
+ [src/commands/app/delete.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/app/delete.ts)_
98
+
99
+ ## `capuchoo app grant EMAIL ROLE`
100
+
101
+ Give someone a role on this app
102
+
103
+ ```
104
+ USAGE
105
+ $ capuchoo app grant EMAIL ROLE
106
+
107
+ ARGUMENTS
108
+ EMAIL Account to grant
109
+ ROLE (admin|developer|tester|viewer) One of admin, developer, tester, viewer
110
+
111
+ DESCRIPTION
112
+ Give someone a role on this app
113
+
114
+ EXAMPLES
115
+ $ capuchoo app grant dev@company.com developer
116
+
117
+ $ capuchoo app grant qa@company.com tester
118
+ ```
119
+
120
+ _See code:
121
+ [src/commands/app/grant.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/app/grant.ts)_
90
122
 
91
123
  ## `capuchoo app list`
92
124
 
@@ -104,11 +136,97 @@ DESCRIPTION
104
136
  ```
105
137
 
106
138
  _See code:
107
- [src/commands/app/list.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/app/list.ts)_
139
+ [src/commands/app/list.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/app/list.ts)_
140
+
141
+ ## `capuchoo app revoke EMAIL`
142
+
143
+ Remove someone's role on this app
144
+
145
+ ```
146
+ USAGE
147
+ $ capuchoo app revoke EMAIL [-y]
148
+
149
+ ARGUMENTS
150
+ EMAIL Account to revoke
151
+
152
+ FLAGS
153
+ -y, --yes Skip the confirmation
154
+
155
+ DESCRIPTION
156
+ Remove someone's role on this app
157
+
158
+ EXAMPLES
159
+ $ capuchoo app revoke dev@company.com
160
+ ```
161
+
162
+ _See code:
163
+ [src/commands/app/revoke.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/app/revoke.ts)_
164
+
165
+ ## `capuchoo app roles`
166
+
167
+ Show who can do what on this app
168
+
169
+ ```
170
+ USAGE
171
+ $ capuchoo app roles
172
+
173
+ DESCRIPTION
174
+ Show who can do what on this app
175
+
176
+ EXAMPLES
177
+ $ capuchoo app roles
178
+ ```
179
+
180
+ _See code:
181
+ [src/commands/app/roles.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/app/roles.ts)_
182
+
183
+ ## `capuchoo auth issue`
184
+
185
+ Create an API key, optionally limited to one app and one role
186
+
187
+ ```
188
+ USAGE
189
+ $ capuchoo auth issue [--name <value>] [--role viewer|tester|developer|admin] [--this-app]
190
+
191
+ FLAGS
192
+ --name=<value> Label shown in capuchoo auth keys
193
+ --role=<option> Ceiling on what the key may do: viewer, tester, developer, admin
194
+ <options: viewer|tester|developer|admin>
195
+ --this-app Restrict the key to the app this directory is linked to
196
+
197
+ DESCRIPTION
198
+ Create an API key, optionally limited to one app and one role
199
+
200
+ EXAMPLES
201
+ $ capuchoo auth issue --name ci --role developer --this-app
202
+
203
+ $ capuchoo auth issue --name readonly --role viewer
204
+ ```
205
+
206
+ _See code:
207
+ [src/commands/auth/issue.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/auth/issue.ts)_
208
+
209
+ ## `capuchoo auth keys`
210
+
211
+ List the API keys on this account
212
+
213
+ ```
214
+ USAGE
215
+ $ capuchoo auth keys
216
+
217
+ DESCRIPTION
218
+ List the API keys on this account
219
+
220
+ EXAMPLES
221
+ $ capuchoo auth keys
222
+ ```
223
+
224
+ _See code:
225
+ [src/commands/auth/keys.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/auth/keys.ts)_
108
226
 
109
227
  ## `capuchoo auth login`
110
228
 
111
- Store an API key for the Capuchoo backend
229
+ Sign in to a Capuchoo backend
112
230
 
113
231
  ```
114
232
  USAGE
@@ -119,7 +237,7 @@ FLAGS
119
237
  -k, --api-key=<value> API key from Settings > API Keys in the dashboard
120
238
 
121
239
  DESCRIPTION
122
- Store an API key for the Capuchoo backend
240
+ Sign in to a Capuchoo backend
123
241
 
124
242
  EXAMPLES
125
243
  $ capuchoo auth login
@@ -128,7 +246,7 @@ EXAMPLES
128
246
  ```
129
247
 
130
248
  _See code:
131
- [src/commands/auth/login.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/auth/login.ts)_
249
+ [src/commands/auth/login.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/auth/login.ts)_
132
250
 
133
251
  ## `capuchoo auth logout`
134
252
 
@@ -143,7 +261,31 @@ DESCRIPTION
143
261
  ```
144
262
 
145
263
  _See code:
146
- [src/commands/auth/logout.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/auth/logout.ts)_
264
+ [src/commands/auth/logout.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/auth/logout.ts)_
265
+
266
+ ## `capuchoo auth revoke ID`
267
+
268
+ Revoke an API key
269
+
270
+ ```
271
+ USAGE
272
+ $ capuchoo auth revoke ID [-y]
273
+
274
+ ARGUMENTS
275
+ ID Key id, from capuchoo auth keys
276
+
277
+ FLAGS
278
+ -y, --yes Skip the confirmation
279
+
280
+ DESCRIPTION
281
+ Revoke an API key
282
+
283
+ EXAMPLES
284
+ $ capuchoo auth revoke <id>
285
+ ```
286
+
287
+ _See code:
288
+ [src/commands/auth/revoke.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/auth/revoke.ts)_
147
289
 
148
290
  ## `capuchoo auth whoami`
149
291
 
@@ -161,7 +303,7 @@ DESCRIPTION
161
303
  ```
162
304
 
163
305
  _See code:
164
- [src/commands/auth/whoami.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/auth/whoami.ts)_
306
+ [src/commands/auth/whoami.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/auth/whoami.ts)_
165
307
 
166
308
  ## `capuchoo channel create [NAME]`
167
309
 
@@ -192,7 +334,7 @@ EXAMPLES
192
334
  ```
193
335
 
194
336
  _See code:
195
- [src/commands/channel/create.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/channel/create.ts)_
337
+ [src/commands/channel/create.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/channel/create.ts)_
196
338
 
197
339
  ## `capuchoo channel delete [NAME]`
198
340
 
@@ -218,7 +360,7 @@ EXAMPLES
218
360
  ```
219
361
 
220
362
  _See code:
221
- [src/commands/channel/delete.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/channel/delete.ts)_
363
+ [src/commands/channel/delete.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/channel/delete.ts)_
222
364
 
223
365
  ## `capuchoo channel list`
224
366
 
@@ -236,7 +378,7 @@ DESCRIPTION
236
378
  ```
237
379
 
238
380
  _See code:
239
- [src/commands/channel/list.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/channel/list.ts)_
381
+ [src/commands/channel/list.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/channel/list.ts)_
240
382
 
241
383
  ## `capuchoo config list`
242
384
 
@@ -254,7 +396,7 @@ DESCRIPTION
254
396
  ```
255
397
 
256
398
  _See code:
257
- [src/commands/config/list.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/config/list.ts)_
399
+ [src/commands/config/list.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/config/list.ts)_
258
400
 
259
401
  ## `capuchoo config set KEY VALUE`
260
402
 
@@ -278,7 +420,7 @@ EXAMPLES
278
420
  ```
279
421
 
280
422
  _See code:
281
- [src/commands/config/set.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/config/set.ts)_
423
+ [src/commands/config/set.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/config/set.ts)_
282
424
 
283
425
  ## `capuchoo deploy native`
284
426
 
@@ -322,7 +464,7 @@ EXAMPLES
322
464
  ```
323
465
 
324
466
  _See code:
325
- [src/commands/deploy/native.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/deploy/native.ts)_
467
+ [src/commands/deploy/native.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/deploy/native.ts)_
326
468
 
327
469
  ## `capuchoo deploy ota`
328
470
 
@@ -359,7 +501,7 @@ EXAMPLES
359
501
  ```
360
502
 
361
503
  _See code:
362
- [src/commands/deploy/ota.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/deploy/ota.ts)_
504
+ [src/commands/deploy/ota.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/deploy/ota.ts)_
363
505
 
364
506
  ## `capuchoo doctor`
365
507
 
@@ -377,7 +519,7 @@ EXAMPLES
377
519
  ```
378
520
 
379
521
  _See code:
380
- [src/commands/doctor.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/doctor.ts)_
522
+ [src/commands/doctor.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/doctor.ts)_
381
523
 
382
524
  ## `capuchoo help [COMMAND]`
383
525
 
@@ -431,7 +573,7 @@ EXAMPLES
431
573
  ```
432
574
 
433
575
  _See code:
434
- [src/commands/init.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/init.ts)_
576
+ [src/commands/init.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/init.ts)_
435
577
 
436
578
  ## `capuchoo menu`
437
579
 
@@ -451,7 +593,7 @@ EXAMPLES
451
593
  ```
452
594
 
453
595
  _See code:
454
- [src/commands/menu.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/menu.ts)_
596
+ [src/commands/menu.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/menu.ts)_
455
597
 
456
598
  ## `capuchoo org create [NAME]`
457
599
 
@@ -477,7 +619,32 @@ EXAMPLES
477
619
  ```
478
620
 
479
621
  _See code:
480
- [src/commands/org/create.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/org/create.ts)_
622
+ [src/commands/org/create.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/org/create.ts)_
623
+
624
+ ## `capuchoo org invite EMAIL ROLE`
625
+
626
+ Add an existing account to an organization
627
+
628
+ ```
629
+ USAGE
630
+ $ capuchoo org invite EMAIL ROLE [--org <value>]
631
+
632
+ ARGUMENTS
633
+ EMAIL Account to add
634
+ ROLE (owner|admin|member) One of owner, admin, member
635
+
636
+ FLAGS
637
+ --org=<value> Organization by name or id
638
+
639
+ DESCRIPTION
640
+ Add an existing account to an organization
641
+
642
+ EXAMPLES
643
+ $ capuchoo org invite dev@company.com member
644
+ ```
645
+
646
+ _See code:
647
+ [src/commands/org/invite.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/org/invite.ts)_
481
648
 
482
649
  ## `capuchoo org list`
483
650
 
@@ -495,7 +662,28 @@ DESCRIPTION
495
662
  ```
496
663
 
497
664
  _See code:
498
- [src/commands/org/list.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/org/list.ts)_
665
+ [src/commands/org/list.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/org/list.ts)_
666
+
667
+ ## `capuchoo org members`
668
+
669
+ List the people in an organization
670
+
671
+ ```
672
+ USAGE
673
+ $ capuchoo org members [--org <value>]
674
+
675
+ FLAGS
676
+ --org=<value> Organization by name or id
677
+
678
+ DESCRIPTION
679
+ List the people in an organization
680
+
681
+ EXAMPLES
682
+ $ capuchoo org members
683
+ ```
684
+
685
+ _See code:
686
+ [src/commands/org/members.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/org/members.ts)_
499
687
 
500
688
  ## `capuchoo setup`
501
689
 
@@ -524,7 +712,7 @@ EXAMPLES
524
712
  ```
525
713
 
526
714
  _See code:
527
- [src/commands/setup.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/setup.ts)_
715
+ [src/commands/setup.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/setup.ts)_
528
716
 
529
717
  ## `capuchoo version bump TYPE`
530
718
 
@@ -551,7 +739,7 @@ EXAMPLES
551
739
  ```
552
740
 
553
741
  _See code:
554
- [src/commands/version/bump.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/version/bump.ts)_
742
+ [src/commands/version/bump.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/version/bump.ts)_
555
743
 
556
744
  ## `capuchoo version sync`
557
745
 
@@ -577,5 +765,5 @@ EXAMPLES
577
765
  ```
578
766
 
579
767
  _See code:
580
- [src/commands/version/sync.ts](https://github.com/aybinv7/capuchoo/blob/v0.7.0/src/commands/version/sync.ts)_
768
+ [src/commands/version/sync.ts](https://github.com/aybinv7/capuchoo/blob/v0.9.0/src/commands/version/sync.ts)_
581
769
  <!-- commandsstop -->
@@ -0,0 +1,23 @@
1
+ import type { Membership } from "../services/cloud.js";
2
+ /** The email on a membership row, or the raw user id when the join came back empty. */
3
+ export declare function emailOf(member: Membership): string;
4
+ export interface Resolution {
5
+ ok: boolean;
6
+ userId?: string;
7
+ /** What to tell the caller when it failed. */
8
+ problem?: string;
9
+ }
10
+ /**
11
+ * Finds a member by email, case-insensitively.
12
+ *
13
+ * Resolved from a list the caller already has rather than by asking the server
14
+ * to look an address up: there is no such endpoint, and adding one would be an
15
+ * email enumeration surface.
16
+ */
17
+ export declare function resolveByEmail(members: Membership[], email: string): Resolution;
18
+ /** Rows for a table, widest-email-first so the roles line up. */
19
+ export declare function membershipRows(members: Membership[]): Array<{
20
+ email: string;
21
+ role: string;
22
+ }>;
23
+ //# sourceMappingURL=members.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"members.d.ts","sourceRoot":"","sources":["../../src/cli/members.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,uFAAuF;AACvF,wBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAElD;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,CAwB/E;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAI5F"}
@@ -0,0 +1,38 @@
1
+ /** The email on a membership row, or the raw user id when the join came back empty. */
2
+ export function emailOf(member) {
3
+ return member.users?.email ?? member.user_id;
4
+ }
5
+ /**
6
+ * Finds a member by email, case-insensitively.
7
+ *
8
+ * Resolved from a list the caller already has rather than by asking the server
9
+ * to look an address up: there is no such endpoint, and adding one would be an
10
+ * email enumeration surface.
11
+ */
12
+ export function resolveByEmail(members, email) {
13
+ const wanted = email.trim().toLowerCase();
14
+ if (!wanted)
15
+ return { ok: false, problem: "An email is required." };
16
+ const matches = members.filter((member) => emailOf(member).toLowerCase() === wanted);
17
+ if (matches.length === 0) {
18
+ const known = members.map(emailOf).sort();
19
+ return {
20
+ ok: false,
21
+ problem: `No member with the email "${email}". ` +
22
+ (known.length > 0 ? `Present: ${known.join(", ")}.` : "There are none yet."),
23
+ };
24
+ }
25
+ // A duplicate would make the choice arbitrary, and removing the wrong person
26
+ // is not recoverable from here.
27
+ if (matches.length > 1) {
28
+ return { ok: false, problem: `"${email}" matches ${matches.length} members. Resolve by hand.` };
29
+ }
30
+ return { ok: true, userId: matches[0].user_id };
31
+ }
32
+ /** Rows for a table, widest-email-first so the roles line up. */
33
+ export function membershipRows(members) {
34
+ return members
35
+ .map((member) => ({ email: emailOf(member), role: member.role }))
36
+ .sort((a, b) => a.email.localeCompare(b.email));
37
+ }
38
+ //# sourceMappingURL=members.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"members.js","sourceRoot":"","sources":["../../src/cli/members.ts"],"names":[],"mappings":"AAEA,uFAAuF;AACvF,MAAM,UAAU,OAAO,CAAC,MAAkB;IACxC,OAAO,MAAM,CAAC,KAAK,EAAE,KAAK,IAAI,MAAM,CAAC,OAAO,CAAC;AAC/C,CAAC;AASD;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,OAAqB,EAAE,KAAa;IACjE,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAE1C,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAC;IAEpE,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,CAAC;IAErF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1C,OAAO;YACL,EAAE,EAAE,KAAK;YACT,OAAO,EACL,6BAA6B,KAAK,KAAK;gBACvC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,qBAAqB,CAAC;SAC/E,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,gCAAgC;IAChC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,KAAK,aAAa,OAAO,CAAC,MAAM,4BAA4B,EAAE,CAAC;IAClG,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAE,CAAC,OAAO,EAAE,CAAC;AACnD,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,cAAc,CAAC,OAAqB;IAClD,OAAO,OAAO;SACX,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;SAChE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AACpD,CAAC"}
@@ -0,0 +1,41 @@
1
+ /** What the CLI can see about this machine and this directory. */
2
+ export interface OnboardingFacts {
3
+ signedIn: boolean;
4
+ /** Whether this looks like an application at all - a package.json. */
5
+ inAppDirectory: boolean;
6
+ /** Whether .capuchoo/project.json exists. */
7
+ linked: boolean;
8
+ updaterInstalled: boolean;
9
+ /** null when not fetched, or when the backend could not be reached. */
10
+ channels?: Array<{
11
+ name: string;
12
+ servingBundle: boolean;
13
+ }> | null | undefined;
14
+ }
15
+ export interface NextStep {
16
+ /** oclif command id. */
17
+ command: string;
18
+ label: string;
19
+ why: string;
20
+ }
21
+ export type Stage = "signed-out" | "not-an-app" | "unlinked" | "incomplete" | "no-channels" | "nothing-served" | "ready";
22
+ export interface OnboardingState {
23
+ stage: Stage;
24
+ /** The one thing to do now, or null when there is nothing to recommend. */
25
+ next: NextStep | null;
26
+ }
27
+ /**
28
+ * The single next action, so the first thing someone sees is a step rather than
29
+ * a list to guess from.
30
+ *
31
+ * Order is by dependency: nothing can be done unsigned, nothing app-specific
32
+ * without an app, nothing published without a channel.
33
+ */
34
+ export declare function resolveOnboarding(facts: OnboardingFacts): OnboardingState;
35
+ /** Commands worth hiding in this state, by oclif id. */
36
+ export declare function hiddenCommands(facts: OnboardingFacts): Set<string>;
37
+ /** A label that reads correctly for the current state, or null to keep the default. */
38
+ export declare function labelFor(command: string, facts: OnboardingFacts): string | null;
39
+ /** Whether only the next step should be offered, rather than the whole menu. */
40
+ export declare function isBlocked(state: OnboardingState): boolean;
41
+ //# sourceMappingURL=onboarding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onboarding.d.ts","sourceRoot":"","sources":["../../src/cli/onboarding.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,sEAAsE;IACtE,cAAc,EAAE,OAAO,CAAC;IACxB,6CAA6C;IAC7C,MAAM,EAAE,OAAO,CAAC;IAChB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;CAC/E;AAED,MAAM,WAAW,QAAQ;IACvB,wBAAwB;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,KAAK,GACb,YAAY,GACZ,YAAY,GACZ,UAAU,GACV,YAAY,GACZ,aAAa,GACb,gBAAgB,GAChB,OAAO,CAAC;AAEZ,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,KAAK,CAAC;IACb,2EAA2E;IAC3E,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,eAAe,CAsEzE;AAED,wDAAwD;AACxD,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,GAAG,CAAC,MAAM,CAAC,CAQlE;AAED,uFAAuF;AACvF,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAI/E;AAED,gFAAgF;AAChF,wBAAgB,SAAS,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAEzD"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The single next action, so the first thing someone sees is a step rather than
3
+ * a list to guess from.
4
+ *
5
+ * Order is by dependency: nothing can be done unsigned, nothing app-specific
6
+ * without an app, nothing published without a channel.
7
+ */
8
+ export function resolveOnboarding(facts) {
9
+ if (!facts.signedIn) {
10
+ return {
11
+ stage: "signed-out",
12
+ next: {
13
+ command: "auth:login",
14
+ label: "Sign in",
15
+ why: "Nothing can reach the backend until this machine has a credential.",
16
+ },
17
+ };
18
+ }
19
+ if (!facts.inAppDirectory) {
20
+ return { stage: "not-an-app", next: null };
21
+ }
22
+ if (!facts.linked) {
23
+ return {
24
+ stage: "unlinked",
25
+ next: {
26
+ command: "setup",
27
+ label: "Set this app up",
28
+ why: "Installs what the app needs and links it to Capuchoo.",
29
+ },
30
+ };
31
+ }
32
+ if (!facts.updaterInstalled) {
33
+ return {
34
+ stage: "incomplete",
35
+ next: {
36
+ command: "setup",
37
+ label: "Finish setting up",
38
+ why: "Linked, but the app cannot check for updates without @capuchoo/updater.",
39
+ },
40
+ };
41
+ }
42
+ // Channels are the only fact that needs the network, so an unknown value must
43
+ // not be reported as a problem.
44
+ if (facts.channels && facts.channels.length === 0) {
45
+ return {
46
+ stage: "no-channels",
47
+ next: {
48
+ command: "channel:create",
49
+ label: "Create a channel",
50
+ why: "There is nowhere to publish to yet.",
51
+ },
52
+ };
53
+ }
54
+ if (facts.channels && !facts.channels.some((channel) => channel.servingBundle)) {
55
+ return {
56
+ stage: "nothing-served",
57
+ next: {
58
+ command: "deploy:ota",
59
+ label: "Publish the first bundle",
60
+ why: "Every channel exists but none is serving anything, so devices get no update.",
61
+ },
62
+ };
63
+ }
64
+ return {
65
+ stage: "ready",
66
+ next: {
67
+ command: "deploy:ota",
68
+ label: "Publish an update",
69
+ why: "Everything is wired up.",
70
+ },
71
+ };
72
+ }
73
+ /** Commands worth hiding in this state, by oclif id. */
74
+ export function hiddenCommands(facts) {
75
+ if (!facts.signedIn) {
76
+ // Signing in is the only useful action, and `config` is how someone points
77
+ // at a different backend before trying.
78
+ return new Set(["auth:logout", "auth:whoami"]);
79
+ }
80
+ return new Set(["auth:login"]);
81
+ }
82
+ /** A label that reads correctly for the current state, or null to keep the default. */
83
+ export function labelFor(command, facts) {
84
+ if (command === "setup" && facts.linked && facts.updaterInstalled)
85
+ return "setup (re-run)";
86
+ if (command === "init" && facts.linked)
87
+ return "init (re-link)";
88
+ return null;
89
+ }
90
+ /** Whether only the next step should be offered, rather than the whole menu. */
91
+ export function isBlocked(state) {
92
+ return state.stage === "signed-out";
93
+ }
94
+ //# sourceMappingURL=onboarding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onboarding.js","sourceRoot":"","sources":["../../src/cli/onboarding.ts"],"names":[],"mappings":"AAkCA;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAsB;IACtD,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QACpB,OAAO;YACL,KAAK,EAAE,YAAY;YACnB,IAAI,EAAE;gBACJ,OAAO,EAAE,YAAY;gBACrB,KAAK,EAAE,SAAS;gBAChB,GAAG,EAAE,oEAAoE;aAC1E;SACF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;QAC1B,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC7C,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO;YACL,KAAK,EAAE,UAAU;YACjB,IAAI,EAAE;gBACJ,OAAO,EAAE,OAAO;gBAChB,KAAK,EAAE,iBAAiB;gBACxB,GAAG,EAAE,uDAAuD;aAC7D;SACF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE,CAAC;QAC5B,OAAO;YACL,KAAK,EAAE,YAAY;YACnB,IAAI,EAAE;gBACJ,OAAO,EAAE,OAAO;gBAChB,KAAK,EAAE,mBAAmB;gBAC1B,GAAG,EAAE,yEAAyE;aAC/E;SACF,CAAC;IACJ,CAAC;IAED,8EAA8E;IAC9E,gCAAgC;IAChC,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,OAAO;YACL,KAAK,EAAE,aAAa;YACpB,IAAI,EAAE;gBACJ,OAAO,EAAE,gBAAgB;gBACzB,KAAK,EAAE,kBAAkB;gBACzB,GAAG,EAAE,qCAAqC;aAC3C;SACF,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;QAC/E,OAAO;YACL,KAAK,EAAE,gBAAgB;YACvB,IAAI,EAAE;gBACJ,OAAO,EAAE,YAAY;gBACrB,KAAK,EAAE,0BAA0B;gBACjC,GAAG,EAAE,8EAA8E;aACpF;SACF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,KAAK,EAAE,OAAO;QACd,IAAI,EAAE;YACJ,OAAO,EAAE,YAAY;YACrB,KAAK,EAAE,mBAAmB;YAC1B,GAAG,EAAE,yBAAyB;SAC/B;KACF,CAAC;AACJ,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,cAAc,CAAC,KAAsB;IACnD,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QACpB,2EAA2E;QAC3E,wCAAwC;QACxC,OAAO,IAAI,GAAG,CAAC,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,OAAO,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;AACjC,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAE,KAAsB;IAC9D,IAAI,OAAO,KAAK,OAAO,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,gBAAgB;QAAE,OAAO,gBAAgB,CAAC;IAC3F,IAAI,OAAO,KAAK,MAAM,IAAI,KAAK,CAAC,MAAM;QAAE,OAAO,gBAAgB,CAAC;IAChE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,SAAS,CAAC,KAAsB;IAC9C,OAAO,KAAK,CAAC,KAAK,KAAK,YAAY,CAAC;AACtC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { Command } from "@oclif/core";
2
+ import type { ResolvedProjectConfig } from "@capuchoo/core";
3
+ import { CloudClient } from "../services/cloud.js";
4
+ export declare const APP_ROLES: readonly ["admin", "developer", "tester", "viewer"];
5
+ export declare const ORG_ROLES: readonly ["owner", "admin", "member"];
6
+ /** Roles that may ship code, mirroring PUBLISH_ROLES on the server. */
7
+ export declare const PUBLISH_ROLES: readonly string[];
8
+ export interface LinkedApp {
9
+ cloud: CloudClient;
10
+ project: ResolvedProjectConfig;
11
+ }
12
+ /** Credentials plus a linked app, or a clear error naming the missing half. */
13
+ export declare function requireLinkedApp(command: Command): LinkedApp;
14
+ /** Credentials only, for commands that are not about one app. */
15
+ export declare function requireCloud(command: Command): CloudClient;
16
+ //# sourceMappingURL=team.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team.d.ts","sourceRoot":"","sources":["../../src/cli/team.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAE5D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,eAAO,MAAM,SAAS,qDAAsD,CAAC;AAC7E,eAAO,MAAM,SAAS,uCAAwC,CAAC;AAE/D,uEAAuE;AACvE,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAA2B,CAAC;AAEvE,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,qBAAqB,CAAC;CAChC;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAe5D;AAED,iEAAiE;AACjE,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,CAO1D"}
@@ -0,0 +1,32 @@
1
+ import chalk from "chalk";
2
+ import { normaliseProjectConfig } from "@capuchoo/core";
3
+ import { CloudClient } from "../services/cloud.js";
4
+ import { readProjectConfig, resolveCredentials } from "../utils/config.js";
5
+ export const APP_ROLES = ["admin", "developer", "tester", "viewer"];
6
+ export const ORG_ROLES = ["owner", "admin", "member"];
7
+ /** Roles that may ship code, mirroring PUBLISH_ROLES on the server. */
8
+ export const PUBLISH_ROLES = ["admin", "developer"];
9
+ /** Credentials plus a linked app, or a clear error naming the missing half. */
10
+ export function requireLinkedApp(command) {
11
+ const credentials = resolveCredentials();
12
+ if (!credentials) {
13
+ command.error(`Not signed in. Run ${chalk.cyan("capuchoo auth login")}.`);
14
+ }
15
+ const raw = readProjectConfig(process.cwd());
16
+ if (!raw) {
17
+ command.error(`This directory is not linked to an app. Run ${chalk.cyan("capuchoo init")}.`);
18
+ }
19
+ return {
20
+ cloud: new CloudClient(credentials.endpoint, credentials.apiKey),
21
+ project: normaliseProjectConfig(raw),
22
+ };
23
+ }
24
+ /** Credentials only, for commands that are not about one app. */
25
+ export function requireCloud(command) {
26
+ const credentials = resolveCredentials();
27
+ if (!credentials) {
28
+ command.error(`Not signed in. Run ${chalk.cyan("capuchoo auth login")}.`);
29
+ }
30
+ return new CloudClient(credentials.endpoint, credentials.apiKey);
31
+ }
32
+ //# sourceMappingURL=team.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team.js","sourceRoot":"","sources":["../../src/cli/team.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE3E,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,CAAU,CAAC;AAC7E,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAU,CAAC;AAE/D,uEAAuE;AACvE,MAAM,CAAC,MAAM,aAAa,GAAsB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;AAOvE,+EAA+E;AAC/E,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,MAAM,WAAW,GAAG,kBAAkB,EAAE,CAAC;IACzC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,CAAC,KAAK,CAAC,sBAAsB,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC;IAC5E,CAAC;IAED,MAAM,GAAG,GAAG,iBAAiB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC7C,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,CAAC,KAAK,CAAC,+CAA+C,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IAC/F,CAAC;IAED,OAAO;QACL,KAAK,EAAE,IAAI,WAAW,CAAC,WAAW,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC;QAChE,OAAO,EAAE,sBAAsB,CAAC,GAAG,CAAC;KACrC,CAAC;AACJ,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,MAAM,WAAW,GAAG,kBAAkB,EAAE,CAAC;IACzC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,CAAC,KAAK,CAAC,sBAAsB,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC;IAC5E,CAAC;IAED,OAAO,IAAI,WAAW,CAAC,WAAW,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;AACnE,CAAC"}