diff --git a/src/App.css b/src/App.css index 854018e..b4fc343 100644 --- a/src/App.css +++ b/src/App.css @@ -1118,6 +1118,55 @@ textarea { resize: vertical; min-height: 60px; line-height: 1.5; } } .copy-icon:hover { color: var(--tx-1); background: var(--bg-1); } +.api-state { + margin-top: 10px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 8px 10px; + border: 1px solid var(--bd-1); + border-radius: var(--radius); + background: var(--bg-2); + font-size: 12px; +} +.api-state strong { color: var(--tx-1); } +.api-state span { + min-width: 0; + overflow: hidden; + color: var(--tx-3); + text-overflow: ellipsis; + white-space: nowrap; +} +.api-state-on { border-color: #4ade8033; } +.api-state-on strong { color: var(--ok); } +.api-state-off { border-color: #fb718540; } +.api-state-off strong { color: var(--err); } +.settings-note { + margin-top: 10px; + padding: 9px 10px; + border: 1px solid var(--bd-1); + border-radius: var(--radius); + background: var(--bg-2); + color: var(--tx-2); + font-size: 12px; + line-height: 1.45; +} +.settings-note code { color: var(--tx-1); } +.settings-note-warn { + border-color: #f59e0b55; + background: #f59e0b12; + color: #fbbf24; +} +.codex-env-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + flex-wrap: wrap; +} +.codex-env-row span { flex: 1 1 260px; } + /* Confirm modal */ .dialog-confirm { max-width: 440px; } .confirm-msg { color: var(--tx-2); font-size: 13px; line-height: 1.5; margin: 0; } diff --git a/src/App.tsx b/src/App.tsx index 585118e..a0665cc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4739,11 +4739,35 @@ function SettingsView() { api_port: 40325, }); const [api, setApi] = useState(null); + const [tokenBusy, setTokenBusy] = useState(false); const refreshApi = () => invoke("api_info").then(setApi).catch(() => {}); useEffect(() => { invoke("settings_get").then(setS); refreshApi(); }, []); const regenToken = async () => { - try { setApi(await invoke("api_regenerate_token")); toast.ok("Token regenerated"); } + const ok = await confirmModal({ + title: "Regenerate Automation API token", + message: + "Existing MCP, Codex, SDK, and script clients using the current token will stop working immediately. Generate a new token?", + buttons: [ + { label: "Cancel", value: false }, + { label: "Regenerate token", value: true, danger: true }, + ], + }); + if (ok !== true) return; + setTokenBusy(true); + try { + setApi(await invoke("api_regenerate_token")); + toast.ok("Token regenerated — update MCP/Codex clients"); + } catch (e) { toast.err(String(e)); } + finally { setTokenBusy(false); } + }; + const copyCodexTokenEnv = async () => { + if (!api?.token) return; + const escapedToken = api.token.replace(/'/g, "''"); + try { + await clip.write(`[Environment]::SetEnvironmentVariable('SHARDX_TOKEN','${escapedToken}','User')`); + toast.ok("Copied SHARDX_TOKEN User env command"); + } catch (e) { toast.err(String(e)); } }; const [mcpBusy, setMcpBusy] = useState(false); @@ -4762,6 +4786,9 @@ function SettingsView() { try { await invoke("settings_save", { value: s }); toast.ok("Settings saved"); } catch (e) { toast.err(String(e)); } }; + const requestedApiEnabled = s.api_enabled ?? true; + const requestedApiPort = s.api_port ?? 40325; + const apiRestartPending = !!api && (requestedApiEnabled !== api.enabled || requestedApiPort !== api.port); return (
{}} /> @@ -4827,12 +4854,22 @@ function SettingsView() { Port setS({ ...s, api_port: Number(e.target.value) || 40325 })} /> {api && ( <> +
+ {api.enabled ? "Server running" : "Server disabled"} + {api.enabled ? api.base_url : "Enable it, save settings, then restart the app."} +
+ {apiRestartPending && ( +
+ Enable/port edits apply after Save settings and app restart. The current API is still{" "} + {api.enabled ? {api.base_url} : "disabled"}. +
+ )} +
+ + For Codex on Windows, store this in the User environment as SHARDX_TOKEN instead of + pasting it into MCP config files. + + +
- + Invalidates the current token immediately.