@xcanwin/manyoyo 5.8.3 → 5.8.5

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.
@@ -341,6 +341,17 @@ textarea:focus-visible {
341
341
  white-space: pre-wrap;
342
342
  }
343
343
 
344
+ .modal-success {
345
+ margin-top: 10px;
346
+ color: #196c43;
347
+ font-size: 13px;
348
+ padding: 8px 10px;
349
+ border: 1px solid #9fd1b4;
350
+ border-radius: 8px;
351
+ background: #eef9f1;
352
+ white-space: pre-wrap;
353
+ }
354
+
344
355
  .config-editor {
345
356
  width: 100%;
346
357
  min-height: 340px;
@@ -136,11 +136,12 @@
136
136
  <div class="modal-body">
137
137
  <div id="configPath" class="modal-tip"></div>
138
138
  <textarea id="configEditor" class="config-editor" spellcheck="false"></textarea>
139
+ <div id="configStatus" class="modal-success" hidden></div>
139
140
  <div id="configError" class="modal-error" hidden></div>
140
141
  </div>
141
142
  <footer class="modal-footer">
142
143
  <button type="button" id="configReloadBtn" class="secondary">重新加载</button>
143
- <button type="button" id="configSaveBtn">只读</button>
144
+ <button type="button" id="configSaveBtn">保存</button>
144
145
  </footer>
145
146
  </section>
146
147
  </div>
@@ -53,6 +53,7 @@
53
53
  agentTemplateModalOpen: false,
54
54
  configLoading: false,
55
55
  configSaving: false,
56
+ configSaveMessage: '',
56
57
  createLoading: false,
57
58
  createSubmitting: false,
58
59
  agentTemplateSaving: false,
@@ -127,6 +128,7 @@
127
128
  const configModalTitle = document.getElementById('configModalTitle');
128
129
  const configPath = document.getElementById('configPath');
129
130
  const configEditor = document.getElementById('configEditor');
131
+ const configStatus = document.getElementById('configStatus');
130
132
  const configError = document.getElementById('configError');
131
133
  const configReloadBtn = document.getElementById('configReloadBtn');
132
134
  const configSaveBtn = document.getElementById('configSaveBtn');
@@ -726,6 +728,18 @@
726
728
  configError.textContent = text;
727
729
  }
728
730
 
731
+ function showConfigStatus(message) {
732
+ if (!configStatus) return;
733
+ const text = String(message || '').trim();
734
+ if (!text) {
735
+ configStatus.hidden = true;
736
+ configStatus.textContent = '';
737
+ return;
738
+ }
739
+ configStatus.hidden = false;
740
+ configStatus.textContent = text;
741
+ }
742
+
729
743
  function showDirectoryPickerError(message) {
730
744
  if (!directoryPickerError) return;
731
745
  const text = String(message || '').trim();
@@ -2032,7 +2046,12 @@
2032
2046
  openConfigBtn.disabled = state.configLoading || state.configSaving;
2033
2047
  }
2034
2048
  if (configSaveBtn) {
2035
- configSaveBtn.disabled = true;
2049
+ configSaveBtn.disabled = state.configLoading
2050
+ || state.configSaving
2051
+ || !state.configModalOpen
2052
+ || !state.configSnapshot
2053
+ || state.configSnapshot.editable === false;
2054
+ configSaveBtn.textContent = state.configSaving ? '保存中...' : '保存';
2036
2055
  }
2037
2056
  if (configReloadBtn) {
2038
2057
  configReloadBtn.disabled = state.configLoading || state.configSaving;
@@ -2224,30 +2243,33 @@
2224
2243
  return snapshot;
2225
2244
  }
2226
2245
 
2246
+ function renderConfigModalSnapshot(config) {
2247
+ if (configModalTitle) {
2248
+ configModalTitle.textContent = '编辑配置 (~/.manyoyo/manyoyo.json)';
2249
+ }
2250
+ if (configPath) {
2251
+ const lines = [config.path || ''];
2252
+ if (config.notice) {
2253
+ lines.push(config.notice);
2254
+ }
2255
+ configPath.textContent = lines.filter(Boolean).join('\n');
2256
+ }
2257
+ if (configEditor) {
2258
+ configEditor.readOnly = config.editable === false;
2259
+ configEditor.value = typeof config.raw === 'string' ? config.raw : '';
2260
+ }
2261
+ }
2262
+
2227
2263
  async function openConfigModal() {
2228
2264
  closeCreateModal();
2229
2265
  state.configLoading = true;
2266
+ state.configSaveMessage = '';
2230
2267
  showConfigError('');
2268
+ showConfigStatus('');
2231
2269
  syncUi();
2232
2270
  try {
2233
2271
  const config = await fetchConfigSnapshot();
2234
- if (configModalTitle) {
2235
- configModalTitle.textContent = '查看配置摘要 (~/.manyoyo/manyoyo.json)';
2236
- }
2237
- if (configPath) {
2238
- const lines = [config.path || ''];
2239
- if (config.notice) {
2240
- lines.push(config.notice);
2241
- }
2242
- configPath.textContent = lines.filter(Boolean).join('\n');
2243
- }
2244
- if (configEditor) {
2245
- configEditor.readOnly = true;
2246
- configEditor.value = stringifyPrettyJson({
2247
- defaults: config.defaults || {},
2248
- runs: config.parsed && config.parsed.runs ? config.parsed.runs : {}
2249
- });
2250
- }
2272
+ renderConfigModalSnapshot(config);
2251
2273
  if (config.parseError) {
2252
2274
  showConfigError('当前文件存在解析错误:' + config.parseError);
2253
2275
  }
@@ -2267,7 +2289,29 @@
2267
2289
  }
2268
2290
 
2269
2291
  async function saveConfig() {
2270
- alert('Web 端已禁用明文配置编辑,请在本地 ~/.manyoyo/manyoyo.json 中维护敏感配置。');
2292
+ if (!configEditor || !state.configSnapshot || state.configSnapshot.editable === false) {
2293
+ return;
2294
+ }
2295
+ state.configSaving = true;
2296
+ state.configSaveMessage = '';
2297
+ showConfigError('');
2298
+ showConfigStatus('');
2299
+ syncUi();
2300
+ try {
2301
+ await api('/api/config', {
2302
+ method: 'PUT',
2303
+ body: JSON.stringify({ raw: configEditor.value || '' })
2304
+ });
2305
+ const config = await fetchConfigSnapshot();
2306
+ renderConfigModalSnapshot(config);
2307
+ state.configSaveMessage = '已保存到 ~/.manyoyo/manyoyo.json';
2308
+ showConfigStatus(state.configSaveMessage);
2309
+ } catch (e) {
2310
+ showConfigError(e.message);
2311
+ } finally {
2312
+ state.configSaving = false;
2313
+ syncUi();
2314
+ }
2271
2315
  }
2272
2316
 
2273
2317
  async function openCreateModal() {
@@ -3562,6 +3606,16 @@
3562
3606
  });
3563
3607
  }
3564
3608
 
3609
+ if (configEditor) {
3610
+ configEditor.addEventListener('input', function () {
3611
+ if (!state.configSaveMessage) {
3612
+ return;
3613
+ }
3614
+ state.configSaveMessage = '';
3615
+ showConfigStatus('');
3616
+ });
3617
+ }
3618
+
3565
3619
  if (createCancelBtn) {
3566
3620
  createCancelBtn.addEventListener('click', function () {
3567
3621
  closeCreateModal();
package/lib/web/server.js CHANGED
@@ -32,6 +32,7 @@ const WEB_AUTH_TTL_SECONDS = 12 * 60 * 60;
32
32
  const WEB_SESSION_KEY_SEPARATOR = '~';
33
33
  const WEB_DEFAULT_AGENT_ID = 'default';
34
34
  const WEB_DEFAULT_AGENT_NAME = 'AGENT 1';
35
+ const WEB_CONFIG_KEEP_SECRET_PLACEHOLDER = '***HIDDEN_SECRET***';
35
36
  const FRONTEND_DIR = path.join(__dirname, 'frontend');
36
37
  const SAFE_CONTAINER_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
37
38
  const IMAGE_VERSION_TAG_PATTERN = /^(\d+\.\d+\.\d+)-([A-Za-z0-9][A-Za-z0-9_.-]*)$/;
@@ -1635,6 +1636,334 @@ function isSensitiveConfigKey(key) {
1635
1636
  return Boolean(normalized) && SENSITIVE_CONFIG_KEY_PATTERN.test(normalized);
1636
1637
  }
1637
1638
 
1639
+ function readConfigQuotedString(text, startIndex) {
1640
+ const quote = text[startIndex];
1641
+ let value = '';
1642
+
1643
+ for (let i = startIndex + 1; i < text.length; i += 1) {
1644
+ const ch = text[i];
1645
+ if (ch === '\\') {
1646
+ value += ch;
1647
+ if (i + 1 < text.length) {
1648
+ value += text[i + 1];
1649
+ i += 1;
1650
+ }
1651
+ continue;
1652
+ }
1653
+ if (ch === quote) {
1654
+ return {
1655
+ value,
1656
+ end: i + 1
1657
+ };
1658
+ }
1659
+ value += ch;
1660
+ }
1661
+
1662
+ return null;
1663
+ }
1664
+
1665
+ function isConfigIdentifierStart(ch) {
1666
+ return /[A-Za-z_$]/.test(ch);
1667
+ }
1668
+
1669
+ function isConfigIdentifierPart(ch) {
1670
+ return /[A-Za-z0-9_$]/.test(ch);
1671
+ }
1672
+
1673
+ function skipConfigTrivia(text, index) {
1674
+ let cursor = index;
1675
+ while (cursor < text.length) {
1676
+ const ch = text[cursor];
1677
+ const next = text[cursor + 1];
1678
+ if (/\s/.test(ch)) {
1679
+ cursor += 1;
1680
+ continue;
1681
+ }
1682
+ if (ch === '/' && next === '/') {
1683
+ cursor += 2;
1684
+ while (cursor < text.length && text[cursor] !== '\n') {
1685
+ cursor += 1;
1686
+ }
1687
+ continue;
1688
+ }
1689
+ if (ch === '/' && next === '*') {
1690
+ cursor += 2;
1691
+ while (cursor + 1 < text.length && !(text[cursor] === '*' && text[cursor + 1] === '/')) {
1692
+ cursor += 1;
1693
+ }
1694
+ cursor = cursor + 1 < text.length ? cursor + 2 : text.length;
1695
+ continue;
1696
+ }
1697
+ break;
1698
+ }
1699
+ return cursor;
1700
+ }
1701
+
1702
+ function scanConfigValueEnd(text, startIndex) {
1703
+ let cursor = startIndex;
1704
+ let stringQuote = '';
1705
+ let lineComment = false;
1706
+ let blockComment = false;
1707
+ let depth = 0;
1708
+
1709
+ for (; cursor < text.length; cursor += 1) {
1710
+ const ch = text[cursor];
1711
+ const next = text[cursor + 1];
1712
+
1713
+ if (lineComment) {
1714
+ if (ch === '\n') {
1715
+ lineComment = false;
1716
+ }
1717
+ continue;
1718
+ }
1719
+ if (blockComment) {
1720
+ if (ch === '*' && next === '/') {
1721
+ blockComment = false;
1722
+ cursor += 1;
1723
+ }
1724
+ continue;
1725
+ }
1726
+ if (stringQuote) {
1727
+ if (ch === '\\') {
1728
+ cursor += 1;
1729
+ continue;
1730
+ }
1731
+ if (ch === stringQuote) {
1732
+ stringQuote = '';
1733
+ }
1734
+ continue;
1735
+ }
1736
+
1737
+ if (ch === '/' && next === '/') {
1738
+ lineComment = true;
1739
+ cursor += 1;
1740
+ continue;
1741
+ }
1742
+ if (ch === '/' && next === '*') {
1743
+ blockComment = true;
1744
+ cursor += 1;
1745
+ continue;
1746
+ }
1747
+ if (ch === '"' || ch === '\'') {
1748
+ stringQuote = ch;
1749
+ continue;
1750
+ }
1751
+ if (ch === '{' || ch === '[' || ch === '(') {
1752
+ depth += 1;
1753
+ continue;
1754
+ }
1755
+ if (ch === '}' || ch === ']' || ch === ')') {
1756
+ if (depth === 0) {
1757
+ break;
1758
+ }
1759
+ depth -= 1;
1760
+ continue;
1761
+ }
1762
+ if (depth === 0 && ch === ',') {
1763
+ break;
1764
+ }
1765
+ }
1766
+
1767
+ let end = cursor;
1768
+ while (end > startIndex && /\s/.test(text[end - 1])) {
1769
+ end -= 1;
1770
+ }
1771
+ return end;
1772
+ }
1773
+
1774
+ function findConfigRootObjectStart(text) {
1775
+ const start = skipConfigTrivia(String(text || ''), 0);
1776
+ return text[start] === '{' ? start : -1;
1777
+ }
1778
+
1779
+ function readConfigPropertyToken(text, startIndex) {
1780
+ const ch = text[startIndex];
1781
+ if (ch === '"' || ch === '\'') {
1782
+ const token = readConfigQuotedString(text, startIndex);
1783
+ if (!token) {
1784
+ return null;
1785
+ }
1786
+ return token;
1787
+ }
1788
+ if (!isConfigIdentifierStart(ch)) {
1789
+ return null;
1790
+ }
1791
+ let end = startIndex + 1;
1792
+ while (end < text.length && isConfigIdentifierPart(text[end])) {
1793
+ end += 1;
1794
+ }
1795
+ return {
1796
+ value: text.slice(startIndex, end),
1797
+ end
1798
+ };
1799
+ }
1800
+
1801
+ function findConfigObjectPropertyValueRange(text, objectStartIndex, propertyName) {
1802
+ let cursor = skipConfigTrivia(text, objectStartIndex + 1);
1803
+ while (cursor < text.length) {
1804
+ cursor = skipConfigTrivia(text, cursor);
1805
+ if (text[cursor] === '}') {
1806
+ return null;
1807
+ }
1808
+ const token = readConfigPropertyToken(text, cursor);
1809
+ if (!token) {
1810
+ return null;
1811
+ }
1812
+ cursor = skipConfigTrivia(text, token.end);
1813
+ if (text[cursor] !== ':') {
1814
+ return null;
1815
+ }
1816
+ const valueStart = skipConfigTrivia(text, cursor + 1);
1817
+ const valueEnd = scanConfigValueEnd(text, valueStart);
1818
+ if (token.value === propertyName) {
1819
+ return { start: valueStart, end: valueEnd };
1820
+ }
1821
+ cursor = skipConfigTrivia(text, valueEnd);
1822
+ if (text[cursor] === ',') {
1823
+ cursor += 1;
1824
+ continue;
1825
+ }
1826
+ if (text[cursor] === '}') {
1827
+ return null;
1828
+ }
1829
+ }
1830
+ return null;
1831
+ }
1832
+
1833
+ function findConfigValueRangeByPath(text, pathParts) {
1834
+ if (!Array.isArray(pathParts) || pathParts.length === 0) {
1835
+ return null;
1836
+ }
1837
+ let objectStart = findConfigRootObjectStart(text);
1838
+ if (objectStart === -1) {
1839
+ return null;
1840
+ }
1841
+ let range = null;
1842
+ for (let i = 0; i < pathParts.length; i += 1) {
1843
+ range = findConfigObjectPropertyValueRange(text, objectStart, pathParts[i]);
1844
+ if (!range) {
1845
+ return null;
1846
+ }
1847
+ if (i === pathParts.length - 1) {
1848
+ return range;
1849
+ }
1850
+ const nextObjectStart = skipConfigTrivia(text, range.start);
1851
+ if (text[nextObjectStart] !== '{') {
1852
+ return null;
1853
+ }
1854
+ objectStart = nextObjectStart;
1855
+ }
1856
+ return range;
1857
+ }
1858
+
1859
+ function collectSensitiveConfigPaths(value, pathParts = []) {
1860
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1861
+ return [];
1862
+ }
1863
+ const result = [];
1864
+ Object.entries(toPlainObject(value)).forEach(([key, item]) => {
1865
+ const nextPath = pathParts.concat(key);
1866
+ if (isSensitiveConfigKey(key)) {
1867
+ result.push(nextPath);
1868
+ return;
1869
+ }
1870
+ if (item && typeof item === 'object' && !Array.isArray(item)) {
1871
+ result.push(...collectSensitiveConfigPaths(item, nextPath));
1872
+ }
1873
+ });
1874
+ return result;
1875
+ }
1876
+
1877
+ function collectSensitivePlaceholderPaths(value, pathParts = []) {
1878
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1879
+ return [];
1880
+ }
1881
+ const result = [];
1882
+ Object.entries(toPlainObject(value)).forEach(([key, item]) => {
1883
+ const nextPath = pathParts.concat(key);
1884
+ if (isSensitiveConfigKey(key)) {
1885
+ if (item === WEB_CONFIG_KEEP_SECRET_PLACEHOLDER) {
1886
+ result.push(nextPath);
1887
+ }
1888
+ return;
1889
+ }
1890
+ if (item && typeof item === 'object' && !Array.isArray(item)) {
1891
+ result.push(...collectSensitivePlaceholderPaths(item, nextPath));
1892
+ }
1893
+ });
1894
+ return result;
1895
+ }
1896
+
1897
+ function applyConfigTextReplacements(text, replacements) {
1898
+ return replacements
1899
+ .slice()
1900
+ .sort((a, b) => b.start - a.start)
1901
+ .reduce((result, item) => `${result.slice(0, item.start)}${item.text}${result.slice(item.end)}`, text);
1902
+ }
1903
+
1904
+ function buildConfigPathLabel(pathParts) {
1905
+ return (Array.isArray(pathParts) ? pathParts : []).join('.');
1906
+ }
1907
+
1908
+ function maskWebConfigRaw(raw, parsed) {
1909
+ const text = String(raw || '');
1910
+ const replacements = collectSensitiveConfigPaths(parsed).map(pathParts => {
1911
+ const range = findConfigValueRangeByPath(text, pathParts);
1912
+ if (!range) {
1913
+ throw new Error(`敏感字段定位失败: ${buildConfigPathLabel(pathParts)}`);
1914
+ }
1915
+ return {
1916
+ start: range.start,
1917
+ end: range.end,
1918
+ text: JSON.stringify(WEB_CONFIG_KEEP_SECRET_PLACEHOLDER)
1919
+ };
1920
+ });
1921
+ return applyConfigTextReplacements(text, replacements);
1922
+ }
1923
+
1924
+ function parseConfigRawObject(raw) {
1925
+ const parsed = JSON5.parse(String(raw || ''));
1926
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
1927
+ throw new Error('配置根节点必须是对象(map)');
1928
+ }
1929
+ return toPlainObject(parsed);
1930
+ }
1931
+
1932
+ function restoreWebConfigSecrets(raw, snapshot) {
1933
+ const text = String(raw || '');
1934
+ if (!text.includes(WEB_CONFIG_KEEP_SECRET_PLACEHOLDER)) {
1935
+ return text;
1936
+ }
1937
+ if (!snapshot || snapshot.parseError) {
1938
+ throw new Error('当前配置存在解析错误,无法回填敏感值');
1939
+ }
1940
+
1941
+ const editedConfig = parseConfigRawObject(text);
1942
+ const placeholderPaths = collectSensitivePlaceholderPaths(editedConfig);
1943
+ if (!placeholderPaths.length) {
1944
+ return text;
1945
+ }
1946
+
1947
+ const currentRaw = String(snapshot.raw || '');
1948
+ const replacements = placeholderPaths.map(pathParts => {
1949
+ const editedRange = findConfigValueRangeByPath(text, pathParts);
1950
+ const currentRange = findConfigValueRangeByPath(currentRaw, pathParts);
1951
+ if (!editedRange) {
1952
+ throw new Error(`敏感字段定位失败: ${buildConfigPathLabel(pathParts)}`);
1953
+ }
1954
+ if (!currentRange) {
1955
+ throw new Error(`敏感字段缺少可保留的旧值: ${buildConfigPathLabel(pathParts)}`);
1956
+ }
1957
+ return {
1958
+ start: editedRange.start,
1959
+ end: editedRange.end,
1960
+ text: currentRaw.slice(currentRange.start, currentRange.end)
1961
+ };
1962
+ });
1963
+
1964
+ return applyConfigTextReplacements(text, replacements);
1965
+ }
1966
+
1638
1967
  function redactConfigValue(value) {
1639
1968
  if (Array.isArray(value)) {
1640
1969
  return value.map(item => redactConfigValue(item));
@@ -1673,13 +2002,29 @@ function redactConfigObject(value) {
1673
2002
 
1674
2003
  function buildSafeWebConfigSnapshot(snapshot, ctx) {
1675
2004
  const parsed = snapshot && snapshot.parseError ? {} : toPlainObject(snapshot && snapshot.parsed);
2005
+ let raw = '';
2006
+ let editable = false;
2007
+ let notice = 'Web 端显示原文 JSON5;敏感值以 ***HIDDEN_SECRET*** 占位,保存时会保留原值。';
2008
+ if (snapshot && !snapshot.parseError) {
2009
+ try {
2010
+ raw = maskWebConfigRaw(snapshot.raw || '', parsed);
2011
+ editable = true;
2012
+ } catch (e) {
2013
+ raw = '';
2014
+ editable = false;
2015
+ notice = '当前配置无法安全脱敏显示,请在本地 manyoyo.json 中维护。';
2016
+ }
2017
+ } else if (snapshot && snapshot.parseError) {
2018
+ notice = '当前配置解析失败,Web 端暂不提供安全编辑;请先在本地修复 manyoyo.json。';
2019
+ }
1676
2020
  return {
1677
2021
  path: snapshot && snapshot.path ? snapshot.path : path.resolve(getDefaultWebConfigPath()),
2022
+ raw,
1678
2023
  parsed: redactConfigObject(parsed),
1679
2024
  defaults: redactConfigObject(buildConfigDefaults(ctx, parsed)),
1680
2025
  parseError: snapshot && snapshot.parseError ? snapshot.parseError : null,
1681
- editable: false,
1682
- notice: 'Web 端仅显示脱敏后的配置摘要;敏感值请在本地 manyoyo.json 中维护。'
2026
+ editable,
2027
+ notice
1683
2028
  };
1684
2029
  }
1685
2030
 
@@ -1995,11 +2340,7 @@ function readWebConfigSnapshot(configPath) {
1995
2340
  }
1996
2341
 
1997
2342
  function parseAndValidateConfigRaw(raw) {
1998
- const parsed = JSON5.parse(String(raw || ''));
1999
- if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
2000
- throw new Error('配置根节点必须是对象(map)');
2001
- }
2002
- const config = toPlainObject(parsed);
2343
+ const config = parseConfigRawObject(raw);
2003
2344
  validateWebConfigShape(config);
2004
2345
  return config;
2005
2346
  }
@@ -3223,9 +3564,12 @@ async function handleWebApi(req, res, pathname, ctx, state) {
3223
3564
  return;
3224
3565
  }
3225
3566
 
3567
+ const currentSnapshot = readWebConfigSnapshot(state.webConfigPath);
3568
+ let finalRaw = raw;
3226
3569
  let parsed = null;
3227
3570
  try {
3228
- parsed = parseAndValidateConfigRaw(raw);
3571
+ finalRaw = restoreWebConfigSecrets(raw, currentSnapshot);
3572
+ parsed = parseAndValidateConfigRaw(finalRaw);
3229
3573
  } catch (e) {
3230
3574
  sendJson(res, 400, { error: '配置格式错误', detail: e.message || '解析失败' });
3231
3575
  return;
@@ -3233,7 +3577,7 @@ async function handleWebApi(req, res, pathname, ctx, state) {
3233
3577
 
3234
3578
  const savePath = path.resolve(state.webConfigPath);
3235
3579
  fs.mkdirSync(path.dirname(savePath), { recursive: true });
3236
- fs.writeFileSync(savePath, raw, 'utf-8');
3580
+ fs.writeFileSync(savePath, finalRaw, 'utf-8');
3237
3581
 
3238
3582
  sendJson(res, 200, {
3239
3583
  saved: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "5.8.3",
3
+ "version": "5.8.5",
4
4
  "imageVersion": "1.9.0-common",
5
5
  "playwrightCliVersion": "0.1.1",
6
6
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",
@@ -74,7 +74,7 @@
74
74
  "glob": "^13.0.6",
75
75
  "minimatch": "^10.2.2",
76
76
  "test-exclude": "^8.0.0",
77
- "vite": "^6.4.1"
77
+ "vite": "^6.4.2"
78
78
  },
79
79
  "jest": {
80
80
  "testMatch": [