terminal-bridge-setup 3.0.0 → 3.1.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.
@@ -135,6 +135,11 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
135
135
  });
136
136
  return true; // 异步
137
137
  }
138
+ if (msg.type === "CSV_CLEAR") {
139
+ chrome.storage.session.remove(CSV_STORAGE_KEY).catch(() => {});
140
+ sendResponse({ ok: true });
141
+ return false;
142
+ }
138
143
  if (msg.type === "CSV_DOWNLOAD") {
139
144
  getCsvExports().then(list => {
140
145
  const record = list.find(e => e.id === msg.id);
@@ -688,15 +693,27 @@ function resultJsonToCsv(jsonText) {
688
693
  return "\uFEFF" + [headers.join(","), ...rows].join("\r\n") + "\r\n";
689
694
  }
690
695
 
696
+ // 文件名:表名_库_数据源_日期。表名从 SQL 提取(from/into/update 后的词),
697
+ // 库和数据源从目标 tab 的 meta(tapTabMeta)取。
698
+ function buildCsvFileName(sql, tabId) {
699
+ const sqlText = sql || "";
700
+ const tableMatch = sqlText.match(/\b(?:from|into|update|join)\s+[`"]?(\w+)[`"]?/i);
701
+ const table = (tableMatch ? tableMatch[1] : "query").slice(0, 40);
702
+ const meta = tapTabMeta.get(tabId) || {};
703
+ const database = (meta.database || "").replace(/[^\w.-]+/g, "") || "nodb";
704
+ const source = (meta.dataSource || "").split(" · ")[0].replace(/[^\w.-]+/g, "") || "nosrc";
705
+ const date = new Date().toISOString().slice(0, 10);
706
+ const time = new Date().toTimeString().slice(0, 5).replace(":", "");
707
+ return `${table}_${database}_${source}_${date}_${time}.csv`;
708
+ }
709
+
691
710
  async function handleYrExportCsv(frame) {
692
711
  const csv = resultJsonToCsv(frame.payload || "");
693
712
  if (!csv) {
694
713
  console.warn("[bg] yr-export-csv: 结果 JSON 解析失败或无表结构");
695
714
  return;
696
715
  }
697
- const stamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
698
- const sqlHead = (frame.sql || "query").slice(0, 30).replace(/[^\w-]+/g, "_");
699
- const name = `yearning-${sqlHead}-${stamp}.csv`;
716
+ const name = buildCsvFileName(frame.sql, frame.tabId);
700
717
  const dataUrl = "data:text/csv;charset=utf-8," + encodeURIComponent(csv);
701
718
  await addCsvExport({
702
719
  id: Date.now(),
@@ -200,10 +200,14 @@
200
200
  <div class="hint" id="wsTapStatus">未监听 Yearning 页面</div>
201
201
  <div id="yearningTabList" style="margin-top:6px;"></div>
202
202
  <div id="csvSection" style="margin-top:8px;display:none;">
203
- <div class="section-title" style="margin-bottom:4px;">CSV 导出记录</div>
203
+ <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px;">
204
+ <span class="section-title" style="margin:0;">CSV 导出记录</span>
205
+ <button class="copy-btn" id="btnCsvClear" style="background:#e53935;">清空</button>
206
+ </div>
204
207
  <div id="csvList"></div>
205
- <div class="hint">点击记录可重新下载(仅保留最近 20 条,插件刷新后清空)</div>
208
+ <div class="hint">点击记录下载 · 表名_库_数据源_日期.csv</div>
206
209
  </div>
210
+ <button class="scan" id="btnCopyPrompt" style="margin-top:8px;background:#7c4dff;">📋 复制 Agent 使用 Prompt</button>
207
211
  </div>
208
212
 
209
213
  <div class="section">
@@ -144,8 +144,10 @@ const csvList = document.getElementById('csvList');
144
144
  function refreshCsvList() {
145
145
  chrome.runtime.sendMessage({ type: 'CSV_LIST' }, (res) => {
146
146
  if (chrome.runtime.lastError || !res || !res.ok) return;
147
- const exports = res.exports || [];
148
- csvSection.style.display = exports.length ? 'block' : 'none';
147
+ const all = res.exports || [];
148
+ csvSection.style.display = all.length ? 'block' : 'none';
149
+ // 只显示最近 5 条,popup 不超长(存储仍保留 20 条)
150
+ const exports = all.slice(0, 5);
149
151
  csvList.innerHTML = exports.map(e => {
150
152
  const time = new Date(e.time).toLocaleTimeString();
151
153
  return `<div class="csv-item" data-id="${e.id}" title="${escapeHtml(e.sql || e.name)}">
@@ -166,6 +168,41 @@ function refreshCsvList() {
166
168
  }
167
169
  refreshCsvList();
168
170
 
171
+ // 清空 CSV 导出记录
172
+ document.getElementById('btnCsvClear').onclick = () => {
173
+ chrome.runtime.sendMessage({ type: 'CSV_CLEAR' }, () => refreshCsvList());
174
+ };
175
+
176
+ // 复制 Agent 使用 Prompt(粘给任意 AI 助手即可用桥接查 Yearning)
177
+ const AGENT_PROMPT = `你的机器上已装好 Terminal Bridge(Yearning SQL 桥接)。请按以下方式查询数据库:
178
+
179
+ ## 查询(Yearning)
180
+ cd ~/.terminal-bridge/proxy && node yr-example.mjs "SELECT ...;" [超时ms]
181
+
182
+ 返回 JSON:{ ok, output(结果 JSON: field 列定义 + data 数据行), error }。
183
+
184
+ ## 说明
185
+ - SQL 会自动注入到用户浏览器里选中的 Yearning 页面并点「查询」,结果自动返回
186
+ - 前置:用户已在 Yearning 页面选择数据库,且插件 popup 已「监听当前 Yearning 页」
187
+ - 多个 Yearning 页面时:用户在 popup 列表点选目标页面(显示 数据源 · 数据库)
188
+ - 报 database-not-selected = 页面未选数据库,请让用户先选
189
+ - 报 timeout = 查询超时或页面未监听,让用户确认 popup 状态
190
+ - 只读查询即可;每次查询的结果会自动出现在插件 popup 的「CSV 导出记录」里供用户下载
191
+
192
+ ## 终端命令(JumpServer/Arthas,同一代理)
193
+ cd ~/.terminal-bridge/proxy && node client-example.mjs "linux 命令" [超时ms]
194
+ 多层引号命令加 --b64 参数下发。`;
195
+
196
+ document.getElementById('btnCopyPrompt').onclick = () => {
197
+ navigator.clipboard.writeText(AGENT_PROMPT).then(() => {
198
+ const btn = document.getElementById('btnCopyPrompt');
199
+ btn.textContent = '✓ 已复制,粘贴给 Agent 即可';
200
+ setTimeout(() => { btn.textContent = '📋 复制 Agent 使用 Prompt'; }, 2000);
201
+ }).catch(() => {
202
+ console.warn('复制失败');
203
+ });
204
+ };
205
+
169
206
  // ============== 代理控制 ==============
170
207
  const proxyDot = document.getElementById('proxyDot');
171
208
  const proxyText = document.getElementById('proxyStatusText');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminal-bridge-setup",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "一次性安装器:释放终端桥接(JumpServer / Arthas)的本地代理 + Chrome 插件,并注册 native messaging host。让 Agent 能通过浏览器 xterm 终端执行命令并拿回输出。",
5
5
  "license": "MIT",
6
6
  "author": "encorearon",