@testsmith/api-spector 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./GraphQLEditor-CmfodipY.js","./codemirror-BKmwMUlV.js","./ajv-CJPwFjiP.js","./SoapEditor-CVRCFvJy.js"])))=>i.map(i=>d[i]);
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./GraphQLEditor-RRDuQogA.js","./codemirror-BKmwMUlV.js","./ajv-CJPwFjiP.js","./SoapEditor-BncNQmjP.js"])))=>i.map(i=>d[i]);
2
2
  import { r as requireReact, g as getDefaultExportFromCjs, R as React, a as reactExports, b as autocompletion, h as hoverTooltip, j as jsxRuntimeExports, V as ViewPlugin, E as EditorView, k as keymap, t as toggleComment, D as Decoration, c as RangeSetBuilder, x as xml, d as json, e as ReactCodeMirror, o as oneDark, f as javascript } from "./codemirror-BKmwMUlV.js";
3
3
  import { r as requireAjv } from "./ajv-CJPwFjiP.js";
4
4
  var client = { exports: {} };
@@ -14771,7 +14771,7 @@ const useStore$1 = create$1()(
14771
14771
  })
14772
14772
  }))
14773
14773
  );
14774
- const { electron: electron$I } = window;
14774
+ const { electron: electron$J } = window;
14775
14775
  function useAutoSave() {
14776
14776
  const collections = useStore$1((s) => s.collections);
14777
14777
  useStore$1((s) => s.environments);
@@ -14790,7 +14790,7 @@ function useAutoSave() {
14790
14790
  for (const { relPath, data, dirty } of dirtyCollections) {
14791
14791
  if (!dirty) continue;
14792
14792
  try {
14793
- await electron$I.saveCollection(relPath, data);
14793
+ await electron$J.saveCollection(relPath, data);
14794
14794
  markCollectionClean(data.id);
14795
14795
  } catch (e) {
14796
14796
  console.error("Auto-save failed for", relPath, e);
@@ -14809,7 +14809,7 @@ function useAutoSave() {
14809
14809
  for (const { relPath, data, dirty } of dirtyFlows) {
14810
14810
  if (!dirty) continue;
14811
14811
  try {
14812
- await electron$I.saveFlow(relPath, data);
14812
+ await electron$J.saveFlow(relPath, data);
14813
14813
  markFlowClean(data.id);
14814
14814
  } catch (e) {
14815
14815
  console.error("Auto-save failed for", relPath, e);
@@ -14825,7 +14825,7 @@ function useAutoSave() {
14825
14825
  if (wsTimerRef.current) clearTimeout(wsTimerRef.current);
14826
14826
  wsTimerRef.current = setTimeout(async () => {
14827
14827
  try {
14828
- await electron$I.saveWorkspace(workspace);
14828
+ await electron$J.saveWorkspace(workspace);
14829
14829
  } catch {
14830
14830
  }
14831
14831
  }, 300);
@@ -14834,7 +14834,7 @@ function useAutoSave() {
14834
14834
  };
14835
14835
  }, [workspace]);
14836
14836
  }
14837
- const { electron: electron$H } = window;
14837
+ const { electron: electron$I } = window;
14838
14838
  function useWorkspaceLoader() {
14839
14839
  const loadCollection = useStore$1((s) => s.loadCollection);
14840
14840
  const loadEnvironment = useStore$1((s) => s.loadEnvironment);
@@ -14861,7 +14861,7 @@ function useWorkspaceLoader() {
14861
14861
  if (typeof ws.settings?.zoom === "number") setZoom(ws.settings.zoom);
14862
14862
  if (ws.settings?.persistHistory) {
14863
14863
  try {
14864
- const entries = await electron$H.loadHistory();
14864
+ const entries = await electron$I.loadHistory();
14865
14865
  useStore$1.getState().setHistory(entries);
14866
14866
  } catch {
14867
14867
  }
@@ -14870,14 +14870,14 @@ function useWorkspaceLoader() {
14870
14870
  }
14871
14871
  for (const colPath of ws.collections ?? []) {
14872
14872
  try {
14873
- const col = await electron$H.loadCollection(colPath);
14873
+ const col = await electron$I.loadCollection(colPath);
14874
14874
  loadCollection(colPath, col);
14875
14875
  } catch {
14876
14876
  }
14877
14877
  }
14878
14878
  for (const envPath of ws.environments ?? []) {
14879
14879
  try {
14880
- const env2 = await electron$H.loadEnvironment(envPath);
14880
+ const env2 = await electron$I.loadEnvironment(envPath);
14881
14881
  loadEnvironment(envPath, env2);
14882
14882
  } catch {
14883
14883
  }
@@ -14896,32 +14896,38 @@ function useWorkspaceLoader() {
14896
14896
  }
14897
14897
  for (const relPath of ws.mocks ?? []) {
14898
14898
  try {
14899
- const mockData = await electron$H.loadMock(relPath);
14899
+ const mockData = await electron$I.loadMock(relPath);
14900
14900
  loadMock(relPath, mockData);
14901
14901
  } catch {
14902
14902
  }
14903
14903
  }
14904
14904
  for (const relPath of ws.flows ?? []) {
14905
14905
  try {
14906
- const flowData = await electron$H.loadFlow(relPath);
14906
+ const flowData = await electron$I.loadFlow(relPath);
14907
14907
  loadFlow(relPath, flowData);
14908
14908
  } catch {
14909
14909
  }
14910
14910
  }
14911
14911
  try {
14912
- const snapshots = await electron$H.listContractSnapshots(ws.contracts ?? []);
14912
+ const snapshots = await electron$I.listContractSnapshots(ws.contracts ?? []);
14913
14913
  for (const { relPath, snapshot } of snapshots) loadContractSnapshot(relPath, snapshot);
14914
14914
  } catch {
14915
14915
  }
14916
14916
  if ((ws.collections ?? []).length > 0) {
14917
14917
  try {
14918
- const firstCol = await electron$H.loadCollection(ws.collections[0]);
14918
+ const firstCol = await electron$I.loadCollection(ws.collections[0]);
14919
14919
  setActiveCollection(firstCol.id);
14920
14920
  } catch {
14921
14921
  }
14922
14922
  }
14923
14923
  }, [loadCollection, loadEnvironment, loadMock, loadFlow, loadContractSnapshot, setActiveCollection, setTheme, setZoom]);
14924
- return { applyWorkspace };
14924
+ const reloadWorkspace = reactExports.useCallback(async () => {
14925
+ const path = useStore$1.getState().workspacePath;
14926
+ if (!path) return;
14927
+ const result = await electron$I.openWorkspacePath(path);
14928
+ if (result) await applyWorkspace(result.workspace, result.workspacePath);
14929
+ }, [applyWorkspace]);
14930
+ return { applyWorkspace, reloadWorkspace };
14925
14931
  }
14926
14932
  var reactDomExports = requireReactDom();
14927
14933
  const ReactDOM = /* @__PURE__ */ getDefaultExportFromCjs(reactDomExports);
@@ -15985,6 +15991,7 @@ function useVarValues() {
15985
15991
  }
15986
15992
  return result;
15987
15993
  }
15994
+ const Workspace = "Workspace";
15988
15995
  const Collections$6 = "Collections";
15989
15996
  const History$6 = "History";
15990
15997
  const Git$6 = "Git";
@@ -16190,6 +16197,9 @@ const or$6 = "or";
16190
16197
  const Disabled$6 = "Disabled";
16191
16198
  const en = {
16192
16199
  "Close tab": "Close tab",
16200
+ "Open api-spector.dev": "Open api-spector.dev",
16201
+ Workspace,
16202
+ "API Spector home": "API Spector home",
16193
16203
  "Request body": "Request body",
16194
16204
  "Script editor": "Script editor",
16195
16205
  "JSON schema": "JSON schema",
@@ -25857,7 +25867,7 @@ function Modal({
25857
25867
  }
25858
25868
  );
25859
25869
  }
25860
- const { electron: electron$G } = window;
25870
+ const { electron: electron$H } = window;
25861
25871
  function Oauth2Form({ auth, onChange }) {
25862
25872
  const t = useT();
25863
25873
  const activeEnv = useActiveEnvironment();
@@ -25877,7 +25887,7 @@ function Oauth2Form({ auth, onChange }) {
25877
25887
  const flow = auth.oauth2Flow ?? "client_credentials";
25878
25888
  async function saveSecret(ref) {
25879
25889
  if (!secretValue || !ref) return;
25880
- await electron$G.setSecret(ref, secretValue);
25890
+ await electron$H.setSecret(ref, secretValue);
25881
25891
  setSaved(true);
25882
25892
  setSecretValue("");
25883
25893
  setTimeout(() => setSaved(false), 2e3);
@@ -25886,7 +25896,7 @@ function Oauth2Form({ auth, onChange }) {
25886
25896
  setStatus("fetching");
25887
25897
  setError("");
25888
25898
  try {
25889
- const result = await electron$G.oauth2StartFlow(auth, resolveCtx());
25899
+ const result = await electron$H.oauth2StartFlow(auth, resolveCtx());
25890
25900
  onChange({ oauth2CachedToken: result.accessToken, oauth2TokenExpiry: result.expiresAt });
25891
25901
  if (result.refreshToken) setRefreshToken(result.refreshToken);
25892
25902
  setStatus("ok");
@@ -25900,7 +25910,7 @@ function Oauth2Form({ auth, onChange }) {
25900
25910
  setStatus("fetching");
25901
25911
  setError("");
25902
25912
  try {
25903
- const result = await electron$G.oauth2RefreshToken(auth, resolveCtx(), refreshToken);
25913
+ const result = await electron$H.oauth2RefreshToken(auth, resolveCtx(), refreshToken);
25904
25914
  onChange({ oauth2CachedToken: result.accessToken, oauth2TokenExpiry: result.expiresAt });
25905
25915
  if (result.refreshToken) setRefreshToken(result.refreshToken);
25906
25916
  setStatus("ok");
@@ -26760,7 +26770,7 @@ function CollectionSettingsModal({ collection: collection2, onClose }) {
26760
26770
  }
26761
26771
  );
26762
26772
  }
26763
- const { electron: electron$F } = window;
26773
+ const { electron: electron$G } = window;
26764
26774
  function normalisePath(url) {
26765
26775
  let path = url.replace(/^\{\{[^}]+\}\}/, "").replace(/^https?:\/\/[^/]+/, "");
26766
26776
  if (!path.startsWith("/")) path = "/" + path;
@@ -26848,7 +26858,7 @@ function SchemaSyncModal({
26848
26858
  setLoading(true);
26849
26859
  setError(null);
26850
26860
  try {
26851
- const entries = await electron$F.extractOpenApiSchemas();
26861
+ const entries = await electron$G.extractOpenApiSchemas();
26852
26862
  if (!entries) {
26853
26863
  setLoading(false);
26854
26864
  return;
@@ -26867,7 +26877,7 @@ function SchemaSyncModal({
26867
26877
  setLoading(true);
26868
26878
  setError(null);
26869
26879
  try {
26870
- const entries = await electron$F.extractOpenApiSchemasFromUrl(trimmed);
26880
+ const entries = await electron$G.extractOpenApiSchemasFromUrl(trimmed);
26871
26881
  setSpecEntries(entries);
26872
26882
  autoSelectChanged(entries);
26873
26883
  } catch (err) {
@@ -26900,7 +26910,7 @@ function SchemaSyncModal({
26900
26910
  }
26901
26911
  const entry = useStore$1.getState().collections[collectionId];
26902
26912
  if (entry) {
26903
- await electron$F.saveCollection(entry.relPath, entry.data);
26913
+ await electron$G.saveCollection(entry.relPath, entry.data);
26904
26914
  markCollectionClean(collectionId);
26905
26915
  }
26906
26916
  onClose();
@@ -27413,7 +27423,7 @@ function hasContract(r) {
27413
27423
  const c = r.contract;
27414
27424
  return !!c && (c.statusCode != null || !!c.bodySchema || !!c.bodyMatcher || (c.headers?.length ?? 0) > 0 || (c.providerStates?.length ?? 0) > 0);
27415
27425
  }
27416
- const { electron: electron$E } = window;
27426
+ const { electron: electron$F } = window;
27417
27427
  function cloudEnabled() {
27418
27428
  return Boolean(useStore$1.getState().workspace?.settings?.cloud?.enabled);
27419
27429
  }
@@ -27422,7 +27432,7 @@ function assertEnabled() {
27422
27432
  }
27423
27433
  async function pushContractToCloud(requests, opts) {
27424
27434
  assertEnabled();
27425
- return electron$E.cloudPushPact({
27435
+ return electron$F.cloudPushPact({
27426
27436
  consumer: opts.consumer,
27427
27437
  provider: opts.provider,
27428
27438
  consumerVersion: opts.version,
@@ -27431,16 +27441,16 @@ async function pushContractToCloud(requests, opts) {
27431
27441
  }
27432
27442
  async function pushProviderSpecToCloud(opts) {
27433
27443
  assertEnabled();
27434
- return electron$E.cloudPushSpec(opts);
27444
+ return electron$F.cloudPushSpec(opts);
27435
27445
  }
27436
27446
  function openCloudMatrix() {
27437
- void electron$E.cloudOpenMatrix();
27447
+ void electron$F.cloudOpenMatrix();
27438
27448
  }
27439
27449
  async function pushFlowToCloud(flow) {
27440
27450
  assertEnabled();
27441
27451
  const s = useStore$1.getState();
27442
27452
  const environment = resolveEnvironmentById(s.environments, s.activeEnvironmentId);
27443
- return electron$E.cloudPushFlow({
27453
+ return electron$F.cloudPushFlow({
27444
27454
  flow,
27445
27455
  collections: Object.values(s.collections).map((c) => c.data),
27446
27456
  flows: Object.values(s.flows).map((f) => f.data),
@@ -27450,12 +27460,12 @@ async function pushFlowToCloud(flow) {
27450
27460
  }
27451
27461
  async function runFlowInCloud(flow, onStatus) {
27452
27462
  assertEnabled();
27453
- const started = await electron$E.cloudRunFlow(flow.name);
27463
+ const started = await electron$F.cloudRunFlow(flow.name);
27454
27464
  onStatus?.(started.status);
27455
27465
  const deadlineMs = 2 * 60 * 1e3;
27456
27466
  const t0 = Date.now();
27457
27467
  for (; ; ) {
27458
- const run = await electron$E.cloudGetFlowRun({ name: flow.name, runId: started.id });
27468
+ const run = await electron$F.cloudGetFlowRun({ name: flow.name, runId: started.id });
27459
27469
  onStatus?.(run.status);
27460
27470
  if (run.status === "passed" || run.status === "failed" || run.status === "error" || run.status === "done") {
27461
27471
  if (run.summary) return run.summary;
@@ -27466,12 +27476,53 @@ async function runFlowInCloud(flow, onStatus) {
27466
27476
  }
27467
27477
  }
27468
27478
  function openCloudFlow(flow) {
27469
- void electron$E.cloudOpenFlow(flow.name);
27479
+ void electron$F.cloudOpenFlow(flow.name);
27480
+ }
27481
+ async function shareCollectionToCloud(collection2) {
27482
+ assertEnabled();
27483
+ return electron$F.cloudShareCollection(collection2);
27484
+ }
27485
+ async function shareRequestToCloud(request2) {
27486
+ assertEnabled();
27487
+ return electron$F.cloudShareRequest(request2);
27488
+ }
27489
+ function collectFolderRequestIds(folder2) {
27490
+ return [...folder2.requestIds, ...folder2.folders.flatMap(collectFolderRequestIds)];
27491
+ }
27492
+ async function shareFolderToCloud(collection2, folder2) {
27493
+ assertEnabled();
27494
+ const ids = new Set(collectFolderRequestIds(folder2));
27495
+ const requests = {};
27496
+ for (const id2 of ids) {
27497
+ const r = collection2.requests[id2];
27498
+ if (r) requests[id2] = r;
27499
+ }
27500
+ const payload = {
27501
+ version: "1.0",
27502
+ id: folder2.id,
27503
+ name: folder2.name,
27504
+ rootFolder: folder2,
27505
+ requests,
27506
+ collectionVariables: collection2.collectionVariables
27507
+ };
27508
+ return electron$F.cloudShareFolder({ sourceId: folder2.id, name: folder2.name, payload });
27509
+ }
27510
+ async function lookupShare(type, sourceId) {
27511
+ assertEnabled();
27512
+ return electron$F.cloudShareLookup({ type, sourceId });
27513
+ }
27514
+ async function restrictShare(token, expiresDays) {
27515
+ assertEnabled();
27516
+ return electron$F.cloudShareUpdate({ token, expiresDays });
27517
+ }
27518
+ async function revokeShare(token) {
27519
+ assertEnabled();
27520
+ await electron$F.cloudShareRevoke(token);
27470
27521
  }
27471
27522
  async function getCloudMockRoutes(name) {
27472
27523
  if (!cloudEnabled()) return null;
27473
27524
  try {
27474
- const res = await electron$E.cloudGetMock(name);
27525
+ const res = await electron$F.cloudGetMock(name);
27475
27526
  return res.exists ? res.routes ?? [] : null;
27476
27527
  } catch {
27477
27528
  return null;
@@ -27480,7 +27531,7 @@ async function getCloudMockRoutes(name) {
27480
27531
  async function pushMockToCloud(server, routeIds) {
27481
27532
  assertEnabled();
27482
27533
  const routes = routeIds ? (server.routes ?? []).filter((r) => routeIds.includes(r.id)) : server.routes ?? [];
27483
- return electron$E.cloudPushMock({ ...server, routes });
27534
+ return electron$F.cloudPushMock({ ...server, routes });
27484
27535
  }
27485
27536
  async function pushRequestAsMonitor(request2, opts) {
27486
27537
  assertEnabled();
@@ -27504,7 +27555,7 @@ async function pushRequestAsMonitor(request2, opts) {
27504
27555
  ...request2.headers
27505
27556
  ];
27506
27557
  const mergedRequest = { ...request2, auth: mergedAuth, headers: mergedHeaders };
27507
- return electron$E.cloudPushMonitor(
27558
+ return electron$F.cloudPushMonitor(
27508
27559
  {
27509
27560
  request: mergedRequest,
27510
27561
  setup,
@@ -27922,6 +27973,201 @@ function Toast({ toast }) {
27922
27973
  if (!toast) return null;
27923
27974
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `mx-3 mb-2 px-2 py-1.5 rounded text-[11px] flex-shrink-0 ${toast.ok ? "bg-emerald-900/50 text-emerald-300 border border-emerald-800/50" : "bg-red-900/50 text-red-300 border border-red-800/50"}`, children: toast.msg });
27924
27975
  }
27976
+ function CloudRequiredModal({
27977
+ action,
27978
+ onConnect,
27979
+ onClose
27980
+ }) {
27981
+ const t = useT();
27982
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
27983
+ Modal,
27984
+ {
27985
+ onClose,
27986
+ closeOnBackdrop: true,
27987
+ overlayClassName: "bg-black/50 z-[300] flex items-center justify-center",
27988
+ panelClassName: "bg-surface-900 border border-surface-700 rounded-lg shadow-2xl p-5 w-96 flex flex-col gap-4",
27989
+ children: [
27990
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
27991
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "shrink-0 w-10 h-10 rounded-full bg-sky-900/40 border border-sky-700/40 flex items-center justify-center text-sky-300", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z" }) }) }),
27992
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
27993
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-sm font-semibold text-white", children: t("Connect to API Spector Cloud") }),
27994
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400 mt-0.5", children: t("Sharing needs the cloud") })
27995
+ ] })
27996
+ ] }),
27997
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-surface-300 leading-relaxed", children: action ? t("To :action you need to connect this workspace to API Spector Cloud, which hosts the public link.", { action }) : t("Public share links are hosted in API Spector Cloud. Connect this workspace to the cloud to create one.") }),
27998
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: t("Only request definitions with {{variables}} are shared. Secrets and environment values are never uploaded.") }),
27999
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2", children: [
28000
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
28001
+ "button",
28002
+ {
28003
+ onClick: onClose,
28004
+ className: "px-3 py-1.5 text-xs text-surface-400 hover:text-white transition-colors",
28005
+ children: t("Not now")
28006
+ }
28007
+ ),
28008
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
28009
+ "button",
28010
+ {
28011
+ onClick: onConnect,
28012
+ className: "px-3 py-1.5 text-xs font-medium text-white bg-sky-700 hover:bg-sky-600 rounded transition-colors",
28013
+ children: t("Open Cloud settings")
28014
+ }
28015
+ )
28016
+ ] })
28017
+ ]
28018
+ }
28019
+ );
28020
+ }
28021
+ function ShareModal({
28022
+ title,
28023
+ type,
28024
+ sourceId,
28025
+ onCreate,
28026
+ onClose
28027
+ }) {
28028
+ const t = useT();
28029
+ const [loading, setLoading] = reactExports.useState(true);
28030
+ const [busy, setBusy] = reactExports.useState(false);
28031
+ const [error2, setError] = reactExports.useState(null);
28032
+ const [copied, setCopied] = reactExports.useState(false);
28033
+ const [live, setLive] = reactExports.useState(null);
28034
+ const [expiryDays, setExpiryDays] = reactExports.useState(0);
28035
+ reactExports.useEffect(() => {
28036
+ let cancelled = false;
28037
+ (async () => {
28038
+ try {
28039
+ const res = await lookupShare(type, sourceId);
28040
+ if (!cancelled && res.exists && res.token && res.url && res.deep_link) {
28041
+ setLive({ token: res.token, url: res.url, deep_link: res.deep_link, expires_at: res.expires_at ?? null });
28042
+ }
28043
+ } catch (e) {
28044
+ if (!cancelled) setError(e.message);
28045
+ } finally {
28046
+ if (!cancelled) setLoading(false);
28047
+ }
28048
+ })();
28049
+ return () => {
28050
+ cancelled = true;
28051
+ };
28052
+ }, [type, sourceId]);
28053
+ async function run(fn) {
28054
+ setBusy(true);
28055
+ setError(null);
28056
+ try {
28057
+ await fn();
28058
+ } catch (e) {
28059
+ setError(e.message);
28060
+ } finally {
28061
+ setBusy(false);
28062
+ }
28063
+ }
28064
+ const create2 = () => run(async () => {
28065
+ const r = await onCreate();
28066
+ setLive({ token: r.token, url: r.url, deep_link: r.deep_link, expires_at: r.expires_at ?? null });
28067
+ });
28068
+ const applyExpiry = () => run(async () => {
28069
+ if (!live) return;
28070
+ const r = await restrictShare(live.token, expiryDays);
28071
+ setLive({ ...live, expires_at: r.expires_at ?? null });
28072
+ });
28073
+ const revoke = () => run(async () => {
28074
+ if (!live) return;
28075
+ await revokeShare(live.token);
28076
+ setLive(null);
28077
+ });
28078
+ const copy2 = async () => {
28079
+ if (!live) return;
28080
+ try {
28081
+ await navigator.clipboard.writeText(live.url);
28082
+ setCopied(true);
28083
+ setTimeout(() => setCopied(false), 2e3);
28084
+ } catch {
28085
+ }
28086
+ };
28087
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
28088
+ Modal,
28089
+ {
28090
+ onClose,
28091
+ title: t("Share: :name", { name: title }),
28092
+ overlayClassName: "bg-black/50 z-[300] flex items-center justify-center",
28093
+ panelClassName: "bg-surface-900 border border-surface-700 rounded-lg shadow-2xl w-[32rem] max-w-[92vw] flex flex-col",
28094
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-5 flex flex-col gap-4", children: [
28095
+ error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-red-400 bg-red-900/30 border border-red-800/40 rounded px-2 py-1.5", children: error2 }),
28096
+ loading ? /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-surface-400", children: t("Checking share status…") }) : !live ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
28097
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-surface-300 leading-relaxed", children: type === "request" ? t("Create a public read-only link to this request. Anyone with the link can view it or import it into their own app.") : type === "folder" ? t("Create a public read-only link to this folder. Anyone with the link can view its requests or import them into their own app.") : t("Create a public read-only link to this collection. Anyone with the link can view it or import it into their own app.") }),
28098
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: t("Only request definitions with {{variables}} are shared. Secrets and environment values are never uploaded.") }),
28099
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
28100
+ "button",
28101
+ {
28102
+ onClick: create2,
28103
+ disabled: busy,
28104
+ className: "px-3 py-1.5 text-xs font-medium text-white bg-sky-700 hover:bg-sky-600 disabled:opacity-50 rounded transition-colors",
28105
+ children: busy ? t("Creating…") : t("Create public link")
28106
+ }
28107
+ ) })
28108
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
28109
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
28110
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[11px] uppercase tracking-wide text-surface-500", children: t("Public link") }),
28111
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 flex items-center gap-2", children: [
28112
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
28113
+ "input",
28114
+ {
28115
+ readOnly: true,
28116
+ value: live.url,
28117
+ onClick: (e) => e.target.select(),
28118
+ className: "flex-1 rounded bg-surface-950 border border-surface-700 px-2 py-1.5 text-xs font-mono text-surface-200"
28119
+ }
28120
+ ),
28121
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: copy2, className: "px-3 py-1.5 text-xs border border-surface-700 rounded text-surface-300 hover:text-white transition-colors", children: copied ? t("Copied") : t("Copy") })
28122
+ ] })
28123
+ ] }),
28124
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-end gap-2", children: [
28125
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
28126
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[11px] uppercase tracking-wide text-surface-500", children: t("Restrict") }),
28127
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400 mt-1", children: live.expires_at ? t("Expires :date", { date: new Date(live.expires_at).toLocaleString() }) : t("Never expires") })
28128
+ ] }),
28129
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
28130
+ "select",
28131
+ {
28132
+ value: expiryDays,
28133
+ onChange: (e) => setExpiryDays(Number(e.target.value)),
28134
+ className: "rounded bg-surface-950 border border-surface-700 px-2 py-1.5 text-xs text-surface-200",
28135
+ children: [
28136
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 0, children: t("No expiry") }),
28137
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 1, children: t("1 day") }),
28138
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 7, children: t("7 days") }),
28139
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 30, children: t("30 days") })
28140
+ ]
28141
+ }
28142
+ ),
28143
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
28144
+ "button",
28145
+ {
28146
+ onClick: applyExpiry,
28147
+ disabled: busy,
28148
+ className: "px-3 py-1.5 text-xs border border-surface-700 rounded text-surface-300 hover:text-white disabled:opacity-50 transition-colors",
28149
+ children: t("Apply")
28150
+ }
28151
+ )
28152
+ ] }),
28153
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: t("Only request definitions with {{variables}} are shared. Secrets and environment values are never uploaded.") }),
28154
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center pt-2 border-t border-surface-800", children: [
28155
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
28156
+ "button",
28157
+ {
28158
+ onClick: revoke,
28159
+ disabled: busy,
28160
+ className: "px-3 py-1.5 text-xs text-red-400 hover:text-red-300 disabled:opacity-50 transition-colors",
28161
+ children: t("Revoke link")
28162
+ }
28163
+ ),
28164
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "px-3 py-1.5 text-xs text-surface-400 hover:text-white transition-colors", children: t("Done") })
28165
+ ] })
28166
+ ] })
28167
+ ] })
28168
+ }
28169
+ );
28170
+ }
27925
28171
  function requestPath(url) {
27926
28172
  const trimmed = url.trim().replace(/^\{\{[^}]+\}\}/, "").replace(/^https?:\/\/[^/]+/i, "");
27927
28173
  return trimmed || url.trim();
@@ -27991,6 +28237,9 @@ function RequestRow({
27991
28237
  const groupIndent = indent + 14;
27992
28238
  const { toast, show } = useToast();
27993
28239
  const addSecurityTests = useStore$1((s) => s.addSecurityTests);
28240
+ const openWorkspaceSettings = useStore$1((s) => s.openWorkspaceSettings);
28241
+ const [showCloudRequired, setShowCloudRequired] = reactExports.useState(false);
28242
+ const [showShare, setShowShare] = reactExports.useState(false);
27994
28243
  function handleRowClick(e) {
27995
28244
  if (e.metaKey || e.ctrlKey) {
27996
28245
  e.preventDefault();
@@ -28010,6 +28259,13 @@ function RequestRow({
28010
28259
  show(e.message, false);
28011
28260
  }
28012
28261
  }
28262
+ function openShare() {
28263
+ if (!cloudEnabled()) {
28264
+ setShowCloudRequired(true);
28265
+ return;
28266
+ }
28267
+ setShowShare(true);
28268
+ }
28013
28269
  const hookMenuItems = ["beforeAll", "before", "after", "afterAll"].map((ht) => ({
28014
28270
  type: "item",
28015
28271
  label: (hookType === ht ? "✓ " : " ") + t(HOOK_LABELS[ht]),
@@ -28150,6 +28406,7 @@ function RequestRow({
28150
28406
  show(n2 > 0 ? t("Added :count OWASP security tests", { count: n2 }) : t("No security tests generated for this request"), n2 > 0);
28151
28407
  } },
28152
28408
  ...cloudEnabled() ? [{ type: "item", label: t("Push as monitor to cloud"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: pushMonitor }] : [],
28409
+ { type: "item", label: t("Share request (public link)"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}), onClick: openShare },
28153
28410
  { type: "item", label: disabled ? t("Enable") : t("Disable"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(BanIcon, {}), onClick: onToggleDisabled },
28154
28411
  { type: "separator" },
28155
28412
  { type: "header", label: t("Hook type") },
@@ -28157,7 +28414,32 @@ function RequestRow({
28157
28414
  { type: "separator" },
28158
28415
  { type: "item", label: t("Delete"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TrashIcon, {}), danger: true, onClick: onDelete }
28159
28416
  ] }) }),
28160
- toast && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed bottom-4 right-4 z-[100] w-96", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Toast, { toast }) })
28417
+ toast && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed bottom-4 right-4 z-[100] w-96", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Toast, { toast }) }),
28418
+ showCloudRequired && /* @__PURE__ */ jsxRuntimeExports.jsx(
28419
+ CloudRequiredModal,
28420
+ {
28421
+ action: t("share this request"),
28422
+ onConnect: () => {
28423
+ setShowCloudRequired(false);
28424
+ openWorkspaceSettings("cloud");
28425
+ },
28426
+ onClose: () => setShowCloudRequired(false)
28427
+ }
28428
+ ),
28429
+ showShare && /* @__PURE__ */ jsxRuntimeExports.jsx(
28430
+ ShareModal,
28431
+ {
28432
+ title: useStore$1.getState().collections[collectionId]?.data.requests[reqId]?.name ?? "",
28433
+ type: "request",
28434
+ sourceId: reqId,
28435
+ onCreate: () => {
28436
+ const r = useStore$1.getState().collections[collectionId]?.data.requests[reqId];
28437
+ if (!r) throw new Error(t("Request not found"));
28438
+ return shareRequestToCloud(r);
28439
+ },
28440
+ onClose: () => setShowShare(false)
28441
+ }
28442
+ )
28161
28443
  ]
28162
28444
  }
28163
28445
  ),
@@ -28818,6 +29100,16 @@ function CollectionNode({
28818
29100
  const [expandCtrl, setExpandCtrl] = reactExports.useState({ value: true, seq: 0 });
28819
29101
  const [dropOver, setDropOver] = reactExports.useState(false);
28820
29102
  const dragCtx = reactExports.useContext(DragCtx);
29103
+ const openWorkspaceSettings = useStore$1((s) => s.openWorkspaceSettings);
29104
+ const [showCloudRequired, setShowCloudRequired] = reactExports.useState(false);
29105
+ const [showShare, setShowShare] = reactExports.useState(false);
29106
+ function openShare() {
29107
+ if (!cloudEnabled()) {
29108
+ setShowCloudRequired(true);
29109
+ return;
29110
+ }
29111
+ setShowShare(true);
29112
+ }
28821
29113
  function expandAll() {
28822
29114
  setExpandCtrl((c) => ({ value: true, seq: c.seq + 1, ids: null }));
28823
29115
  }
@@ -28899,6 +29191,7 @@ function CollectionNode({
28899
29191
  { type: "item", label: t("Settings"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(GearIcon, {}), onClick: () => setShowSettings(true) },
28900
29192
  { type: "item", label: t("Sync schemas"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: () => setShowSchemaSync(true) },
28901
29193
  ...cloudEnabled() ? [{ type: "item", label: t("Push contract to cloud"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: () => setShowPushContract(true) }] : [],
29194
+ { type: "item", label: t("Share collection (public link)"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}), onClick: openShare },
28902
29195
  { type: "item", label: col.disabled ? t("Enable") : t("Disable"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(BanIcon, {}), onClick: onToggleCollectionDisabled },
28903
29196
  { type: "separator" },
28904
29197
  { type: "item", label: t("Delete collection"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TrashIcon, {}), danger: true, onClick: onDeleteCollection }
@@ -28943,7 +29236,28 @@ function CollectionNode({
28943
29236
  }
28944
29237
  ),
28945
29238
  showSettings && /* @__PURE__ */ jsxRuntimeExports.jsx(CollectionSettingsModal, { collection: col, onClose: () => setShowSettings(false) }),
28946
- showSchemaSync && /* @__PURE__ */ jsxRuntimeExports.jsx(SchemaSyncModal, { collectionId: col.id, scope: { type: "collection" }, onClose: () => setShowSchemaSync(false) })
29239
+ showSchemaSync && /* @__PURE__ */ jsxRuntimeExports.jsx(SchemaSyncModal, { collectionId: col.id, scope: { type: "collection" }, onClose: () => setShowSchemaSync(false) }),
29240
+ showCloudRequired && /* @__PURE__ */ jsxRuntimeExports.jsx(
29241
+ CloudRequiredModal,
29242
+ {
29243
+ action: t("share this collection"),
29244
+ onConnect: () => {
29245
+ setShowCloudRequired(false);
29246
+ openWorkspaceSettings("cloud");
29247
+ },
29248
+ onClose: () => setShowCloudRequired(false)
29249
+ }
29250
+ ),
29251
+ showShare && /* @__PURE__ */ jsxRuntimeExports.jsx(
29252
+ ShareModal,
29253
+ {
29254
+ title: col.name,
29255
+ type: "collection",
29256
+ sourceId: col.id,
29257
+ onCreate: () => shareCollectionToCloud(col),
29258
+ onClose: () => setShowShare(false)
29259
+ }
29260
+ )
28947
29261
  ] });
28948
29262
  }
28949
29263
  function FolderRow({
@@ -28987,6 +29301,16 @@ function FolderRow({
28987
29301
  }));
28988
29302
  const [dropInside, setDropInside] = reactExports.useState(false);
28989
29303
  const dragCtx = reactExports.useContext(DragCtx);
29304
+ const openWorkspaceSettings = useStore$1((s) => s.openWorkspaceSettings);
29305
+ const [showCloudRequired, setShowCloudRequired] = reactExports.useState(false);
29306
+ const [showShare, setShowShare] = reactExports.useState(false);
29307
+ function openShare() {
29308
+ if (!cloudEnabled()) {
29309
+ setShowCloudRequired(true);
29310
+ return;
29311
+ }
29312
+ setShowShare(true);
29313
+ }
28990
29314
  const tags = folder2.tags ?? [];
28991
29315
  const indent = indentPx(depth);
28992
29316
  const hasInheritedConfig = folder2.auth && folder2.auth.type !== "none" || folder2.headers && folder2.headers.length > 0;
@@ -29100,6 +29424,7 @@ function FolderRow({
29100
29424
  { type: "item", label: t("Settings"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(GearIcon, {}), onClick: () => setShowSettings(true) },
29101
29425
  { type: "item", label: t("Sync schemas"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: () => setShowSchemaSync(true) },
29102
29426
  ...cloudEnabled() ? [{ type: "item", label: t("Push contract to cloud"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: () => setShowPushContract(true) }] : [],
29427
+ { type: "item", label: t("Share folder (public link)"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}), onClick: openShare },
29103
29428
  { type: "item", label: folder2.disabled ? t("Enable") : t("Disable"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(BanIcon, {}), onClick: onToggleDisabled },
29104
29429
  { type: "separator" },
29105
29430
  { type: "header", label: t("Run folder as hook") },
@@ -29134,6 +29459,27 @@ function FolderRow({
29134
29459
  scope: { type: "folder", folderId: folder2.id },
29135
29460
  onClose: () => setShowSchemaSync(false)
29136
29461
  }
29462
+ ),
29463
+ showCloudRequired && /* @__PURE__ */ jsxRuntimeExports.jsx(
29464
+ CloudRequiredModal,
29465
+ {
29466
+ action: t("share this folder"),
29467
+ onConnect: () => {
29468
+ setShowCloudRequired(false);
29469
+ openWorkspaceSettings("cloud");
29470
+ },
29471
+ onClose: () => setShowCloudRequired(false)
29472
+ }
29473
+ ),
29474
+ showShare && folderCollection && /* @__PURE__ */ jsxRuntimeExports.jsx(
29475
+ ShareModal,
29476
+ {
29477
+ title: folder2.name,
29478
+ type: "folder",
29479
+ sourceId: folder2.id,
29480
+ onCreate: () => shareFolderToCloud(folderCollection, folder2),
29481
+ onClose: () => setShowShare(false)
29482
+ }
29137
29483
  )
29138
29484
  ] });
29139
29485
  }
@@ -29587,8 +29933,8 @@ function jsonWithComments() {
29587
29933
  function xmlWithComments() {
29588
29934
  return [xml(), commentKeymap];
29589
29935
  }
29590
- const GraphQLEditor = reactExports.lazy(() => __vitePreload(() => import("./GraphQLEditor-CmfodipY.js"), true ? __vite__mapDeps([0,1,2]) : void 0, import.meta.url).then((m) => ({ default: m.GraphQLEditor })));
29591
- const SoapEditor = reactExports.lazy(() => __vitePreload(() => import("./SoapEditor-CVRCFvJy.js"), true ? __vite__mapDeps([3,1,2]) : void 0, import.meta.url).then((m) => ({ default: m.SoapEditor })));
29936
+ const GraphQLEditor = reactExports.lazy(() => __vitePreload(() => import("./GraphQLEditor-RRDuQogA.js"), true ? __vite__mapDeps([0,1,2]) : void 0, import.meta.url).then((m) => ({ default: m.GraphQLEditor })));
29937
+ const SoapEditor = reactExports.lazy(() => __vitePreload(() => import("./SoapEditor-BncNQmjP.js"), true ? __vite__mapDeps([3,1,2]) : void 0, import.meta.url).then((m) => ({ default: m.SoapEditor })));
29592
29938
  function BodyTab({ request: request2, onChange }) {
29593
29939
  const t = useT();
29594
29940
  const editorFallback = /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 text-xs text-surface-500", children: t("Loading editor…") });
@@ -29686,7 +30032,7 @@ function BodyTab({ request: request2, onChange }) {
29686
30032
  mode === "soap" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: editorFallback, children: /* @__PURE__ */ jsxRuntimeExports.jsx(SoapEditor, { request: request2, onChange }) }) })
29687
30033
  ] });
29688
30034
  }
29689
- const { electron: electron$D } = window;
30035
+ const { electron: electron$E } = window;
29690
30036
  function AuthTab({ request: request2, onChange }) {
29691
30037
  const auth = request2.auth;
29692
30038
  const [secretValue, setSecretValue] = reactExports.useState("");
@@ -29696,7 +30042,7 @@ function AuthTab({ request: request2, onChange }) {
29696
30042
  }
29697
30043
  async function saveSecret(ref) {
29698
30044
  if (!secretValue || !ref) return;
29699
- await electron$D.setSecret(ref, secretValue);
30045
+ await electron$E.setSecret(ref, secretValue);
29700
30046
  setSaved(true);
29701
30047
  setSecretValue("");
29702
30048
  setTimeout(() => setSaved(false), 2e3);
@@ -30056,7 +30402,7 @@ function validateBodyAgainstSchema(schema, body) {
30056
30402
  return { status: "error", message: e instanceof Error ? e.message : String(e) };
30057
30403
  }
30058
30404
  }
30059
- const { electron: electron$C } = window;
30405
+ const { electron: electron$D } = window;
30060
30406
  function SchemaTab({ request: request2, onChange }) {
30061
30407
  const t = useT();
30062
30408
  const activeTab = useStore$1((s) => s.tabs.find((tab) => tab.id === s.activeTabId));
@@ -30080,7 +30426,7 @@ function SchemaTab({ request: request2, onChange }) {
30080
30426
  if (!lastResponse?.body) return;
30081
30427
  setInferring(true);
30082
30428
  try {
30083
- const schema = await electron$C.inferContractSchema(lastResponse.body);
30429
+ const schema = await electron$D.inferContractSchema(lastResponse.body);
30084
30430
  if (schema) setSchema(schema);
30085
30431
  } finally {
30086
30432
  setInferring(false);
@@ -39510,7 +39856,7 @@ function DocsEditor({ value: value2, onChange, placeholder, onGenerate, aiAvaila
39510
39856
  }
39511
39857
  const AI_OPENAI_TOKEN_REF = "ai:openai:token";
39512
39858
  const DEFAULT_AI_MODEL = "gpt-4o-mini";
39513
- const { electron: electron$B } = window;
39859
+ const { electron: electron$C } = window;
39514
39860
  const CFG_KEY = STORAGE_KEYS.ai;
39515
39861
  function getAiConfig() {
39516
39862
  try {
@@ -39532,7 +39878,7 @@ function invalidateAiAvailable() {
39532
39878
  async function checkAvailable() {
39533
39879
  if (availableCache != null) return availableCache;
39534
39880
  try {
39535
- availableCache = (await electron$B.hasSecret(AI_OPENAI_TOKEN_REF)).has;
39881
+ availableCache = (await electron$C.hasSecret(AI_OPENAI_TOKEN_REF)).has;
39536
39882
  } catch {
39537
39883
  availableCache = false;
39538
39884
  }
@@ -39552,7 +39898,7 @@ function useAiAvailable() {
39552
39898
  return available;
39553
39899
  }
39554
39900
  async function generateDocs(input) {
39555
- return electron$B.generateAiDocs({ ...input, model: getAiConfig().model });
39901
+ return electron$C.generateAiDocs({ ...input, model: getAiConfig().model });
39556
39902
  }
39557
39903
  function kvLines(label, rows) {
39558
39904
  const on = (rows ?? []).filter((r) => r.enabled !== false && r.key);
@@ -40080,7 +40426,7 @@ function RealtimeTestSection({
40080
40426
  ] })
40081
40427
  ] });
40082
40428
  }
40083
- const { electron: electron$A } = window;
40429
+ const { electron: electron$B } = window;
40084
40430
  function formatTime$4(ts) {
40085
40431
  const d = new Date(ts);
40086
40432
  const hh = String(d.getHours()).padStart(2, "0");
@@ -40110,7 +40456,7 @@ function WebSocketPanel({ request: request2, onChange }) {
40110
40456
  const url = interp(request2.url).trim();
40111
40457
  const headers = {};
40112
40458
  for (const h of request2.headers) if (h.enabled && h.key) headers[interp(h.key)] = interp(h.value);
40113
- return electron$A.wsTest({
40459
+ return electron$B.wsTest({
40114
40460
  url,
40115
40461
  headers,
40116
40462
  sendMessage: cfg.sendMessage ? interp(cfg.sendMessage) : void 0,
@@ -40120,14 +40466,14 @@ function WebSocketPanel({ request: request2, onChange }) {
40120
40466
  });
40121
40467
  });
40122
40468
  reactExports.useEffect(() => {
40123
- electron$A.onWsMessage(({ requestId, message }) => {
40469
+ electron$B.onWsMessage(({ requestId, message }) => {
40124
40470
  addWsMessage(requestId, message);
40125
40471
  });
40126
- electron$A.onWsStatus(({ requestId, status, error: error2 }) => {
40472
+ electron$B.onWsStatus(({ requestId, status, error: error2 }) => {
40127
40473
  setWsStatus(requestId, status, error2);
40128
40474
  });
40129
40475
  return () => {
40130
- electron$A.offWsEvents();
40476
+ electron$B.offWsEvents();
40131
40477
  };
40132
40478
  }, [addWsMessage, setWsStatus]);
40133
40479
  reactExports.useEffect(() => {
@@ -40140,19 +40486,19 @@ function WebSocketPanel({ request: request2, onChange }) {
40140
40486
  if (h.enabled && h.key) headers[h.key] = h.value;
40141
40487
  }
40142
40488
  try {
40143
- await electron$A.wsConnect(request2.id, request2.url, headers);
40489
+ await electron$B.wsConnect(request2.id, request2.url, headers);
40144
40490
  } catch (err) {
40145
40491
  setWsStatus(request2.id, "error", err instanceof Error ? err.message : String(err));
40146
40492
  }
40147
40493
  }
40148
40494
  async function disconnect() {
40149
- await electron$A.wsDisconnect(request2.id);
40495
+ await electron$B.wsDisconnect(request2.id);
40150
40496
  }
40151
40497
  async function sendMessage() {
40152
40498
  const text = sendText.trim();
40153
40499
  if (!text || !isConnected) return;
40154
40500
  try {
40155
- await electron$A.wsSend(request2.id, text);
40501
+ await electron$B.wsSend(request2.id, text);
40156
40502
  const msg = {
40157
40503
  id: crypto.randomUUID(),
40158
40504
  direction: "sent",
@@ -40304,7 +40650,7 @@ function WebSocketPanel({ request: request2, onChange }) {
40304
40650
  ] })
40305
40651
  ] });
40306
40652
  }
40307
- const { electron: electron$z } = window;
40653
+ const { electron: electron$A } = window;
40308
40654
  function formatTime$3(ts) {
40309
40655
  const d = new Date(ts);
40310
40656
  const p = (n2, w = 2) => String(n2).padStart(w, "0");
@@ -40338,7 +40684,7 @@ function SocketIoPanel({ request: request2, onChange }) {
40338
40684
  const url = interp(request2.url).trim();
40339
40685
  const headers = {};
40340
40686
  for (const h of request2.headers) if (h.enabled && h.key) headers[interp(h.key)] = interp(h.value);
40341
- return electron$z.socketIoTest({
40687
+ return electron$A.socketIoTest({
40342
40688
  url,
40343
40689
  namespace: namespace2,
40344
40690
  auth: auth ? interp(auth) : void 0,
@@ -40357,19 +40703,19 @@ function SocketIoPanel({ request: request2, onChange }) {
40357
40703
  if (h.enabled && h.key) headers[h.key] = h.value;
40358
40704
  }
40359
40705
  try {
40360
- await electron$z.socketIoConnect(request2.id, { url: request2.url, namespace: namespace2, auth, headers });
40706
+ await electron$A.socketIoConnect(request2.id, { url: request2.url, namespace: namespace2, auth, headers });
40361
40707
  } catch (err) {
40362
40708
  setStatus(request2.id, "error", err instanceof Error ? err.message : String(err));
40363
40709
  }
40364
40710
  }
40365
40711
  async function disconnect() {
40366
- await electron$z.socketIoDisconnect(request2.id);
40712
+ await electron$A.socketIoDisconnect(request2.id);
40367
40713
  }
40368
40714
  async function emit() {
40369
40715
  const event = eventName2.trim();
40370
40716
  if (!event || !isConnected) return;
40371
40717
  try {
40372
- await electron$z.socketIoEmit(request2.id, { event, args: argsText, withAck });
40718
+ await electron$A.socketIoEmit(request2.id, { event, args: argsText, withAck });
40373
40719
  const msg = {
40374
40720
  id: crypto.randomUUID(),
40375
40721
  direction: "sent",
@@ -40583,7 +40929,7 @@ function SocketIoPanel({ request: request2, onChange }) {
40583
40929
  ] })
40584
40930
  ] });
40585
40931
  }
40586
- const { electron: electron$y } = window;
40932
+ const { electron: electron$z } = window;
40587
40933
  function formatTime$2(ts) {
40588
40934
  const d = new Date(ts);
40589
40935
  const p = (n2, w = 2) => String(n2).padStart(w, "0");
@@ -40623,7 +40969,7 @@ function MqttPanel({ request: request2, onChange }) {
40623
40969
  return;
40624
40970
  }
40625
40971
  try {
40626
- await electron$y.mqttConnect(request2.id, {
40972
+ await electron$z.mqttConnect(request2.id, {
40627
40973
  url,
40628
40974
  clientId: cfg.clientId ? interp(cfg.clientId) : void 0,
40629
40975
  username: cfg.username ? interp(cfg.username) : void 0,
@@ -40637,13 +40983,13 @@ function MqttPanel({ request: request2, onChange }) {
40637
40983
  }
40638
40984
  }
40639
40985
  async function disconnect() {
40640
- await electron$y.mqttDisconnect(request2.id);
40986
+ await electron$z.mqttDisconnect(request2.id);
40641
40987
  }
40642
40988
  async function subscribe() {
40643
40989
  const topic = interp(cfg.subscribeTopic ?? "").trim();
40644
40990
  if (!topic || !isConnected) return;
40645
40991
  try {
40646
- await electron$y.mqttSubscribe(request2.id, { topic, qos });
40992
+ await electron$z.mqttSubscribe(request2.id, { topic, qos });
40647
40993
  } catch (err) {
40648
40994
  setStatus(request2.id, "error", err instanceof Error ? err.message : String(err));
40649
40995
  }
@@ -40651,7 +40997,7 @@ function MqttPanel({ request: request2, onChange }) {
40651
40997
  async function unsubscribe() {
40652
40998
  const topic = interp(cfg.subscribeTopic ?? "").trim();
40653
40999
  if (!topic || !isConnected) return;
40654
- await electron$y.mqttUnsubscribe(request2.id, { topic });
41000
+ await electron$z.mqttUnsubscribe(request2.id, { topic });
40655
41001
  }
40656
41002
  async function publish() {
40657
41003
  const topic = interp(cfg.publishTopic ?? "").trim();
@@ -40663,7 +41009,7 @@ function MqttPanel({ request: request2, onChange }) {
40663
41009
  setPublishError("");
40664
41010
  const body = interp(payload);
40665
41011
  try {
40666
- await electron$y.mqttPublish(request2.id, { topic, payload: body, qos, retain: Boolean(cfg.retain) });
41012
+ await electron$z.mqttPublish(request2.id, { topic, payload: body, qos, retain: Boolean(cfg.retain) });
40667
41013
  addMessage(request2.id, {
40668
41014
  id: crypto.randomUUID(),
40669
41015
  direction: "sent",
@@ -40687,7 +41033,7 @@ function MqttPanel({ request: request2, onChange }) {
40687
41033
  setTestRemaining(Math.ceil(collectMs / 1e3));
40688
41034
  const countdown = setInterval(() => setTestRemaining((r) => r && r > 1 ? r - 1 : 0), 1e3);
40689
41035
  try {
40690
- const res = await electron$y.mqttTest({
41036
+ const res = await electron$z.mqttTest({
40691
41037
  url,
40692
41038
  // The test opens its own connection; never reuse the panel's Client ID,
40693
41039
  // or the broker would kick whichever connected first (a clientId clash).
@@ -41036,7 +41382,7 @@ function MqttPanel({ request: request2, onChange }) {
41036
41382
  ] })
41037
41383
  ] });
41038
41384
  }
41039
- const { electron: electron$x } = window;
41385
+ const { electron: electron$y } = window;
41040
41386
  function formatTime$1(ts) {
41041
41387
  const d = new Date(ts);
41042
41388
  const p = (n2, w = 2) => String(n2).padStart(w, "0");
@@ -41072,13 +41418,13 @@ function GrpcPanel({ request: request2, onChange }) {
41072
41418
  onChange({ body: { ...request2.body, mode: "grpc", grpc: { ...grpc, ...changes } } });
41073
41419
  }
41074
41420
  reactExports.useEffect(() => {
41075
- electron$x.onGrpcMessage(({ requestId, message }) => {
41421
+ electron$y.onGrpcMessage(({ requestId, message }) => {
41076
41422
  addGrpcMessage(requestId, message);
41077
41423
  });
41078
- electron$x.onGrpcStatus(({ requestId, status, code, codeName, error: error2 }) => {
41424
+ electron$y.onGrpcStatus(({ requestId, status, code, codeName, error: error2 }) => {
41079
41425
  setGrpcStatus(requestId, status, { code, codeName, error: error2 });
41080
41426
  });
41081
- return () => electron$x.offGrpcEvents();
41427
+ return () => electron$y.offGrpcEvents();
41082
41428
  }, [addGrpcMessage, setGrpcStatus]);
41083
41429
  reactExports.useEffect(() => {
41084
41430
  logEndRef.current?.scrollIntoView({ behavior: "smooth" });
@@ -41091,7 +41437,7 @@ function GrpcPanel({ request: request2, onChange }) {
41091
41437
  if (!grpc.protoSource?.trim() && !grpc.protoPath?.trim()) return;
41092
41438
  setLoadingProto(true);
41093
41439
  try {
41094
- const result = await electron$x.grpcLoadProto({ protoSource: grpc.protoSource, protoPath: grpc.protoPath });
41440
+ const result = await electron$y.grpcLoadProto({ protoSource: grpc.protoSource, protoPath: grpc.protoPath });
41095
41441
  const services = result.services;
41096
41442
  setGrpcServices(request2.id, services);
41097
41443
  if (services.length && !services.some((s) => s.name === grpc.serviceName)) {
@@ -41110,7 +41456,7 @@ function GrpcPanel({ request: request2, onChange }) {
41110
41456
  const metadata = {};
41111
41457
  for (const m of grpc.metadata ?? []) if (m.enabled && m.key) metadata[m.key] = m.value;
41112
41458
  try {
41113
- await electron$x.grpcInvoke(request2.id, {
41459
+ await electron$y.grpcInvoke(request2.id, {
41114
41460
  target: request2.url,
41115
41461
  serviceName: grpc.serviceName,
41116
41462
  methodName: grpc.methodName,
@@ -41125,7 +41471,7 @@ function GrpcPanel({ request: request2, onChange }) {
41125
41471
  }
41126
41472
  }
41127
41473
  function cancel() {
41128
- electron$x.grpcCancel(request2.id);
41474
+ electron$y.grpcCancel(request2.id);
41129
41475
  }
41130
41476
  const statusColors = {
41131
41477
  running: "bg-amber-400",
@@ -41249,7 +41595,7 @@ function GrpcPanel({ request: request2, onChange }) {
41249
41595
  ] })
41250
41596
  ] });
41251
41597
  }
41252
- const { electron: electron$w } = window;
41598
+ const { electron: electron$x } = window;
41253
41599
  function isSubscriptionQuery(query) {
41254
41600
  return /(^|\n)\s*subscription\b/i.test(query ?? "");
41255
41601
  }
@@ -41298,7 +41644,7 @@ function GraphQlSubscription({ request: request2, gql, onPatch }) {
41298
41644
  const headers = {};
41299
41645
  for (const h of request2.headers) if (h.enabled && h.key) headers[interp(h.key)] = interp(h.value);
41300
41646
  try {
41301
- await electron$w.graphqlWsSubscribe(request2.id, {
41647
+ await electron$x.graphqlWsSubscribe(request2.id, {
41302
41648
  url,
41303
41649
  headers,
41304
41650
  query: gql.query,
@@ -41315,7 +41661,7 @@ function GraphQlSubscription({ request: request2, gql, onPatch }) {
41315
41661
  const url = deriveWsUrl(interp(rawUrl));
41316
41662
  const headers = {};
41317
41663
  for (const h of request2.headers) if (h.enabled && h.key) headers[interp(h.key)] = interp(h.value);
41318
- return electron$w.graphqlWsTest({
41664
+ return electron$x.graphqlWsTest({
41319
41665
  url,
41320
41666
  headers,
41321
41667
  query: gql.query,
@@ -41350,7 +41696,7 @@ function GraphQlSubscription({ request: request2, gql, onPatch }) {
41350
41696
  className: "flex-1 bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs font-mono focus:outline-none focus:border-blue-500 placeholder-surface-600 disabled:opacity-50"
41351
41697
  }
41352
41698
  ),
41353
- isActive ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => electron$w.graphqlWsStop(request2.id), className: "px-3 py-1 bg-red-700 hover:bg-red-600 rounded text-xs font-medium transition-colors whitespace-nowrap", children: conn.status === "connecting" ? t("Connecting…") : t("Stop") }) : /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: subscribe, className: "px-3 py-1 bg-fuchsia-700 hover:bg-fuchsia-600 rounded text-xs font-medium transition-colors whitespace-nowrap", children: t("Subscribe") })
41699
+ isActive ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => electron$x.graphqlWsStop(request2.id), className: "px-3 py-1 bg-red-700 hover:bg-red-600 rounded text-xs font-medium transition-colors whitespace-nowrap", children: conn.status === "connecting" ? t("Connecting…") : t("Stop") }) : /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: subscribe, className: "px-3 py-1 bg-fuchsia-700 hover:bg-fuchsia-600 rounded text-xs font-medium transition-colors whitespace-nowrap", children: t("Subscribe") })
41354
41700
  ] }),
41355
41701
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-shrink-0", children: [
41356
41702
  /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: () => setShowParams((v) => !v), className: "text-[10px] text-surface-600 hover:text-surface-300 transition-colors", children: [
@@ -41618,7 +41964,7 @@ function FuzzResultsPanel({ report, onClear }) {
41618
41964
  ] }) })
41619
41965
  ] });
41620
41966
  }
41621
- const { electron: electron$v } = window;
41967
+ const { electron: electron$w } = window;
41622
41968
  const WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
41623
41969
  function FuzzModal({ request: request2, onClose }) {
41624
41970
  const t = useT();
@@ -41646,7 +41992,7 @@ function FuzzModal({ request: request2, onClose }) {
41646
41992
  const env2 = resolveEnvironmentById(environments, activeEnvironmentId);
41647
41993
  const envVars = env2 ? Object.fromEntries(env2.variables.filter((v) => v.enabled).map((v) => [v.key, v.value])) : {};
41648
41994
  const collectionVars = activeCollectionId ? collections[activeCollectionId]?.data.collectionVariables ?? {} : {};
41649
- const result = await electron$v.fuzzContracts({
41995
+ const result = await electron$w.fuzzContracts({
41650
41996
  requests: [request2],
41651
41997
  envVars,
41652
41998
  collectionVars,
@@ -41863,7 +42209,7 @@ function resolveAskValues(asks, answers) {
41863
42209
  for (const a of asks) out[a.key] = answers[a.message] ?? "";
41864
42210
  return out;
41865
42211
  }
41866
- const { electron: electron$u } = window;
42212
+ const { electron: electron$v } = window;
41867
42213
  const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "QUERY"];
41868
42214
  const METHOD_COLORS = {
41869
42215
  GET: "text-emerald-400",
@@ -42009,7 +42355,7 @@ function RequestBuilder({ request: request2 }) {
42009
42355
  const headers = {};
42010
42356
  for (const h of request2.headers) if (h.enabled && h.key) headers[interp(h.key)] = interp(h.value);
42011
42357
  try {
42012
- await electron$u.graphqlWsSubscribe(request2.id, {
42358
+ await electron$v.graphqlWsSubscribe(request2.id, {
42013
42359
  url,
42014
42360
  headers,
42015
42361
  query: gql.query,
@@ -42070,7 +42416,7 @@ function RequestBuilder({ request: request2 }) {
42070
42416
  const start2 = Date.now();
42071
42417
  try {
42072
42418
  const hookEnv = withEnvOverrides(activeEnv, liveEnvOverrides);
42073
- const r = await electron$u.sendRequest({
42419
+ const r = await electron$v.sendRequest({
42074
42420
  ...basePayload,
42075
42421
  environment: hookEnv,
42076
42422
  request: hook,
@@ -42122,7 +42468,7 @@ function RequestBuilder({ request: request2 }) {
42122
42468
  startLiveStream(activeTabId, streamId);
42123
42469
  let result;
42124
42470
  try {
42125
- result = await electron$u.sendRequest({
42471
+ result = await electron$v.sendRequest({
42126
42472
  ...basePayload,
42127
42473
  environment: freshEnv,
42128
42474
  request: mergedRequest,
@@ -42159,7 +42505,7 @@ function RequestBuilder({ request: request2 }) {
42159
42505
  const start2 = Date.now();
42160
42506
  try {
42161
42507
  const hookEnv = withEnvOverrides(activeEnv, liveEnvOverrides);
42162
- const r = await electron$u.sendRequest({
42508
+ const r = await electron$v.sendRequest({
42163
42509
  ...basePayload,
42164
42510
  environment: hookEnv,
42165
42511
  request: hook,
@@ -43342,7 +43688,7 @@ function Cell({ value: value2, onDrill }) {
43342
43688
  const text = String(value2);
43343
43689
  return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-block max-w-[26rem] truncate align-bottom text-surface-300", title: text.length > 40 ? text : void 0, children: text });
43344
43690
  }
43345
- const { electron: electron$t } = window;
43691
+ const { electron: electron$u } = window;
43346
43692
  const RENDER_TAIL = 500;
43347
43693
  const CLOSE_LABEL = {
43348
43694
  complete: "closed",
@@ -43384,7 +43730,7 @@ function StreamView({ events, streaming: streaming2, streamId, streamClose, firs
43384
43730
  if (!streamId) return;
43385
43731
  setStopping(true);
43386
43732
  try {
43387
- await electron$t.stopStream(streamId);
43733
+ await electron$u.stopStream(streamId);
43388
43734
  } catch {
43389
43735
  }
43390
43736
  }
@@ -43527,7 +43873,7 @@ function HookResultsPanel({ results }) {
43527
43873
  }) })
43528
43874
  ] });
43529
43875
  }
43530
- const { electron: electron$s } = window;
43876
+ const { electron: electron$t } = window;
43531
43877
  function extractPath(url) {
43532
43878
  try {
43533
43879
  return new URL(url).pathname || "/";
@@ -43582,14 +43928,14 @@ function SaveAsMockModal({ onClose }) {
43582
43928
  const entry = state.mocks[serverId];
43583
43929
  const updated = { ...entry.data, name: newServerName, port: Number(newServerPort), routes: [route] };
43584
43930
  updateMock(serverId, updated);
43585
- await electron$s.saveMock(entry.relPath, updated);
43931
+ await electron$t.saveMock(entry.relPath, updated);
43586
43932
  const ws = useStore$1.getState().workspace;
43587
- if (ws) await electron$s.saveWorkspace(ws);
43933
+ if (ws) await electron$t.saveWorkspace(ws);
43588
43934
  } else {
43589
43935
  const entry = useStore$1.getState().mocks[serverId];
43590
43936
  const updated = { ...entry.data, routes: [...entry.data.routes, route] };
43591
43937
  updateMock(serverId, updated);
43592
- await electron$s.saveMock(entry.relPath, updated);
43938
+ await electron$t.saveMock(entry.relPath, updated);
43593
43939
  }
43594
43940
  onClose();
43595
43941
  } finally {
@@ -44107,7 +44453,7 @@ function validateHttpSemantics(res, opts = {}) {
44107
44453
  const order = { error: 0, warning: 1, hint: 2 };
44108
44454
  return f.sort((a, b) => order[a.severity] - order[b.severity]);
44109
44455
  }
44110
- const { electron: electron$r } = window;
44456
+ const { electron: electron$s } = window;
44111
44457
  function xmlWellFormed(body) {
44112
44458
  try {
44113
44459
  const doc = new DOMParser().parseFromString(body, "application/xml");
@@ -44337,7 +44683,7 @@ function ResponseViewer() {
44337
44683
  const schemaCheck = currentRequest?.schema?.trim() && response && !response.error ? validateBodyAgainstSchema(currentRequest.schema, response.body) : null;
44338
44684
  async function saveAsSchema() {
44339
44685
  if (!response || !requestId || !activeTabId) return;
44340
- const schema = response.body ? await electron$r.inferContractSchema(response.body) : null;
44686
+ const schema = response.body ? await electron$s.inferContractSchema(response.body) : null;
44341
44687
  if (!schema) return;
44342
44688
  updateRequest(requestId, { schema });
44343
44689
  setTabRequestTab(activeTabId, "schema");
@@ -44925,7 +45271,7 @@ function listFolders(root2, depth = 0, out = []) {
44925
45271
  }
44926
45272
  return out;
44927
45273
  }
44928
- const { electron: electron$q } = window;
45274
+ const { electron: electron$r } = window;
44929
45275
  function fmtMs(ms) {
44930
45276
  if (ms < 1) return "<1 ms";
44931
45277
  if (ms < 1e3) return `${Math.round(ms)} ms`;
@@ -45116,11 +45462,11 @@ function PerformancePanel() {
45116
45462
  setError("");
45117
45463
  }, [activeId]);
45118
45464
  reactExports.useEffect(() => {
45119
- electron$q.onPerfProgress((p) => {
45465
+ electron$r.onPerfProgress((p) => {
45120
45466
  setProgress(p);
45121
45467
  setSamples((s) => [...s, p].slice(-240));
45122
45468
  });
45123
- return () => electron$q.offPerfEvents();
45469
+ return () => electron$r.offPerfEvents();
45124
45470
  }, []);
45125
45471
  const requestOptions = reactExports.useMemo(() => {
45126
45472
  const out = [];
@@ -45167,7 +45513,7 @@ function PerformancePanel() {
45167
45513
  if (saveTimer2.current) clearTimeout(saveTimer2.current);
45168
45514
  saveTimer2.current = setTimeout(() => {
45169
45515
  const ws = useStore$1.getState().workspace;
45170
- if (ws) void electron$q.saveWorkspace(ws);
45516
+ if (ws) void electron$r.saveWorkspace(ws);
45171
45517
  }, 500);
45172
45518
  }
45173
45519
  async function run() {
@@ -45192,7 +45538,7 @@ function PerformancePanel() {
45192
45538
  collections: Object.values(collections).map((c) => c.data),
45193
45539
  ...common
45194
45540
  };
45195
- setResult(await electron$q.perfRun(payload));
45541
+ setResult(await electron$r.perfRun(payload));
45196
45542
  } catch (err) {
45197
45543
  setError(err instanceof Error ? err.message : String(err));
45198
45544
  } finally {
@@ -45201,14 +45547,14 @@ function PerformancePanel() {
45201
45547
  }
45202
45548
  }
45203
45549
  async function stop() {
45204
- await electron$q.perfStop();
45550
+ await electron$r.perfStop();
45205
45551
  }
45206
45552
  async function exportReport() {
45207
45553
  if (!result || !plan) return;
45208
45554
  setExporting(true);
45209
45555
  try {
45210
45556
  const target = plan.target.kind === "request" ? selectedRequest?.name ?? "request" : selectedFlow?.name ?? "flow";
45211
- await electron$q.perfExportHtml(result, { planName: plan.name, target });
45557
+ await electron$r.perfExportHtml(result, { planName: plan.name, target });
45212
45558
  } catch (err) {
45213
45559
  setError(err instanceof Error ? err.message : String(err));
45214
45560
  } finally {
@@ -45643,7 +45989,7 @@ function PerformancePanel() {
45643
45989
  !result && !running && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 flex items-center justify-center text-sm text-surface-500", children: t("Set the load and run this plan.") })
45644
45990
  ] });
45645
45991
  }
45646
- const { electron: electron$p } = window;
45992
+ const { electron: electron$q } = window;
45647
45993
  function makePlan(name) {
45648
45994
  return {
45649
45995
  id: crypto.randomUUID(),
@@ -45680,7 +46026,7 @@ function PerfPlansPanel() {
45680
46026
  const updated = { ...workspace, perfPlans: next };
45681
46027
  setWorkspace(updated, workspacePath ?? "");
45682
46028
  try {
45683
- await electron$p.saveWorkspace(updated);
46029
+ await electron$q.saveWorkspace(updated);
45684
46030
  } catch {
45685
46031
  }
45686
46032
  }
@@ -45824,7 +46170,7 @@ class ErrorBoundary extends React.Component {
45824
46170
  ] });
45825
46171
  }
45826
46172
  }
45827
- const { electron: electron$o } = window;
46173
+ const { electron: electron$p } = window;
45828
46174
  const TARGETS = [
45829
46175
  { id: "robot_framework", label: "Robot Framework", description: "Python RequestsLibrary keywords + test suite" },
45830
46176
  { id: "playwright_ts", label: "Playwright TS", description: "TypeScript page-object API classes + spec files" },
@@ -45866,7 +46212,7 @@ function GeneratorPanel() {
45866
46212
  try {
45867
46213
  const col = collections[selectedCollectionId]?.data;
45868
46214
  const env2 = resolveEnvironmentById(environments, activeEnvironmentId);
45869
- const generated = await electron$o.generateCode({ collection: col, environment: env2, target });
46215
+ const generated = await electron$p.generateCode({ collection: col, environment: env2, target });
45870
46216
  setFiles(generated);
45871
46217
  setSelectedFile(generated[0]?.path ?? null);
45872
46218
  } catch (e) {
@@ -45878,7 +46224,7 @@ function GeneratorPanel() {
45878
46224
  async function saveZip() {
45879
46225
  if (files.length === 0) return;
45880
46226
  const col = collections[selectedCollectionId]?.data;
45881
- await electron$o.saveGeneratedFilesAsZip(files, col?.name ?? "api-tests", target);
46227
+ await electron$p.saveGeneratedFilesAsZip(files, col?.name ?? "api-tests", target);
45882
46228
  }
45883
46229
  const selectedContent = files.find((f) => f.path === selectedFile)?.content ?? "";
45884
46230
  const activeTarget = TARGETS.find((tgt) => tgt.id === target);
@@ -46062,7 +46408,7 @@ function historyToHar(entries, creatorVersion = "1.0") {
46062
46408
  }
46063
46409
  }, null, 2);
46064
46410
  }
46065
- const { electron: electron$n } = window;
46411
+ const { electron: electron$o } = window;
46066
46412
  const STATUS_COLOR = {
46067
46413
  "2": "text-emerald-400",
46068
46414
  "3": "text-amber-400",
@@ -46116,7 +46462,7 @@ function HistoryPanel() {
46116
46462
  }, [history, search]);
46117
46463
  async function downloadHar() {
46118
46464
  const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replaceAll(/[:T]/g, "-");
46119
- await electron$n.saveResults(historyToHar(history), `api-spector-history-${stamp}.har`);
46465
+ await electron$o.saveResults(historyToHar(history), `api-spector-history-${stamp}.har`);
46120
46466
  }
46121
46467
  function open2(entry) {
46122
46468
  setSelected(entry);
@@ -46244,7 +46590,7 @@ function HistoryRow({
46244
46590
  }
46245
46591
  );
46246
46592
  }
46247
- const { electron: electron$m } = window;
46593
+ const { electron: electron$n } = window;
46248
46594
  function WelcomeScreen() {
46249
46595
  const t = useT();
46250
46596
  const { applyWorkspace } = useWorkspaceLoader();
@@ -46257,10 +46603,10 @@ function WelcomeScreen() {
46257
46603
  const busyRef = reactExports.useRef(false);
46258
46604
  const [busy, setBusy] = reactExports.useState(false);
46259
46605
  reactExports.useEffect(() => {
46260
- electron$m.getRecentWorkspaces().then(setRecents).catch(() => setRecents([]));
46261
- electron$m.getUpdateMode().then((mode) => {
46606
+ electron$n.getRecentWorkspaces().then(setRecents).catch(() => setRecents([]));
46607
+ electron$n.getUpdateMode().then((mode) => {
46262
46608
  if (mode !== "npm") return;
46263
- return electron$m.checkForUpdate().then((info) => {
46609
+ return electron$n.checkForUpdate().then((info) => {
46264
46610
  if (info?.updateAvailable) setUpdate(info);
46265
46611
  });
46266
46612
  }).catch(() => {
@@ -46271,7 +46617,7 @@ function WelcomeScreen() {
46271
46617
  busyRef.current = true;
46272
46618
  setBusy(true);
46273
46619
  try {
46274
- const result = await electron$m.openWorkspace();
46620
+ const result = await electron$n.openWorkspace();
46275
46621
  if (result) await applyWorkspace(result.workspace, result.workspacePath);
46276
46622
  } finally {
46277
46623
  busyRef.current = false;
@@ -46283,7 +46629,7 @@ function WelcomeScreen() {
46283
46629
  busyRef.current = true;
46284
46630
  setBusy(true);
46285
46631
  try {
46286
- const result = await electron$m.newWorkspace();
46632
+ const result = await electron$n.newWorkspace();
46287
46633
  if (result) await applyWorkspace(result.workspace, result.workspacePath);
46288
46634
  } finally {
46289
46635
  busyRef.current = false;
@@ -46297,7 +46643,7 @@ function WelcomeScreen() {
46297
46643
  setGitBusy(true);
46298
46644
  setGitError(null);
46299
46645
  try {
46300
- const result = await electron$m.openFromGit(url);
46646
+ const result = await electron$n.openFromGit(url);
46301
46647
  if (!result) return;
46302
46648
  await applyWorkspace(result.workspace, result.workspacePath);
46303
46649
  } catch (err) {
@@ -46312,7 +46658,7 @@ function WelcomeScreen() {
46312
46658
  busyRef.current = true;
46313
46659
  setBusy(true);
46314
46660
  try {
46315
- const result = await electron$m.openWorkspacePath(path);
46661
+ const result = await electron$n.openWorkspacePath(path);
46316
46662
  if (!result) {
46317
46663
  setRecents((prev) => prev.filter((r) => r.path !== path));
46318
46664
  return;
@@ -46348,7 +46694,7 @@ function WelcomeScreen() {
46348
46694
  )
46349
46695
  ] }),
46350
46696
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-400 text-sm max-w-sm", children: t("Local-first API testing with Robot Framework & Playwright code generation. Secrets stay on your machine.") }),
46351
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] mt-3", style: { color: "var(--text-muted)" }, children: t("version :version", { version: "0.9.0" }) })
46697
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] mt-3", style: { color: "var(--text-muted)" }, children: t("version :version", { version: "0.9.2" }) })
46352
46698
  ] }),
46353
46699
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 w-64", children: [
46354
46700
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -46430,7 +46776,7 @@ function WelcomeScreen() {
46430
46776
  ] })
46431
46777
  ] });
46432
46778
  }
46433
- const { electron: electron$l } = window;
46779
+ const { electron: electron$m } = window;
46434
46780
  const EXAMPLES = [
46435
46781
  {
46436
46782
  label: "macOS / Linux (~/.zshrc or ~/.bashrc)",
@@ -46455,7 +46801,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
46455
46801
  setError(t("Password cannot be empty."));
46456
46802
  return;
46457
46803
  }
46458
- await electron$l.setMasterKey(password);
46804
+ await electron$m.setMasterKey(password);
46459
46805
  onSuccess(password);
46460
46806
  }
46461
46807
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
@@ -46534,7 +46880,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
46534
46880
  }
46535
46881
  );
46536
46882
  }
46537
- const { electron: electron$k } = window;
46883
+ const { electron: electron$l } = window;
46538
46884
  async function shortHash(value2) {
46539
46885
  const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value2));
46540
46886
  return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 8);
@@ -46649,12 +46995,12 @@ function EnvironmentEditor({ onClose }) {
46649
46995
  if (next) setSelectedId(next);
46650
46996
  }
46651
46997
  async function importFromPostman() {
46652
- const imported = await electron$k.importPostmanEnvironment();
46998
+ const imported = await electron$l.importPostmanEnvironment();
46653
46999
  if (!imported) return;
46654
47000
  const existingNames = Object.values(useStore$1.getState().environments).map((e) => e.data.name);
46655
47001
  const finalEnv = { ...imported, name: uniqueName$1(imported.name || t("Imported Environment"), existingNames) };
46656
47002
  const relPath = envRelPath(finalEnv.name, finalEnv.id);
46657
- await electron$k.saveEnvironment(relPath, finalEnv);
47003
+ await electron$l.saveEnvironment(relPath, finalEnv);
46658
47004
  useStore$1.setState((s) => {
46659
47005
  s.environments[finalEnv.id] = { relPath, data: finalEnv };
46660
47006
  if (!s.activeEnvironmentId) s.activeEnvironmentId = finalEnv.id;
@@ -46664,7 +47010,7 @@ function EnvironmentEditor({ onClose }) {
46664
47010
  return s;
46665
47011
  });
46666
47012
  const ws = useStore$1.getState().workspace;
46667
- if (ws) await electron$k.saveWorkspace(ws);
47013
+ if (ws) await electron$l.saveWorkspace(ws);
46668
47014
  setSelectedId(finalEnv.id);
46669
47015
  }
46670
47016
  function updateVar(idx, patch) {
@@ -46717,9 +47063,9 @@ function EnvironmentEditor({ onClose }) {
46717
47063
  async function saveEncrypted(idx) {
46718
47064
  const plaintext = secretInputs[idx] ?? "";
46719
47065
  if (!plaintext) return;
46720
- const { set: set2 } = await electron$k.checkMasterKey();
47066
+ const { set: set2 } = await electron$l.checkMasterKey();
46721
47067
  if (set2) {
46722
- applyEncrypted(idx, await electron$k.encryptWithMasterKey(plaintext));
47068
+ applyEncrypted(idx, await electron$l.encryptWithMasterKey(plaintext));
46723
47069
  return;
46724
47070
  }
46725
47071
  setPendingEncryptIdx(idx);
@@ -46754,9 +47100,9 @@ function EnvironmentEditor({ onClose }) {
46754
47100
  } : state.workspace
46755
47101
  }));
46756
47102
  const ws = useStore$1.getState().workspace;
46757
- if (ws) await electron$k.saveWorkspace(ws);
47103
+ if (ws) await electron$l.saveWorkspace(ws);
46758
47104
  }
46759
- await electron$k.saveEnvironment(newRelPath, env2);
47105
+ await electron$l.saveEnvironment(newRelPath, env2);
46760
47106
  }
46761
47107
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
46762
47108
  confirmDialog,
@@ -47121,7 +47467,7 @@ function EnvironmentEditor({ onClose }) {
47121
47467
  )
47122
47468
  ] });
47123
47469
  }
47124
- const { electron: electron$j } = window;
47470
+ const { electron: electron$k } = window;
47125
47471
  function EnvironmentBar({ inline = false }) {
47126
47472
  const t = useT();
47127
47473
  const environments = useStore$1((s) => s.environments);
@@ -47137,7 +47483,7 @@ function EnvironmentBar({ inline = false }) {
47137
47483
  if (id2) {
47138
47484
  const hasSecrets = environments[id2]?.data.variables.some((v) => v.enabled && v.secret);
47139
47485
  if (hasSecrets) {
47140
- const { set: set2 } = await electron$j.checkMasterKey();
47486
+ const { set: set2 } = await electron$k.checkMasterKey();
47141
47487
  if (!set2) {
47142
47488
  setPendingEnvId(id2);
47143
47489
  return;
@@ -47344,10 +47690,204 @@ function ProductTour() {
47344
47690
  )
47345
47691
  ] });
47346
47692
  }
47693
+ const { electron: electron$j } = window;
47694
+ function getSyncSettings() {
47695
+ return useStore$1.getState().workspace?.settings?.sync ?? { mode: "git" };
47696
+ }
47697
+ async function persistSync(patch) {
47698
+ const s = useStore$1.getState();
47699
+ const ws = s.workspace;
47700
+ if (!ws) return;
47701
+ const sync = { ...ws.settings?.sync ?? { mode: "cloud" }, ...patch };
47702
+ const settings = { ...ws.settings, sync };
47703
+ s.updateWorkspaceSettings(settings);
47704
+ await electron$j.saveWorkspace({ ...ws, settings });
47705
+ }
47706
+ async function setSyncMode(mode) {
47707
+ await persistSync({ mode });
47708
+ }
47709
+ function workspaceName(manifestName) {
47710
+ return manifestName.replace(/\.(spector|json)$/i, "") || "Workspace";
47711
+ }
47712
+ async function flushToDisk() {
47713
+ const s = useStore$1.getState();
47714
+ for (const { relPath, data, dirty } of Object.values(s.collections)) {
47715
+ if (!dirty) continue;
47716
+ await electron$j.saveCollection(relPath, data);
47717
+ s.markCollectionClean(data.id);
47718
+ }
47719
+ for (const { relPath, data } of Object.values(s.environments)) {
47720
+ await electron$j.saveEnvironment(relPath, data);
47721
+ }
47722
+ for (const { relPath, data } of Object.values(s.flows)) {
47723
+ await electron$j.saveFlow(relPath, data);
47724
+ }
47725
+ if (s.workspace) await electron$j.saveWorkspace(s.workspace);
47726
+ }
47727
+ async function pushWorkspace() {
47728
+ const sync = getSyncSettings();
47729
+ await flushToDisk();
47730
+ const snap = await electron$j.serializeWorkspace();
47731
+ const files = snap.files ?? {};
47732
+ const collections = Object.keys(files).filter((p) => p.startsWith("collections/")).length;
47733
+ const flows = Object.keys(files).filter((p) => p.startsWith("flows/")).length;
47734
+ if (!sync.cloudWorkspaceId) {
47735
+ const created = await electron$j.cloudWorkspaceCreate({ name: workspaceName(snap.manifestName), payload: snap });
47736
+ await persistSync({ mode: "cloud", cloudWorkspaceId: created.id, baseVersion: created.version });
47737
+ return { ok: true, version: created.version, collections, flows };
47738
+ }
47739
+ const res = await electron$j.cloudWorkspacePush({
47740
+ id: sync.cloudWorkspaceId,
47741
+ baseVersion: sync.baseVersion ?? 0,
47742
+ payload: snap
47743
+ });
47744
+ if (res.conflict) return { ok: false, conflict: true, currentVersion: res.currentVersion };
47745
+ await persistSync({ baseVersion: res.version });
47746
+ return { ok: true, version: res.version ?? 0, collections, flows };
47747
+ }
47748
+ async function pullWorkspace() {
47749
+ const sync = getSyncSettings();
47750
+ if (!sync.cloudWorkspaceId) {
47751
+ throw new Error("This workspace is not linked to a cloud workspace yet. Push it first, or link an existing one.");
47752
+ }
47753
+ const remote = await electron$j.cloudWorkspacePull(sync.cloudWorkspaceId);
47754
+ const payload = remote.payload ?? {};
47755
+ if (!payload.manifest || !payload.files) {
47756
+ throw new Error("The cloud workspace has no snapshot yet. Push from a populated workspace first.");
47757
+ }
47758
+ const applied = await electron$j.applyWorkspaceSnapshot({ manifest: payload.manifest, files: payload.files });
47759
+ return {
47760
+ version: remote.version,
47761
+ filesWritten: applied.filesWritten,
47762
+ collections: applied.collections,
47763
+ workspace: applied.workspace,
47764
+ workspacePath: applied.workspacePath
47765
+ };
47766
+ }
47767
+ async function markPulled(version) {
47768
+ await persistSync({ baseVersion: version });
47769
+ }
47770
+ async function listCloudWorkspaces() {
47771
+ const res = await electron$j.cloudWorkspaceList();
47772
+ return res.workspaces;
47773
+ }
47774
+ async function linkToCloudWorkspace(id2) {
47775
+ await persistSync({ mode: "cloud", cloudWorkspaceId: id2, baseVersion: 0 });
47776
+ }
47777
+ function SyncSettingsPanel() {
47778
+ const t = useT();
47779
+ const { applyWorkspace } = useWorkspaceLoader();
47780
+ const initial = getSyncSettings();
47781
+ const [mode, setMode] = reactExports.useState(initial.mode);
47782
+ const [linked, setLinked] = reactExports.useState(!!initial.cloudWorkspaceId);
47783
+ const [busy, setBusy] = reactExports.useState(false);
47784
+ const [status, setStatus] = reactExports.useState(null);
47785
+ const [remote, setRemote] = reactExports.useState(null);
47786
+ const cloudOn = cloudEnabled();
47787
+ async function changeMode(m) {
47788
+ setMode(m);
47789
+ setStatus(null);
47790
+ try {
47791
+ await setSyncMode(m);
47792
+ } catch (e) {
47793
+ setStatus({ msg: e.message, ok: false });
47794
+ }
47795
+ }
47796
+ async function run(fn) {
47797
+ setBusy(true);
47798
+ setStatus(null);
47799
+ try {
47800
+ setStatus({ msg: await fn(), ok: true });
47801
+ } catch (e) {
47802
+ setStatus({ msg: e.message, ok: false });
47803
+ } finally {
47804
+ setBusy(false);
47805
+ }
47806
+ }
47807
+ const doPush = () => run(async () => {
47808
+ const res = await pushWorkspace();
47809
+ if (!res.ok) throw new Error(t("Someone else pushed changes (cloud is at v:v). Pull first, then push again.", { v: res.currentVersion ?? "?" }));
47810
+ setLinked(true);
47811
+ return t("Pushed :c collection(s) and :f flow(s). Cloud is now at version :v.", { c: res.collections, f: res.flows, v: res.version });
47812
+ });
47813
+ const doPull = () => run(async () => {
47814
+ const r = await pullWorkspace();
47815
+ await applyWorkspace(r.workspace, r.workspacePath);
47816
+ await markPulled(r.version);
47817
+ return t("Pulled version :v: :files file(s), :c collection(s).", { v: r.version, files: r.filesWritten, c: r.collections });
47818
+ });
47819
+ const loadRemote = () => run(async () => {
47820
+ const list = await listCloudWorkspaces();
47821
+ setRemote(list);
47822
+ return t(":n cloud workspace(s) in your organization.", { n: list.length });
47823
+ });
47824
+ const joinExisting = (id2) => run(async () => {
47825
+ await linkToCloudWorkspace(id2);
47826
+ const r = await pullWorkspace();
47827
+ await applyWorkspace(r.workspace, r.workspacePath);
47828
+ await markPulled(r.version);
47829
+ setLinked(true);
47830
+ return t("Linked and pulled: :files file(s), :c collection(s).", { files: r.filesWritten, c: r.collections });
47831
+ });
47832
+ const btn = "px-3 py-1.5 text-xs rounded border border-surface-700 text-surface-200 hover:bg-surface-800 disabled:opacity-50 transition-colors";
47833
+ const btnPrimary = "px-3 py-1.5 text-xs rounded font-medium text-white bg-sky-700 hover:bg-sky-600 disabled:opacity-50 transition-colors";
47834
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-5 text-sm text-surface-200", children: [
47835
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
47836
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-semibold text-[var(--text-primary)]", children: t("Workspace sync") }),
47837
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400 mt-1", children: t("How this workspace is shared with your team. Git is the default; Cloud lets teams without git collaborate.") })
47838
+ ] }),
47839
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex flex-col gap-1", children: [
47840
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs uppercase tracking-wide text-surface-500", children: t("Mode") }),
47841
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
47842
+ "select",
47843
+ {
47844
+ value: mode,
47845
+ onChange: (e) => changeMode(e.target.value),
47846
+ className: "rounded bg-surface-950 border border-surface-700 px-2 py-1.5 text-sm w-56",
47847
+ children: [
47848
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "git", children: t("Git (default) — use the Git panel") }),
47849
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "cloud", children: t("Cloud — sync through API Spector Cloud") }),
47850
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "local", children: t("Local only — no sync") })
47851
+ ]
47852
+ }
47853
+ )
47854
+ ] }),
47855
+ mode === "git" && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400", children: t("This workspace syncs via Git. Use the Git panel in the sidebar to commit, pull and push.") }),
47856
+ mode === "local" && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400", children: t("No syncing. Changes stay on this machine.") }),
47857
+ mode === "cloud" && !cloudOn && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-amber-400", children: t("Connect this workspace to API Spector Cloud first (the Cloud tab), then come back to sync.") }),
47858
+ mode === "cloud" && cloudOn && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3", children: [
47859
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: t("Collections, folders, requests, flows and environments sync. Secret values are never uploaded — each member keeps their own.") }),
47860
+ linked ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
47861
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: btnPrimary, disabled: busy, onClick: doPush, children: t("Push") }),
47862
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: btn, disabled: busy, onClick: doPull, children: t("Pull") })
47863
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3", children: [
47864
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
47865
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: btnPrimary, disabled: busy, onClick: doPush, children: t("Create cloud workspace (push)") }),
47866
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: btn, disabled: busy, onClick: loadRemote, children: t("Join an existing one") })
47867
+ ] }),
47868
+ remote && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-surface-700 divide-y divide-surface-800", children: [
47869
+ remote.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-2 text-xs text-surface-500", children: t("No cloud workspaces yet.") }),
47870
+ remote.map((w) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [
47871
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex-1 truncate", children: [
47872
+ w.name,
47873
+ " ",
47874
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500", children: [
47875
+ "v",
47876
+ w.version
47877
+ ] })
47878
+ ] }),
47879
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: btn, disabled: busy, onClick: () => joinExisting(w.id), children: t("Join & pull") })
47880
+ ] }, w.id))
47881
+ ] })
47882
+ ] })
47883
+ ] }),
47884
+ status && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: `text-xs ${status.ok ? "text-emerald-400" : "text-red-400"}`, children: status.msg })
47885
+ ] });
47886
+ }
47347
47887
  const { electron: electron$i } = window;
47348
47888
  const DEFAULT_PII_PATTERNS = ["authorization", "password", "token", "secret", "api-key", "x-api-key"];
47349
47889
  const ZOOM_STEPS = [0.75, 0.9, 1, 1.1, 1.25, 1.5];
47350
- const SETTINGS_TABS = ["general", "appearance", "proxy", "tls", "privacy", "cloud", "ai", "secrets"];
47890
+ const SETTINGS_TABS = ["general", "appearance", "proxy", "tls", "privacy", "cloud", "sync", "ai", "secrets"];
47351
47891
  function WorkspaceSettingsModal({ onClose, initialTab }) {
47352
47892
  const workspace = useStore$1((s) => s.workspace);
47353
47893
  const updateWorkspaceSettings = useStore$1((s) => s.updateWorkspaceSettings);
@@ -47555,6 +48095,7 @@ function WorkspaceSettingsModal({ onClose, initialTab }) {
47555
48095
  { id: "privacy", label: t("Privacy") },
47556
48096
  { id: "secrets", label: t("Secrets") },
47557
48097
  { id: "cloud", label: t("Cloud") },
48098
+ { id: "sync", label: t("Sync") },
47558
48099
  { id: "ai", label: t("AI") }
47559
48100
  ];
47560
48101
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
@@ -47879,6 +48420,7 @@ function WorkspaceSettingsModal({ onClose, initialTab }) {
47879
48420
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("Reject unauthorized / self-signed certificates") })
47880
48421
  ] })
47881
48422
  ] }),
48423
+ activeTab === "sync" && /* @__PURE__ */ jsxRuntimeExports.jsx(SyncSettingsPanel, {}),
47882
48424
  activeTab === "ai" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
47883
48425
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-600 text-[11px] leading-relaxed", children: t("Optional. Configure an OpenAI key to generate documentation from the Docs tab of a request, folder, or collection. The key is stored only in your OS keychain and never written to the workspace file or version control.") }),
47884
48426
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
@@ -47965,10 +48507,17 @@ function WorkspaceSettingsModal({ onClose, initialTab }) {
47965
48507
  " ",
47966
48508
  t("in an environment variable or an auth field; it is resolved at send-time and never written to the workspace. This connection config is non-secret - tokens and secret-ids come from your environment (or the Vault sign-in below).")
47967
48509
  ] }),
48510
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-surface-600 text-[11px]", children: [
48511
+ t("Using OpenBao? It is API-compatible, so the same settings apply. Reference secrets with"),
48512
+ " ",
48513
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-surface-400", children: "bao:secret/data/app#token" }),
48514
+ " ",
48515
+ t("and set BAO_ADDR / BAO_TOKEN (VAULT_* also works).")
48516
+ ] }),
47968
48517
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2 border border-surface-800 rounded p-3", children: [
47969
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] uppercase tracking-wider text-surface-500 font-semibold", children: "HashiCorp Vault" }),
48518
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] uppercase tracking-wider text-surface-500 font-semibold", children: t("HashiCorp Vault / OpenBao") }),
47970
48519
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
47971
- /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: labelCls, children: t("Address (VAULT_ADDR)") }),
48520
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: labelCls, children: t("Address (VAULT_ADDR / BAO_ADDR)") }),
47972
48521
  /* @__PURE__ */ jsxRuntimeExports.jsx(
47973
48522
  "input",
47974
48523
  {
@@ -49249,6 +49798,46 @@ function collectRequestsByFolder(folder2, src) {
49249
49798
  walk2(folder2);
49250
49799
  return out;
49251
49800
  }
49801
+ function Tooltip({
49802
+ label,
49803
+ align = "center",
49804
+ children: children2
49805
+ }) {
49806
+ const pos = align === "right" ? "right-0" : align === "left" ? "left-0" : "left-1/2 -translate-x-1/2";
49807
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "relative inline-flex group/tip", children: [
49808
+ children2,
49809
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
49810
+ "span",
49811
+ {
49812
+ "aria-hidden": "true",
49813
+ className: `pointer-events-none absolute top-full ${pos} mt-1.5 z-50 hidden group-hover/tip:block group-focus-within/tip:block whitespace-nowrap rounded px-2 py-1 text-[11px] bg-surface-900 text-[var(--text-primary)] border border-surface-700 shadow-lg`,
49814
+ children: label
49815
+ }
49816
+ )
49817
+ ] });
49818
+ }
49819
+ function DocIcon() {
49820
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { className: "w-3 h-3", fill: "none", stroke: "currentColor", strokeWidth: 1.8, viewBox: "0 0 24 24", "aria-hidden": "true", children: [
49821
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M14 3H7a2 2 0 00-2 2v14a2 2 0 002 2h10a2 2 0 002-2V8l-5-5z" }),
49822
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M14 3v5h5M8.5 13h7M8.5 16.5h5" })
49823
+ ] });
49824
+ }
49825
+ function ImportIcon() {
49826
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { className: "w-3 h-3", fill: "none", stroke: "currentColor", strokeWidth: 1.8, viewBox: "0 0 24 24", "aria-hidden": "true", children: [
49827
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 3v10m0 0l-3.5-3.5M12 13l3.5-3.5" }),
49828
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4 16v3a2 2 0 002 2h12a2 2 0 002-2v-3" })
49829
+ ] });
49830
+ }
49831
+ function Svg({ children: children2 }) {
49832
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "w-3 h-3", fill: "none", stroke: "currentColor", strokeWidth: 1.8, viewBox: "0 0 24 24", "aria-hidden": "true", children: children2 });
49833
+ }
49834
+ const OpenWsIcon = () => /* @__PURE__ */ jsxRuntimeExports.jsx(Svg, { children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3.75 9.75h16.5a1.5 1.5 0 011.47 1.8l-1.2 6a1.5 1.5 0 01-1.47 1.2H4.95a1.5 1.5 0 01-1.47-1.2l-1.2-6a1.5 1.5 0 011.47-1.8zM3.75 9.75V6.75A1.5 1.5 0 015.25 5.25h3.88a1.5 1.5 0 011.06.44l1.12 1.12a1.5 1.5 0 001.06.44h4.88a1.5 1.5 0 011.5 1.5v.56" }) });
49835
+ const NewWsIcon = () => /* @__PURE__ */ jsxRuntimeExports.jsx(Svg, { children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 11v5m2.5-2.5h-5M3.75 7.5V18a1.5 1.5 0 001.5 1.5h13.5A1.5 1.5 0 0020.25 18V9a1.5 1.5 0 00-1.5-1.5h-5.19a1.5 1.5 0 01-1.06-.44l-1.12-1.12a1.5 1.5 0 00-1.06-.44H5.25a1.5 1.5 0 00-1.5 1.5z" }) });
49836
+ const CloseWsIcon = () => /* @__PURE__ */ jsxRuntimeExports.jsx(Svg, { children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 6l12 12M18 6L6 18" }) });
49837
+ const SaveIcon = () => /* @__PURE__ */ jsxRuntimeExports.jsxs(Svg, { children: [
49838
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 4h11l3 3v12a1 1 0 01-1 1H5a1 1 0 01-1-1V5a1 1 0 011-1z" }),
49839
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8 4v4h6V4M8 13h8v6H8z" })
49840
+ ] });
49252
49841
  const { electron: electron$f } = window;
49253
49842
  function Toolbar({ onOpenDocs: _onOpenDocs }) {
49254
49843
  const t = useT();
@@ -49360,34 +49949,51 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
49360
49949
  .logo-pupil-r { animation-delay: 0.04s; }
49361
49950
  ` }),
49362
49951
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 mr-1 shrink-0 select-none", children: [
49363
- /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "52", height: "52", viewBox: "0 0 512 512", xmlns: "http://www.w3.org/2000/svg", children: [
49364
- /* @__PURE__ */ jsxRuntimeExports.jsxs("defs", { children: [
49365
- /* @__PURE__ */ jsxRuntimeExports.jsxs("radialGradient", { id: "logoBody", cx: "35%", cy: "22%", r: "70%", children: [
49366
- /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "0%", stopColor: "#5497c8" }),
49367
- /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "55%", stopColor: "#205d96" }),
49368
- /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "100%", stopColor: "#123a60" })
49369
- ] }),
49370
- /* @__PURE__ */ jsxRuntimeExports.jsxs("linearGradient", { id: "logoGloss", x1: "0", y1: "0", x2: "0", y2: "1", children: [
49371
- /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "0%", stopColor: "white", stopOpacity: "0.45" }),
49372
- /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "100%", stopColor: "white", stopOpacity: "0" })
49373
- ] }),
49374
- /* @__PURE__ */ jsxRuntimeExports.jsx("clipPath", { id: "logoGhostClip", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M 130,225 C 130,95 382,95 382,225 L 382,390 Q 340,435 298,390 Q 256,435 214,390 Q 172,435 130,390 Z" }) })
49375
- ] }),
49376
- /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M 130,225 C 130,95 382,95 382,225 L 382,390 Q 340,435 298,390 Q 256,435 214,390 Q 172,435 130,390 Z", fill: "url(#logoBody)" }),
49377
- /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "198", cy: "262", rx: "29", ry: "33", fill: "white" }),
49378
- /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "314", cy: "262", rx: "29", ry: "33", fill: "white" }),
49379
- /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "204", cy: "268", rx: "16", ry: "20", fill: "#0b1624", className: "logo-pupil" }),
49380
- /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "320", cy: "268", rx: "16", ry: "20", fill: "#0b1624", className: "logo-pupil logo-pupil-r" }),
49381
- /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "194", cy: "254", rx: "6", ry: "7", fill: "white", opacity: "0.65" }),
49382
- /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "310", cy: "254", rx: "6", ry: "7", fill: "white", opacity: "0.65" }),
49383
- /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "256", cy: "100", rx: "200", ry: "180", fill: "url(#logoGloss)", clipPath: "url(#logoGhostClip)" })
49384
- ] }),
49952
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
49953
+ "button",
49954
+ {
49955
+ onClick: () => window.electron.openExternal("https://api-spector.dev"),
49956
+ title: t("Open api-spector.dev"),
49957
+ "aria-label": t("API Spector home"),
49958
+ className: "shrink-0 focus:outline-none rounded hover:opacity-80 transition-opacity cursor-pointer",
49959
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "52", height: "52", viewBox: "0 0 512 512", xmlns: "http://www.w3.org/2000/svg", children: [
49960
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("defs", { children: [
49961
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("radialGradient", { id: "logoBody", cx: "35%", cy: "22%", r: "70%", children: [
49962
+ /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "0%", stopColor: "#5497c8" }),
49963
+ /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "55%", stopColor: "#205d96" }),
49964
+ /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "100%", stopColor: "#123a60" })
49965
+ ] }),
49966
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("linearGradient", { id: "logoGloss", x1: "0", y1: "0", x2: "0", y2: "1", children: [
49967
+ /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "0%", stopColor: "white", stopOpacity: "0.45" }),
49968
+ /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "100%", stopColor: "white", stopOpacity: "0" })
49969
+ ] }),
49970
+ /* @__PURE__ */ jsxRuntimeExports.jsx("clipPath", { id: "logoGhostClip", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M 130,225 C 130,95 382,95 382,225 L 382,390 Q 340,435 298,390 Q 256,435 214,390 Q 172,435 130,390 Z" }) })
49971
+ ] }),
49972
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M 130,225 C 130,95 382,95 382,225 L 382,390 Q 340,435 298,390 Q 256,435 214,390 Q 172,435 130,390 Z", fill: "url(#logoBody)" }),
49973
+ /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "198", cy: "262", rx: "29", ry: "33", fill: "white" }),
49974
+ /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "314", cy: "262", rx: "29", ry: "33", fill: "white" }),
49975
+ /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "204", cy: "268", rx: "16", ry: "20", fill: "#0b1624", className: "logo-pupil" }),
49976
+ /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "320", cy: "268", rx: "16", ry: "20", fill: "#0b1624", className: "logo-pupil logo-pupil-r" }),
49977
+ /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "194", cy: "254", rx: "6", ry: "7", fill: "white", opacity: "0.65" }),
49978
+ /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "310", cy: "254", rx: "6", ry: "7", fill: "white", opacity: "0.65" }),
49979
+ /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "256", cy: "100", rx: "200", ry: "180", fill: "url(#logoGloss)", clipPath: "url(#logoGhostClip)" })
49980
+ ] })
49981
+ }
49982
+ ),
49385
49983
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col leading-tight", children: [
49386
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs font-semibold tracking-wide", children: [
49387
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--wordmark-muted)" }, children: "API" }),
49388
- " ",
49389
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--wordmark-accent)" }, children: "Spector" })
49390
- ] }),
49984
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
49985
+ "button",
49986
+ {
49987
+ onClick: () => window.electron.openExternal("https://api-spector.dev"),
49988
+ title: t("Open api-spector.dev"),
49989
+ className: "text-xs font-semibold tracking-wide text-left hover:underline focus:outline-none cursor-pointer",
49990
+ children: [
49991
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--wordmark-muted)" }, children: "API" }),
49992
+ " ",
49993
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--wordmark-accent)" }, children: "Spector" })
49994
+ ]
49995
+ }
49996
+ ),
49391
49997
  /* @__PURE__ */ jsxRuntimeExports.jsx(
49392
49998
  "button",
49393
49999
  {
@@ -49399,92 +50005,98 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
49399
50005
  )
49400
50006
  ] })
49401
50007
  ] }),
49402
- /* @__PURE__ */ jsxRuntimeExports.jsx(
49403
- "button",
49404
- {
49405
- onClick: () => setImportOpen(true),
49406
- className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors shrink-0",
49407
- title: t("Import collection (Postman, OpenAPI, Insomnia, Bruno)"),
49408
- children: t("Import")
49409
- }
49410
- ),
49411
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-4 bg-surface-800 mx-1 shrink-0" }),
49412
50008
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 text-xs shrink-0", children: t("ENV") }),
49413
50009
  /* @__PURE__ */ jsxRuntimeExports.jsx(EnvironmentBar, { inline: true })
49414
50010
  ] }),
49415
50011
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-3 py-1.5 shrink-0 border-l border-surface-800", children: [
49416
- /* @__PURE__ */ jsxRuntimeExports.jsx(
50012
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 text-xs shrink-0", children: t("Workspace") }),
50013
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { label: t("Open a different workspace"), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
49417
50014
  "button",
49418
50015
  {
49419
50016
  onClick: openWs,
49420
50017
  disabled: wsBusy,
49421
- className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
49422
- title: t("Open a different workspace"),
49423
- children: t("Open WS")
50018
+ className: "p-1.5 bg-surface-800 hover:bg-surface-700 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
50019
+ "aria-label": t("Open a different workspace"),
50020
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(OpenWsIcon, {})
49424
50021
  }
49425
- ),
49426
- /* @__PURE__ */ jsxRuntimeExports.jsx(
50022
+ ) }),
50023
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { label: t("Create a new workspace"), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
49427
50024
  "button",
49428
50025
  {
49429
50026
  onClick: newWs,
49430
50027
  disabled: wsBusy,
49431
- className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
49432
- title: t("Create a new workspace"),
49433
- children: t("New WS")
50028
+ className: "p-1.5 bg-surface-800 hover:bg-surface-700 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
50029
+ "aria-label": t("Create a new workspace"),
50030
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(NewWsIcon, {})
49434
50031
  }
49435
- ),
49436
- /* @__PURE__ */ jsxRuntimeExports.jsx(
50032
+ ) }),
50033
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { label: t("Close current workspace"), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
49437
50034
  "button",
49438
50035
  {
49439
50036
  onClick: async () => {
49440
50037
  await electron$f.closeWorkspace();
49441
50038
  closeWorkspace();
49442
50039
  },
49443
- className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
49444
- title: t("Close current workspace"),
49445
- children: t("Close WS")
50040
+ className: "p-1.5 bg-surface-800 hover:bg-surface-700 rounded transition-colors",
50041
+ "aria-label": t("Close current workspace"),
50042
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseWsIcon, {})
49446
50043
  }
49447
- ),
49448
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
50044
+ ) }),
50045
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { label: saving ? t("Saving…") : t("Save all unsaved changes"), children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
49449
50046
  "button",
49450
50047
  {
49451
50048
  onClick: saveAll,
49452
50049
  disabled: saving,
49453
- className: `px-2.5 py-1 text-xs rounded transition-colors flex items-center gap-1.5 ${hasDirty ? "bg-blue-700 hover:bg-blue-600 text-white" : "bg-surface-800 hover:bg-surface-700 text-surface-500"}`,
49454
- title: t("Save all unsaved changes"),
50050
+ className: `relative p-1.5 rounded transition-colors ${hasDirty ? "bg-blue-700 hover:bg-blue-600 text-white" : "bg-surface-800 hover:bg-surface-700 text-surface-500"} ${saving ? "opacity-60" : ""}`,
50051
+ "aria-label": t("Save all unsaved changes"),
49455
50052
  children: [
49456
- hasDirty && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-amber-400 inline-block" }),
49457
- saving ? t("Saving…") : t("Save")
50053
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SaveIcon, {}),
50054
+ hasDirty && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "absolute -top-0.5 -right-0.5 w-1.5 h-1.5 rounded-full bg-amber-400" })
49458
50055
  ]
49459
50056
  }
49460
- ),
49461
- /* @__PURE__ */ jsxRuntimeExports.jsx(
49462
- "button",
49463
- {
49464
- onClick: () => setWorkspaceSettingsOpen(true),
49465
- className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
49466
- title: t("Workspace settings"),
49467
- children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: "16px", lineHeight: 1 }, children: "⚙" })
49468
- }
49469
- ),
49470
- /* @__PURE__ */ jsxRuntimeExports.jsx(
50057
+ ) }),
50058
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-4 bg-surface-800 mx-1 shrink-0" }),
50059
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { label: t("Import collection (Postman, OpenAPI, Insomnia, Bruno)"), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
49471
50060
  "button",
49472
50061
  {
49473
- onClick: () => setDocsOpen(true),
49474
- className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
49475
- title: t("Generate API documentation"),
49476
- children: t("Docs")
50062
+ onClick: () => setImportOpen(true),
50063
+ className: "p-1.5 bg-surface-800 hover:bg-surface-700 rounded transition-colors",
50064
+ "aria-label": t("Import collection (Postman, OpenAPI, Insomnia, Bruno)"),
50065
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(ImportIcon, {})
49477
50066
  }
49478
- ),
49479
- /* @__PURE__ */ jsxRuntimeExports.jsx(
50067
+ ) }),
50068
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-4 bg-surface-800 mx-1 shrink-0" }),
50069
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
50070
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 text-xs", children: t("Generate") }),
50071
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { label: t("Generate API documentation"), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
50072
+ "button",
50073
+ {
50074
+ onClick: () => setDocsOpen(true),
50075
+ className: "p-1.5 bg-surface-800 hover:bg-surface-700 rounded transition-colors",
50076
+ "aria-label": t("Generate API documentation"),
50077
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(DocIcon, {})
50078
+ }
50079
+ ) }),
50080
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { label: t("Toggle code generator"), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
50081
+ "button",
50082
+ {
50083
+ onClick: () => setShowGeneratorPanel(!showGeneratorPanel),
50084
+ className: `px-2 py-1 text-xs font-mono rounded transition-colors ${showGeneratorPanel ? "bg-blue-700 text-white" : "bg-surface-800 hover:bg-surface-700"}`,
50085
+ "aria-label": t("Toggle code generator"),
50086
+ children: "</>"
50087
+ }
50088
+ ) })
50089
+ ] }),
50090
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-4 bg-surface-800 mx-1 shrink-0" }),
50091
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { label: t("Workspace settings"), align: "right", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
49480
50092
  "button",
49481
50093
  {
49482
- onClick: () => setShowGeneratorPanel(!showGeneratorPanel),
49483
- className: `px-2.5 py-1 text-xs rounded transition-colors font-mono ${showGeneratorPanel ? "bg-blue-700 text-white" : "bg-surface-800 hover:bg-surface-700"}`,
49484
- title: t("Toggle code generator"),
49485
- children: "</>"
50094
+ onClick: () => setWorkspaceSettingsOpen(true),
50095
+ className: "p-1.5 bg-surface-800 hover:bg-surface-700 rounded transition-colors",
50096
+ "aria-label": t("Workspace settings"),
50097
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(GearIcon, {})
49486
50098
  }
49487
- )
50099
+ ) })
49488
50100
  ] })
49489
50101
  ] })
49490
50102
  ] });
@@ -54084,6 +54696,7 @@ function detectPlatform(remotes) {
54084
54696
  const NODE_LTS = "lts/*";
54085
54697
  const PROVIDER_ENV = {
54086
54698
  vault: ["VAULT_ADDR", "VAULT_ROLE_ID", "VAULT_SECRET_ID"],
54699
+ bao: ["BAO_ADDR", "BAO_ROLE_ID", "BAO_SECRET_ID"],
54087
54700
  aws: ["AWS_REGION", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"],
54088
54701
  azure: ["AZURE_TENANT_ID", "AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET"],
54089
54702
  gcp: ["GCP_PROJECT", "GOOGLE_APPLICATION_CREDENTIALS_JSON"],
@@ -54092,10 +54705,10 @@ const PROVIDER_ENV = {
54092
54705
  function secretManagerOf(ref) {
54093
54706
  if (ref.startsWith("op://")) return "op";
54094
54707
  const scheme = ref.split(":", 1)[0];
54095
- if (scheme === "vault" || scheme === "aws" || scheme === "azure" || scheme === "gcp") return scheme;
54708
+ if (scheme === "vault" || scheme === "bao" || scheme === "aws" || scheme === "azure" || scheme === "gcp") return scheme;
54096
54709
  return null;
54097
54710
  }
54098
- const INLINE_REF_RE = /\{\{\s*(vault:|aws:|azure:|gcp:|op:\/\/)/g;
54711
+ const INLINE_REF_RE = /\{\{\s*(vault:|bao:|aws:|azure:|gcp:|op:\/\/)/g;
54099
54712
  function inlineSecretManagers(text) {
54100
54713
  if (!text) return [];
54101
54714
  const kinds = /* @__PURE__ */ new Set();
@@ -54497,6 +55110,16 @@ function ChangesTab({ status, onRefresh }) {
54497
55110
  setError(String(e));
54498
55111
  }
54499
55112
  }
55113
+ async function stash() {
55114
+ try {
55115
+ setError(null);
55116
+ await electron$4.gitStash();
55117
+ showToast(t("Changes stashed - restore them from the Branches tab"), true);
55118
+ onRefresh();
55119
+ } catch (e) {
55120
+ setError(String(e));
55121
+ }
55122
+ }
54500
55123
  async function fetch() {
54501
55124
  setFetching(true);
54502
55125
  try {
@@ -54549,6 +55172,7 @@ function ChangesTab({ status, onRefresh }) {
54549
55172
  }
54550
55173
  const allUnstaged = [...status.unstaged, ...status.untracked];
54551
55174
  const hasStaged = status.staged.length > 0;
55175
+ const isDirty = hasStaged || status.unstaged.length > 0 || status.untracked.length > 0;
54552
55176
  const needsPush = status.ahead > 0;
54553
55177
  const needsPull = status.behind > 0;
54554
55178
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0", children: [
@@ -54805,15 +55429,27 @@ function ChangesTab({ status, onRefresh }) {
54805
55429
  className: "w-full bg-surface-800 border border-surface-700 rounded px-2 py-1.5 text-xs resize-none focus:outline-none focus:border-blue-500 placeholder-surface-600"
54806
55430
  }
54807
55431
  ),
54808
- /* @__PURE__ */ jsxRuntimeExports.jsx(
54809
- "button",
54810
- {
54811
- onClick: commit,
54812
- disabled: !message.trim() || !hasStaged,
54813
- 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",
54814
- children: hasStaged ? t("Commit (:count)", { count: status.staged.length }) : t("Commit")
54815
- }
54816
- )
55432
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
55433
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55434
+ "button",
55435
+ {
55436
+ onClick: commit,
55437
+ disabled: !message.trim() || !hasStaged,
55438
+ className: "flex-1 py-1.5 bg-blue-600 hover:bg-blue-500 disabled:opacity-40 disabled:cursor-not-allowed rounded text-xs font-medium transition-colors",
55439
+ children: hasStaged ? t("Commit (:count)", { count: status.staged.length }) : t("Commit")
55440
+ }
55441
+ ),
55442
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55443
+ "button",
55444
+ {
55445
+ onClick: stash,
55446
+ disabled: !isDirty,
55447
+ title: t("Park all uncommitted changes - restore them later from the Branches tab"),
55448
+ className: "px-3 py-1.5 bg-surface-800 hover:bg-surface-700 border border-surface-700 disabled:opacity-40 disabled:cursor-not-allowed rounded text-xs font-medium text-surface-200 transition-colors",
55449
+ children: t("Stash")
55450
+ }
55451
+ )
55452
+ ] })
54817
55453
  ] })
54818
55454
  ] });
54819
55455
  }
@@ -54841,6 +55477,8 @@ function BranchesTab({ onRefresh }) {
54841
55477
  const t = useT();
54842
55478
  const [branches, setBranches] = reactExports.useState([]);
54843
55479
  const [remotes, setRemotes] = reactExports.useState([]);
55480
+ const [stashes, setStashes] = reactExports.useState([]);
55481
+ const [info, setInfo] = reactExports.useState(null);
54844
55482
  const [loading, setLoading] = reactExports.useState(true);
54845
55483
  const [newBranch, setNewBranch] = reactExports.useState("");
54846
55484
  const [creating, setCreating] = reactExports.useState(false);
@@ -54854,9 +55492,10 @@ function BranchesTab({ onRefresh }) {
54854
55492
  const [filter2, setFilter] = reactExports.useState("");
54855
55493
  const load2 = reactExports.useCallback(() => {
54856
55494
  setLoadError(null);
54857
- Promise.all([electron$4.gitBranches(), electron$4.gitRemotes()]).then(([b, r]) => {
55495
+ Promise.all([electron$4.gitBranches(), electron$4.gitRemotes(), electron$4.gitStashList()]).then(([b, r, s]) => {
54858
55496
  setBranches(b);
54859
55497
  setRemotes(r);
55498
+ setStashes(s);
54860
55499
  }).catch((err) => {
54861
55500
  console.warn("GitPanel: failed to load branches/remotes", err);
54862
55501
  setLoadError(err instanceof Error ? err.message : String(err));
@@ -54865,13 +55504,34 @@ function BranchesTab({ onRefresh }) {
54865
55504
  reactExports.useEffect(() => {
54866
55505
  load2();
54867
55506
  }, [load2]);
55507
+ function isDirtyTreeError(msg) {
55508
+ return /overwritten by (checkout|merge)|commit your changes or stash/i.test(msg);
55509
+ }
55510
+ async function checkoutOrStash(localName, create2) {
55511
+ try {
55512
+ await electron$4.gitCheckout(localName, create2);
55513
+ return true;
55514
+ } catch (e) {
55515
+ const msg = String(e);
55516
+ if (!isDirtyTreeError(msg)) throw e;
55517
+ if (!confirm(t('You have uncommitted changes that would be lost switching to ":name". Stash them and switch?', { name: localName }))) {
55518
+ return false;
55519
+ }
55520
+ await electron$4.gitStash(t("Auto-stash before switching to :name", { name: localName }));
55521
+ await electron$4.gitCheckout(localName, create2);
55522
+ setInfo(t("Your changes were stashed. Restore them from the Stashes list below when you return."));
55523
+ return true;
55524
+ }
55525
+ }
54868
55526
  async function checkout(name, remote2) {
54869
55527
  try {
54870
55528
  setError(null);
55529
+ setInfo(null);
54871
55530
  const localName = remote2 ? name.replace(/^origin\//, "") : name;
54872
- await electron$4.gitCheckout(localName, false);
54873
- load2();
54874
- onRefresh();
55531
+ if (await checkoutOrStash(localName, false)) {
55532
+ load2();
55533
+ onRefresh();
55534
+ }
54875
55535
  } catch (e) {
54876
55536
  setError(String(e));
54877
55537
  }
@@ -54880,15 +55540,39 @@ function BranchesTab({ onRefresh }) {
54880
55540
  if (!newBranch.trim()) return;
54881
55541
  try {
54882
55542
  setError(null);
54883
- await electron$4.gitCheckout(newBranch.trim(), true);
54884
- setNewBranch("");
54885
- setCreating(false);
55543
+ setInfo(null);
55544
+ if (await checkoutOrStash(newBranch.trim(), true)) {
55545
+ setNewBranch("");
55546
+ setCreating(false);
55547
+ load2();
55548
+ onRefresh();
55549
+ }
55550
+ } catch (e) {
55551
+ setError(String(e));
55552
+ }
55553
+ }
55554
+ async function popStash(ref) {
55555
+ try {
55556
+ setError(null);
55557
+ setInfo(null);
55558
+ await electron$4.gitStashPop(ref);
54886
55559
  load2();
54887
55560
  onRefresh();
54888
55561
  } catch (e) {
54889
55562
  setError(String(e));
54890
55563
  }
54891
55564
  }
55565
+ async function dropStash(ref) {
55566
+ if (!confirm(t("Discard stashed changes (:ref)? This cannot be undone.", { ref }))) return;
55567
+ try {
55568
+ setError(null);
55569
+ setInfo(null);
55570
+ await electron$4.gitStashDrop(ref);
55571
+ load2();
55572
+ } catch (e) {
55573
+ setError(String(e));
55574
+ }
55575
+ }
54892
55576
  async function addRemote() {
54893
55577
  if (!remoteName.trim() || !remoteUrl.trim()) return;
54894
55578
  try {
@@ -54947,7 +55631,8 @@ function BranchesTab({ onRefresh }) {
54947
55631
  const remote = branches.filter((b) => b.remote && (!q || b.name.toLowerCase().includes(q)));
54948
55632
  if (loading) return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 px-3 py-4", children: t("Loading…") });
54949
55633
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0 overflow-y-auto", children: [
54950
- error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-red-400 px-3 py-1 truncate", children: error2 }),
55634
+ error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-red-400 px-3 py-1 truncate", title: error2, children: error2 }),
55635
+ info && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-blue-300 px-3 py-1 bg-blue-900/20 border-b border-surface-800", children: info }),
54951
55636
  loadError && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] text-amber-400 px-3 py-1 truncate", title: loadError, children: [
54952
55637
  t("Couldn't load git state:"),
54953
55638
  " ",
@@ -55039,6 +55724,37 @@ function BranchesTab({ onRefresh }) {
55039
55724
  ))
55040
55725
  ] }),
55041
55726
  q && local.length === 0 && remote.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "px-3 py-3 text-[11px] text-surface-500", children: t('No branches match ":filter".', { filter: filter2 }) }),
55727
+ stashes.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "border-t border-surface-800 mt-1", children: [
55728
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "px-3 py-1.5 text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: t("Stashes") }),
55729
+ stashes.map((s) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "group flex items-center gap-2 px-3 py-1.5 text-xs text-surface-300 hover:bg-surface-800/50", children: [
55730
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-[10px] text-surface-500 shrink-0", children: s.ref }),
55731
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "truncate flex-1", title: s.branch ? t("on :branch: :message", { branch: s.branch, message: s.message }) : s.message, children: [
55732
+ s.message,
55733
+ s.branch && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-600", children: [
55734
+ " ",
55735
+ t("(on :branch)", { branch: s.branch })
55736
+ ] })
55737
+ ] }),
55738
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55739
+ "button",
55740
+ {
55741
+ onClick: () => popStash(s.ref),
55742
+ className: "opacity-0 group-hover:opacity-100 text-[10px] text-blue-400 hover:text-blue-300 transition-all shrink-0",
55743
+ title: t("Restore these changes onto the current branch"),
55744
+ children: t("Pop")
55745
+ }
55746
+ ),
55747
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55748
+ "button",
55749
+ {
55750
+ onClick: () => dropStash(s.ref),
55751
+ className: "opacity-0 group-hover:opacity-100 text-[10px] text-surface-500 hover:text-red-400 transition-all shrink-0",
55752
+ title: t("Discard this stash"),
55753
+ children: "✕"
55754
+ }
55755
+ )
55756
+ ] }, s.ref))
55757
+ ] }),
55042
55758
  /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "border-t border-surface-800 mt-1", children: [
55043
55759
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-1.5 flex items-center justify-between", children: [
55044
55760
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: t("Remotes") }),
@@ -56599,7 +57315,7 @@ function AddBlockMenu({ onAdd: onAdd2 }) {
56599
57315
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-0 top-full mt-1 z-50 w-56 bg-surface-900 border border-surface-700 rounded-lg shadow-2xl max-h-[70vh] overflow-y-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx(BlockPickerList, { onPick: (type) => {
56600
57316
  onAdd2(type);
56601
57317
  setOpen(false);
56602
- } }) })
57318
+ }, includeEnd: true }) })
56603
57319
  ] })
56604
57320
  ] });
56605
57321
  }
@@ -57837,6 +58553,43 @@ function App() {
57837
58553
  });
57838
58554
  return () => electron.offOpenFromGit();
57839
58555
  }, [applyWorkspace]);
58556
+ reactExports.useEffect(() => {
58557
+ electron.onImportShare(async (token) => {
58558
+ try {
58559
+ const { type, name, payload } = await electron.cloudImportShare(token);
58560
+ let collection2;
58561
+ if (type === "request") {
58562
+ const r = payload;
58563
+ const reqId = r.id || crypto.randomUUID();
58564
+ const req = { ...r, id: reqId };
58565
+ collection2 = {
58566
+ version: "1.0",
58567
+ id: crypto.randomUUID(),
58568
+ name: name || req.name || "Shared request",
58569
+ rootFolder: { id: crypto.randomUUID(), name: "", folders: [], requestIds: [reqId] },
58570
+ requests: { [reqId]: req }
58571
+ };
58572
+ } else {
58573
+ collection2 = { ...payload, id: crypto.randomUUID() };
58574
+ }
58575
+ const store = useStore$1.getState();
58576
+ const id2 = store.addCollectionObject(collection2);
58577
+ try {
58578
+ const entry = useStore$1.getState().collections[id2];
58579
+ if (entry && useStore$1.getState().workspace) {
58580
+ await electron.saveCollection(entry.relPath, entry.data);
58581
+ await electron.saveWorkspace(useStore$1.getState().workspace);
58582
+ }
58583
+ } catch (persistErr) {
58584
+ console.error("Imported shared collection but could not save it yet:", persistErr);
58585
+ }
58586
+ setSidebarTab("collections");
58587
+ } catch (err) {
58588
+ console.error("Import from share link failed:", err);
58589
+ }
58590
+ });
58591
+ return () => electron.offImportShare();
58592
+ }, [setSidebarTab]);
57840
58593
  reactExports.useEffect(() => {
57841
58594
  electron.onMockHit(addMockHit);
57842
58595
  return () => electron.offMockHit();
@@ -58005,7 +58758,7 @@ function App() {
58005
58758
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--wordmark-accent)" }, children: "Spector" }),
58006
58759
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal", style: { color: "var(--text-muted)" }, children: [
58007
58760
  "v",
58008
- "0.9.0"
58761
+ "0.9.2"
58009
58762
  ] }),
58010
58763
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mx-2 opacity-30", "aria-hidden": "true", children: "·" }),
58011
58764
  /* @__PURE__ */ jsxRuntimeExports.jsx(