@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.js CHANGED
@@ -13,6 +13,7 @@ import React2, {
13
13
  useRef as useRef2,
14
14
  useMemo
15
15
  } from "react";
16
+ import ReactDOM from "react-dom";
16
17
 
17
18
  // src/client/api.ts
18
19
  var DEFAULT_OPENVIKING_ENDPOINT = "http://127.0.0.1:1933";
@@ -66,6 +67,30 @@ var OpenVikingClient = class {
66
67
  this.endpoint = resolveEndpoint(endpoint);
67
68
  this.apiKey = resolveApiKey(apiKey);
68
69
  }
70
+ /**
71
+ * Обновление конфигурации клиента на лету (например, после сохранения настроек в UI).
72
+ */
73
+ updateConfig(config) {
74
+ if (config.endpoint && config.endpoint.trim()) {
75
+ this.endpoint = resolveEndpoint(config.endpoint);
76
+ }
77
+ if (config.apiKey !== void 0) {
78
+ this.apiKey = resolveApiKey(config.apiKey);
79
+ }
80
+ this.resolvedSessionIds.clear();
81
+ }
82
+ /**
83
+ * Очистить кэш разрешенных идентификаторов сессий.
84
+ */
85
+ clearResolvedSessions() {
86
+ this.resolvedSessionIds.clear();
87
+ }
88
+ /**
89
+ * Проверка, работает ли клиент через DSH Web Server proxy.
90
+ */
91
+ isProxy() {
92
+ return this.endpoint.startsWith("/") || this.endpoint.includes("/openviking-status/api");
93
+ }
69
94
  /**
70
95
  * Формирование заголовков запроса, включая опциональный заголовок авторизации
71
96
  */
@@ -107,6 +132,32 @@ var OpenVikingClient = class {
107
132
  * Проверка доступности и состояния сервиса OpenViking
108
133
  */
109
134
  async checkHealth() {
135
+ if (this.isProxy()) {
136
+ try {
137
+ const res = await fetch(`${this.endpoint}/health`, {
138
+ method: "GET",
139
+ headers: this.getHeaders()
140
+ });
141
+ if (!res.ok) {
142
+ return {
143
+ ok: false,
144
+ error: `HTTP ${res.status}: ${res.statusText}`
145
+ };
146
+ }
147
+ const body = await res.json().catch(() => ({}));
148
+ const isOk = body.ok !== false && body.status !== "error" && (body.ok === true || body.status === "ok" || body.status === "healthy" || res.ok);
149
+ return {
150
+ ok: isOk,
151
+ version: typeof body.version === "string" ? body.version : void 0,
152
+ storage: typeof body.storage === "string" ? body.storage : void 0
153
+ };
154
+ } catch (err) {
155
+ return {
156
+ ok: false,
157
+ error: err instanceof Error ? err.message : String(err)
158
+ };
159
+ }
160
+ }
110
161
  try {
111
162
  const res = await fetch(`${this.endpoint}/health`, {
112
163
  method: "GET",
@@ -144,6 +195,48 @@ var OpenVikingClient = class {
144
195
  if (!sessionId || !sessionId.trim()) {
145
196
  return { status: "missing" };
146
197
  }
198
+ if (this.isProxy()) {
199
+ try {
200
+ const res = await fetch(
201
+ `${this.endpoint}/session?id=${encodeURIComponent(sessionId.trim())}`,
202
+ {
203
+ method: "GET",
204
+ headers: this.getHeaders()
205
+ }
206
+ );
207
+ if (res.status === 401 || res.status === 403) {
208
+ return { status: "unauthorized" };
209
+ }
210
+ if (!res.ok) {
211
+ return { status: "error", detail: `HTTP ${res.status}` };
212
+ }
213
+ const data = await res.json();
214
+ if (data.status === "unauthorized") return { status: "unauthorized" };
215
+ if (data.status === "missing") return { status: "missing" };
216
+ if (data.status === "unreachable")
217
+ return {
218
+ status: "unreachable",
219
+ detail: typeof data.detail === "string" ? data.detail : void 0
220
+ };
221
+ if (data.status === "error")
222
+ return {
223
+ status: "error",
224
+ detail: typeof data.detail === "string" ? data.detail : void 0
225
+ };
226
+ if (data.status === "ok" && data.session) {
227
+ return {
228
+ status: "ok",
229
+ session: data.session
230
+ };
231
+ }
232
+ return { status: "error", detail: "malformed response from proxy" };
233
+ } catch (err) {
234
+ return {
235
+ status: "unreachable",
236
+ detail: err instanceof Error ? err.message : String(err)
237
+ };
238
+ }
239
+ }
147
240
  const candidates = this.getCandidateSessionIds(sessionId);
148
241
  for (const candidateId of candidates) {
149
242
  try {
@@ -213,6 +306,31 @@ var OpenVikingClient = class {
213
306
  if (!sessionId || !sessionId.trim()) {
214
307
  return { ok: false, error: "Missing sessionId" };
215
308
  }
309
+ if (this.isProxy()) {
310
+ try {
311
+ const res = await fetch(`${this.endpoint}/session/commit`, {
312
+ method: "POST",
313
+ headers: this.getHeaders(),
314
+ body: JSON.stringify({
315
+ sessionId: sessionId.trim(),
316
+ ...options ?? { keep_recent_count: 10 }
317
+ })
318
+ });
319
+ if (!res.ok) {
320
+ return { ok: false, error: `HTTP ${res.status}` };
321
+ }
322
+ const data = await res.json().catch(() => ({}));
323
+ return {
324
+ ok: data.ok === true,
325
+ error: typeof data.error === "string" ? data.error : void 0
326
+ };
327
+ } catch (err) {
328
+ return {
329
+ ok: false,
330
+ error: err instanceof Error ? err.message : String(err)
331
+ };
332
+ }
333
+ }
216
334
  const candidates = this.getCandidateSessionIds(sessionId);
217
335
  const bodyPayload = JSON.stringify(options ?? { keep_recent_count: 10 });
218
336
  let lastError = "Session commit failed";
@@ -292,6 +410,10 @@ var THEME = {
292
410
  stateWarning: "--dsw-alias-state-warn-primary",
293
411
  /** Утопленная поверхность: дорожка прогресс-бара. */
294
412
  insetSurface: "--dsw-alias-bg-layer-2",
413
+ /** Заливка основной кнопки действий. */
414
+ buttonPrimaryFill: "--dsw-alias-button-primary-fill",
415
+ /** Цвет текста на основной кнопке действий. */
416
+ buttonPrimaryText: "--dsw-alias-label-primary-inverted",
295
417
  /** Моноширинный шрифт для идентификаторов и путей. */
296
418
  fontMono: "--dsw-font-markdown-code-font-family"
297
419
  };
@@ -1225,8 +1347,22 @@ function StatusChipView({
1225
1347
  const [isCommitting, setIsCommitting] = useState2(false);
1226
1348
  const [commitError, setCommitError] = useState2(null);
1227
1349
  const [isHovered, setIsHovered] = useState2(false);
1350
+ const [statsHost, setStatsHost] = useState2(null);
1228
1351
  const popoverRef = useRef2(null);
1229
1352
  const apiClient = client ?? defaultOpenVikingClient;
1353
+ useEffect2(() => {
1354
+ if (typeof document === "undefined") return;
1355
+ function findHost() {
1356
+ const el = document.querySelector("[data-composer-stats]");
1357
+ setStatsHost((prev) => prev !== el ? el : prev);
1358
+ }
1359
+ findHost();
1360
+ const observer = new MutationObserver(() => {
1361
+ findHost();
1362
+ });
1363
+ observer.observe(document.body, { childList: true, subtree: true });
1364
+ return () => observer.disconnect();
1365
+ }, []);
1230
1366
  const conversationText = useMemo(() => {
1231
1367
  if (typeof contextText === "string") return contextText;
1232
1368
  return chatNodesToText(messages);
@@ -1312,10 +1448,11 @@ function StatusChipView({
1312
1448
  sessionUnreadable
1313
1449
  });
1314
1450
  const label = !isOnline ? "OV offline" : sessionUnreadable ? `OV: ${recalledCount} rec \xB7 no access` : `OV: ${recalledCount} rec \xB7 ${Math.round(pendingTokens / 1e3)}k pend`;
1315
- return /* @__PURE__ */ jsxs2(
1451
+ const chipElement = /* @__PURE__ */ jsxs2(
1316
1452
  "span",
1317
1453
  {
1318
1454
  className,
1455
+ "data-openviking-status": "true",
1319
1456
  style: { minWidth: 0, display: "inline-flex", position: "relative" },
1320
1457
  ref: popoverRef,
1321
1458
  children: [
@@ -1329,14 +1466,13 @@ function StatusChipView({
1329
1466
  "aria-expanded": isOpen,
1330
1467
  "aria-haspopup": "dialog",
1331
1468
  style: {
1332
- // Геометрия и типографика повторяют штатный чип статистики DSH:
1333
- // прозрачный фон, без рамки, шрифт и кегль наследуются от строки.
1334
1469
  boxSizing: "border-box",
1335
1470
  maxWidth: "100%",
1336
- color: themeVar("labelTertiary"),
1337
- font: "inherit",
1471
+ color: isHovered ? themeVar("labelSecondary") : themeVar("labelTertiary"),
1472
+ fontFamily: "var(--dsw-font-family, system-ui)",
1473
+ fontSize: "var(--dsh-content-font-size-secondary, 13px)",
1474
+ lineHeight: "calc(20px + var(--dsh-content-font-delta-secondary, 0px))",
1338
1475
  fontVariantNumeric: "tabular-nums",
1339
- lineHeight: "inherit",
1340
1476
  whiteSpace: "nowrap",
1341
1477
  background: isHovered ? themeVar("hoverBackground") : "transparent",
1342
1478
  border: "none",
@@ -1386,6 +1522,572 @@ function StatusChipView({
1386
1522
  ]
1387
1523
  }
1388
1524
  );
1525
+ if (statsHost && typeof document !== "undefined") {
1526
+ return ReactDOM.createPortal(chipElement, statsHost);
1527
+ }
1528
+ return /* @__PURE__ */ jsx2(
1529
+ "div",
1530
+ {
1531
+ style: {
1532
+ maxWidth: "var(--dsh-chat-content-width, 748px)",
1533
+ boxSizing: "border-box",
1534
+ width: "100%",
1535
+ padding: "4px calc(var(--dsh-composer-side-clearance, 0px) + 16px) 0px",
1536
+ fontSize: "var(--dsh-content-font-size-secondary, 13px)",
1537
+ lineHeight: "calc(20px + var(--dsh-content-font-delta-secondary, 0px))",
1538
+ justifyContent: "center",
1539
+ gap: "12px",
1540
+ margin: "0 auto",
1541
+ display: "flex"
1542
+ },
1543
+ children: chipElement
1544
+ }
1545
+ );
1546
+ }
1547
+
1548
+ // src/client/OpenVikingSettingsSection.tsx
1549
+ import { useState as useState3, useEffect as useEffect3 } from "react";
1550
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1551
+ var API_CONFIG = "/openviking-status/api/config";
1552
+ var API_TEST = "/openviking-status/api/test-connection";
1553
+ var SOURCE_LABELS = {
1554
+ ovcli: "Auto-detected from ~/.openviking/ovcli.conf",
1555
+ env: "Auto-detected from environment variables",
1556
+ ov: "Auto-detected from ~/.openviking/ov.conf",
1557
+ settings: "Custom override in DSH settings.yaml",
1558
+ default: "Default local configuration"
1559
+ };
1560
+ function OpenVikingSettingsSection({
1561
+ initialConfig,
1562
+ onConfigSaved,
1563
+ className,
1564
+ style
1565
+ }) {
1566
+ const [endpoint, setEndpoint] = useState3(
1567
+ initialConfig?.endpoint || "http://127.0.0.1:1933"
1568
+ );
1569
+ const [apiKey, setApiKey] = useState3("");
1570
+ const [showKey, setShowKey] = useState3(false);
1571
+ const [source, setSource] = useState3(
1572
+ initialConfig?.source || "default"
1573
+ );
1574
+ const [hasStoredKey, setHasStoredKey] = useState3(
1575
+ initialConfig?.hasApiKey || false
1576
+ );
1577
+ const [loading, setLoading] = useState3(!initialConfig);
1578
+ const [testing, setTesting] = useState3(false);
1579
+ const [saving, setSaving] = useState3(false);
1580
+ const [testResult, setTestResult] = useState3(null);
1581
+ const [flash, setFlash] = useState3(null);
1582
+ useEffect3(() => {
1583
+ if (initialConfig) return;
1584
+ let active = true;
1585
+ async function fetchConfig() {
1586
+ try {
1587
+ setLoading(true);
1588
+ const res = await fetch(API_CONFIG, { cache: "no-store" });
1589
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1590
+ const data = await res.json();
1591
+ if (!active) return;
1592
+ const newEp = data.endpoint || "http://127.0.0.1:1933";
1593
+ setEndpoint(newEp);
1594
+ setSource(data.source || "default");
1595
+ setHasStoredKey(data.hasApiKey);
1596
+ defaultOpenVikingClient.clearResolvedSessions();
1597
+ } catch {
1598
+ } finally {
1599
+ if (active) setLoading(false);
1600
+ }
1601
+ }
1602
+ void fetchConfig();
1603
+ return () => {
1604
+ active = false;
1605
+ };
1606
+ }, [initialConfig]);
1607
+ async function handleTestConnection() {
1608
+ setTesting(true);
1609
+ setTestResult(null);
1610
+ setFlash(null);
1611
+ try {
1612
+ const res = await fetch(API_TEST, {
1613
+ method: "POST",
1614
+ headers: { "Content-Type": "application/json" },
1615
+ body: JSON.stringify({
1616
+ endpoint: endpoint.trim(),
1617
+ apiKey: apiKey.trim() || void 0
1618
+ })
1619
+ });
1620
+ const data = await res.json();
1621
+ setTestResult(data);
1622
+ } catch (err) {
1623
+ setTestResult({
1624
+ ok: false,
1625
+ authenticated: false,
1626
+ error: String(err instanceof Error ? err.message : err)
1627
+ });
1628
+ } finally {
1629
+ setTesting(false);
1630
+ }
1631
+ }
1632
+ async function handleSave() {
1633
+ setSaving(true);
1634
+ setFlash(null);
1635
+ try {
1636
+ const res = await fetch(API_CONFIG, {
1637
+ method: "POST",
1638
+ headers: { "Content-Type": "application/json" },
1639
+ body: JSON.stringify({
1640
+ endpoint: endpoint.trim(),
1641
+ apiKey: apiKey.trim() || void 0
1642
+ })
1643
+ });
1644
+ const data = await res.json();
1645
+ if (!res.ok || data.error) {
1646
+ throw new Error(data.error || `HTTP ${res.status}`);
1647
+ }
1648
+ setFlash({ kind: "ok", message: "Settings saved successfully" });
1649
+ setSource("settings");
1650
+ defaultOpenVikingClient.clearResolvedSessions();
1651
+ if (apiKey.trim()) {
1652
+ setHasStoredKey(true);
1653
+ setApiKey("");
1654
+ }
1655
+ onConfigSaved?.(
1656
+ data.config || {
1657
+ endpoint,
1658
+ hasApiKey: hasStoredKey || Boolean(apiKey.trim()),
1659
+ source: "settings"
1660
+ }
1661
+ );
1662
+ } catch (err) {
1663
+ setFlash({
1664
+ kind: "err",
1665
+ message: `Failed to save: ${err instanceof Error ? err.message : String(err)}`
1666
+ });
1667
+ } finally {
1668
+ setSaving(false);
1669
+ }
1670
+ }
1671
+ async function handleReset() {
1672
+ setSaving(true);
1673
+ setFlash(null);
1674
+ setTestResult(null);
1675
+ try {
1676
+ const res = await fetch(API_CONFIG, {
1677
+ method: "POST",
1678
+ headers: { "Content-Type": "application/json" },
1679
+ body: JSON.stringify({ reset: true })
1680
+ });
1681
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1682
+ const cfgRes = await fetch(API_CONFIG, { cache: "no-store" });
1683
+ if (cfgRes.ok) {
1684
+ const data = await cfgRes.json();
1685
+ const newEp = data.endpoint || "http://127.0.0.1:1933";
1686
+ setEndpoint(newEp);
1687
+ setSource(data.source || "default");
1688
+ setHasStoredKey(data.hasApiKey);
1689
+ setApiKey("");
1690
+ defaultOpenVikingClient.clearResolvedSessions();
1691
+ setFlash({
1692
+ kind: "ok",
1693
+ message: "Reset to auto-detected local settings"
1694
+ });
1695
+ onConfigSaved?.(data);
1696
+ }
1697
+ } catch (err) {
1698
+ setFlash({
1699
+ kind: "err",
1700
+ message: `Failed to reset: ${err instanceof Error ? err.message : String(err)}`
1701
+ });
1702
+ } finally {
1703
+ setSaving(false);
1704
+ }
1705
+ }
1706
+ const sourceBadgeText = SOURCE_LABELS[source] || SOURCE_LABELS["default"];
1707
+ return /* @__PURE__ */ jsxs3(
1708
+ "div",
1709
+ {
1710
+ className: `ov-settings-root ${className || ""}`,
1711
+ style: {
1712
+ display: "flex",
1713
+ flexDirection: "column",
1714
+ gap: 20,
1715
+ maxWidth: 720,
1716
+ padding: "24px 28px",
1717
+ color: themeVar("labelPrimary"),
1718
+ fontSize: 14,
1719
+ lineHeight: 1.6,
1720
+ ...style
1721
+ },
1722
+ children: [
1723
+ /* @__PURE__ */ jsxs3("div", { children: [
1724
+ /* @__PURE__ */ jsxs3(
1725
+ "div",
1726
+ {
1727
+ style: {
1728
+ display: "flex",
1729
+ alignItems: "baseline",
1730
+ gap: 12,
1731
+ marginBottom: 6
1732
+ },
1733
+ children: [
1734
+ /* @__PURE__ */ jsx3(
1735
+ "h2",
1736
+ {
1737
+ style: {
1738
+ fontSize: 20,
1739
+ fontWeight: 600,
1740
+ margin: 0,
1741
+ color: themeVar("labelPrimary")
1742
+ },
1743
+ children: "OpenViking Status"
1744
+ }
1745
+ ),
1746
+ /* @__PURE__ */ jsx3(
1747
+ "span",
1748
+ {
1749
+ style: {
1750
+ fontSize: 12,
1751
+ fontWeight: 500,
1752
+ padding: "2px 8px",
1753
+ borderRadius: 6,
1754
+ background: themeVar("insetSurface"),
1755
+ border: `1px solid ${themeVar("hairline")}`,
1756
+ color: themeVar("labelSecondary")
1757
+ },
1758
+ children: sourceBadgeText
1759
+ }
1760
+ )
1761
+ ]
1762
+ }
1763
+ ),
1764
+ /* @__PURE__ */ jsx3(
1765
+ "p",
1766
+ {
1767
+ style: { margin: 0, color: themeVar("labelSecondary"), fontSize: 13 },
1768
+ children: "Configure connection credentials for monitoring OpenViking persistent memory and session status."
1769
+ }
1770
+ )
1771
+ ] }),
1772
+ /* @__PURE__ */ jsxs3(
1773
+ "div",
1774
+ {
1775
+ style: {
1776
+ border: `1px solid ${themeVar("hairline")}`,
1777
+ borderRadius: 12,
1778
+ padding: 20,
1779
+ background: themeVar("insetSurface"),
1780
+ display: "flex",
1781
+ flexDirection: "column",
1782
+ gap: 16
1783
+ },
1784
+ children: [
1785
+ /* @__PURE__ */ jsxs3("div", { children: [
1786
+ /* @__PURE__ */ jsx3(
1787
+ "label",
1788
+ {
1789
+ style: {
1790
+ display: "block",
1791
+ fontSize: 13,
1792
+ fontWeight: 600,
1793
+ marginBottom: 6,
1794
+ color: themeVar("labelPrimary")
1795
+ },
1796
+ children: "Daemon Endpoint"
1797
+ }
1798
+ ),
1799
+ /* @__PURE__ */ jsx3(
1800
+ "input",
1801
+ {
1802
+ type: "text",
1803
+ value: endpoint,
1804
+ onChange: (e) => setEndpoint(e.target.value),
1805
+ placeholder: "http://127.0.0.1:1933",
1806
+ style: {
1807
+ width: "100%",
1808
+ boxSizing: "border-box",
1809
+ padding: "9px 12px",
1810
+ fontSize: 14,
1811
+ fontFamily: themeVar("fontMono"),
1812
+ borderRadius: 8,
1813
+ border: `1px solid ${themeVar("hairline")}`,
1814
+ background: themeVar("panelSurface"),
1815
+ color: themeVar("labelPrimary"),
1816
+ outline: "none"
1817
+ }
1818
+ }
1819
+ ),
1820
+ /* @__PURE__ */ jsx3(
1821
+ "span",
1822
+ {
1823
+ style: {
1824
+ fontSize: 12,
1825
+ color: themeVar("labelTertiary"),
1826
+ marginTop: 4,
1827
+ display: "block"
1828
+ },
1829
+ children: "The URL of the local or remote OpenViking HTTP server. Resolved from the DSH host."
1830
+ }
1831
+ )
1832
+ ] }),
1833
+ /* @__PURE__ */ jsxs3("div", { children: [
1834
+ /* @__PURE__ */ jsxs3(
1835
+ "div",
1836
+ {
1837
+ style: {
1838
+ display: "flex",
1839
+ justifyContent: "space-between",
1840
+ alignItems: "center",
1841
+ marginBottom: 6
1842
+ },
1843
+ children: [
1844
+ /* @__PURE__ */ jsx3(
1845
+ "label",
1846
+ {
1847
+ style: {
1848
+ fontSize: 13,
1849
+ fontWeight: 600,
1850
+ color: themeVar("labelPrimary")
1851
+ },
1852
+ children: "API Token (Authentication)"
1853
+ }
1854
+ ),
1855
+ hasStoredKey && !apiKey && /* @__PURE__ */ jsx3(
1856
+ "span",
1857
+ {
1858
+ style: {
1859
+ fontSize: 12,
1860
+ color: themeVar("stateSuccess"),
1861
+ fontWeight: 500
1862
+ },
1863
+ children: "\u25CF Active token configured"
1864
+ }
1865
+ )
1866
+ ]
1867
+ }
1868
+ ),
1869
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: 8 }, children: [
1870
+ /* @__PURE__ */ jsx3(
1871
+ "input",
1872
+ {
1873
+ type: showKey ? "text" : "password",
1874
+ value: apiKey,
1875
+ onChange: (e) => setApiKey(e.target.value),
1876
+ placeholder: hasStoredKey ? "(Token stored \u2014 leave blank to keep unchanged)" : "Optional or required if daemon uses auth_mode: api_key",
1877
+ style: {
1878
+ flex: 1,
1879
+ padding: "9px 12px",
1880
+ fontSize: 14,
1881
+ fontFamily: themeVar("fontMono"),
1882
+ borderRadius: 8,
1883
+ border: `1px solid ${themeVar("hairline")}`,
1884
+ background: themeVar("panelSurface"),
1885
+ color: themeVar("labelPrimary"),
1886
+ outline: "none"
1887
+ }
1888
+ }
1889
+ ),
1890
+ /* @__PURE__ */ jsx3(
1891
+ "button",
1892
+ {
1893
+ type: "button",
1894
+ onClick: () => setShowKey((v) => !v),
1895
+ style: {
1896
+ padding: "0 14px",
1897
+ fontSize: 13,
1898
+ fontWeight: 500,
1899
+ borderRadius: 8,
1900
+ border: `1px solid ${themeVar("hairline")}`,
1901
+ background: themeVar("panelSurface"),
1902
+ color: themeVar("labelSecondary"),
1903
+ cursor: "pointer"
1904
+ },
1905
+ children: showKey ? "Hide" : "Show"
1906
+ }
1907
+ )
1908
+ ] }),
1909
+ /* @__PURE__ */ jsx3(
1910
+ "span",
1911
+ {
1912
+ style: {
1913
+ fontSize: 12,
1914
+ color: themeVar("labelTertiary"),
1915
+ marginTop: 4,
1916
+ display: "block"
1917
+ },
1918
+ children: "Required when the daemon runs with auth_mode: api_key. Local tokens are typically found in ~/.openviking/ovcli.conf."
1919
+ }
1920
+ )
1921
+ ] }),
1922
+ testResult && /* @__PURE__ */ jsxs3(
1923
+ "div",
1924
+ {
1925
+ style: {
1926
+ padding: "10px 14px",
1927
+ borderRadius: 8,
1928
+ fontSize: 13,
1929
+ display: "flex",
1930
+ alignItems: "center",
1931
+ gap: 8,
1932
+ border: `1px solid ${testResult.ok && testResult.authenticated ? themeVar("stateSuccess") : themeVar("stateError")}`,
1933
+ background: themeVar("panelSurface"),
1934
+ color: testResult.ok && testResult.authenticated ? themeVar("stateSuccess") : themeVar("stateError")
1935
+ },
1936
+ children: [
1937
+ /* @__PURE__ */ jsx3("span", { children: testResult.ok && testResult.authenticated ? "\u25CF" : "\u2715" }),
1938
+ /* @__PURE__ */ jsx3("div", { style: { flex: 1 }, children: testResult.ok && testResult.authenticated ? /* @__PURE__ */ jsxs3("div", { children: [
1939
+ /* @__PURE__ */ jsx3("strong", { children: "Connected successfully" }),
1940
+ testResult.version && /* @__PURE__ */ jsxs3("span", { children: [
1941
+ " \xB7 ",
1942
+ testResult.version
1943
+ ] }),
1944
+ testResult.storage && /* @__PURE__ */ jsxs3("span", { children: [
1945
+ " (storage: ",
1946
+ testResult.storage,
1947
+ ")"
1948
+ ] })
1949
+ ] }) : /* @__PURE__ */ jsxs3("div", { children: [
1950
+ /* @__PURE__ */ jsx3("strong", { children: "Connection failed: " }),
1951
+ /* @__PURE__ */ jsx3("span", { children: testResult.error || "Unable to reach daemon or token unauthorized" })
1952
+ ] }) })
1953
+ ]
1954
+ }
1955
+ ),
1956
+ /* @__PURE__ */ jsxs3(
1957
+ "div",
1958
+ {
1959
+ style: {
1960
+ display: "flex",
1961
+ flexWrap: "wrap",
1962
+ alignItems: "center",
1963
+ gap: 10,
1964
+ paddingTop: 4
1965
+ },
1966
+ children: [
1967
+ /* @__PURE__ */ jsx3(
1968
+ "button",
1969
+ {
1970
+ type: "button",
1971
+ disabled: testing || loading,
1972
+ onClick: () => void handleTestConnection(),
1973
+ style: {
1974
+ padding: "8px 16px",
1975
+ fontSize: 13,
1976
+ fontWeight: 500,
1977
+ borderRadius: 8,
1978
+ border: `1px solid ${themeVar("hairline")}`,
1979
+ background: themeVar("panelSurface"),
1980
+ color: themeVar("labelPrimary"),
1981
+ cursor: testing ? "not-allowed" : "pointer",
1982
+ opacity: testing ? 0.6 : 1
1983
+ },
1984
+ children: testing ? "Testing..." : "Test Connection"
1985
+ }
1986
+ ),
1987
+ /* @__PURE__ */ jsx3(
1988
+ "button",
1989
+ {
1990
+ type: "button",
1991
+ disabled: saving || loading,
1992
+ onClick: () => void handleSave(),
1993
+ style: {
1994
+ padding: "8px 18px",
1995
+ fontSize: 13,
1996
+ fontWeight: 600,
1997
+ borderRadius: 8,
1998
+ border: "none",
1999
+ background: themeVar("buttonPrimaryFill"),
2000
+ color: themeVar("buttonPrimaryText"),
2001
+ cursor: saving ? "not-allowed" : "pointer",
2002
+ opacity: saving ? 0.6 : 1
2003
+ },
2004
+ children: saving ? "Saving..." : "Save Settings"
2005
+ }
2006
+ ),
2007
+ source === "settings" && /* @__PURE__ */ jsx3(
2008
+ "button",
2009
+ {
2010
+ type: "button",
2011
+ disabled: saving || loading,
2012
+ onClick: () => void handleReset(),
2013
+ style: {
2014
+ padding: "8px 14px",
2015
+ fontSize: 13,
2016
+ fontWeight: 500,
2017
+ borderRadius: 8,
2018
+ border: `1px solid ${themeVar("hairline")}`,
2019
+ background: "transparent",
2020
+ color: themeVar("labelSecondary"),
2021
+ cursor: "pointer",
2022
+ marginLeft: "auto"
2023
+ },
2024
+ children: "Reset to Auto-detected"
2025
+ }
2026
+ ),
2027
+ flash && /* @__PURE__ */ jsx3(
2028
+ "span",
2029
+ {
2030
+ style: {
2031
+ fontSize: 13,
2032
+ color: flash.kind === "ok" ? themeVar("stateSuccess") : themeVar("stateError"),
2033
+ fontWeight: 500
2034
+ },
2035
+ children: flash.message
2036
+ }
2037
+ )
2038
+ ]
2039
+ }
2040
+ )
2041
+ ]
2042
+ }
2043
+ ),
2044
+ /* @__PURE__ */ jsxs3(
2045
+ "div",
2046
+ {
2047
+ style: {
2048
+ border: `1px solid ${themeVar("hairline")}`,
2049
+ borderRadius: 10,
2050
+ padding: "14px 18px",
2051
+ background: themeVar("panelSurface"),
2052
+ fontSize: 13,
2053
+ color: themeVar("labelSecondary"),
2054
+ lineHeight: 1.5
2055
+ },
2056
+ children: [
2057
+ /* @__PURE__ */ jsx3(
2058
+ "p",
2059
+ {
2060
+ style: {
2061
+ margin: "0 0 6px 0",
2062
+ fontWeight: 600,
2063
+ color: themeVar("labelPrimary")
2064
+ },
2065
+ children: "How OpenViking connection works:"
2066
+ }
2067
+ ),
2068
+ /* @__PURE__ */ jsxs3("ul", { style: { margin: 0, paddingLeft: 18 }, children: [
2069
+ /* @__PURE__ */ jsx3("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." }),
2070
+ /* @__PURE__ */ jsxs3("li", { style: { marginBottom: 4 }, children: [
2071
+ "If OpenViking runs locally on the standard port (1933), the plugin auto-detects credentials from ",
2072
+ /* @__PURE__ */ jsx3("code", { children: "~/.openviking/ovcli.conf" }),
2073
+ "."
2074
+ ] }),
2075
+ /* @__PURE__ */ jsxs3("li", { children: [
2076
+ "Custom values saved here are persisted in",
2077
+ " ",
2078
+ /* @__PURE__ */ jsx3("code", { children: "~/.dsh/settings.yaml" }),
2079
+ " under the",
2080
+ " ",
2081
+ /* @__PURE__ */ jsx3("code", { children: "openviking-status" }),
2082
+ " namespace."
2083
+ ] })
2084
+ ] })
2085
+ ]
2086
+ }
2087
+ )
2088
+ ]
2089
+ }
2090
+ );
1389
2091
  }
1390
2092
 
1391
2093
  // src/client/index.tsx
@@ -1407,11 +2109,27 @@ function apply(ctx) {
1407
2109
  ),
1408
2110
  "openviking-status: composer stats chip"
1409
2111
  );
2112
+ ctx.effect(
2113
+ () => ctx.slots.inject(
2114
+ "settings.section",
2115
+ () => ctx.slots.register(
2116
+ {
2117
+ name: "settings.section",
2118
+ id: "openviking-status",
2119
+ order: 35,
2120
+ label: () => "OpenViking"
2121
+ },
2122
+ OpenVikingSettingsSection
2123
+ )
2124
+ ),
2125
+ "openviking-status: settings section"
2126
+ );
1410
2127
  }
1411
2128
  export {
1412
2129
  COMMIT_THRESHOLD,
1413
2130
  DEFAULT_OPENVIKING_ENDPOINT,
1414
2131
  OpenVikingClient,
2132
+ OpenVikingSettingsSection,
1415
2133
  OpenVikingStatusChip,
1416
2134
  OpenVikingStatusPopover,
1417
2135
  THEME,