@fias/plugin-dev-harness 1.5.2 → 1.5.4

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.
@@ -16,14 +16,9 @@
16
16
  var themeToggle = document.getElementById('theme-toggle');
17
17
  var reloadBtn = document.getElementById('reload-btn');
18
18
  var modeBadge = document.getElementById('mode-badge');
19
+ var modeToggle = document.getElementById('mode-toggle');
19
20
  var pluginStatus = document.getElementById('plugin-status');
20
21
  var themeBadge = document.getElementById('theme-badge');
21
- var loginModal = document.getElementById('login-modal');
22
- var loginEmail = document.getElementById('login-email');
23
- var loginPassword = document.getElementById('login-password');
24
- var loginError = document.getElementById('login-error');
25
- var loginSubmit = document.getElementById('login-submit');
26
- var loginCancel = document.getElementById('login-cancel');
27
22
  var envSelector = document.getElementById('env-selector');
28
23
 
29
24
  var messageCount = 0;
@@ -38,6 +33,7 @@
38
33
  get_user: 'user:profile:read',
39
34
  get_theme: 'theme:read',
40
35
  entity_invoke: 'entities:invoke',
36
+ image_generate: 'entities:image_generate',
41
37
  storage_read: 'storage:sandbox',
42
38
  storage_write: 'storage:sandbox',
43
39
  storage_list: 'storage:sandbox',
@@ -47,6 +43,7 @@
47
43
  /** Rate limits per message type (matches production) */
48
44
  var RATE_LIMITS = {
49
45
  entity_invoke: { maxPerMinute: 60 },
46
+ image_generate: { maxPerMinute: 10 },
50
47
  storage_write: { maxPerMinute: 120 },
51
48
  storage_read: { maxPerMinute: 300 },
52
49
  storage_list: { maxPerMinute: 60 },
@@ -124,11 +121,11 @@
124
121
  logMessage('send', 'theme_update', { mode: currentTheme });
125
122
  });
126
123
 
127
- // Mode toggle
128
- modeBadge.addEventListener('click', function () {
124
+ // Mode toggle button
125
+ modeToggle.addEventListener('click', function () {
129
126
  if (currentMode === 'mock') {
130
127
  if (!hasCredentials) {
131
- showLoginModal();
128
+ logMessage('error', 'Not authenticated. Run: npx fias-dev login');
132
129
  return;
133
130
  }
134
131
  switchMode('live');
@@ -205,88 +202,6 @@
205
202
  });
206
203
  }
207
204
 
208
- // ────────────────────────────────────────────────────────────────
209
- // Login Modal
210
- // ────────────────────────────────────────────────────────────────
211
-
212
- var loginTarget = document.getElementById('login-target');
213
-
214
- function showLoginModal() {
215
- loginModal.style.display = 'flex';
216
- loginEmail.value = '';
217
- loginPassword.value = '';
218
- loginError.style.display = 'none';
219
- loginSubmit.disabled = false;
220
- loginSubmit.textContent = 'Sign in';
221
- loginTarget.textContent = currentEnvironment === 'production' ? 'fias.io' : 'staging.fias.io';
222
- loginEmail.focus();
223
- }
224
-
225
- function hideLoginModal() {
226
- loginModal.style.display = 'none';
227
- }
228
-
229
- loginCancel.addEventListener('click', hideLoginModal);
230
-
231
- loginModal.addEventListener('click', function (e) {
232
- if (e.target === loginModal) hideLoginModal();
233
- });
234
-
235
- loginEmail.addEventListener('keydown', function (e) {
236
- if (e.key === 'Enter') loginPassword.focus();
237
- if (e.key === 'Escape') hideLoginModal();
238
- });
239
-
240
- loginPassword.addEventListener('keydown', function (e) {
241
- if (e.key === 'Enter') submitLogin();
242
- if (e.key === 'Escape') hideLoginModal();
243
- });
244
-
245
- loginSubmit.addEventListener('click', submitLogin);
246
-
247
- function submitLogin() {
248
- var email = loginEmail.value.trim();
249
- var password = loginPassword.value;
250
-
251
- if (!email || !password) {
252
- loginError.textContent = 'Email and password are required.';
253
- loginError.style.display = 'block';
254
- return;
255
- }
256
-
257
- loginSubmit.disabled = true;
258
- loginSubmit.textContent = 'Signing in...';
259
- loginError.style.display = 'none';
260
-
261
- fetch('/api/auth/login', {
262
- method: 'POST',
263
- headers: { 'Content-Type': 'application/json' },
264
- body: JSON.stringify({ email: email, password: password }),
265
- })
266
- .then(function (r) {
267
- if (!r.ok) {
268
- return r.json().then(function (err) {
269
- throw new Error(err.error || 'Sign in failed');
270
- });
271
- }
272
- return r.json();
273
- })
274
- .then(function () {
275
- hasCredentials = true;
276
- hideLoginModal();
277
- logMessage('info', 'Signed in for ' + currentEnvironment.toUpperCase());
278
- switchMode('live');
279
- })
280
- .catch(function (err) {
281
- loginError.textContent = err.message;
282
- loginError.style.display = 'block';
283
- })
284
- .finally(function () {
285
- loginSubmit.disabled = false;
286
- loginSubmit.textContent = 'Sign in';
287
- });
288
- }
289
-
290
205
  // ────────────────────────────────────────────────────────────────
291
206
  // Message Handling
292
207
  // ────────────────────────────────────────────────────────────────
@@ -475,6 +390,388 @@
475
390
  });
476
391
  }
477
392
 
393
+ // ────────────────────────────────────────────────────────────────
394
+ // Publish Wizard
395
+ // ────────────────────────────────────────────────────────────────
396
+
397
+ var publishBtn = document.getElementById('publish-btn');
398
+ var publishModal = document.getElementById('publish-modal');
399
+ var pubCancel = document.getElementById('pub-cancel');
400
+ var pubBack = document.getElementById('pub-back');
401
+ var pubNext = document.getElementById('pub-next');
402
+ var pubError = document.getElementById('pub-error');
403
+ var pubStep = 1;
404
+ var pubManifest = null;
405
+ var pubSubmissionId = null;
406
+ var pubPollTimer = null;
407
+ var pubValidationPassed = false;
408
+
409
+ publishBtn.addEventListener('click', function () {
410
+ if (currentMode !== 'live' || !hasCredentials) {
411
+ pubError.textContent = 'Switch to live mode and sign in to publish.';
412
+ pubError.style.display = 'block';
413
+ publishModal.style.display = 'flex';
414
+ showPublishStep(1);
415
+ return;
416
+ }
417
+ pubError.style.display = 'none';
418
+ fetch('/api/manifest')
419
+ .then(function (r) {
420
+ if (!r.ok) throw new Error('fias-plugin.json not found');
421
+ return r.json();
422
+ })
423
+ .then(function (manifest) {
424
+ pubManifest = manifest;
425
+ populatePublishForm(manifest);
426
+ publishModal.style.display = 'flex';
427
+ showPublishStep(1);
428
+ })
429
+ .catch(function (err) {
430
+ pubError.textContent = err.message;
431
+ pubError.style.display = 'block';
432
+ publishModal.style.display = 'flex';
433
+ showPublishStep(1);
434
+ });
435
+ });
436
+
437
+ pubCancel.addEventListener('click', closePublishModal);
438
+ publishModal.addEventListener('click', function (e) {
439
+ if (e.target === publishModal) closePublishModal();
440
+ });
441
+
442
+ function closePublishModal() {
443
+ publishModal.style.display = 'none';
444
+ if (pubPollTimer) { clearInterval(pubPollTimer); pubPollTimer = null; }
445
+ pubStep = 1;
446
+ }
447
+
448
+ pubBack.addEventListener('click', function () {
449
+ if (pubStep > 1) showPublishStep(pubStep - 1);
450
+ });
451
+
452
+ pubNext.addEventListener('click', function () {
453
+ pubError.style.display = 'none';
454
+ if (pubStep === 1) {
455
+ collectAndSaveManifest().then(function () { showPublishStep(2); startValidation(); });
456
+ } else if (pubStep === 2 && pubValidationPassed) {
457
+ showPublishStep(3);
458
+ showCostInfo();
459
+ } else if (pubStep === 3) {
460
+ startBuildAndSubmit();
461
+ } else if (pubStep === 4) {
462
+ closePublishModal();
463
+ }
464
+ });
465
+
466
+ function showPublishStep(step) {
467
+ pubStep = step;
468
+ for (var i = 1; i <= 4; i++) {
469
+ var el = document.getElementById('publish-step-' + i);
470
+ if (el) el.style.display = i === step ? 'block' : 'none';
471
+ }
472
+ var steps = document.querySelectorAll('.publish-step');
473
+ steps.forEach(function (s, idx) {
474
+ s.className = 'publish-step' + (idx + 1 === step ? ' active' : idx + 1 < step ? ' completed' : '');
475
+ });
476
+ pubBack.style.display = step > 1 && step < 4 ? 'inline-block' : 'none';
477
+ if (step === 1) { pubNext.textContent = 'Validate'; pubNext.style.display = 'inline-block'; }
478
+ else if (step === 2) { pubNext.textContent = 'Build & Submit'; pubNext.style.display = 'inline-block'; pubNext.disabled = !pubValidationPassed; }
479
+ else if (step === 3) { pubNext.textContent = 'Confirm & Build'; pubNext.style.display = 'inline-block'; }
480
+ else if (step === 4) { pubNext.textContent = 'Close'; pubNext.style.display = 'inline-block'; }
481
+ }
482
+
483
+ function populatePublishForm(m) {
484
+ document.getElementById('pub-name').value = m.name || '';
485
+ document.getElementById('pub-version').value = m.version || '1.0.0';
486
+ document.getElementById('pub-description').value = m.description || '';
487
+ document.getElementById('pub-expanded-desc').value = m.expandedDescription || '';
488
+ document.getElementById('pub-archetype').value = m.archeType || 'tool';
489
+ document.getElementById('pub-pricing-model').value = m.pricing?.model || 'free';
490
+ document.getElementById('pub-price').value = m.pricing?.priceCents || '';
491
+ document.getElementById('pub-price-row').style.display = m.pricing?.model === 'free' ? 'none' : 'block';
492
+
493
+ // Tags
494
+ var tagsList = document.getElementById('pub-tags-list');
495
+ tagsList.innerHTML = '';
496
+ (m.tags || []).forEach(function (t) { addPublishTag(t); });
497
+
498
+ // Permissions
499
+ var checkboxes = document.querySelectorAll('#pub-permissions input[type="checkbox"]');
500
+ var perms = m.permissions || [];
501
+ checkboxes.forEach(function (cb) { cb.checked = perms.indexOf(cb.value) !== -1; });
502
+ }
503
+
504
+ // Tag management
505
+ document.getElementById('pub-tags-input').addEventListener('keydown', function (e) {
506
+ if (e.key === 'Enter' || e.key === ',') {
507
+ e.preventDefault();
508
+ var val = this.value.trim().replace(/,/g, '');
509
+ if (val) { addPublishTag(val); this.value = ''; }
510
+ }
511
+ });
512
+
513
+ function addPublishTag(text) {
514
+ var tag = document.createElement('span');
515
+ tag.className = 'pub-tag';
516
+ tag.innerHTML = escapeHtml(text) + ' <button class="pub-tag-remove">&times;</button>';
517
+ tag.querySelector('.pub-tag-remove').addEventListener('click', function () { tag.remove(); });
518
+ document.getElementById('pub-tags-list').appendChild(tag);
519
+ }
520
+
521
+ // Pricing model change
522
+ document.getElementById('pub-pricing-model').addEventListener('change', function () {
523
+ document.getElementById('pub-price-row').style.display = this.value === 'free' ? 'none' : 'block';
524
+ });
525
+
526
+ function collectManifest() {
527
+ var tags = [];
528
+ document.querySelectorAll('#pub-tags-list .pub-tag').forEach(function (t) {
529
+ tags.push(t.textContent.replace('\u00d7', '').trim());
530
+ });
531
+ var perms = [];
532
+ document.querySelectorAll('#pub-permissions input:checked').forEach(function (cb) { perms.push(cb.value); });
533
+ var pricingModel = document.getElementById('pub-pricing-model').value;
534
+ var pricing = { model: pricingModel, currency: 'usd' };
535
+ if (pricingModel !== 'free') pricing.priceCents = parseInt(document.getElementById('pub-price').value, 10) || 0;
536
+
537
+ return {
538
+ name: document.getElementById('pub-name').value.trim(),
539
+ version: document.getElementById('pub-version').value.trim(),
540
+ description: document.getElementById('pub-description').value.trim(),
541
+ expandedDescription: document.getElementById('pub-expanded-desc').value.trim() || undefined,
542
+ main: (pubManifest && pubManifest.main) || 'src/index.tsx',
543
+ archeType: document.getElementById('pub-archetype').value,
544
+ tags: tags,
545
+ pricing: pricing,
546
+ permissions: perms,
547
+ sdk: (pubManifest && pubManifest.sdk) || '^1.0.0',
548
+ dependencies: pubManifest && pubManifest.dependencies,
549
+ };
550
+ }
551
+
552
+ function collectAndSaveManifest() {
553
+ var manifest = collectManifest();
554
+ pubManifest = manifest;
555
+ return fetch('/api/manifest', {
556
+ method: 'PUT',
557
+ headers: { 'Content-Type': 'application/json' },
558
+ body: JSON.stringify(manifest),
559
+ }).then(function (r) {
560
+ if (!r.ok) throw new Error('Failed to save manifest');
561
+ });
562
+ }
563
+
564
+ function startValidation() {
565
+ pubValidationPassed = false;
566
+ pubNext.disabled = true;
567
+ document.getElementById('pub-validate-loading').style.display = 'flex';
568
+ document.getElementById('pub-validate-results').style.display = 'none';
569
+
570
+ fetch('/api/publish/validate', { method: 'POST' })
571
+ .then(function (r) { return r.json(); })
572
+ .then(function (data) {
573
+ document.getElementById('pub-validate-loading').style.display = 'none';
574
+ document.getElementById('pub-validate-results').style.display = 'block';
575
+
576
+ var statusEl = document.getElementById('pub-validate-status');
577
+ var errorsEl = document.getElementById('pub-validate-errors');
578
+ var warningsEl = document.getElementById('pub-validate-warnings');
579
+ errorsEl.innerHTML = '';
580
+ warningsEl.innerHTML = '';
581
+
582
+ if (data.valid && (!data.errors || data.errors.length === 0)) {
583
+ statusEl.innerHTML = '<div class="pub-valid">Manifest is valid</div>';
584
+ pubValidationPassed = true;
585
+ pubNext.disabled = false;
586
+ } else {
587
+ statusEl.innerHTML = '<div style="color:#fca5a5;font-weight:500;margin-bottom:8px">Validation failed</div>';
588
+ }
589
+
590
+ if (data.errors) {
591
+ data.errors.forEach(function (e) {
592
+ var div = document.createElement('div');
593
+ div.className = 'pub-error-item';
594
+ div.textContent = e;
595
+ errorsEl.appendChild(div);
596
+ });
597
+ }
598
+ if (data.warnings) {
599
+ data.warnings.forEach(function (w) {
600
+ var div = document.createElement('div');
601
+ div.className = 'pub-warn-item';
602
+ div.textContent = w;
603
+ warningsEl.appendChild(div);
604
+ });
605
+ }
606
+
607
+ // Listing preview
608
+ var preview = document.getElementById('pub-listing-preview');
609
+ var m = pubManifest;
610
+ preview.innerHTML =
611
+ '<div class="pub-listing-name">' + escapeHtml(m.name) + '</div>' +
612
+ '<div class="pub-listing-desc">' + escapeHtml(m.description) + '</div>' +
613
+ '<div class="pub-listing-meta">' +
614
+ '<span>v' + escapeHtml(m.version) + '</span>' +
615
+ '<span>' + escapeHtml(m.archeType) + '</span>' +
616
+ '<span>' + escapeHtml(m.pricing.model) + '</span>' +
617
+ '</div>';
618
+ })
619
+ .catch(function (err) {
620
+ document.getElementById('pub-validate-loading').style.display = 'none';
621
+ pubError.textContent = err.message;
622
+ pubError.style.display = 'block';
623
+ });
624
+ }
625
+
626
+ function showCostInfo() {
627
+ var costEl = document.getElementById('pub-cost-info');
628
+ costEl.innerHTML = '<p>Submission cost: <strong>5,000 credits ($50.00)</strong> for first listing, <strong>100 credits ($1.00)</strong> for updates.</p>';
629
+ fetch('/api/credits').then(function (r) { return r.json(); }).then(function (d) {
630
+ if (d.balance != null && isFinite(d.balance)) {
631
+ costEl.innerHTML += '<p style="margin-top:8px">Your balance: <strong>' + d.balance.toFixed(2) + ' credits</strong></p>';
632
+ }
633
+ }).catch(function () {});
634
+ document.getElementById('pub-build-output').style.display = 'none';
635
+ document.getElementById('pub-build-output').textContent = '';
636
+ document.getElementById('pub-submit-progress').innerHTML = '';
637
+ }
638
+
639
+ function startBuildAndSubmit() {
640
+ pubNext.style.display = 'none';
641
+ pubBack.style.display = 'none';
642
+ var outputEl = document.getElementById('pub-build-output');
643
+ var progressEl = document.getElementById('pub-submit-progress');
644
+ outputEl.style.display = 'block';
645
+ outputEl.textContent = '';
646
+ progressEl.innerHTML = '<div class="pub-phase active" id="phase-build">Building...</div>' +
647
+ '<div class="pub-phase" id="phase-package">Packaging...</div>' +
648
+ '<div class="pub-phase" id="phase-submit">Uploading & submitting...</div>';
649
+
650
+ // SSE build
651
+ fetch('/api/publish/build', { method: 'POST' })
652
+ .then(function (response) {
653
+ var reader = response.body.getReader();
654
+ var decoder = new TextDecoder();
655
+ var buffer = '';
656
+
657
+ function pump() {
658
+ return reader.read().then(function (chunk) {
659
+ if (chunk.done) return;
660
+ buffer += decoder.decode(chunk.value, { stream: true });
661
+ var lines = buffer.split('\n');
662
+ buffer = lines.pop() || '';
663
+ for (var i = 0; i < lines.length; i++) {
664
+ if (lines[i].indexOf('data: ') !== 0) continue;
665
+ try {
666
+ var parsed = JSON.parse(lines[i].slice(6));
667
+ if (parsed.line) {
668
+ outputEl.textContent += parsed.line + '\n';
669
+ outputEl.scrollTop = outputEl.scrollHeight;
670
+ }
671
+ if (parsed.done) {
672
+ if (parsed.code !== 0) throw new Error('Build failed (exit code ' + parsed.code + ')');
673
+ document.getElementById('phase-build').className = 'pub-phase done';
674
+ document.getElementById('phase-build').textContent = 'Build complete';
675
+ return doPackageAndSubmit();
676
+ }
677
+ } catch (e) {
678
+ if (e instanceof SyntaxError) continue;
679
+ throw e;
680
+ }
681
+ }
682
+ return pump();
683
+ });
684
+ }
685
+ return pump();
686
+ })
687
+ .catch(function (err) {
688
+ document.getElementById('phase-build').className = 'pub-phase error';
689
+ document.getElementById('phase-build').textContent = 'Build failed: ' + err.message;
690
+ pubBack.style.display = 'inline-block';
691
+ });
692
+ }
693
+
694
+ function doPackageAndSubmit() {
695
+ var progressPhase = document.getElementById('phase-package');
696
+ progressPhase.className = 'pub-phase active';
697
+
698
+ return fetch('/api/publish/package', { method: 'POST' })
699
+ .then(function (r) {
700
+ if (!r.ok) return r.json().then(function (e) { throw new Error(e.error); });
701
+ return r.json();
702
+ })
703
+ .then(function (data) {
704
+ progressPhase.className = 'pub-phase done';
705
+ progressPhase.textContent = 'Packaged (' + Math.round(data.sizeBytes / 1024) + ' KB)';
706
+ document.getElementById('phase-submit').className = 'pub-phase active';
707
+
708
+ return fetch('/api/publish/submit', {
709
+ method: 'POST',
710
+ headers: { 'Content-Type': 'application/json' },
711
+ body: JSON.stringify({}),
712
+ });
713
+ })
714
+ .then(function (r) {
715
+ if (!r.ok) return r.json().then(function (e) { throw new Error(e.error); });
716
+ return r.json();
717
+ })
718
+ .then(function (data) {
719
+ document.getElementById('phase-submit').className = 'pub-phase done';
720
+ document.getElementById('phase-submit').textContent = 'Submitted for review';
721
+ pubSubmissionId = data.submissionId;
722
+ logMessage('info', 'Submitted: ' + pubSubmissionId);
723
+ setTimeout(function () { showPublishStep(4); startStatusPolling(); }, 1000);
724
+ })
725
+ .catch(function (err) {
726
+ var active = document.querySelector('.pub-phase.active');
727
+ if (active) { active.className = 'pub-phase error'; active.textContent += ' — ' + err.message; }
728
+ pubBack.style.display = 'inline-block';
729
+ });
730
+ }
731
+
732
+ function startStatusPolling() {
733
+ if (pubPollTimer) clearInterval(pubPollTimer);
734
+ document.getElementById('pub-review-status').style.display = 'block';
735
+ document.getElementById('pub-review-result').style.display = 'none';
736
+
737
+ pubPollTimer = setInterval(function () {
738
+ fetch('/api/publish/status/' + pubSubmissionId)
739
+ .then(function (r) { return r.json(); })
740
+ .then(function (data) {
741
+ var sub = data.submission || data;
742
+ var status = sub.status;
743
+ var statusEl = document.getElementById('pub-review-status');
744
+
745
+ if (status === 'reviewing') {
746
+ statusEl.innerHTML = '<div class="status-spinner"></div><p>AI is reviewing your plugin...</p>';
747
+ } else if (status === 'building') {
748
+ statusEl.innerHTML = '<div class="status-spinner"></div><p>Building your plugin...</p>';
749
+ } else if (status === 'published') {
750
+ clearInterval(pubPollTimer);
751
+ pubPollTimer = null;
752
+ statusEl.style.display = 'none';
753
+ document.getElementById('pub-review-result').style.display = 'block';
754
+ document.getElementById('pub-review-result').innerHTML =
755
+ '<div class="pub-congrats">Published!</div>' +
756
+ '<p style="text-align:center;color:#a1a1aa;font-size:13px">Your plugin is now live on the Fias marketplace.</p>';
757
+ logMessage('info', 'Plugin published!');
758
+ } else if (status === 'rejected') {
759
+ clearInterval(pubPollTimer);
760
+ pubPollTimer = null;
761
+ statusEl.style.display = 'none';
762
+ document.getElementById('pub-review-result').style.display = 'block';
763
+ var html = '<div class="pub-rejected">Submission Rejected</div>';
764
+ if (sub.errorMessage) html += '<p style="color:#a1a1aa;font-size:12px;margin-bottom:8px">' + escapeHtml(sub.errorMessage) + '</p>';
765
+ if (data.review) {
766
+ html += '<p style="color:#6b7280;font-size:11px">Risk score: ' + (data.review.riskScore || 'N/A') + '</p>';
767
+ }
768
+ document.getElementById('pub-review-result').innerHTML = html;
769
+ }
770
+ })
771
+ .catch(function () {});
772
+ }, 3000);
773
+ }
774
+
478
775
  // ────────────────────────────────────────────────────────────────
479
776
  // Iframe Init
480
777
  // ────────────────────────────────────────────────────────────────
@@ -501,9 +798,9 @@
501
798
 
502
799
  function updateModeBadge() {
503
800
  var opposite = currentMode === 'live' ? 'Mock' : 'Live';
504
- modeBadge.textContent = currentMode.toUpperCase() + ' \u21C6';
801
+ modeBadge.textContent = currentMode.toUpperCase();
505
802
  modeBadge.className = 'mode-badge ' + (currentMode === 'live' ? 'mode-live' : 'mode-mock');
506
- modeBadge.title = 'Click to switch to ' + opposite + ' mode';
803
+ modeToggle.textContent = 'Switch to ' + opposite;
507
804
  }
508
805
 
509
806
  function updateThemeBadge() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fias/plugin-dev-harness",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "Development harness for building and testing FIAS plugin arches locally",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",