@testsmith/api-spector 0.9.1 → 0.9.2

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.
@@ -1,4 +1,4 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./GraphQLEditor-CJm8glW5.js","./codemirror-BKmwMUlV.js","./ajv-CJPwFjiP.js","./SoapEditor-C2eMs7-Z.js"])))=>i.map(i=>d[i]);
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./GraphQLEditor-RRDuQogA.js","./codemirror-BKmwMUlV.js","./ajv-CJPwFjiP.js","./SoapEditor-BncNQmjP.js"])))=>i.map(i=>d[i]);
2
2
  import { r as requireReact, g as getDefaultExportFromCjs, R as React, a as reactExports, b as autocompletion, h as hoverTooltip, j as jsxRuntimeExports, V as ViewPlugin, E as EditorView, k as keymap, t as toggleComment, D as Decoration, c as RangeSetBuilder, x as xml, d as json, e as ReactCodeMirror, o as oneDark, f as javascript } from "./codemirror-BKmwMUlV.js";
3
3
  import { r as requireAjv } from "./ajv-CJPwFjiP.js";
4
4
  var client = { exports: {} };
@@ -14771,7 +14771,7 @@ const useStore$1 = create$1()(
14771
14771
  })
14772
14772
  }))
14773
14773
  );
14774
- const { electron: electron$I } = window;
14774
+ const { electron: electron$J } = window;
14775
14775
  function useAutoSave() {
14776
14776
  const collections = useStore$1((s) => s.collections);
14777
14777
  useStore$1((s) => s.environments);
@@ -14790,7 +14790,7 @@ function useAutoSave() {
14790
14790
  for (const { relPath, data, dirty } of dirtyCollections) {
14791
14791
  if (!dirty) continue;
14792
14792
  try {
14793
- await electron$I.saveCollection(relPath, data);
14793
+ await electron$J.saveCollection(relPath, data);
14794
14794
  markCollectionClean(data.id);
14795
14795
  } catch (e) {
14796
14796
  console.error("Auto-save failed for", relPath, e);
@@ -14809,7 +14809,7 @@ function useAutoSave() {
14809
14809
  for (const { relPath, data, dirty } of dirtyFlows) {
14810
14810
  if (!dirty) continue;
14811
14811
  try {
14812
- await electron$I.saveFlow(relPath, data);
14812
+ await electron$J.saveFlow(relPath, data);
14813
14813
  markFlowClean(data.id);
14814
14814
  } catch (e) {
14815
14815
  console.error("Auto-save failed for", relPath, e);
@@ -14825,7 +14825,7 @@ function useAutoSave() {
14825
14825
  if (wsTimerRef.current) clearTimeout(wsTimerRef.current);
14826
14826
  wsTimerRef.current = setTimeout(async () => {
14827
14827
  try {
14828
- await electron$I.saveWorkspace(workspace);
14828
+ await electron$J.saveWorkspace(workspace);
14829
14829
  } catch {
14830
14830
  }
14831
14831
  }, 300);
@@ -14834,7 +14834,7 @@ function useAutoSave() {
14834
14834
  };
14835
14835
  }, [workspace]);
14836
14836
  }
14837
- const { electron: electron$H } = window;
14837
+ const { electron: electron$I } = window;
14838
14838
  function useWorkspaceLoader() {
14839
14839
  const loadCollection = useStore$1((s) => s.loadCollection);
14840
14840
  const loadEnvironment = useStore$1((s) => s.loadEnvironment);
@@ -14861,7 +14861,7 @@ function useWorkspaceLoader() {
14861
14861
  if (typeof ws.settings?.zoom === "number") setZoom(ws.settings.zoom);
14862
14862
  if (ws.settings?.persistHistory) {
14863
14863
  try {
14864
- const entries = await electron$H.loadHistory();
14864
+ const entries = await electron$I.loadHistory();
14865
14865
  useStore$1.getState().setHistory(entries);
14866
14866
  } catch {
14867
14867
  }
@@ -14870,14 +14870,14 @@ function useWorkspaceLoader() {
14870
14870
  }
14871
14871
  for (const colPath of ws.collections ?? []) {
14872
14872
  try {
14873
- const col = await electron$H.loadCollection(colPath);
14873
+ const col = await electron$I.loadCollection(colPath);
14874
14874
  loadCollection(colPath, col);
14875
14875
  } catch {
14876
14876
  }
14877
14877
  }
14878
14878
  for (const envPath of ws.environments ?? []) {
14879
14879
  try {
14880
- const env2 = await electron$H.loadEnvironment(envPath);
14880
+ const env2 = await electron$I.loadEnvironment(envPath);
14881
14881
  loadEnvironment(envPath, env2);
14882
14882
  } catch {
14883
14883
  }
@@ -14896,32 +14896,38 @@ function useWorkspaceLoader() {
14896
14896
  }
14897
14897
  for (const relPath of ws.mocks ?? []) {
14898
14898
  try {
14899
- const mockData = await electron$H.loadMock(relPath);
14899
+ const mockData = await electron$I.loadMock(relPath);
14900
14900
  loadMock(relPath, mockData);
14901
14901
  } catch {
14902
14902
  }
14903
14903
  }
14904
14904
  for (const relPath of ws.flows ?? []) {
14905
14905
  try {
14906
- const flowData = await electron$H.loadFlow(relPath);
14906
+ const flowData = await electron$I.loadFlow(relPath);
14907
14907
  loadFlow(relPath, flowData);
14908
14908
  } catch {
14909
14909
  }
14910
14910
  }
14911
14911
  try {
14912
- const snapshots = await electron$H.listContractSnapshots(ws.contracts ?? []);
14912
+ const snapshots = await electron$I.listContractSnapshots(ws.contracts ?? []);
14913
14913
  for (const { relPath, snapshot } of snapshots) loadContractSnapshot(relPath, snapshot);
14914
14914
  } catch {
14915
14915
  }
14916
14916
  if ((ws.collections ?? []).length > 0) {
14917
14917
  try {
14918
- const firstCol = await electron$H.loadCollection(ws.collections[0]);
14918
+ const firstCol = await electron$I.loadCollection(ws.collections[0]);
14919
14919
  setActiveCollection(firstCol.id);
14920
14920
  } catch {
14921
14921
  }
14922
14922
  }
14923
14923
  }, [loadCollection, loadEnvironment, loadMock, loadFlow, loadContractSnapshot, setActiveCollection, setTheme, setZoom]);
14924
- return { applyWorkspace };
14924
+ const reloadWorkspace = reactExports.useCallback(async () => {
14925
+ const path = useStore$1.getState().workspacePath;
14926
+ if (!path) return;
14927
+ const result = await electron$I.openWorkspacePath(path);
14928
+ if (result) await applyWorkspace(result.workspace, result.workspacePath);
14929
+ }, [applyWorkspace]);
14930
+ return { applyWorkspace, reloadWorkspace };
14925
14931
  }
14926
14932
  var reactDomExports = requireReactDom();
14927
14933
  const ReactDOM = /* @__PURE__ */ getDefaultExportFromCjs(reactDomExports);
@@ -25861,7 +25867,7 @@ function Modal({
25861
25867
  }
25862
25868
  );
25863
25869
  }
25864
- const { electron: electron$G } = window;
25870
+ const { electron: electron$H } = window;
25865
25871
  function Oauth2Form({ auth, onChange }) {
25866
25872
  const t = useT();
25867
25873
  const activeEnv = useActiveEnvironment();
@@ -25881,7 +25887,7 @@ function Oauth2Form({ auth, onChange }) {
25881
25887
  const flow = auth.oauth2Flow ?? "client_credentials";
25882
25888
  async function saveSecret(ref) {
25883
25889
  if (!secretValue || !ref) return;
25884
- await electron$G.setSecret(ref, secretValue);
25890
+ await electron$H.setSecret(ref, secretValue);
25885
25891
  setSaved(true);
25886
25892
  setSecretValue("");
25887
25893
  setTimeout(() => setSaved(false), 2e3);
@@ -25890,7 +25896,7 @@ function Oauth2Form({ auth, onChange }) {
25890
25896
  setStatus("fetching");
25891
25897
  setError("");
25892
25898
  try {
25893
- const result = await electron$G.oauth2StartFlow(auth, resolveCtx());
25899
+ const result = await electron$H.oauth2StartFlow(auth, resolveCtx());
25894
25900
  onChange({ oauth2CachedToken: result.accessToken, oauth2TokenExpiry: result.expiresAt });
25895
25901
  if (result.refreshToken) setRefreshToken(result.refreshToken);
25896
25902
  setStatus("ok");
@@ -25904,7 +25910,7 @@ function Oauth2Form({ auth, onChange }) {
25904
25910
  setStatus("fetching");
25905
25911
  setError("");
25906
25912
  try {
25907
- const result = await electron$G.oauth2RefreshToken(auth, resolveCtx(), refreshToken);
25913
+ const result = await electron$H.oauth2RefreshToken(auth, resolveCtx(), refreshToken);
25908
25914
  onChange({ oauth2CachedToken: result.accessToken, oauth2TokenExpiry: result.expiresAt });
25909
25915
  if (result.refreshToken) setRefreshToken(result.refreshToken);
25910
25916
  setStatus("ok");
@@ -26764,7 +26770,7 @@ function CollectionSettingsModal({ collection: collection2, onClose }) {
26764
26770
  }
26765
26771
  );
26766
26772
  }
26767
- const { electron: electron$F } = window;
26773
+ const { electron: electron$G } = window;
26768
26774
  function normalisePath(url) {
26769
26775
  let path = url.replace(/^\{\{[^}]+\}\}/, "").replace(/^https?:\/\/[^/]+/, "");
26770
26776
  if (!path.startsWith("/")) path = "/" + path;
@@ -26852,7 +26858,7 @@ function SchemaSyncModal({
26852
26858
  setLoading(true);
26853
26859
  setError(null);
26854
26860
  try {
26855
- const entries = await electron$F.extractOpenApiSchemas();
26861
+ const entries = await electron$G.extractOpenApiSchemas();
26856
26862
  if (!entries) {
26857
26863
  setLoading(false);
26858
26864
  return;
@@ -26871,7 +26877,7 @@ function SchemaSyncModal({
26871
26877
  setLoading(true);
26872
26878
  setError(null);
26873
26879
  try {
26874
- const entries = await electron$F.extractOpenApiSchemasFromUrl(trimmed);
26880
+ const entries = await electron$G.extractOpenApiSchemasFromUrl(trimmed);
26875
26881
  setSpecEntries(entries);
26876
26882
  autoSelectChanged(entries);
26877
26883
  } catch (err) {
@@ -26904,7 +26910,7 @@ function SchemaSyncModal({
26904
26910
  }
26905
26911
  const entry = useStore$1.getState().collections[collectionId];
26906
26912
  if (entry) {
26907
- await electron$F.saveCollection(entry.relPath, entry.data);
26913
+ await electron$G.saveCollection(entry.relPath, entry.data);
26908
26914
  markCollectionClean(collectionId);
26909
26915
  }
26910
26916
  onClose();
@@ -27417,7 +27423,7 @@ function hasContract(r) {
27417
27423
  const c = r.contract;
27418
27424
  return !!c && (c.statusCode != null || !!c.bodySchema || !!c.bodyMatcher || (c.headers?.length ?? 0) > 0 || (c.providerStates?.length ?? 0) > 0);
27419
27425
  }
27420
- const { electron: electron$E } = window;
27426
+ const { electron: electron$F } = window;
27421
27427
  function cloudEnabled() {
27422
27428
  return Boolean(useStore$1.getState().workspace?.settings?.cloud?.enabled);
27423
27429
  }
@@ -27426,7 +27432,7 @@ function assertEnabled() {
27426
27432
  }
27427
27433
  async function pushContractToCloud(requests, opts) {
27428
27434
  assertEnabled();
27429
- return electron$E.cloudPushPact({
27435
+ return electron$F.cloudPushPact({
27430
27436
  consumer: opts.consumer,
27431
27437
  provider: opts.provider,
27432
27438
  consumerVersion: opts.version,
@@ -27435,16 +27441,16 @@ async function pushContractToCloud(requests, opts) {
27435
27441
  }
27436
27442
  async function pushProviderSpecToCloud(opts) {
27437
27443
  assertEnabled();
27438
- return electron$E.cloudPushSpec(opts);
27444
+ return electron$F.cloudPushSpec(opts);
27439
27445
  }
27440
27446
  function openCloudMatrix() {
27441
- void electron$E.cloudOpenMatrix();
27447
+ void electron$F.cloudOpenMatrix();
27442
27448
  }
27443
27449
  async function pushFlowToCloud(flow) {
27444
27450
  assertEnabled();
27445
27451
  const s = useStore$1.getState();
27446
27452
  const environment = resolveEnvironmentById(s.environments, s.activeEnvironmentId);
27447
- return electron$E.cloudPushFlow({
27453
+ return electron$F.cloudPushFlow({
27448
27454
  flow,
27449
27455
  collections: Object.values(s.collections).map((c) => c.data),
27450
27456
  flows: Object.values(s.flows).map((f) => f.data),
@@ -27454,12 +27460,12 @@ async function pushFlowToCloud(flow) {
27454
27460
  }
27455
27461
  async function runFlowInCloud(flow, onStatus) {
27456
27462
  assertEnabled();
27457
- const started = await electron$E.cloudRunFlow(flow.name);
27463
+ const started = await electron$F.cloudRunFlow(flow.name);
27458
27464
  onStatus?.(started.status);
27459
27465
  const deadlineMs = 2 * 60 * 1e3;
27460
27466
  const t0 = Date.now();
27461
27467
  for (; ; ) {
27462
- const run = await electron$E.cloudGetFlowRun({ name: flow.name, runId: started.id });
27468
+ const run = await electron$F.cloudGetFlowRun({ name: flow.name, runId: started.id });
27463
27469
  onStatus?.(run.status);
27464
27470
  if (run.status === "passed" || run.status === "failed" || run.status === "error" || run.status === "done") {
27465
27471
  if (run.summary) return run.summary;
@@ -27470,12 +27476,53 @@ async function runFlowInCloud(flow, onStatus) {
27470
27476
  }
27471
27477
  }
27472
27478
  function openCloudFlow(flow) {
27473
- void electron$E.cloudOpenFlow(flow.name);
27479
+ void electron$F.cloudOpenFlow(flow.name);
27480
+ }
27481
+ async function shareCollectionToCloud(collection2) {
27482
+ assertEnabled();
27483
+ return electron$F.cloudShareCollection(collection2);
27484
+ }
27485
+ async function shareRequestToCloud(request2) {
27486
+ assertEnabled();
27487
+ return electron$F.cloudShareRequest(request2);
27488
+ }
27489
+ function collectFolderRequestIds(folder2) {
27490
+ return [...folder2.requestIds, ...folder2.folders.flatMap(collectFolderRequestIds)];
27491
+ }
27492
+ async function shareFolderToCloud(collection2, folder2) {
27493
+ assertEnabled();
27494
+ const ids = new Set(collectFolderRequestIds(folder2));
27495
+ const requests = {};
27496
+ for (const id2 of ids) {
27497
+ const r = collection2.requests[id2];
27498
+ if (r) requests[id2] = r;
27499
+ }
27500
+ const payload = {
27501
+ version: "1.0",
27502
+ id: folder2.id,
27503
+ name: folder2.name,
27504
+ rootFolder: folder2,
27505
+ requests,
27506
+ collectionVariables: collection2.collectionVariables
27507
+ };
27508
+ return electron$F.cloudShareFolder({ sourceId: folder2.id, name: folder2.name, payload });
27509
+ }
27510
+ async function lookupShare(type, sourceId) {
27511
+ assertEnabled();
27512
+ return electron$F.cloudShareLookup({ type, sourceId });
27513
+ }
27514
+ async function restrictShare(token, expiresDays) {
27515
+ assertEnabled();
27516
+ return electron$F.cloudShareUpdate({ token, expiresDays });
27517
+ }
27518
+ async function revokeShare(token) {
27519
+ assertEnabled();
27520
+ await electron$F.cloudShareRevoke(token);
27474
27521
  }
27475
27522
  async function getCloudMockRoutes(name) {
27476
27523
  if (!cloudEnabled()) return null;
27477
27524
  try {
27478
- const res = await electron$E.cloudGetMock(name);
27525
+ const res = await electron$F.cloudGetMock(name);
27479
27526
  return res.exists ? res.routes ?? [] : null;
27480
27527
  } catch {
27481
27528
  return null;
@@ -27484,7 +27531,7 @@ async function getCloudMockRoutes(name) {
27484
27531
  async function pushMockToCloud(server, routeIds) {
27485
27532
  assertEnabled();
27486
27533
  const routes = routeIds ? (server.routes ?? []).filter((r) => routeIds.includes(r.id)) : server.routes ?? [];
27487
- return electron$E.cloudPushMock({ ...server, routes });
27534
+ return electron$F.cloudPushMock({ ...server, routes });
27488
27535
  }
27489
27536
  async function pushRequestAsMonitor(request2, opts) {
27490
27537
  assertEnabled();
@@ -27508,7 +27555,7 @@ async function pushRequestAsMonitor(request2, opts) {
27508
27555
  ...request2.headers
27509
27556
  ];
27510
27557
  const mergedRequest = { ...request2, auth: mergedAuth, headers: mergedHeaders };
27511
- return electron$E.cloudPushMonitor(
27558
+ return electron$F.cloudPushMonitor(
27512
27559
  {
27513
27560
  request: mergedRequest,
27514
27561
  setup,
@@ -27926,6 +27973,201 @@ function Toast({ toast }) {
27926
27973
  if (!toast) return null;
27927
27974
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `mx-3 mb-2 px-2 py-1.5 rounded text-[11px] flex-shrink-0 ${toast.ok ? "bg-emerald-900/50 text-emerald-300 border border-emerald-800/50" : "bg-red-900/50 text-red-300 border border-red-800/50"}`, children: toast.msg });
27928
27975
  }
27976
+ function CloudRequiredModal({
27977
+ action,
27978
+ onConnect,
27979
+ onClose
27980
+ }) {
27981
+ const t = useT();
27982
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
27983
+ Modal,
27984
+ {
27985
+ onClose,
27986
+ closeOnBackdrop: true,
27987
+ overlayClassName: "bg-black/50 z-[300] flex items-center justify-center",
27988
+ panelClassName: "bg-surface-900 border border-surface-700 rounded-lg shadow-2xl p-5 w-96 flex flex-col gap-4",
27989
+ children: [
27990
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
27991
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "shrink-0 w-10 h-10 rounded-full bg-sky-900/40 border border-sky-700/40 flex items-center justify-center text-sky-300", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z" }) }) }),
27992
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
27993
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-sm font-semibold text-white", children: t("Connect to API Spector Cloud") }),
27994
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400 mt-0.5", children: t("Sharing needs the cloud") })
27995
+ ] })
27996
+ ] }),
27997
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-surface-300 leading-relaxed", children: action ? t("To :action you need to connect this workspace to API Spector Cloud, which hosts the public link.", { action }) : t("Public share links are hosted in API Spector Cloud. Connect this workspace to the cloud to create one.") }),
27998
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: t("Only request definitions with {{variables}} are shared. Secrets and environment values are never uploaded.") }),
27999
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2", children: [
28000
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
28001
+ "button",
28002
+ {
28003
+ onClick: onClose,
28004
+ className: "px-3 py-1.5 text-xs text-surface-400 hover:text-white transition-colors",
28005
+ children: t("Not now")
28006
+ }
28007
+ ),
28008
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
28009
+ "button",
28010
+ {
28011
+ onClick: onConnect,
28012
+ className: "px-3 py-1.5 text-xs font-medium text-white bg-sky-700 hover:bg-sky-600 rounded transition-colors",
28013
+ children: t("Open Cloud settings")
28014
+ }
28015
+ )
28016
+ ] })
28017
+ ]
28018
+ }
28019
+ );
28020
+ }
28021
+ function ShareModal({
28022
+ title,
28023
+ type,
28024
+ sourceId,
28025
+ onCreate,
28026
+ onClose
28027
+ }) {
28028
+ const t = useT();
28029
+ const [loading, setLoading] = reactExports.useState(true);
28030
+ const [busy, setBusy] = reactExports.useState(false);
28031
+ const [error2, setError] = reactExports.useState(null);
28032
+ const [copied, setCopied] = reactExports.useState(false);
28033
+ const [live, setLive] = reactExports.useState(null);
28034
+ const [expiryDays, setExpiryDays] = reactExports.useState(0);
28035
+ reactExports.useEffect(() => {
28036
+ let cancelled = false;
28037
+ (async () => {
28038
+ try {
28039
+ const res = await lookupShare(type, sourceId);
28040
+ if (!cancelled && res.exists && res.token && res.url && res.deep_link) {
28041
+ setLive({ token: res.token, url: res.url, deep_link: res.deep_link, expires_at: res.expires_at ?? null });
28042
+ }
28043
+ } catch (e) {
28044
+ if (!cancelled) setError(e.message);
28045
+ } finally {
28046
+ if (!cancelled) setLoading(false);
28047
+ }
28048
+ })();
28049
+ return () => {
28050
+ cancelled = true;
28051
+ };
28052
+ }, [type, sourceId]);
28053
+ async function run(fn) {
28054
+ setBusy(true);
28055
+ setError(null);
28056
+ try {
28057
+ await fn();
28058
+ } catch (e) {
28059
+ setError(e.message);
28060
+ } finally {
28061
+ setBusy(false);
28062
+ }
28063
+ }
28064
+ const create2 = () => run(async () => {
28065
+ const r = await onCreate();
28066
+ setLive({ token: r.token, url: r.url, deep_link: r.deep_link, expires_at: r.expires_at ?? null });
28067
+ });
28068
+ const applyExpiry = () => run(async () => {
28069
+ if (!live) return;
28070
+ const r = await restrictShare(live.token, expiryDays);
28071
+ setLive({ ...live, expires_at: r.expires_at ?? null });
28072
+ });
28073
+ const revoke = () => run(async () => {
28074
+ if (!live) return;
28075
+ await revokeShare(live.token);
28076
+ setLive(null);
28077
+ });
28078
+ const copy2 = async () => {
28079
+ if (!live) return;
28080
+ try {
28081
+ await navigator.clipboard.writeText(live.url);
28082
+ setCopied(true);
28083
+ setTimeout(() => setCopied(false), 2e3);
28084
+ } catch {
28085
+ }
28086
+ };
28087
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
28088
+ Modal,
28089
+ {
28090
+ onClose,
28091
+ title: t("Share: :name", { name: title }),
28092
+ overlayClassName: "bg-black/50 z-[300] flex items-center justify-center",
28093
+ panelClassName: "bg-surface-900 border border-surface-700 rounded-lg shadow-2xl w-[32rem] max-w-[92vw] flex flex-col",
28094
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-5 flex flex-col gap-4", children: [
28095
+ error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-red-400 bg-red-900/30 border border-red-800/40 rounded px-2 py-1.5", children: error2 }),
28096
+ loading ? /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-surface-400", children: t("Checking share status…") }) : !live ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
28097
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-surface-300 leading-relaxed", children: type === "request" ? t("Create a public read-only link to this request. Anyone with the link can view it or import it into their own app.") : type === "folder" ? t("Create a public read-only link to this folder. Anyone with the link can view its requests or import them into their own app.") : t("Create a public read-only link to this collection. Anyone with the link can view it or import it into their own app.") }),
28098
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: t("Only request definitions with {{variables}} are shared. Secrets and environment values are never uploaded.") }),
28099
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
28100
+ "button",
28101
+ {
28102
+ onClick: create2,
28103
+ disabled: busy,
28104
+ className: "px-3 py-1.5 text-xs font-medium text-white bg-sky-700 hover:bg-sky-600 disabled:opacity-50 rounded transition-colors",
28105
+ children: busy ? t("Creating…") : t("Create public link")
28106
+ }
28107
+ ) })
28108
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
28109
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
28110
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[11px] uppercase tracking-wide text-surface-500", children: t("Public link") }),
28111
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 flex items-center gap-2", children: [
28112
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
28113
+ "input",
28114
+ {
28115
+ readOnly: true,
28116
+ value: live.url,
28117
+ onClick: (e) => e.target.select(),
28118
+ className: "flex-1 rounded bg-surface-950 border border-surface-700 px-2 py-1.5 text-xs font-mono text-surface-200"
28119
+ }
28120
+ ),
28121
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: copy2, className: "px-3 py-1.5 text-xs border border-surface-700 rounded text-surface-300 hover:text-white transition-colors", children: copied ? t("Copied") : t("Copy") })
28122
+ ] })
28123
+ ] }),
28124
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-end gap-2", children: [
28125
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
28126
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[11px] uppercase tracking-wide text-surface-500", children: t("Restrict") }),
28127
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400 mt-1", children: live.expires_at ? t("Expires :date", { date: new Date(live.expires_at).toLocaleString() }) : t("Never expires") })
28128
+ ] }),
28129
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
28130
+ "select",
28131
+ {
28132
+ value: expiryDays,
28133
+ onChange: (e) => setExpiryDays(Number(e.target.value)),
28134
+ className: "rounded bg-surface-950 border border-surface-700 px-2 py-1.5 text-xs text-surface-200",
28135
+ children: [
28136
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 0, children: t("No expiry") }),
28137
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 1, children: t("1 day") }),
28138
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 7, children: t("7 days") }),
28139
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 30, children: t("30 days") })
28140
+ ]
28141
+ }
28142
+ ),
28143
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
28144
+ "button",
28145
+ {
28146
+ onClick: applyExpiry,
28147
+ disabled: busy,
28148
+ className: "px-3 py-1.5 text-xs border border-surface-700 rounded text-surface-300 hover:text-white disabled:opacity-50 transition-colors",
28149
+ children: t("Apply")
28150
+ }
28151
+ )
28152
+ ] }),
28153
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: t("Only request definitions with {{variables}} are shared. Secrets and environment values are never uploaded.") }),
28154
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center pt-2 border-t border-surface-800", children: [
28155
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
28156
+ "button",
28157
+ {
28158
+ onClick: revoke,
28159
+ disabled: busy,
28160
+ className: "px-3 py-1.5 text-xs text-red-400 hover:text-red-300 disabled:opacity-50 transition-colors",
28161
+ children: t("Revoke link")
28162
+ }
28163
+ ),
28164
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "px-3 py-1.5 text-xs text-surface-400 hover:text-white transition-colors", children: t("Done") })
28165
+ ] })
28166
+ ] })
28167
+ ] })
28168
+ }
28169
+ );
28170
+ }
27929
28171
  function requestPath(url) {
27930
28172
  const trimmed = url.trim().replace(/^\{\{[^}]+\}\}/, "").replace(/^https?:\/\/[^/]+/i, "");
27931
28173
  return trimmed || url.trim();
@@ -27995,6 +28237,9 @@ function RequestRow({
27995
28237
  const groupIndent = indent + 14;
27996
28238
  const { toast, show } = useToast();
27997
28239
  const addSecurityTests = useStore$1((s) => s.addSecurityTests);
28240
+ const openWorkspaceSettings = useStore$1((s) => s.openWorkspaceSettings);
28241
+ const [showCloudRequired, setShowCloudRequired] = reactExports.useState(false);
28242
+ const [showShare, setShowShare] = reactExports.useState(false);
27998
28243
  function handleRowClick(e) {
27999
28244
  if (e.metaKey || e.ctrlKey) {
28000
28245
  e.preventDefault();
@@ -28014,6 +28259,13 @@ function RequestRow({
28014
28259
  show(e.message, false);
28015
28260
  }
28016
28261
  }
28262
+ function openShare() {
28263
+ if (!cloudEnabled()) {
28264
+ setShowCloudRequired(true);
28265
+ return;
28266
+ }
28267
+ setShowShare(true);
28268
+ }
28017
28269
  const hookMenuItems = ["beforeAll", "before", "after", "afterAll"].map((ht) => ({
28018
28270
  type: "item",
28019
28271
  label: (hookType === ht ? "✓ " : " ") + t(HOOK_LABELS[ht]),
@@ -28154,6 +28406,7 @@ function RequestRow({
28154
28406
  show(n2 > 0 ? t("Added :count OWASP security tests", { count: n2 }) : t("No security tests generated for this request"), n2 > 0);
28155
28407
  } },
28156
28408
  ...cloudEnabled() ? [{ type: "item", label: t("Push as monitor to cloud"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: pushMonitor }] : [],
28409
+ { type: "item", label: t("Share request (public link)"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}), onClick: openShare },
28157
28410
  { type: "item", label: disabled ? t("Enable") : t("Disable"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(BanIcon, {}), onClick: onToggleDisabled },
28158
28411
  { type: "separator" },
28159
28412
  { type: "header", label: t("Hook type") },
@@ -28161,7 +28414,32 @@ function RequestRow({
28161
28414
  { type: "separator" },
28162
28415
  { type: "item", label: t("Delete"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TrashIcon, {}), danger: true, onClick: onDelete }
28163
28416
  ] }) }),
28164
- toast && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed bottom-4 right-4 z-[100] w-96", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Toast, { toast }) })
28417
+ toast && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed bottom-4 right-4 z-[100] w-96", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Toast, { toast }) }),
28418
+ showCloudRequired && /* @__PURE__ */ jsxRuntimeExports.jsx(
28419
+ CloudRequiredModal,
28420
+ {
28421
+ action: t("share this request"),
28422
+ onConnect: () => {
28423
+ setShowCloudRequired(false);
28424
+ openWorkspaceSettings("cloud");
28425
+ },
28426
+ onClose: () => setShowCloudRequired(false)
28427
+ }
28428
+ ),
28429
+ showShare && /* @__PURE__ */ jsxRuntimeExports.jsx(
28430
+ ShareModal,
28431
+ {
28432
+ title: useStore$1.getState().collections[collectionId]?.data.requests[reqId]?.name ?? "",
28433
+ type: "request",
28434
+ sourceId: reqId,
28435
+ onCreate: () => {
28436
+ const r = useStore$1.getState().collections[collectionId]?.data.requests[reqId];
28437
+ if (!r) throw new Error(t("Request not found"));
28438
+ return shareRequestToCloud(r);
28439
+ },
28440
+ onClose: () => setShowShare(false)
28441
+ }
28442
+ )
28165
28443
  ]
28166
28444
  }
28167
28445
  ),
@@ -28822,6 +29100,16 @@ function CollectionNode({
28822
29100
  const [expandCtrl, setExpandCtrl] = reactExports.useState({ value: true, seq: 0 });
28823
29101
  const [dropOver, setDropOver] = reactExports.useState(false);
28824
29102
  const dragCtx = reactExports.useContext(DragCtx);
29103
+ const openWorkspaceSettings = useStore$1((s) => s.openWorkspaceSettings);
29104
+ const [showCloudRequired, setShowCloudRequired] = reactExports.useState(false);
29105
+ const [showShare, setShowShare] = reactExports.useState(false);
29106
+ function openShare() {
29107
+ if (!cloudEnabled()) {
29108
+ setShowCloudRequired(true);
29109
+ return;
29110
+ }
29111
+ setShowShare(true);
29112
+ }
28825
29113
  function expandAll() {
28826
29114
  setExpandCtrl((c) => ({ value: true, seq: c.seq + 1, ids: null }));
28827
29115
  }
@@ -28903,6 +29191,7 @@ function CollectionNode({
28903
29191
  { type: "item", label: t("Settings"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(GearIcon, {}), onClick: () => setShowSettings(true) },
28904
29192
  { type: "item", label: t("Sync schemas"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: () => setShowSchemaSync(true) },
28905
29193
  ...cloudEnabled() ? [{ type: "item", label: t("Push contract to cloud"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: () => setShowPushContract(true) }] : [],
29194
+ { type: "item", label: t("Share collection (public link)"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}), onClick: openShare },
28906
29195
  { type: "item", label: col.disabled ? t("Enable") : t("Disable"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(BanIcon, {}), onClick: onToggleCollectionDisabled },
28907
29196
  { type: "separator" },
28908
29197
  { type: "item", label: t("Delete collection"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TrashIcon, {}), danger: true, onClick: onDeleteCollection }
@@ -28947,7 +29236,28 @@ function CollectionNode({
28947
29236
  }
28948
29237
  ),
28949
29238
  showSettings && /* @__PURE__ */ jsxRuntimeExports.jsx(CollectionSettingsModal, { collection: col, onClose: () => setShowSettings(false) }),
28950
- showSchemaSync && /* @__PURE__ */ jsxRuntimeExports.jsx(SchemaSyncModal, { collectionId: col.id, scope: { type: "collection" }, onClose: () => setShowSchemaSync(false) })
29239
+ showSchemaSync && /* @__PURE__ */ jsxRuntimeExports.jsx(SchemaSyncModal, { collectionId: col.id, scope: { type: "collection" }, onClose: () => setShowSchemaSync(false) }),
29240
+ showCloudRequired && /* @__PURE__ */ jsxRuntimeExports.jsx(
29241
+ CloudRequiredModal,
29242
+ {
29243
+ action: t("share this collection"),
29244
+ onConnect: () => {
29245
+ setShowCloudRequired(false);
29246
+ openWorkspaceSettings("cloud");
29247
+ },
29248
+ onClose: () => setShowCloudRequired(false)
29249
+ }
29250
+ ),
29251
+ showShare && /* @__PURE__ */ jsxRuntimeExports.jsx(
29252
+ ShareModal,
29253
+ {
29254
+ title: col.name,
29255
+ type: "collection",
29256
+ sourceId: col.id,
29257
+ onCreate: () => shareCollectionToCloud(col),
29258
+ onClose: () => setShowShare(false)
29259
+ }
29260
+ )
28951
29261
  ] });
28952
29262
  }
28953
29263
  function FolderRow({
@@ -28991,6 +29301,16 @@ function FolderRow({
28991
29301
  }));
28992
29302
  const [dropInside, setDropInside] = reactExports.useState(false);
28993
29303
  const dragCtx = reactExports.useContext(DragCtx);
29304
+ const openWorkspaceSettings = useStore$1((s) => s.openWorkspaceSettings);
29305
+ const [showCloudRequired, setShowCloudRequired] = reactExports.useState(false);
29306
+ const [showShare, setShowShare] = reactExports.useState(false);
29307
+ function openShare() {
29308
+ if (!cloudEnabled()) {
29309
+ setShowCloudRequired(true);
29310
+ return;
29311
+ }
29312
+ setShowShare(true);
29313
+ }
28994
29314
  const tags = folder2.tags ?? [];
28995
29315
  const indent = indentPx(depth);
28996
29316
  const hasInheritedConfig = folder2.auth && folder2.auth.type !== "none" || folder2.headers && folder2.headers.length > 0;
@@ -29104,6 +29424,7 @@ function FolderRow({
29104
29424
  { type: "item", label: t("Settings"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(GearIcon, {}), onClick: () => setShowSettings(true) },
29105
29425
  { type: "item", label: t("Sync schemas"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: () => setShowSchemaSync(true) },
29106
29426
  ...cloudEnabled() ? [{ type: "item", label: t("Push contract to cloud"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: () => setShowPushContract(true) }] : [],
29427
+ { type: "item", label: t("Share folder (public link)"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}), onClick: openShare },
29107
29428
  { type: "item", label: folder2.disabled ? t("Enable") : t("Disable"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(BanIcon, {}), onClick: onToggleDisabled },
29108
29429
  { type: "separator" },
29109
29430
  { type: "header", label: t("Run folder as hook") },
@@ -29138,6 +29459,27 @@ function FolderRow({
29138
29459
  scope: { type: "folder", folderId: folder2.id },
29139
29460
  onClose: () => setShowSchemaSync(false)
29140
29461
  }
29462
+ ),
29463
+ showCloudRequired && /* @__PURE__ */ jsxRuntimeExports.jsx(
29464
+ CloudRequiredModal,
29465
+ {
29466
+ action: t("share this folder"),
29467
+ onConnect: () => {
29468
+ setShowCloudRequired(false);
29469
+ openWorkspaceSettings("cloud");
29470
+ },
29471
+ onClose: () => setShowCloudRequired(false)
29472
+ }
29473
+ ),
29474
+ showShare && folderCollection && /* @__PURE__ */ jsxRuntimeExports.jsx(
29475
+ ShareModal,
29476
+ {
29477
+ title: folder2.name,
29478
+ type: "folder",
29479
+ sourceId: folder2.id,
29480
+ onCreate: () => shareFolderToCloud(folderCollection, folder2),
29481
+ onClose: () => setShowShare(false)
29482
+ }
29141
29483
  )
29142
29484
  ] });
29143
29485
  }
@@ -29591,8 +29933,8 @@ function jsonWithComments() {
29591
29933
  function xmlWithComments() {
29592
29934
  return [xml(), commentKeymap];
29593
29935
  }
29594
- const GraphQLEditor = reactExports.lazy(() => __vitePreload(() => import("./GraphQLEditor-CJm8glW5.js"), true ? __vite__mapDeps([0,1,2]) : void 0, import.meta.url).then((m) => ({ default: m.GraphQLEditor })));
29595
- const SoapEditor = reactExports.lazy(() => __vitePreload(() => import("./SoapEditor-C2eMs7-Z.js"), true ? __vite__mapDeps([3,1,2]) : void 0, import.meta.url).then((m) => ({ default: m.SoapEditor })));
29936
+ const GraphQLEditor = reactExports.lazy(() => __vitePreload(() => import("./GraphQLEditor-RRDuQogA.js"), true ? __vite__mapDeps([0,1,2]) : void 0, import.meta.url).then((m) => ({ default: m.GraphQLEditor })));
29937
+ const SoapEditor = reactExports.lazy(() => __vitePreload(() => import("./SoapEditor-BncNQmjP.js"), true ? __vite__mapDeps([3,1,2]) : void 0, import.meta.url).then((m) => ({ default: m.SoapEditor })));
29596
29938
  function BodyTab({ request: request2, onChange }) {
29597
29939
  const t = useT();
29598
29940
  const editorFallback = /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 text-xs text-surface-500", children: t("Loading editor…") });
@@ -29690,7 +30032,7 @@ function BodyTab({ request: request2, onChange }) {
29690
30032
  mode === "soap" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: editorFallback, children: /* @__PURE__ */ jsxRuntimeExports.jsx(SoapEditor, { request: request2, onChange }) }) })
29691
30033
  ] });
29692
30034
  }
29693
- const { electron: electron$D } = window;
30035
+ const { electron: electron$E } = window;
29694
30036
  function AuthTab({ request: request2, onChange }) {
29695
30037
  const auth = request2.auth;
29696
30038
  const [secretValue, setSecretValue] = reactExports.useState("");
@@ -29700,7 +30042,7 @@ function AuthTab({ request: request2, onChange }) {
29700
30042
  }
29701
30043
  async function saveSecret(ref) {
29702
30044
  if (!secretValue || !ref) return;
29703
- await electron$D.setSecret(ref, secretValue);
30045
+ await electron$E.setSecret(ref, secretValue);
29704
30046
  setSaved(true);
29705
30047
  setSecretValue("");
29706
30048
  setTimeout(() => setSaved(false), 2e3);
@@ -30060,7 +30402,7 @@ function validateBodyAgainstSchema(schema, body) {
30060
30402
  return { status: "error", message: e instanceof Error ? e.message : String(e) };
30061
30403
  }
30062
30404
  }
30063
- const { electron: electron$C } = window;
30405
+ const { electron: electron$D } = window;
30064
30406
  function SchemaTab({ request: request2, onChange }) {
30065
30407
  const t = useT();
30066
30408
  const activeTab = useStore$1((s) => s.tabs.find((tab) => tab.id === s.activeTabId));
@@ -30084,7 +30426,7 @@ function SchemaTab({ request: request2, onChange }) {
30084
30426
  if (!lastResponse?.body) return;
30085
30427
  setInferring(true);
30086
30428
  try {
30087
- const schema = await electron$C.inferContractSchema(lastResponse.body);
30429
+ const schema = await electron$D.inferContractSchema(lastResponse.body);
30088
30430
  if (schema) setSchema(schema);
30089
30431
  } finally {
30090
30432
  setInferring(false);
@@ -39514,7 +39856,7 @@ function DocsEditor({ value: value2, onChange, placeholder, onGenerate, aiAvaila
39514
39856
  }
39515
39857
  const AI_OPENAI_TOKEN_REF = "ai:openai:token";
39516
39858
  const DEFAULT_AI_MODEL = "gpt-4o-mini";
39517
- const { electron: electron$B } = window;
39859
+ const { electron: electron$C } = window;
39518
39860
  const CFG_KEY = STORAGE_KEYS.ai;
39519
39861
  function getAiConfig() {
39520
39862
  try {
@@ -39536,7 +39878,7 @@ function invalidateAiAvailable() {
39536
39878
  async function checkAvailable() {
39537
39879
  if (availableCache != null) return availableCache;
39538
39880
  try {
39539
- availableCache = (await electron$B.hasSecret(AI_OPENAI_TOKEN_REF)).has;
39881
+ availableCache = (await electron$C.hasSecret(AI_OPENAI_TOKEN_REF)).has;
39540
39882
  } catch {
39541
39883
  availableCache = false;
39542
39884
  }
@@ -39556,7 +39898,7 @@ function useAiAvailable() {
39556
39898
  return available;
39557
39899
  }
39558
39900
  async function generateDocs(input) {
39559
- return electron$B.generateAiDocs({ ...input, model: getAiConfig().model });
39901
+ return electron$C.generateAiDocs({ ...input, model: getAiConfig().model });
39560
39902
  }
39561
39903
  function kvLines(label, rows) {
39562
39904
  const on = (rows ?? []).filter((r) => r.enabled !== false && r.key);
@@ -40084,7 +40426,7 @@ function RealtimeTestSection({
40084
40426
  ] })
40085
40427
  ] });
40086
40428
  }
40087
- const { electron: electron$A } = window;
40429
+ const { electron: electron$B } = window;
40088
40430
  function formatTime$4(ts) {
40089
40431
  const d = new Date(ts);
40090
40432
  const hh = String(d.getHours()).padStart(2, "0");
@@ -40114,7 +40456,7 @@ function WebSocketPanel({ request: request2, onChange }) {
40114
40456
  const url = interp(request2.url).trim();
40115
40457
  const headers = {};
40116
40458
  for (const h of request2.headers) if (h.enabled && h.key) headers[interp(h.key)] = interp(h.value);
40117
- return electron$A.wsTest({
40459
+ return electron$B.wsTest({
40118
40460
  url,
40119
40461
  headers,
40120
40462
  sendMessage: cfg.sendMessage ? interp(cfg.sendMessage) : void 0,
@@ -40124,14 +40466,14 @@ function WebSocketPanel({ request: request2, onChange }) {
40124
40466
  });
40125
40467
  });
40126
40468
  reactExports.useEffect(() => {
40127
- electron$A.onWsMessage(({ requestId, message }) => {
40469
+ electron$B.onWsMessage(({ requestId, message }) => {
40128
40470
  addWsMessage(requestId, message);
40129
40471
  });
40130
- electron$A.onWsStatus(({ requestId, status, error: error2 }) => {
40472
+ electron$B.onWsStatus(({ requestId, status, error: error2 }) => {
40131
40473
  setWsStatus(requestId, status, error2);
40132
40474
  });
40133
40475
  return () => {
40134
- electron$A.offWsEvents();
40476
+ electron$B.offWsEvents();
40135
40477
  };
40136
40478
  }, [addWsMessage, setWsStatus]);
40137
40479
  reactExports.useEffect(() => {
@@ -40144,19 +40486,19 @@ function WebSocketPanel({ request: request2, onChange }) {
40144
40486
  if (h.enabled && h.key) headers[h.key] = h.value;
40145
40487
  }
40146
40488
  try {
40147
- await electron$A.wsConnect(request2.id, request2.url, headers);
40489
+ await electron$B.wsConnect(request2.id, request2.url, headers);
40148
40490
  } catch (err) {
40149
40491
  setWsStatus(request2.id, "error", err instanceof Error ? err.message : String(err));
40150
40492
  }
40151
40493
  }
40152
40494
  async function disconnect() {
40153
- await electron$A.wsDisconnect(request2.id);
40495
+ await electron$B.wsDisconnect(request2.id);
40154
40496
  }
40155
40497
  async function sendMessage() {
40156
40498
  const text = sendText.trim();
40157
40499
  if (!text || !isConnected) return;
40158
40500
  try {
40159
- await electron$A.wsSend(request2.id, text);
40501
+ await electron$B.wsSend(request2.id, text);
40160
40502
  const msg = {
40161
40503
  id: crypto.randomUUID(),
40162
40504
  direction: "sent",
@@ -40308,7 +40650,7 @@ function WebSocketPanel({ request: request2, onChange }) {
40308
40650
  ] })
40309
40651
  ] });
40310
40652
  }
40311
- const { electron: electron$z } = window;
40653
+ const { electron: electron$A } = window;
40312
40654
  function formatTime$3(ts) {
40313
40655
  const d = new Date(ts);
40314
40656
  const p = (n2, w = 2) => String(n2).padStart(w, "0");
@@ -40342,7 +40684,7 @@ function SocketIoPanel({ request: request2, onChange }) {
40342
40684
  const url = interp(request2.url).trim();
40343
40685
  const headers = {};
40344
40686
  for (const h of request2.headers) if (h.enabled && h.key) headers[interp(h.key)] = interp(h.value);
40345
- return electron$z.socketIoTest({
40687
+ return electron$A.socketIoTest({
40346
40688
  url,
40347
40689
  namespace: namespace2,
40348
40690
  auth: auth ? interp(auth) : void 0,
@@ -40361,19 +40703,19 @@ function SocketIoPanel({ request: request2, onChange }) {
40361
40703
  if (h.enabled && h.key) headers[h.key] = h.value;
40362
40704
  }
40363
40705
  try {
40364
- await electron$z.socketIoConnect(request2.id, { url: request2.url, namespace: namespace2, auth, headers });
40706
+ await electron$A.socketIoConnect(request2.id, { url: request2.url, namespace: namespace2, auth, headers });
40365
40707
  } catch (err) {
40366
40708
  setStatus(request2.id, "error", err instanceof Error ? err.message : String(err));
40367
40709
  }
40368
40710
  }
40369
40711
  async function disconnect() {
40370
- await electron$z.socketIoDisconnect(request2.id);
40712
+ await electron$A.socketIoDisconnect(request2.id);
40371
40713
  }
40372
40714
  async function emit() {
40373
40715
  const event = eventName2.trim();
40374
40716
  if (!event || !isConnected) return;
40375
40717
  try {
40376
- await electron$z.socketIoEmit(request2.id, { event, args: argsText, withAck });
40718
+ await electron$A.socketIoEmit(request2.id, { event, args: argsText, withAck });
40377
40719
  const msg = {
40378
40720
  id: crypto.randomUUID(),
40379
40721
  direction: "sent",
@@ -40587,7 +40929,7 @@ function SocketIoPanel({ request: request2, onChange }) {
40587
40929
  ] })
40588
40930
  ] });
40589
40931
  }
40590
- const { electron: electron$y } = window;
40932
+ const { electron: electron$z } = window;
40591
40933
  function formatTime$2(ts) {
40592
40934
  const d = new Date(ts);
40593
40935
  const p = (n2, w = 2) => String(n2).padStart(w, "0");
@@ -40627,7 +40969,7 @@ function MqttPanel({ request: request2, onChange }) {
40627
40969
  return;
40628
40970
  }
40629
40971
  try {
40630
- await electron$y.mqttConnect(request2.id, {
40972
+ await electron$z.mqttConnect(request2.id, {
40631
40973
  url,
40632
40974
  clientId: cfg.clientId ? interp(cfg.clientId) : void 0,
40633
40975
  username: cfg.username ? interp(cfg.username) : void 0,
@@ -40641,13 +40983,13 @@ function MqttPanel({ request: request2, onChange }) {
40641
40983
  }
40642
40984
  }
40643
40985
  async function disconnect() {
40644
- await electron$y.mqttDisconnect(request2.id);
40986
+ await electron$z.mqttDisconnect(request2.id);
40645
40987
  }
40646
40988
  async function subscribe() {
40647
40989
  const topic = interp(cfg.subscribeTopic ?? "").trim();
40648
40990
  if (!topic || !isConnected) return;
40649
40991
  try {
40650
- await electron$y.mqttSubscribe(request2.id, { topic, qos });
40992
+ await electron$z.mqttSubscribe(request2.id, { topic, qos });
40651
40993
  } catch (err) {
40652
40994
  setStatus(request2.id, "error", err instanceof Error ? err.message : String(err));
40653
40995
  }
@@ -40655,7 +40997,7 @@ function MqttPanel({ request: request2, onChange }) {
40655
40997
  async function unsubscribe() {
40656
40998
  const topic = interp(cfg.subscribeTopic ?? "").trim();
40657
40999
  if (!topic || !isConnected) return;
40658
- await electron$y.mqttUnsubscribe(request2.id, { topic });
41000
+ await electron$z.mqttUnsubscribe(request2.id, { topic });
40659
41001
  }
40660
41002
  async function publish() {
40661
41003
  const topic = interp(cfg.publishTopic ?? "").trim();
@@ -40667,7 +41009,7 @@ function MqttPanel({ request: request2, onChange }) {
40667
41009
  setPublishError("");
40668
41010
  const body = interp(payload);
40669
41011
  try {
40670
- await electron$y.mqttPublish(request2.id, { topic, payload: body, qos, retain: Boolean(cfg.retain) });
41012
+ await electron$z.mqttPublish(request2.id, { topic, payload: body, qos, retain: Boolean(cfg.retain) });
40671
41013
  addMessage(request2.id, {
40672
41014
  id: crypto.randomUUID(),
40673
41015
  direction: "sent",
@@ -40691,7 +41033,7 @@ function MqttPanel({ request: request2, onChange }) {
40691
41033
  setTestRemaining(Math.ceil(collectMs / 1e3));
40692
41034
  const countdown = setInterval(() => setTestRemaining((r) => r && r > 1 ? r - 1 : 0), 1e3);
40693
41035
  try {
40694
- const res = await electron$y.mqttTest({
41036
+ const res = await electron$z.mqttTest({
40695
41037
  url,
40696
41038
  // The test opens its own connection; never reuse the panel's Client ID,
40697
41039
  // or the broker would kick whichever connected first (a clientId clash).
@@ -41040,7 +41382,7 @@ function MqttPanel({ request: request2, onChange }) {
41040
41382
  ] })
41041
41383
  ] });
41042
41384
  }
41043
- const { electron: electron$x } = window;
41385
+ const { electron: electron$y } = window;
41044
41386
  function formatTime$1(ts) {
41045
41387
  const d = new Date(ts);
41046
41388
  const p = (n2, w = 2) => String(n2).padStart(w, "0");
@@ -41076,13 +41418,13 @@ function GrpcPanel({ request: request2, onChange }) {
41076
41418
  onChange({ body: { ...request2.body, mode: "grpc", grpc: { ...grpc, ...changes } } });
41077
41419
  }
41078
41420
  reactExports.useEffect(() => {
41079
- electron$x.onGrpcMessage(({ requestId, message }) => {
41421
+ electron$y.onGrpcMessage(({ requestId, message }) => {
41080
41422
  addGrpcMessage(requestId, message);
41081
41423
  });
41082
- electron$x.onGrpcStatus(({ requestId, status, code, codeName, error: error2 }) => {
41424
+ electron$y.onGrpcStatus(({ requestId, status, code, codeName, error: error2 }) => {
41083
41425
  setGrpcStatus(requestId, status, { code, codeName, error: error2 });
41084
41426
  });
41085
- return () => electron$x.offGrpcEvents();
41427
+ return () => electron$y.offGrpcEvents();
41086
41428
  }, [addGrpcMessage, setGrpcStatus]);
41087
41429
  reactExports.useEffect(() => {
41088
41430
  logEndRef.current?.scrollIntoView({ behavior: "smooth" });
@@ -41095,7 +41437,7 @@ function GrpcPanel({ request: request2, onChange }) {
41095
41437
  if (!grpc.protoSource?.trim() && !grpc.protoPath?.trim()) return;
41096
41438
  setLoadingProto(true);
41097
41439
  try {
41098
- const result = await electron$x.grpcLoadProto({ protoSource: grpc.protoSource, protoPath: grpc.protoPath });
41440
+ const result = await electron$y.grpcLoadProto({ protoSource: grpc.protoSource, protoPath: grpc.protoPath });
41099
41441
  const services = result.services;
41100
41442
  setGrpcServices(request2.id, services);
41101
41443
  if (services.length && !services.some((s) => s.name === grpc.serviceName)) {
@@ -41114,7 +41456,7 @@ function GrpcPanel({ request: request2, onChange }) {
41114
41456
  const metadata = {};
41115
41457
  for (const m of grpc.metadata ?? []) if (m.enabled && m.key) metadata[m.key] = m.value;
41116
41458
  try {
41117
- await electron$x.grpcInvoke(request2.id, {
41459
+ await electron$y.grpcInvoke(request2.id, {
41118
41460
  target: request2.url,
41119
41461
  serviceName: grpc.serviceName,
41120
41462
  methodName: grpc.methodName,
@@ -41129,7 +41471,7 @@ function GrpcPanel({ request: request2, onChange }) {
41129
41471
  }
41130
41472
  }
41131
41473
  function cancel() {
41132
- electron$x.grpcCancel(request2.id);
41474
+ electron$y.grpcCancel(request2.id);
41133
41475
  }
41134
41476
  const statusColors = {
41135
41477
  running: "bg-amber-400",
@@ -41253,7 +41595,7 @@ function GrpcPanel({ request: request2, onChange }) {
41253
41595
  ] })
41254
41596
  ] });
41255
41597
  }
41256
- const { electron: electron$w } = window;
41598
+ const { electron: electron$x } = window;
41257
41599
  function isSubscriptionQuery(query) {
41258
41600
  return /(^|\n)\s*subscription\b/i.test(query ?? "");
41259
41601
  }
@@ -41302,7 +41644,7 @@ function GraphQlSubscription({ request: request2, gql, onPatch }) {
41302
41644
  const headers = {};
41303
41645
  for (const h of request2.headers) if (h.enabled && h.key) headers[interp(h.key)] = interp(h.value);
41304
41646
  try {
41305
- await electron$w.graphqlWsSubscribe(request2.id, {
41647
+ await electron$x.graphqlWsSubscribe(request2.id, {
41306
41648
  url,
41307
41649
  headers,
41308
41650
  query: gql.query,
@@ -41319,7 +41661,7 @@ function GraphQlSubscription({ request: request2, gql, onPatch }) {
41319
41661
  const url = deriveWsUrl(interp(rawUrl));
41320
41662
  const headers = {};
41321
41663
  for (const h of request2.headers) if (h.enabled && h.key) headers[interp(h.key)] = interp(h.value);
41322
- return electron$w.graphqlWsTest({
41664
+ return electron$x.graphqlWsTest({
41323
41665
  url,
41324
41666
  headers,
41325
41667
  query: gql.query,
@@ -41354,7 +41696,7 @@ function GraphQlSubscription({ request: request2, gql, onPatch }) {
41354
41696
  className: "flex-1 bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs font-mono focus:outline-none focus:border-blue-500 placeholder-surface-600 disabled:opacity-50"
41355
41697
  }
41356
41698
  ),
41357
- isActive ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => electron$w.graphqlWsStop(request2.id), className: "px-3 py-1 bg-red-700 hover:bg-red-600 rounded text-xs font-medium transition-colors whitespace-nowrap", children: conn.status === "connecting" ? t("Connecting…") : t("Stop") }) : /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: subscribe, className: "px-3 py-1 bg-fuchsia-700 hover:bg-fuchsia-600 rounded text-xs font-medium transition-colors whitespace-nowrap", children: t("Subscribe") })
41699
+ isActive ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => electron$x.graphqlWsStop(request2.id), className: "px-3 py-1 bg-red-700 hover:bg-red-600 rounded text-xs font-medium transition-colors whitespace-nowrap", children: conn.status === "connecting" ? t("Connecting…") : t("Stop") }) : /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: subscribe, className: "px-3 py-1 bg-fuchsia-700 hover:bg-fuchsia-600 rounded text-xs font-medium transition-colors whitespace-nowrap", children: t("Subscribe") })
41358
41700
  ] }),
41359
41701
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-shrink-0", children: [
41360
41702
  /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: () => setShowParams((v) => !v), className: "text-[10px] text-surface-600 hover:text-surface-300 transition-colors", children: [
@@ -41622,7 +41964,7 @@ function FuzzResultsPanel({ report, onClear }) {
41622
41964
  ] }) })
41623
41965
  ] });
41624
41966
  }
41625
- const { electron: electron$v } = window;
41967
+ const { electron: electron$w } = window;
41626
41968
  const WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
41627
41969
  function FuzzModal({ request: request2, onClose }) {
41628
41970
  const t = useT();
@@ -41650,7 +41992,7 @@ function FuzzModal({ request: request2, onClose }) {
41650
41992
  const env2 = resolveEnvironmentById(environments, activeEnvironmentId);
41651
41993
  const envVars = env2 ? Object.fromEntries(env2.variables.filter((v) => v.enabled).map((v) => [v.key, v.value])) : {};
41652
41994
  const collectionVars = activeCollectionId ? collections[activeCollectionId]?.data.collectionVariables ?? {} : {};
41653
- const result = await electron$v.fuzzContracts({
41995
+ const result = await electron$w.fuzzContracts({
41654
41996
  requests: [request2],
41655
41997
  envVars,
41656
41998
  collectionVars,
@@ -41867,7 +42209,7 @@ function resolveAskValues(asks, answers) {
41867
42209
  for (const a of asks) out[a.key] = answers[a.message] ?? "";
41868
42210
  return out;
41869
42211
  }
41870
- const { electron: electron$u } = window;
42212
+ const { electron: electron$v } = window;
41871
42213
  const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "QUERY"];
41872
42214
  const METHOD_COLORS = {
41873
42215
  GET: "text-emerald-400",
@@ -42013,7 +42355,7 @@ function RequestBuilder({ request: request2 }) {
42013
42355
  const headers = {};
42014
42356
  for (const h of request2.headers) if (h.enabled && h.key) headers[interp(h.key)] = interp(h.value);
42015
42357
  try {
42016
- await electron$u.graphqlWsSubscribe(request2.id, {
42358
+ await electron$v.graphqlWsSubscribe(request2.id, {
42017
42359
  url,
42018
42360
  headers,
42019
42361
  query: gql.query,
@@ -42074,7 +42416,7 @@ function RequestBuilder({ request: request2 }) {
42074
42416
  const start2 = Date.now();
42075
42417
  try {
42076
42418
  const hookEnv = withEnvOverrides(activeEnv, liveEnvOverrides);
42077
- const r = await electron$u.sendRequest({
42419
+ const r = await electron$v.sendRequest({
42078
42420
  ...basePayload,
42079
42421
  environment: hookEnv,
42080
42422
  request: hook,
@@ -42126,7 +42468,7 @@ function RequestBuilder({ request: request2 }) {
42126
42468
  startLiveStream(activeTabId, streamId);
42127
42469
  let result;
42128
42470
  try {
42129
- result = await electron$u.sendRequest({
42471
+ result = await electron$v.sendRequest({
42130
42472
  ...basePayload,
42131
42473
  environment: freshEnv,
42132
42474
  request: mergedRequest,
@@ -42163,7 +42505,7 @@ function RequestBuilder({ request: request2 }) {
42163
42505
  const start2 = Date.now();
42164
42506
  try {
42165
42507
  const hookEnv = withEnvOverrides(activeEnv, liveEnvOverrides);
42166
- const r = await electron$u.sendRequest({
42508
+ const r = await electron$v.sendRequest({
42167
42509
  ...basePayload,
42168
42510
  environment: hookEnv,
42169
42511
  request: hook,
@@ -43346,7 +43688,7 @@ function Cell({ value: value2, onDrill }) {
43346
43688
  const text = String(value2);
43347
43689
  return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-block max-w-[26rem] truncate align-bottom text-surface-300", title: text.length > 40 ? text : void 0, children: text });
43348
43690
  }
43349
- const { electron: electron$t } = window;
43691
+ const { electron: electron$u } = window;
43350
43692
  const RENDER_TAIL = 500;
43351
43693
  const CLOSE_LABEL = {
43352
43694
  complete: "closed",
@@ -43388,7 +43730,7 @@ function StreamView({ events, streaming: streaming2, streamId, streamClose, firs
43388
43730
  if (!streamId) return;
43389
43731
  setStopping(true);
43390
43732
  try {
43391
- await electron$t.stopStream(streamId);
43733
+ await electron$u.stopStream(streamId);
43392
43734
  } catch {
43393
43735
  }
43394
43736
  }
@@ -43531,7 +43873,7 @@ function HookResultsPanel({ results }) {
43531
43873
  }) })
43532
43874
  ] });
43533
43875
  }
43534
- const { electron: electron$s } = window;
43876
+ const { electron: electron$t } = window;
43535
43877
  function extractPath(url) {
43536
43878
  try {
43537
43879
  return new URL(url).pathname || "/";
@@ -43586,14 +43928,14 @@ function SaveAsMockModal({ onClose }) {
43586
43928
  const entry = state.mocks[serverId];
43587
43929
  const updated = { ...entry.data, name: newServerName, port: Number(newServerPort), routes: [route] };
43588
43930
  updateMock(serverId, updated);
43589
- await electron$s.saveMock(entry.relPath, updated);
43931
+ await electron$t.saveMock(entry.relPath, updated);
43590
43932
  const ws = useStore$1.getState().workspace;
43591
- if (ws) await electron$s.saveWorkspace(ws);
43933
+ if (ws) await electron$t.saveWorkspace(ws);
43592
43934
  } else {
43593
43935
  const entry = useStore$1.getState().mocks[serverId];
43594
43936
  const updated = { ...entry.data, routes: [...entry.data.routes, route] };
43595
43937
  updateMock(serverId, updated);
43596
- await electron$s.saveMock(entry.relPath, updated);
43938
+ await electron$t.saveMock(entry.relPath, updated);
43597
43939
  }
43598
43940
  onClose();
43599
43941
  } finally {
@@ -44111,7 +44453,7 @@ function validateHttpSemantics(res, opts = {}) {
44111
44453
  const order = { error: 0, warning: 1, hint: 2 };
44112
44454
  return f.sort((a, b) => order[a.severity] - order[b.severity]);
44113
44455
  }
44114
- const { electron: electron$r } = window;
44456
+ const { electron: electron$s } = window;
44115
44457
  function xmlWellFormed(body) {
44116
44458
  try {
44117
44459
  const doc = new DOMParser().parseFromString(body, "application/xml");
@@ -44341,7 +44683,7 @@ function ResponseViewer() {
44341
44683
  const schemaCheck = currentRequest?.schema?.trim() && response && !response.error ? validateBodyAgainstSchema(currentRequest.schema, response.body) : null;
44342
44684
  async function saveAsSchema() {
44343
44685
  if (!response || !requestId || !activeTabId) return;
44344
- const schema = response.body ? await electron$r.inferContractSchema(response.body) : null;
44686
+ const schema = response.body ? await electron$s.inferContractSchema(response.body) : null;
44345
44687
  if (!schema) return;
44346
44688
  updateRequest(requestId, { schema });
44347
44689
  setTabRequestTab(activeTabId, "schema");
@@ -44929,7 +45271,7 @@ function listFolders(root2, depth = 0, out = []) {
44929
45271
  }
44930
45272
  return out;
44931
45273
  }
44932
- const { electron: electron$q } = window;
45274
+ const { electron: electron$r } = window;
44933
45275
  function fmtMs(ms) {
44934
45276
  if (ms < 1) return "<1 ms";
44935
45277
  if (ms < 1e3) return `${Math.round(ms)} ms`;
@@ -45120,11 +45462,11 @@ function PerformancePanel() {
45120
45462
  setError("");
45121
45463
  }, [activeId]);
45122
45464
  reactExports.useEffect(() => {
45123
- electron$q.onPerfProgress((p) => {
45465
+ electron$r.onPerfProgress((p) => {
45124
45466
  setProgress(p);
45125
45467
  setSamples((s) => [...s, p].slice(-240));
45126
45468
  });
45127
- return () => electron$q.offPerfEvents();
45469
+ return () => electron$r.offPerfEvents();
45128
45470
  }, []);
45129
45471
  const requestOptions = reactExports.useMemo(() => {
45130
45472
  const out = [];
@@ -45171,7 +45513,7 @@ function PerformancePanel() {
45171
45513
  if (saveTimer2.current) clearTimeout(saveTimer2.current);
45172
45514
  saveTimer2.current = setTimeout(() => {
45173
45515
  const ws = useStore$1.getState().workspace;
45174
- if (ws) void electron$q.saveWorkspace(ws);
45516
+ if (ws) void electron$r.saveWorkspace(ws);
45175
45517
  }, 500);
45176
45518
  }
45177
45519
  async function run() {
@@ -45196,7 +45538,7 @@ function PerformancePanel() {
45196
45538
  collections: Object.values(collections).map((c) => c.data),
45197
45539
  ...common
45198
45540
  };
45199
- setResult(await electron$q.perfRun(payload));
45541
+ setResult(await electron$r.perfRun(payload));
45200
45542
  } catch (err) {
45201
45543
  setError(err instanceof Error ? err.message : String(err));
45202
45544
  } finally {
@@ -45205,14 +45547,14 @@ function PerformancePanel() {
45205
45547
  }
45206
45548
  }
45207
45549
  async function stop() {
45208
- await electron$q.perfStop();
45550
+ await electron$r.perfStop();
45209
45551
  }
45210
45552
  async function exportReport() {
45211
45553
  if (!result || !plan) return;
45212
45554
  setExporting(true);
45213
45555
  try {
45214
45556
  const target = plan.target.kind === "request" ? selectedRequest?.name ?? "request" : selectedFlow?.name ?? "flow";
45215
- await electron$q.perfExportHtml(result, { planName: plan.name, target });
45557
+ await electron$r.perfExportHtml(result, { planName: plan.name, target });
45216
45558
  } catch (err) {
45217
45559
  setError(err instanceof Error ? err.message : String(err));
45218
45560
  } finally {
@@ -45647,7 +45989,7 @@ function PerformancePanel() {
45647
45989
  !result && !running && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 flex items-center justify-center text-sm text-surface-500", children: t("Set the load and run this plan.") })
45648
45990
  ] });
45649
45991
  }
45650
- const { electron: electron$p } = window;
45992
+ const { electron: electron$q } = window;
45651
45993
  function makePlan(name) {
45652
45994
  return {
45653
45995
  id: crypto.randomUUID(),
@@ -45684,7 +46026,7 @@ function PerfPlansPanel() {
45684
46026
  const updated = { ...workspace, perfPlans: next };
45685
46027
  setWorkspace(updated, workspacePath ?? "");
45686
46028
  try {
45687
- await electron$p.saveWorkspace(updated);
46029
+ await electron$q.saveWorkspace(updated);
45688
46030
  } catch {
45689
46031
  }
45690
46032
  }
@@ -45828,7 +46170,7 @@ class ErrorBoundary extends React.Component {
45828
46170
  ] });
45829
46171
  }
45830
46172
  }
45831
- const { electron: electron$o } = window;
46173
+ const { electron: electron$p } = window;
45832
46174
  const TARGETS = [
45833
46175
  { id: "robot_framework", label: "Robot Framework", description: "Python RequestsLibrary keywords + test suite" },
45834
46176
  { id: "playwright_ts", label: "Playwright TS", description: "TypeScript page-object API classes + spec files" },
@@ -45870,7 +46212,7 @@ function GeneratorPanel() {
45870
46212
  try {
45871
46213
  const col = collections[selectedCollectionId]?.data;
45872
46214
  const env2 = resolveEnvironmentById(environments, activeEnvironmentId);
45873
- const generated = await electron$o.generateCode({ collection: col, environment: env2, target });
46215
+ const generated = await electron$p.generateCode({ collection: col, environment: env2, target });
45874
46216
  setFiles(generated);
45875
46217
  setSelectedFile(generated[0]?.path ?? null);
45876
46218
  } catch (e) {
@@ -45882,7 +46224,7 @@ function GeneratorPanel() {
45882
46224
  async function saveZip() {
45883
46225
  if (files.length === 0) return;
45884
46226
  const col = collections[selectedCollectionId]?.data;
45885
- await electron$o.saveGeneratedFilesAsZip(files, col?.name ?? "api-tests", target);
46227
+ await electron$p.saveGeneratedFilesAsZip(files, col?.name ?? "api-tests", target);
45886
46228
  }
45887
46229
  const selectedContent = files.find((f) => f.path === selectedFile)?.content ?? "";
45888
46230
  const activeTarget = TARGETS.find((tgt) => tgt.id === target);
@@ -46066,7 +46408,7 @@ function historyToHar(entries, creatorVersion = "1.0") {
46066
46408
  }
46067
46409
  }, null, 2);
46068
46410
  }
46069
- const { electron: electron$n } = window;
46411
+ const { electron: electron$o } = window;
46070
46412
  const STATUS_COLOR = {
46071
46413
  "2": "text-emerald-400",
46072
46414
  "3": "text-amber-400",
@@ -46120,7 +46462,7 @@ function HistoryPanel() {
46120
46462
  }, [history, search]);
46121
46463
  async function downloadHar() {
46122
46464
  const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replaceAll(/[:T]/g, "-");
46123
- await electron$n.saveResults(historyToHar(history), `api-spector-history-${stamp}.har`);
46465
+ await electron$o.saveResults(historyToHar(history), `api-spector-history-${stamp}.har`);
46124
46466
  }
46125
46467
  function open2(entry) {
46126
46468
  setSelected(entry);
@@ -46248,7 +46590,7 @@ function HistoryRow({
46248
46590
  }
46249
46591
  );
46250
46592
  }
46251
- const { electron: electron$m } = window;
46593
+ const { electron: electron$n } = window;
46252
46594
  function WelcomeScreen() {
46253
46595
  const t = useT();
46254
46596
  const { applyWorkspace } = useWorkspaceLoader();
@@ -46261,10 +46603,10 @@ function WelcomeScreen() {
46261
46603
  const busyRef = reactExports.useRef(false);
46262
46604
  const [busy, setBusy] = reactExports.useState(false);
46263
46605
  reactExports.useEffect(() => {
46264
- electron$m.getRecentWorkspaces().then(setRecents).catch(() => setRecents([]));
46265
- electron$m.getUpdateMode().then((mode) => {
46606
+ electron$n.getRecentWorkspaces().then(setRecents).catch(() => setRecents([]));
46607
+ electron$n.getUpdateMode().then((mode) => {
46266
46608
  if (mode !== "npm") return;
46267
- return electron$m.checkForUpdate().then((info) => {
46609
+ return electron$n.checkForUpdate().then((info) => {
46268
46610
  if (info?.updateAvailable) setUpdate(info);
46269
46611
  });
46270
46612
  }).catch(() => {
@@ -46275,7 +46617,7 @@ function WelcomeScreen() {
46275
46617
  busyRef.current = true;
46276
46618
  setBusy(true);
46277
46619
  try {
46278
- const result = await electron$m.openWorkspace();
46620
+ const result = await electron$n.openWorkspace();
46279
46621
  if (result) await applyWorkspace(result.workspace, result.workspacePath);
46280
46622
  } finally {
46281
46623
  busyRef.current = false;
@@ -46287,7 +46629,7 @@ function WelcomeScreen() {
46287
46629
  busyRef.current = true;
46288
46630
  setBusy(true);
46289
46631
  try {
46290
- const result = await electron$m.newWorkspace();
46632
+ const result = await electron$n.newWorkspace();
46291
46633
  if (result) await applyWorkspace(result.workspace, result.workspacePath);
46292
46634
  } finally {
46293
46635
  busyRef.current = false;
@@ -46301,7 +46643,7 @@ function WelcomeScreen() {
46301
46643
  setGitBusy(true);
46302
46644
  setGitError(null);
46303
46645
  try {
46304
- const result = await electron$m.openFromGit(url);
46646
+ const result = await electron$n.openFromGit(url);
46305
46647
  if (!result) return;
46306
46648
  await applyWorkspace(result.workspace, result.workspacePath);
46307
46649
  } catch (err) {
@@ -46316,7 +46658,7 @@ function WelcomeScreen() {
46316
46658
  busyRef.current = true;
46317
46659
  setBusy(true);
46318
46660
  try {
46319
- const result = await electron$m.openWorkspacePath(path);
46661
+ const result = await electron$n.openWorkspacePath(path);
46320
46662
  if (!result) {
46321
46663
  setRecents((prev) => prev.filter((r) => r.path !== path));
46322
46664
  return;
@@ -46352,7 +46694,7 @@ function WelcomeScreen() {
46352
46694
  )
46353
46695
  ] }),
46354
46696
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-400 text-sm max-w-sm", children: t("Local-first API testing with Robot Framework & Playwright code generation. Secrets stay on your machine.") }),
46355
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] mt-3", style: { color: "var(--text-muted)" }, children: t("version :version", { version: "0.9.1" }) })
46697
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] mt-3", style: { color: "var(--text-muted)" }, children: t("version :version", { version: "0.9.2" }) })
46356
46698
  ] }),
46357
46699
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 w-64", children: [
46358
46700
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -46434,7 +46776,7 @@ function WelcomeScreen() {
46434
46776
  ] })
46435
46777
  ] });
46436
46778
  }
46437
- const { electron: electron$l } = window;
46779
+ const { electron: electron$m } = window;
46438
46780
  const EXAMPLES = [
46439
46781
  {
46440
46782
  label: "macOS / Linux (~/.zshrc or ~/.bashrc)",
@@ -46459,7 +46801,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
46459
46801
  setError(t("Password cannot be empty."));
46460
46802
  return;
46461
46803
  }
46462
- await electron$l.setMasterKey(password);
46804
+ await electron$m.setMasterKey(password);
46463
46805
  onSuccess(password);
46464
46806
  }
46465
46807
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
@@ -46538,7 +46880,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
46538
46880
  }
46539
46881
  );
46540
46882
  }
46541
- const { electron: electron$k } = window;
46883
+ const { electron: electron$l } = window;
46542
46884
  async function shortHash(value2) {
46543
46885
  const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value2));
46544
46886
  return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 8);
@@ -46653,12 +46995,12 @@ function EnvironmentEditor({ onClose }) {
46653
46995
  if (next) setSelectedId(next);
46654
46996
  }
46655
46997
  async function importFromPostman() {
46656
- const imported = await electron$k.importPostmanEnvironment();
46998
+ const imported = await electron$l.importPostmanEnvironment();
46657
46999
  if (!imported) return;
46658
47000
  const existingNames = Object.values(useStore$1.getState().environments).map((e) => e.data.name);
46659
47001
  const finalEnv = { ...imported, name: uniqueName$1(imported.name || t("Imported Environment"), existingNames) };
46660
47002
  const relPath = envRelPath(finalEnv.name, finalEnv.id);
46661
- await electron$k.saveEnvironment(relPath, finalEnv);
47003
+ await electron$l.saveEnvironment(relPath, finalEnv);
46662
47004
  useStore$1.setState((s) => {
46663
47005
  s.environments[finalEnv.id] = { relPath, data: finalEnv };
46664
47006
  if (!s.activeEnvironmentId) s.activeEnvironmentId = finalEnv.id;
@@ -46668,7 +47010,7 @@ function EnvironmentEditor({ onClose }) {
46668
47010
  return s;
46669
47011
  });
46670
47012
  const ws = useStore$1.getState().workspace;
46671
- if (ws) await electron$k.saveWorkspace(ws);
47013
+ if (ws) await electron$l.saveWorkspace(ws);
46672
47014
  setSelectedId(finalEnv.id);
46673
47015
  }
46674
47016
  function updateVar(idx, patch) {
@@ -46721,9 +47063,9 @@ function EnvironmentEditor({ onClose }) {
46721
47063
  async function saveEncrypted(idx) {
46722
47064
  const plaintext = secretInputs[idx] ?? "";
46723
47065
  if (!plaintext) return;
46724
- const { set: set2 } = await electron$k.checkMasterKey();
47066
+ const { set: set2 } = await electron$l.checkMasterKey();
46725
47067
  if (set2) {
46726
- applyEncrypted(idx, await electron$k.encryptWithMasterKey(plaintext));
47068
+ applyEncrypted(idx, await electron$l.encryptWithMasterKey(plaintext));
46727
47069
  return;
46728
47070
  }
46729
47071
  setPendingEncryptIdx(idx);
@@ -46758,9 +47100,9 @@ function EnvironmentEditor({ onClose }) {
46758
47100
  } : state.workspace
46759
47101
  }));
46760
47102
  const ws = useStore$1.getState().workspace;
46761
- if (ws) await electron$k.saveWorkspace(ws);
47103
+ if (ws) await electron$l.saveWorkspace(ws);
46762
47104
  }
46763
- await electron$k.saveEnvironment(newRelPath, env2);
47105
+ await electron$l.saveEnvironment(newRelPath, env2);
46764
47106
  }
46765
47107
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
46766
47108
  confirmDialog,
@@ -47125,7 +47467,7 @@ function EnvironmentEditor({ onClose }) {
47125
47467
  )
47126
47468
  ] });
47127
47469
  }
47128
- const { electron: electron$j } = window;
47470
+ const { electron: electron$k } = window;
47129
47471
  function EnvironmentBar({ inline = false }) {
47130
47472
  const t = useT();
47131
47473
  const environments = useStore$1((s) => s.environments);
@@ -47141,7 +47483,7 @@ function EnvironmentBar({ inline = false }) {
47141
47483
  if (id2) {
47142
47484
  const hasSecrets = environments[id2]?.data.variables.some((v) => v.enabled && v.secret);
47143
47485
  if (hasSecrets) {
47144
- const { set: set2 } = await electron$j.checkMasterKey();
47486
+ const { set: set2 } = await electron$k.checkMasterKey();
47145
47487
  if (!set2) {
47146
47488
  setPendingEnvId(id2);
47147
47489
  return;
@@ -47348,10 +47690,204 @@ function ProductTour() {
47348
47690
  )
47349
47691
  ] });
47350
47692
  }
47693
+ const { electron: electron$j } = window;
47694
+ function getSyncSettings() {
47695
+ return useStore$1.getState().workspace?.settings?.sync ?? { mode: "git" };
47696
+ }
47697
+ async function persistSync(patch) {
47698
+ const s = useStore$1.getState();
47699
+ const ws = s.workspace;
47700
+ if (!ws) return;
47701
+ const sync = { ...ws.settings?.sync ?? { mode: "cloud" }, ...patch };
47702
+ const settings = { ...ws.settings, sync };
47703
+ s.updateWorkspaceSettings(settings);
47704
+ await electron$j.saveWorkspace({ ...ws, settings });
47705
+ }
47706
+ async function setSyncMode(mode) {
47707
+ await persistSync({ mode });
47708
+ }
47709
+ function workspaceName(manifestName) {
47710
+ return manifestName.replace(/\.(spector|json)$/i, "") || "Workspace";
47711
+ }
47712
+ async function flushToDisk() {
47713
+ const s = useStore$1.getState();
47714
+ for (const { relPath, data, dirty } of Object.values(s.collections)) {
47715
+ if (!dirty) continue;
47716
+ await electron$j.saveCollection(relPath, data);
47717
+ s.markCollectionClean(data.id);
47718
+ }
47719
+ for (const { relPath, data } of Object.values(s.environments)) {
47720
+ await electron$j.saveEnvironment(relPath, data);
47721
+ }
47722
+ for (const { relPath, data } of Object.values(s.flows)) {
47723
+ await electron$j.saveFlow(relPath, data);
47724
+ }
47725
+ if (s.workspace) await electron$j.saveWorkspace(s.workspace);
47726
+ }
47727
+ async function pushWorkspace() {
47728
+ const sync = getSyncSettings();
47729
+ await flushToDisk();
47730
+ const snap = await electron$j.serializeWorkspace();
47731
+ const files = snap.files ?? {};
47732
+ const collections = Object.keys(files).filter((p) => p.startsWith("collections/")).length;
47733
+ const flows = Object.keys(files).filter((p) => p.startsWith("flows/")).length;
47734
+ if (!sync.cloudWorkspaceId) {
47735
+ const created = await electron$j.cloudWorkspaceCreate({ name: workspaceName(snap.manifestName), payload: snap });
47736
+ await persistSync({ mode: "cloud", cloudWorkspaceId: created.id, baseVersion: created.version });
47737
+ return { ok: true, version: created.version, collections, flows };
47738
+ }
47739
+ const res = await electron$j.cloudWorkspacePush({
47740
+ id: sync.cloudWorkspaceId,
47741
+ baseVersion: sync.baseVersion ?? 0,
47742
+ payload: snap
47743
+ });
47744
+ if (res.conflict) return { ok: false, conflict: true, currentVersion: res.currentVersion };
47745
+ await persistSync({ baseVersion: res.version });
47746
+ return { ok: true, version: res.version ?? 0, collections, flows };
47747
+ }
47748
+ async function pullWorkspace() {
47749
+ const sync = getSyncSettings();
47750
+ if (!sync.cloudWorkspaceId) {
47751
+ throw new Error("This workspace is not linked to a cloud workspace yet. Push it first, or link an existing one.");
47752
+ }
47753
+ const remote = await electron$j.cloudWorkspacePull(sync.cloudWorkspaceId);
47754
+ const payload = remote.payload ?? {};
47755
+ if (!payload.manifest || !payload.files) {
47756
+ throw new Error("The cloud workspace has no snapshot yet. Push from a populated workspace first.");
47757
+ }
47758
+ const applied = await electron$j.applyWorkspaceSnapshot({ manifest: payload.manifest, files: payload.files });
47759
+ return {
47760
+ version: remote.version,
47761
+ filesWritten: applied.filesWritten,
47762
+ collections: applied.collections,
47763
+ workspace: applied.workspace,
47764
+ workspacePath: applied.workspacePath
47765
+ };
47766
+ }
47767
+ async function markPulled(version) {
47768
+ await persistSync({ baseVersion: version });
47769
+ }
47770
+ async function listCloudWorkspaces() {
47771
+ const res = await electron$j.cloudWorkspaceList();
47772
+ return res.workspaces;
47773
+ }
47774
+ async function linkToCloudWorkspace(id2) {
47775
+ await persistSync({ mode: "cloud", cloudWorkspaceId: id2, baseVersion: 0 });
47776
+ }
47777
+ function SyncSettingsPanel() {
47778
+ const t = useT();
47779
+ const { applyWorkspace } = useWorkspaceLoader();
47780
+ const initial = getSyncSettings();
47781
+ const [mode, setMode] = reactExports.useState(initial.mode);
47782
+ const [linked, setLinked] = reactExports.useState(!!initial.cloudWorkspaceId);
47783
+ const [busy, setBusy] = reactExports.useState(false);
47784
+ const [status, setStatus] = reactExports.useState(null);
47785
+ const [remote, setRemote] = reactExports.useState(null);
47786
+ const cloudOn = cloudEnabled();
47787
+ async function changeMode(m) {
47788
+ setMode(m);
47789
+ setStatus(null);
47790
+ try {
47791
+ await setSyncMode(m);
47792
+ } catch (e) {
47793
+ setStatus({ msg: e.message, ok: false });
47794
+ }
47795
+ }
47796
+ async function run(fn) {
47797
+ setBusy(true);
47798
+ setStatus(null);
47799
+ try {
47800
+ setStatus({ msg: await fn(), ok: true });
47801
+ } catch (e) {
47802
+ setStatus({ msg: e.message, ok: false });
47803
+ } finally {
47804
+ setBusy(false);
47805
+ }
47806
+ }
47807
+ const doPush = () => run(async () => {
47808
+ const res = await pushWorkspace();
47809
+ if (!res.ok) throw new Error(t("Someone else pushed changes (cloud is at v:v). Pull first, then push again.", { v: res.currentVersion ?? "?" }));
47810
+ setLinked(true);
47811
+ return t("Pushed :c collection(s) and :f flow(s). Cloud is now at version :v.", { c: res.collections, f: res.flows, v: res.version });
47812
+ });
47813
+ const doPull = () => run(async () => {
47814
+ const r = await pullWorkspace();
47815
+ await applyWorkspace(r.workspace, r.workspacePath);
47816
+ await markPulled(r.version);
47817
+ return t("Pulled version :v: :files file(s), :c collection(s).", { v: r.version, files: r.filesWritten, c: r.collections });
47818
+ });
47819
+ const loadRemote = () => run(async () => {
47820
+ const list = await listCloudWorkspaces();
47821
+ setRemote(list);
47822
+ return t(":n cloud workspace(s) in your organization.", { n: list.length });
47823
+ });
47824
+ const joinExisting = (id2) => run(async () => {
47825
+ await linkToCloudWorkspace(id2);
47826
+ const r = await pullWorkspace();
47827
+ await applyWorkspace(r.workspace, r.workspacePath);
47828
+ await markPulled(r.version);
47829
+ setLinked(true);
47830
+ return t("Linked and pulled: :files file(s), :c collection(s).", { files: r.filesWritten, c: r.collections });
47831
+ });
47832
+ const btn = "px-3 py-1.5 text-xs rounded border border-surface-700 text-surface-200 hover:bg-surface-800 disabled:opacity-50 transition-colors";
47833
+ const btnPrimary = "px-3 py-1.5 text-xs rounded font-medium text-white bg-sky-700 hover:bg-sky-600 disabled:opacity-50 transition-colors";
47834
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-5 text-sm text-surface-200", children: [
47835
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
47836
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-semibold text-[var(--text-primary)]", children: t("Workspace sync") }),
47837
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400 mt-1", children: t("How this workspace is shared with your team. Git is the default; Cloud lets teams without git collaborate.") })
47838
+ ] }),
47839
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex flex-col gap-1", children: [
47840
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs uppercase tracking-wide text-surface-500", children: t("Mode") }),
47841
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
47842
+ "select",
47843
+ {
47844
+ value: mode,
47845
+ onChange: (e) => changeMode(e.target.value),
47846
+ className: "rounded bg-surface-950 border border-surface-700 px-2 py-1.5 text-sm w-56",
47847
+ children: [
47848
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "git", children: t("Git (default) — use the Git panel") }),
47849
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "cloud", children: t("Cloud — sync through API Spector Cloud") }),
47850
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "local", children: t("Local only — no sync") })
47851
+ ]
47852
+ }
47853
+ )
47854
+ ] }),
47855
+ mode === "git" && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400", children: t("This workspace syncs via Git. Use the Git panel in the sidebar to commit, pull and push.") }),
47856
+ mode === "local" && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400", children: t("No syncing. Changes stay on this machine.") }),
47857
+ mode === "cloud" && !cloudOn && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-amber-400", children: t("Connect this workspace to API Spector Cloud first (the Cloud tab), then come back to sync.") }),
47858
+ mode === "cloud" && cloudOn && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3", children: [
47859
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: t("Collections, folders, requests, flows and environments sync. Secret values are never uploaded — each member keeps their own.") }),
47860
+ linked ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
47861
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: btnPrimary, disabled: busy, onClick: doPush, children: t("Push") }),
47862
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: btn, disabled: busy, onClick: doPull, children: t("Pull") })
47863
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3", children: [
47864
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
47865
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: btnPrimary, disabled: busy, onClick: doPush, children: t("Create cloud workspace (push)") }),
47866
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: btn, disabled: busy, onClick: loadRemote, children: t("Join an existing one") })
47867
+ ] }),
47868
+ remote && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-surface-700 divide-y divide-surface-800", children: [
47869
+ remote.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-2 text-xs text-surface-500", children: t("No cloud workspaces yet.") }),
47870
+ remote.map((w) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [
47871
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex-1 truncate", children: [
47872
+ w.name,
47873
+ " ",
47874
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500", children: [
47875
+ "v",
47876
+ w.version
47877
+ ] })
47878
+ ] }),
47879
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: btn, disabled: busy, onClick: () => joinExisting(w.id), children: t("Join & pull") })
47880
+ ] }, w.id))
47881
+ ] })
47882
+ ] })
47883
+ ] }),
47884
+ status && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: `text-xs ${status.ok ? "text-emerald-400" : "text-red-400"}`, children: status.msg })
47885
+ ] });
47886
+ }
47351
47887
  const { electron: electron$i } = window;
47352
47888
  const DEFAULT_PII_PATTERNS = ["authorization", "password", "token", "secret", "api-key", "x-api-key"];
47353
47889
  const ZOOM_STEPS = [0.75, 0.9, 1, 1.1, 1.25, 1.5];
47354
- const SETTINGS_TABS = ["general", "appearance", "proxy", "tls", "privacy", "cloud", "ai", "secrets"];
47890
+ const SETTINGS_TABS = ["general", "appearance", "proxy", "tls", "privacy", "cloud", "sync", "ai", "secrets"];
47355
47891
  function WorkspaceSettingsModal({ onClose, initialTab }) {
47356
47892
  const workspace = useStore$1((s) => s.workspace);
47357
47893
  const updateWorkspaceSettings = useStore$1((s) => s.updateWorkspaceSettings);
@@ -47559,6 +48095,7 @@ function WorkspaceSettingsModal({ onClose, initialTab }) {
47559
48095
  { id: "privacy", label: t("Privacy") },
47560
48096
  { id: "secrets", label: t("Secrets") },
47561
48097
  { id: "cloud", label: t("Cloud") },
48098
+ { id: "sync", label: t("Sync") },
47562
48099
  { id: "ai", label: t("AI") }
47563
48100
  ];
47564
48101
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
@@ -47883,6 +48420,7 @@ function WorkspaceSettingsModal({ onClose, initialTab }) {
47883
48420
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("Reject unauthorized / self-signed certificates") })
47884
48421
  ] })
47885
48422
  ] }),
48423
+ activeTab === "sync" && /* @__PURE__ */ jsxRuntimeExports.jsx(SyncSettingsPanel, {}),
47886
48424
  activeTab === "ai" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
47887
48425
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-600 text-[11px] leading-relaxed", children: t("Optional. Configure an OpenAI key to generate documentation from the Docs tab of a request, folder, or collection. The key is stored only in your OS keychain and never written to the workspace file or version control.") }),
47888
48426
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
@@ -54572,6 +55110,16 @@ function ChangesTab({ status, onRefresh }) {
54572
55110
  setError(String(e));
54573
55111
  }
54574
55112
  }
55113
+ async function stash() {
55114
+ try {
55115
+ setError(null);
55116
+ await electron$4.gitStash();
55117
+ showToast(t("Changes stashed - restore them from the Branches tab"), true);
55118
+ onRefresh();
55119
+ } catch (e) {
55120
+ setError(String(e));
55121
+ }
55122
+ }
54575
55123
  async function fetch() {
54576
55124
  setFetching(true);
54577
55125
  try {
@@ -54624,6 +55172,7 @@ function ChangesTab({ status, onRefresh }) {
54624
55172
  }
54625
55173
  const allUnstaged = [...status.unstaged, ...status.untracked];
54626
55174
  const hasStaged = status.staged.length > 0;
55175
+ const isDirty = hasStaged || status.unstaged.length > 0 || status.untracked.length > 0;
54627
55176
  const needsPush = status.ahead > 0;
54628
55177
  const needsPull = status.behind > 0;
54629
55178
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0", children: [
@@ -54880,15 +55429,27 @@ function ChangesTab({ status, onRefresh }) {
54880
55429
  className: "w-full bg-surface-800 border border-surface-700 rounded px-2 py-1.5 text-xs resize-none focus:outline-none focus:border-blue-500 placeholder-surface-600"
54881
55430
  }
54882
55431
  ),
54883
- /* @__PURE__ */ jsxRuntimeExports.jsx(
54884
- "button",
54885
- {
54886
- onClick: commit,
54887
- disabled: !message.trim() || !hasStaged,
54888
- className: "w-full py-1.5 bg-blue-600 hover:bg-blue-500 disabled:opacity-40 disabled:cursor-not-allowed rounded text-xs font-medium transition-colors",
54889
- children: hasStaged ? t("Commit (:count)", { count: status.staged.length }) : t("Commit")
54890
- }
54891
- )
55432
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
55433
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55434
+ "button",
55435
+ {
55436
+ onClick: commit,
55437
+ disabled: !message.trim() || !hasStaged,
55438
+ className: "flex-1 py-1.5 bg-blue-600 hover:bg-blue-500 disabled:opacity-40 disabled:cursor-not-allowed rounded text-xs font-medium transition-colors",
55439
+ children: hasStaged ? t("Commit (:count)", { count: status.staged.length }) : t("Commit")
55440
+ }
55441
+ ),
55442
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55443
+ "button",
55444
+ {
55445
+ onClick: stash,
55446
+ disabled: !isDirty,
55447
+ title: t("Park all uncommitted changes - restore them later from the Branches tab"),
55448
+ className: "px-3 py-1.5 bg-surface-800 hover:bg-surface-700 border border-surface-700 disabled:opacity-40 disabled:cursor-not-allowed rounded text-xs font-medium text-surface-200 transition-colors",
55449
+ children: t("Stash")
55450
+ }
55451
+ )
55452
+ ] })
54892
55453
  ] })
54893
55454
  ] });
54894
55455
  }
@@ -54916,6 +55477,8 @@ function BranchesTab({ onRefresh }) {
54916
55477
  const t = useT();
54917
55478
  const [branches, setBranches] = reactExports.useState([]);
54918
55479
  const [remotes, setRemotes] = reactExports.useState([]);
55480
+ const [stashes, setStashes] = reactExports.useState([]);
55481
+ const [info, setInfo] = reactExports.useState(null);
54919
55482
  const [loading, setLoading] = reactExports.useState(true);
54920
55483
  const [newBranch, setNewBranch] = reactExports.useState("");
54921
55484
  const [creating, setCreating] = reactExports.useState(false);
@@ -54929,9 +55492,10 @@ function BranchesTab({ onRefresh }) {
54929
55492
  const [filter2, setFilter] = reactExports.useState("");
54930
55493
  const load2 = reactExports.useCallback(() => {
54931
55494
  setLoadError(null);
54932
- Promise.all([electron$4.gitBranches(), electron$4.gitRemotes()]).then(([b, r]) => {
55495
+ Promise.all([electron$4.gitBranches(), electron$4.gitRemotes(), electron$4.gitStashList()]).then(([b, r, s]) => {
54933
55496
  setBranches(b);
54934
55497
  setRemotes(r);
55498
+ setStashes(s);
54935
55499
  }).catch((err) => {
54936
55500
  console.warn("GitPanel: failed to load branches/remotes", err);
54937
55501
  setLoadError(err instanceof Error ? err.message : String(err));
@@ -54940,13 +55504,34 @@ function BranchesTab({ onRefresh }) {
54940
55504
  reactExports.useEffect(() => {
54941
55505
  load2();
54942
55506
  }, [load2]);
55507
+ function isDirtyTreeError(msg) {
55508
+ return /overwritten by (checkout|merge)|commit your changes or stash/i.test(msg);
55509
+ }
55510
+ async function checkoutOrStash(localName, create2) {
55511
+ try {
55512
+ await electron$4.gitCheckout(localName, create2);
55513
+ return true;
55514
+ } catch (e) {
55515
+ const msg = String(e);
55516
+ if (!isDirtyTreeError(msg)) throw e;
55517
+ if (!confirm(t('You have uncommitted changes that would be lost switching to ":name". Stash them and switch?', { name: localName }))) {
55518
+ return false;
55519
+ }
55520
+ await electron$4.gitStash(t("Auto-stash before switching to :name", { name: localName }));
55521
+ await electron$4.gitCheckout(localName, create2);
55522
+ setInfo(t("Your changes were stashed. Restore them from the Stashes list below when you return."));
55523
+ return true;
55524
+ }
55525
+ }
54943
55526
  async function checkout(name, remote2) {
54944
55527
  try {
54945
55528
  setError(null);
55529
+ setInfo(null);
54946
55530
  const localName = remote2 ? name.replace(/^origin\//, "") : name;
54947
- await electron$4.gitCheckout(localName, false);
54948
- load2();
54949
- onRefresh();
55531
+ if (await checkoutOrStash(localName, false)) {
55532
+ load2();
55533
+ onRefresh();
55534
+ }
54950
55535
  } catch (e) {
54951
55536
  setError(String(e));
54952
55537
  }
@@ -54955,15 +55540,39 @@ function BranchesTab({ onRefresh }) {
54955
55540
  if (!newBranch.trim()) return;
54956
55541
  try {
54957
55542
  setError(null);
54958
- await electron$4.gitCheckout(newBranch.trim(), true);
54959
- setNewBranch("");
54960
- setCreating(false);
55543
+ setInfo(null);
55544
+ if (await checkoutOrStash(newBranch.trim(), true)) {
55545
+ setNewBranch("");
55546
+ setCreating(false);
55547
+ load2();
55548
+ onRefresh();
55549
+ }
55550
+ } catch (e) {
55551
+ setError(String(e));
55552
+ }
55553
+ }
55554
+ async function popStash(ref) {
55555
+ try {
55556
+ setError(null);
55557
+ setInfo(null);
55558
+ await electron$4.gitStashPop(ref);
54961
55559
  load2();
54962
55560
  onRefresh();
54963
55561
  } catch (e) {
54964
55562
  setError(String(e));
54965
55563
  }
54966
55564
  }
55565
+ async function dropStash(ref) {
55566
+ if (!confirm(t("Discard stashed changes (:ref)? This cannot be undone.", { ref }))) return;
55567
+ try {
55568
+ setError(null);
55569
+ setInfo(null);
55570
+ await electron$4.gitStashDrop(ref);
55571
+ load2();
55572
+ } catch (e) {
55573
+ setError(String(e));
55574
+ }
55575
+ }
54967
55576
  async function addRemote() {
54968
55577
  if (!remoteName.trim() || !remoteUrl.trim()) return;
54969
55578
  try {
@@ -55022,7 +55631,8 @@ function BranchesTab({ onRefresh }) {
55022
55631
  const remote = branches.filter((b) => b.remote && (!q || b.name.toLowerCase().includes(q)));
55023
55632
  if (loading) return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 px-3 py-4", children: t("Loading…") });
55024
55633
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0 overflow-y-auto", children: [
55025
- error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-red-400 px-3 py-1 truncate", children: error2 }),
55634
+ error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-red-400 px-3 py-1 truncate", title: error2, children: error2 }),
55635
+ info && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-blue-300 px-3 py-1 bg-blue-900/20 border-b border-surface-800", children: info }),
55026
55636
  loadError && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] text-amber-400 px-3 py-1 truncate", title: loadError, children: [
55027
55637
  t("Couldn't load git state:"),
55028
55638
  " ",
@@ -55114,6 +55724,37 @@ function BranchesTab({ onRefresh }) {
55114
55724
  ))
55115
55725
  ] }),
55116
55726
  q && local.length === 0 && remote.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "px-3 py-3 text-[11px] text-surface-500", children: t('No branches match ":filter".', { filter: filter2 }) }),
55727
+ stashes.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "border-t border-surface-800 mt-1", children: [
55728
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "px-3 py-1.5 text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: t("Stashes") }),
55729
+ stashes.map((s) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "group flex items-center gap-2 px-3 py-1.5 text-xs text-surface-300 hover:bg-surface-800/50", children: [
55730
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-[10px] text-surface-500 shrink-0", children: s.ref }),
55731
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "truncate flex-1", title: s.branch ? t("on :branch: :message", { branch: s.branch, message: s.message }) : s.message, children: [
55732
+ s.message,
55733
+ s.branch && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-600", children: [
55734
+ " ",
55735
+ t("(on :branch)", { branch: s.branch })
55736
+ ] })
55737
+ ] }),
55738
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55739
+ "button",
55740
+ {
55741
+ onClick: () => popStash(s.ref),
55742
+ className: "opacity-0 group-hover:opacity-100 text-[10px] text-blue-400 hover:text-blue-300 transition-all shrink-0",
55743
+ title: t("Restore these changes onto the current branch"),
55744
+ children: t("Pop")
55745
+ }
55746
+ ),
55747
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55748
+ "button",
55749
+ {
55750
+ onClick: () => dropStash(s.ref),
55751
+ className: "opacity-0 group-hover:opacity-100 text-[10px] text-surface-500 hover:text-red-400 transition-all shrink-0",
55752
+ title: t("Discard this stash"),
55753
+ children: "✕"
55754
+ }
55755
+ )
55756
+ ] }, s.ref))
55757
+ ] }),
55117
55758
  /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "border-t border-surface-800 mt-1", children: [
55118
55759
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-1.5 flex items-center justify-between", children: [
55119
55760
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: t("Remotes") }),
@@ -57912,6 +58553,43 @@ function App() {
57912
58553
  });
57913
58554
  return () => electron.offOpenFromGit();
57914
58555
  }, [applyWorkspace]);
58556
+ reactExports.useEffect(() => {
58557
+ electron.onImportShare(async (token) => {
58558
+ try {
58559
+ const { type, name, payload } = await electron.cloudImportShare(token);
58560
+ let collection2;
58561
+ if (type === "request") {
58562
+ const r = payload;
58563
+ const reqId = r.id || crypto.randomUUID();
58564
+ const req = { ...r, id: reqId };
58565
+ collection2 = {
58566
+ version: "1.0",
58567
+ id: crypto.randomUUID(),
58568
+ name: name || req.name || "Shared request",
58569
+ rootFolder: { id: crypto.randomUUID(), name: "", folders: [], requestIds: [reqId] },
58570
+ requests: { [reqId]: req }
58571
+ };
58572
+ } else {
58573
+ collection2 = { ...payload, id: crypto.randomUUID() };
58574
+ }
58575
+ const store = useStore$1.getState();
58576
+ const id2 = store.addCollectionObject(collection2);
58577
+ try {
58578
+ const entry = useStore$1.getState().collections[id2];
58579
+ if (entry && useStore$1.getState().workspace) {
58580
+ await electron.saveCollection(entry.relPath, entry.data);
58581
+ await electron.saveWorkspace(useStore$1.getState().workspace);
58582
+ }
58583
+ } catch (persistErr) {
58584
+ console.error("Imported shared collection but could not save it yet:", persistErr);
58585
+ }
58586
+ setSidebarTab("collections");
58587
+ } catch (err) {
58588
+ console.error("Import from share link failed:", err);
58589
+ }
58590
+ });
58591
+ return () => electron.offImportShare();
58592
+ }, [setSidebarTab]);
57915
58593
  reactExports.useEffect(() => {
57916
58594
  electron.onMockHit(addMockHit);
57917
58595
  return () => electron.offMockHit();
@@ -58080,7 +58758,7 @@ function App() {
58080
58758
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--wordmark-accent)" }, children: "Spector" }),
58081
58759
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal", style: { color: "var(--text-muted)" }, children: [
58082
58760
  "v",
58083
- "0.9.1"
58761
+ "0.9.2"
58084
58762
  ] }),
58085
58763
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mx-2 opacity-30", "aria-hidden": "true", children: "·" }),
58086
58764
  /* @__PURE__ */ jsxRuntimeExports.jsx(