@patch-adams/core 1.4.36 → 1.5.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.
package/dist/index.d.cts CHANGED
@@ -291,6 +291,8 @@ declare const PatchAdamsConfigSchema: z.ZodObject<{
291
291
  /** Whether this plugin is enabled */
292
292
  enabled: z.ZodDefault<z.ZodBoolean>;
293
293
  }, z.ZodTypeAny, "passthrough">>>>;
294
+ /** Optional skin name — adds 'pa-skinned' + skin class to <html>, loads skin CSS/JS after core files */
295
+ skin: z.ZodOptional<z.ZodString>;
294
296
  }, "strip", z.ZodTypeAny, {
295
297
  remoteDomain: string;
296
298
  htmlClass: string;
@@ -341,6 +343,7 @@ declare const PatchAdamsConfigSchema: z.ZodObject<{
341
343
  /** Whether this plugin is enabled */
342
344
  enabled: z.ZodDefault<z.ZodBoolean>;
343
345
  }, z.ZodTypeAny, "passthrough">>;
346
+ skin?: string | undefined;
344
347
  }, {
345
348
  remoteDomain: string;
346
349
  htmlClass?: string | undefined;
@@ -391,6 +394,7 @@ declare const PatchAdamsConfigSchema: z.ZodObject<{
391
394
  /** Whether this plugin is enabled */
392
395
  enabled: z.ZodDefault<z.ZodBoolean>;
393
396
  }, z.ZodTypeAny, "passthrough">> | undefined;
397
+ skin?: string | undefined;
394
398
  }>;
395
399
  type PatchAdamsConfig = z.infer<typeof PatchAdamsConfigSchema>;
396
400
  type LrsBridgeConfig = z.infer<typeof LrsBridgeConfigSchema>;
@@ -698,6 +702,14 @@ declare class HtmlInjector {
698
702
  * Inject JS After loader at end of <body> (after __loadEntry)
699
703
  */
700
704
  private injectJsAfter;
705
+ /**
706
+ * Inject Skin CSS loader after CSS After (before </head>)
707
+ */
708
+ private injectSkinCss;
709
+ /**
710
+ * Inject Skin JS loader after JS After (before </body>)
711
+ */
712
+ private injectSkinJs;
701
713
  }
702
714
 
703
715
  /**
@@ -770,6 +782,14 @@ declare class StorylineHtmlInjector {
770
782
  * We inject before </body> so our code runs before the bootstrapper.
771
783
  */
772
784
  private injectJsAfter;
785
+ /**
786
+ * Inject Skin CSS loader after CSS After (before </head>)
787
+ */
788
+ private injectSkinCss;
789
+ /**
790
+ * Inject Skin JS loader after JS After (before </body>)
791
+ */
792
+ private injectSkinJs;
773
793
  }
774
794
 
775
795
  interface ManifestPaths {
@@ -777,6 +797,8 @@ interface ManifestPaths {
777
797
  cssAfter?: string;
778
798
  jsBefore?: string;
779
799
  jsAfter?: string;
800
+ skinCss?: string;
801
+ skinJs?: string;
780
802
  }
781
803
  /**
782
804
  * Update manifest files to include injected assets
@@ -840,6 +862,8 @@ interface PatchOptions {
840
862
  jsAfterContent?: string;
841
863
  /** Skip fetching remote files even if fetchFallbacks is enabled in config */
842
864
  skipFetch?: boolean;
865
+ /** Optional skin name — per-call override for config.skin */
866
+ skin?: string;
843
867
  }
844
868
  /**
845
869
  * Main Patcher class for patching e-learning course packages
@@ -912,7 +936,7 @@ declare class Patcher {
912
936
  * @param buffer - Input ZIP file as Buffer
913
937
  * @returns Patched ZIP file as Buffer
914
938
  */
915
- patchBuffer(buffer: Buffer): Promise<Buffer>;
939
+ patchBuffer(buffer: Buffer, options?: PatchOptions): Promise<Buffer>;
916
940
  }
917
941
 
918
942
  /**
@@ -1030,6 +1054,8 @@ interface JsBeforeOptions {
1030
1054
  metadata: CourseMetadata | null;
1031
1055
  /** LRS Bridge configuration */
1032
1056
  lrsBridge: LrsBridgeOptions;
1057
+ /** Optional skin name */
1058
+ skin?: string;
1033
1059
  }
1034
1060
  /**
1035
1061
  * Generate the blocking JS loader for the "before" slot
package/dist/index.d.ts CHANGED
@@ -291,6 +291,8 @@ declare const PatchAdamsConfigSchema: z.ZodObject<{
291
291
  /** Whether this plugin is enabled */
292
292
  enabled: z.ZodDefault<z.ZodBoolean>;
293
293
  }, z.ZodTypeAny, "passthrough">>>>;
294
+ /** Optional skin name — adds 'pa-skinned' + skin class to <html>, loads skin CSS/JS after core files */
295
+ skin: z.ZodOptional<z.ZodString>;
294
296
  }, "strip", z.ZodTypeAny, {
295
297
  remoteDomain: string;
296
298
  htmlClass: string;
@@ -341,6 +343,7 @@ declare const PatchAdamsConfigSchema: z.ZodObject<{
341
343
  /** Whether this plugin is enabled */
342
344
  enabled: z.ZodDefault<z.ZodBoolean>;
343
345
  }, z.ZodTypeAny, "passthrough">>;
346
+ skin?: string | undefined;
344
347
  }, {
345
348
  remoteDomain: string;
346
349
  htmlClass?: string | undefined;
@@ -391,6 +394,7 @@ declare const PatchAdamsConfigSchema: z.ZodObject<{
391
394
  /** Whether this plugin is enabled */
392
395
  enabled: z.ZodDefault<z.ZodBoolean>;
393
396
  }, z.ZodTypeAny, "passthrough">> | undefined;
397
+ skin?: string | undefined;
394
398
  }>;
395
399
  type PatchAdamsConfig = z.infer<typeof PatchAdamsConfigSchema>;
396
400
  type LrsBridgeConfig = z.infer<typeof LrsBridgeConfigSchema>;
@@ -698,6 +702,14 @@ declare class HtmlInjector {
698
702
  * Inject JS After loader at end of <body> (after __loadEntry)
699
703
  */
700
704
  private injectJsAfter;
705
+ /**
706
+ * Inject Skin CSS loader after CSS After (before </head>)
707
+ */
708
+ private injectSkinCss;
709
+ /**
710
+ * Inject Skin JS loader after JS After (before </body>)
711
+ */
712
+ private injectSkinJs;
701
713
  }
702
714
 
703
715
  /**
@@ -770,6 +782,14 @@ declare class StorylineHtmlInjector {
770
782
  * We inject before </body> so our code runs before the bootstrapper.
771
783
  */
772
784
  private injectJsAfter;
785
+ /**
786
+ * Inject Skin CSS loader after CSS After (before </head>)
787
+ */
788
+ private injectSkinCss;
789
+ /**
790
+ * Inject Skin JS loader after JS After (before </body>)
791
+ */
792
+ private injectSkinJs;
773
793
  }
774
794
 
775
795
  interface ManifestPaths {
@@ -777,6 +797,8 @@ interface ManifestPaths {
777
797
  cssAfter?: string;
778
798
  jsBefore?: string;
779
799
  jsAfter?: string;
800
+ skinCss?: string;
801
+ skinJs?: string;
780
802
  }
781
803
  /**
782
804
  * Update manifest files to include injected assets
@@ -840,6 +862,8 @@ interface PatchOptions {
840
862
  jsAfterContent?: string;
841
863
  /** Skip fetching remote files even if fetchFallbacks is enabled in config */
842
864
  skipFetch?: boolean;
865
+ /** Optional skin name — per-call override for config.skin */
866
+ skin?: string;
843
867
  }
844
868
  /**
845
869
  * Main Patcher class for patching e-learning course packages
@@ -912,7 +936,7 @@ declare class Patcher {
912
936
  * @param buffer - Input ZIP file as Buffer
913
937
  * @returns Patched ZIP file as Buffer
914
938
  */
915
- patchBuffer(buffer: Buffer): Promise<Buffer>;
939
+ patchBuffer(buffer: Buffer, options?: PatchOptions): Promise<Buffer>;
916
940
  }
917
941
 
918
942
  /**
@@ -1030,6 +1054,8 @@ interface JsBeforeOptions {
1030
1054
  metadata: CourseMetadata | null;
1031
1055
  /** LRS Bridge configuration */
1032
1056
  lrsBridge: LrsBridgeOptions;
1057
+ /** Optional skin name */
1058
+ skin?: string;
1033
1059
  }
1034
1060
  /**
1035
1061
  * Generate the blocking JS loader for the "before" slot
package/dist/index.js CHANGED
@@ -106,7 +106,9 @@ var PatchAdamsConfigSchema = z.object({
106
106
  /** LRS Bridge configuration for xAPI/LRS communication with Bravais */
107
107
  lrsBridge: LrsBridgeConfigSchema.default({}),
108
108
  /** Plugin configurations - each plugin is keyed by its name */
109
- plugins: PluginsConfigSchema
109
+ plugins: PluginsConfigSchema,
110
+ /** Optional skin name — adds 'pa-skinned' + skin class to <html>, loads skin CSS/JS after core files */
111
+ skin: z.string().min(1).optional()
110
112
  });
111
113
 
112
114
  // src/config/defaults.ts
@@ -596,6 +598,7 @@ function generateLrsBridgeCode(options) {
596
598
  sessionId: null, // Session UUID
597
599
  launchTime: null, // ISO timestamp of course launch
598
600
  courseAttemptId: null, // Unique ID for this course attempt session (Xyleme format)
601
+ employeeId: null, // Employee ID from employee lookup
599
602
  // Statistics
600
603
  stats: {
601
604
  statementsSent: 0,
@@ -1426,6 +1429,11 @@ function generateLrsBridgeCode(options) {
1426
1429
  };
1427
1430
  log('Updated actor account: bravaisUserId=' + employeeData.bravaisUserId + ', homePage=' + actor.account.homePage + ' (was: ' + originalAccountName + ')');
1428
1431
  }
1432
+
1433
+ // Expose employee ID on LRS object for plugins (e.g. feedback)
1434
+ if (employeeData.employeeId) {
1435
+ LRS.employeeId = employeeData.employeeId;
1436
+ }
1429
1437
  }
1430
1438
  callback(actor);
1431
1439
  });
@@ -4371,7 +4379,7 @@ function escapeJs(str) {
4371
4379
  return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r");
4372
4380
  }
4373
4381
  function generateJsBeforeLoader(options) {
4374
- const { remoteUrl, localPath, htmlClass, loadingClass, metadata, lrsBridge } = options;
4382
+ const { remoteUrl, localPath, htmlClass, loadingClass, metadata, lrsBridge, skin } = options;
4375
4383
  const lrsBridgeCode = generateLrsBridgeCode(lrsBridge);
4376
4384
  const courseLines = [];
4377
4385
  if (metadata) {
@@ -4414,12 +4422,13 @@ ${courseLines.join("\n")}
4414
4422
  window.pa_patcher = window.pa_patcher || {
4415
4423
  version: '1.0.25',
4416
4424
  htmlClass: '${htmlClass}',
4417
- loadingClass: '${loadingClass}',${courseBlock}
4425
+ loadingClass: '${loadingClass}',${skin ? `
4426
+ skin: '${escapeJs(skin)}',` : ""}${courseBlock}
4418
4427
  loaded: {
4419
4428
  cssBefore: false,
4420
4429
  cssAfter: false,
4421
4430
  jsBefore: false,
4422
- jsAfter: false
4431
+ jsAfter: false${skin ? ",\n skinCss: false,\n skinJs: false" : ""}
4423
4432
  }
4424
4433
  };
4425
4434
 
@@ -4540,7 +4549,8 @@ function buildJsBeforeOptions(config, metadata) {
4540
4549
  htmlClass: config.htmlClass,
4541
4550
  loadingClass: config.loadingClass,
4542
4551
  metadata: metadata ?? null,
4543
- lrsBridge
4552
+ lrsBridge,
4553
+ skin: config.skin
4544
4554
  };
4545
4555
  }
4546
4556
 
@@ -4663,6 +4673,196 @@ function buildJsAfterOptions(config) {
4663
4673
  };
4664
4674
  }
4665
4675
 
4676
+ // src/templates/skin-css.ts
4677
+ function generateSkinCssLoader(options) {
4678
+ const { remoteUrl, localPath, timeout } = options;
4679
+ return `<!-- === PATCH-ADAMS: SKIN CSS (async with fallback) === -->
4680
+ <script data-pa="skin-css-loader">
4681
+ (function() {
4682
+ 'use strict';
4683
+ var REMOTE_URL = "${remoteUrl}";
4684
+ var LOCAL_PATH = "${localPath}";
4685
+ var TIMEOUT = ${timeout};
4686
+
4687
+ function loadCSS(url, onSuccess, onError) {
4688
+ var link = document.createElement('link');
4689
+ link.rel = 'stylesheet';
4690
+ link.href = url;
4691
+ link.setAttribute('data-pa', 'skin-css');
4692
+
4693
+ link.onload = function() {
4694
+ if (onSuccess) onSuccess();
4695
+ };
4696
+
4697
+ link.onerror = function() {
4698
+ if (onError) onError();
4699
+ };
4700
+
4701
+ document.head.appendChild(link);
4702
+ return link;
4703
+ }
4704
+
4705
+ function loadCSSWithFallback() {
4706
+ var loaded = false;
4707
+ var timeoutId;
4708
+
4709
+ // Try remote first
4710
+ var remoteLink = loadCSS(
4711
+ REMOTE_URL,
4712
+ function() {
4713
+ if (loaded) return;
4714
+ loaded = true;
4715
+ clearTimeout(timeoutId);
4716
+ console.log('[PA-Patcher] Skin CSS loaded from remote:', REMOTE_URL);
4717
+ },
4718
+ function() {
4719
+ if (loaded) return;
4720
+ loaded = true;
4721
+ clearTimeout(timeoutId);
4722
+ loadLocalFallback();
4723
+ }
4724
+ );
4725
+
4726
+ // Timeout fallback
4727
+ timeoutId = setTimeout(function() {
4728
+ if (loaded) return;
4729
+ loaded = true;
4730
+ console.warn('[PA-Patcher] Skin CSS timed out, using local fallback');
4731
+ if (remoteLink.parentNode) {
4732
+ document.head.removeChild(remoteLink);
4733
+ }
4734
+ loadLocalFallback();
4735
+ }, TIMEOUT);
4736
+ }
4737
+
4738
+ function loadLocalFallback() {
4739
+ loadCSS(
4740
+ LOCAL_PATH,
4741
+ function() {
4742
+ console.log('[PA-Patcher] Skin CSS loaded from local fallback:', LOCAL_PATH);
4743
+ },
4744
+ function() {
4745
+ console.error('[PA-Patcher] Skin CSS failed to load from both remote and local');
4746
+ }
4747
+ );
4748
+ }
4749
+
4750
+ // Execute immediately
4751
+ loadCSSWithFallback();
4752
+ })();
4753
+ </script>`;
4754
+ }
4755
+ function buildSkinCssOptions(config) {
4756
+ if (!config.skin) return null;
4757
+ return {
4758
+ remoteUrl: `${config.remoteDomain}/skin/${config.skin}.css`,
4759
+ localPath: `skin/${config.skin}.css`,
4760
+ timeout: config.cssAfter.timeout
4761
+ // reuse cssAfter timeout
4762
+ };
4763
+ }
4764
+
4765
+ // src/templates/skin-js.ts
4766
+ function generateSkinJsLoader(options) {
4767
+ const { remoteUrl, localPath, timeout } = options;
4768
+ return `<!-- === PATCH-ADAMS: SKIN JS (async with fallback) === -->
4769
+ <script data-pa="skin-js-loader">
4770
+ (function() {
4771
+ 'use strict';
4772
+ var REMOTE_URL = "${remoteUrl}";
4773
+ var LOCAL_PATH = "${localPath}";
4774
+ var TIMEOUT = ${timeout};
4775
+
4776
+ function loadJS(url, onSuccess, onError) {
4777
+ var script = document.createElement('script');
4778
+ script.src = url;
4779
+ script.async = true;
4780
+ script.setAttribute('data-pa', 'skin-js');
4781
+
4782
+ script.onload = function() {
4783
+ if (onSuccess) onSuccess();
4784
+ };
4785
+
4786
+ script.onerror = function() {
4787
+ if (onError) onError();
4788
+ };
4789
+
4790
+ document.body.appendChild(script);
4791
+ return script;
4792
+ }
4793
+
4794
+ function loadJSWithFallback() {
4795
+ var loaded = false;
4796
+ var timeoutId;
4797
+
4798
+ // Try remote first
4799
+ var remoteScript = loadJS(
4800
+ REMOTE_URL,
4801
+ function() {
4802
+ if (loaded) return;
4803
+ loaded = true;
4804
+ clearTimeout(timeoutId);
4805
+ console.log('[PA-Patcher] Skin JS loaded from remote:', REMOTE_URL);
4806
+ if (window.pa_patcher && window.pa_patcher.loaded) {
4807
+ window.pa_patcher.loaded.skinJs = true;
4808
+ }
4809
+ },
4810
+ function() {
4811
+ if (loaded) return;
4812
+ loaded = true;
4813
+ clearTimeout(timeoutId);
4814
+ loadLocalFallback();
4815
+ }
4816
+ );
4817
+
4818
+ // Timeout fallback
4819
+ timeoutId = setTimeout(function() {
4820
+ if (loaded) return;
4821
+ loaded = true;
4822
+ console.warn('[PA-Patcher] Skin JS timed out, using local fallback');
4823
+ if (remoteScript.parentNode) {
4824
+ document.body.removeChild(remoteScript);
4825
+ }
4826
+ loadLocalFallback();
4827
+ }, TIMEOUT);
4828
+ }
4829
+
4830
+ function loadLocalFallback() {
4831
+ loadJS(
4832
+ LOCAL_PATH,
4833
+ function() {
4834
+ console.log('[PA-Patcher] Skin JS loaded from local fallback:', LOCAL_PATH);
4835
+ if (window.pa_patcher && window.pa_patcher.loaded) {
4836
+ window.pa_patcher.loaded.skinJs = true;
4837
+ }
4838
+ },
4839
+ function() {
4840
+ console.error('[PA-Patcher] Skin JS failed to load from both remote and local');
4841
+ }
4842
+ );
4843
+ }
4844
+
4845
+ // Execute after DOM is ready
4846
+ if (document.readyState === 'loading') {
4847
+ document.addEventListener('DOMContentLoaded', function() {
4848
+ setTimeout(loadJSWithFallback, 150);
4849
+ });
4850
+ } else {
4851
+ setTimeout(loadJSWithFallback, 150);
4852
+ }
4853
+ })();
4854
+ </script>`;
4855
+ }
4856
+ function buildSkinJsOptions(config) {
4857
+ if (!config.skin) return null;
4858
+ return {
4859
+ remoteUrl: `${config.remoteDomain}/skin/${config.skin}.js`,
4860
+ localPath: `skin/${config.skin}.js`,
4861
+ timeout: config.jsAfter.timeout
4862
+ // reuse jsAfter timeout
4863
+ };
4864
+ }
4865
+
4666
4866
  // src/patcher/html-injector.ts
4667
4867
  var HtmlInjector = class {
4668
4868
  config;
@@ -4726,6 +4926,10 @@ var HtmlInjector = class {
4726
4926
  if (this.config.jsAfter.enabled) {
4727
4927
  result = this.injectJsAfter(result);
4728
4928
  }
4929
+ if (this.config.skin) {
4930
+ result = this.injectSkinCss(result);
4931
+ result = this.injectSkinJs(result);
4932
+ }
4729
4933
  if (this.pluginAssets) {
4730
4934
  result = this.injectPluginAssets(result);
4731
4935
  }
@@ -4772,8 +4976,8 @@ ${pluginJs}
4772
4976
  * Also adds data attributes for course metadata
4773
4977
  */
4774
4978
  addHtmlAttributes(html) {
4775
- const { htmlClass, loadingClass } = this.config;
4776
- const classes = `${htmlClass} ${loadingClass}`;
4979
+ const { htmlClass, loadingClass, skin } = this.config;
4980
+ const classes = `${htmlClass} ${loadingClass}${skin ? ` pa-skinned ${skin}` : ""}`;
4777
4981
  const dataAttrs = [];
4778
4982
  if (this.metadata) {
4779
4983
  dataAttrs.push(`data-pa-course-id="${this.escapeAttr(this.metadata.courseId)}"`);
@@ -4782,6 +4986,9 @@ ${pluginJs}
4782
4986
  dataAttrs.push(`data-pa-title="${this.escapeAttr(this.metadata.title)}"`);
4783
4987
  }
4784
4988
  }
4989
+ if (skin) {
4990
+ dataAttrs.push(`data-pa-skin="${this.escapeAttr(skin)}"`);
4991
+ }
4785
4992
  const dataAttrString = dataAttrs.length > 0 ? " " + dataAttrs.join(" ") : "";
4786
4993
  const htmlTagPattern = /<html([^>]*)>/i;
4787
4994
  const match = html.match(htmlTagPattern);
@@ -4860,6 +5067,26 @@ ${loader}`);
4860
5067
  ${loader}`);
4861
5068
  }
4862
5069
  return html.replace(/<\/body>/i, `${loader}
5070
+ </body>`);
5071
+ }
5072
+ /**
5073
+ * Inject Skin CSS loader after CSS After (before </head>)
5074
+ */
5075
+ injectSkinCss(html) {
5076
+ const options = buildSkinCssOptions(this.config);
5077
+ if (!options) return html;
5078
+ const loader = generateSkinCssLoader(options);
5079
+ return html.replace(/<\/head>/i, `${loader}
5080
+ </head>`);
5081
+ }
5082
+ /**
5083
+ * Inject Skin JS loader after JS After (before </body>)
5084
+ */
5085
+ injectSkinJs(html) {
5086
+ const options = buildSkinJsOptions(this.config);
5087
+ if (!options) return html;
5088
+ const loader = generateSkinJsLoader(options);
5089
+ return html.replace(/<\/body>/i, `${loader}
4863
5090
  </body>`);
4864
5091
  }
4865
5092
  };
@@ -4903,6 +5130,10 @@ var StorylineHtmlInjector = class {
4903
5130
  if (this.config.jsAfter.enabled) {
4904
5131
  result = this.injectJsAfter(result);
4905
5132
  }
5133
+ if (this.config.skin) {
5134
+ result = this.injectSkinCss(result);
5135
+ result = this.injectSkinJs(result);
5136
+ }
4906
5137
  if (this.pluginAssets) {
4907
5138
  result = this.injectPluginAssets(result);
4908
5139
  }
@@ -4947,8 +5178,8 @@ ${pluginJs}
4947
5178
  * Also adds data attributes for course metadata
4948
5179
  */
4949
5180
  addHtmlAttributes(html) {
4950
- const { htmlClass, loadingClass } = this.config;
4951
- const classes = `${htmlClass} ${loadingClass}`;
5181
+ const { htmlClass, loadingClass, skin } = this.config;
5182
+ const classes = `${htmlClass} ${loadingClass}${skin ? ` pa-skinned ${skin}` : ""}`;
4952
5183
  const dataAttrs = [];
4953
5184
  if (this.metadata) {
4954
5185
  dataAttrs.push(`data-pa-course-id="${this.escapeAttr(this.metadata.courseId)}"`);
@@ -4958,6 +5189,9 @@ ${pluginJs}
4958
5189
  dataAttrs.push(`data-pa-title="${this.escapeAttr(this.metadata.title)}"`);
4959
5190
  }
4960
5191
  }
5192
+ if (skin) {
5193
+ dataAttrs.push(`data-pa-skin="${this.escapeAttr(skin)}"`);
5194
+ }
4961
5195
  const dataAttrString = dataAttrs.length > 0 ? " " + dataAttrs.join(" ") : "";
4962
5196
  const htmlTagPattern = /<html([^>]*)>/i;
4963
5197
  const match = html.match(htmlTagPattern);
@@ -5035,6 +5269,26 @@ ${loader}`);
5035
5269
  const options = buildJsAfterOptions(this.config);
5036
5270
  const loader = generateJsAfterLoader(options);
5037
5271
  return html.replace(/<\/body>/i, `${loader}
5272
+ </body>`);
5273
+ }
5274
+ /**
5275
+ * Inject Skin CSS loader after CSS After (before </head>)
5276
+ */
5277
+ injectSkinCss(html) {
5278
+ const options = buildSkinCssOptions(this.config);
5279
+ if (!options) return html;
5280
+ const loader = generateSkinCssLoader(options);
5281
+ return html.replace(/<\/head>/i, `${loader}
5282
+ </head>`);
5283
+ }
5284
+ /**
5285
+ * Inject Skin JS loader after JS After (before </body>)
5286
+ */
5287
+ injectSkinJs(html) {
5288
+ const options = buildSkinJsOptions(this.config);
5289
+ if (!options) return html;
5290
+ const loader = generateSkinJsLoader(options);
5291
+ return html.replace(/<\/body>/i, `${loader}
5038
5292
  </body>`);
5039
5293
  }
5040
5294
  };
@@ -5085,7 +5339,9 @@ var ManifestUpdater = class {
5085
5339
  paths.cssBefore,
5086
5340
  paths.cssAfter,
5087
5341
  paths.jsBefore,
5088
- paths.jsAfter
5342
+ paths.jsAfter,
5343
+ paths.skinCss,
5344
+ paths.skinJs
5089
5345
  ].filter(Boolean);
5090
5346
  if (filesToAdd.length === 0) {
5091
5347
  return [];
@@ -5875,6 +6131,20 @@ var Patcher = class {
5875
6131
  })
5876
6132
  );
5877
6133
  }
6134
+ if (this.config.skin) {
6135
+ const skinCssUrl = `${remoteDomain}/skin/${this.config.skin}.css`;
6136
+ fetchPromises.push(
6137
+ this.fetchFile(skinCssUrl).then((content) => {
6138
+ fetched.skinCss = content;
6139
+ })
6140
+ );
6141
+ const skinJsUrl = `${remoteDomain}/skin/${this.config.skin}.js`;
6142
+ fetchPromises.push(
6143
+ this.fetchFile(skinJsUrl).then((content) => {
6144
+ fetched.skinJs = content;
6145
+ })
6146
+ );
6147
+ }
5878
6148
  await Promise.all(fetchPromises);
5879
6149
  return fetched;
5880
6150
  }
@@ -5958,6 +6228,16 @@ var Patcher = class {
5958
6228
  console.log(`[Patcher] Wrote generated UUID to manifest identifier`);
5959
6229
  }
5960
6230
  }
6231
+ const effectiveSkin = options.skin || this.config.skin;
6232
+ if (options.skin && options.skin !== this.config.skin) {
6233
+ this.config.skin = options.skin;
6234
+ this.riseHtmlInjector = new HtmlInjector(this.config);
6235
+ this.storylineHtmlInjector = new StorylineHtmlInjector(this.config);
6236
+ console.log(`[Patcher] Using per-call skin override: ${options.skin}`);
6237
+ }
6238
+ if (effectiveSkin) {
6239
+ console.log(`[Patcher] Skin: ${effectiveSkin}`);
6240
+ }
5961
6241
  const htmlInjector = this.getHtmlInjector(toolInfo.tool);
5962
6242
  htmlInjector.setMetadata(metadata);
5963
6243
  let fetchedFallbacks = {};
@@ -6086,7 +6366,11 @@ var Patcher = class {
6086
6366
  buildManifestPaths(addedFiles) {
6087
6367
  const paths = {};
6088
6368
  for (const filePath of addedFiles) {
6089
- if (filePath.endsWith(this.config.cssBefore.filename)) {
6369
+ if (this.config.skin && filePath.endsWith(`skin/${this.config.skin}.css`)) {
6370
+ paths.skinCss = filePath;
6371
+ } else if (this.config.skin && filePath.endsWith(`skin/${this.config.skin}.js`)) {
6372
+ paths.skinJs = filePath;
6373
+ } else if (filePath.endsWith(this.config.cssBefore.filename)) {
6090
6374
  paths.cssBefore = filePath;
6091
6375
  } else if (filePath.endsWith(this.config.cssAfter.filename)) {
6092
6376
  paths.cssAfter = filePath;
@@ -6170,6 +6454,22 @@ var Patcher = class {
6170
6454
  added.push(path);
6171
6455
  if (fetched.jsAfter) console.log(`[Patcher] Using fetched content for ${path}`);
6172
6456
  }
6457
+ if (this.config.skin) {
6458
+ const skinCssPath = `${basePath}skin/${this.config.skin}.css`;
6459
+ const skinCssContent = fetched.skinCss ?? `/* PA-Patcher: Skin CSS (${this.config.skin}) */
6460
+ `;
6461
+ zip.addFile(skinCssPath, Buffer.from(skinCssContent, "utf-8"));
6462
+ added.push(skinCssPath);
6463
+ if (fetched.skinCss) console.log(`[Patcher] Using fetched skin CSS for ${skinCssPath}`);
6464
+ else console.log(`[Patcher] Added placeholder skin CSS: ${skinCssPath}`);
6465
+ const skinJsPath = `${basePath}skin/${this.config.skin}.js`;
6466
+ const skinJsContent = fetched.skinJs ?? `// PA-Patcher: Skin JS (${this.config.skin})
6467
+ `;
6468
+ zip.addFile(skinJsPath, Buffer.from(skinJsContent, "utf-8"));
6469
+ added.push(skinJsPath);
6470
+ if (fetched.skinJs) console.log(`[Patcher] Using fetched skin JS for ${skinJsPath}`);
6471
+ else console.log(`[Patcher] Added placeholder skin JS: ${skinJsPath}`);
6472
+ }
6173
6473
  return added;
6174
6474
  }
6175
6475
  /**
@@ -6184,9 +6484,9 @@ var Patcher = class {
6184
6484
  * @param buffer - Input ZIP file as Buffer
6185
6485
  * @returns Patched ZIP file as Buffer
6186
6486
  */
6187
- async patchBuffer(buffer) {
6487
+ async patchBuffer(buffer, options) {
6188
6488
  console.log(`[Patcher] patchBuffer called with ${buffer.length} bytes`);
6189
- const { buffer: patchedBuffer, result } = await this.patch(buffer);
6489
+ const { buffer: patchedBuffer, result } = await this.patch(buffer, options);
6190
6490
  console.log(`[Patcher] Patch result:`, JSON.stringify(result, null, 2));
6191
6491
  console.log(`[Patcher] Returning ${patchedBuffer.length} bytes`);
6192
6492
  return patchedBuffer;