nx-kv 0.6.0 → 0.7.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/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  本文件记录对外可见的变更。格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。
4
4
 
5
+ ## [0.7.0] - 2026-10-09
6
+
7
+ ### Added
8
+
9
+ - **支持删除整个主题与批量删除**:
10
+ - 「实时任务」页主题卡片右下角新增「删除主题」,一次删除该主题及其全部任务;
11
+ - 卡片左上角可勾选(总览标题处支持「全选」),顶部「批量删除 (N)」一键删除多个主题。
12
+ - 新增 CLI/HTTP:
13
+ - `nx-kv sessiontask topic remove --cwd "<目录>"`(或 `--key`)/ `DELETE /api/sessiontasks/topic`;
14
+ - `nx-kv sessiontask topic remove-many --keys "<k,k>"`(也可 `--cwds` 或 `--all`)
15
+ / `POST /api/sessiontasks/topics/remove`。
16
+ - 安全保护:等待中的主题默认不删(避免阻塞的 agent 干等到超时),界面二次确认、
17
+ 命令加 `--force` 才连等待中一起删;批量返回 `{count,removed,skipped}`,skipped
18
+ 列出被跳过的「等待中 / 不存在」主题。
19
+ - 新增删除/批量删除单测与冒烟断言。
20
+
5
21
  ## [0.6.0] - 2026-10-08
6
22
 
7
23
  ### Added
package/README.md CHANGED
@@ -81,8 +81,9 @@ nx-kv sessiontask wait --summary "<当前现状总结>" --json
81
81
  新等待点。弹窗/面板顶部直接显示现状,用户不读对话也能快速了解、点选等待点
82
82
  **分别回填(分散答复)**,也可提前排队;等待点非空时 wait 立即取走、不开窗。
83
83
  纯本机协调、存本地文件,**不需登录后端**。其余命令:
84
- `sessiontask list / add / get / update / remove`(均按 `--summary` 定位),
85
- 面板上对应「实时任务」页。
84
+ `sessiontask list / add / get / update / remove`,以及主题级
85
+ `sessiontask topic remove`(删整个主题)与 `sessiontask topic remove-many`
86
+ (批量删除,`--keys/--cwds/--all`;等待中需 `--force`),面板上对应「实时任务」页。
86
87
 
87
88
  配套 skill:
88
89
 
@@ -77,9 +77,15 @@ nx-kv sessiontask list # 等待点总览
77
77
  nx-kv sessiontask add "<回填内容>" --summary "<现状>" # 针对现状排队/投递
78
78
  nx-kv sessiontask get --id <n> --summary "<现状>" # 查看单条
79
79
  nx-kv sessiontask update --id <n> --text "<新内容>" --summary "<现状>"
80
- nx-kv sessiontask remove --id <n> --summary "<现状>" # 删除
80
+ nx-kv sessiontask remove --id <n> --summary "<现状>" # 删除单条任务
81
+ nx-kv sessiontask topic remove --cwd "<工作目录>" # 删除整个主题(等待中需 --force)
82
+ nx-kv sessiontask topic remove-many --keys "<key,key>" # 批量删除主题(或 --cwds "<dir,dir>",或 --all)
81
83
  ```
82
84
 
85
+ > 删除主题:面板「主题总览」每张卡有「删除主题」,顶部勾选后可「批量删除」。
86
+ > 等待中的主题默认不删(避免让阻塞的 agent 干等),确认要删会二次提示并以
87
+ > `--force` 强删、agent 随后超时收尾。
88
+
83
89
  常驻面板(`nx-kv serve`)的「实时任务」页就是等待点总览,可直接点选回填;
84
90
  wait 同样能取走(跨进程由轮询兜底)。
85
91
 
@@ -103,7 +103,23 @@ wait 必带一句当前现状总结,它被归一化(折叠首尾/连续空
103
103
  各现状下,而不是挤在一个队列。
104
104
  - agent 用对应现状调 wait 时只取该等待点的任务,互不串。
105
105
 
106
- ## 七、常见错误与避坑
106
+ ## 七、删除主题与批量删除
107
+
108
+ 主题(工作目录)可整体删除,不必逐条删任务:
109
+
110
+ - **单个删除**:
111
+ - 面板:主题卡片右下角「删除主题」;
112
+ - CLI:`nx-kv sessiontask topic remove --cwd "<工作目录>"`(或 `--key <key>`)。
113
+ - **批量删除**:
114
+ - 面板:勾选卡片左上角(或总览标题处「全选」)→「批量删除 (N)」;
115
+ - CLI:`nx-kv sessiontask topic remove-many --keys "<key,key>"`,也可
116
+ `--cwds "<dir,dir>"`,或 `--all` 清空。
117
+ - **安全保护**:等待中的主题默认不删(删了阻塞的 agent 只能等到超时);界面会
118
+ 二次确认,命令需加 `--force` 才连等待中一起删,agent 随后超时收尾。
119
+ - **返回**:单个 `{status:"removed",topic}`;批量 `{count,removed,skipped}`,
120
+ skipped 列出被跳过的「等待中 / 不存在」主题。
121
+
122
+ ## 八、常见错误与避坑
107
123
 
108
124
  | 错误做法 | 后果 | 正确做法 |
109
125
  | --- | --- | --- |
@@ -117,7 +133,7 @@ wait 必带一句当前现状总结,它被归一化(折叠首尾/连续空
117
133
  | 同时开多个 wait(同一等待点) | 只有一个收到回填,另一个等到超时 | 同一等待点同一时刻只调一个 wait |
118
134
  | 没用 --json | 输出人读文本,宿主难以可靠解析 | agent 调用一律加 `--json` |
119
135
 
120
- ## 八、与常驻面板配合 / 排障
136
+ ## 九、与常驻面板配合 / 排障
121
137
 
122
138
  - 已 `nx-kv serve` 打开面板时,直接在「等待点总览」点选现状回填;正在阻塞的
123
139
  wait 通过轮询(约每 0.5 秒)取走,不依赖弹窗本身。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nx-kv",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "nx-kv — KV 清单管理中枢 + 对话内实时任务接力:serve 驱动 CLI 与 Web 面板,同源同构,附带可安装 skill 供 agent 领取任务与阻塞接收用户输入",
5
5
  "type": "module",
6
6
  "bin": {
@@ -60,6 +60,21 @@ function renderWait(r) {
60
60
  );
61
61
  }
62
62
 
63
+ // 批量删除的人读输出:删除清单 + 跳过清单(等待中/不存在)
64
+ function renderRemoveMany(d) {
65
+ const lines = [d.all ? `清空:删除 ${d.count} 个主题` : `已删除 ${d.count} 个主题`];
66
+ for (const r of d.removed) lines.push(` - ${r.name}${r.cwd ? ` ${r.cwd}` : ''}`);
67
+ if (d.skipped.length) {
68
+ lines.push(`跳过 ${d.skipped.length} 个:`);
69
+ for (const s of d.skipped) {
70
+ const why = s.reason === 'waiting' ? '等待中' : '不存在';
71
+ lines.push(` - [${why}] ${s.name || s.key}`);
72
+ }
73
+ }
74
+ if (!d.removed.length && !d.skipped.length) lines.push('(没有匹配的主题)');
75
+ return lines.join('\n');
76
+ }
77
+
63
78
  export default {
64
79
  id: 'sessiontask',
65
80
  title: '实时任务(= Web「实时任务」页)',
@@ -121,6 +136,37 @@ export default {
121
136
  render: (d) => `已删除主题「${d.name}」#${d.removed.id}: ${d.removed.text}`,
122
137
  },
123
138
 
139
+ // ─── 主题删除(单个 / 批量)──────────────────────────────
140
+ {
141
+ id: 'sessiontask.topic.remove',
142
+ cli: ['sessiontask', 'topic', 'remove'],
143
+ http: ['DELETE', '/api/sessiontasks/topic'],
144
+ summary: '删除整个主题(含全部任务;按 --cwd 或 --key,等待中需 --force)',
145
+ flags: {
146
+ cwd: { type: 'string', hint: '工作目录(与 --key 二选一)' },
147
+ key: { type: 'string', hint: '主题 key(与 --cwd 二选一)' },
148
+ force: { type: 'boolean', hint: '连等待中的主题也删(agent 将超时收尾)' },
149
+ },
150
+ run: (ctx) => service.removeTopic({ cwd: ctx.cwd, key: ctx.key, force: ctx.force }),
151
+ render: (d) =>
152
+ `已删除主题「${d.name}」(待领取/共 ${d.topic.pending}/${d.topic.total} 条任务一并删除)`,
153
+ },
154
+ {
155
+ id: 'sessiontask.topic.removeMany',
156
+ cli: ['sessiontask', 'topic', 'remove-many'],
157
+ http: ['POST', '/api/sessiontasks/topics/remove'],
158
+ summary: '批量删除主题(--keys/--cwds 逗号分隔,或 --all;等待中默认跳过,--force 连等待中删)',
159
+ flags: {
160
+ keys: { type: 'array', hint: '主题 key,逗号分隔' },
161
+ cwds: { type: 'array', hint: '工作目录,逗号分隔' },
162
+ all: { type: 'boolean', hint: '清空全部主题(等待中仍跳过,除非 --force)' },
163
+ force: { type: 'boolean', hint: '连等待中的主题一起删' },
164
+ },
165
+ run: (ctx) =>
166
+ service.removeTopics({ keys: ctx.keys, cwds: ctx.cwds, all: ctx.all, force: ctx.force }),
167
+ render: renderRemoveMany,
168
+ },
169
+
124
170
  {
125
171
  id: 'sessiontask.config',
126
172
  cli: ['sessiontask', 'config'],
@@ -274,6 +274,18 @@ async function setWaiting(key, cwd, summary, waiting, deadline) {
274
274
  await saveStore(store);
275
275
  }
276
276
 
277
+ // wait 结束复位:**仅当主题仍存在**时清等待态,绝不重建——主题可能已被 force 删除,
278
+ // 重建会让「删除主题」失效(曾在超时清理路径上把删掉的主题又建回来)。
279
+ async function clearWaiting(key) {
280
+ const store = await loadStore();
281
+ const t = store.topics[key];
282
+ if (!t) return;
283
+ t.waiting = false;
284
+ t.waitingUntil = '';
285
+ t.updatedAt = stampNow();
286
+ await saveStore(store);
287
+ }
288
+
277
289
  // ─── CRUD ───────────────────────────────────────────────────────────
278
290
 
279
291
  function settingsView(s) {
@@ -430,6 +442,76 @@ export async function removeTask({ cwd: rawCwd, id } = {}) {
430
442
  return { status: 'ok', key, cwd, name: t.name, removed: normalizeTask(removed) };
431
443
  }
432
444
 
445
+ // ─── 主题删除(单个 / 批量) ───────────────────────────────────────
446
+
447
+ // 定位主题 key:显式 --key 优先,否则按工作目录(目录名)推导
448
+ function resolveTopicKey({ key: rawKey, cwd: rawCwd }) {
449
+ if (rawKey && String(rawKey).trim()) return String(rawKey).trim();
450
+ return normalizeCwd(rawCwd).key;
451
+ }
452
+
453
+ // 删除整个主题(连同其全部任务)。默认拒绝删除「等待中」主题——那会让正阻塞的
454
+ // agent 只能等到超时;force=true 才强删(agent 随后超时收尾)。
455
+ export async function removeTopic({ cwd: rawCwd, key: rawKey, force = false } = {}) {
456
+ if (!rawKey && !rawCwd) {
457
+ throw badInput('需要 --cwd "<工作目录>" 或 --key "<主题key>"');
458
+ }
459
+ const key = resolveTopicKey({ key: rawKey, cwd: rawCwd });
460
+ const store = await loadStore();
461
+ const t = store.topics[key];
462
+ if (!t) {
463
+ const { notFound } = await import('../../core/errors.js');
464
+ throw notFound(`主题不存在(key ${key})`);
465
+ }
466
+ if (t.waiting && !force) {
467
+ throw badInput('主题正在等待中,不能删除(确认要删请加 --force,agent 将超时收尾)');
468
+ }
469
+ const view = topicView(t);
470
+ delete store.topics[key];
471
+ await saveStore(store);
472
+ return { status: 'removed', key, name: t.name, cwd: t.cwd, topic: view };
473
+ }
474
+
475
+ // 批量删除主题:keys / cwds(数组,可并用),或 all=true 清空全部。
476
+ // 默认把「等待中」「不存在」计入 skipped 而不删;force=true 连等待中一起删。
477
+ export async function removeTopics({ keys, cwds, all = false, force = false } = {}) {
478
+ const hasList = (a) => Array.isArray(a) && a.some((x) => String(x).trim());
479
+ if (!all && !hasList(keys) && !hasList(cwds)) {
480
+ throw badInput('需要 --keys "<key,key>" 或 --cwds "<目录,目录>",或 --all');
481
+ }
482
+
483
+ const store = await loadStore();
484
+ let targets;
485
+ if (all) {
486
+ targets = Object.keys(store.topics);
487
+ } else {
488
+ const list = [];
489
+ if (Array.isArray(keys)) list.push(...keys.map((x) => String(x).trim()).filter(Boolean));
490
+ if (Array.isArray(cwds)) {
491
+ for (const c of cwds) if (String(c).trim()) list.push(normalizeCwd(c).key);
492
+ }
493
+ targets = [...new Set(list)];
494
+ }
495
+
496
+ const removed = [];
497
+ const skipped = [];
498
+ for (const key of targets) {
499
+ const t = store.topics[key];
500
+ if (!t) {
501
+ skipped.push({ key, reason: 'not_found' });
502
+ continue;
503
+ }
504
+ if (t.waiting && !force) {
505
+ skipped.push({ key, name: t.name, cwd: t.cwd, reason: 'waiting' });
506
+ continue;
507
+ }
508
+ removed.push({ key, name: t.name, cwd: t.cwd });
509
+ delete store.topics[key];
510
+ }
511
+ if (removed.length) await saveStore(store);
512
+ return { status: 'ok', all: !!all, count: removed.length, removed, skipped };
513
+ }
514
+
433
515
  // 调整**待领取**任务的优先级:把 #id 移动到 #beforeId 之前或 #afterId 之后。
434
516
  // 领取顺序 = tasks 数组里 pending 子序列的先后(consumeFirstPending 取第一条 pending),
435
517
  // 故只在 pending 之间换位;已领取(历史)任务固定、不可移动。
@@ -562,7 +644,7 @@ export async function waitForTask({
562
644
  clearInterval(poll);
563
645
  clearTimeout(timer);
564
646
  waiters.delete(key);
565
- await setWaiting(key, cwd, '', false);
647
+ await clearWaiting(key);
566
648
  await stopServer(server);
567
649
  }
568
650
  return result;
@@ -41,6 +41,10 @@ export default function SessionTaskView() {
41
41
  const [over, setOver] = useState(null); // { id, pos:'top'|'bottom' }
42
42
  const [reordering, setReordering] = useState(false);
43
43
 
44
+ // 主题勾选(批量删除)
45
+ const [selected, setSelected] = useState(() => new Set());
46
+ const [deleting, setDeleting] = useState(false);
47
+
44
48
  const load = useCallback(async () => {
45
49
  try {
46
50
  setOverview(await api('/api/sessiontasks'));
@@ -106,8 +110,27 @@ export default function SessionTaskView() {
106
110
  setActiveKey(w.key);
107
111
  }, [topics, activeKey]);
108
112
 
113
+ // 主题被删除后,从勾选集合里剔除失效 key
114
+ useEffect(() => {
115
+ setSelected((s) => {
116
+ const valid = new Set([...s].filter((k) => topics.some((t) => t.key === k)));
117
+ return valid.size === s.size ? s : valid;
118
+ });
119
+ }, [topics]);
120
+
109
121
  const active = topics.find((t) => t.key === activeKey) || null;
110
122
 
123
+ const toggleSelect = (key) =>
124
+ setSelected((s) => {
125
+ const n = new Set(s);
126
+ if (n.has(key)) n.delete(key);
127
+ else n.add(key);
128
+ return n;
129
+ });
130
+ const allSelected = topics.length > 0 && selected.size === topics.length;
131
+ const toggleAll = () =>
132
+ setSelected(allSelected ? new Set() : new Set(topics.map((t) => t.key)));
133
+
111
134
  const submit = () =>
112
135
  guard(async () => {
113
136
  const body = text.trim();
@@ -143,6 +166,56 @@ export default function SessionTaskView() {
143
166
  await load();
144
167
  });
145
168
 
169
+ // 删除整个主题(卡片上的「删除主题」);等待中需二次确认并 force
170
+ const deleteTopic = (t) =>
171
+ guard(async () => {
172
+ const msg = t.waiting
173
+ ? `主题「${t.name}」正在等待中,删除后其 agent 将超时收尾,确认删除?`
174
+ : `删除主题「${t.name}」及其全部 ${t.total} 条任务?`;
175
+ if (!window.confirm(msg)) return;
176
+ setDeleting(true);
177
+ try {
178
+ await api('/api/sessiontasks/topic', {
179
+ method: 'DELETE',
180
+ body: t.waiting ? { key: t.key, force: true } : { key: t.key },
181
+ });
182
+ setSelected((s) => {
183
+ const n = new Set(s);
184
+ n.delete(t.key);
185
+ return n;
186
+ });
187
+ toast('已删除主题');
188
+ await load();
189
+ } finally {
190
+ setDeleting(false);
191
+ }
192
+ });
193
+
194
+ // 批量删除勾选主题;含等待中时 force 并提示
195
+ const batchDelete = () =>
196
+ guard(async () => {
197
+ if (!selected.size) return;
198
+ const sel = topics.filter((t) => selected.has(t.key));
199
+ const waitingCount = sel.filter((t) => t.waiting).length;
200
+ let msg = `确认删除选中的 ${sel.length} 个主题?`;
201
+ if (waitingCount) msg += `(其中 ${waitingCount} 个等待中,删除后其 agent 将超时收尾)`;
202
+ if (!window.confirm(msg)) return;
203
+ setDeleting(true);
204
+ try {
205
+ const r = await api('/api/sessiontasks/topics/remove', {
206
+ method: 'POST',
207
+ body: { keys: [...selected], force: waitingCount > 0 },
208
+ });
209
+ toast(
210
+ `已删除 ${r.count} 个主题` + (r.skipped.length ? `,跳过 ${r.skipped.length} 个` : '')
211
+ );
212
+ setSelected(new Set());
213
+ await load();
214
+ } finally {
215
+ setDeleting(false);
216
+ }
217
+ });
218
+
146
219
  const startEdit = (t) => {
147
220
  setEditingId(t.id);
148
221
  setEditText(t.text);
@@ -256,17 +329,34 @@ export default function SessionTaskView() {
256
329
  </div>
257
330
 
258
331
  <div className="colhead">
332
+ <input
333
+ type="checkbox"
334
+ checked={allSelected}
335
+ onChange={toggleAll}
336
+ disabled={!topics.length}
337
+ title="全选 / 取消全选"
338
+ style={{ margin: 0 }}
339
+ />
259
340
  <h3>主题总览(按工作目录)</h3>
260
341
  <span className="muted">{overview ? `${topics.length} 个 · 等待中 ${overview.waiting}` : ''}</span>
261
- <button
262
- className="btn small ghost"
263
- onClick={prune}
264
- disabled={pruning}
265
- style={{ marginLeft: 'auto' }}
266
- title="清理旧模型无目录主题与空主题(等待中一律保留)"
267
- >
268
- {pruning ? '清理中…' : '清理旧数据'}
269
- </button>
342
+ <span style={{ marginLeft: 'auto', display: 'flex', gap: 8 }}>
343
+ <button
344
+ className="btn small ghost"
345
+ onClick={batchDelete}
346
+ disabled={!selected.size || deleting}
347
+ title="删除勾选的主题(含其全部任务)"
348
+ >
349
+ {deleting ? '删除中…' : `批量删除${selected.size ? ` (${selected.size})` : ''}`}
350
+ </button>
351
+ <button
352
+ className="btn small ghost"
353
+ onClick={prune}
354
+ disabled={pruning}
355
+ title="清理旧模型无目录主题与空主题(等待中一律保留)"
356
+ >
357
+ {pruning ? '清理中…' : '清理旧数据'}
358
+ </button>
359
+ </span>
270
360
  </div>
271
361
 
272
362
  <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', marginBottom: 14 }}>
@@ -274,7 +364,7 @@ export default function SessionTaskView() {
274
364
  topics.map((t) => {
275
365
  const isActive = t.key === activeKey;
276
366
  return (
277
- <button
367
+ <div
278
368
  key={t.key}
279
369
  onClick={() => setActiveKey(t.key)}
280
370
  className="card"
@@ -283,21 +373,31 @@ export default function SessionTaskView() {
283
373
  width: 270,
284
374
  padding: '10px 12px',
285
375
  cursor: 'pointer',
286
- font: 'inherit',
287
- color: 'inherit',
288
376
  border: isActive ? '1px solid var(--ink,#14161a)' : '1px solid transparent',
289
377
  boxShadow: isActive
290
378
  ? '0 0 0 2px rgba(20,22,26,.12)'
291
379
  : '0 1px 2px rgba(20,22,26,.06), 0 6px 16px rgba(20,22,26,.05)',
292
380
  }}
293
381
  >
294
- <div style={{ fontWeight: 700, marginBottom: 2 }}>{t.name || '(未命名)'}</div>
295
- <div
296
- className="mono muted"
297
- title={t.cwd}
298
- style={{ fontSize: 11, wordBreak: 'break-all', marginBottom: 6, minHeight: 15 }}
299
- >
300
- {t.cwd || '(旧数据,无目录)'}
382
+ <div style={{ display: 'flex', gap: 8, alignItems: 'flex-start' }}>
383
+ <input
384
+ type="checkbox"
385
+ checked={selected.has(t.key)}
386
+ onChange={() => toggleSelect(t.key)}
387
+ onClick={(e) => e.stopPropagation()}
388
+ title="选择以批量删除"
389
+ style={{ margin: '3px 0 0' }}
390
+ />
391
+ <div style={{ flex: 1, minWidth: 0 }}>
392
+ <div style={{ fontWeight: 700, marginBottom: 2 }}>{t.name || '(未命名)'}</div>
393
+ <div
394
+ className="mono muted"
395
+ title={t.cwd}
396
+ style={{ fontSize: 11, wordBreak: 'break-all', marginBottom: 6, minHeight: 15 }}
397
+ >
398
+ {t.cwd || '(旧数据,无目录)'}
399
+ </div>
400
+ </div>
301
401
  </div>
302
402
  {t.summary && (
303
403
  <div className="muted" style={{ fontSize: 11, whiteSpace: 'pre-wrap', marginBottom: 6 }}>
@@ -314,7 +414,20 @@ export default function SessionTaskView() {
314
414
  待领取 {t.pending}/{t.total}
315
415
  </span>
316
416
  </div>
317
- </button>
417
+ <div
418
+ style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 8 }}
419
+ onClick={(e) => e.stopPropagation()}
420
+ >
421
+ <button
422
+ className="btn small ghost"
423
+ onClick={() => deleteTopic(t)}
424
+ disabled={deleting}
425
+ title="删除整个主题(含全部任务)"
426
+ >
427
+ 删除主题
428
+ </button>
429
+ </div>
430
+ </div>
318
431
  );
319
432
  })
320
433
  ) : (
@@ -1 +1 @@
1
- import{o as e,s as t,t as n}from"./index-DDDim1NE.js";var r=t();function i({module:t}){let{boot:i}=e(),a=(i?.commands||[]).filter(e=>e.module===t);return a.length?(0,r.jsxs)(`div`,{className:`cli-hint`,children:[(0,r.jsx)(`span`,{className:`cli-hint-label`,children:`CLI 等价(同构命令,加 --json 得机器可读输出):`}),a.map(e=>(0,r.jsx)(n,{className:`cli-cmd`,text:e.command,title:`点击复制:${e.usage}`,children:e.command},e.id))]}):null}export{i as t};
1
+ import{o as e,s as t,t as n}from"./index-BC4hXOgL.js";var r=t();function i({module:t}){let{boot:i}=e(),a=(i?.commands||[]).filter(e=>e.module===t);return a.length?(0,r.jsxs)(`div`,{className:`cli-hint`,children:[(0,r.jsx)(`span`,{className:`cli-hint-label`,children:`CLI 等价(同构命令,加 --json 得机器可读输出):`}),a.map(e=>(0,r.jsx)(n,{className:`cli-cmd`,text:e.command,title:`点击复制:${e.usage}`,children:e.command},e.id))]}):null}export{i as t};