@deepwhale-cn/dsh-docx-batch 0.1.0 → 0.1.3

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/README.md CHANGED
@@ -58,6 +58,7 @@ dsh-docx-batch to-md --input ./合同 --out ./md
58
58
  - ❌ 不处理脚注、尾注、批注、文本框、SmartArt、图片里的文字 —— 只覆盖正文段落、表格、页眉页脚。
59
59
  - ❌ `merge` 只合并正文和表格;页眉页脚、样式主题不合并,复杂排版(分栏、自动编号域)会退化为纯文本顺序。
60
60
  - ❌ `headers` 只**读**页眉页脚,不改;`replace` 能改页眉页脚里的文字。
61
+ - ❌ **转 Markdown 会丢东西**:批注 / 修订痕迹 / 复杂表格样式(合并单元格降级为普通表格)/ 图片位置与浮动排版 / 脚注尾注 —— 只保留标题、段落、列表与基础表格文字,适合进知识库,不适合当排版成品。
61
62
  - ❌ 不是格式转换器:不导出 PDF、不做 OCR、不做批量重命名。
62
63
  - ❌ 替换是**字面匹配**,大小写敏感,不支持正则;`--from` 是空字符串之类无意义输入不做特殊处理。
63
64
  - ✅ 全部在本机由 python-docx 处理,不联网、不上传文档。
@@ -90,3 +91,15 @@ dsh-docx-batch <action> --input <文件或目录> [--out <目录>] [--json]
90
91
  ## License
91
92
 
92
93
  MIT © 温州深鲸智能科技有限公司
94
+
95
+ ## 面板(0.1.1 起)
96
+
97
+ 装上后侧栏会多一个「Word 批量」图标,点开即可:
98
+
99
+ - 选操作:批量替换 / 提取正文 / 合并 / 页眉页脚 / 转 Markdown
100
+ - 填文件或目录、输出目录(留空 = `<输入目录>/_输出`)
101
+ - 批量替换时每行写一组「旧 → 新」
102
+ - 点「开始处理」,结果直接显示在面板里
103
+
104
+ 面板与 CLI 是同一套内核(`lib/docx_tool.py`),能力完全一致;所有处理都在本机完成,不上传文件。
105
+ 需要 `python3` + `python-docx`(面板里会给出提示)。
package/lib/api.js ADDED
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Word 批量处理 —— 宿主半边 HTTP 出口(面板用)
3
+ *
4
+ * 面板(lib/client.js)跟这里是同源的:浏览器 fetch `/dsh-docx-batch/api/*`,
5
+ * 由 DSH 宿主把请求交给这个 handler,真正干活的是随包的 bin/cli.mjs(Python 内核不变)。
6
+ *
7
+ * 安全约束:
8
+ * · 参数一律走数组传给子进程,不拼 shell 字符串;
9
+ * · 只允许白名单里的 action;
10
+ * · 超时 10 分钟,超时杀掉整棵子进程树。
11
+ */
12
+ import { spawn } from 'node:child_process';
13
+ import { dirname, join } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { existsSync } from 'node:fs';
16
+
17
+ const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
18
+ const CLI = join(PKG_ROOT, 'bin', 'cli.mjs');
19
+ const ROUTE = '/dsh-docx-batch';
20
+
21
+ /** 面板可做的操作(与 CLI 的 action 一一对应) */
22
+ const OPS = [
23
+ { key: 'replace', label: '批量替换文本', needs: ['pairs'], hint: '每行一组「旧文本 → 新文本」,可多行' },
24
+ { key: 'extract', label: '提取正文到 txt' },
25
+ { key: 'merge', label: '合并成一个 docx' },
26
+ { key: 'headers', label: '列出页眉页脚' },
27
+ { key: 'to-md', label: '转 Markdown' },
28
+ ];
29
+
30
+ const 上限 = 10 * 60 * 1000;
31
+
32
+ function 回(res, code, obj) {
33
+ const body = JSON.stringify(obj);
34
+ res.writeHead(code, { 'content-type': 'application/json; charset=utf-8', 'content-length': Buffer.byteLength(body) });
35
+ res.end(body);
36
+ }
37
+
38
+ async function 读体(req) {
39
+ const chunks = [];
40
+ for await (const c of req) chunks.push(c);
41
+ const text = Buffer.concat(chunks).toString('utf8');
42
+ if (!text) return {};
43
+ try {
44
+ return JSON.parse(text);
45
+ } catch {
46
+ return {};
47
+ }
48
+ }
49
+
50
+ /** 跑 CLI:返回 { ok, code, output } */
51
+ function 跑CLI(args) {
52
+ return new Promise((resolve) => {
53
+ const child = spawn(process.execPath, [CLI, ...args], {
54
+ // 与壳里其它子进程一致:用 Electron 当 Node(纯 node 环境下这个变量无副作用)
55
+ env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' },
56
+ windowsHide: true,
57
+ });
58
+ let out = '';
59
+ const 收集 = (d) => {
60
+ out += String(d);
61
+ if (out.length > 200_000) out = out.slice(-200_000); // 防止刷爆内存
62
+ };
63
+ child.stdout?.on('data', 收集);
64
+ child.stderr?.on('data', 收集);
65
+ const timer = setTimeout(() => {
66
+ try {
67
+ child.kill('SIGKILL');
68
+ } catch {
69
+ /* 已退出 */
70
+ }
71
+ 收集('\n[超时] 超过 10 分钟仍未完成,已终止。');
72
+ }, 上限);
73
+ child.on('error', (e) => {
74
+ clearTimeout(timer);
75
+ resolve({ ok: false, code: -1, output: `启动失败:${e instanceof Error ? e.message : String(e)}` });
76
+ });
77
+ child.on('close', (code) => {
78
+ clearTimeout(timer);
79
+ resolve({ ok: code === 0, code: code ?? -1, output: out.trim() });
80
+ });
81
+ });
82
+ }
83
+
84
+ /** 把面板参数翻译成 CLI 参数 */
85
+ function 组参数(b) {
86
+ const action = String(b.action || '');
87
+ if (!OPS.some((o) => o.key === action)) return { error: `未知操作:${action}` };
88
+ const input = String(b.input || '').trim();
89
+ if (!input) return { error: '请填写要处理的文件或目录路径' };
90
+ if (!existsSync(input)) return { error: `路径不存在:${input}` };
91
+ const args = [action, '--input', input];
92
+ const out = String(b.out || '').trim();
93
+ if (out) args.push('--out', out);
94
+ if (action === 'replace') {
95
+ const 行 = String(b.pairs || '')
96
+ .split('\n')
97
+ .map((l) => l.trim())
98
+ .filter(Boolean);
99
+ if (!行.length) return { error: '批量替换至少要一组「旧 → 新」' };
100
+ const 旧 = [];
101
+ const 新 = [];
102
+ for (const l of 行) {
103
+ const i = l.indexOf('→') >= 0 ? l.indexOf('→') : l.indexOf('->');
104
+ if (i < 0) return { error: `这一行缺少箭头(用「旧 → 新」):${l}` };
105
+ const sep = l[i] === '→' ? 1 : 2;
106
+ 旧.push(l.slice(0, i).trim());
107
+ 新.push(l.slice(i + sep).trim());
108
+ }
109
+ for (const v of 旧) args.push('--from', v);
110
+ for (const v of 新) args.push('--to', v);
111
+ }
112
+ return { args };
113
+ }
114
+
115
+ /**
116
+ * 挂到宿主 webServer 上。
117
+ * @param {any} ctx 插件上下文(需已注入 webServer)
118
+ */
119
+ export function registerDocxBatchApi(ctx) {
120
+ const handler = async (req, res) => {
121
+ try {
122
+ const u = new URL(req.url || '/', 'http://127.0.0.1');
123
+ const p = u.pathname;
124
+ if (p === `${ROUTE}/api/ops` && req.method === 'GET') {
125
+ return 回(res, 200, { ok: true, ops: OPS });
126
+ }
127
+ if (p === `${ROUTE}/api/run` && req.method === 'POST') {
128
+ const b = await 读体(req);
129
+ const { args, error } = 组参数(b);
130
+ if (error) return 回(res, 400, { ok: false, error });
131
+ const r = await 跑CLI(args);
132
+ return 回(res, 200, r);
133
+ }
134
+ return 回(res, 404, { ok: false, error: 'not found' });
135
+ } catch (e) {
136
+ return 回(res, 500, { ok: false, error: e instanceof Error ? e.message : String(e) });
137
+ }
138
+ };
139
+ ctx.webServer.register({ kind: 'prefix', path: ROUTE, handler });
140
+ }
package/lib/client.js ADDED
@@ -0,0 +1,192 @@
1
+ /**
2
+ * 深鲸文档批量处理 —— 客户端半边(统一面板)
3
+ *
4
+ * 一个入口管三种文档:Word / Excel / PDF(各自宿主 API 不同,面板里切页签)。
5
+ * 设计取舍(2026-10-28 用户反馈):不要为每种格式各放一个侧栏图标 ——
6
+ * 用户只需要知道「我要批量处理文档」,不需要先分辨格式再找入口。
7
+ *
8
+ * 槽位:sidebar.panellist(一个图标)+ main(keyed 面板)
9
+ * 数据:/dsh-docx-batch/api、/dsh-xlsx-batch/api、/dsh-pdf-kit/api(同源)
10
+ */
11
+ window.__ModuleLoader__.load({
12
+ id: '@deepwhale-cn/dsh-docx-batch',
13
+ factory: (require) => {
14
+ var module = { exports: {} };
15
+ var exports = module.exports;
16
+ const react = require('react');
17
+ const h = react.createElement;
18
+
19
+ const SLOT_ICON = 'sidebar.panellist';
20
+ const SLOT_MAIN = 'main';
21
+ const PANEL_ID = 'office-batch';
22
+
23
+ const FORMATS = [
24
+ { key: 'word', label: 'Word', api: '/dsh-docx-batch/api', tips: '需要 python3 + python-docx' },
25
+ { key: 'excel', label: 'Excel', api: '/dsh-xlsx-batch/api', tips: '需要 python3 + openpyxl' },
26
+ { key: 'pdf', label: 'PDF', api: '/dsh-pdf-kit/api', tips: '需要 python3 + pypdf' },
27
+ ];
28
+
29
+ async function call(api, path, options) {
30
+ const res = await fetch(`${api}${path}`, {
31
+ headers: { 'content-type': 'application/json' },
32
+ ...options,
33
+ body: options && options.body !== undefined ? JSON.stringify(options.body) : undefined,
34
+ });
35
+ const text = await res.text();
36
+ let data = null;
37
+ try { data = text ? JSON.parse(text) : null; } catch { data = { ok: false, error: text }; }
38
+ if (!res.ok && data && !data.error) data.error = `HTTP ${res.status}`;
39
+ return data;
40
+ }
41
+
42
+ const IcoDoc = ({ size = 18 } = {}) => h('svg', { width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round' }, [
43
+ h('path', { key: 'a', d: 'M4 4h10l6 6v10a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z' }),
44
+ h('path', { key: 'b', d: 'M14 4v6h6' }),
45
+ h('path', { key: 'c', d: 'M8 13h8M8 17h5' }),
46
+ ]);
47
+
48
+ const CSS = `
49
+ .dwhb-root { display:flex; flex-direction:column; gap:14px; padding:18px 22px 30px; height:100%; overflow:auto; }
50
+ .dwhb-head { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
51
+ .dwhb-title { font-size:16px; font-weight:600; }
52
+ .dwhb-sub { color: var(--dsw-alias-text-secondary, #8b98a8); font-size:12px; }
53
+ .dwhb-brand { margin-left:auto; display:inline-flex; align-items:center; gap:6px; font-size:11px; color:var(--dsw-alias-text-secondary,#8b98a8); border:1px solid var(--dsw-alias-border-l1, rgba(255,255,255,.12)); border-radius:999px; padding:2px 10px; }
54
+ .dwhb-brand b { color: var(--dsw-alias-brand-primary, #7fe3f0); font-weight:600; }
55
+ .dwhb-tabs { display:flex; gap:6px; }
56
+ .dwhb-tab { padding:6px 16px; border-radius:8px; font-size:13px; cursor:pointer; border:1px solid transparent; background:transparent; color:inherit; }
57
+ .dwhb-tab[data-on='true'] { background: var(--dsw-alias-bg-layer-1, rgba(255,255,255,.06)); border-color: var(--dsw-alias-border-l1, rgba(255,255,255,.14)); font-weight:600; }
58
+ .dwhb-card { border:1px solid var(--dsw-alias-border-l1, rgba(255,255,255,.08)); border-radius:12px; background: var(--dsw-alias-bg-layer-1, rgba(255,255,255,.03)); padding:14px; display:flex; flex-direction:column; gap:10px; }
59
+ .dwhb-row { display:flex; gap:8px; align-items:center; }
60
+ .dwhb-label { font-size:12px; color: var(--dsw-alias-text-secondary, #8b98a8); width:70px; flex:none; }
61
+ .dwhb-input, .dwhb-select, .dwhb-area { flex:1; min-width:0; padding:7px 9px; border-radius:8px; border:1px solid var(--dsw-alias-border-l1, rgba(255,255,255,.12)); background: var(--dsw-alias-bg-base, transparent); color:inherit; font-size:13px; font-family:inherit; }
62
+ .dwhb-area { min-height:70px; resize:vertical; font-family: ui-monospace, Menlo, monospace; }
63
+ .dwhb-btn { padding:7px 16px; border-radius:8px; border:1px solid var(--dsw-alias-border-l1, rgba(255,255,255,.14)); background: var(--dsw-alias-brand-primary, #2f6f9f); color:#fff; font-size:13px; cursor:pointer; }
64
+ .dwhb-btn[disabled] { opacity:.55; cursor:default; }
65
+ .dwhb-out { white-space:pre-wrap; word-break:break-word; font-family: ui-monospace, Menlo, monospace; font-size:12px; line-height:1.6; max-height:280px; overflow:auto; background: var(--dsw-alias-bg-layer-2, rgba(0,0,0,.18)); border-radius:8px; padding:10px; }
66
+ .dwhb-err { color: var(--dsw-alias-state-error-primary, #e0645a); }
67
+ .dwhb-ok { color: var(--dsw-alias-state-success-primary, #46b47a); }
68
+ .dwhb-tip { font-size:12px; color: var(--dsw-alias-text-secondary, #8b98a8); }
69
+ `;
70
+
71
+ function 面板() {
72
+ const [fmt, 设fmt] = react.useState('word');
73
+ const [ops, 设ops] = react.useState({});
74
+ const [action, 设action] = react.useState('');
75
+ const [input, 设input] = react.useState('');
76
+ const [out, 设out] = react.useState('');
77
+ const [pairs, 设pairs] = react.useState('');
78
+ const [text, 设text] = react.useState('');
79
+ const [mode, 设mode] = react.useState('page');
80
+ const [n, 设n] = react.useState('');
81
+ const [跑, 设跑] = react.useState(false);
82
+ const [结果, 设结果] = react.useState(null);
83
+
84
+ const 当前格式 = FORMATS.find((f) => f.key === fmt) || FORMATS[0];
85
+
86
+ react.useEffect(() => {
87
+ let 活 = true;
88
+ void (async () => {
89
+ const r = await call(当前格式.api, '/ops');
90
+ if (!活 || !r) return;
91
+ const list = Array.isArray(r.ops) ? r.ops : [];
92
+ 设ops((prev) => ({ ...prev, [fmt]: list }));
93
+ if (list.length) 设action(list[0].key);
94
+ })();
95
+ return () => { 活 = false; };
96
+ }, [fmt]);
97
+
98
+ const 列表 = ops[fmt] || [];
99
+ const 当前操作 = 列表.find((o) => o.key === action);
100
+ const 是替换 = fmt === 'word' && action === 'replace';
101
+ const 是拆分 = fmt === 'pdf' && action === 'split';
102
+ const 是水印 = fmt === 'pdf' && action === 'watermark';
103
+
104
+ const 执行 = async () => {
105
+ 设跑(true); 设结果(null);
106
+ try {
107
+ const r = await call(当前格式.api, '/run', { method: 'POST', body: { action, input, out, pairs, text, mode, n, dpi: n } });
108
+ 设结果(r);
109
+ } catch (e) {
110
+ 设结果({ ok: false, error: e instanceof Error ? e.message : String(e) });
111
+ } finally { 设跑(false); }
112
+ };
113
+
114
+ return h('div', { className: 'dwhb-root' }, [
115
+ h('style', { key: 'css' }, CSS),
116
+ h('div', { key: 'head', className: 'dwhb-head' }, [
117
+ h(IcoDoc, { key: 'i' }),
118
+ h('div', { key: 't' }, [
119
+ h('div', { key: 'a', className: 'dwhb-title' }, '深鲸文档批量处理'),
120
+ h('div', { key: 'b', className: 'dwhb-sub' }, 'Word / Excel / PDF 一次处理整目录 —— 替换、提取、合并、拆分、导出'),
121
+ ]),
122
+ h('span', { key: 'brand', className: 'dwhb-brand' }, ['powered by ', h('b', { key: 'b' }, 'dwh'), ' 深鲸']),
123
+ ]),
124
+ h('div', { key: 'tabs', className: 'dwhb-tabs' }, FORMATS.map((f) => h('button', {
125
+ key: f.key, className: 'dwhb-tab', 'data-on': f.key === fmt ? 'true' : 'false',
126
+ onClick: () => { 设fmt(f.key); 设结果(null); },
127
+ }, f.label))),
128
+
129
+ h('div', { key: 'form', className: 'dwhb-card' }, [
130
+ h('div', { key: 'op', className: 'dwhb-row' }, [
131
+ h('span', { key: 'l', className: 'dwhb-label' }, '操作'),
132
+ h('select', { key: 's', className: 'dwhb-select', value: action, onChange: (e) => 设action(e.target.value) },
133
+ 列表.map((o) => h('option', { key: o.key, value: o.key }, o.label))),
134
+ ]),
135
+ h('div', { key: 'in', className: 'dwhb-row' }, [
136
+ h('span', { key: 'l', className: 'dwhb-label' }, '文件/目录'),
137
+ h('input', { key: 'i', className: 'dwhb-input', placeholder: '把文件或文件夹路径粘进来(可从访达/资源管理器复制)', value: input, onChange: (e) => 设input(e.target.value) }),
138
+ ]),
139
+ h('div', { key: 'out', className: 'dwhb-row' }, [
140
+ h('span', { key: 'l', className: 'dwhb-label' }, '输出目录'),
141
+ h('input', { key: 'i', className: 'dwhb-input', placeholder: '留空 = <输入目录>/_输出', value: out, onChange: (e) => 设out(e.target.value) }),
142
+ ]),
143
+ 是替换 ? h('div', { key: 'pairs', className: 'dwhb-row', style: { alignItems: 'flex-start' } }, [
144
+ h('span', { key: 'l', className: 'dwhb-label', style: { paddingTop: 7 } }, '替换规则'),
145
+ h('textarea', { key: 't', className: 'dwhb-area', placeholder: '深鲸科技 → 深海鲸\n旧公司名 → 新公司名', value: pairs, onChange: (e) => 设pairs(e.target.value) }),
146
+ ]) : null,
147
+ 是拆分 ? h('div', { key: 'split', className: 'dwhb-row' }, [
148
+ h('span', { key: 'l', className: 'dwhb-label' }, '拆分方式'),
149
+ h('select', { key: 's', className: 'dwhb-select', value: mode, onChange: (e) => 设mode(e.target.value) }, [
150
+ h('option', { key: 'p', value: 'page' }, '按页拆(每页一个)'),
151
+ h('option', { key: 'e', value: 'every' }, '每 N 页一组'),
152
+ ]),
153
+ h('input', { key: 'n', className: 'dwhb-input', style: { maxWidth: 110 }, placeholder: 'N', value: n, onChange: (e) => 设n(e.target.value) }),
154
+ ]) : null,
155
+ 是水印 ? h('div', { key: 'wm', className: 'dwhb-row' }, [
156
+ h('span', { key: 'l', className: 'dwhb-label' }, '水印文字'),
157
+ h('input', { key: 't', className: 'dwhb-input', placeholder: '例如 机密 / 内部资料', value: text, onChange: (e) => 设text(e.target.value) }),
158
+ ]) : null,
159
+ 当前操作 && 当前操作.hint ? h('div', { key: 'hint', className: 'dwhb-tip' }, 当前操作.hint) : null,
160
+ h('div', { key: 'act', className: 'dwhb-row', style: { justifyContent: 'flex-end' } }, [
161
+ h('button', { key: 'b', className: 'dwhb-btn', disabled: 跑 || !action, onClick: 执行 }, 跑 ? '正在处理…' : '开始处理'),
162
+ ]),
163
+ ]),
164
+
165
+ 结果 ? h('div', { key: 'res', className: 'dwhb-card' }, [
166
+ h('div', { key: 's', className: 结果.ok ? 'dwhb-ok' : 'dwhb-err' }, 结果.ok ? '完成' : `失败${结果.error ? ':' + 结果.error : ''}`),
167
+ 结果.output ? h('div', { key: 'o', className: 'dwhb-out' }, 结果.output) : null,
168
+ ]) : null,
169
+
170
+ h('div', { key: 'tip', className: 'dwhb-tip' },
171
+ `本机处理、不上传文件。${当前格式.tips}。命令等价于 CLI:dsh-${fmt === 'word' ? 'docx' : fmt === 'excel' ? 'xlsx' : 'pdf'}-batch <操作> --input <路径>`),
172
+ ]);
173
+ }
174
+
175
+ const inject = ['slots'];
176
+ function apply(ctx) {
177
+ const 试 = (名, fn) => {
178
+ try { fn(); } catch (e) { try { console.warn('[office-batch] 插槽注册失败(已跳过):' + 名, e && e.message ? e.message : e); } catch {} }
179
+ };
180
+ 试('侧栏图标', () => ctx.slots.inject(SLOT_ICON, function* () {
181
+ yield ctx.slots.register({ name: SLOT_ICON, id: PANEL_ID, order: 24, label: '深鲸文档批量' }, IcoDoc);
182
+ }));
183
+ 试('主面板', () => ctx.slots.inject(SLOT_MAIN, function* () {
184
+ yield ctx.slots.register({ name: SLOT_MAIN, key: PANEL_ID }, 面板);
185
+ }));
186
+ }
187
+
188
+ exports.inject = inject;
189
+ exports.apply = apply;
190
+ return module.exports;
191
+ },
192
+ });
package/lib/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createPlugin, pkgRootFrom } from '@deepwhale-cn/dsh-guard-core/plugin';
2
+ import { registerDocxBatchApi } from './api.js';
2
3
 
3
- export const { name, inject, apply } = createPlugin({
4
+ const base = createPlugin({
4
5
  name: 'docx-batch',
5
6
  pkgRoot: pkgRootFrom(import.meta.url),
6
7
  skillFile: 'skill/docx-batch.md',
@@ -8,3 +9,24 @@ export const { name, inject, apply } = createPlugin({
8
9
  description: 'Word 批量替换 / 合并 / 提取文本 / 页眉页脚 / 转 Markdown,一次处理整目录',
9
10
  whenToUse: '当用户要批量处理整目录的 Word(.docx)时使用:批量替换文本(合同改甲方、术语统一)、合并多份文档、提取正文到 txt、查看页眉页脚、转 Markdown。典型说法:「把这批 Word 里的旧公司名全换掉」「这些 docx 合并成一份」「Word 转 Markdown」。不用于 .doc 老格式、加密文档或需要保留复杂排版的场景。',
10
11
  });
12
+
13
+ /** 技能名(与 cordis.patch.yml 的行 id 对应) */
14
+ export const name = base.name;
15
+ /** 技能服务 + 面板要用的 HTTP 出口 */
16
+ export const inject = [...base.inject, 'webServer'];
17
+
18
+ /**
19
+ * 先注册技能(CLI/Agent 用),再把面板用的 HTTP 出口挂上。
20
+ * webServer 缺失时只影响面板,CLI 与 skill 照常 —— 不让可选能力阻断启动。
21
+ */
22
+ export function apply(ctx) {
23
+ base.apply(ctx);
24
+ try {
25
+ if (ctx.webServer && typeof ctx.webServer.register === 'function') {
26
+ registerDocxBatchApi(ctx);
27
+ ctx.logger?.info?.('[docx-batch] 已挂面板出口 /dsh-docx-batch/api');
28
+ }
29
+ } catch (e) {
30
+ ctx.logger?.warn?.('[docx-batch] 面板出口注册失败(CLI/skill 不受影响): ' + (e && e.message ? e.message : e));
31
+ }
32
+ }
package/package.json CHANGED
@@ -1,19 +1,64 @@
1
1
  {
2
2
  "name": "@deepwhale-cn/dsh-docx-batch",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "Word 批量替换 / 合并 / 提取文本 / 页眉页脚 / 转 Markdown,一次处理整目录",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
- "keywords": ["dsh", "dsh-plugin", "deepseek-harness", "skill", "tool", "docx-batch"],
8
- "files": ["lib", "bin", "skill", "cordis.patch.yml", "tests/fixtures", "README.md", "LICENSE"],
7
+ "keywords": [
8
+ "dsh",
9
+ "dsh-plugin",
10
+ "deepseek-harness",
11
+ "skill",
12
+ "tool",
13
+ "docx-batch"
14
+ ],
15
+ "files": [
16
+ "lib",
17
+ "bin",
18
+ "skill",
19
+ "cordis.patch.yml",
20
+ "tests/fixtures",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
9
24
  "main": "./lib/index.js",
10
- "exports": { ".": "./lib/index.js", "./package.json": "./package.json" },
11
- "bin": { "dsh-docx-batch": "./bin/cli.mjs" },
12
- "engines": { "dsh": ">=0.1.0-rc.6 <0.3.0", "node": ">=18" },
13
- "dsh": { "bundle": { "patch": "./cordis.patch.yml" } },
14
- "dependencies": { "@deepwhale-cn/dsh-guard-core": "^0.5.1" },
15
- "peerDependencies": { "@deepseek-ai/dsh-skill": "*" },
16
- "repository": { "type": "git", "url": "git+https://github.com/feely0208/dsh-docx-batch.git" },
25
+ "exports": {
26
+ ".": "./lib/index.js",
27
+ "./client": "./lib/client.js",
28
+ "./package.json": "./package.json"
29
+ },
30
+ "bin": {
31
+ "dsh-docx-batch": "./bin/cli.mjs"
32
+ },
33
+ "engines": {
34
+ "dsh": ">=0.1.0-rc.6 <0.3.0",
35
+ "node": ">=18"
36
+ },
37
+ "dsh": {
38
+ "bundle": {
39
+ "patch": "./cordis.patch.yml"
40
+ },
41
+ "client": {
42
+ "inject": [
43
+ "@deepseek-ai/dsh-client-runtime"
44
+ ],
45
+ "platform": "web",
46
+ "immediately": true
47
+ }
48
+ },
49
+ "dependencies": {
50
+ "@deepwhale-cn/dsh-guard-core": "^0.5.1"
51
+ },
52
+ "peerDependencies": {
53
+ "@deepseek-ai/dsh-skill": "*"
54
+ },
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "git+https://github.com/feely0208/dsh-docx-batch.git"
58
+ },
17
59
  "homepage": "https://www.deepwhale.org.cn/skills.html",
18
- "bugs": { "url": "https://github.com/feely0208/dsh-docx-batch/issues" }
60
+ "bugs": {
61
+ "url": "https://github.com/feely0208/dsh-docx-batch/issues"
62
+ },
63
+ "author": "温州深鲸智能科技有限公司 <contact@deepwhale.org.cn>"
19
64
  }