arcane-os 0.2.2 → 0.3.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 (117) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +8 -8
  3. package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +29 -22
  4. package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +203 -0
  5. package/browser-runtime/ai/browser-kokoro-worker.mjs +11 -2
  6. package/browser-runtime/ai/browser-speech-artifacts.mjs +3230 -397
  7. package/browser-runtime/ai/browser-speech-providers.mjs +1141 -157
  8. package/browser-runtime/ai/browser-speech.mjs +2 -0
  9. package/browser-runtime/ai/browser-whisper-worker.mjs +11 -2
  10. package/browser-runtime/ai/model-controller.mjs +285 -95
  11. package/browser-runtime/ai/speech-worker-client.mjs +247 -32
  12. package/browser-runtime/ai/speech-worker-runtime.mjs +2310 -167
  13. package/browser-runtime/event-manager.mjs +1097 -1
  14. package/docs/architecture.md +2 -2
  15. package/docs/event-manager.md +155 -27
  16. package/docs/reference/README.md +27 -27
  17. package/docs/reference/ai/browser-speech-package-authority.json +835 -0
  18. package/docs/reference/ai/browser-speech.md +1162 -246
  19. package/docs/reference/ai/browser-wasm.md +18 -7
  20. package/docs/reference/availability-and-normalization.md +6 -3
  21. package/docs/reference/behavioral-testing.md +29 -6
  22. package/docs/reference/cli.md +117 -9
  23. package/docs/reference/core/arcane-ai-contracts.md +1 -1
  24. package/docs/reference/event-manager.md +577 -32
  25. package/docs/reference/inventory/package-api.json +478 -2
  26. package/docs/reference/inventory/runtime-components.json +108 -44
  27. package/docs/reference/inventory/runtime-modules.json +131 -53
  28. package/docs/reference/mail.md +316 -0
  29. package/docs/reference/protocols.md +157 -43
  30. package/docs/reference/runtime-components.md +258 -83
  31. package/docs/reference/runtime-modules.md +613 -77
  32. package/docs/reference/sdk-api.md +1014 -25
  33. package/package.json +5 -4
  34. package/runtime/ARCANE_RUNTIME_RELEASE.json +145 -140
  35. package/runtime/arcane/components/app-bar.html +34 -13
  36. package/runtime/arcane/components/assistant-panel.html +110 -57
  37. package/runtime/arcane/components/calculator.html +7 -4
  38. package/runtime/arcane/components/chart.html +58 -17
  39. package/runtime/arcane/components/chat.html +606 -136
  40. package/runtime/arcane/components/conversation-view.html +13 -6
  41. package/runtime/arcane/components/dashboard-config.html +96 -59
  42. package/runtime/arcane/components/data-maintenance.html +69 -14
  43. package/runtime/arcane/components/data-view.html +53 -7
  44. package/runtime/arcane/components/directory-picker.html +118 -32
  45. package/runtime/arcane/components/document-inspector.html +47 -10
  46. package/runtime/arcane/components/file-drop.html +81 -35
  47. package/runtime/arcane/components/file-inspector.html +72 -22
  48. package/runtime/arcane/components/file-manager.html +374 -79
  49. package/runtime/arcane/components/integration-settings.html +12 -5
  50. package/runtime/arcane/components/local-ai-status.html +48 -19
  51. package/runtime/arcane/components/markdown-document.html +161 -68
  52. package/runtime/arcane/components/markdown-editor.html +110 -33
  53. package/runtime/arcane/components/media-embed.html +8 -5
  54. package/runtime/arcane/components/modal.html +15 -5
  55. package/runtime/arcane/components/output-panel.html +28 -23
  56. package/runtime/arcane/components/preferences-form.html +22 -4
  57. package/runtime/arcane/components/record-timeline.html +18 -2
  58. package/runtime/arcane/components/relationship-board.html +23 -3
  59. package/runtime/arcane/components/screen-capture.html +10 -4
  60. package/runtime/arcane/components/source-code-viewer.html +76 -9
  61. package/runtime/arcane/components/source-explanation.html +23 -3
  62. package/runtime/arcane/components/speech.html +462 -384
  63. package/runtime/arcane/components/summary-strip.html +22 -11
  64. package/runtime/arcane/components/table.html +39 -21
  65. package/runtime/arcane/components/task-progress.html +79 -21
  66. package/runtime/arcane/components/terminal-workspace.html +7 -4
  67. package/runtime/arcane/components/theme-editor.html +7 -3
  68. package/runtime/arcane/components/unified-inbox.html +9 -4
  69. package/runtime/arcane/components/voice-transcription.html +639 -98
  70. package/runtime/arcane/components/weather-widget.html +5 -3
  71. package/runtime/arcane/components/web-navigator.html +48 -8
  72. package/runtime/arcane/entities/Chat.js +1 -1
  73. package/runtime/arcane/entities/User.js +110 -23
  74. package/runtime/arcane/modules/AI.js +2109 -130
  75. package/runtime/arcane/modules/AIProviderRuntime.js +720 -13
  76. package/runtime/arcane/modules/AIRuntimeState.js +109 -52
  77. package/runtime/arcane/modules/ApiModelDatabase.js +390 -17
  78. package/runtime/arcane/modules/BrowserTestSuite.js +205 -28
  79. package/runtime/arcane/modules/CalculatorEngine.js +63 -3
  80. package/runtime/arcane/modules/CommunicationAppController.js +588 -28
  81. package/runtime/arcane/modules/CommunicationHub.js +590 -10
  82. package/runtime/arcane/modules/ComponentContracts.js +470 -0
  83. package/runtime/arcane/modules/ConversationTimebox.js +152 -33
  84. package/runtime/arcane/modules/DBLS.js +40 -7
  85. package/runtime/arcane/modules/DBOPFS.js +35 -11
  86. package/runtime/arcane/modules/DataMaintenance.js +12 -2
  87. package/runtime/arcane/modules/Errors.js +65 -7
  88. package/runtime/arcane/modules/HTMLImport.js +198 -14
  89. package/runtime/arcane/modules/LocalAIReadinessController.js +208 -29
  90. package/runtime/arcane/modules/Mail.js +738 -115
  91. package/runtime/arcane/modules/MailOutbox.mjs +1395 -0
  92. package/runtime/arcane/modules/MailTransport.mjs +197 -39
  93. package/runtime/arcane/modules/Ollama.js +36 -1
  94. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +583 -7
  95. package/runtime/arcane/modules/PreferenceStore.js +367 -33
  96. package/runtime/arcane/modules/RecordReviewStore.js +322 -23
  97. package/runtime/arcane/modules/ScreenCapture.js +1397 -15
  98. package/runtime/arcane/modules/SpeechPlayback.js +438 -41
  99. package/runtime/arcane/modules/TerminalClient.js +277 -12
  100. package/runtime/arcane/modules/ThemeBootstrap.js +80 -6
  101. package/runtime/arcane/modules/ThemeManager.js +39 -7
  102. package/runtime/arcane/modules/TimeGuard.js +131 -20
  103. package/runtime/arcane/modules/WaitForComponent.js +386 -33
  104. package/schemas/arcane-lock.schema.json +2 -2
  105. package/src/cli/main.mjs +435 -9
  106. package/src/event-manager.mjs +1097 -1
  107. package/src/import-map.mjs +21 -3
  108. package/src/index.mjs +13 -0
  109. package/src/installed-sdk-runtime.mjs +112 -0
  110. package/src/mail-api.mjs +22 -0
  111. package/src/mail-credentials.mjs +667 -0
  112. package/src/mail-server.mjs +1769 -0
  113. package/src/mail.mjs +261 -0
  114. package/src/sdk-browser-runtime.mjs +85 -41
  115. package/src/testing-loader.mjs +7 -0
  116. package/src/toolchain.mjs +3 -0
  117. package/src/workspace.mjs +1 -1
package/src/cli/main.mjs CHANGED
@@ -22,6 +22,12 @@ const VALUE_OPTIONS=new Set([
22
22
  'scope',
23
23
  'test-file',
24
24
  'artifact',
25
+ 'profile',
26
+ 'from',
27
+ 'origin',
28
+ 'allow-to',
29
+ 'report-key',
30
+ 'request-timeout',
25
31
  'output'
26
32
  ]);
27
33
  const FLAG_OPTIONS=new Set([
@@ -30,9 +36,17 @@ const FLAG_OPTIONS=new Set([
30
36
  'dry-run',
31
37
  'require-local-ai',
32
38
  'overwrite',
39
+ 'secret-stdin',
40
+ 'app-key-stdin',
41
+ 'report-stdin',
33
42
  'help',
34
43
  'version'
35
44
  ]);
45
+ const REPORTABLE_COMMANDS=new Set([
46
+ 'build','bundle','check','dev','doctor','help','import-map','init','mail',
47
+ 'native-doctor','native-prepare','new','package','repo','run','targets','test',
48
+ 'update-check','verify','verify-bundle','version'
49
+ ]);
36
50
 
37
51
  export const HELP_TEXT=`Arcane OS application SDK ${SDK_VERSION}
38
52
 
@@ -57,6 +71,11 @@ Usage:
57
71
  ${CLI_NAME} update-check
58
72
  ${CLI_NAME} targets
59
73
  ${CLI_NAME} repo status|pull|push
74
+ ${CLI_NAME} mail key set <profile> [--secret-stdin]
75
+ ${CLI_NAME} mail key status <profile>
76
+ ${CLI_NAME} mail key delete <profile>
77
+ ${CLI_NAME} mail send --profile <profile> --from <address> --report-key <id> --report-stdin [--request-timeout <ms>]
78
+ ${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>]
60
79
 
61
80
  Development:
62
81
  --sdk-runtime-source <sdk-root> Dev-only live SDK checkout; omitted preserves the workspace runtime mode.
@@ -99,7 +118,7 @@ function extractOutput(argv){
99
118
  remaining.push(argument);
100
119
  }
101
120
  if(!OUTPUT_MODES.includes(output)){
102
- usage(`Invalid --output value: ${String(output)}. Expected human, json, or ndjson.`);
121
+ usage('Invalid --output value. Expected human, json, or ndjson.');
103
122
  }
104
123
  return {output,remaining};
105
124
  }
@@ -126,7 +145,7 @@ export function parseCliArguments(argv){
126
145
  continue;
127
146
  }
128
147
  if(!VALUE_OPTIONS.has(name)){
129
- usage(`Unknown option: ${argument}.`);
148
+ usage('Unknown command-line option.');
130
149
  }
131
150
  const value=equal===-1?argv[++index]:argument.slice(equal+1);
132
151
  if(value===undefined||value===''||(equal===-1&&value.startsWith('-'))){
@@ -154,6 +173,254 @@ function readPort(value,defaultValue){
154
173
  return port;
155
174
  }
156
175
 
176
+ function readRequestTimeout(value,defaultValue){
177
+ if(value===undefined){
178
+ return defaultValue;
179
+ }
180
+ if(!/^\d+$/u.test(value)){
181
+ usage(`Invalid request timeout: ${value}.`);
182
+ }
183
+ const timeout=Number(value);
184
+ if(!Number.isSafeInteger(timeout)||timeout<1_000||timeout>600_000){
185
+ usage('Mail request timeout must be an integer between 1000 and 600000 milliseconds.');
186
+ }
187
+ return timeout;
188
+ }
189
+
190
+ const MAX_MAIL_SECRET_BYTES=8_192;
191
+ const MAX_MAIL_REPORT_BYTES=52*1024*1024;
192
+
193
+ function normalizedSecret(value){
194
+ const secret=String(value??'').trim();
195
+ if(!secret||Buffer.byteLength(secret,'utf8')>MAX_MAIL_SECRET_BYTES){
196
+ usage(`Mail credential input must contain 1-${MAX_MAIL_SECRET_BYTES} UTF-8 bytes.`);
197
+ }
198
+ return secret;
199
+ }
200
+
201
+ function readPipedMailSecret(input,signal){
202
+ return new Promise((resolve,reject)=>{
203
+ const chunks=[];
204
+ let byteLength=0;
205
+ let settled=false;
206
+
207
+ const cleanup=function cleanupMailSecretRead(){
208
+ input.removeListener('data',onData);
209
+ input.removeListener('end',onEnd);
210
+ input.removeListener('error',onError);
211
+ signal?.removeEventListener('abort',onAbort);
212
+ };
213
+ const finish=function finishMailSecretRead(callback,value){
214
+ if(settled)return;
215
+ settled=true;
216
+ cleanup();
217
+ callback(value);
218
+ };
219
+ const onData=function collectMailSecretChunk(chunk){
220
+ const bytes=Buffer.isBuffer(chunk)?chunk:Buffer.from(chunk);
221
+ byteLength+=bytes.byteLength;
222
+ if(byteLength>MAX_MAIL_SECRET_BYTES+2){
223
+ finish(reject,new ArcaneError(
224
+ ERROR_CODES.usage,
225
+ `Mail credential input cannot exceed ${MAX_MAIL_SECRET_BYTES} UTF-8 bytes.`
226
+ ));
227
+ return;
228
+ }
229
+ chunks.push(bytes);
230
+ };
231
+ const onEnd=function finishPipedMailSecret(){
232
+ try{
233
+ finish(resolve,normalizedSecret(Buffer.concat(chunks).toString('utf8')));
234
+ }catch(error){
235
+ finish(reject,error);
236
+ }
237
+ };
238
+ const onError=function failPipedMailSecret(error){
239
+ finish(reject,new ArcaneError(
240
+ ERROR_CODES.operationFailed,
241
+ 'Unable to read the mail credential from standard input.',
242
+ {cause:error}
243
+ ));
244
+ };
245
+ const onAbort=function cancelPipedMailSecret(){
246
+ finish(reject,new ArcaneError(
247
+ ERROR_CODES.cancelled,
248
+ 'Mail credential entry was cancelled.',
249
+ {cause:signal?.reason,exitCode:130}
250
+ ));
251
+ };
252
+
253
+ input.on('data',onData);
254
+ input.once('end',onEnd);
255
+ input.once('error',onError);
256
+ signal?.addEventListener('abort',onAbort,{once:true});
257
+ if(signal?.aborted){
258
+ onAbort();
259
+ }else{
260
+ input.resume?.();
261
+ }
262
+ });
263
+ }
264
+
265
+ function readMaskedMailSecret(input,output,signal,label,stdinOption){
266
+ if(!input?.isTTY||typeof input.setRawMode!=='function'||typeof output?.write!=='function'){
267
+ usage(`Interactive mail credential entry requires a terminal; use ${stdinOption} for piped input.`);
268
+ }
269
+ return new Promise((resolve,reject)=>{
270
+ let secret='';
271
+ let settled=false;
272
+ const priorRaw=Boolean(input.isRaw);
273
+
274
+ const cleanup=function cleanupMaskedMailSecret(){
275
+ input.removeListener('data',onData);
276
+ signal?.removeEventListener('abort',onAbort);
277
+ input.setRawMode(priorRaw);
278
+ if(!priorRaw)input.pause?.();
279
+ };
280
+ const finish=function finishMaskedMailSecret(callback,value){
281
+ if(settled)return;
282
+ settled=true;
283
+ cleanup();
284
+ output.write('\n');
285
+ callback(value);
286
+ };
287
+ const onAbort=function cancelMaskedMailSecret(){
288
+ finish(reject,new ArcaneError(
289
+ ERROR_CODES.cancelled,
290
+ 'Mail credential entry was cancelled.',
291
+ {cause:signal?.reason,exitCode:130}
292
+ ));
293
+ };
294
+ const onData=function collectMaskedMailSecret(chunk){
295
+ for(const character of String(chunk)){
296
+ if(character==='\u0003'){
297
+ onAbort();
298
+ return;
299
+ }
300
+ if(character==='\r'||character==='\n'){
301
+ try{
302
+ finish(resolve,normalizedSecret(secret));
303
+ }catch(error){
304
+ finish(reject,error);
305
+ }
306
+ return;
307
+ }
308
+ if(character==='\b'||character==='\u007f'){
309
+ secret=Array.from(secret).slice(0,-1).join('');
310
+ continue;
311
+ }
312
+ if(character>=' '&&character!=='\u007f'){
313
+ const candidate=secret+character;
314
+ if(Buffer.byteLength(candidate,'utf8')<=MAX_MAIL_SECRET_BYTES){
315
+ secret=candidate;
316
+ }
317
+ }
318
+ }
319
+ };
320
+
321
+ output.write(`${label} (input hidden): `);
322
+ input.setRawMode(true);
323
+ input.on('data',onData);
324
+ signal?.addEventListener('abort',onAbort,{once:true});
325
+ input.resume();
326
+ if(signal?.aborted)onAbort();
327
+ });
328
+ }
329
+
330
+ export function readMailSecretInput({input=process.stdin,output=process.stderr,
331
+ secretStdin=false,signal,label='Resend API key',stdinOption='--secret-stdin'}={}){
332
+ if(secretStdin&&input?.isTTY){
333
+ usage(`${stdinOption} requires redirected or piped input; omit it for hidden interactive entry.`);
334
+ }
335
+ return secretStdin
336
+ ? readPipedMailSecret(input,signal)
337
+ : readMaskedMailSecret(input,output,signal,label,stdinOption);
338
+ }
339
+
340
+ function readPipedMailReport(input,signal){
341
+ return new Promise((resolve,reject)=>{
342
+ const chunks=[];
343
+ let byteLength=0;
344
+ let settled=false;
345
+
346
+ const cleanup=function cleanupMailReportRead(){
347
+ input.removeListener('data',onData);
348
+ input.removeListener('end',onEnd);
349
+ input.removeListener('error',onError);
350
+ signal?.removeEventListener('abort',onAbort);
351
+ };
352
+ const finish=function finishMailReportRead(callback,value){
353
+ if(settled)return;
354
+ settled=true;
355
+ cleanup();
356
+ callback(value);
357
+ };
358
+ const onData=function collectMailReportChunk(chunk){
359
+ const bytes=Buffer.isBuffer(chunk)?chunk:Buffer.from(chunk);
360
+ byteLength+=bytes.byteLength;
361
+ if(byteLength>MAX_MAIL_REPORT_BYTES){
362
+ finish(reject,new ArcaneError(
363
+ ERROR_CODES.usage,
364
+ `Mail report input cannot exceed ${MAX_MAIL_REPORT_BYTES} UTF-8 bytes.`
365
+ ));
366
+ return;
367
+ }
368
+ chunks.push(bytes);
369
+ };
370
+ const onEnd=function finishPipedMailReport(){
371
+ try{
372
+ const serialized=Buffer.concat(chunks).toString('utf8');
373
+ let report;
374
+ try{
375
+ report=JSON.parse(serialized);
376
+ }catch{
377
+ usage('Mail report input must be one valid JSON object.');
378
+ }
379
+ if(!report||typeof report!=='object'||Array.isArray(report)){
380
+ usage('Mail report input must be one valid JSON object.');
381
+ }
382
+ finish(resolve,report);
383
+ }catch(error){
384
+ finish(reject,error);
385
+ }
386
+ };
387
+ const onError=function failPipedMailReport(error){
388
+ finish(reject,new ArcaneError(
389
+ ERROR_CODES.operationFailed,
390
+ 'Unable to read the mail report from standard input.',
391
+ {cause:error}
392
+ ));
393
+ };
394
+ const onAbort=function cancelPipedMailReport(){
395
+ finish(reject,new ArcaneError(
396
+ ERROR_CODES.cancelled,
397
+ 'Mail report input was cancelled.',
398
+ {cause:signal?.reason,exitCode:130}
399
+ ));
400
+ };
401
+
402
+ input.on('data',onData);
403
+ input.once('end',onEnd);
404
+ input.once('error',onError);
405
+ signal?.addEventListener('abort',onAbort,{once:true});
406
+ if(signal?.aborted){
407
+ onAbort();
408
+ }else{
409
+ input.resume?.();
410
+ }
411
+ });
412
+ }
413
+
414
+ export function readMailReportInput({input=process.stdin,reportStdin=false,signal}={}){
415
+ if(!reportStdin){
416
+ usage('mail send requires --report-stdin.');
417
+ }
418
+ if(input?.isTTY){
419
+ usage('--report-stdin requires redirected or piped input.');
420
+ }
421
+ return readPipedMailReport(input,signal);
422
+ }
423
+
157
424
  function readScope(value){
158
425
  const scope=value??'app';
159
426
  if(!['app','shared'].includes(scope)){
@@ -173,7 +440,7 @@ function inferredAppId(workspaceRoot){
173
440
 
174
441
  function noExtraPositionals(command,positionals,expected=0){
175
442
  if(positionals.length>expected){
176
- usage(`Unexpected argument for ${command}: ${positionals[expected]}.`);
443
+ usage(`Unexpected argument for ${command}.`);
177
444
  }
178
445
  }
179
446
 
@@ -184,7 +451,10 @@ function operationOptions(command,parsed,cwd){
184
451
  const scope=readScope(values.scope);
185
452
  const common={workspaceRoot,appId:values.app,scope};
186
453
  if(scope==='shared'&&!['test','check'].includes(command)){
187
- usage(`--scope shared is not supported by ${command}; shared development cannot package or build app output.`);
454
+ usage(
455
+ `--scope shared is not supported by ${reportableCommand(command)}; `
456
+ +'shared development cannot package or build app output.'
457
+ );
188
458
  }
189
459
  if(values['test-file']!==undefined&&command!=='test'){
190
460
  usage('--test-file is supported only by test --scope shared.');
@@ -198,6 +468,19 @@ function operationOptions(command,parsed,cwd){
198
468
  if(flags.has('overwrite')&&command!=='bundle'){
199
469
  usage('--overwrite is supported only by bundle.');
200
470
  }
471
+ const mailOnlyOptions=['profile','from','origin','allow-to','report-key','request-timeout'];
472
+ if(command!=='mail'&&mailOnlyOptions.some(name=>values[name]!==undefined)){
473
+ usage(`Mail options are supported only by the mail command.`);
474
+ }
475
+ if(command!=='mail'&&flags.has('secret-stdin')){
476
+ usage('--secret-stdin is supported only by mail key set.');
477
+ }
478
+ if(command!=='mail'&&flags.has('app-key-stdin')){
479
+ usage('--app-key-stdin is supported only by mail serve.');
480
+ }
481
+ if(command!=='mail'&&flags.has('report-stdin')){
482
+ usage('--report-stdin is supported only by mail send.');
483
+ }
201
484
 
202
485
  if(command==='new'){
203
486
  const appId=positionals[0];
@@ -342,7 +625,98 @@ function operationOptions(command,parsed,cwd){
342
625
  }
343
626
  return {...common,action};
344
627
  }
345
- usage(`Unknown command: ${command}. Run ${CLI_NAME} --help for usage.`);
628
+ if(command==='mail'){
629
+ const area=positionals[0];
630
+ if(area==='key'){
631
+ noExtraPositionals(command,positionals,3);
632
+ const action=positionals[1];
633
+ const profile=positionals[2];
634
+ if(!['set','status','delete'].includes(action)||!profile){
635
+ usage('mail key requires set, status, or delete followed by one profile id.');
636
+ }
637
+ if(values.profile!==undefined||values.from!==undefined||values.app!==undefined
638
+ ||values.origin!==undefined
639
+ ||values['allow-to']!==undefined||values.host!==undefined||values.port!==undefined
640
+ ||values['report-key']!==undefined||values['request-timeout']!==undefined){
641
+ usage('mail key accepts only its profile argument and optional --secret-stdin for set.');
642
+ }
643
+ if(flags.has('secret-stdin')&&action!=='set'){
644
+ usage('--secret-stdin is supported only by mail key set.');
645
+ }
646
+ if(flags.has('app-key-stdin')){
647
+ usage('--app-key-stdin is supported only by mail serve.');
648
+ }
649
+ if(flags.has('report-stdin')){
650
+ usage('--report-stdin is supported only by mail send.');
651
+ }
652
+ return {
653
+ action:`key-${action}`,
654
+ profile,
655
+ secretStdin:flags.has('secret-stdin'),
656
+ };
657
+ }
658
+ if(area==='serve'){
659
+ noExtraPositionals(command,positionals,1);
660
+ if(flags.has('secret-stdin')){
661
+ usage('--secret-stdin is not supported by mail serve.');
662
+ }
663
+ if(flags.has('report-stdin')||values['report-key']!==undefined){
664
+ usage('--report-stdin and --report-key are supported only by mail send.');
665
+ }
666
+ for(const [name,value]of Object.entries({
667
+ profile:values.profile,
668
+ from:values.from,
669
+ app:values.app,
670
+ origin:values.origin,
671
+ 'allow-to':values['allow-to'],
672
+ })){
673
+ if(!value)usage(`mail serve requires --${name} <value>.`);
674
+ }
675
+ return {
676
+ action:'serve',
677
+ profile:values.profile,
678
+ from:values.from,
679
+ appId:values.app,
680
+ origin:values.origin,
681
+ allowTo:values['allow-to'],
682
+ appKeyStdin:flags.has('app-key-stdin'),
683
+ host:values.host??'127.0.0.1',
684
+ port:readPort(values.port,8025),
685
+ requestTimeout:readRequestTimeout(values['request-timeout'],30_000),
686
+ };
687
+ }
688
+ if(area==='send'){
689
+ noExtraPositionals(command,positionals,1);
690
+ if(flags.has('secret-stdin')||flags.has('app-key-stdin')){
691
+ usage('mail send accepts report input only through --report-stdin.');
692
+ }
693
+ if(values.app!==undefined||values.origin!==undefined
694
+ ||values['allow-to']!==undefined||values.host!==undefined
695
+ ||values.port!==undefined){
696
+ usage('mail send does not accept gateway server options.');
697
+ }
698
+ for(const [name,value]of Object.entries({
699
+ profile:values.profile,
700
+ from:values.from,
701
+ 'report-key':values['report-key'],
702
+ })){
703
+ if(!value)usage(`mail send requires --${name} <value>.`);
704
+ }
705
+ if(!flags.has('report-stdin')){
706
+ usage('mail send requires --report-stdin.');
707
+ }
708
+ return {
709
+ action:'send',
710
+ profile:values.profile,
711
+ from:values.from,
712
+ reportKey:values['report-key'],
713
+ reportStdin:true,
714
+ requestTimeout:readRequestTimeout(values['request-timeout'],30_000),
715
+ };
716
+ }
717
+ usage('mail requires key set|status|delete <profile>, send, or serve.');
718
+ }
719
+ usage(`Unknown command. Run ${CLI_NAME} --help for usage.`);
346
720
  }
347
721
 
348
722
  const NATIVE_REQUESTS=Object.freeze({
@@ -460,6 +834,10 @@ function commandFromArgs(args){
460
834
  return 'help';
461
835
  }
462
836
 
837
+ function reportableCommand(command){
838
+ return REPORTABLE_COMMANDS.has(command)?command:'unknown';
839
+ }
840
+
463
841
  function serverSummary(result){
464
842
  return {
465
843
  target:result.target??'browser',
@@ -468,6 +846,9 @@ function serverSummary(result){
468
846
  host:result.host,
469
847
  port:result.port,
470
848
  url:result.url,
849
+ ...(result.callerAuthentication
850
+ ?{callerAuthentication:result.callerAuthentication}
851
+ :{}),
471
852
  ...(result.runtimeMode?{runtimeMode:result.runtimeMode}:{}),
472
853
  ...(result.runtime?{runtime:result.runtime}:{}),
473
854
  ...(result.verified?{verified:result.verified}:{})
@@ -517,6 +898,7 @@ async function waitForServer(result,signal,reporter){
517
898
 
518
899
  export async function runCli(argv=process.argv.slice(2),{
519
900
  cwd=process.cwd(),
901
+ stdin=process.stdin,
520
902
  stdout=process.stdout,
521
903
  stderr=process.stderr,
522
904
  execute=executeOperation,
@@ -529,8 +911,13 @@ export async function runCli(argv=process.argv.slice(2),{
529
911
  try{
530
912
  const extracted=extractOutput([...argv]);
531
913
  command=commandFromArgs(extracted.remaining);
532
- reporter=createReporter({command,output:extracted.output,stdout,stderr});
533
- reporter.accept({argv:extracted.remaining});
914
+ reporter=createReporter({
915
+ command:reportableCommand(command),
916
+ output:extracted.output,
917
+ stdout,
918
+ stderr
919
+ });
920
+ reporter.accept();
534
921
  process.once('SIGINT',onSignal);
535
922
  process.once('SIGTERM',onSignal);
536
923
 
@@ -544,9 +931,47 @@ export async function runCli(argv=process.argv.slice(2),{
544
931
  return 0;
545
932
  }
546
933
 
934
+ const operation=operationOptions(command,parsed,cwd);
935
+ if(command==='mail'&&operation.action==='key-set'){
936
+ operation.readSecret=function readMailCredentialForOperation(){
937
+ if(reporter.output!=='human'&&!operation.secretStdin){
938
+ usage('Structured output requires mail key set --secret-stdin.');
939
+ }
940
+ return readMailSecretInput({
941
+ input:stdin,
942
+ output:stderr,
943
+ secretStdin:operation.secretStdin,
944
+ signal:controller.signal,
945
+ });
946
+ };
947
+ }
948
+ if(command==='mail'&&operation.action==='serve'){
949
+ operation.readAppKey=function readMailGatewayAppKeyForOperation(){
950
+ if(reporter.output!=='human'&&!operation.appKeyStdin){
951
+ usage('Structured output requires mail serve --app-key-stdin.');
952
+ }
953
+ return readMailSecretInput({
954
+ input:stdin,
955
+ output:stderr,
956
+ secretStdin:operation.appKeyStdin,
957
+ signal:controller.signal,
958
+ label:'Mail gateway app key',
959
+ stdinOption:'--app-key-stdin',
960
+ });
961
+ };
962
+ }
963
+ if(command==='mail'&&operation.action==='send'){
964
+ operation.readReport=function readMailReportForOperation(){
965
+ return readMailReportInput({
966
+ input:stdin,
967
+ reportStdin:operation.reportStdin,
968
+ signal:controller.signal,
969
+ });
970
+ };
971
+ }
547
972
  const options=await pairNativeProvider(
548
973
  command,
549
- operationOptions(command,parsed,cwd),
974
+ operation,
550
975
  loadNativeProvider,
551
976
  {signal:controller.signal,onEvent:event=>reporter.forward(event)}
552
977
  );
@@ -555,7 +980,8 @@ export async function runCli(argv=process.argv.slice(2),{
555
980
  signal:controller.signal,
556
981
  onEvent:event=>reporter.forward(event)
557
982
  });
558
- const finalResult=(command==='dev'||(command==='run'&&(options.target??'browser')==='browser'))
983
+ const finalResult=(command==='dev'||(command==='run'&&(options.target??'browser')==='browser')
984
+ ||(command==='mail'&&options.action==='serve'))
559
985
  ?await waitForServer(result,controller.signal,reporter)
560
986
  :result;
561
987
  reporter.complete(finalResult);