arcane-os 0.15.2 → 0.16.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.
@@ -7,22 +7,18 @@ import {
7
7
  sendMailReport,
8
8
  } from './MailTransport.mjs';
9
9
 
10
- let userInstance=null;
11
-
12
- function completeResult(value){return value;}
13
-
14
10
  const MAIL_TYPES=new Set(['error','report','crisis_detected']);
15
- const MAIL_OUTBOX_EVENTS=completeResult([
11
+ const MAIL_OUTBOX_EVENTS=[
16
12
  'mail-outbox-state',
17
13
  'mail-outbox-delivery',
18
14
  'mail-outbox-drain'
19
- ]);
15
+ ];
20
16
  const PENDING_OUTBOX_STATES=new Set(['queued','sending','retry_wait']);
21
- const NATIVE_MAIL_RESPONSE_STATUS_CODES=completeResult({
17
+ const NATIVE_MAIL_RESPONSE_STATUS_CODES={
22
18
  accepted:202,
23
19
  delivery_uncertain:207,
24
20
  partially_accepted:207
25
- });
21
+ };
26
22
  const NATIVE_MAIL_REQUEST_ID_PATTERN=/^[A-Za-z0-9-]+$/;
27
23
  const NATIVE_MAIL_UNCERTAIN_ERROR_CODES=new Set([
28
24
  'ARCANE_REQUEST_TIMEOUT',
@@ -37,7 +33,6 @@ const NATIVE_MAIL_RETRYABLE_ERROR_CODES=new Set([
37
33
  'ARCANE_TRANSPORT_UNAVAILABLE'
38
34
  ]);
39
35
  const EMAIL_PATTERN=/^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/i;
40
- const ARCANE_APP_ID_PATTERN=/^[a-z0-9](?:[a-z0-9-]{0,62})$/;
41
36
 
42
37
  function codedError(message,code,ErrorType=Error){
43
38
  const error=new ErrorType(message);
@@ -71,10 +66,10 @@ function linkedMailSignal(primary,secondary){
71
66
  if(signal&&!signals.includes(signal)) signals.push(signal);
72
67
  }
73
68
  if(signals.length===0){
74
- return completeResult({signal:null,dispose:function disposeEmptyMailSignal(){}});
69
+ return {signal:null,dispose:function disposeEmptyMailSignal(){}};
75
70
  }
76
71
  if(signals.length===1){
77
- return completeResult({signal:signals[0],dispose:function disposeSingleMailSignal(){}});
72
+ return {signal:signals[0],dispose:function disposeSingleMailSignal(){}};
78
73
  }
79
74
  const controller=new AbortController();
80
75
  const listeners=[];
@@ -86,14 +81,14 @@ function linkedMailSignal(primary,secondary){
86
81
  if(signal.aborted) listener();
87
82
  else signal.addEventListener('abort',listener,{once:true});
88
83
  }
89
- return completeResult({
84
+ return {
90
85
  signal:controller.signal,
91
86
  dispose:function disposeLinkedMailSignal(){
92
87
  for(const entry of listeners){
93
88
  entry.signal.removeEventListener('abort',entry.listener);
94
89
  }
95
90
  }
96
- });
91
+ };
97
92
  }
98
93
 
99
94
  function waitForMailOperation(operation,signal){
@@ -190,58 +185,23 @@ async function loadRequiredMailStorage(){
190
185
  return storage;
191
186
  }
192
187
 
193
- async function loadOptionalMailUser(injectedUser){
188
+ async function resolveMailUserEntity(injectedUser){
194
189
  if(injectedUser!==undefined) return injectedUser;
195
- if(!userInstance){
196
- const {default:UserEntity}=await import('../entities/User.js');
197
- userInstance=new UserEntity();
198
- }
199
- return userInstance;
190
+ if(globalThis.window?.user) return globalThis.window.user;
191
+ const {default:UserEntity}=await import('../entities/User.js');
192
+ return new UserEntity();
200
193
  }
201
194
 
202
195
  function declaredApplicationId(document=globalThis.document){
203
- const value=document?.querySelector?.('meta[name="arcane-app-id"]')?.content?.trim();
204
- return ARCANE_APP_ID_PATTERN.test(value||'') ? value:'';
205
- }
206
-
207
- function declaredMailBaseDomain(document=globalThis.document){
208
- return normalizeBaseDomain(
209
- document?.querySelector?.('meta[name="arcane-mail-base-domain"]')?.content,
210
- );
211
- }
212
-
213
- function normalizeBaseDomain(value){
214
- const domain=String(value||'').trim().toLowerCase();
215
- if(!domain||domain.length>253
216
- || !/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(domain)){
217
- return '';
218
- }
219
- return domain;
196
+ const appName=document?.querySelector?.('meta[name="arcane-app-id"]')?.content;
197
+ return is.string(appName)?appName:'';
220
198
  }
221
199
 
222
- function hostedBaseDomain(hostname,configuredBaseDomain=''){
223
- const configured=normalizeBaseDomain(configuredBaseDomain);
224
- return configured&&(hostname===configured||hostname.endsWith(`.${configured}`)) ? configured:'';
225
- }
226
-
227
- function defaultMailEndpoint(location=globalThis.location,baseDomain=''){
200
+ function defaultMailEndpoint(location=globalThis.location){
228
201
  if(!location||!['http:','https:'].includes(location.protocol)){
229
202
  return '';
230
203
  }
231
- const hostname=String(location.hostname||'').toLowerCase();
232
- const loopback=['localhost','127.0.0.1','::1','[::1]'].includes(hostname);
233
- if(loopback&&location.protocol==='http:'&&String(location.port||'')!=='8025'){
234
- const authority=hostname==='::1' ? '[::1]':hostname;
235
- return `http://${authority}:8025/v1/mail`;
236
- }
237
- if(loopback){
238
- return new URL('/v1/mail',location.origin).href;
239
- }
240
- const root=hostedBaseDomain(hostname,baseDomain);
241
- if(!root){
242
- return '';
243
- }
244
- return `https://mail.${root}/v1/mail`;
204
+ return new URL('/v1/mail',location.origin).href;
245
205
  }
246
206
 
247
207
  export function resolveMailConfig(
@@ -249,19 +209,19 @@ export function resolveMailConfig(
249
209
  {document=globalThis.document,location=globalThis.location}={}
250
210
  ){
251
211
  const supplied=config&&is.object(config)&&!is.array(config)?config:{};
252
- const appName=is.string(supplied.appName)&&supplied.appName.trim()
253
- ? supplied.appName.trim()
212
+ const appName=is.string(supplied.appName)
213
+ ? supplied.appName
254
214
  : declaredApplicationId(document);
255
- return completeResult({
256
- appName:ARCANE_APP_ID_PATTERN.test(appName) ? appName:'',
257
- appKey:is.string(supplied.appKey) ? supplied.appKey:'',
258
- endpoint:is.string(supplied.endpoint)&&supplied.endpoint.trim()
259
- ? supplied.endpoint.trim()
260
- : defaultMailEndpoint(location,supplied.baseDomain||declaredMailBaseDomain(document)),
215
+ return {
216
+ appName,
217
+ subscriptionKey:supplied.subscriptionKey,
218
+ endpoint:is.string(supplied.endpoint)
219
+ ? supplied.endpoint
220
+ : defaultMailEndpoint(location),
261
221
  requestTimeout:is.finite(supplied.requestTimeout)
262
222
  ? supplied.requestTimeout
263
223
  : null,
264
- });
224
+ };
265
225
  }
266
226
 
267
227
  function escapeHtml(value){
@@ -384,12 +344,12 @@ function publicSendResult(record){
384
344
  }
385
345
 
386
346
  function safeDrainDetail(summary){
387
- return completeResult({
347
+ return {
388
348
  ...summary,
389
349
  records:[...summary.records],
390
350
  invalidRecords:[...summary.invalidRecords],
391
- states:completeResult({...summary.states})
392
- });
351
+ states:{...summary.states}
352
+ };
393
353
  }
394
354
 
395
355
  function normalizeMailOptions(options){
@@ -505,7 +465,7 @@ class Mail {
505
465
  );
506
466
  }
507
467
  this.appName=resolved.appName;
508
- this.appKey=resolved.appKey;
468
+ this.subscriptionKey=resolved.subscriptionKey;
509
469
  this.endpoint=resolved.endpoint;
510
470
  this.requestTimeout=resolved.requestTimeout;
511
471
  this.#storageInjected=options.storageInjected;
@@ -599,8 +559,19 @@ class Mail {
599
559
  async #transportDelivery(request){
600
560
  if(this.#deliver) return this.#deliver(request);
601
561
  if(this.endpoint){
562
+ throwIfMailAborted(request.signal);
563
+ let subscriptionKey=this.subscriptionKey;
564
+ if(subscriptionKey===undefined){
565
+ const user=await resolveMailUserEntity(this.#user);
566
+ throwIfMailAborted(request.signal);
567
+ if(is.function(user?.load)){
568
+ await waitForMailOperation(user.load(),request.signal);
569
+ }
570
+ subscriptionKey=user?.subscription_key;
571
+ }
572
+ throwIfMailAborted(request.signal);
602
573
  return sendMailReport({
603
- appKey:this.appKey,
574
+ subscriptionKey,
604
575
  appName:this.appName,
605
576
  endpoint:this.endpoint,
606
577
  report:request.report,
@@ -697,7 +668,7 @@ class Mail {
697
668
  async #optionalProfile(){
698
669
  const fallback={email:'',language:'',phone:'',username:''};
699
670
  try{
700
- const user=await loadOptionalMailUser(this.#user);
671
+ const user=await resolveMailUserEntity(this.#user);
701
672
  if(!user) return fallback;
702
673
  try{
703
674
  await user.load?.();
@@ -176,8 +176,8 @@ function normalizedFailure(value){
176
176
  if(!isPlainRecord(value)){
177
177
  fail('Mail outbox failure record is invalid.','MAIL_OUTBOX_RECORD_INVALID');
178
178
  }
179
- const code=safeString(value.code,SAFE_CODE_PATTERN);
180
- if(!code||!is.boolean(value.retryable)||!is.boolean(value.uncertain)){
179
+ const code=value.code;
180
+ if(!is.string(code)||!code||!is.boolean(value.retryable)||!is.boolean(value.uncertain)){
181
181
  fail('Mail outbox failure record is invalid.','MAIL_OUTBOX_RECORD_INVALID');
182
182
  }
183
183
  return {
@@ -211,12 +211,12 @@ function normalizedResult(value,{invalidCode='MAIL_OUTBOX_RECORD_INVALID'}={}){
211
211
  classification:safeString(value.classification,SAFE_CODE_PATTERN),
212
212
  acceptanceAuthority,
213
213
  requestId:safeString(value.requestId,SAFE_ID_PATTERN),
214
- providerId:safeString(value.providerId,SAFE_ID_PATTERN),
214
+ providerId:is.string(value.providerId)?value.providerId:null,
215
215
  providerStatus:is.string(value.providerStatus)
216
216
  ||is.safeInteger(value.providerStatus)
217
217
  ?value.providerStatus
218
218
  :null,
219
- providerCode:safeString(value.providerCode,SAFE_CODE_PATTERN),
219
+ providerCode:is.string(value.providerCode)?value.providerCode:null,
220
220
  statusCode:safeStatusCode(value.statusCode),
221
221
  retryAfterSeconds:retryAfterSeconds(value.retryAfterSeconds)
222
222
  };
@@ -451,7 +451,7 @@ function deliveryFailure(error){
451
451
  name:is.string(error?.name)?error.name:'Error',
452
452
  message:is.string(error?.message)?error.message:String(error??''),
453
453
  ...(is.string(error?.stack)?{stack:error.stack}:{}),
454
- code:safeString(error?.code,SAFE_CODE_PATTERN)||'MAIL_DELIVERY_FAILED',
454
+ code:is.string(error?.code)&&error.code?error.code:'MAIL_DELIVERY_FAILED',
455
455
  statusCode:safeStatusCode(error?.statusCode),
456
456
  retryable,
457
457
  uncertain,
@@ -1,10 +1,7 @@
1
1
  import Is from 'strong-type';
2
2
  const is=new Is(false);
3
3
 
4
- const REPORT_KEY_PATTERN=/^[a-zA-Z0-9._:-]+$/;
5
4
  const REQUEST_ID_PATTERN=/^[a-zA-Z0-9-]+$/;
6
- const PROVIDER_ID_PATTERN=/^[a-zA-Z0-9._:-]+$/;
7
- const ERROR_CODE_PATTERN=/^[a-zA-Z0-9._:-]+$/;
8
5
  const RETRYABLE_STATUS_CODES=new Set([408,425,429,500,502,503,504]);
9
6
  const NON_RETRYABLE_RATE_CODES=new Set(['daily_quota_exceeded','monthly_quota_exceeded']);
10
7
  const RESPONSE_CONTRACT={
@@ -39,12 +36,8 @@ export function normalizeMailEndpoint(endpoint,base=globalThis.location?.href){
39
36
  }catch{
40
37
  throw new Error('Mail endpoint is invalid');
41
38
  }
42
- const loopback=['localhost','127.0.0.1','[::1]'].includes(url.hostname.toLowerCase());
43
- if(url.protocol!=='https:'&&!(url.protocol==='http:'&&loopback)){
44
- throw new Error('Mail endpoint must use HTTPS or loopback HTTP');
45
- }
46
- if(url.username||url.password||url.search||url.hash){
47
- throw new Error('Mail endpoint must not contain credentials, a query, or a fragment');
39
+ if(url.protocol!=='https:'&&url.protocol!=='http:'){
40
+ throw new Error('Mail endpoint must use HTTP or HTTPS');
48
41
  }
49
42
  return url.href;
50
43
  }
@@ -125,7 +118,7 @@ function parseDeliveryResponse(response,responseText){
125
118
  }
126
119
  }
127
120
  if(body.providerId!==undefined
128
- && (!is.string(body.providerId)||!PROVIDER_ID_PATTERN.test(body.providerId))){
121
+ && (!is.string(body.providerId)||!body.providerId)){
129
122
  throw invalidSuccessResponse(response,responseText);
130
123
  }
131
124
 
@@ -150,7 +143,7 @@ function parseRejection(response,responseText){
150
143
  ? body.error
151
144
  : body;
152
145
  const rawCode=source?.code;
153
- const code=is.string(rawCode)&&ERROR_CODE_PATTERN.test(rawCode)
146
+ const code=is.string(rawCode)
154
147
  ? rawCode
155
148
  : `MAIL_HTTP_${String(response.status)}`;
156
149
  let retryable=is.boolean(source?.retryable)
@@ -218,7 +211,7 @@ function requestBodyFrom({report,serializedReport}){
218
211
  }
219
212
 
220
213
  export async function sendMailReport({
221
- appKey,
214
+ subscriptionKey,
222
215
  appName,
223
216
  endpoint,
224
217
  fetchImpl=globalThis.fetch,
@@ -232,18 +225,18 @@ export async function sendMailReport({
232
225
  throw new MailTransportError('Mail transport is unavailable',{code:'MAIL_UNAVAILABLE'});
233
226
  }
234
227
  const resolvedEndpoint=normalizeMailEndpoint(endpoint);
235
- if(!is.string(appName)||!/^[a-z0-9](?:[a-z0-9-]{0,62})$/.test(appName)){
236
- throw new Error('Mail application identity is invalid');
228
+ if(!is.string(appName)||!appName){
229
+ throw new Error('Mail application name is required');
237
230
  }
238
- if(!is.string(reportKey)||!REPORT_KEY_PATTERN.test(reportKey)){
239
- throw new Error('Mail report key must contain safe characters');
231
+ if(!is.string(reportKey)||!reportKey){
232
+ throw new Error('Mail report key is required');
240
233
  }
241
234
  if(requestTimeout!==null&&requestTimeout!==undefined
242
235
  &&(!is.safeInteger(requestTimeout)||requestTimeout<1)){
243
236
  throw new Error('Mail request timeout must be a positive integer');
244
237
  }
245
- if(appKey!==undefined&&appKey!==null&&!is.string(appKey)){
246
- throw new Error('Mail application key must be a string');
238
+ if(subscriptionKey!==undefined&&subscriptionKey!==null&&!is.string(subscriptionKey)){
239
+ throw new TypeError('Mail subscription key must be a string');
247
240
  }
248
241
  if(signal!==undefined&&!(signal instanceof AbortSignal)){
249
242
  throw new TypeError('Mail signal must be an AbortSignal');
@@ -255,9 +248,7 @@ export async function sendMailReport({
255
248
  'Idempotency-Key':reportKey,
256
249
  'X-Mail-App':appName,
257
250
  };
258
- if(is.string(appKey)&&appKey){
259
- headers['X-Mail-Key']=appKey;
260
- }
251
+ if(subscriptionKey)headers.Authorization=`Bearer ${subscriptionKey}`;
261
252
 
262
253
  const controller=new AbortController();
263
254
  let timedOut=false;
package/src/cli/main.mjs CHANGED
@@ -46,7 +46,6 @@ const FLAG_OPTIONS=new Set([
46
46
  'require-local-ai',
47
47
  'overwrite',
48
48
  'secret-stdin',
49
- 'app-key-stdin',
50
49
  'report-stdin',
51
50
  'help',
52
51
  'version'
@@ -85,8 +84,8 @@ Usage:
85
84
  ${CLI_NAME} mail key set <profile> [--secret-stdin]
86
85
  ${CLI_NAME} mail key status <profile>
87
86
  ${CLI_NAME} mail key delete <profile>
88
- ${CLI_NAME} mail send --profile <profile> --from <address> --report-key <id> --report-stdin [--request-timeout <ms>]
89
- ${CLI_NAME} mail serve --profile <profile> --from <address> --app <id> --origin <origin> [--allow-to <addresses>] [--app-key-stdin] [--host 127.0.0.1] [--port 8025] [--request-timeout <ms>]
87
+ ${CLI_NAME} mail send --profile <profile> [--from <address>] --report-key <id> --report-stdin [--request-timeout <ms>]
88
+ ${CLI_NAME} mail serve --profile <profile> [--from <address>] [--app <label>] [--origin <origin>] [--allow-to <addresses>] [--host 0.0.0.0] [--port 8025] [--request-timeout <ms>]
90
89
 
91
90
  Development:
92
91
  --public Bind dev to all IPv4 interfaces (0.0.0.0) and print network URLs.
@@ -190,7 +189,7 @@ function readPort(value,defaultValue){
190
189
  return port;
191
190
  }
192
191
 
193
- function readRequestTimeout(value){
192
+ function readMailRequestTimeout(value){
194
193
  if(value===undefined)return undefined;
195
194
  if(!/^\d+$/u.test(value)){
196
195
  usage(`Invalid request timeout: ${value}.`);
@@ -205,7 +204,7 @@ function readRequestTimeout(value){
205
204
  return timeout;
206
205
  }
207
206
 
208
- function normalizedSecret(value){
207
+ function normalizeMailCredentialInput(value){
209
208
  const secret=String(value??'').trim();
210
209
  if(!secret){
211
210
  usage('Mail credential input must not be empty.');
@@ -235,7 +234,7 @@ function readPipedMailSecret(input,signal){
235
234
  };
236
235
  const onEnd=function finishPipedMailSecret(){
237
236
  try{
238
- finish(resolve,normalizedSecret(Buffer.concat(chunks).toString('utf8')));
237
+ finish(resolve,normalizeMailCredentialInput(Buffer.concat(chunks).toString('utf8')));
239
238
  }catch(error){
240
239
  finish(reject,error);
241
240
  }
@@ -304,7 +303,7 @@ function readMaskedMailSecret(input,output,signal,label,stdinOption){
304
303
  }
305
304
  if(character==='\r'||character==='\n'){
306
305
  try{
307
- finish(resolve,normalizedSecret(secret));
306
+ finish(resolve,normalizeMailCredentialInput(secret));
308
307
  }catch(error){
309
308
  finish(reject,error);
310
309
  }
@@ -500,9 +499,6 @@ function operationOptions(command,parsed,cwd){
500
499
  if(command!=='mail'&&flags.has('secret-stdin')){
501
500
  usage('--secret-stdin is supported only by mail key set.');
502
501
  }
503
- if(command!=='mail'&&flags.has('app-key-stdin')){
504
- usage('--app-key-stdin is supported only by mail serve.');
505
- }
506
502
  if(command!=='mail'&&flags.has('report-stdin')){
507
503
  usage('--report-stdin is supported only by mail send.');
508
504
  }
@@ -674,9 +670,6 @@ function operationOptions(command,parsed,cwd){
674
670
  if(flags.has('secret-stdin')&&action!=='set'){
675
671
  usage('--secret-stdin is supported only by mail key set.');
676
672
  }
677
- if(flags.has('app-key-stdin')){
678
- usage('--app-key-stdin is supported only by mail serve.');
679
- }
680
673
  if(flags.has('report-stdin')){
681
674
  usage('--report-stdin is supported only by mail send.');
682
675
  }
@@ -694,14 +687,7 @@ function operationOptions(command,parsed,cwd){
694
687
  if(flags.has('report-stdin')||values['report-key']!==undefined){
695
688
  usage('--report-stdin and --report-key are supported only by mail send.');
696
689
  }
697
- for(const [name,value]of Object.entries({
698
- profile:values.profile,
699
- from:values.from,
700
- app:values.app,
701
- origin:values.origin
702
- })){
703
- if(!value)usage(`mail serve requires --${name} <value>.`);
704
- }
690
+ if(!values.profile)usage('mail serve requires --profile <value>.');
705
691
  return {
706
692
  action:'serve',
707
693
  profile:values.profile,
@@ -709,15 +695,14 @@ function operationOptions(command,parsed,cwd){
709
695
  appId:values.app,
710
696
  origin:values.origin,
711
697
  allowTo:values['allow-to'],
712
- appKeyStdin:flags.has('app-key-stdin'),
713
- host:values.host??'127.0.0.1',
698
+ host:values.host??'0.0.0.0',
714
699
  port:readPort(values.port,8025),
715
- requestTimeout:readRequestTimeout(values['request-timeout']),
700
+ requestTimeout:readMailRequestTimeout(values['request-timeout']),
716
701
  };
717
702
  }
718
703
  if(area==='send'){
719
704
  noExtraPositionals(command,positionals,1);
720
- if(flags.has('secret-stdin')||flags.has('app-key-stdin')){
705
+ if(flags.has('secret-stdin')){
721
706
  usage('mail send accepts report input only through --report-stdin.');
722
707
  }
723
708
  if(values.app!==undefined||values.origin!==undefined
@@ -727,7 +712,6 @@ function operationOptions(command,parsed,cwd){
727
712
  }
728
713
  for(const [name,value]of Object.entries({
729
714
  profile:values.profile,
730
- from:values.from,
731
715
  'report-key':values['report-key'],
732
716
  })){
733
717
  if(!value)usage(`mail send requires --${name} <value>.`);
@@ -741,7 +725,7 @@ function operationOptions(command,parsed,cwd){
741
725
  from:values.from,
742
726
  reportKey:values['report-key'],
743
727
  reportStdin:true,
744
- requestTimeout:readRequestTimeout(values['request-timeout']),
728
+ requestTimeout:readMailRequestTimeout(values['request-timeout']),
745
729
  };
746
730
  }
747
731
  usage('mail requires key set|status|delete <profile>, send, or serve.');
@@ -879,10 +863,8 @@ function serverSummary(result){
879
863
  ...(result.httpOrigin===undefined?{}:{httpOrigin:result.httpOrigin}),
880
864
  ...(result.httpUrl===undefined?{}:{httpUrl:result.httpUrl}),
881
865
  ...(result.protocol===undefined?{}:{protocol:result.protocol}),
866
+ ...(result.callerAuthentication===undefined?{}:{callerAuthentication:result.callerAuthentication}),
882
867
  ...(result.networkUrls===undefined?{}:{networkUrls:result.networkUrls}),
883
- ...(result.callerAuthentication
884
- ?{callerAuthentication:result.callerAuthentication}
885
- :{}),
886
868
  ...(result.runtimeMode?{runtimeMode:result.runtimeMode}:{}),
887
869
  ...(result.runtime?{runtime:result.runtime}:{}),
888
870
  ...(result.verified?{verified:result.verified}:{})
@@ -891,7 +873,8 @@ function serverSummary(result){
891
873
 
892
874
  async function waitForServer(result,signal,reporter){
893
875
  const readyMessage=[
894
- `Development server ready at ${result.url}`,
876
+ `${result.target==='mail'?'Mail':'Development'} server ready at ${result.url}`,
877
+ ...(result.target==='mail'?[`Subscription verification: ${result.callerAuthentication==='subscription'?'configured':'disabled'}`]:[]),
895
878
  ...(result.httpUrl && result.protocol !== 'http:' ? [`HTTP redirect: ${result.httpUrl}`] : []),
896
879
  ...(result.networkUrls??[]).map(function networkAddress(url){return `Network: ${url}`;})
897
880
  ].join('\n');
@@ -984,21 +967,6 @@ export async function runCli(argv=process.argv.slice(2),{
984
967
  });
985
968
  };
986
969
  }
987
- if(command==='mail'&&operation.action==='serve'){
988
- operation.readAppKey=function readMailGatewayAppKeyForOperation(){
989
- if(reporter.output!=='human'&&!operation.appKeyStdin){
990
- usage('Structured output requires mail serve --app-key-stdin.');
991
- }
992
- return readMailSecretInput({
993
- input:stdin,
994
- output:stderr,
995
- secretStdin:operation.appKeyStdin,
996
- signal:controller.signal,
997
- label:'Mail gateway app key',
998
- stdinOption:'--app-key-stdin',
999
- });
1000
- };
1001
- }
1002
970
  if(command==='mail'&&operation.action==='send'){
1003
971
  operation.readReport=function readMailReportForOperation(){
1004
972
  return readMailReportInput({