@autocode-cli/autocode 0.1.31 → 0.1.33

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 (58) hide show
  1. package/README.md +33 -3
  2. package/dist/cli/commands/health.d.ts +9 -0
  3. package/dist/cli/commands/health.d.ts.map +1 -0
  4. package/dist/cli/commands/health.js +101 -0
  5. package/dist/cli/commands/health.js.map +1 -0
  6. package/dist/cli/commands/init.js +1 -1
  7. package/dist/cli/commands/move.d.ts.map +1 -1
  8. package/dist/cli/commands/move.js +14 -7
  9. package/dist/cli/commands/move.js.map +1 -1
  10. package/dist/cli/parser.d.ts.map +1 -1
  11. package/dist/cli/parser.js +2 -0
  12. package/dist/cli/parser.js.map +1 -1
  13. package/dist/core/hierarchy.d.ts +24 -0
  14. package/dist/core/hierarchy.d.ts.map +1 -1
  15. package/dist/core/hierarchy.js +76 -0
  16. package/dist/core/hierarchy.js.map +1 -1
  17. package/dist/core/issue.d.ts.map +1 -1
  18. package/dist/core/issue.js +11 -3
  19. package/dist/core/issue.js.map +1 -1
  20. package/dist/core/loop-detector.d.ts +45 -0
  21. package/dist/core/loop-detector.d.ts.map +1 -0
  22. package/dist/core/loop-detector.js +81 -0
  23. package/dist/core/loop-detector.js.map +1 -0
  24. package/dist/core/validation.d.ts +26 -0
  25. package/dist/core/validation.d.ts.map +1 -0
  26. package/dist/core/validation.js +67 -0
  27. package/dist/core/validation.js.map +1 -0
  28. package/dist/core/workflow.d.ts.map +1 -1
  29. package/dist/core/workflow.js +34 -1
  30. package/dist/core/workflow.js.map +1 -1
  31. package/dist/server/api.d.ts.map +1 -1
  32. package/dist/server/api.js +23 -2
  33. package/dist/server/api.js.map +1 -1
  34. package/dist/server/dashboard/pages/changelog.js +2 -2
  35. package/dist/server/dashboard/pages/changelog.js.map +1 -1
  36. package/dist/server/dashboard/pages/main-dashboard.js +1 -1
  37. package/dist/server/dashboard/pages/main-dashboard.js.map +1 -1
  38. package/dist/server/dashboard/pages/new-issue.d.ts.map +1 -1
  39. package/dist/server/dashboard/pages/new-issue.js +347 -3
  40. package/dist/server/dashboard/pages/new-issue.js.map +1 -1
  41. package/dist/server/dashboard/pages/new-issue.test.js +475 -0
  42. package/dist/server/dashboard/pages/new-issue.test.js.map +1 -1
  43. package/dist/server/watcher.d.ts +1 -1
  44. package/dist/server/watcher.js +1 -1
  45. package/dist/services/issue-io.d.ts +1 -1
  46. package/dist/services/issue-io.js +1 -1
  47. package/dist/services/monitoring.d.ts +47 -0
  48. package/dist/services/monitoring.d.ts.map +1 -0
  49. package/dist/services/monitoring.js +136 -0
  50. package/dist/services/monitoring.js.map +1 -0
  51. package/dist/types/index.d.ts +1 -0
  52. package/dist/types/index.d.ts.map +1 -1
  53. package/dist/types/index.js.map +1 -1
  54. package/dist/utils/config.js +2 -2
  55. package/dist/utils/config.js.map +1 -1
  56. package/package.json +2 -1
  57. package/templates/prompts/in-progress.en.md +25 -9
  58. package/templates/prompts/in-progress.fr.md +25 -9
@@ -248,12 +248,114 @@ export function generateNewIssuePage(lang) {
248
248
  animation: spin 0.8s linear infinite;
249
249
  }
250
250
  @keyframes spin { to { transform: rotate(360deg); } }
251
+ @keyframes pulse-recording {
252
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7); }
253
+ 50% { box-shadow: 0 0 0 8px rgba(239, 68, 68, 0); }
254
+ }
255
+ .btn-mic {
256
+ background: var(--bg);
257
+ border: 1px solid var(--border);
258
+ color: var(--muted);
259
+ width: 42px;
260
+ height: 42px;
261
+ border-radius: 8px;
262
+ cursor: pointer;
263
+ display: flex;
264
+ align-items: center;
265
+ justify-content: center;
266
+ transition: all 0.2s;
267
+ flex-shrink: 0;
268
+ }
269
+ .btn-mic:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
270
+ .btn-mic:disabled { opacity: 0.4; cursor: not-allowed; }
271
+ .btn-mic.recording {
272
+ background: var(--red);
273
+ border-color: var(--red);
274
+ color: white;
275
+ animation: pulse-recording 1.5s ease-in-out infinite;
276
+ }
277
+ .btn-mic svg { width: 18px; height: 18px; }
278
+ .description-row {
279
+ display: flex;
280
+ gap: 8px;
281
+ align-items: flex-start;
282
+ }
283
+ .description-row .form-group { flex: 1; margin-bottom: 0; }
251
284
  .title-row {
252
285
  display: flex;
253
286
  gap: 12px;
254
287
  align-items: flex-end;
255
288
  }
256
289
  .title-row .form-group { flex: 1; margin-bottom: 0; }
290
+ .title-input-row {
291
+ display: flex;
292
+ gap: 8px;
293
+ align-items: center;
294
+ }
295
+ .title-input-row input { flex: 1; }
296
+ .autocomplete-countdown {
297
+ position: fixed;
298
+ bottom: 80px;
299
+ right: 24px;
300
+ background: var(--bg-card);
301
+ border: 1px solid var(--accent);
302
+ border-radius: 12px;
303
+ padding: 16px 20px;
304
+ z-index: 100;
305
+ display: none;
306
+ flex-direction: column;
307
+ gap: 12px;
308
+ min-width: 280px;
309
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
310
+ }
311
+ .autocomplete-countdown.show { display: flex; }
312
+ .countdown-header {
313
+ display: flex;
314
+ align-items: center;
315
+ justify-content: space-between;
316
+ gap: 12px;
317
+ }
318
+ .countdown-text {
319
+ font-size: 14px;
320
+ color: var(--fg);
321
+ }
322
+ .countdown-timer {
323
+ font-weight: 600;
324
+ color: var(--accent);
325
+ font-size: 18px;
326
+ }
327
+ .countdown-progress {
328
+ height: 4px;
329
+ background: var(--border);
330
+ border-radius: 2px;
331
+ overflow: hidden;
332
+ }
333
+ .countdown-progress-bar {
334
+ height: 100%;
335
+ background: var(--accent);
336
+ transition: width 0.1s linear;
337
+ }
338
+ .countdown-actions {
339
+ display: flex;
340
+ gap: 8px;
341
+ }
342
+ .btn-countdown {
343
+ flex: 1;
344
+ padding: 8px 12px;
345
+ border-radius: 6px;
346
+ font-size: 13px;
347
+ cursor: pointer;
348
+ border: 1px solid var(--border);
349
+ background: var(--bg);
350
+ color: var(--fg);
351
+ }
352
+ .btn-countdown:hover { border-color: var(--accent); }
353
+ .btn-countdown.primary {
354
+ background: var(--accent);
355
+ border-color: var(--accent);
356
+ color: white;
357
+ }
358
+ .btn-countdown.primary:hover { opacity: 0.9; }
257
359
  .btn-secondary {
258
360
  background: var(--bg);
259
361
  color: var(--fg);
@@ -294,7 +396,16 @@ export function generateNewIssuePage(lang) {
294
396
  <div class="title-row">
295
397
  <div class="form-group">
296
398
  <label for="issue-title" data-i18n="newIssue.titleLabel">Title *</label>
297
- <input type="text" id="issue-title" placeholder="E.g.: Fix the login bug" data-i18n-placeholder="newIssue.titlePlaceholder">
399
+ <div class="title-input-row">
400
+ <input type="text" id="issue-title" placeholder="E.g.: Fix the login bug" data-i18n-placeholder="newIssue.titlePlaceholder">
401
+ <button type="button" class="btn-mic" id="btn-mic" onclick="toggleSpeechToText()" title="Speech to text">
402
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
403
+ <path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/>
404
+ <path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
405
+ <line x1="12" x2="12" y1="19" y2="22"/>
406
+ </svg>
407
+ </button>
408
+ </div>
298
409
  </div>
299
410
  <button type="button" class="btn-autocomplete" id="btn-autocomplete" onclick="autocomplete()" title="Auto-fill fields using AI">
300
411
  <span id="autocomplete-icon">&#10024;</span>
@@ -371,6 +482,20 @@ export function generateNewIssuePage(lang) {
371
482
 
372
483
  <div class="notification" id="notification"></div>
373
484
 
485
+ <div class="autocomplete-countdown" id="autocomplete-countdown">
486
+ <div class="countdown-header">
487
+ <span class="countdown-text" data-i18n="countdown.autocompletingIn">AutoComplete in</span>
488
+ <span class="countdown-timer" id="countdown-timer">3</span>
489
+ </div>
490
+ <div class="countdown-progress">
491
+ <div class="countdown-progress-bar" id="countdown-progress-bar" style="width: 100%"></div>
492
+ </div>
493
+ <div class="countdown-actions">
494
+ <button class="btn-countdown" onclick="cancelAutocompleteCountdown()" data-i18n="countdown.cancel">Cancel</button>
495
+ <button class="btn-countdown primary" onclick="triggerAutocompleteNow()" data-i18n="countdown.now">Now</button>
496
+ </div>
497
+ </div>
498
+
374
499
  <script>
375
500
  const STORAGE_KEY = 'autocode-lang';
376
501
  let currentLang = localStorage.getItem(STORAGE_KEY) || 'fr';
@@ -407,7 +532,20 @@ export function generateNewIssuePage(lang) {
407
532
  'notify.error': 'Error',
408
533
  'notify.titleRequired': 'Title is required',
409
534
  'notify.autocompleteSuccess': 'Fields auto-filled!',
410
- 'notify.autocompleteTitleRequired': 'Enter a title first'
535
+ 'notify.autocompleteTitleRequired': 'Enter a title first',
536
+ 'mic.title': 'Speech to text',
537
+ 'mic.titleRecording': 'Recording... Click to stop',
538
+ 'mic.notSupported': 'Speech recognition not supported',
539
+ 'notify.micNotSupported': 'Speech recognition is not supported by your browser',
540
+ 'notify.micError': 'Speech recognition error',
541
+ 'notify.micErrorNetwork': 'Network error: Speech recognition requires an internet connection. Make sure you are connected and try again.',
542
+ 'notify.micErrorNotAllowed': 'Microphone access denied. Please allow microphone access in your browser settings.',
543
+ 'notify.micErrorNoSpeech': 'No speech detected. Please speak louder or closer to the microphone.',
544
+ 'notify.micRetrying': 'Network error, retrying...',
545
+ 'countdown.autocompletingIn': 'AutoComplete in',
546
+ 'countdown.cancel': 'Cancel',
547
+ 'countdown.now': 'Now',
548
+ 'countdown.seconds': 's'
411
549
  },
412
550
  fr: {
413
551
  'newIssue.title': 'Nouveau ticket',
@@ -439,7 +577,20 @@ export function generateNewIssuePage(lang) {
439
577
  'notify.error': 'Erreur',
440
578
  'notify.titleRequired': 'Le titre est requis',
441
579
  'notify.autocompleteSuccess': 'Champs remplis automatiquement !',
442
- 'notify.autocompleteTitleRequired': 'Entrez un titre d\\'abord'
580
+ 'notify.autocompleteTitleRequired': 'Entrez un titre d\\'abord',
581
+ 'mic.title': 'Dictée vocale',
582
+ 'mic.titleRecording': 'Enregistrement... Cliquer pour arrêter',
583
+ 'mic.notSupported': 'Reconnaissance vocale non supportée',
584
+ 'notify.micNotSupported': 'La reconnaissance vocale n\\'est pas supportée par votre navigateur',
585
+ 'notify.micError': 'Erreur de reconnaissance vocale',
586
+ 'notify.micErrorNetwork': 'Erreur réseau : La reconnaissance vocale nécessite une connexion internet. Vérifiez votre connexion et réessayez.',
587
+ 'notify.micErrorNotAllowed': 'Accès au microphone refusé. Veuillez autoriser l\\'accès au microphone dans les paramètres de votre navigateur.',
588
+ 'notify.micErrorNoSpeech': 'Aucune parole détectée. Parlez plus fort ou rapprochez-vous du microphone.',
589
+ 'notify.micRetrying': 'Erreur réseau, nouvelle tentative...',
590
+ 'countdown.autocompletingIn': 'AutoComplete dans',
591
+ 'countdown.cancel': 'Annuler',
592
+ 'countdown.now': 'Maintenant',
593
+ 'countdown.seconds': 's'
443
594
  }
444
595
  };
445
596
 
@@ -561,6 +712,7 @@ export function generateNewIssuePage(lang) {
561
712
  currentLang = newLang;
562
713
  localStorage.setItem(STORAGE_KEY, newLang);
563
714
  updateLangUI();
715
+ updateMicButton();
564
716
  }
565
717
  });
566
718
  });
@@ -639,8 +791,200 @@ export function generateNewIssuePage(lang) {
639
791
  }
640
792
  }
641
793
 
794
+ // Speech-to-text
795
+ let recognition = null;
796
+ let isRecording = false;
797
+ let autocompleteCountdownTimer = null;
798
+ let autocompleteCountdownInterval = null;
799
+ const AUTOCOMPLETE_DELAY_SECONDS = 3;
800
+ let speechRetryCount = 0;
801
+ const MAX_SPEECH_RETRIES = 2;
802
+ const SPEECH_RETRY_DELAY_MS = 1000;
803
+
804
+ function isSpeechSupported() {
805
+ return 'SpeechRecognition' in window || 'webkitSpeechRecognition' in window;
806
+ }
807
+
808
+ function initSpeechRecognition() {
809
+ if (!isSpeechSupported()) return null;
810
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
811
+ const rec = new SpeechRecognition();
812
+ rec.continuous = true;
813
+ rec.interimResults = true;
814
+ rec.lang = currentLang === 'fr' ? 'fr-FR' : 'en-US';
815
+ return rec;
816
+ }
817
+
818
+ function updateMicButton() {
819
+ const btn = document.getElementById('btn-mic');
820
+ if (!isSpeechSupported()) {
821
+ btn.disabled = true;
822
+ btn.title = t('mic.notSupported');
823
+ } else {
824
+ btn.disabled = false;
825
+ btn.title = isRecording ? t('mic.titleRecording') : t('mic.title');
826
+ btn.classList.toggle('recording', isRecording);
827
+ }
828
+ }
829
+
830
+ function handleSpeechResult(event, titleInput, state) {
831
+ for (let i = event.resultIndex; i < event.results.length; i++) {
832
+ const result = event.results[i];
833
+ if (result.isFinal) {
834
+ const text = result[0].transcript;
835
+ state.finalTranscript += (state.finalTranscript ? ' ' : '') + text;
836
+ titleInput.value = state.finalTranscript;
837
+ }
838
+ }
839
+ }
840
+
841
+ function getErrorMessage(errorType) {
842
+ switch (errorType) {
843
+ case 'network':
844
+ return t('notify.micErrorNetwork');
845
+ case 'not-allowed':
846
+ case 'service-not-allowed':
847
+ return t('notify.micErrorNotAllowed');
848
+ case 'no-speech':
849
+ return t('notify.micErrorNoSpeech');
850
+ case 'aborted':
851
+ return null; // Don't show notification for aborted
852
+ default:
853
+ return t('notify.micError') + ': ' + errorType;
854
+ }
855
+ }
856
+
857
+ function handleSpeechError(event) {
858
+ const errorType = event.error;
859
+
860
+ // Handle network errors with retry
861
+ if (errorType === 'network' && speechRetryCount < MAX_SPEECH_RETRIES) {
862
+ speechRetryCount++;
863
+ showNotification(t('notify.micRetrying') + ' (' + speechRetryCount + '/' + MAX_SPEECH_RETRIES + ')', true);
864
+
865
+ // Retry after a delay
866
+ setTimeout(function() {
867
+ if (isRecording) {
868
+ recognition = initSpeechRecognition();
869
+ if (recognition) {
870
+ const titleInput = document.getElementById('issue-title');
871
+ const state = { finalTranscript: titleInput.value };
872
+ recognition.onresult = function(event) { handleSpeechResult(event, titleInput, state); };
873
+ recognition.onerror = handleSpeechError;
874
+ recognition.onend = handleSpeechEnd;
875
+ try {
876
+ recognition.start();
877
+ } catch (e) {
878
+ // If start fails, show the network error
879
+ showNotification(getErrorMessage('network'), true);
880
+ isRecording = false;
881
+ updateMicButton();
882
+ }
883
+ }
884
+ }
885
+ }, SPEECH_RETRY_DELAY_MS);
886
+ return;
887
+ }
888
+
889
+ // Show appropriate error message
890
+ const errorMessage = getErrorMessage(errorType);
891
+ if (errorMessage) {
892
+ showNotification(errorMessage, true);
893
+ }
894
+
895
+ // Reset state
896
+ isRecording = false;
897
+ speechRetryCount = 0;
898
+ updateMicButton();
899
+ }
900
+
901
+ function handleSpeechEnd() {
902
+ isRecording = false;
903
+ updateMicButton();
904
+ const title = document.getElementById('issue-title').value.trim();
905
+ if (title) {
906
+ startAutocompleteCountdown();
907
+ }
908
+ }
909
+
910
+ function startAutocompleteCountdown() {
911
+ const countdown = document.getElementById('autocomplete-countdown');
912
+ const timerEl = document.getElementById('countdown-timer');
913
+ const progressBar = document.getElementById('countdown-progress-bar');
914
+
915
+ let remaining = AUTOCOMPLETE_DELAY_SECONDS;
916
+ timerEl.textContent = remaining + t('countdown.seconds');
917
+ progressBar.style.width = '100%';
918
+ countdown.classList.add('show');
919
+
920
+ autocompleteCountdownInterval = setInterval(function() {
921
+ remaining -= 0.1;
922
+ const percentage = (remaining / AUTOCOMPLETE_DELAY_SECONDS) * 100;
923
+ progressBar.style.width = percentage + '%';
924
+ if (remaining <= 0) {
925
+ remaining = 0;
926
+ }
927
+ timerEl.textContent = Math.ceil(remaining) + t('countdown.seconds');
928
+ }, 100);
929
+
930
+ autocompleteCountdownTimer = setTimeout(function() {
931
+ hideAutocompleteCountdown();
932
+ autocomplete();
933
+ }, AUTOCOMPLETE_DELAY_SECONDS * 1000);
934
+ }
935
+
936
+ function hideAutocompleteCountdown() {
937
+ const countdown = document.getElementById('autocomplete-countdown');
938
+ countdown.classList.remove('show');
939
+ if (autocompleteCountdownTimer) {
940
+ clearTimeout(autocompleteCountdownTimer);
941
+ autocompleteCountdownTimer = null;
942
+ }
943
+ if (autocompleteCountdownInterval) {
944
+ clearInterval(autocompleteCountdownInterval);
945
+ autocompleteCountdownInterval = null;
946
+ }
947
+ }
948
+
949
+ function cancelAutocompleteCountdown() {
950
+ hideAutocompleteCountdown();
951
+ }
952
+
953
+ function triggerAutocompleteNow() {
954
+ hideAutocompleteCountdown();
955
+ autocomplete();
956
+ }
957
+
958
+ function toggleSpeechToText() {
959
+ if (!isSpeechSupported()) {
960
+ showNotification(t('notify.micNotSupported'), true);
961
+ return;
962
+ }
963
+
964
+ if (isRecording && recognition) {
965
+ recognition.stop();
966
+ return;
967
+ }
968
+
969
+ recognition = initSpeechRecognition();
970
+ if (!recognition) return;
971
+
972
+ const titleInput = document.getElementById('issue-title');
973
+ const state = { finalTranscript: titleInput.value };
974
+
975
+ recognition.onresult = function(event) { handleSpeechResult(event, titleInput, state); };
976
+ recognition.onerror = handleSpeechError;
977
+ recognition.onend = handleSpeechEnd;
978
+
979
+ speechRetryCount = 0; // Reset retry count on new recording
980
+ recognition.start();
981
+ isRecording = true;
982
+ updateMicButton();
983
+ }
984
+
642
985
  // Init
643
986
  updateLangUI();
987
+ updateMicButton();
644
988
  </script>
645
989
  </body>
646
990
  </html>`;
@@ -1 +1 @@
1
- {"version":3,"file":"new-issue.js","sourceRoot":"","sources":["../../../../src/server/dashboard/pages/new-issue.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAErE,OAAO;cACK,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAmUJ,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,kBAAkB,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,IAAI,WAAW,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAuTjJ,CAAC;AACT,CAAC"}
1
+ {"version":3,"file":"new-issue.js","sourceRoot":"","sources":["../../../../src/server/dashboard/pages/new-issue.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAErE,OAAO;cACK,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAkbJ,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,kBAAkB,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,IAAI,WAAW,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAgiBjJ,CAAC;AACT,CAAC"}