@dipertq/dsh-openviking-status 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.cjs CHANGED
@@ -38,6 +38,7 @@ __export(client_exports, {
38
38
  COMMIT_THRESHOLD: () => COMMIT_THRESHOLD,
39
39
  DEFAULT_OPENVIKING_ENDPOINT: () => DEFAULT_OPENVIKING_ENDPOINT,
40
40
  OpenVikingClient: () => OpenVikingClient,
41
+ OpenVikingSettingsSection: () => OpenVikingSettingsSection,
41
42
  OpenVikingStatusChip: () => OpenVikingStatusChip,
42
43
  OpenVikingStatusPopover: () => OpenVikingStatusPopover,
43
44
  THEME: () => THEME,
@@ -73,6 +74,7 @@ module.exports = __toCommonJS(client_exports);
73
74
 
74
75
  // src/client/OpenVikingStatusChip.tsx
75
76
  var import_react2 = __toESM(require("react"), 1);
77
+ var import_react_dom = __toESM(require("react-dom"), 1);
76
78
 
77
79
  // src/client/api.ts
78
80
  var DEFAULT_OPENVIKING_ENDPOINT = "http://127.0.0.1:1933";
@@ -126,6 +128,30 @@ var OpenVikingClient = class {
126
128
  this.endpoint = resolveEndpoint(endpoint);
127
129
  this.apiKey = resolveApiKey(apiKey);
128
130
  }
131
+ /**
132
+ * Обновление конфигурации клиента на лету (например, после сохранения настроек в UI).
133
+ */
134
+ updateConfig(config) {
135
+ if (config.endpoint && config.endpoint.trim()) {
136
+ this.endpoint = resolveEndpoint(config.endpoint);
137
+ }
138
+ if (config.apiKey !== void 0) {
139
+ this.apiKey = resolveApiKey(config.apiKey);
140
+ }
141
+ this.resolvedSessionIds.clear();
142
+ }
143
+ /**
144
+ * Очистить кэш разрешенных идентификаторов сессий.
145
+ */
146
+ clearResolvedSessions() {
147
+ this.resolvedSessionIds.clear();
148
+ }
149
+ /**
150
+ * Проверка, работает ли клиент через DSH Web Server proxy.
151
+ */
152
+ isProxy() {
153
+ return this.endpoint.startsWith("/") || this.endpoint.includes("/openviking-status/api");
154
+ }
129
155
  /**
130
156
  * Формирование заголовков запроса, включая опциональный заголовок авторизации
131
157
  */
@@ -167,6 +193,32 @@ var OpenVikingClient = class {
167
193
  * Проверка доступности и состояния сервиса OpenViking
168
194
  */
169
195
  async checkHealth() {
196
+ if (this.isProxy()) {
197
+ try {
198
+ const res = await fetch(`${this.endpoint}/health`, {
199
+ method: "GET",
200
+ headers: this.getHeaders()
201
+ });
202
+ if (!res.ok) {
203
+ return {
204
+ ok: false,
205
+ error: `HTTP ${res.status}: ${res.statusText}`
206
+ };
207
+ }
208
+ const body = await res.json().catch(() => ({}));
209
+ const isOk = body.ok !== false && body.status !== "error" && (body.ok === true || body.status === "ok" || body.status === "healthy" || res.ok);
210
+ return {
211
+ ok: isOk,
212
+ version: typeof body.version === "string" ? body.version : void 0,
213
+ storage: typeof body.storage === "string" ? body.storage : void 0
214
+ };
215
+ } catch (err) {
216
+ return {
217
+ ok: false,
218
+ error: err instanceof Error ? err.message : String(err)
219
+ };
220
+ }
221
+ }
170
222
  try {
171
223
  const res = await fetch(`${this.endpoint}/health`, {
172
224
  method: "GET",
@@ -204,6 +256,48 @@ var OpenVikingClient = class {
204
256
  if (!sessionId || !sessionId.trim()) {
205
257
  return { status: "missing" };
206
258
  }
259
+ if (this.isProxy()) {
260
+ try {
261
+ const res = await fetch(
262
+ `${this.endpoint}/session?id=${encodeURIComponent(sessionId.trim())}`,
263
+ {
264
+ method: "GET",
265
+ headers: this.getHeaders()
266
+ }
267
+ );
268
+ if (res.status === 401 || res.status === 403) {
269
+ return { status: "unauthorized" };
270
+ }
271
+ if (!res.ok) {
272
+ return { status: "error", detail: `HTTP ${res.status}` };
273
+ }
274
+ const data = await res.json();
275
+ if (data.status === "unauthorized") return { status: "unauthorized" };
276
+ if (data.status === "missing") return { status: "missing" };
277
+ if (data.status === "unreachable")
278
+ return {
279
+ status: "unreachable",
280
+ detail: typeof data.detail === "string" ? data.detail : void 0
281
+ };
282
+ if (data.status === "error")
283
+ return {
284
+ status: "error",
285
+ detail: typeof data.detail === "string" ? data.detail : void 0
286
+ };
287
+ if (data.status === "ok" && data.session) {
288
+ return {
289
+ status: "ok",
290
+ session: data.session
291
+ };
292
+ }
293
+ return { status: "error", detail: "malformed response from proxy" };
294
+ } catch (err) {
295
+ return {
296
+ status: "unreachable",
297
+ detail: err instanceof Error ? err.message : String(err)
298
+ };
299
+ }
300
+ }
207
301
  const candidates = this.getCandidateSessionIds(sessionId);
208
302
  for (const candidateId of candidates) {
209
303
  try {
@@ -273,6 +367,31 @@ var OpenVikingClient = class {
273
367
  if (!sessionId || !sessionId.trim()) {
274
368
  return { ok: false, error: "Missing sessionId" };
275
369
  }
370
+ if (this.isProxy()) {
371
+ try {
372
+ const res = await fetch(`${this.endpoint}/session/commit`, {
373
+ method: "POST",
374
+ headers: this.getHeaders(),
375
+ body: JSON.stringify({
376
+ sessionId: sessionId.trim(),
377
+ ...options ?? { keep_recent_count: 10 }
378
+ })
379
+ });
380
+ if (!res.ok) {
381
+ return { ok: false, error: `HTTP ${res.status}` };
382
+ }
383
+ const data = await res.json().catch(() => ({}));
384
+ return {
385
+ ok: data.ok === true,
386
+ error: typeof data.error === "string" ? data.error : void 0
387
+ };
388
+ } catch (err) {
389
+ return {
390
+ ok: false,
391
+ error: err instanceof Error ? err.message : String(err)
392
+ };
393
+ }
394
+ }
276
395
  const candidates = this.getCandidateSessionIds(sessionId);
277
396
  const bodyPayload = JSON.stringify(options ?? { keep_recent_count: 10 });
278
397
  let lastError = "Session commit failed";
@@ -352,6 +471,10 @@ var THEME = {
352
471
  stateWarning: "--dsw-alias-state-warn-primary",
353
472
  /** Утопленная поверхность: дорожка прогресс-бара. */
354
473
  insetSurface: "--dsw-alias-bg-layer-2",
474
+ /** Заливка основной кнопки действий. */
475
+ buttonPrimaryFill: "--dsw-alias-button-primary-fill",
476
+ /** Цвет текста на основной кнопке действий. */
477
+ buttonPrimaryText: "--dsw-alias-label-primary-inverted",
355
478
  /** Моноширинный шрифт для идентификаторов и путей. */
356
479
  fontMono: "--dsw-font-markdown-code-font-family"
357
480
  };
@@ -1285,8 +1408,22 @@ function StatusChipView({
1285
1408
  const [isCommitting, setIsCommitting] = (0, import_react2.useState)(false);
1286
1409
  const [commitError, setCommitError] = (0, import_react2.useState)(null);
1287
1410
  const [isHovered, setIsHovered] = (0, import_react2.useState)(false);
1411
+ const [statsHost, setStatsHost] = (0, import_react2.useState)(null);
1288
1412
  const popoverRef = (0, import_react2.useRef)(null);
1289
1413
  const apiClient = client ?? defaultOpenVikingClient;
1414
+ (0, import_react2.useEffect)(() => {
1415
+ if (typeof document === "undefined") return;
1416
+ function findHost() {
1417
+ const el = document.querySelector("[data-composer-stats]");
1418
+ setStatsHost((prev) => prev !== el ? el : prev);
1419
+ }
1420
+ findHost();
1421
+ const observer = new MutationObserver(() => {
1422
+ findHost();
1423
+ });
1424
+ observer.observe(document.body, { childList: true, subtree: true });
1425
+ return () => observer.disconnect();
1426
+ }, []);
1290
1427
  const conversationText = (0, import_react2.useMemo)(() => {
1291
1428
  if (typeof contextText === "string") return contextText;
1292
1429
  return chatNodesToText(messages);
@@ -1372,10 +1509,11 @@ function StatusChipView({
1372
1509
  sessionUnreadable
1373
1510
  });
1374
1511
  const label = !isOnline ? "OV offline" : sessionUnreadable ? `OV: ${recalledCount} rec \xB7 no access` : `OV: ${recalledCount} rec \xB7 ${Math.round(pendingTokens / 1e3)}k pend`;
1375
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1512
+ const chipElement = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1376
1513
  "span",
1377
1514
  {
1378
1515
  className,
1516
+ "data-openviking-status": "true",
1379
1517
  style: { minWidth: 0, display: "inline-flex", position: "relative" },
1380
1518
  ref: popoverRef,
1381
1519
  children: [
@@ -1389,14 +1527,13 @@ function StatusChipView({
1389
1527
  "aria-expanded": isOpen,
1390
1528
  "aria-haspopup": "dialog",
1391
1529
  style: {
1392
- // Геометрия и типографика повторяют штатный чип статистики DSH:
1393
- // прозрачный фон, без рамки, шрифт и кегль наследуются от строки.
1394
1530
  boxSizing: "border-box",
1395
1531
  maxWidth: "100%",
1396
- color: themeVar("labelTertiary"),
1397
- font: "inherit",
1532
+ color: isHovered ? themeVar("labelSecondary") : themeVar("labelTertiary"),
1533
+ fontFamily: "var(--dsw-font-family, system-ui)",
1534
+ fontSize: "var(--dsh-content-font-size-secondary, 13px)",
1535
+ lineHeight: "calc(20px + var(--dsh-content-font-delta-secondary, 0px))",
1398
1536
  fontVariantNumeric: "tabular-nums",
1399
- lineHeight: "inherit",
1400
1537
  whiteSpace: "nowrap",
1401
1538
  background: isHovered ? themeVar("hoverBackground") : "transparent",
1402
1539
  border: "none",
@@ -1446,6 +1583,572 @@ function StatusChipView({
1446
1583
  ]
1447
1584
  }
1448
1585
  );
1586
+ if (statsHost && typeof document !== "undefined") {
1587
+ return import_react_dom.default.createPortal(chipElement, statsHost);
1588
+ }
1589
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1590
+ "div",
1591
+ {
1592
+ style: {
1593
+ maxWidth: "var(--dsh-chat-content-width, 748px)",
1594
+ boxSizing: "border-box",
1595
+ width: "100%",
1596
+ padding: "4px calc(var(--dsh-composer-side-clearance, 0px) + 16px) 0px",
1597
+ fontSize: "var(--dsh-content-font-size-secondary, 13px)",
1598
+ lineHeight: "calc(20px + var(--dsh-content-font-delta-secondary, 0px))",
1599
+ justifyContent: "center",
1600
+ gap: "12px",
1601
+ margin: "0 auto",
1602
+ display: "flex"
1603
+ },
1604
+ children: chipElement
1605
+ }
1606
+ );
1607
+ }
1608
+
1609
+ // src/client/OpenVikingSettingsSection.tsx
1610
+ var import_react3 = require("react");
1611
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1612
+ var API_CONFIG = "/openviking-status/api/config";
1613
+ var API_TEST = "/openviking-status/api/test-connection";
1614
+ var SOURCE_LABELS = {
1615
+ ovcli: "Auto-detected from ~/.openviking/ovcli.conf",
1616
+ env: "Auto-detected from environment variables",
1617
+ ov: "Auto-detected from ~/.openviking/ov.conf",
1618
+ settings: "Custom override in DSH settings.yaml",
1619
+ default: "Default local configuration"
1620
+ };
1621
+ function OpenVikingSettingsSection({
1622
+ initialConfig,
1623
+ onConfigSaved,
1624
+ className,
1625
+ style
1626
+ }) {
1627
+ const [endpoint, setEndpoint] = (0, import_react3.useState)(
1628
+ initialConfig?.endpoint || "http://127.0.0.1:1933"
1629
+ );
1630
+ const [apiKey, setApiKey] = (0, import_react3.useState)("");
1631
+ const [showKey, setShowKey] = (0, import_react3.useState)(false);
1632
+ const [source, setSource] = (0, import_react3.useState)(
1633
+ initialConfig?.source || "default"
1634
+ );
1635
+ const [hasStoredKey, setHasStoredKey] = (0, import_react3.useState)(
1636
+ initialConfig?.hasApiKey || false
1637
+ );
1638
+ const [loading, setLoading] = (0, import_react3.useState)(!initialConfig);
1639
+ const [testing, setTesting] = (0, import_react3.useState)(false);
1640
+ const [saving, setSaving] = (0, import_react3.useState)(false);
1641
+ const [testResult, setTestResult] = (0, import_react3.useState)(null);
1642
+ const [flash, setFlash] = (0, import_react3.useState)(null);
1643
+ (0, import_react3.useEffect)(() => {
1644
+ if (initialConfig) return;
1645
+ let active = true;
1646
+ async function fetchConfig() {
1647
+ try {
1648
+ setLoading(true);
1649
+ const res = await fetch(API_CONFIG, { cache: "no-store" });
1650
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1651
+ const data = await res.json();
1652
+ if (!active) return;
1653
+ const newEp = data.endpoint || "http://127.0.0.1:1933";
1654
+ setEndpoint(newEp);
1655
+ setSource(data.source || "default");
1656
+ setHasStoredKey(data.hasApiKey);
1657
+ defaultOpenVikingClient.clearResolvedSessions();
1658
+ } catch {
1659
+ } finally {
1660
+ if (active) setLoading(false);
1661
+ }
1662
+ }
1663
+ void fetchConfig();
1664
+ return () => {
1665
+ active = false;
1666
+ };
1667
+ }, [initialConfig]);
1668
+ async function handleTestConnection() {
1669
+ setTesting(true);
1670
+ setTestResult(null);
1671
+ setFlash(null);
1672
+ try {
1673
+ const res = await fetch(API_TEST, {
1674
+ method: "POST",
1675
+ headers: { "Content-Type": "application/json" },
1676
+ body: JSON.stringify({
1677
+ endpoint: endpoint.trim(),
1678
+ apiKey: apiKey.trim() || void 0
1679
+ })
1680
+ });
1681
+ const data = await res.json();
1682
+ setTestResult(data);
1683
+ } catch (err) {
1684
+ setTestResult({
1685
+ ok: false,
1686
+ authenticated: false,
1687
+ error: String(err instanceof Error ? err.message : err)
1688
+ });
1689
+ } finally {
1690
+ setTesting(false);
1691
+ }
1692
+ }
1693
+ async function handleSave() {
1694
+ setSaving(true);
1695
+ setFlash(null);
1696
+ try {
1697
+ const res = await fetch(API_CONFIG, {
1698
+ method: "POST",
1699
+ headers: { "Content-Type": "application/json" },
1700
+ body: JSON.stringify({
1701
+ endpoint: endpoint.trim(),
1702
+ apiKey: apiKey.trim() || void 0
1703
+ })
1704
+ });
1705
+ const data = await res.json();
1706
+ if (!res.ok || data.error) {
1707
+ throw new Error(data.error || `HTTP ${res.status}`);
1708
+ }
1709
+ setFlash({ kind: "ok", message: "Settings saved successfully" });
1710
+ setSource("settings");
1711
+ defaultOpenVikingClient.clearResolvedSessions();
1712
+ if (apiKey.trim()) {
1713
+ setHasStoredKey(true);
1714
+ setApiKey("");
1715
+ }
1716
+ onConfigSaved?.(
1717
+ data.config || {
1718
+ endpoint,
1719
+ hasApiKey: hasStoredKey || Boolean(apiKey.trim()),
1720
+ source: "settings"
1721
+ }
1722
+ );
1723
+ } catch (err) {
1724
+ setFlash({
1725
+ kind: "err",
1726
+ message: `Failed to save: ${err instanceof Error ? err.message : String(err)}`
1727
+ });
1728
+ } finally {
1729
+ setSaving(false);
1730
+ }
1731
+ }
1732
+ async function handleReset() {
1733
+ setSaving(true);
1734
+ setFlash(null);
1735
+ setTestResult(null);
1736
+ try {
1737
+ const res = await fetch(API_CONFIG, {
1738
+ method: "POST",
1739
+ headers: { "Content-Type": "application/json" },
1740
+ body: JSON.stringify({ reset: true })
1741
+ });
1742
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1743
+ const cfgRes = await fetch(API_CONFIG, { cache: "no-store" });
1744
+ if (cfgRes.ok) {
1745
+ const data = await cfgRes.json();
1746
+ const newEp = data.endpoint || "http://127.0.0.1:1933";
1747
+ setEndpoint(newEp);
1748
+ setSource(data.source || "default");
1749
+ setHasStoredKey(data.hasApiKey);
1750
+ setApiKey("");
1751
+ defaultOpenVikingClient.clearResolvedSessions();
1752
+ setFlash({
1753
+ kind: "ok",
1754
+ message: "Reset to auto-detected local settings"
1755
+ });
1756
+ onConfigSaved?.(data);
1757
+ }
1758
+ } catch (err) {
1759
+ setFlash({
1760
+ kind: "err",
1761
+ message: `Failed to reset: ${err instanceof Error ? err.message : String(err)}`
1762
+ });
1763
+ } finally {
1764
+ setSaving(false);
1765
+ }
1766
+ }
1767
+ const sourceBadgeText = SOURCE_LABELS[source] || SOURCE_LABELS["default"];
1768
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1769
+ "div",
1770
+ {
1771
+ className: `ov-settings-root ${className || ""}`,
1772
+ style: {
1773
+ display: "flex",
1774
+ flexDirection: "column",
1775
+ gap: 20,
1776
+ maxWidth: 720,
1777
+ padding: "24px 28px",
1778
+ color: themeVar("labelPrimary"),
1779
+ fontSize: 14,
1780
+ lineHeight: 1.6,
1781
+ ...style
1782
+ },
1783
+ children: [
1784
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1785
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1786
+ "div",
1787
+ {
1788
+ style: {
1789
+ display: "flex",
1790
+ alignItems: "baseline",
1791
+ gap: 12,
1792
+ marginBottom: 6
1793
+ },
1794
+ children: [
1795
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1796
+ "h2",
1797
+ {
1798
+ style: {
1799
+ fontSize: 20,
1800
+ fontWeight: 600,
1801
+ margin: 0,
1802
+ color: themeVar("labelPrimary")
1803
+ },
1804
+ children: "OpenViking Status"
1805
+ }
1806
+ ),
1807
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1808
+ "span",
1809
+ {
1810
+ style: {
1811
+ fontSize: 12,
1812
+ fontWeight: 500,
1813
+ padding: "2px 8px",
1814
+ borderRadius: 6,
1815
+ background: themeVar("insetSurface"),
1816
+ border: `1px solid ${themeVar("hairline")}`,
1817
+ color: themeVar("labelSecondary")
1818
+ },
1819
+ children: sourceBadgeText
1820
+ }
1821
+ )
1822
+ ]
1823
+ }
1824
+ ),
1825
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1826
+ "p",
1827
+ {
1828
+ style: { margin: 0, color: themeVar("labelSecondary"), fontSize: 13 },
1829
+ children: "Configure connection credentials for monitoring OpenViking persistent memory and session status."
1830
+ }
1831
+ )
1832
+ ] }),
1833
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1834
+ "div",
1835
+ {
1836
+ style: {
1837
+ border: `1px solid ${themeVar("hairline")}`,
1838
+ borderRadius: 12,
1839
+ padding: 20,
1840
+ background: themeVar("insetSurface"),
1841
+ display: "flex",
1842
+ flexDirection: "column",
1843
+ gap: 16
1844
+ },
1845
+ children: [
1846
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1847
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1848
+ "label",
1849
+ {
1850
+ style: {
1851
+ display: "block",
1852
+ fontSize: 13,
1853
+ fontWeight: 600,
1854
+ marginBottom: 6,
1855
+ color: themeVar("labelPrimary")
1856
+ },
1857
+ children: "Daemon Endpoint"
1858
+ }
1859
+ ),
1860
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1861
+ "input",
1862
+ {
1863
+ type: "text",
1864
+ value: endpoint,
1865
+ onChange: (e) => setEndpoint(e.target.value),
1866
+ placeholder: "http://127.0.0.1:1933",
1867
+ style: {
1868
+ width: "100%",
1869
+ boxSizing: "border-box",
1870
+ padding: "9px 12px",
1871
+ fontSize: 14,
1872
+ fontFamily: themeVar("fontMono"),
1873
+ borderRadius: 8,
1874
+ border: `1px solid ${themeVar("hairline")}`,
1875
+ background: themeVar("panelSurface"),
1876
+ color: themeVar("labelPrimary"),
1877
+ outline: "none"
1878
+ }
1879
+ }
1880
+ ),
1881
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1882
+ "span",
1883
+ {
1884
+ style: {
1885
+ fontSize: 12,
1886
+ color: themeVar("labelTertiary"),
1887
+ marginTop: 4,
1888
+ display: "block"
1889
+ },
1890
+ children: "The URL of the local or remote OpenViking HTTP server. Resolved from the DSH host."
1891
+ }
1892
+ )
1893
+ ] }),
1894
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1895
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1896
+ "div",
1897
+ {
1898
+ style: {
1899
+ display: "flex",
1900
+ justifyContent: "space-between",
1901
+ alignItems: "center",
1902
+ marginBottom: 6
1903
+ },
1904
+ children: [
1905
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1906
+ "label",
1907
+ {
1908
+ style: {
1909
+ fontSize: 13,
1910
+ fontWeight: 600,
1911
+ color: themeVar("labelPrimary")
1912
+ },
1913
+ children: "API Token (Authentication)"
1914
+ }
1915
+ ),
1916
+ hasStoredKey && !apiKey && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1917
+ "span",
1918
+ {
1919
+ style: {
1920
+ fontSize: 12,
1921
+ color: themeVar("stateSuccess"),
1922
+ fontWeight: 500
1923
+ },
1924
+ children: "\u25CF Active token configured"
1925
+ }
1926
+ )
1927
+ ]
1928
+ }
1929
+ ),
1930
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", gap: 8 }, children: [
1931
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1932
+ "input",
1933
+ {
1934
+ type: showKey ? "text" : "password",
1935
+ value: apiKey,
1936
+ onChange: (e) => setApiKey(e.target.value),
1937
+ placeholder: hasStoredKey ? "(Token stored \u2014 leave blank to keep unchanged)" : "Optional or required if daemon uses auth_mode: api_key",
1938
+ style: {
1939
+ flex: 1,
1940
+ padding: "9px 12px",
1941
+ fontSize: 14,
1942
+ fontFamily: themeVar("fontMono"),
1943
+ borderRadius: 8,
1944
+ border: `1px solid ${themeVar("hairline")}`,
1945
+ background: themeVar("panelSurface"),
1946
+ color: themeVar("labelPrimary"),
1947
+ outline: "none"
1948
+ }
1949
+ }
1950
+ ),
1951
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1952
+ "button",
1953
+ {
1954
+ type: "button",
1955
+ onClick: () => setShowKey((v) => !v),
1956
+ style: {
1957
+ padding: "0 14px",
1958
+ fontSize: 13,
1959
+ fontWeight: 500,
1960
+ borderRadius: 8,
1961
+ border: `1px solid ${themeVar("hairline")}`,
1962
+ background: themeVar("panelSurface"),
1963
+ color: themeVar("labelSecondary"),
1964
+ cursor: "pointer"
1965
+ },
1966
+ children: showKey ? "Hide" : "Show"
1967
+ }
1968
+ )
1969
+ ] }),
1970
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1971
+ "span",
1972
+ {
1973
+ style: {
1974
+ fontSize: 12,
1975
+ color: themeVar("labelTertiary"),
1976
+ marginTop: 4,
1977
+ display: "block"
1978
+ },
1979
+ children: "Required when the daemon runs with auth_mode: api_key. Local tokens are typically found in ~/.openviking/ovcli.conf."
1980
+ }
1981
+ )
1982
+ ] }),
1983
+ testResult && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1984
+ "div",
1985
+ {
1986
+ style: {
1987
+ padding: "10px 14px",
1988
+ borderRadius: 8,
1989
+ fontSize: 13,
1990
+ display: "flex",
1991
+ alignItems: "center",
1992
+ gap: 8,
1993
+ border: `1px solid ${testResult.ok && testResult.authenticated ? themeVar("stateSuccess") : themeVar("stateError")}`,
1994
+ background: themeVar("panelSurface"),
1995
+ color: testResult.ok && testResult.authenticated ? themeVar("stateSuccess") : themeVar("stateError")
1996
+ },
1997
+ children: [
1998
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: testResult.ok && testResult.authenticated ? "\u25CF" : "\u2715" }),
1999
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { flex: 1 }, children: testResult.ok && testResult.authenticated ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
2000
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Connected successfully" }),
2001
+ testResult.version && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { children: [
2002
+ " \xB7 ",
2003
+ testResult.version
2004
+ ] }),
2005
+ testResult.storage && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { children: [
2006
+ " (storage: ",
2007
+ testResult.storage,
2008
+ ")"
2009
+ ] })
2010
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
2011
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Connection failed: " }),
2012
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: testResult.error || "Unable to reach daemon or token unauthorized" })
2013
+ ] }) })
2014
+ ]
2015
+ }
2016
+ ),
2017
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2018
+ "div",
2019
+ {
2020
+ style: {
2021
+ display: "flex",
2022
+ flexWrap: "wrap",
2023
+ alignItems: "center",
2024
+ gap: 10,
2025
+ paddingTop: 4
2026
+ },
2027
+ children: [
2028
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2029
+ "button",
2030
+ {
2031
+ type: "button",
2032
+ disabled: testing || loading,
2033
+ onClick: () => void handleTestConnection(),
2034
+ style: {
2035
+ padding: "8px 16px",
2036
+ fontSize: 13,
2037
+ fontWeight: 500,
2038
+ borderRadius: 8,
2039
+ border: `1px solid ${themeVar("hairline")}`,
2040
+ background: themeVar("panelSurface"),
2041
+ color: themeVar("labelPrimary"),
2042
+ cursor: testing ? "not-allowed" : "pointer",
2043
+ opacity: testing ? 0.6 : 1
2044
+ },
2045
+ children: testing ? "Testing..." : "Test Connection"
2046
+ }
2047
+ ),
2048
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2049
+ "button",
2050
+ {
2051
+ type: "button",
2052
+ disabled: saving || loading,
2053
+ onClick: () => void handleSave(),
2054
+ style: {
2055
+ padding: "8px 18px",
2056
+ fontSize: 13,
2057
+ fontWeight: 600,
2058
+ borderRadius: 8,
2059
+ border: "none",
2060
+ background: themeVar("buttonPrimaryFill"),
2061
+ color: themeVar("buttonPrimaryText"),
2062
+ cursor: saving ? "not-allowed" : "pointer",
2063
+ opacity: saving ? 0.6 : 1
2064
+ },
2065
+ children: saving ? "Saving..." : "Save Settings"
2066
+ }
2067
+ ),
2068
+ source === "settings" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2069
+ "button",
2070
+ {
2071
+ type: "button",
2072
+ disabled: saving || loading,
2073
+ onClick: () => void handleReset(),
2074
+ style: {
2075
+ padding: "8px 14px",
2076
+ fontSize: 13,
2077
+ fontWeight: 500,
2078
+ borderRadius: 8,
2079
+ border: `1px solid ${themeVar("hairline")}`,
2080
+ background: "transparent",
2081
+ color: themeVar("labelSecondary"),
2082
+ cursor: "pointer",
2083
+ marginLeft: "auto"
2084
+ },
2085
+ children: "Reset to Auto-detected"
2086
+ }
2087
+ ),
2088
+ flash && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2089
+ "span",
2090
+ {
2091
+ style: {
2092
+ fontSize: 13,
2093
+ color: flash.kind === "ok" ? themeVar("stateSuccess") : themeVar("stateError"),
2094
+ fontWeight: 500
2095
+ },
2096
+ children: flash.message
2097
+ }
2098
+ )
2099
+ ]
2100
+ }
2101
+ )
2102
+ ]
2103
+ }
2104
+ ),
2105
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2106
+ "div",
2107
+ {
2108
+ style: {
2109
+ border: `1px solid ${themeVar("hairline")}`,
2110
+ borderRadius: 10,
2111
+ padding: "14px 18px",
2112
+ background: themeVar("panelSurface"),
2113
+ fontSize: 13,
2114
+ color: themeVar("labelSecondary"),
2115
+ lineHeight: 1.5
2116
+ },
2117
+ children: [
2118
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2119
+ "p",
2120
+ {
2121
+ style: {
2122
+ margin: "0 0 6px 0",
2123
+ fontWeight: 600,
2124
+ color: themeVar("labelPrimary")
2125
+ },
2126
+ children: "How OpenViking connection works:"
2127
+ }
2128
+ ),
2129
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("ul", { style: { margin: 0, paddingLeft: 18 }, children: [
2130
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { style: { marginBottom: 4 }, children: "All requests to OpenViking proxy through the DSH Desktop host, making it work seamlessly even when managing DSH remotely over Tailscale, mobile, or LAN." }),
2131
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { style: { marginBottom: 4 }, children: [
2132
+ "If OpenViking runs locally on the standard port (1933), the plugin auto-detects credentials from ",
2133
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { children: "~/.openviking/ovcli.conf" }),
2134
+ "."
2135
+ ] }),
2136
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { children: [
2137
+ "Custom values saved here are persisted in",
2138
+ " ",
2139
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { children: "~/.dsh/settings.yaml" }),
2140
+ " under the",
2141
+ " ",
2142
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { children: "openviking-status" }),
2143
+ " namespace."
2144
+ ] })
2145
+ ] })
2146
+ ]
2147
+ }
2148
+ )
2149
+ ]
2150
+ }
2151
+ );
1449
2152
  }
1450
2153
 
1451
2154
  // src/client/index.tsx
@@ -1467,6 +2170,21 @@ function apply(ctx) {
1467
2170
  ),
1468
2171
  "openviking-status: composer stats chip"
1469
2172
  );
2173
+ ctx.effect(
2174
+ () => ctx.slots.inject(
2175
+ "settings.section",
2176
+ () => ctx.slots.register(
2177
+ {
2178
+ name: "settings.section",
2179
+ id: "openviking-status",
2180
+ order: 35,
2181
+ label: () => "OpenViking"
2182
+ },
2183
+ OpenVikingSettingsSection
2184
+ )
2185
+ ),
2186
+ "openviking-status: settings section"
2187
+ );
1470
2188
  }
1471
2189
  return module.exports;
1472
2190
  },