@testsmith/api-spector 0.9.1 → 0.9.3

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-J-8g0S0P.js","./codemirror-BKmwMUlV.js","./ajv-CJPwFjiP.js","./SoapEditor-CmsqdXhh.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: {} };
@@ -13203,7 +13203,8 @@ function baseTab(collectionId) {
13203
13203
  lastSentRequest: null,
13204
13204
  lastHookResults: null,
13205
13205
  isSending: false,
13206
- scriptTab: "post"
13206
+ scriptTab: "post",
13207
+ jqExpr: ""
13207
13208
  };
13208
13209
  }
13209
13210
  function makeTab(requestId, collectionId, opts = {}) {
@@ -13337,6 +13338,10 @@ const createTabsSlice = (set2, get2) => ({
13337
13338
  const t = s.tabs.find((x) => x.id === tabId);
13338
13339
  if (t) t.scriptTab = scriptTab;
13339
13340
  }),
13341
+ setTabJqExpr: (tabId, jqExpr) => set2((s) => {
13342
+ const t = s.tabs.find((x) => x.id === tabId);
13343
+ if (t) t.jqExpr = jqExpr;
13344
+ }),
13340
13345
  // ── Navigation ──────────────────────────────────────────────────────────────
13341
13346
  setActiveCollection: (id2) => set2((s) => {
13342
13347
  s.activeCollectionId = id2;
@@ -14771,7 +14776,7 @@ const useStore$1 = create$1()(
14771
14776
  })
14772
14777
  }))
14773
14778
  );
14774
- const { electron: electron$I } = window;
14779
+ const { electron: electron$J } = window;
14775
14780
  function useAutoSave() {
14776
14781
  const collections = useStore$1((s) => s.collections);
14777
14782
  useStore$1((s) => s.environments);
@@ -14790,7 +14795,7 @@ function useAutoSave() {
14790
14795
  for (const { relPath, data, dirty } of dirtyCollections) {
14791
14796
  if (!dirty) continue;
14792
14797
  try {
14793
- await electron$I.saveCollection(relPath, data);
14798
+ await electron$J.saveCollection(relPath, data);
14794
14799
  markCollectionClean(data.id);
14795
14800
  } catch (e) {
14796
14801
  console.error("Auto-save failed for", relPath, e);
@@ -14809,7 +14814,7 @@ function useAutoSave() {
14809
14814
  for (const { relPath, data, dirty } of dirtyFlows) {
14810
14815
  if (!dirty) continue;
14811
14816
  try {
14812
- await electron$I.saveFlow(relPath, data);
14817
+ await electron$J.saveFlow(relPath, data);
14813
14818
  markFlowClean(data.id);
14814
14819
  } catch (e) {
14815
14820
  console.error("Auto-save failed for", relPath, e);
@@ -14825,7 +14830,7 @@ function useAutoSave() {
14825
14830
  if (wsTimerRef.current) clearTimeout(wsTimerRef.current);
14826
14831
  wsTimerRef.current = setTimeout(async () => {
14827
14832
  try {
14828
- await electron$I.saveWorkspace(workspace);
14833
+ await electron$J.saveWorkspace(workspace);
14829
14834
  } catch {
14830
14835
  }
14831
14836
  }, 300);
@@ -14834,7 +14839,7 @@ function useAutoSave() {
14834
14839
  };
14835
14840
  }, [workspace]);
14836
14841
  }
14837
- const { electron: electron$H } = window;
14842
+ const { electron: electron$I } = window;
14838
14843
  function useWorkspaceLoader() {
14839
14844
  const loadCollection = useStore$1((s) => s.loadCollection);
14840
14845
  const loadEnvironment = useStore$1((s) => s.loadEnvironment);
@@ -14861,7 +14866,7 @@ function useWorkspaceLoader() {
14861
14866
  if (typeof ws.settings?.zoom === "number") setZoom(ws.settings.zoom);
14862
14867
  if (ws.settings?.persistHistory) {
14863
14868
  try {
14864
- const entries = await electron$H.loadHistory();
14869
+ const entries = await electron$I.loadHistory();
14865
14870
  useStore$1.getState().setHistory(entries);
14866
14871
  } catch {
14867
14872
  }
@@ -14870,14 +14875,14 @@ function useWorkspaceLoader() {
14870
14875
  }
14871
14876
  for (const colPath of ws.collections ?? []) {
14872
14877
  try {
14873
- const col = await electron$H.loadCollection(colPath);
14878
+ const col = await electron$I.loadCollection(colPath);
14874
14879
  loadCollection(colPath, col);
14875
14880
  } catch {
14876
14881
  }
14877
14882
  }
14878
14883
  for (const envPath of ws.environments ?? []) {
14879
14884
  try {
14880
- const env2 = await electron$H.loadEnvironment(envPath);
14885
+ const env2 = await electron$I.loadEnvironment(envPath);
14881
14886
  loadEnvironment(envPath, env2);
14882
14887
  } catch {
14883
14888
  }
@@ -14896,32 +14901,38 @@ function useWorkspaceLoader() {
14896
14901
  }
14897
14902
  for (const relPath of ws.mocks ?? []) {
14898
14903
  try {
14899
- const mockData = await electron$H.loadMock(relPath);
14904
+ const mockData = await electron$I.loadMock(relPath);
14900
14905
  loadMock(relPath, mockData);
14901
14906
  } catch {
14902
14907
  }
14903
14908
  }
14904
14909
  for (const relPath of ws.flows ?? []) {
14905
14910
  try {
14906
- const flowData = await electron$H.loadFlow(relPath);
14911
+ const flowData = await electron$I.loadFlow(relPath);
14907
14912
  loadFlow(relPath, flowData);
14908
14913
  } catch {
14909
14914
  }
14910
14915
  }
14911
14916
  try {
14912
- const snapshots = await electron$H.listContractSnapshots(ws.contracts ?? []);
14917
+ const snapshots = await electron$I.listContractSnapshots(ws.contracts ?? []);
14913
14918
  for (const { relPath, snapshot } of snapshots) loadContractSnapshot(relPath, snapshot);
14914
14919
  } catch {
14915
14920
  }
14916
14921
  if ((ws.collections ?? []).length > 0) {
14917
14922
  try {
14918
- const firstCol = await electron$H.loadCollection(ws.collections[0]);
14923
+ const firstCol = await electron$I.loadCollection(ws.collections[0]);
14919
14924
  setActiveCollection(firstCol.id);
14920
14925
  } catch {
14921
14926
  }
14922
14927
  }
14923
14928
  }, [loadCollection, loadEnvironment, loadMock, loadFlow, loadContractSnapshot, setActiveCollection, setTheme, setZoom]);
14924
- return { applyWorkspace };
14929
+ const reloadWorkspace = reactExports.useCallback(async () => {
14930
+ const path = useStore$1.getState().workspacePath;
14931
+ if (!path) return;
14932
+ const result = await electron$I.openWorkspacePath(path);
14933
+ if (result) await applyWorkspace(result.workspace, result.workspacePath);
14934
+ }, [applyWorkspace]);
14935
+ return { applyWorkspace, reloadWorkspace };
14925
14936
  }
14926
14937
  var reactDomExports = requireReactDom();
14927
14938
  const ReactDOM = /* @__PURE__ */ getDefaultExportFromCjs(reactDomExports);
@@ -25861,7 +25872,7 @@ function Modal({
25861
25872
  }
25862
25873
  );
25863
25874
  }
25864
- const { electron: electron$G } = window;
25875
+ const { electron: electron$H } = window;
25865
25876
  function Oauth2Form({ auth, onChange }) {
25866
25877
  const t = useT();
25867
25878
  const activeEnv = useActiveEnvironment();
@@ -25881,7 +25892,7 @@ function Oauth2Form({ auth, onChange }) {
25881
25892
  const flow = auth.oauth2Flow ?? "client_credentials";
25882
25893
  async function saveSecret(ref) {
25883
25894
  if (!secretValue || !ref) return;
25884
- await electron$G.setSecret(ref, secretValue);
25895
+ await electron$H.setSecret(ref, secretValue);
25885
25896
  setSaved(true);
25886
25897
  setSecretValue("");
25887
25898
  setTimeout(() => setSaved(false), 2e3);
@@ -25890,7 +25901,7 @@ function Oauth2Form({ auth, onChange }) {
25890
25901
  setStatus("fetching");
25891
25902
  setError("");
25892
25903
  try {
25893
- const result = await electron$G.oauth2StartFlow(auth, resolveCtx());
25904
+ const result = await electron$H.oauth2StartFlow(auth, resolveCtx());
25894
25905
  onChange({ oauth2CachedToken: result.accessToken, oauth2TokenExpiry: result.expiresAt });
25895
25906
  if (result.refreshToken) setRefreshToken(result.refreshToken);
25896
25907
  setStatus("ok");
@@ -25904,7 +25915,7 @@ function Oauth2Form({ auth, onChange }) {
25904
25915
  setStatus("fetching");
25905
25916
  setError("");
25906
25917
  try {
25907
- const result = await electron$G.oauth2RefreshToken(auth, resolveCtx(), refreshToken);
25918
+ const result = await electron$H.oauth2RefreshToken(auth, resolveCtx(), refreshToken);
25908
25919
  onChange({ oauth2CachedToken: result.accessToken, oauth2TokenExpiry: result.expiresAt });
25909
25920
  if (result.refreshToken) setRefreshToken(result.refreshToken);
25910
25921
  setStatus("ok");
@@ -26764,7 +26775,7 @@ function CollectionSettingsModal({ collection: collection2, onClose }) {
26764
26775
  }
26765
26776
  );
26766
26777
  }
26767
- const { electron: electron$F } = window;
26778
+ const { electron: electron$G } = window;
26768
26779
  function normalisePath(url) {
26769
26780
  let path = url.replace(/^\{\{[^}]+\}\}/, "").replace(/^https?:\/\/[^/]+/, "");
26770
26781
  if (!path.startsWith("/")) path = "/" + path;
@@ -26852,7 +26863,7 @@ function SchemaSyncModal({
26852
26863
  setLoading(true);
26853
26864
  setError(null);
26854
26865
  try {
26855
- const entries = await electron$F.extractOpenApiSchemas();
26866
+ const entries = await electron$G.extractOpenApiSchemas();
26856
26867
  if (!entries) {
26857
26868
  setLoading(false);
26858
26869
  return;
@@ -26871,7 +26882,7 @@ function SchemaSyncModal({
26871
26882
  setLoading(true);
26872
26883
  setError(null);
26873
26884
  try {
26874
- const entries = await electron$F.extractOpenApiSchemasFromUrl(trimmed);
26885
+ const entries = await electron$G.extractOpenApiSchemasFromUrl(trimmed);
26875
26886
  setSpecEntries(entries);
26876
26887
  autoSelectChanged(entries);
26877
26888
  } catch (err) {
@@ -26904,7 +26915,7 @@ function SchemaSyncModal({
26904
26915
  }
26905
26916
  const entry = useStore$1.getState().collections[collectionId];
26906
26917
  if (entry) {
26907
- await electron$F.saveCollection(entry.relPath, entry.data);
26918
+ await electron$G.saveCollection(entry.relPath, entry.data);
26908
26919
  markCollectionClean(collectionId);
26909
26920
  }
26910
26921
  onClose();
@@ -27417,7 +27428,7 @@ function hasContract(r) {
27417
27428
  const c = r.contract;
27418
27429
  return !!c && (c.statusCode != null || !!c.bodySchema || !!c.bodyMatcher || (c.headers?.length ?? 0) > 0 || (c.providerStates?.length ?? 0) > 0);
27419
27430
  }
27420
- const { electron: electron$E } = window;
27431
+ const { electron: electron$F } = window;
27421
27432
  function cloudEnabled() {
27422
27433
  return Boolean(useStore$1.getState().workspace?.settings?.cloud?.enabled);
27423
27434
  }
@@ -27426,7 +27437,7 @@ function assertEnabled() {
27426
27437
  }
27427
27438
  async function pushContractToCloud(requests, opts) {
27428
27439
  assertEnabled();
27429
- return electron$E.cloudPushPact({
27440
+ return electron$F.cloudPushPact({
27430
27441
  consumer: opts.consumer,
27431
27442
  provider: opts.provider,
27432
27443
  consumerVersion: opts.version,
@@ -27435,16 +27446,16 @@ async function pushContractToCloud(requests, opts) {
27435
27446
  }
27436
27447
  async function pushProviderSpecToCloud(opts) {
27437
27448
  assertEnabled();
27438
- return electron$E.cloudPushSpec(opts);
27449
+ return electron$F.cloudPushSpec(opts);
27439
27450
  }
27440
27451
  function openCloudMatrix() {
27441
- void electron$E.cloudOpenMatrix();
27452
+ void electron$F.cloudOpenMatrix();
27442
27453
  }
27443
27454
  async function pushFlowToCloud(flow) {
27444
27455
  assertEnabled();
27445
27456
  const s = useStore$1.getState();
27446
27457
  const environment = resolveEnvironmentById(s.environments, s.activeEnvironmentId);
27447
- return electron$E.cloudPushFlow({
27458
+ return electron$F.cloudPushFlow({
27448
27459
  flow,
27449
27460
  collections: Object.values(s.collections).map((c) => c.data),
27450
27461
  flows: Object.values(s.flows).map((f) => f.data),
@@ -27454,12 +27465,12 @@ async function pushFlowToCloud(flow) {
27454
27465
  }
27455
27466
  async function runFlowInCloud(flow, onStatus) {
27456
27467
  assertEnabled();
27457
- const started = await electron$E.cloudRunFlow(flow.name);
27468
+ const started = await electron$F.cloudRunFlow(flow.name);
27458
27469
  onStatus?.(started.status);
27459
27470
  const deadlineMs = 2 * 60 * 1e3;
27460
27471
  const t0 = Date.now();
27461
27472
  for (; ; ) {
27462
- const run = await electron$E.cloudGetFlowRun({ name: flow.name, runId: started.id });
27473
+ const run = await electron$F.cloudGetFlowRun({ name: flow.name, runId: started.id });
27463
27474
  onStatus?.(run.status);
27464
27475
  if (run.status === "passed" || run.status === "failed" || run.status === "error" || run.status === "done") {
27465
27476
  if (run.summary) return run.summary;
@@ -27470,12 +27481,53 @@ async function runFlowInCloud(flow, onStatus) {
27470
27481
  }
27471
27482
  }
27472
27483
  function openCloudFlow(flow) {
27473
- void electron$E.cloudOpenFlow(flow.name);
27484
+ void electron$F.cloudOpenFlow(flow.name);
27485
+ }
27486
+ async function shareCollectionToCloud(collection2) {
27487
+ assertEnabled();
27488
+ return electron$F.cloudShareCollection(collection2);
27489
+ }
27490
+ async function shareRequestToCloud(request2) {
27491
+ assertEnabled();
27492
+ return electron$F.cloudShareRequest(request2);
27493
+ }
27494
+ function collectFolderRequestIds(folder2) {
27495
+ return [...folder2.requestIds, ...folder2.folders.flatMap(collectFolderRequestIds)];
27496
+ }
27497
+ async function shareFolderToCloud(collection2, folder2) {
27498
+ assertEnabled();
27499
+ const ids = new Set(collectFolderRequestIds(folder2));
27500
+ const requests = {};
27501
+ for (const id2 of ids) {
27502
+ const r = collection2.requests[id2];
27503
+ if (r) requests[id2] = r;
27504
+ }
27505
+ const payload = {
27506
+ version: "1.0",
27507
+ id: folder2.id,
27508
+ name: folder2.name,
27509
+ rootFolder: folder2,
27510
+ requests,
27511
+ collectionVariables: collection2.collectionVariables
27512
+ };
27513
+ return electron$F.cloudShareFolder({ sourceId: folder2.id, name: folder2.name, payload });
27514
+ }
27515
+ async function lookupShare(type, sourceId) {
27516
+ assertEnabled();
27517
+ return electron$F.cloudShareLookup({ type, sourceId });
27518
+ }
27519
+ async function restrictShare(token, expiresDays) {
27520
+ assertEnabled();
27521
+ return electron$F.cloudShareUpdate({ token, expiresDays });
27522
+ }
27523
+ async function revokeShare(token) {
27524
+ assertEnabled();
27525
+ await electron$F.cloudShareRevoke(token);
27474
27526
  }
27475
27527
  async function getCloudMockRoutes(name) {
27476
27528
  if (!cloudEnabled()) return null;
27477
27529
  try {
27478
- const res = await electron$E.cloudGetMock(name);
27530
+ const res = await electron$F.cloudGetMock(name);
27479
27531
  return res.exists ? res.routes ?? [] : null;
27480
27532
  } catch {
27481
27533
  return null;
@@ -27484,7 +27536,7 @@ async function getCloudMockRoutes(name) {
27484
27536
  async function pushMockToCloud(server, routeIds) {
27485
27537
  assertEnabled();
27486
27538
  const routes = routeIds ? (server.routes ?? []).filter((r) => routeIds.includes(r.id)) : server.routes ?? [];
27487
- return electron$E.cloudPushMock({ ...server, routes });
27539
+ return electron$F.cloudPushMock({ ...server, routes });
27488
27540
  }
27489
27541
  async function pushRequestAsMonitor(request2, opts) {
27490
27542
  assertEnabled();
@@ -27508,7 +27560,7 @@ async function pushRequestAsMonitor(request2, opts) {
27508
27560
  ...request2.headers
27509
27561
  ];
27510
27562
  const mergedRequest = { ...request2, auth: mergedAuth, headers: mergedHeaders };
27511
- return electron$E.cloudPushMonitor(
27563
+ return electron$F.cloudPushMonitor(
27512
27564
  {
27513
27565
  request: mergedRequest,
27514
27566
  setup,
@@ -27926,6 +27978,201 @@ function Toast({ toast }) {
27926
27978
  if (!toast) return null;
27927
27979
  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
27980
  }
27981
+ function CloudRequiredModal({
27982
+ action,
27983
+ onConnect,
27984
+ onClose
27985
+ }) {
27986
+ const t = useT();
27987
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
27988
+ Modal,
27989
+ {
27990
+ onClose,
27991
+ closeOnBackdrop: true,
27992
+ overlayClassName: "bg-black/50 z-[300] flex items-center justify-center",
27993
+ panelClassName: "bg-surface-900 border border-surface-700 rounded-lg shadow-2xl p-5 w-96 flex flex-col gap-4",
27994
+ children: [
27995
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
27996
+ /* @__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" }) }) }),
27997
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
27998
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-sm font-semibold text-white", children: t("Connect to API Spector Cloud") }),
27999
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400 mt-0.5", children: t("Sharing needs the cloud") })
28000
+ ] })
28001
+ ] }),
28002
+ /* @__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.") }),
28003
+ /* @__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.") }),
28004
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2", children: [
28005
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
28006
+ "button",
28007
+ {
28008
+ onClick: onClose,
28009
+ className: "px-3 py-1.5 text-xs text-surface-400 hover:text-white transition-colors",
28010
+ children: t("Not now")
28011
+ }
28012
+ ),
28013
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
28014
+ "button",
28015
+ {
28016
+ onClick: onConnect,
28017
+ className: "px-3 py-1.5 text-xs font-medium text-white bg-sky-700 hover:bg-sky-600 rounded transition-colors",
28018
+ children: t("Open Cloud settings")
28019
+ }
28020
+ )
28021
+ ] })
28022
+ ]
28023
+ }
28024
+ );
28025
+ }
28026
+ function ShareModal({
28027
+ title,
28028
+ type,
28029
+ sourceId,
28030
+ onCreate,
28031
+ onClose
28032
+ }) {
28033
+ const t = useT();
28034
+ const [loading, setLoading] = reactExports.useState(true);
28035
+ const [busy, setBusy] = reactExports.useState(false);
28036
+ const [error2, setError] = reactExports.useState(null);
28037
+ const [copied, setCopied] = reactExports.useState(false);
28038
+ const [live, setLive] = reactExports.useState(null);
28039
+ const [expiryDays, setExpiryDays] = reactExports.useState(0);
28040
+ reactExports.useEffect(() => {
28041
+ let cancelled = false;
28042
+ (async () => {
28043
+ try {
28044
+ const res = await lookupShare(type, sourceId);
28045
+ if (!cancelled && res.exists && res.token && res.url && res.deep_link) {
28046
+ setLive({ token: res.token, url: res.url, deep_link: res.deep_link, expires_at: res.expires_at ?? null });
28047
+ }
28048
+ } catch (e) {
28049
+ if (!cancelled) setError(e.message);
28050
+ } finally {
28051
+ if (!cancelled) setLoading(false);
28052
+ }
28053
+ })();
28054
+ return () => {
28055
+ cancelled = true;
28056
+ };
28057
+ }, [type, sourceId]);
28058
+ async function run(fn) {
28059
+ setBusy(true);
28060
+ setError(null);
28061
+ try {
28062
+ await fn();
28063
+ } catch (e) {
28064
+ setError(e.message);
28065
+ } finally {
28066
+ setBusy(false);
28067
+ }
28068
+ }
28069
+ const create2 = () => run(async () => {
28070
+ const r = await onCreate();
28071
+ setLive({ token: r.token, url: r.url, deep_link: r.deep_link, expires_at: r.expires_at ?? null });
28072
+ });
28073
+ const applyExpiry = () => run(async () => {
28074
+ if (!live) return;
28075
+ const r = await restrictShare(live.token, expiryDays);
28076
+ setLive({ ...live, expires_at: r.expires_at ?? null });
28077
+ });
28078
+ const revoke = () => run(async () => {
28079
+ if (!live) return;
28080
+ await revokeShare(live.token);
28081
+ setLive(null);
28082
+ });
28083
+ const copy2 = async () => {
28084
+ if (!live) return;
28085
+ try {
28086
+ await navigator.clipboard.writeText(live.url);
28087
+ setCopied(true);
28088
+ setTimeout(() => setCopied(false), 2e3);
28089
+ } catch {
28090
+ }
28091
+ };
28092
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
28093
+ Modal,
28094
+ {
28095
+ onClose,
28096
+ title: t("Share: :name", { name: title }),
28097
+ overlayClassName: "bg-black/50 z-[300] flex items-center justify-center",
28098
+ panelClassName: "bg-surface-900 border border-surface-700 rounded-lg shadow-2xl w-[32rem] max-w-[92vw] flex flex-col",
28099
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-5 flex flex-col gap-4", children: [
28100
+ 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 }),
28101
+ loading ? /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-surface-400", children: t("Checking share status…") }) : !live ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
28102
+ /* @__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.") }),
28103
+ /* @__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.") }),
28104
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
28105
+ "button",
28106
+ {
28107
+ onClick: create2,
28108
+ disabled: busy,
28109
+ 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",
28110
+ children: busy ? t("Creating…") : t("Create public link")
28111
+ }
28112
+ ) })
28113
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
28114
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
28115
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[11px] uppercase tracking-wide text-surface-500", children: t("Public link") }),
28116
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 flex items-center gap-2", children: [
28117
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
28118
+ "input",
28119
+ {
28120
+ readOnly: true,
28121
+ value: live.url,
28122
+ onClick: (e) => e.target.select(),
28123
+ className: "flex-1 rounded bg-surface-950 border border-surface-700 px-2 py-1.5 text-xs font-mono text-surface-200"
28124
+ }
28125
+ ),
28126
+ /* @__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") })
28127
+ ] })
28128
+ ] }),
28129
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-end gap-2", children: [
28130
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
28131
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[11px] uppercase tracking-wide text-surface-500", children: t("Restrict") }),
28132
+ /* @__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") })
28133
+ ] }),
28134
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
28135
+ "select",
28136
+ {
28137
+ value: expiryDays,
28138
+ onChange: (e) => setExpiryDays(Number(e.target.value)),
28139
+ className: "rounded bg-surface-950 border border-surface-700 px-2 py-1.5 text-xs text-surface-200",
28140
+ children: [
28141
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 0, children: t("No expiry") }),
28142
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 1, children: t("1 day") }),
28143
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 7, children: t("7 days") }),
28144
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 30, children: t("30 days") })
28145
+ ]
28146
+ }
28147
+ ),
28148
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
28149
+ "button",
28150
+ {
28151
+ onClick: applyExpiry,
28152
+ disabled: busy,
28153
+ className: "px-3 py-1.5 text-xs border border-surface-700 rounded text-surface-300 hover:text-white disabled:opacity-50 transition-colors",
28154
+ children: t("Apply")
28155
+ }
28156
+ )
28157
+ ] }),
28158
+ /* @__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.") }),
28159
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center pt-2 border-t border-surface-800", children: [
28160
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
28161
+ "button",
28162
+ {
28163
+ onClick: revoke,
28164
+ disabled: busy,
28165
+ className: "px-3 py-1.5 text-xs text-red-400 hover:text-red-300 disabled:opacity-50 transition-colors",
28166
+ children: t("Revoke link")
28167
+ }
28168
+ ),
28169
+ /* @__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") })
28170
+ ] })
28171
+ ] })
28172
+ ] })
28173
+ }
28174
+ );
28175
+ }
27929
28176
  function requestPath(url) {
27930
28177
  const trimmed = url.trim().replace(/^\{\{[^}]+\}\}/, "").replace(/^https?:\/\/[^/]+/i, "");
27931
28178
  return trimmed || url.trim();
@@ -27995,6 +28242,9 @@ function RequestRow({
27995
28242
  const groupIndent = indent + 14;
27996
28243
  const { toast, show } = useToast();
27997
28244
  const addSecurityTests = useStore$1((s) => s.addSecurityTests);
28245
+ const openWorkspaceSettings = useStore$1((s) => s.openWorkspaceSettings);
28246
+ const [showCloudRequired, setShowCloudRequired] = reactExports.useState(false);
28247
+ const [showShare, setShowShare] = reactExports.useState(false);
27998
28248
  function handleRowClick(e) {
27999
28249
  if (e.metaKey || e.ctrlKey) {
28000
28250
  e.preventDefault();
@@ -28014,6 +28264,13 @@ function RequestRow({
28014
28264
  show(e.message, false);
28015
28265
  }
28016
28266
  }
28267
+ function openShare() {
28268
+ if (!cloudEnabled()) {
28269
+ setShowCloudRequired(true);
28270
+ return;
28271
+ }
28272
+ setShowShare(true);
28273
+ }
28017
28274
  const hookMenuItems = ["beforeAll", "before", "after", "afterAll"].map((ht) => ({
28018
28275
  type: "item",
28019
28276
  label: (hookType === ht ? "✓ " : " ") + t(HOOK_LABELS[ht]),
@@ -28154,6 +28411,7 @@ function RequestRow({
28154
28411
  show(n2 > 0 ? t("Added :count OWASP security tests", { count: n2 }) : t("No security tests generated for this request"), n2 > 0);
28155
28412
  } },
28156
28413
  ...cloudEnabled() ? [{ type: "item", label: t("Push as monitor to cloud"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: pushMonitor }] : [],
28414
+ { type: "item", label: t("Share request (public link)"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}), onClick: openShare },
28157
28415
  { type: "item", label: disabled ? t("Enable") : t("Disable"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(BanIcon, {}), onClick: onToggleDisabled },
28158
28416
  { type: "separator" },
28159
28417
  { type: "header", label: t("Hook type") },
@@ -28161,7 +28419,32 @@ function RequestRow({
28161
28419
  { type: "separator" },
28162
28420
  { type: "item", label: t("Delete"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TrashIcon, {}), danger: true, onClick: onDelete }
28163
28421
  ] }) }),
28164
- toast && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed bottom-4 right-4 z-[100] w-96", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Toast, { toast }) })
28422
+ toast && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed bottom-4 right-4 z-[100] w-96", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Toast, { toast }) }),
28423
+ showCloudRequired && /* @__PURE__ */ jsxRuntimeExports.jsx(
28424
+ CloudRequiredModal,
28425
+ {
28426
+ action: t("share this request"),
28427
+ onConnect: () => {
28428
+ setShowCloudRequired(false);
28429
+ openWorkspaceSettings("cloud");
28430
+ },
28431
+ onClose: () => setShowCloudRequired(false)
28432
+ }
28433
+ ),
28434
+ showShare && /* @__PURE__ */ jsxRuntimeExports.jsx(
28435
+ ShareModal,
28436
+ {
28437
+ title: useStore$1.getState().collections[collectionId]?.data.requests[reqId]?.name ?? "",
28438
+ type: "request",
28439
+ sourceId: reqId,
28440
+ onCreate: () => {
28441
+ const r = useStore$1.getState().collections[collectionId]?.data.requests[reqId];
28442
+ if (!r) throw new Error(t("Request not found"));
28443
+ return shareRequestToCloud(r);
28444
+ },
28445
+ onClose: () => setShowShare(false)
28446
+ }
28447
+ )
28165
28448
  ]
28166
28449
  }
28167
28450
  ),
@@ -28822,6 +29105,16 @@ function CollectionNode({
28822
29105
  const [expandCtrl, setExpandCtrl] = reactExports.useState({ value: true, seq: 0 });
28823
29106
  const [dropOver, setDropOver] = reactExports.useState(false);
28824
29107
  const dragCtx = reactExports.useContext(DragCtx);
29108
+ const openWorkspaceSettings = useStore$1((s) => s.openWorkspaceSettings);
29109
+ const [showCloudRequired, setShowCloudRequired] = reactExports.useState(false);
29110
+ const [showShare, setShowShare] = reactExports.useState(false);
29111
+ function openShare() {
29112
+ if (!cloudEnabled()) {
29113
+ setShowCloudRequired(true);
29114
+ return;
29115
+ }
29116
+ setShowShare(true);
29117
+ }
28825
29118
  function expandAll() {
28826
29119
  setExpandCtrl((c) => ({ value: true, seq: c.seq + 1, ids: null }));
28827
29120
  }
@@ -28903,6 +29196,7 @@ function CollectionNode({
28903
29196
  { type: "item", label: t("Settings"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(GearIcon, {}), onClick: () => setShowSettings(true) },
28904
29197
  { type: "item", label: t("Sync schemas"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: () => setShowSchemaSync(true) },
28905
29198
  ...cloudEnabled() ? [{ type: "item", label: t("Push contract to cloud"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: () => setShowPushContract(true) }] : [],
29199
+ { type: "item", label: t("Share collection (public link)"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}), onClick: openShare },
28906
29200
  { type: "item", label: col.disabled ? t("Enable") : t("Disable"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(BanIcon, {}), onClick: onToggleCollectionDisabled },
28907
29201
  { type: "separator" },
28908
29202
  { type: "item", label: t("Delete collection"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TrashIcon, {}), danger: true, onClick: onDeleteCollection }
@@ -28947,7 +29241,28 @@ function CollectionNode({
28947
29241
  }
28948
29242
  ),
28949
29243
  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) })
29244
+ showSchemaSync && /* @__PURE__ */ jsxRuntimeExports.jsx(SchemaSyncModal, { collectionId: col.id, scope: { type: "collection" }, onClose: () => setShowSchemaSync(false) }),
29245
+ showCloudRequired && /* @__PURE__ */ jsxRuntimeExports.jsx(
29246
+ CloudRequiredModal,
29247
+ {
29248
+ action: t("share this collection"),
29249
+ onConnect: () => {
29250
+ setShowCloudRequired(false);
29251
+ openWorkspaceSettings("cloud");
29252
+ },
29253
+ onClose: () => setShowCloudRequired(false)
29254
+ }
29255
+ ),
29256
+ showShare && /* @__PURE__ */ jsxRuntimeExports.jsx(
29257
+ ShareModal,
29258
+ {
29259
+ title: col.name,
29260
+ type: "collection",
29261
+ sourceId: col.id,
29262
+ onCreate: () => shareCollectionToCloud(col),
29263
+ onClose: () => setShowShare(false)
29264
+ }
29265
+ )
28951
29266
  ] });
28952
29267
  }
28953
29268
  function FolderRow({
@@ -28991,6 +29306,16 @@ function FolderRow({
28991
29306
  }));
28992
29307
  const [dropInside, setDropInside] = reactExports.useState(false);
28993
29308
  const dragCtx = reactExports.useContext(DragCtx);
29309
+ const openWorkspaceSettings = useStore$1((s) => s.openWorkspaceSettings);
29310
+ const [showCloudRequired, setShowCloudRequired] = reactExports.useState(false);
29311
+ const [showShare, setShowShare] = reactExports.useState(false);
29312
+ function openShare() {
29313
+ if (!cloudEnabled()) {
29314
+ setShowCloudRequired(true);
29315
+ return;
29316
+ }
29317
+ setShowShare(true);
29318
+ }
28994
29319
  const tags = folder2.tags ?? [];
28995
29320
  const indent = indentPx(depth);
28996
29321
  const hasInheritedConfig = folder2.auth && folder2.auth.type !== "none" || folder2.headers && folder2.headers.length > 0;
@@ -29104,6 +29429,7 @@ function FolderRow({
29104
29429
  { type: "item", label: t("Settings"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(GearIcon, {}), onClick: () => setShowSettings(true) },
29105
29430
  { type: "item", label: t("Sync schemas"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: () => setShowSchemaSync(true) },
29106
29431
  ...cloudEnabled() ? [{ type: "item", label: t("Push contract to cloud"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: () => setShowPushContract(true) }] : [],
29432
+ { type: "item", label: t("Share folder (public link)"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}), onClick: openShare },
29107
29433
  { type: "item", label: folder2.disabled ? t("Enable") : t("Disable"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(BanIcon, {}), onClick: onToggleDisabled },
29108
29434
  { type: "separator" },
29109
29435
  { type: "header", label: t("Run folder as hook") },
@@ -29138,6 +29464,27 @@ function FolderRow({
29138
29464
  scope: { type: "folder", folderId: folder2.id },
29139
29465
  onClose: () => setShowSchemaSync(false)
29140
29466
  }
29467
+ ),
29468
+ showCloudRequired && /* @__PURE__ */ jsxRuntimeExports.jsx(
29469
+ CloudRequiredModal,
29470
+ {
29471
+ action: t("share this folder"),
29472
+ onConnect: () => {
29473
+ setShowCloudRequired(false);
29474
+ openWorkspaceSettings("cloud");
29475
+ },
29476
+ onClose: () => setShowCloudRequired(false)
29477
+ }
29478
+ ),
29479
+ showShare && folderCollection && /* @__PURE__ */ jsxRuntimeExports.jsx(
29480
+ ShareModal,
29481
+ {
29482
+ title: folder2.name,
29483
+ type: "folder",
29484
+ sourceId: folder2.id,
29485
+ onCreate: () => shareFolderToCloud(folderCollection, folder2),
29486
+ onClose: () => setShowShare(false)
29487
+ }
29141
29488
  )
29142
29489
  ] });
29143
29490
  }
@@ -29591,8 +29938,8 @@ function jsonWithComments() {
29591
29938
  function xmlWithComments() {
29592
29939
  return [xml(), commentKeymap];
29593
29940
  }
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 })));
29941
+ const GraphQLEditor = reactExports.lazy(() => __vitePreload(() => import("./GraphQLEditor-J-8g0S0P.js"), true ? __vite__mapDeps([0,1,2]) : void 0, import.meta.url).then((m) => ({ default: m.GraphQLEditor })));
29942
+ const SoapEditor = reactExports.lazy(() => __vitePreload(() => import("./SoapEditor-CmsqdXhh.js"), true ? __vite__mapDeps([3,1,2]) : void 0, import.meta.url).then((m) => ({ default: m.SoapEditor })));
29596
29943
  function BodyTab({ request: request2, onChange }) {
29597
29944
  const t = useT();
29598
29945
  const editorFallback = /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 text-xs text-surface-500", children: t("Loading editor…") });
@@ -29690,7 +30037,7 @@ function BodyTab({ request: request2, onChange }) {
29690
30037
  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
30038
  ] });
29692
30039
  }
29693
- const { electron: electron$D } = window;
30040
+ const { electron: electron$E } = window;
29694
30041
  function AuthTab({ request: request2, onChange }) {
29695
30042
  const auth = request2.auth;
29696
30043
  const [secretValue, setSecretValue] = reactExports.useState("");
@@ -29700,7 +30047,7 @@ function AuthTab({ request: request2, onChange }) {
29700
30047
  }
29701
30048
  async function saveSecret(ref) {
29702
30049
  if (!secretValue || !ref) return;
29703
- await electron$D.setSecret(ref, secretValue);
30050
+ await electron$E.setSecret(ref, secretValue);
29704
30051
  setSaved(true);
29705
30052
  setSecretValue("");
29706
30053
  setTimeout(() => setSaved(false), 2e3);
@@ -30060,7 +30407,7 @@ function validateBodyAgainstSchema(schema, body) {
30060
30407
  return { status: "error", message: e instanceof Error ? e.message : String(e) };
30061
30408
  }
30062
30409
  }
30063
- const { electron: electron$C } = window;
30410
+ const { electron: electron$D } = window;
30064
30411
  function SchemaTab({ request: request2, onChange }) {
30065
30412
  const t = useT();
30066
30413
  const activeTab = useStore$1((s) => s.tabs.find((tab) => tab.id === s.activeTabId));
@@ -30084,7 +30431,7 @@ function SchemaTab({ request: request2, onChange }) {
30084
30431
  if (!lastResponse?.body) return;
30085
30432
  setInferring(true);
30086
30433
  try {
30087
- const schema = await electron$C.inferContractSchema(lastResponse.body);
30434
+ const schema = await electron$D.inferContractSchema(lastResponse.body);
30088
30435
  if (schema) setSchema(schema);
30089
30436
  } finally {
30090
30437
  setInferring(false);
@@ -39514,7 +39861,7 @@ function DocsEditor({ value: value2, onChange, placeholder, onGenerate, aiAvaila
39514
39861
  }
39515
39862
  const AI_OPENAI_TOKEN_REF = "ai:openai:token";
39516
39863
  const DEFAULT_AI_MODEL = "gpt-4o-mini";
39517
- const { electron: electron$B } = window;
39864
+ const { electron: electron$C } = window;
39518
39865
  const CFG_KEY = STORAGE_KEYS.ai;
39519
39866
  function getAiConfig() {
39520
39867
  try {
@@ -39536,7 +39883,7 @@ function invalidateAiAvailable() {
39536
39883
  async function checkAvailable() {
39537
39884
  if (availableCache != null) return availableCache;
39538
39885
  try {
39539
- availableCache = (await electron$B.hasSecret(AI_OPENAI_TOKEN_REF)).has;
39886
+ availableCache = (await electron$C.hasSecret(AI_OPENAI_TOKEN_REF)).has;
39540
39887
  } catch {
39541
39888
  availableCache = false;
39542
39889
  }
@@ -39556,7 +39903,7 @@ function useAiAvailable() {
39556
39903
  return available;
39557
39904
  }
39558
39905
  async function generateDocs(input) {
39559
- return electron$B.generateAiDocs({ ...input, model: getAiConfig().model });
39906
+ return electron$C.generateAiDocs({ ...input, model: getAiConfig().model });
39560
39907
  }
39561
39908
  function kvLines(label, rows) {
39562
39909
  const on = (rows ?? []).filter((r) => r.enabled !== false && r.key);
@@ -40084,7 +40431,7 @@ function RealtimeTestSection({
40084
40431
  ] })
40085
40432
  ] });
40086
40433
  }
40087
- const { electron: electron$A } = window;
40434
+ const { electron: electron$B } = window;
40088
40435
  function formatTime$4(ts) {
40089
40436
  const d = new Date(ts);
40090
40437
  const hh = String(d.getHours()).padStart(2, "0");
@@ -40114,7 +40461,7 @@ function WebSocketPanel({ request: request2, onChange }) {
40114
40461
  const url = interp(request2.url).trim();
40115
40462
  const headers = {};
40116
40463
  for (const h of request2.headers) if (h.enabled && h.key) headers[interp(h.key)] = interp(h.value);
40117
- return electron$A.wsTest({
40464
+ return electron$B.wsTest({
40118
40465
  url,
40119
40466
  headers,
40120
40467
  sendMessage: cfg.sendMessage ? interp(cfg.sendMessage) : void 0,
@@ -40124,14 +40471,14 @@ function WebSocketPanel({ request: request2, onChange }) {
40124
40471
  });
40125
40472
  });
40126
40473
  reactExports.useEffect(() => {
40127
- electron$A.onWsMessage(({ requestId, message }) => {
40474
+ electron$B.onWsMessage(({ requestId, message }) => {
40128
40475
  addWsMessage(requestId, message);
40129
40476
  });
40130
- electron$A.onWsStatus(({ requestId, status, error: error2 }) => {
40477
+ electron$B.onWsStatus(({ requestId, status, error: error2 }) => {
40131
40478
  setWsStatus(requestId, status, error2);
40132
40479
  });
40133
40480
  return () => {
40134
- electron$A.offWsEvents();
40481
+ electron$B.offWsEvents();
40135
40482
  };
40136
40483
  }, [addWsMessage, setWsStatus]);
40137
40484
  reactExports.useEffect(() => {
@@ -40144,19 +40491,19 @@ function WebSocketPanel({ request: request2, onChange }) {
40144
40491
  if (h.enabled && h.key) headers[h.key] = h.value;
40145
40492
  }
40146
40493
  try {
40147
- await electron$A.wsConnect(request2.id, request2.url, headers);
40494
+ await electron$B.wsConnect(request2.id, request2.url, headers);
40148
40495
  } catch (err) {
40149
40496
  setWsStatus(request2.id, "error", err instanceof Error ? err.message : String(err));
40150
40497
  }
40151
40498
  }
40152
40499
  async function disconnect() {
40153
- await electron$A.wsDisconnect(request2.id);
40500
+ await electron$B.wsDisconnect(request2.id);
40154
40501
  }
40155
40502
  async function sendMessage() {
40156
40503
  const text = sendText.trim();
40157
40504
  if (!text || !isConnected) return;
40158
40505
  try {
40159
- await electron$A.wsSend(request2.id, text);
40506
+ await electron$B.wsSend(request2.id, text);
40160
40507
  const msg = {
40161
40508
  id: crypto.randomUUID(),
40162
40509
  direction: "sent",
@@ -40308,7 +40655,7 @@ function WebSocketPanel({ request: request2, onChange }) {
40308
40655
  ] })
40309
40656
  ] });
40310
40657
  }
40311
- const { electron: electron$z } = window;
40658
+ const { electron: electron$A } = window;
40312
40659
  function formatTime$3(ts) {
40313
40660
  const d = new Date(ts);
40314
40661
  const p = (n2, w = 2) => String(n2).padStart(w, "0");
@@ -40342,7 +40689,7 @@ function SocketIoPanel({ request: request2, onChange }) {
40342
40689
  const url = interp(request2.url).trim();
40343
40690
  const headers = {};
40344
40691
  for (const h of request2.headers) if (h.enabled && h.key) headers[interp(h.key)] = interp(h.value);
40345
- return electron$z.socketIoTest({
40692
+ return electron$A.socketIoTest({
40346
40693
  url,
40347
40694
  namespace: namespace2,
40348
40695
  auth: auth ? interp(auth) : void 0,
@@ -40361,19 +40708,19 @@ function SocketIoPanel({ request: request2, onChange }) {
40361
40708
  if (h.enabled && h.key) headers[h.key] = h.value;
40362
40709
  }
40363
40710
  try {
40364
- await electron$z.socketIoConnect(request2.id, { url: request2.url, namespace: namespace2, auth, headers });
40711
+ await electron$A.socketIoConnect(request2.id, { url: request2.url, namespace: namespace2, auth, headers });
40365
40712
  } catch (err) {
40366
40713
  setStatus(request2.id, "error", err instanceof Error ? err.message : String(err));
40367
40714
  }
40368
40715
  }
40369
40716
  async function disconnect() {
40370
- await electron$z.socketIoDisconnect(request2.id);
40717
+ await electron$A.socketIoDisconnect(request2.id);
40371
40718
  }
40372
40719
  async function emit() {
40373
40720
  const event = eventName2.trim();
40374
40721
  if (!event || !isConnected) return;
40375
40722
  try {
40376
- await electron$z.socketIoEmit(request2.id, { event, args: argsText, withAck });
40723
+ await electron$A.socketIoEmit(request2.id, { event, args: argsText, withAck });
40377
40724
  const msg = {
40378
40725
  id: crypto.randomUUID(),
40379
40726
  direction: "sent",
@@ -40587,7 +40934,7 @@ function SocketIoPanel({ request: request2, onChange }) {
40587
40934
  ] })
40588
40935
  ] });
40589
40936
  }
40590
- const { electron: electron$y } = window;
40937
+ const { electron: electron$z } = window;
40591
40938
  function formatTime$2(ts) {
40592
40939
  const d = new Date(ts);
40593
40940
  const p = (n2, w = 2) => String(n2).padStart(w, "0");
@@ -40627,7 +40974,7 @@ function MqttPanel({ request: request2, onChange }) {
40627
40974
  return;
40628
40975
  }
40629
40976
  try {
40630
- await electron$y.mqttConnect(request2.id, {
40977
+ await electron$z.mqttConnect(request2.id, {
40631
40978
  url,
40632
40979
  clientId: cfg.clientId ? interp(cfg.clientId) : void 0,
40633
40980
  username: cfg.username ? interp(cfg.username) : void 0,
@@ -40641,13 +40988,13 @@ function MqttPanel({ request: request2, onChange }) {
40641
40988
  }
40642
40989
  }
40643
40990
  async function disconnect() {
40644
- await electron$y.mqttDisconnect(request2.id);
40991
+ await electron$z.mqttDisconnect(request2.id);
40645
40992
  }
40646
40993
  async function subscribe() {
40647
40994
  const topic = interp(cfg.subscribeTopic ?? "").trim();
40648
40995
  if (!topic || !isConnected) return;
40649
40996
  try {
40650
- await electron$y.mqttSubscribe(request2.id, { topic, qos });
40997
+ await electron$z.mqttSubscribe(request2.id, { topic, qos });
40651
40998
  } catch (err) {
40652
40999
  setStatus(request2.id, "error", err instanceof Error ? err.message : String(err));
40653
41000
  }
@@ -40655,7 +41002,7 @@ function MqttPanel({ request: request2, onChange }) {
40655
41002
  async function unsubscribe() {
40656
41003
  const topic = interp(cfg.subscribeTopic ?? "").trim();
40657
41004
  if (!topic || !isConnected) return;
40658
- await electron$y.mqttUnsubscribe(request2.id, { topic });
41005
+ await electron$z.mqttUnsubscribe(request2.id, { topic });
40659
41006
  }
40660
41007
  async function publish() {
40661
41008
  const topic = interp(cfg.publishTopic ?? "").trim();
@@ -40667,7 +41014,7 @@ function MqttPanel({ request: request2, onChange }) {
40667
41014
  setPublishError("");
40668
41015
  const body = interp(payload);
40669
41016
  try {
40670
- await electron$y.mqttPublish(request2.id, { topic, payload: body, qos, retain: Boolean(cfg.retain) });
41017
+ await electron$z.mqttPublish(request2.id, { topic, payload: body, qos, retain: Boolean(cfg.retain) });
40671
41018
  addMessage(request2.id, {
40672
41019
  id: crypto.randomUUID(),
40673
41020
  direction: "sent",
@@ -40691,7 +41038,7 @@ function MqttPanel({ request: request2, onChange }) {
40691
41038
  setTestRemaining(Math.ceil(collectMs / 1e3));
40692
41039
  const countdown = setInterval(() => setTestRemaining((r) => r && r > 1 ? r - 1 : 0), 1e3);
40693
41040
  try {
40694
- const res = await electron$y.mqttTest({
41041
+ const res = await electron$z.mqttTest({
40695
41042
  url,
40696
41043
  // The test opens its own connection; never reuse the panel's Client ID,
40697
41044
  // or the broker would kick whichever connected first (a clientId clash).
@@ -41040,7 +41387,7 @@ function MqttPanel({ request: request2, onChange }) {
41040
41387
  ] })
41041
41388
  ] });
41042
41389
  }
41043
- const { electron: electron$x } = window;
41390
+ const { electron: electron$y } = window;
41044
41391
  function formatTime$1(ts) {
41045
41392
  const d = new Date(ts);
41046
41393
  const p = (n2, w = 2) => String(n2).padStart(w, "0");
@@ -41076,13 +41423,13 @@ function GrpcPanel({ request: request2, onChange }) {
41076
41423
  onChange({ body: { ...request2.body, mode: "grpc", grpc: { ...grpc, ...changes } } });
41077
41424
  }
41078
41425
  reactExports.useEffect(() => {
41079
- electron$x.onGrpcMessage(({ requestId, message }) => {
41426
+ electron$y.onGrpcMessage(({ requestId, message }) => {
41080
41427
  addGrpcMessage(requestId, message);
41081
41428
  });
41082
- electron$x.onGrpcStatus(({ requestId, status, code, codeName, error: error2 }) => {
41429
+ electron$y.onGrpcStatus(({ requestId, status, code, codeName, error: error2 }) => {
41083
41430
  setGrpcStatus(requestId, status, { code, codeName, error: error2 });
41084
41431
  });
41085
- return () => electron$x.offGrpcEvents();
41432
+ return () => electron$y.offGrpcEvents();
41086
41433
  }, [addGrpcMessage, setGrpcStatus]);
41087
41434
  reactExports.useEffect(() => {
41088
41435
  logEndRef.current?.scrollIntoView({ behavior: "smooth" });
@@ -41095,7 +41442,7 @@ function GrpcPanel({ request: request2, onChange }) {
41095
41442
  if (!grpc.protoSource?.trim() && !grpc.protoPath?.trim()) return;
41096
41443
  setLoadingProto(true);
41097
41444
  try {
41098
- const result = await electron$x.grpcLoadProto({ protoSource: grpc.protoSource, protoPath: grpc.protoPath });
41445
+ const result = await electron$y.grpcLoadProto({ protoSource: grpc.protoSource, protoPath: grpc.protoPath });
41099
41446
  const services = result.services;
41100
41447
  setGrpcServices(request2.id, services);
41101
41448
  if (services.length && !services.some((s) => s.name === grpc.serviceName)) {
@@ -41114,7 +41461,7 @@ function GrpcPanel({ request: request2, onChange }) {
41114
41461
  const metadata = {};
41115
41462
  for (const m of grpc.metadata ?? []) if (m.enabled && m.key) metadata[m.key] = m.value;
41116
41463
  try {
41117
- await electron$x.grpcInvoke(request2.id, {
41464
+ await electron$y.grpcInvoke(request2.id, {
41118
41465
  target: request2.url,
41119
41466
  serviceName: grpc.serviceName,
41120
41467
  methodName: grpc.methodName,
@@ -41129,7 +41476,7 @@ function GrpcPanel({ request: request2, onChange }) {
41129
41476
  }
41130
41477
  }
41131
41478
  function cancel() {
41132
- electron$x.grpcCancel(request2.id);
41479
+ electron$y.grpcCancel(request2.id);
41133
41480
  }
41134
41481
  const statusColors = {
41135
41482
  running: "bg-amber-400",
@@ -41253,7 +41600,7 @@ function GrpcPanel({ request: request2, onChange }) {
41253
41600
  ] })
41254
41601
  ] });
41255
41602
  }
41256
- const { electron: electron$w } = window;
41603
+ const { electron: electron$x } = window;
41257
41604
  function isSubscriptionQuery(query) {
41258
41605
  return /(^|\n)\s*subscription\b/i.test(query ?? "");
41259
41606
  }
@@ -41302,7 +41649,7 @@ function GraphQlSubscription({ request: request2, gql, onPatch }) {
41302
41649
  const headers = {};
41303
41650
  for (const h of request2.headers) if (h.enabled && h.key) headers[interp(h.key)] = interp(h.value);
41304
41651
  try {
41305
- await electron$w.graphqlWsSubscribe(request2.id, {
41652
+ await electron$x.graphqlWsSubscribe(request2.id, {
41306
41653
  url,
41307
41654
  headers,
41308
41655
  query: gql.query,
@@ -41319,7 +41666,7 @@ function GraphQlSubscription({ request: request2, gql, onPatch }) {
41319
41666
  const url = deriveWsUrl(interp(rawUrl));
41320
41667
  const headers = {};
41321
41668
  for (const h of request2.headers) if (h.enabled && h.key) headers[interp(h.key)] = interp(h.value);
41322
- return electron$w.graphqlWsTest({
41669
+ return electron$x.graphqlWsTest({
41323
41670
  url,
41324
41671
  headers,
41325
41672
  query: gql.query,
@@ -41354,7 +41701,7 @@ function GraphQlSubscription({ request: request2, gql, onPatch }) {
41354
41701
  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
41702
  }
41356
41703
  ),
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") })
41704
+ 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
41705
  ] }),
41359
41706
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-shrink-0", children: [
41360
41707
  /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: () => setShowParams((v) => !v), className: "text-[10px] text-surface-600 hover:text-surface-300 transition-colors", children: [
@@ -41622,7 +41969,7 @@ function FuzzResultsPanel({ report, onClear }) {
41622
41969
  ] }) })
41623
41970
  ] });
41624
41971
  }
41625
- const { electron: electron$v } = window;
41972
+ const { electron: electron$w } = window;
41626
41973
  const WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
41627
41974
  function FuzzModal({ request: request2, onClose }) {
41628
41975
  const t = useT();
@@ -41650,7 +41997,7 @@ function FuzzModal({ request: request2, onClose }) {
41650
41997
  const env2 = resolveEnvironmentById(environments, activeEnvironmentId);
41651
41998
  const envVars = env2 ? Object.fromEntries(env2.variables.filter((v) => v.enabled).map((v) => [v.key, v.value])) : {};
41652
41999
  const collectionVars = activeCollectionId ? collections[activeCollectionId]?.data.collectionVariables ?? {} : {};
41653
- const result = await electron$v.fuzzContracts({
42000
+ const result = await electron$w.fuzzContracts({
41654
42001
  requests: [request2],
41655
42002
  envVars,
41656
42003
  collectionVars,
@@ -41867,7 +42214,7 @@ function resolveAskValues(asks, answers) {
41867
42214
  for (const a of asks) out[a.key] = answers[a.message] ?? "";
41868
42215
  return out;
41869
42216
  }
41870
- const { electron: electron$u } = window;
42217
+ const { electron: electron$v } = window;
41871
42218
  const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "QUERY"];
41872
42219
  const METHOD_COLORS = {
41873
42220
  GET: "text-emerald-400",
@@ -42013,7 +42360,7 @@ function RequestBuilder({ request: request2 }) {
42013
42360
  const headers = {};
42014
42361
  for (const h of request2.headers) if (h.enabled && h.key) headers[interp(h.key)] = interp(h.value);
42015
42362
  try {
42016
- await electron$u.graphqlWsSubscribe(request2.id, {
42363
+ await electron$v.graphqlWsSubscribe(request2.id, {
42017
42364
  url,
42018
42365
  headers,
42019
42366
  query: gql.query,
@@ -42074,7 +42421,7 @@ function RequestBuilder({ request: request2 }) {
42074
42421
  const start2 = Date.now();
42075
42422
  try {
42076
42423
  const hookEnv = withEnvOverrides(activeEnv, liveEnvOverrides);
42077
- const r = await electron$u.sendRequest({
42424
+ const r = await electron$v.sendRequest({
42078
42425
  ...basePayload,
42079
42426
  environment: hookEnv,
42080
42427
  request: hook,
@@ -42126,7 +42473,7 @@ function RequestBuilder({ request: request2 }) {
42126
42473
  startLiveStream(activeTabId, streamId);
42127
42474
  let result;
42128
42475
  try {
42129
- result = await electron$u.sendRequest({
42476
+ result = await electron$v.sendRequest({
42130
42477
  ...basePayload,
42131
42478
  environment: freshEnv,
42132
42479
  request: mergedRequest,
@@ -42163,7 +42510,7 @@ function RequestBuilder({ request: request2 }) {
42163
42510
  const start2 = Date.now();
42164
42511
  try {
42165
42512
  const hookEnv = withEnvOverrides(activeEnv, liveEnvOverrides);
42166
- const r = await electron$u.sendRequest({
42513
+ const r = await electron$v.sendRequest({
42167
42514
  ...basePayload,
42168
42515
  environment: hookEnv,
42169
42516
  request: hook,
@@ -43346,7 +43693,7 @@ function Cell({ value: value2, onDrill }) {
43346
43693
  const text = String(value2);
43347
43694
  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
43695
  }
43349
- const { electron: electron$t } = window;
43696
+ const { electron: electron$u } = window;
43350
43697
  const RENDER_TAIL = 500;
43351
43698
  const CLOSE_LABEL = {
43352
43699
  complete: "closed",
@@ -43388,7 +43735,7 @@ function StreamView({ events, streaming: streaming2, streamId, streamClose, firs
43388
43735
  if (!streamId) return;
43389
43736
  setStopping(true);
43390
43737
  try {
43391
- await electron$t.stopStream(streamId);
43738
+ await electron$u.stopStream(streamId);
43392
43739
  } catch {
43393
43740
  }
43394
43741
  }
@@ -43531,7 +43878,7 @@ function HookResultsPanel({ results }) {
43531
43878
  }) })
43532
43879
  ] });
43533
43880
  }
43534
- const { electron: electron$s } = window;
43881
+ const { electron: electron$t } = window;
43535
43882
  function extractPath(url) {
43536
43883
  try {
43537
43884
  return new URL(url).pathname || "/";
@@ -43586,14 +43933,14 @@ function SaveAsMockModal({ onClose }) {
43586
43933
  const entry = state.mocks[serverId];
43587
43934
  const updated = { ...entry.data, name: newServerName, port: Number(newServerPort), routes: [route] };
43588
43935
  updateMock(serverId, updated);
43589
- await electron$s.saveMock(entry.relPath, updated);
43936
+ await electron$t.saveMock(entry.relPath, updated);
43590
43937
  const ws = useStore$1.getState().workspace;
43591
- if (ws) await electron$s.saveWorkspace(ws);
43938
+ if (ws) await electron$t.saveWorkspace(ws);
43592
43939
  } else {
43593
43940
  const entry = useStore$1.getState().mocks[serverId];
43594
43941
  const updated = { ...entry.data, routes: [...entry.data.routes, route] };
43595
43942
  updateMock(serverId, updated);
43596
- await electron$s.saveMock(entry.relPath, updated);
43943
+ await electron$t.saveMock(entry.relPath, updated);
43597
43944
  }
43598
43945
  onClose();
43599
43946
  } finally {
@@ -44111,7 +44458,7 @@ function validateHttpSemantics(res, opts = {}) {
44111
44458
  const order = { error: 0, warning: 1, hint: 2 };
44112
44459
  return f.sort((a, b) => order[a.severity] - order[b.severity]);
44113
44460
  }
44114
- const { electron: electron$r } = window;
44461
+ const { electron: electron$s } = window;
44115
44462
  function xmlWellFormed(body) {
44116
44463
  try {
44117
44464
  const doc = new DOMParser().parseFromString(body, "application/xml");
@@ -44227,6 +44574,7 @@ function ResponseViewer() {
44227
44574
  const setTabRequestTab = useStore$1((s) => s.setTabRequestTab);
44228
44575
  const openContractDesigner = useStore$1((s) => s.openContractDesigner);
44229
44576
  const setTabScriptTab = useStore$1((s) => s.setTabScriptTab);
44577
+ const setTabJqExpr = useStore$1((s) => s.setTabJqExpr);
44230
44578
  const isSending = activeTab?.isSending ?? false;
44231
44579
  const liveStream = useStore$1((s) => s.liveStream);
44232
44580
  const streamForTab = liveStream?.tabId === activeTabId ? liveStream : null;
@@ -44306,7 +44654,7 @@ function ResponseViewer() {
44306
44654
  const [diffMode, setDiffMode] = reactExports.useState(false);
44307
44655
  const [showMockModal, setShowMockModal] = reactExports.useState(false);
44308
44656
  const [bodyView, setBodyView] = reactExports.useState("raw");
44309
- const [jqExpr, setJqExpr] = reactExports.useState("");
44657
+ const jqExpr = activeTab?.jqExpr ?? "";
44310
44658
  const [jqResult, setJqResult] = reactExports.useState(null);
44311
44659
  const [jqError, setJqError] = reactExports.useState(null);
44312
44660
  reactExports.useEffect(() => {
@@ -44341,7 +44689,7 @@ function ResponseViewer() {
44341
44689
  const schemaCheck = currentRequest?.schema?.trim() && response && !response.error ? validateBodyAgainstSchema(currentRequest.schema, response.body) : null;
44342
44690
  async function saveAsSchema() {
44343
44691
  if (!response || !requestId || !activeTabId) return;
44344
- const schema = response.body ? await electron$r.inferContractSchema(response.body) : null;
44692
+ const schema = response.body ? await electron$s.inferContractSchema(response.body) : null;
44345
44693
  if (!schema) return;
44346
44694
  updateRequest(requestId, { schema });
44347
44695
  setTabRequestTab(activeTabId, "schema");
@@ -44548,7 +44896,9 @@ function ResponseViewer() {
44548
44896
  "input",
44549
44897
  {
44550
44898
  value: jqExpr,
44551
- onChange: (e) => setJqExpr(e.target.value),
44899
+ onChange: (e) => {
44900
+ if (activeTabId) setTabJqExpr(activeTabId, e.target.value);
44901
+ },
44552
44902
  spellCheck: false,
44553
44903
  placeholder: t("jq filter, e.g. .data | map(.id)"),
44554
44904
  title: t("Filter this JSON response with a jq expression"),
@@ -44929,7 +45279,7 @@ function listFolders(root2, depth = 0, out = []) {
44929
45279
  }
44930
45280
  return out;
44931
45281
  }
44932
- const { electron: electron$q } = window;
45282
+ const { electron: electron$r } = window;
44933
45283
  function fmtMs(ms) {
44934
45284
  if (ms < 1) return "<1 ms";
44935
45285
  if (ms < 1e3) return `${Math.round(ms)} ms`;
@@ -45120,11 +45470,11 @@ function PerformancePanel() {
45120
45470
  setError("");
45121
45471
  }, [activeId]);
45122
45472
  reactExports.useEffect(() => {
45123
- electron$q.onPerfProgress((p) => {
45473
+ electron$r.onPerfProgress((p) => {
45124
45474
  setProgress(p);
45125
45475
  setSamples((s) => [...s, p].slice(-240));
45126
45476
  });
45127
- return () => electron$q.offPerfEvents();
45477
+ return () => electron$r.offPerfEvents();
45128
45478
  }, []);
45129
45479
  const requestOptions = reactExports.useMemo(() => {
45130
45480
  const out = [];
@@ -45171,7 +45521,7 @@ function PerformancePanel() {
45171
45521
  if (saveTimer2.current) clearTimeout(saveTimer2.current);
45172
45522
  saveTimer2.current = setTimeout(() => {
45173
45523
  const ws = useStore$1.getState().workspace;
45174
- if (ws) void electron$q.saveWorkspace(ws);
45524
+ if (ws) void electron$r.saveWorkspace(ws);
45175
45525
  }, 500);
45176
45526
  }
45177
45527
  async function run() {
@@ -45196,7 +45546,7 @@ function PerformancePanel() {
45196
45546
  collections: Object.values(collections).map((c) => c.data),
45197
45547
  ...common
45198
45548
  };
45199
- setResult(await electron$q.perfRun(payload));
45549
+ setResult(await electron$r.perfRun(payload));
45200
45550
  } catch (err) {
45201
45551
  setError(err instanceof Error ? err.message : String(err));
45202
45552
  } finally {
@@ -45205,14 +45555,14 @@ function PerformancePanel() {
45205
45555
  }
45206
45556
  }
45207
45557
  async function stop() {
45208
- await electron$q.perfStop();
45558
+ await electron$r.perfStop();
45209
45559
  }
45210
45560
  async function exportReport() {
45211
45561
  if (!result || !plan) return;
45212
45562
  setExporting(true);
45213
45563
  try {
45214
45564
  const target = plan.target.kind === "request" ? selectedRequest?.name ?? "request" : selectedFlow?.name ?? "flow";
45215
- await electron$q.perfExportHtml(result, { planName: plan.name, target });
45565
+ await electron$r.perfExportHtml(result, { planName: plan.name, target });
45216
45566
  } catch (err) {
45217
45567
  setError(err instanceof Error ? err.message : String(err));
45218
45568
  } finally {
@@ -45647,7 +45997,7 @@ function PerformancePanel() {
45647
45997
  !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
45998
  ] });
45649
45999
  }
45650
- const { electron: electron$p } = window;
46000
+ const { electron: electron$q } = window;
45651
46001
  function makePlan(name) {
45652
46002
  return {
45653
46003
  id: crypto.randomUUID(),
@@ -45684,7 +46034,7 @@ function PerfPlansPanel() {
45684
46034
  const updated = { ...workspace, perfPlans: next };
45685
46035
  setWorkspace(updated, workspacePath ?? "");
45686
46036
  try {
45687
- await electron$p.saveWorkspace(updated);
46037
+ await electron$q.saveWorkspace(updated);
45688
46038
  } catch {
45689
46039
  }
45690
46040
  }
@@ -45828,7 +46178,7 @@ class ErrorBoundary extends React.Component {
45828
46178
  ] });
45829
46179
  }
45830
46180
  }
45831
- const { electron: electron$o } = window;
46181
+ const { electron: electron$p } = window;
45832
46182
  const TARGETS = [
45833
46183
  { id: "robot_framework", label: "Robot Framework", description: "Python RequestsLibrary keywords + test suite" },
45834
46184
  { id: "playwright_ts", label: "Playwright TS", description: "TypeScript page-object API classes + spec files" },
@@ -45870,7 +46220,7 @@ function GeneratorPanel() {
45870
46220
  try {
45871
46221
  const col = collections[selectedCollectionId]?.data;
45872
46222
  const env2 = resolveEnvironmentById(environments, activeEnvironmentId);
45873
- const generated = await electron$o.generateCode({ collection: col, environment: env2, target });
46223
+ const generated = await electron$p.generateCode({ collection: col, environment: env2, target });
45874
46224
  setFiles(generated);
45875
46225
  setSelectedFile(generated[0]?.path ?? null);
45876
46226
  } catch (e) {
@@ -45882,7 +46232,7 @@ function GeneratorPanel() {
45882
46232
  async function saveZip() {
45883
46233
  if (files.length === 0) return;
45884
46234
  const col = collections[selectedCollectionId]?.data;
45885
- await electron$o.saveGeneratedFilesAsZip(files, col?.name ?? "api-tests", target);
46235
+ await electron$p.saveGeneratedFilesAsZip(files, col?.name ?? "api-tests", target);
45886
46236
  }
45887
46237
  const selectedContent = files.find((f) => f.path === selectedFile)?.content ?? "";
45888
46238
  const activeTarget = TARGETS.find((tgt) => tgt.id === target);
@@ -46066,7 +46416,7 @@ function historyToHar(entries, creatorVersion = "1.0") {
46066
46416
  }
46067
46417
  }, null, 2);
46068
46418
  }
46069
- const { electron: electron$n } = window;
46419
+ const { electron: electron$o } = window;
46070
46420
  const STATUS_COLOR = {
46071
46421
  "2": "text-emerald-400",
46072
46422
  "3": "text-amber-400",
@@ -46120,7 +46470,7 @@ function HistoryPanel() {
46120
46470
  }, [history, search]);
46121
46471
  async function downloadHar() {
46122
46472
  const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replaceAll(/[:T]/g, "-");
46123
- await electron$n.saveResults(historyToHar(history), `api-spector-history-${stamp}.har`);
46473
+ await electron$o.saveResults(historyToHar(history), `api-spector-history-${stamp}.har`);
46124
46474
  }
46125
46475
  function open2(entry) {
46126
46476
  setSelected(entry);
@@ -46248,7 +46598,7 @@ function HistoryRow({
46248
46598
  }
46249
46599
  );
46250
46600
  }
46251
- const { electron: electron$m } = window;
46601
+ const { electron: electron$n } = window;
46252
46602
  function WelcomeScreen() {
46253
46603
  const t = useT();
46254
46604
  const { applyWorkspace } = useWorkspaceLoader();
@@ -46261,10 +46611,10 @@ function WelcomeScreen() {
46261
46611
  const busyRef = reactExports.useRef(false);
46262
46612
  const [busy, setBusy] = reactExports.useState(false);
46263
46613
  reactExports.useEffect(() => {
46264
- electron$m.getRecentWorkspaces().then(setRecents).catch(() => setRecents([]));
46265
- electron$m.getUpdateMode().then((mode) => {
46614
+ electron$n.getRecentWorkspaces().then(setRecents).catch(() => setRecents([]));
46615
+ electron$n.getUpdateMode().then((mode) => {
46266
46616
  if (mode !== "npm") return;
46267
- return electron$m.checkForUpdate().then((info) => {
46617
+ return electron$n.checkForUpdate().then((info) => {
46268
46618
  if (info?.updateAvailable) setUpdate(info);
46269
46619
  });
46270
46620
  }).catch(() => {
@@ -46275,7 +46625,7 @@ function WelcomeScreen() {
46275
46625
  busyRef.current = true;
46276
46626
  setBusy(true);
46277
46627
  try {
46278
- const result = await electron$m.openWorkspace();
46628
+ const result = await electron$n.openWorkspace();
46279
46629
  if (result) await applyWorkspace(result.workspace, result.workspacePath);
46280
46630
  } finally {
46281
46631
  busyRef.current = false;
@@ -46287,7 +46637,7 @@ function WelcomeScreen() {
46287
46637
  busyRef.current = true;
46288
46638
  setBusy(true);
46289
46639
  try {
46290
- const result = await electron$m.newWorkspace();
46640
+ const result = await electron$n.newWorkspace();
46291
46641
  if (result) await applyWorkspace(result.workspace, result.workspacePath);
46292
46642
  } finally {
46293
46643
  busyRef.current = false;
@@ -46301,7 +46651,7 @@ function WelcomeScreen() {
46301
46651
  setGitBusy(true);
46302
46652
  setGitError(null);
46303
46653
  try {
46304
- const result = await electron$m.openFromGit(url);
46654
+ const result = await electron$n.openFromGit(url);
46305
46655
  if (!result) return;
46306
46656
  await applyWorkspace(result.workspace, result.workspacePath);
46307
46657
  } catch (err) {
@@ -46316,7 +46666,7 @@ function WelcomeScreen() {
46316
46666
  busyRef.current = true;
46317
46667
  setBusy(true);
46318
46668
  try {
46319
- const result = await electron$m.openWorkspacePath(path);
46669
+ const result = await electron$n.openWorkspacePath(path);
46320
46670
  if (!result) {
46321
46671
  setRecents((prev) => prev.filter((r) => r.path !== path));
46322
46672
  return;
@@ -46352,7 +46702,7 @@ function WelcomeScreen() {
46352
46702
  )
46353
46703
  ] }),
46354
46704
  /* @__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" }) })
46705
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] mt-3", style: { color: "var(--text-muted)" }, children: t("version :version", { version: "0.9.3" }) })
46356
46706
  ] }),
46357
46707
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 w-64", children: [
46358
46708
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -46434,7 +46784,7 @@ function WelcomeScreen() {
46434
46784
  ] })
46435
46785
  ] });
46436
46786
  }
46437
- const { electron: electron$l } = window;
46787
+ const { electron: electron$m } = window;
46438
46788
  const EXAMPLES = [
46439
46789
  {
46440
46790
  label: "macOS / Linux (~/.zshrc or ~/.bashrc)",
@@ -46459,7 +46809,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
46459
46809
  setError(t("Password cannot be empty."));
46460
46810
  return;
46461
46811
  }
46462
- await electron$l.setMasterKey(password);
46812
+ await electron$m.setMasterKey(password);
46463
46813
  onSuccess(password);
46464
46814
  }
46465
46815
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
@@ -46538,7 +46888,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
46538
46888
  }
46539
46889
  );
46540
46890
  }
46541
- const { electron: electron$k } = window;
46891
+ const { electron: electron$l } = window;
46542
46892
  async function shortHash(value2) {
46543
46893
  const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value2));
46544
46894
  return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 8);
@@ -46653,12 +47003,12 @@ function EnvironmentEditor({ onClose }) {
46653
47003
  if (next) setSelectedId(next);
46654
47004
  }
46655
47005
  async function importFromPostman() {
46656
- const imported = await electron$k.importPostmanEnvironment();
47006
+ const imported = await electron$l.importPostmanEnvironment();
46657
47007
  if (!imported) return;
46658
47008
  const existingNames = Object.values(useStore$1.getState().environments).map((e) => e.data.name);
46659
47009
  const finalEnv = { ...imported, name: uniqueName$1(imported.name || t("Imported Environment"), existingNames) };
46660
47010
  const relPath = envRelPath(finalEnv.name, finalEnv.id);
46661
- await electron$k.saveEnvironment(relPath, finalEnv);
47011
+ await electron$l.saveEnvironment(relPath, finalEnv);
46662
47012
  useStore$1.setState((s) => {
46663
47013
  s.environments[finalEnv.id] = { relPath, data: finalEnv };
46664
47014
  if (!s.activeEnvironmentId) s.activeEnvironmentId = finalEnv.id;
@@ -46668,7 +47018,7 @@ function EnvironmentEditor({ onClose }) {
46668
47018
  return s;
46669
47019
  });
46670
47020
  const ws = useStore$1.getState().workspace;
46671
- if (ws) await electron$k.saveWorkspace(ws);
47021
+ if (ws) await electron$l.saveWorkspace(ws);
46672
47022
  setSelectedId(finalEnv.id);
46673
47023
  }
46674
47024
  function updateVar(idx, patch) {
@@ -46721,9 +47071,9 @@ function EnvironmentEditor({ onClose }) {
46721
47071
  async function saveEncrypted(idx) {
46722
47072
  const plaintext = secretInputs[idx] ?? "";
46723
47073
  if (!plaintext) return;
46724
- const { set: set2 } = await electron$k.checkMasterKey();
47074
+ const { set: set2 } = await electron$l.checkMasterKey();
46725
47075
  if (set2) {
46726
- applyEncrypted(idx, await electron$k.encryptWithMasterKey(plaintext));
47076
+ applyEncrypted(idx, await electron$l.encryptWithMasterKey(plaintext));
46727
47077
  return;
46728
47078
  }
46729
47079
  setPendingEncryptIdx(idx);
@@ -46758,9 +47108,9 @@ function EnvironmentEditor({ onClose }) {
46758
47108
  } : state.workspace
46759
47109
  }));
46760
47110
  const ws = useStore$1.getState().workspace;
46761
- if (ws) await electron$k.saveWorkspace(ws);
47111
+ if (ws) await electron$l.saveWorkspace(ws);
46762
47112
  }
46763
- await electron$k.saveEnvironment(newRelPath, env2);
47113
+ await electron$l.saveEnvironment(newRelPath, env2);
46764
47114
  }
46765
47115
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
46766
47116
  confirmDialog,
@@ -47125,7 +47475,7 @@ function EnvironmentEditor({ onClose }) {
47125
47475
  )
47126
47476
  ] });
47127
47477
  }
47128
- const { electron: electron$j } = window;
47478
+ const { electron: electron$k } = window;
47129
47479
  function EnvironmentBar({ inline = false }) {
47130
47480
  const t = useT();
47131
47481
  const environments = useStore$1((s) => s.environments);
@@ -47141,7 +47491,7 @@ function EnvironmentBar({ inline = false }) {
47141
47491
  if (id2) {
47142
47492
  const hasSecrets = environments[id2]?.data.variables.some((v) => v.enabled && v.secret);
47143
47493
  if (hasSecrets) {
47144
- const { set: set2 } = await electron$j.checkMasterKey();
47494
+ const { set: set2 } = await electron$k.checkMasterKey();
47145
47495
  if (!set2) {
47146
47496
  setPendingEnvId(id2);
47147
47497
  return;
@@ -47348,10 +47698,204 @@ function ProductTour() {
47348
47698
  )
47349
47699
  ] });
47350
47700
  }
47701
+ const { electron: electron$j } = window;
47702
+ function getSyncSettings() {
47703
+ return useStore$1.getState().workspace?.settings?.sync ?? { mode: "git" };
47704
+ }
47705
+ async function persistSync(patch) {
47706
+ const s = useStore$1.getState();
47707
+ const ws = s.workspace;
47708
+ if (!ws) return;
47709
+ const sync = { ...ws.settings?.sync ?? { mode: "cloud" }, ...patch };
47710
+ const settings = { ...ws.settings, sync };
47711
+ s.updateWorkspaceSettings(settings);
47712
+ await electron$j.saveWorkspace({ ...ws, settings });
47713
+ }
47714
+ async function setSyncMode(mode) {
47715
+ await persistSync({ mode });
47716
+ }
47717
+ function workspaceName(manifestName) {
47718
+ return manifestName.replace(/\.(spector|json)$/i, "") || "Workspace";
47719
+ }
47720
+ async function flushToDisk() {
47721
+ const s = useStore$1.getState();
47722
+ for (const { relPath, data, dirty } of Object.values(s.collections)) {
47723
+ if (!dirty) continue;
47724
+ await electron$j.saveCollection(relPath, data);
47725
+ s.markCollectionClean(data.id);
47726
+ }
47727
+ for (const { relPath, data } of Object.values(s.environments)) {
47728
+ await electron$j.saveEnvironment(relPath, data);
47729
+ }
47730
+ for (const { relPath, data } of Object.values(s.flows)) {
47731
+ await electron$j.saveFlow(relPath, data);
47732
+ }
47733
+ if (s.workspace) await electron$j.saveWorkspace(s.workspace);
47734
+ }
47735
+ async function pushWorkspace() {
47736
+ const sync = getSyncSettings();
47737
+ await flushToDisk();
47738
+ const snap = await electron$j.serializeWorkspace();
47739
+ const files = snap.files ?? {};
47740
+ const collections = Object.keys(files).filter((p) => p.startsWith("collections/")).length;
47741
+ const flows = Object.keys(files).filter((p) => p.startsWith("flows/")).length;
47742
+ if (!sync.cloudWorkspaceId) {
47743
+ const created = await electron$j.cloudWorkspaceCreate({ name: workspaceName(snap.manifestName), payload: snap });
47744
+ await persistSync({ mode: "cloud", cloudWorkspaceId: created.id, baseVersion: created.version });
47745
+ return { ok: true, version: created.version, collections, flows };
47746
+ }
47747
+ const res = await electron$j.cloudWorkspacePush({
47748
+ id: sync.cloudWorkspaceId,
47749
+ baseVersion: sync.baseVersion ?? 0,
47750
+ payload: snap
47751
+ });
47752
+ if (res.conflict) return { ok: false, conflict: true, currentVersion: res.currentVersion };
47753
+ await persistSync({ baseVersion: res.version });
47754
+ return { ok: true, version: res.version ?? 0, collections, flows };
47755
+ }
47756
+ async function pullWorkspace() {
47757
+ const sync = getSyncSettings();
47758
+ if (!sync.cloudWorkspaceId) {
47759
+ throw new Error("This workspace is not linked to a cloud workspace yet. Push it first, or link an existing one.");
47760
+ }
47761
+ const remote = await electron$j.cloudWorkspacePull(sync.cloudWorkspaceId);
47762
+ const payload = remote.payload ?? {};
47763
+ if (!payload.manifest || !payload.files) {
47764
+ throw new Error("The cloud workspace has no snapshot yet. Push from a populated workspace first.");
47765
+ }
47766
+ const applied = await electron$j.applyWorkspaceSnapshot({ manifest: payload.manifest, files: payload.files });
47767
+ return {
47768
+ version: remote.version,
47769
+ filesWritten: applied.filesWritten,
47770
+ collections: applied.collections,
47771
+ workspace: applied.workspace,
47772
+ workspacePath: applied.workspacePath
47773
+ };
47774
+ }
47775
+ async function markPulled(version) {
47776
+ await persistSync({ baseVersion: version });
47777
+ }
47778
+ async function listCloudWorkspaces() {
47779
+ const res = await electron$j.cloudWorkspaceList();
47780
+ return res.workspaces;
47781
+ }
47782
+ async function linkToCloudWorkspace(id2) {
47783
+ await persistSync({ mode: "cloud", cloudWorkspaceId: id2, baseVersion: 0 });
47784
+ }
47785
+ function SyncSettingsPanel() {
47786
+ const t = useT();
47787
+ const { applyWorkspace } = useWorkspaceLoader();
47788
+ const initial = getSyncSettings();
47789
+ const [mode, setMode] = reactExports.useState(initial.mode);
47790
+ const [linked, setLinked] = reactExports.useState(!!initial.cloudWorkspaceId);
47791
+ const [busy, setBusy] = reactExports.useState(false);
47792
+ const [status, setStatus] = reactExports.useState(null);
47793
+ const [remote, setRemote] = reactExports.useState(null);
47794
+ const cloudOn = cloudEnabled();
47795
+ async function changeMode(m) {
47796
+ setMode(m);
47797
+ setStatus(null);
47798
+ try {
47799
+ await setSyncMode(m);
47800
+ } catch (e) {
47801
+ setStatus({ msg: e.message, ok: false });
47802
+ }
47803
+ }
47804
+ async function run(fn) {
47805
+ setBusy(true);
47806
+ setStatus(null);
47807
+ try {
47808
+ setStatus({ msg: await fn(), ok: true });
47809
+ } catch (e) {
47810
+ setStatus({ msg: e.message, ok: false });
47811
+ } finally {
47812
+ setBusy(false);
47813
+ }
47814
+ }
47815
+ const doPush = () => run(async () => {
47816
+ const res = await pushWorkspace();
47817
+ if (!res.ok) throw new Error(t("Someone else pushed changes (cloud is at v:v). Pull first, then push again.", { v: res.currentVersion ?? "?" }));
47818
+ setLinked(true);
47819
+ 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 });
47820
+ });
47821
+ const doPull = () => run(async () => {
47822
+ const r = await pullWorkspace();
47823
+ await applyWorkspace(r.workspace, r.workspacePath);
47824
+ await markPulled(r.version);
47825
+ return t("Pulled version :v: :files file(s), :c collection(s).", { v: r.version, files: r.filesWritten, c: r.collections });
47826
+ });
47827
+ const loadRemote = () => run(async () => {
47828
+ const list = await listCloudWorkspaces();
47829
+ setRemote(list);
47830
+ return t(":n cloud workspace(s) in your organization.", { n: list.length });
47831
+ });
47832
+ const joinExisting = (id2) => run(async () => {
47833
+ await linkToCloudWorkspace(id2);
47834
+ const r = await pullWorkspace();
47835
+ await applyWorkspace(r.workspace, r.workspacePath);
47836
+ await markPulled(r.version);
47837
+ setLinked(true);
47838
+ return t("Linked and pulled: :files file(s), :c collection(s).", { files: r.filesWritten, c: r.collections });
47839
+ });
47840
+ 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";
47841
+ 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";
47842
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-5 text-sm text-surface-200", children: [
47843
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
47844
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-semibold text-[var(--text-primary)]", children: t("Workspace sync") }),
47845
+ /* @__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.") })
47846
+ ] }),
47847
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex flex-col gap-1", children: [
47848
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs uppercase tracking-wide text-surface-500", children: t("Mode") }),
47849
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
47850
+ "select",
47851
+ {
47852
+ value: mode,
47853
+ onChange: (e) => changeMode(e.target.value),
47854
+ className: "rounded bg-surface-950 border border-surface-700 px-2 py-1.5 text-sm w-56",
47855
+ children: [
47856
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "git", children: t("Git (default) — use the Git panel") }),
47857
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "cloud", children: t("Cloud — sync through API Spector Cloud") }),
47858
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "local", children: t("Local only — no sync") })
47859
+ ]
47860
+ }
47861
+ )
47862
+ ] }),
47863
+ 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.") }),
47864
+ mode === "local" && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400", children: t("No syncing. Changes stay on this machine.") }),
47865
+ 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.") }),
47866
+ mode === "cloud" && cloudOn && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3", children: [
47867
+ /* @__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.") }),
47868
+ linked ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
47869
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: btnPrimary, disabled: busy, onClick: doPush, children: t("Push") }),
47870
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: btn, disabled: busy, onClick: doPull, children: t("Pull") })
47871
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3", children: [
47872
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
47873
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: btnPrimary, disabled: busy, onClick: doPush, children: t("Create cloud workspace (push)") }),
47874
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: btn, disabled: busy, onClick: loadRemote, children: t("Join an existing one") })
47875
+ ] }),
47876
+ remote && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-surface-700 divide-y divide-surface-800", children: [
47877
+ remote.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-2 text-xs text-surface-500", children: t("No cloud workspaces yet.") }),
47878
+ remote.map((w) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [
47879
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex-1 truncate", children: [
47880
+ w.name,
47881
+ " ",
47882
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500", children: [
47883
+ "v",
47884
+ w.version
47885
+ ] })
47886
+ ] }),
47887
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: btn, disabled: busy, onClick: () => joinExisting(w.id), children: t("Join & pull") })
47888
+ ] }, w.id))
47889
+ ] })
47890
+ ] })
47891
+ ] }),
47892
+ status && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: `text-xs ${status.ok ? "text-emerald-400" : "text-red-400"}`, children: status.msg })
47893
+ ] });
47894
+ }
47351
47895
  const { electron: electron$i } = window;
47352
47896
  const DEFAULT_PII_PATTERNS = ["authorization", "password", "token", "secret", "api-key", "x-api-key"];
47353
47897
  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"];
47898
+ const SETTINGS_TABS = ["general", "appearance", "proxy", "tls", "privacy", "cloud", "sync", "ai", "secrets"];
47355
47899
  function WorkspaceSettingsModal({ onClose, initialTab }) {
47356
47900
  const workspace = useStore$1((s) => s.workspace);
47357
47901
  const updateWorkspaceSettings = useStore$1((s) => s.updateWorkspaceSettings);
@@ -47559,6 +48103,7 @@ function WorkspaceSettingsModal({ onClose, initialTab }) {
47559
48103
  { id: "privacy", label: t("Privacy") },
47560
48104
  { id: "secrets", label: t("Secrets") },
47561
48105
  { id: "cloud", label: t("Cloud") },
48106
+ { id: "sync", label: t("Sync") },
47562
48107
  { id: "ai", label: t("AI") }
47563
48108
  ];
47564
48109
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
@@ -47883,6 +48428,7 @@ function WorkspaceSettingsModal({ onClose, initialTab }) {
47883
48428
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("Reject unauthorized / self-signed certificates") })
47884
48429
  ] })
47885
48430
  ] }),
48431
+ activeTab === "sync" && /* @__PURE__ */ jsxRuntimeExports.jsx(SyncSettingsPanel, {}),
47886
48432
  activeTab === "ai" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
47887
48433
  /* @__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
48434
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
@@ -54572,6 +55118,16 @@ function ChangesTab({ status, onRefresh }) {
54572
55118
  setError(String(e));
54573
55119
  }
54574
55120
  }
55121
+ async function stash() {
55122
+ try {
55123
+ setError(null);
55124
+ await electron$4.gitStash();
55125
+ showToast(t("Changes stashed - restore them from the Branches tab"), true);
55126
+ onRefresh();
55127
+ } catch (e) {
55128
+ setError(String(e));
55129
+ }
55130
+ }
54575
55131
  async function fetch() {
54576
55132
  setFetching(true);
54577
55133
  try {
@@ -54624,6 +55180,7 @@ function ChangesTab({ status, onRefresh }) {
54624
55180
  }
54625
55181
  const allUnstaged = [...status.unstaged, ...status.untracked];
54626
55182
  const hasStaged = status.staged.length > 0;
55183
+ const isDirty = hasStaged || status.unstaged.length > 0 || status.untracked.length > 0;
54627
55184
  const needsPush = status.ahead > 0;
54628
55185
  const needsPull = status.behind > 0;
54629
55186
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0", children: [
@@ -54880,15 +55437,27 @@ function ChangesTab({ status, onRefresh }) {
54880
55437
  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
55438
  }
54882
55439
  ),
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
- )
55440
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
55441
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55442
+ "button",
55443
+ {
55444
+ onClick: commit,
55445
+ disabled: !message.trim() || !hasStaged,
55446
+ 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",
55447
+ children: hasStaged ? t("Commit (:count)", { count: status.staged.length }) : t("Commit")
55448
+ }
55449
+ ),
55450
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55451
+ "button",
55452
+ {
55453
+ onClick: stash,
55454
+ disabled: !isDirty,
55455
+ title: t("Park all uncommitted changes - restore them later from the Branches tab"),
55456
+ 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",
55457
+ children: t("Stash")
55458
+ }
55459
+ )
55460
+ ] })
54892
55461
  ] })
54893
55462
  ] });
54894
55463
  }
@@ -54916,6 +55485,8 @@ function BranchesTab({ onRefresh }) {
54916
55485
  const t = useT();
54917
55486
  const [branches, setBranches] = reactExports.useState([]);
54918
55487
  const [remotes, setRemotes] = reactExports.useState([]);
55488
+ const [stashes, setStashes] = reactExports.useState([]);
55489
+ const [info, setInfo] = reactExports.useState(null);
54919
55490
  const [loading, setLoading] = reactExports.useState(true);
54920
55491
  const [newBranch, setNewBranch] = reactExports.useState("");
54921
55492
  const [creating, setCreating] = reactExports.useState(false);
@@ -54929,9 +55500,10 @@ function BranchesTab({ onRefresh }) {
54929
55500
  const [filter2, setFilter] = reactExports.useState("");
54930
55501
  const load2 = reactExports.useCallback(() => {
54931
55502
  setLoadError(null);
54932
- Promise.all([electron$4.gitBranches(), electron$4.gitRemotes()]).then(([b, r]) => {
55503
+ Promise.all([electron$4.gitBranches(), electron$4.gitRemotes(), electron$4.gitStashList()]).then(([b, r, s]) => {
54933
55504
  setBranches(b);
54934
55505
  setRemotes(r);
55506
+ setStashes(s);
54935
55507
  }).catch((err) => {
54936
55508
  console.warn("GitPanel: failed to load branches/remotes", err);
54937
55509
  setLoadError(err instanceof Error ? err.message : String(err));
@@ -54940,13 +55512,34 @@ function BranchesTab({ onRefresh }) {
54940
55512
  reactExports.useEffect(() => {
54941
55513
  load2();
54942
55514
  }, [load2]);
55515
+ function isDirtyTreeError(msg) {
55516
+ return /overwritten by (checkout|merge)|commit your changes or stash/i.test(msg);
55517
+ }
55518
+ async function checkoutOrStash(localName, create2) {
55519
+ try {
55520
+ await electron$4.gitCheckout(localName, create2);
55521
+ return true;
55522
+ } catch (e) {
55523
+ const msg = String(e);
55524
+ if (!isDirtyTreeError(msg)) throw e;
55525
+ if (!confirm(t('You have uncommitted changes that would be lost switching to ":name". Stash them and switch?', { name: localName }))) {
55526
+ return false;
55527
+ }
55528
+ await electron$4.gitStash(t("Auto-stash before switching to :name", { name: localName }));
55529
+ await electron$4.gitCheckout(localName, create2);
55530
+ setInfo(t("Your changes were stashed. Restore them from the Stashes list below when you return."));
55531
+ return true;
55532
+ }
55533
+ }
54943
55534
  async function checkout(name, remote2) {
54944
55535
  try {
54945
55536
  setError(null);
55537
+ setInfo(null);
54946
55538
  const localName = remote2 ? name.replace(/^origin\//, "") : name;
54947
- await electron$4.gitCheckout(localName, false);
54948
- load2();
54949
- onRefresh();
55539
+ if (await checkoutOrStash(localName, false)) {
55540
+ load2();
55541
+ onRefresh();
55542
+ }
54950
55543
  } catch (e) {
54951
55544
  setError(String(e));
54952
55545
  }
@@ -54955,15 +55548,39 @@ function BranchesTab({ onRefresh }) {
54955
55548
  if (!newBranch.trim()) return;
54956
55549
  try {
54957
55550
  setError(null);
54958
- await electron$4.gitCheckout(newBranch.trim(), true);
54959
- setNewBranch("");
54960
- setCreating(false);
55551
+ setInfo(null);
55552
+ if (await checkoutOrStash(newBranch.trim(), true)) {
55553
+ setNewBranch("");
55554
+ setCreating(false);
55555
+ load2();
55556
+ onRefresh();
55557
+ }
55558
+ } catch (e) {
55559
+ setError(String(e));
55560
+ }
55561
+ }
55562
+ async function popStash(ref) {
55563
+ try {
55564
+ setError(null);
55565
+ setInfo(null);
55566
+ await electron$4.gitStashPop(ref);
54961
55567
  load2();
54962
55568
  onRefresh();
54963
55569
  } catch (e) {
54964
55570
  setError(String(e));
54965
55571
  }
54966
55572
  }
55573
+ async function dropStash(ref) {
55574
+ if (!confirm(t("Discard stashed changes (:ref)? This cannot be undone.", { ref }))) return;
55575
+ try {
55576
+ setError(null);
55577
+ setInfo(null);
55578
+ await electron$4.gitStashDrop(ref);
55579
+ load2();
55580
+ } catch (e) {
55581
+ setError(String(e));
55582
+ }
55583
+ }
54967
55584
  async function addRemote() {
54968
55585
  if (!remoteName.trim() || !remoteUrl.trim()) return;
54969
55586
  try {
@@ -55022,7 +55639,8 @@ function BranchesTab({ onRefresh }) {
55022
55639
  const remote = branches.filter((b) => b.remote && (!q || b.name.toLowerCase().includes(q)));
55023
55640
  if (loading) return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 px-3 py-4", children: t("Loading…") });
55024
55641
  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 }),
55642
+ error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-red-400 px-3 py-1 truncate", title: error2, children: error2 }),
55643
+ 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
55644
  loadError && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] text-amber-400 px-3 py-1 truncate", title: loadError, children: [
55027
55645
  t("Couldn't load git state:"),
55028
55646
  " ",
@@ -55114,6 +55732,37 @@ function BranchesTab({ onRefresh }) {
55114
55732
  ))
55115
55733
  ] }),
55116
55734
  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 }) }),
55735
+ stashes.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "border-t border-surface-800 mt-1", children: [
55736
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "px-3 py-1.5 text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: t("Stashes") }),
55737
+ 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: [
55738
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-[10px] text-surface-500 shrink-0", children: s.ref }),
55739
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "truncate flex-1", title: s.branch ? t("on :branch: :message", { branch: s.branch, message: s.message }) : s.message, children: [
55740
+ s.message,
55741
+ s.branch && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-600", children: [
55742
+ " ",
55743
+ t("(on :branch)", { branch: s.branch })
55744
+ ] })
55745
+ ] }),
55746
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55747
+ "button",
55748
+ {
55749
+ onClick: () => popStash(s.ref),
55750
+ className: "opacity-0 group-hover:opacity-100 text-[10px] text-blue-400 hover:text-blue-300 transition-all shrink-0",
55751
+ title: t("Restore these changes onto the current branch"),
55752
+ children: t("Pop")
55753
+ }
55754
+ ),
55755
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55756
+ "button",
55757
+ {
55758
+ onClick: () => dropStash(s.ref),
55759
+ className: "opacity-0 group-hover:opacity-100 text-[10px] text-surface-500 hover:text-red-400 transition-all shrink-0",
55760
+ title: t("Discard this stash"),
55761
+ children: "✕"
55762
+ }
55763
+ )
55764
+ ] }, s.ref))
55765
+ ] }),
55117
55766
  /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "border-t border-surface-800 mt-1", children: [
55118
55767
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-1.5 flex items-center justify-between", children: [
55119
55768
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: t("Remotes") }),
@@ -57912,6 +58561,43 @@ function App() {
57912
58561
  });
57913
58562
  return () => electron.offOpenFromGit();
57914
58563
  }, [applyWorkspace]);
58564
+ reactExports.useEffect(() => {
58565
+ electron.onImportShare(async (token) => {
58566
+ try {
58567
+ const { type, name, payload } = await electron.cloudImportShare(token);
58568
+ let collection2;
58569
+ if (type === "request") {
58570
+ const r = payload;
58571
+ const reqId = r.id || crypto.randomUUID();
58572
+ const req = { ...r, id: reqId };
58573
+ collection2 = {
58574
+ version: "1.0",
58575
+ id: crypto.randomUUID(),
58576
+ name: name || req.name || "Shared request",
58577
+ rootFolder: { id: crypto.randomUUID(), name: "", folders: [], requestIds: [reqId] },
58578
+ requests: { [reqId]: req }
58579
+ };
58580
+ } else {
58581
+ collection2 = { ...payload, id: crypto.randomUUID() };
58582
+ }
58583
+ const store = useStore$1.getState();
58584
+ const id2 = store.addCollectionObject(collection2);
58585
+ try {
58586
+ const entry = useStore$1.getState().collections[id2];
58587
+ if (entry && useStore$1.getState().workspace) {
58588
+ await electron.saveCollection(entry.relPath, entry.data);
58589
+ await electron.saveWorkspace(useStore$1.getState().workspace);
58590
+ }
58591
+ } catch (persistErr) {
58592
+ console.error("Imported shared collection but could not save it yet:", persistErr);
58593
+ }
58594
+ setSidebarTab("collections");
58595
+ } catch (err) {
58596
+ console.error("Import from share link failed:", err);
58597
+ }
58598
+ });
58599
+ return () => electron.offImportShare();
58600
+ }, [setSidebarTab]);
57915
58601
  reactExports.useEffect(() => {
57916
58602
  electron.onMockHit(addMockHit);
57917
58603
  return () => electron.offMockHit();
@@ -58080,7 +58766,7 @@ function App() {
58080
58766
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--wordmark-accent)" }, children: "Spector" }),
58081
58767
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal", style: { color: "var(--text-muted)" }, children: [
58082
58768
  "v",
58083
- "0.9.1"
58769
+ "0.9.3"
58084
58770
  ] }),
58085
58771
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mx-2 opacity-30", "aria-hidden": "true", children: "·" }),
58086
58772
  /* @__PURE__ */ jsxRuntimeExports.jsx(