@symbiosis-lab/moss-api 0.5.3 → 0.6.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.mjs CHANGED
@@ -541,6 +541,570 @@ async function executeBinary(options) {
541
541
  };
542
542
  }
543
543
 
544
+ //#endregion
545
+ //#region src/utils/platform.ts
546
+ /**
547
+ * Platform detection utilities for Moss plugins
548
+ *
549
+ * Detects the current operating system and architecture to enable
550
+ * platform-specific binary downloads and operations.
551
+ */
552
+ let cachedPlatform = null;
553
+ /**
554
+ * Detect the current platform (OS and architecture)
555
+ *
556
+ * Uses system commands to detect the platform:
557
+ * - On macOS/Linux: `uname -s` for OS, `uname -m` for architecture
558
+ * - On Windows: Falls back to environment variables and defaults
559
+ *
560
+ * Results are cached after the first call.
561
+ *
562
+ * @returns Platform information including OS, architecture, and combined key
563
+ * @throws Error if platform detection fails or platform is unsupported
564
+ *
565
+ * @example
566
+ * ```typescript
567
+ * const platform = await getPlatformInfo();
568
+ * console.log(platform.platformKey); // "darwin-arm64"
569
+ * ```
570
+ */
571
+ async function getPlatformInfo() {
572
+ if (cachedPlatform) return cachedPlatform;
573
+ const os = await detectOS();
574
+ const arch = await detectArch(os);
575
+ const platformKey = `${os}-${arch}`;
576
+ const supportedPlatforms = [
577
+ "darwin-arm64",
578
+ "darwin-x64",
579
+ "linux-x64",
580
+ "windows-x64"
581
+ ];
582
+ if (!supportedPlatforms.includes(platformKey)) throw new Error(`Unsupported platform: ${platformKey}. Supported platforms: ${supportedPlatforms.join(", ")}`);
583
+ cachedPlatform = {
584
+ os,
585
+ arch,
586
+ platformKey
587
+ };
588
+ return cachedPlatform;
589
+ }
590
+ /**
591
+ * Clear the cached platform info
592
+ *
593
+ * Useful for testing or when platform detection needs to be re-run.
594
+ *
595
+ * @internal
596
+ */
597
+ function clearPlatformCache() {
598
+ cachedPlatform = null;
599
+ }
600
+ /**
601
+ * Detect the operating system
602
+ */
603
+ async function detectOS() {
604
+ try {
605
+ const result = await executeBinary({
606
+ binaryPath: "uname",
607
+ args: ["-s"],
608
+ timeoutMs: 5e3
609
+ });
610
+ if (result.success) {
611
+ const osName = result.stdout.trim().toLowerCase();
612
+ if (osName === "darwin") return "darwin";
613
+ if (osName === "linux") return "linux";
614
+ }
615
+ } catch {}
616
+ try {
617
+ const result = await executeBinary({
618
+ binaryPath: "cmd",
619
+ args: ["/c", "ver"],
620
+ timeoutMs: 5e3
621
+ });
622
+ if (result.success && result.stdout.toLowerCase().includes("windows")) return "windows";
623
+ } catch {}
624
+ throw new Error("Unable to detect operating system. Supported systems: macOS (Darwin), Linux, Windows");
625
+ }
626
+ /**
627
+ * Detect the CPU architecture
628
+ */
629
+ async function detectArch(os) {
630
+ if (os === "windows") try {
631
+ const result = await executeBinary({
632
+ binaryPath: "cmd",
633
+ args: [
634
+ "/c",
635
+ "echo",
636
+ "%PROCESSOR_ARCHITECTURE%"
637
+ ],
638
+ timeoutMs: 5e3
639
+ });
640
+ if (result.success) {
641
+ if (result.stdout.trim().toLowerCase() === "arm64") return "arm64";
642
+ return "x64";
643
+ }
644
+ } catch {
645
+ return "x64";
646
+ }
647
+ try {
648
+ const result = await executeBinary({
649
+ binaryPath: "uname",
650
+ args: ["-m"],
651
+ timeoutMs: 5e3
652
+ });
653
+ if (result.success) {
654
+ const machine = result.stdout.trim().toLowerCase();
655
+ if (machine === "arm64" || machine === "aarch64") return "arm64";
656
+ if (machine === "x86_64" || machine === "amd64") return "x64";
657
+ if (machine.includes("arm")) return "arm64";
658
+ return "x64";
659
+ }
660
+ } catch {}
661
+ return "x64";
662
+ }
663
+
664
+ //#endregion
665
+ //#region src/utils/archive.ts
666
+ /**
667
+ * Archive extraction utilities for Moss plugins
668
+ *
669
+ * Provides functions to extract .tar.gz and .zip archives using
670
+ * system commands (tar, unzip, PowerShell).
671
+ */
672
+ /**
673
+ * Extract an archive to a destination directory
674
+ *
675
+ * Uses system commands for extraction:
676
+ * - .tar.gz: `tar -xzf` (macOS/Linux)
677
+ * - .zip: `unzip` (macOS/Linux) or PowerShell `Expand-Archive` (Windows)
678
+ *
679
+ * @param options - Extraction options
680
+ * @returns Extraction result
681
+ *
682
+ * @example
683
+ * ```typescript
684
+ * const result = await extractArchive({
685
+ * archivePath: "/path/to/hugo.tar.gz",
686
+ * destDir: "/path/to/extract/",
687
+ * });
688
+ *
689
+ * if (!result.success) {
690
+ * console.error(`Extraction failed: ${result.error}`);
691
+ * }
692
+ * ```
693
+ */
694
+ async function extractArchive(options) {
695
+ const { archivePath, destDir, timeoutMs = 6e4 } = options;
696
+ const format = options.format ?? detectFormat(archivePath);
697
+ if (!format) return {
698
+ success: false,
699
+ error: `Unable to detect archive format for: ${archivePath}. Supported formats: .tar.gz, .zip`
700
+ };
701
+ const platform = await getPlatformInfo();
702
+ try {
703
+ if (format === "tar.gz") return await extractTarGz(archivePath, destDir, timeoutMs);
704
+ else if (platform.os === "windows") return await extractZipWindows(archivePath, destDir, timeoutMs);
705
+ else return await extractZipUnix(archivePath, destDir, timeoutMs);
706
+ } catch (error$1) {
707
+ return {
708
+ success: false,
709
+ error: error$1 instanceof Error ? error$1.message : String(error$1)
710
+ };
711
+ }
712
+ }
713
+ /**
714
+ * Make a file executable (Unix only)
715
+ *
716
+ * Runs `chmod +x` on the specified file. No-op on Windows.
717
+ *
718
+ * @param filePath - Absolute path to the file
719
+ * @returns Whether the operation succeeded
720
+ *
721
+ * @example
722
+ * ```typescript
723
+ * await makeExecutable("/path/to/binary");
724
+ * ```
725
+ */
726
+ async function makeExecutable(filePath) {
727
+ if ((await getPlatformInfo()).os === "windows") return true;
728
+ try {
729
+ return (await executeBinary({
730
+ binaryPath: "chmod",
731
+ args: ["+x", filePath],
732
+ timeoutMs: 5e3
733
+ })).success;
734
+ } catch {
735
+ return false;
736
+ }
737
+ }
738
+ /**
739
+ * Detect archive format from file extension
740
+ */
741
+ function detectFormat(archivePath) {
742
+ const lowerPath = archivePath.toLowerCase();
743
+ if (lowerPath.endsWith(".tar.gz") || lowerPath.endsWith(".tgz")) return "tar.gz";
744
+ if (lowerPath.endsWith(".zip")) return "zip";
745
+ return null;
746
+ }
747
+ /**
748
+ * Extract .tar.gz using tar command
749
+ */
750
+ async function extractTarGz(archivePath, destDir, timeoutMs) {
751
+ const result = await executeBinary({
752
+ binaryPath: "tar",
753
+ args: [
754
+ "-xzf",
755
+ archivePath,
756
+ "-C",
757
+ destDir
758
+ ],
759
+ timeoutMs
760
+ });
761
+ if (!result.success) return {
762
+ success: false,
763
+ error: result.stderr || `tar extraction failed with exit code ${result.exitCode}`
764
+ };
765
+ return { success: true };
766
+ }
767
+ /**
768
+ * Extract .zip using unzip command (macOS/Linux)
769
+ */
770
+ async function extractZipUnix(archivePath, destDir, timeoutMs) {
771
+ const result = await executeBinary({
772
+ binaryPath: "unzip",
773
+ args: [
774
+ "-o",
775
+ "-q",
776
+ archivePath,
777
+ "-d",
778
+ destDir
779
+ ],
780
+ timeoutMs
781
+ });
782
+ if (!result.success) return {
783
+ success: false,
784
+ error: result.stderr || `unzip extraction failed with exit code ${result.exitCode}`
785
+ };
786
+ return { success: true };
787
+ }
788
+ /**
789
+ * Extract .zip using PowerShell (Windows)
790
+ */
791
+ async function extractZipWindows(archivePath, destDir, timeoutMs) {
792
+ const result = await executeBinary({
793
+ binaryPath: "powershell",
794
+ args: [
795
+ "-NoProfile",
796
+ "-NonInteractive",
797
+ "-Command",
798
+ `Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force`
799
+ ],
800
+ timeoutMs
801
+ });
802
+ if (!result.success) return {
803
+ success: false,
804
+ error: result.stderr || `PowerShell extraction failed with exit code ${result.exitCode}`
805
+ };
806
+ return { success: true };
807
+ }
808
+
809
+ //#endregion
810
+ //#region src/utils/binary-resolver.ts
811
+ /**
812
+ * Binary resolver for Moss plugins
813
+ *
814
+ * Provides auto-detection and download of external CLI tools like Hugo.
815
+ * Implements a 4-step resolution flow:
816
+ * 1. Check configured path (from plugin config)
817
+ * 2. Check system PATH
818
+ * 3. Check plugin bin directory
819
+ * 4. Download from GitHub releases (if enabled)
820
+ */
821
+ /**
822
+ * Error thrown during binary resolution
823
+ */
824
+ var BinaryResolutionError = class extends Error {
825
+ constructor(message, phase, cause) {
826
+ super(message);
827
+ this.phase = phase;
828
+ this.cause = cause;
829
+ this.name = "BinaryResolutionError";
830
+ }
831
+ };
832
+ /**
833
+ * Resolve a binary, downloading if necessary
834
+ *
835
+ * Resolution order:
836
+ * 1. Configured path (from plugin config, e.g., hugo_path)
837
+ * 2. System PATH (just the binary name)
838
+ * 3. Plugin bin directory (.moss/plugins/{plugin}/bin/{name})
839
+ * 4. Download from GitHub releases (if autoDownload is true)
840
+ *
841
+ * @param config - Binary configuration
842
+ * @param options - Resolution options
843
+ * @returns Resolution result with path and source
844
+ * @throws BinaryResolutionError if binary cannot be resolved
845
+ *
846
+ * @example
847
+ * ```typescript
848
+ * const hugo = await resolveBinary(HUGO_CONFIG, {
849
+ * configuredPath: context.config.hugo_path,
850
+ * onProgress: (phase, msg) => reportProgress(phase, 0, 1, msg),
851
+ * });
852
+ *
853
+ * await executeBinary({
854
+ * binaryPath: hugo.path,
855
+ * args: ["--version"],
856
+ * });
857
+ * ```
858
+ */
859
+ async function resolveBinary(config, options = {}) {
860
+ const { configuredPath, autoDownload = true, onProgress } = options;
861
+ const progress = (phase, message) => {
862
+ onProgress?.(phase, message);
863
+ };
864
+ if (configuredPath) {
865
+ progress("detection", `Checking configured path: ${configuredPath}`);
866
+ const result = await checkBinary(configuredPath, config);
867
+ if (result) return {
868
+ path: configuredPath,
869
+ version: result.version,
870
+ source: "config"
871
+ };
872
+ }
873
+ progress("detection", `Checking system PATH for ${config.name}`);
874
+ const pathResult = await checkBinary(config.name, config);
875
+ if (pathResult) return {
876
+ path: config.name,
877
+ version: pathResult.version,
878
+ source: "path"
879
+ };
880
+ const pluginBinPath = await getPluginBinPath(config);
881
+ progress("detection", `Checking plugin storage: ${pluginBinPath}`);
882
+ if (await binaryExistsInPluginStorage(config)) {
883
+ const storedResult = await checkBinary(pluginBinPath, config);
884
+ if (storedResult) return {
885
+ path: pluginBinPath,
886
+ version: storedResult.version,
887
+ source: "plugin-storage"
888
+ };
889
+ }
890
+ if (!autoDownload) throw new BinaryResolutionError(`${config.name} not found. Please install it manually or set the path in plugin configuration.\n\nInstallation options:\n- Install via package manager (brew, apt, etc.)\n- Download from the official website\n- Set ${config.name}_path in .moss/config.toml`, "detection");
891
+ progress("download", `Downloading ${config.name}...`);
892
+ const downloadedPath = await downloadBinary(config, progress);
893
+ const downloadedResult = await checkBinary(downloadedPath, config);
894
+ if (!downloadedResult) throw new BinaryResolutionError(`Downloaded ${config.name} binary failed verification. The binary may be corrupted or incompatible with your system.`, "validation");
895
+ return {
896
+ path: downloadedPath,
897
+ version: downloadedResult.version,
898
+ source: "downloaded"
899
+ };
900
+ }
901
+ /**
902
+ * Check if a binary exists and optionally extract its version
903
+ */
904
+ async function checkBinary(binaryPath, config) {
905
+ try {
906
+ const [cmd, ...args] = parseCommand(config.versionCommand ?? `${config.name} version`, binaryPath, config.name);
907
+ const result = await executeBinary({
908
+ binaryPath: cmd,
909
+ args,
910
+ timeoutMs: 1e4
911
+ });
912
+ if (!result.success) return null;
913
+ let version;
914
+ if (config.versionPattern) {
915
+ const match = (result.stdout + result.stderr).match(config.versionPattern);
916
+ if (match && match[1]) version = match[1];
917
+ }
918
+ return { version };
919
+ } catch {
920
+ return null;
921
+ }
922
+ }
923
+ /**
924
+ * Parse a version command, replacing {name} with the binary path
925
+ */
926
+ function parseCommand(template, binaryPath, name) {
927
+ const resolved = template.replace(/{name}/g, binaryPath);
928
+ if (resolved.startsWith(binaryPath)) return [binaryPath, ...resolved.slice(binaryPath.length).trim().split(/\s+/).filter(Boolean)];
929
+ return resolved.split(/\s+/).filter(Boolean);
930
+ }
931
+ /**
932
+ * Get the full path to the binary in plugin storage
933
+ */
934
+ async function getPluginBinPath(config) {
935
+ const ctx = getInternalContext();
936
+ const binaryName = getBinaryFilename(config, (await getPlatformInfo()).os === "windows");
937
+ return `${ctx.moss_dir}/plugins/${ctx.plugin_name}/bin/${binaryName}`;
938
+ }
939
+ /**
940
+ * Get the binary filename (with .exe on Windows)
941
+ */
942
+ function getBinaryFilename(config, isWindows) {
943
+ const baseName = config.binaryName ?? config.name;
944
+ return isWindows ? `${baseName}.exe` : baseName;
945
+ }
946
+ /**
947
+ * Check if binary exists in plugin storage
948
+ */
949
+ async function binaryExistsInPluginStorage(config) {
950
+ return pluginFileExists(`bin/${getBinaryFilename(config, (await getPlatformInfo()).os === "windows")}`);
951
+ }
952
+ /**
953
+ * Download and extract binary from GitHub releases
954
+ */
955
+ async function downloadBinary(config, progress) {
956
+ const platform = await getPlatformInfo();
957
+ const source = config.sources[platform.platformKey];
958
+ if (!source) throw new BinaryResolutionError(`No download source configured for platform: ${platform.platformKey}`, "download");
959
+ let version;
960
+ let downloadUrl;
961
+ if (source.github) {
962
+ progress("download", "Fetching latest release info from GitHub...");
963
+ version = (await getLatestRelease(source.github.owner, source.github.repo)).version;
964
+ const assetName = resolveAssetPattern(source.github.assetPattern, version, platform);
965
+ downloadUrl = `https://github.com/${source.github.owner}/${source.github.repo}/releases/download/v${version}/${assetName}`;
966
+ } else if (source.directUrl) throw new BinaryResolutionError("Direct URL downloads not yet implemented. Please use GitHub source.", "download");
967
+ else throw new BinaryResolutionError(`No download source configured for ${config.name}`, "download");
968
+ progress("download", `Downloading ${config.name} v${version}...`);
969
+ const ctx = getInternalContext();
970
+ const archiveFilename = downloadUrl.split("/").pop() ?? "archive";
971
+ const archivePath = `${ctx.moss_dir}/plugins/${ctx.plugin_name}/.tmp/${archiveFilename}`;
972
+ await downloadToPluginStorage(downloadUrl, `.tmp/${archiveFilename}`);
973
+ progress("extraction", "Extracting archive...");
974
+ const binDir = `${ctx.moss_dir}/plugins/${ctx.plugin_name}/bin`;
975
+ await writePluginFile("bin/.gitkeep", "");
976
+ const extractResult = await extractArchive({
977
+ archivePath,
978
+ destDir: binDir
979
+ });
980
+ if (!extractResult.success) throw new BinaryResolutionError(`Failed to extract archive: ${extractResult.error}`, "extraction");
981
+ const binaryPath = await getPluginBinPath(config);
982
+ await makeExecutable(binaryPath);
983
+ progress("complete", `${config.name} v${version} installed successfully`);
984
+ await cacheReleaseInfo(config.name, version);
985
+ return binaryPath;
986
+ }
987
+ /**
988
+ * Fetch latest release info from GitHub API
989
+ */
990
+ async function getLatestRelease(owner, repo) {
991
+ const cacheKey = `${owner}/${repo}`;
992
+ try {
993
+ const response = await fetchUrl(`https://api.github.com/repos/${owner}/${repo}/releases/latest`, { timeoutMs: 1e4 });
994
+ if (!response.ok) {
995
+ if (response.status === 403 || response.status === 429) {
996
+ const cached = await getCachedRelease(cacheKey);
997
+ if (cached) return cached;
998
+ throw new BinaryResolutionError("GitHub API rate limit exceeded. Please try again later or install the binary manually.", "download");
999
+ }
1000
+ throw new BinaryResolutionError(`Failed to fetch release info: HTTP ${response.status}`, "download");
1001
+ }
1002
+ const tag = JSON.parse(response.text()).tag_name;
1003
+ return {
1004
+ version: tag.replace(/^v/, ""),
1005
+ tag
1006
+ };
1007
+ } catch (error$1) {
1008
+ if (error$1 instanceof BinaryResolutionError) throw error$1;
1009
+ const cached = await getCachedRelease(cacheKey);
1010
+ if (cached) return cached;
1011
+ throw new BinaryResolutionError(`Failed to fetch release info: ${error$1 instanceof Error ? error$1.message : String(error$1)}`, "download", error$1 instanceof Error ? error$1 : void 0);
1012
+ }
1013
+ }
1014
+ /**
1015
+ * Resolve asset pattern with actual values
1016
+ */
1017
+ function resolveAssetPattern(pattern, version, platform) {
1018
+ return pattern.replace(/{version}/g, version).replace(/{os}/g, platform.os).replace(/{arch}/g, {
1019
+ x64: "amd64",
1020
+ arm64: "arm64"
1021
+ }[platform.arch] ?? platform.arch);
1022
+ }
1023
+ /**
1024
+ * Download a file to plugin storage
1025
+ *
1026
+ * Uses curl (macOS/Linux) or PowerShell (Windows) to download the file
1027
+ * directly to the target path, avoiding memory limitations of base64 encoding.
1028
+ */
1029
+ async function downloadToPluginStorage(url, relativePath) {
1030
+ const ctx = getInternalContext();
1031
+ const platform = await getPlatformInfo();
1032
+ const targetPath = `${ctx.moss_dir}/plugins/${ctx.plugin_name}/${relativePath}`;
1033
+ const parentDir = targetPath.substring(0, targetPath.lastIndexOf("/"));
1034
+ if (platform.os === "windows") {
1035
+ await executeBinary({
1036
+ binaryPath: "powershell",
1037
+ args: [
1038
+ "-NoProfile",
1039
+ "-NonInteractive",
1040
+ "-Command",
1041
+ `New-Item -ItemType Directory -Force -Path '${parentDir}'`
1042
+ ],
1043
+ timeoutMs: 5e3
1044
+ });
1045
+ const result = await executeBinary({
1046
+ binaryPath: "powershell",
1047
+ args: [
1048
+ "-NoProfile",
1049
+ "-NonInteractive",
1050
+ "-Command",
1051
+ `Invoke-WebRequest -Uri '${url}' -OutFile '${targetPath}'`
1052
+ ],
1053
+ timeoutMs: 3e5
1054
+ });
1055
+ if (!result.success) throw new BinaryResolutionError(`Download failed: ${result.stderr || result.stdout}`, "download");
1056
+ } else {
1057
+ await executeBinary({
1058
+ binaryPath: "mkdir",
1059
+ args: ["-p", parentDir],
1060
+ timeoutMs: 5e3
1061
+ });
1062
+ const result = await executeBinary({
1063
+ binaryPath: "curl",
1064
+ args: [
1065
+ "-fsSL",
1066
+ "--create-dirs",
1067
+ "-o",
1068
+ targetPath,
1069
+ url
1070
+ ],
1071
+ timeoutMs: 3e5
1072
+ });
1073
+ if (!result.success) throw new BinaryResolutionError(`Download failed: ${result.stderr || `curl exited with code ${result.exitCode}`}`, "download");
1074
+ }
1075
+ }
1076
+ /**
1077
+ * Cache release info for fallback
1078
+ */
1079
+ async function cacheReleaseInfo(name, version) {
1080
+ try {
1081
+ const cached = {
1082
+ version,
1083
+ tag: `v${version}`,
1084
+ cachedAt: (/* @__PURE__ */ new Date()).toISOString()
1085
+ };
1086
+ await writePluginFile(`cache/${name}-release.json`, JSON.stringify(cached, null, 2));
1087
+ } catch {}
1088
+ }
1089
+ /**
1090
+ * Get cached release info
1091
+ */
1092
+ async function getCachedRelease(cacheKey) {
1093
+ try {
1094
+ const name = cacheKey.split("/")[1];
1095
+ if (!name) return null;
1096
+ if (!await pluginFileExists(`cache/${name}-release.json`)) return null;
1097
+ const content = await readPluginFile(`cache/${name}-release.json`);
1098
+ const cached = JSON.parse(content);
1099
+ return {
1100
+ version: cached.version,
1101
+ tag: cached.tag
1102
+ };
1103
+ } catch {
1104
+ return null;
1105
+ }
1106
+ }
1107
+
544
1108
  //#endregion
545
1109
  //#region src/utils/cookies.ts
546
1110
  /**
@@ -608,5 +1172,194 @@ async function setPluginCookie(cookies) {
608
1172
  }
609
1173
 
610
1174
  //#endregion
611
- export { closeBrowser, downloadAsset, error, executeBinary, fetchUrl, fileExists, getMessageContext, getPluginCookie, getTauriCore, isTauriAvailable, listFiles, listPluginFiles, log, openBrowser, pluginFileExists, readFile, readPluginFile, reportComplete, reportError, reportProgress, sendMessage, setMessageContext, setPluginCookie, warn, writeFile, writePluginFile };
1175
+ //#region src/utils/window.ts
1176
+ /**
1177
+ * Window utilities for plugins
1178
+ * Enables plugins to show custom dialogs and UI elements
1179
+ */
1180
+ /**
1181
+ * Show a plugin dialog and wait for user response
1182
+ *
1183
+ * The dialog can be an embedded HTML page (via data: URL) that communicates
1184
+ * back to the plugin via the submitDialogResult function.
1185
+ *
1186
+ * @param options - Dialog configuration
1187
+ * @returns Dialog result with submitted value or cancellation
1188
+ *
1189
+ * @example
1190
+ * ```typescript
1191
+ * const result = await showPluginDialog({
1192
+ * url: createMyDialogUrl(),
1193
+ * title: "Create Repository",
1194
+ * width: 400,
1195
+ * height: 300,
1196
+ * });
1197
+ *
1198
+ * if (result.type === "submitted") {
1199
+ * console.log("User submitted:", result.value);
1200
+ * } else {
1201
+ * console.log("User cancelled");
1202
+ * }
1203
+ * ```
1204
+ */
1205
+ async function showPluginDialog(options) {
1206
+ return await getTauriCore().invoke("show_plugin_dialog", {
1207
+ url: options.url,
1208
+ title: options.title,
1209
+ width: options.width ?? 500,
1210
+ height: options.height ?? 400,
1211
+ timeoutMs: options.timeoutMs ?? 3e5
1212
+ });
1213
+ }
1214
+ /**
1215
+ * Submit a result from within a plugin dialog
1216
+ *
1217
+ * This is called from inside the dialog HTML to send data back to the plugin.
1218
+ * The dialog will be closed automatically after submission.
1219
+ *
1220
+ * @param dialogId - The dialog ID (provided in the dialog's query string)
1221
+ * @param value - The value to submit
1222
+ * @returns Whether the submission was successful
1223
+ *
1224
+ * @example
1225
+ * ```typescript
1226
+ * // Inside dialog HTML:
1227
+ * const dialogId = new URLSearchParams(location.search).get('dialogId');
1228
+ * await submitDialogResult(dialogId, { repoName: 'my-repo' });
1229
+ * ```
1230
+ */
1231
+ async function submitDialogResult(dialogId, value) {
1232
+ return getTauriCore().invoke("submit_dialog_result", {
1233
+ dialogId,
1234
+ result: {
1235
+ type: "submitted",
1236
+ value
1237
+ }
1238
+ });
1239
+ }
1240
+ /**
1241
+ * Cancel a plugin dialog
1242
+ *
1243
+ * This is called from inside the dialog HTML to cancel without submitting.
1244
+ * The dialog will be closed automatically.
1245
+ *
1246
+ * @param dialogId - The dialog ID (provided in the dialog's query string)
1247
+ *
1248
+ * @example
1249
+ * ```typescript
1250
+ * // Inside dialog HTML:
1251
+ * const dialogId = new URLSearchParams(location.search).get('dialogId');
1252
+ * await cancelDialog(dialogId);
1253
+ * ```
1254
+ */
1255
+ async function cancelDialog(dialogId) {
1256
+ return getTauriCore().invoke("submit_dialog_result", {
1257
+ dialogId,
1258
+ result: { type: "cancelled" }
1259
+ });
1260
+ }
1261
+
1262
+ //#endregion
1263
+ //#region src/utils/events.ts
1264
+ /**
1265
+ * Get Tauri event API
1266
+ * @internal
1267
+ */
1268
+ function getTauriEvent() {
1269
+ const w = window;
1270
+ if (!w.__TAURI__?.event) throw new Error("Tauri event API not available");
1271
+ return w.__TAURI__.event;
1272
+ }
1273
+ /**
1274
+ * Check if Tauri event API is available
1275
+ */
1276
+ function isEventApiAvailable() {
1277
+ return !!window.__TAURI__?.event;
1278
+ }
1279
+ /**
1280
+ * Emit an event to other parts of the application
1281
+ *
1282
+ * @param event - Event name (e.g., "repo-created", "dialog-result")
1283
+ * @param payload - Data to send with the event
1284
+ *
1285
+ * @example
1286
+ * ```typescript
1287
+ * // From dialog:
1288
+ * await emitEvent("repo-name-validated", { name: "my-repo", available: true });
1289
+ *
1290
+ * // From plugin:
1291
+ * await emitEvent("deployment-started", { url: "https://github.com/..." });
1292
+ * ```
1293
+ */
1294
+ async function emitEvent(event, payload) {
1295
+ await getTauriEvent().emit(event, payload);
1296
+ }
1297
+ /**
1298
+ * Listen for events from other parts of the application
1299
+ *
1300
+ * @param event - Event name to listen for
1301
+ * @param handler - Function to call when event is received
1302
+ * @returns Cleanup function to stop listening
1303
+ *
1304
+ * @example
1305
+ * ```typescript
1306
+ * const unlisten = await onEvent<{ name: string; available: boolean }>(
1307
+ * "repo-name-validated",
1308
+ * (data) => {
1309
+ * console.log(`Repo ${data.name} is ${data.available ? "available" : "taken"}`);
1310
+ * }
1311
+ * );
1312
+ *
1313
+ * // Later, to stop listening:
1314
+ * unlisten();
1315
+ * ```
1316
+ */
1317
+ async function onEvent(event, handler) {
1318
+ return await getTauriEvent().listen(event, (e) => {
1319
+ handler(e.payload);
1320
+ });
1321
+ }
1322
+ /**
1323
+ * Wait for a single event occurrence
1324
+ *
1325
+ * @param event - Event name to wait for
1326
+ * @param timeoutMs - Maximum time to wait (default: 30000ms)
1327
+ * @returns Promise that resolves with the event payload
1328
+ * @throws Error if timeout is reached
1329
+ *
1330
+ * @example
1331
+ * ```typescript
1332
+ * try {
1333
+ * const result = await waitForEvent<{ confirmed: boolean }>("user-confirmed", 10000);
1334
+ * if (result.confirmed) {
1335
+ * // proceed
1336
+ * }
1337
+ * } catch (e) {
1338
+ * console.log("User did not respond in time");
1339
+ * }
1340
+ * ```
1341
+ */
1342
+ async function waitForEvent(event, timeoutMs = 3e4) {
1343
+ return new Promise((resolve, reject) => {
1344
+ let unlisten = null;
1345
+ let timeoutId;
1346
+ const cleanup = () => {
1347
+ if (unlisten) unlisten();
1348
+ clearTimeout(timeoutId);
1349
+ };
1350
+ timeoutId = setTimeout(() => {
1351
+ cleanup();
1352
+ reject(/* @__PURE__ */ new Error(`Timeout waiting for event: ${event}`));
1353
+ }, timeoutMs);
1354
+ onEvent(event, (payload) => {
1355
+ cleanup();
1356
+ resolve(payload);
1357
+ }).then((unlistenFn) => {
1358
+ unlisten = unlistenFn;
1359
+ });
1360
+ });
1361
+ }
1362
+
1363
+ //#endregion
1364
+ export { BinaryResolutionError, cancelDialog, clearPlatformCache, closeBrowser, downloadAsset, emitEvent, error, executeBinary, extractArchive, fetchUrl, fileExists, getMessageContext, getPlatformInfo, getPluginCookie, getTauriCore, isEventApiAvailable, isTauriAvailable, listFiles, listPluginFiles, log, makeExecutable, onEvent, openBrowser, pluginFileExists, readFile, readPluginFile, reportComplete, reportError, reportProgress, resolveBinary, sendMessage, setMessageContext, setPluginCookie, showPluginDialog, submitDialogResult, waitForEvent, warn, writeFile, writePluginFile };
612
1365
  //# sourceMappingURL=index.mjs.map