agim-cli 1.1.7 → 1.1.8
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/CHANGELOG.md +62 -0
- package/dist/cli.js +29 -0
- package/dist/cli.js.map +1 -1
- package/dist/core/tunnel.d.ts +23 -0
- package/dist/core/tunnel.d.ts.map +1 -0
- package/dist/core/tunnel.js +151 -0
- package/dist/core/tunnel.js.map +1 -0
- package/dist/core/viewer-config.d.ts +17 -0
- package/dist/core/viewer-config.d.ts.map +1 -1
- package/dist/core/viewer-config.js +24 -2
- package/dist/core/viewer-config.js.map +1 -1
- package/dist/web/public/settings.html +77 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +17 -0
- package/dist/web/server.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"viewer-config.js","sourceRoot":"","sources":["../../src/core/viewer-config.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,4EAA4E;AAC5E,0EAA0E;AAC1E,0EAA0E;AAE1E,OAAO,EAAE,kBAAkB,EAAyB,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"viewer-config.js","sourceRoot":"","sources":["../../src/core/viewer-config.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,4EAA4E;AAC5E,0EAA0E;AAC1E,0EAA0E;AAE1E,OAAO,EAAE,kBAAkB,EAAyB,MAAM,oBAAoB,CAAA;AAC9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAyBjD,SAAS,gBAAgB,CAAC,GAAuB,EAAE,QAAgB;IACjE,IAAI,CAAC,GAAG;QAAE,OAAO,QAAQ,CAAA;IACzB,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;IAC3B,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;AACnD,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;IACzE,MAAM,OAAO,GAAG,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,MAAM,IAAI,UAAU,KAAK,KAAK,CAAA;IACnF,MAAM,aAAa,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,4BAA4B,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;IACzF,MAAM,aAAa,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAA;IACnF,MAAM,UAAU,GAAqB,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAA;IAChF,OAAO;QACL,OAAO;QACP,aAAa;QACb,UAAU;QACV,UAAU,EAAE;YACV,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,KAAK,CAAC;YACjF,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,KAAK,CAAC;YACjF,SAAS,EAAE,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,kBAAkB,CAAC,SAAS,CAAC;SAC/F;KACF,CAAA;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,yBAAyB,CAAC,QAAiB;IACzD,MAAM,GAAG,GAAG,eAAe,EAAE,CAAA;IAC7B,IAAI,GAAG,CAAC,aAAa;QAAE,OAAO,GAAG,CAAC,aAAa,CAAA;IAC/C,IAAI,GAAG,CAAC,UAAU,KAAK,OAAO,EAAE,CAAC;QAC/B,MAAM,EAAE,GAAG,mBAAmB,EAAE,CAAA;QAChC,IAAI,EAAE;YAAE,OAAO,EAAE,CAAA;IACnB,CAAC;IACD,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,EAAU,EAAE,eAAwB;IAChE,MAAM,IAAI,GAAG,yBAAyB,CAAC,eAAe,CAAC,CAAA;IACvD,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IACtB,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,CAAA;AAC7C,CAAC"}
|
|
@@ -168,6 +168,16 @@
|
|
|
168
168
|
viewerSaved: 'Saved — change takes effect on the next reply.',
|
|
169
169
|
viewerSaveFailed: 'Failed to save viewer settings',
|
|
170
170
|
viewerMissingUrl: '⚠️ Public URL is empty — Agim will fall back to inline text for long replies (no link can be built).',
|
|
171
|
+
viewerTunnelMode: 'Auto-tunnel (cloudflared)',
|
|
172
|
+
viewerTunnelHint: 'For users without a public domain. Agim launches a cloudflared quick tunnel on startup and auto-detects a temporary `*.trycloudflare.com` URL. Requires the `cloudflared` binary to be installed (`brew install cloudflared` / `apt install cloudflared`). URL changes per restart — old paste links die. Leave off when you have a static reverse proxy.',
|
|
173
|
+
viewerTunnelOff: 'Off (use Public URL above)',
|
|
174
|
+
viewerTunnelQuick: 'Quick tunnel (temporary URL, no domain needed)',
|
|
175
|
+
viewerTunnelStatus: 'Tunnel status',
|
|
176
|
+
viewerTunnelRunning: 'Running',
|
|
177
|
+
viewerTunnelNotRunning: 'Not running',
|
|
178
|
+
viewerTunnelBinaryMissing: 'cloudflared not installed',
|
|
179
|
+
viewerTunnelCurrentUrl: 'Current URL',
|
|
180
|
+
viewerTunnelRefresh: 'Refresh',
|
|
171
181
|
},
|
|
172
182
|
zh: {
|
|
173
183
|
title: 'Agim — 设置',
|
|
@@ -322,6 +332,16 @@
|
|
|
322
332
|
viewerSaved: '已保存 — 下一条回复生效。',
|
|
323
333
|
viewerSaveFailed: '保存 Viewer 配置失败',
|
|
324
334
|
viewerMissingUrl: '⚠️ 公网域名为空 — 长回复将退回到内联文本(无法构造跳转链接)。',
|
|
335
|
+
viewerTunnelMode: '自动 tunnel(cloudflared)',
|
|
336
|
+
viewerTunnelHint: '给没有公网域名的用户。Agim 启动时自动拉起 cloudflared quick tunnel,拿一个临时 `*.trycloudflare.com` URL。需要先装 `cloudflared`(`brew install cloudflared` / `apt install cloudflared`)。**重启后 URL 会变,旧 paste 链接打不开**。有反代域名的话保持关闭即可。',
|
|
337
|
+
viewerTunnelOff: '关闭(用上面的公网域名)',
|
|
338
|
+
viewerTunnelQuick: 'Quick tunnel(临时 URL,无需域名)',
|
|
339
|
+
viewerTunnelStatus: 'Tunnel 状态',
|
|
340
|
+
viewerTunnelRunning: '运行中',
|
|
341
|
+
viewerTunnelNotRunning: '未运行',
|
|
342
|
+
viewerTunnelBinaryMissing: '未安装 cloudflared',
|
|
343
|
+
viewerTunnelCurrentUrl: '当前 URL',
|
|
344
|
+
viewerTunnelRefresh: '刷新',
|
|
325
345
|
},
|
|
326
346
|
};
|
|
327
347
|
function t(key) { return T[window.__lang][key] || T.en[key] || key; }
|
|
@@ -1419,6 +1439,18 @@
|
|
|
1419
1439
|
<input type="text" id="viewerPublicUrl" placeholder="https://agim.example.com" style="max-width:480px" />
|
|
1420
1440
|
<p class="muted" style="margin-top:4px;font-size:12px">${t('viewerPublicUrlHint')}</p>
|
|
1421
1441
|
|
|
1442
|
+
<label style="margin-top:12px;display:block">${t('viewerTunnelMode')}</label>
|
|
1443
|
+
<select id="viewerTunnelMode" style="max-width:480px">
|
|
1444
|
+
<option value="off">${t('viewerTunnelOff')}</option>
|
|
1445
|
+
<option value="quick">${t('viewerTunnelQuick')}</option>
|
|
1446
|
+
</select>
|
|
1447
|
+
<p class="muted" style="margin-top:4px;font-size:12px">${t('viewerTunnelHint')}</p>
|
|
1448
|
+
<div id="viewerTunnelStatusBox" style="margin-top:6px;padding:8px 10px;border:1px solid var(--border, #d0d7de);border-radius:6px;font-size:12px;display:none">
|
|
1449
|
+
<div><strong>${t('viewerTunnelStatus')}:</strong> <span id="viewerTunnelState">—</span></div>
|
|
1450
|
+
<div style="margin-top:4px"><strong>${t('viewerTunnelCurrentUrl')}:</strong> <code id="viewerTunnelCurrentUrl" style="word-break:break-all">—</code></div>
|
|
1451
|
+
<button type="button" class="btn" id="viewerTunnelRefreshBtn" style="margin-top:6px;padding:4px 10px;font-size:12px">${t('viewerTunnelRefresh')}</button>
|
|
1452
|
+
</div>
|
|
1453
|
+
|
|
1422
1454
|
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-top:12px;max-width:720px">
|
|
1423
1455
|
<div>
|
|
1424
1456
|
<label>${t('viewerChars')}</label>
|
|
@@ -1462,6 +1494,32 @@
|
|
|
1462
1494
|
`;
|
|
1463
1495
|
}
|
|
1464
1496
|
|
|
1497
|
+
async function refreshViewerTunnelStatus() {
|
|
1498
|
+
const stateEl = document.getElementById('viewerTunnelState');
|
|
1499
|
+
const urlEl = document.getElementById('viewerTunnelCurrentUrl');
|
|
1500
|
+
if (!stateEl || !urlEl) return;
|
|
1501
|
+
stateEl.textContent = '…';
|
|
1502
|
+
urlEl.textContent = '—';
|
|
1503
|
+
try {
|
|
1504
|
+
const data = await authFetch('/api/viewer/tunnel').then(r => r.json());
|
|
1505
|
+
const tun = data && data.tunnel ? data.tunnel : {};
|
|
1506
|
+
if (!tun.binaryFound) {
|
|
1507
|
+
stateEl.textContent = t('viewerTunnelBinaryMissing');
|
|
1508
|
+
stateEl.style.color = '#c0392b';
|
|
1509
|
+
} else if (tun.running) {
|
|
1510
|
+
stateEl.textContent = '✓ ' + t('viewerTunnelRunning');
|
|
1511
|
+
stateEl.style.color = '#16a34a';
|
|
1512
|
+
} else {
|
|
1513
|
+
stateEl.textContent = t('viewerTunnelNotRunning');
|
|
1514
|
+
stateEl.style.color = '';
|
|
1515
|
+
}
|
|
1516
|
+
urlEl.textContent = tun.url || (data && data.effectivePublicUrl) || '—';
|
|
1517
|
+
} catch (err) {
|
|
1518
|
+
stateEl.textContent = 'error';
|
|
1519
|
+
urlEl.textContent = (err && err.message) || String(err);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1465
1523
|
async function loadEnvSection(reveal) {
|
|
1466
1524
|
try {
|
|
1467
1525
|
const url = reveal ? '/api/env?reveal=1' : '/api/env';
|
|
@@ -1490,6 +1548,16 @@
|
|
|
1490
1548
|
set('viewerLines', 'IMHUB_VIEWER_LINES');
|
|
1491
1549
|
set('viewerCodeLines', 'IMHUB_VIEWER_CODE_LINES');
|
|
1492
1550
|
set('viewerMaxPastes', 'IMHUB_VIEWER_MAX_PASTES');
|
|
1551
|
+
const vTunnelMode = document.getElementById('viewerTunnelMode');
|
|
1552
|
+
if (vTunnelMode) {
|
|
1553
|
+
vTunnelMode.value = (env['IMHUB_VIEWER_TUNNEL_MODE'] || 'off').toLowerCase() === 'quick' ? 'quick' : 'off';
|
|
1554
|
+
}
|
|
1555
|
+
// Show tunnel status panel only when tunnel mode = quick.
|
|
1556
|
+
const tunnelBox = document.getElementById('viewerTunnelStatusBox');
|
|
1557
|
+
if (tunnelBox) {
|
|
1558
|
+
tunnelBox.style.display = (vTunnelMode && vTunnelMode.value === 'quick') ? 'block' : 'none';
|
|
1559
|
+
if (vTunnelMode && vTunnelMode.value === 'quick') void refreshViewerTunnelStatus();
|
|
1560
|
+
}
|
|
1493
1561
|
const viewerStatus = document.getElementById('viewerStatus');
|
|
1494
1562
|
if (viewerStatus) {
|
|
1495
1563
|
if (!env['IMHUB_VIEWER_PUBLIC_BASE_URL']) {
|
|
@@ -1801,6 +1869,13 @@
|
|
|
1801
1869
|
});
|
|
1802
1870
|
document.getElementById('revealBaidu')?.addEventListener('click', () => loadEnvSection(true));
|
|
1803
1871
|
|
|
1872
|
+
// Tunnel mode dropdown — show/hide status panel on change.
|
|
1873
|
+
document.getElementById('viewerTunnelMode')?.addEventListener('change', (e) => {
|
|
1874
|
+
const box = document.getElementById('viewerTunnelStatusBox');
|
|
1875
|
+
if (box) box.style.display = e.target.value === 'quick' ? 'block' : 'none';
|
|
1876
|
+
});
|
|
1877
|
+
document.getElementById('viewerTunnelRefreshBtn')?.addEventListener('click', () => refreshViewerTunnelStatus());
|
|
1878
|
+
|
|
1804
1879
|
// Viewer card — save toggle + URL + thresholds in one round-trip.
|
|
1805
1880
|
document.getElementById('saveViewer')?.addEventListener('click', async () => {
|
|
1806
1881
|
const enabled = document.getElementById('viewerEnabled')?.checked ? '1' : '0';
|
|
@@ -1809,6 +1884,7 @@
|
|
|
1809
1884
|
const lines = (document.getElementById('viewerLines')?.value || '').trim();
|
|
1810
1885
|
const codeLines = (document.getElementById('viewerCodeLines')?.value || '').trim();
|
|
1811
1886
|
const maxPastes = (document.getElementById('viewerMaxPastes')?.value || '').trim();
|
|
1887
|
+
const tunnelMode = document.getElementById('viewerTunnelMode')?.value || 'off';
|
|
1812
1888
|
const status = document.getElementById('viewerStatus');
|
|
1813
1889
|
try {
|
|
1814
1890
|
await authFetch('/api/env', {
|
|
@@ -1822,6 +1898,7 @@
|
|
|
1822
1898
|
IMHUB_VIEWER_LINES: lines || null,
|
|
1823
1899
|
IMHUB_VIEWER_CODE_LINES: codeLines || null,
|
|
1824
1900
|
IMHUB_VIEWER_MAX_PASTES: maxPastes || null,
|
|
1901
|
+
IMHUB_VIEWER_TUNNEL_MODE: tunnelMode || 'off',
|
|
1825
1902
|
},
|
|
1826
1903
|
}),
|
|
1827
1904
|
});
|
package/dist/web/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAsDA,wBAAgB,iBAAiB,IAAI,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAOrE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAsDA,wBAAgB,iBAAiB,IAAI,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAOrE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAksB/C"}
|
package/dist/web/server.js
CHANGED
|
@@ -412,6 +412,9 @@ export async function startWebServer(options) {
|
|
|
412
412
|
if (url.pathname === '/api/viewer' && req.method === 'GET') {
|
|
413
413
|
return handleViewerList(req, res, url);
|
|
414
414
|
}
|
|
415
|
+
if (url.pathname === '/api/viewer/tunnel' && req.method === 'GET') {
|
|
416
|
+
return handleViewerTunnelStatus(req, res);
|
|
417
|
+
}
|
|
415
418
|
const viewerIdMatch = url.pathname.match(/^\/api\/viewer\/([0-9a-f-]{8,})$/i);
|
|
416
419
|
if (viewerIdMatch && req.method === 'GET') {
|
|
417
420
|
return handleViewerGet(req, res, viewerIdMatch[1]);
|
|
@@ -1640,6 +1643,7 @@ const ENV_EDITABLE_KEYS = [
|
|
|
1640
1643
|
'IMHUB_VIEWER_LINES',
|
|
1641
1644
|
'IMHUB_VIEWER_CODE_LINES',
|
|
1642
1645
|
'IMHUB_VIEWER_MAX_PASTES',
|
|
1646
|
+
'IMHUB_VIEWER_TUNNEL_MODE',
|
|
1643
1647
|
];
|
|
1644
1648
|
const SECRET_KEYS = new Set(['IMHUB_SMTP_PASS', 'IMHUB_BAIDU_MAP_AK']);
|
|
1645
1649
|
function maskSecret(v) {
|
|
@@ -2786,6 +2790,19 @@ async function handleViewerDelete(_req, res, id) {
|
|
|
2786
2790
|
}
|
|
2787
2791
|
sendJson(res, 200, { ok: true });
|
|
2788
2792
|
}
|
|
2793
|
+
async function handleViewerTunnelStatus(_req, res) {
|
|
2794
|
+
const { getTunnelStatus } = await import('../core/tunnel.js');
|
|
2795
|
+
const { getViewerConfig, getEffectivePublicBaseUrl } = await import('../core/viewer-config.js');
|
|
2796
|
+
const cfg = getViewerConfig();
|
|
2797
|
+
const tunnel = getTunnelStatus();
|
|
2798
|
+
sendJson(res, 200, {
|
|
2799
|
+
enabled: cfg.enabled,
|
|
2800
|
+
tunnelMode: cfg.tunnelMode,
|
|
2801
|
+
staticPublicUrl: cfg.publicBaseUrl,
|
|
2802
|
+
effectivePublicUrl: getEffectivePublicBaseUrl(),
|
|
2803
|
+
tunnel,
|
|
2804
|
+
});
|
|
2805
|
+
}
|
|
2789
2806
|
// ============================================
|
|
2790
2807
|
// WebSocket chat handlers
|
|
2791
2808
|
// ============================================
|