@deepwhale-cn/dsh-deliverable-pack 0.0.0-stage → 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 深鲸 DeepWhale
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,42 @@
1
- # Temporary Holding Version
1
+ # 深鲸交付包(@deepwhale-cn/dsh-deliverable-pack)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 把产出物打成一个**可核对、可追溯**的交付物:`zip + 文件清单 + SHA256`。
4
+
5
+ 律师交证据、设计师交稿、顾问交报告——对方拿到的是一个能校验的包,而不是一堆散文件。
6
+
7
+ ## 安装
8
+
9
+ ```bash
10
+ dsh plugin --profile web add @deepwhale-cn/dsh-deliverable-pack
11
+ ```
12
+
13
+ 也可源码安装:`dsh plugin --profile web add github:feely0208/dsh-deliverable-pack`。
14
+
15
+ ## 面板
16
+
17
+ 侧栏「深鲸交付包」→ 填要交付的目录(可选输出目录/包名/后缀过滤)→ 生成交付包 → 可点「重新校验 SHA256」。
18
+
19
+ ## 命令
20
+
21
+ ```bash
22
+ dsh-deliverable-pack --input ./结案材料 --out ./交付 --name 张某案-结案交付
23
+ dsh-deliverable-pack --input ./设计稿 --exts png,jpg,pdf --no-zip
24
+ dsh-deliverable-pack --verify ./交付/张某案-结案交付
25
+ ```
26
+
27
+ ## 产出结构
28
+
29
+ ```
30
+ <名称>/
31
+ files/... 原始产出物(保留相对路径;自动跳过 node_modules/.git/dist 等)
32
+ MANIFEST.json 每个文件的路径 / 字节数 / SHA256 / mtime
33
+ CHECKSUMS.sha256 shasum -a 256 -c 可直接核对
34
+ 交付说明.md 怎么核对
35
+ <名称>.zip
36
+ ```
37
+
38
+ ## 说明
39
+
40
+ - 默认只收常见办公与图片后缀,可用 `--exts` 覆盖;
41
+ - 全部本机完成,不上传任何文件;
42
+ - 生成后可 `--verify` 复核,篡改会被发现(测试里有对应用例)。
package/bin/cli.mjs ADDED
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 深鲸交付包 CLI
4
+ *
5
+ * dsh-deliverable-pack --input <目录> [--out <目录>] [--name 名称] [--exts docx,pdf] [--no-zip]
6
+ * dsh-deliverable-pack --verify <包目录>
7
+ * dsh-deliverable-pack --demo
8
+ */
9
+ import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
10
+ import { tmpdir } from 'node:os';
11
+ import { join, resolve } from 'node:path';
12
+ import { buildDeliverable, verifyDeliverable } from '../lib/pack.mjs';
13
+
14
+ const CMD = 'dsh-deliverable-pack';
15
+ const args = process.argv.slice(2);
16
+
17
+ function help() {
18
+ console.log(`${CMD} —— 深鲸交付包:把产出物打包成可核对、可追溯的交付物
19
+
20
+ 用法:
21
+ ${CMD} --input <目录或文件> [--out <目录>] [--name <名称>] [--exts docx,pdf,png] [--no-zip]
22
+ ${CMD} --verify <包目录> # 按 MANIFEST 重算 SHA256
23
+ ${CMD} --demo # 自造样本跑一遍
24
+
25
+ 产出(在 --out 下):
26
+ <名称>/files/... 原始产出物(保留相对路径)
27
+ <名称>/MANIFEST.json 每个文件的路径/字节数/SHA256
28
+ <名称>/CHECKSUMS.sha256 shasum -c 可直接核对
29
+ <名称>/交付说明.md 怎么核对
30
+ <名称>.zip 打包结果(默认生成)
31
+ `);
32
+ }
33
+
34
+ function get(name, def = '') {
35
+ const i = args.indexOf(name);
36
+ return i >= 0 && args[i + 1] && !args[i + 1].startsWith('--') ? args[i + 1] : def;
37
+ }
38
+
39
+ if (!args.length || args.includes('--help') || args.includes('-h')) { help(); process.exit(0); }
40
+
41
+ if (args.includes('--demo')) {
42
+ const d = mkdtempDemo();
43
+ const r = buildDeliverable({ input: d, name: '交付包-演示', zip: true });
44
+ if (!r.ok) { console.error('❌', r.error); process.exit(1); }
45
+ const v = verifyDeliverable(r.packageDir);
46
+ console.log('演示交付包:', r.packageDir);
47
+ console.log(` 文件 ${r.fileCount} 个 / ${(r.totalBytes / 1024).toFixed(1)} KB`);
48
+ console.log(` 校验:${v.ok ? `✅ ${v.checked} 个文件全部一致` : '❌ ' + v.problems.join('; ')}`);
49
+ console.log(` zip:${r.zip || '(未生成,系统没有 zip 命令)'}`);
50
+ process.exit(0);
51
+ }
52
+
53
+ const verifyTarget = get('--verify');
54
+ if (verifyTarget) {
55
+ const r = verifyDeliverable(resolve(verifyTarget));
56
+ if (r.error) { console.error('❌', r.error); process.exit(1); }
57
+ if (r.ok) { console.log(`✅ 校验通过:${r.checked} 个文件与 MANIFEST 一致`); process.exit(0); }
58
+ console.error(`❌ 校验失败:${r.problems.length} 个问题`);
59
+ r.problems.forEach((p) => console.error(' ·', p));
60
+ process.exit(1);
61
+ }
62
+
63
+ const input = get('--input', get('-i'));
64
+ if (!input) { console.error('缺少 --input <目录或文件>'); help(); process.exit(2); }
65
+ const extsArg = get('--exts');
66
+ const r = buildDeliverable({
67
+ input,
68
+ out: get('--out'),
69
+ name: get('--name'),
70
+ exts: extsArg ? extsArg.split(',').map((s) => s.trim()).filter(Boolean) : undefined,
71
+ zip: !args.includes('--no-zip'),
72
+ });
73
+ if (!r.ok) { console.error('❌', r.error); process.exit(1); }
74
+ console.log(`✅ 已生成交付包:${r.packageDir}`);
75
+ console.log(` 文件 ${r.fileCount} 个 / ${(r.totalBytes / 1024 / 1024).toFixed(2)} MB`);
76
+ console.log(` 清单:MANIFEST.json + CHECKSUMS.sha256`);
77
+ if (r.zip) console.log(` zip:${r.zip}`);
78
+ else console.log(' (系统没有 zip 命令,仅生成目录)');
79
+
80
+ function mkdtempDemo() {
81
+ const d = mkdtempSync(join(tmpdir(), 'dwh-deliver-demo-'));
82
+ mkdirSync(join(d, '合同'), { recursive: true });
83
+ writeFileSync(join(d, '合同', '甲合同.docx'), 'demo-docx');
84
+ writeFileSync(join(d, '合同', '乙合同.pdf'), 'demo-pdf');
85
+ writeFileSync(join(d, '证据清单.md'), '# 证据清单\n');
86
+ mkdirSync(join(d, 'node_modules'), { recursive: true });
87
+ writeFileSync(join(d, 'node_modules', 'skip.js'), 'should be skipped');
88
+ return d;
89
+ }
@@ -0,0 +1,4 @@
1
+ # @deepwhale-cn/dsh-deliverable-pack 的组合层
2
+ - insert:
3
+ - id: deliverable-pack
4
+ name: '@deepwhale-cn/dsh-deliverable-pack'
package/lib/api.js ADDED
@@ -0,0 +1,55 @@
1
+ /** 深鲸交付包 · 宿主 HTTP 出口(面板用) */
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { readFileSync } from 'node:fs';
5
+ import { buildDeliverable, verifyDeliverable } from './pack.mjs';
6
+
7
+ const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
8
+ const ROUTE = '/dsh-deliverable-pack';
9
+ const VERSION = (() => {
10
+ try { return JSON.parse(readFileSync(join(PKG_ROOT, 'package.json'), 'utf8')).version || ''; }
11
+ catch { return ''; }
12
+ })();
13
+
14
+ function 回(res, code, obj) {
15
+ const body = JSON.stringify(obj);
16
+ res.writeHead(code, { 'content-type': 'application/json; charset=utf-8', 'content-length': Buffer.byteLength(body) });
17
+ res.end(body);
18
+ }
19
+ async function 读体(req) {
20
+ const chunks = [];
21
+ for await (const c of req) chunks.push(c);
22
+ const t = Buffer.concat(chunks).toString('utf8');
23
+ if (!t) return {};
24
+ try { return JSON.parse(t); } catch { return {}; }
25
+ }
26
+
27
+ export function registerDeliverableApi(ctx) {
28
+ const handler = async (req, res) => {
29
+ try {
30
+ const u = new URL(req.url || '/', 'http://127.0.0.1');
31
+ const p = u.pathname;
32
+ if (p === `${ROUTE}/api/info` && req.method === 'GET') return 回(res, 200, { ok: true, version: VERSION });
33
+ if (p === `${ROUTE}/api/run` && req.method === 'POST') {
34
+ const b = await 读体(req);
35
+ const exts = String(b.exts || '').split(',').map((s) => s.trim()).filter(Boolean);
36
+ const r = buildDeliverable({
37
+ input: String(b.input || ''),
38
+ out: String(b.out || '') || undefined,
39
+ name: String(b.name || '') || undefined,
40
+ exts: exts.length ? exts : undefined,
41
+ zip: b.zip !== false,
42
+ });
43
+ return 回(res, 200, r);
44
+ }
45
+ if (p === `${ROUTE}/api/verify` && req.method === 'POST') {
46
+ const b = await 读体(req);
47
+ return 回(res, 200, verifyDeliverable(String(b.packageDir || '')));
48
+ }
49
+ return 回(res, 404, { ok: false, error: 'not found' });
50
+ } catch (e) {
51
+ return 回(res, 500, { ok: false, error: e?.message || String(e) });
52
+ }
53
+ };
54
+ ctx.webServer.register({ kind: 'prefix', path: ROUTE, handler });
55
+ }
package/lib/client.js ADDED
@@ -0,0 +1,149 @@
1
+ /**
2
+ * 深鲸交付包 —— 客户端半边(面板)
3
+ * 槽位:sidebar.panellist(图标)+ main(keyed 面板);数据走 /dsh-deliverable-pack/api。
4
+ */
5
+ window.__ModuleLoader__.load({
6
+ id: '@deepwhale-cn/dsh-deliverable-pack',
7
+ factory: (require) => {
8
+ var module = { exports: {} };
9
+ var exports = module.exports;
10
+ const react = require('react');
11
+ const h = react.createElement;
12
+ const SLOT_ICON = 'sidebar.panellist';
13
+ const SLOT_MAIN = 'main';
14
+ const PANEL_ID = 'deliverable-pack';
15
+ const API = '/dsh-deliverable-pack/api';
16
+ const BADGE = 'data:image/png;base64,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';
17
+
18
+ async function call(path, options) {
19
+ const res = await fetch(`${API}${path}`, {
20
+ headers: { 'content-type': 'application/json' },
21
+ ...options,
22
+ body: options && options.body !== undefined ? JSON.stringify(options.body) : undefined,
23
+ });
24
+ const text = await res.text();
25
+ let data = null;
26
+ try { data = text ? JSON.parse(text) : null; } catch { data = { ok: false, error: text }; }
27
+ return data;
28
+ }
29
+
30
+ const Ico = ({ size = 18 } = {}) => h('svg', { width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round' }, [
31
+ h('path', { key: 'a', d: 'M4 7h16v13a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z' }),
32
+ h('path', { key: 'b', d: 'M4 7l3-3h10l3 3' }),
33
+ h('path', { key: 'c', d: 'M9 12h6M9 16h4' }),
34
+ ]);
35
+
36
+ const CSS = `
37
+ .dlp-root { position:relative; display:flex; flex-direction:column; gap:14px; padding:18px 22px 52px; height:100%; overflow:auto; }
38
+ .dlp-head { display:flex; align-items:center; gap:10px; }
39
+ .dlp-title { font-size:16px; font-weight:600; }
40
+ .dlp-sub { color: var(--dsw-alias-text-secondary, #8b98a8); font-size:12px; }
41
+ .dlp-ver { margin-left:8px; font-size:11px; opacity:.75; border:1px solid var(--dsw-alias-border-l1, rgba(255,255,255,.12)); border-radius:6px; padding:0 5px; }
42
+ .dlp-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; }
43
+ .dlp-row { display:flex; gap:8px; align-items:center; }
44
+ .dlp-label { font-size:12px; color: var(--dsw-alias-text-secondary, #8b98a8); width:76px; flex:none; }
45
+ .dlp-input { 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; }
46
+ .dlp-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; }
47
+ .dlp-btn[disabled] { opacity:.55; cursor:default; }
48
+ .dlp-out { white-space:pre-wrap; word-break:break-word; font-family: ui-monospace, Menlo, monospace; font-size:12px; line-height:1.6; max-height:260px; overflow:auto; background: var(--dsw-alias-bg-layer-2, rgba(0,0,0,.18)); border-radius:8px; padding:10px; }
49
+ .dlp-ok { color: var(--dsw-alias-state-success-primary, #46b47a); }
50
+ .dlp-err { color: var(--dsw-alias-state-error-primary, #e0645a); }
51
+ .dlp-tip { font-size:12px; color: var(--dsw-alias-text-secondary, #8b98a8); }
52
+ .dlp-badge { position:absolute; right:16px; bottom:12px; display:inline-flex; opacity:.9; }
53
+ .dlp-badge img { width:121px; height:20px; display:block; }
54
+ `;
55
+
56
+ function 面板() {
57
+ const [input, 设input] = react.useState('');
58
+ const [out, 设out] = react.useState('');
59
+ const [name, 设name] = react.useState('');
60
+ const [exts, 设exts] = react.useState('');
61
+ const [zip, 设zip] = react.useState(true);
62
+ const [版本, 设版本] = react.useState('');
63
+ const [跑, 设跑] = react.useState(false);
64
+ const [结果, 设结果] = react.useState(null);
65
+ const [校验, 设校验] = react.useState(null);
66
+
67
+ react.useEffect(() => {
68
+ void (async () => { const r = await call('/info'); if (r && r.version) 设版本(String(r.version)); })();
69
+ }, []);
70
+
71
+ const 执行 = async () => {
72
+ 设跑(true); 设结果(null); 设校验(null);
73
+ try {
74
+ const r = await call('/run', { method: 'POST', body: { input, out, name, exts, zip } });
75
+ 设结果(r);
76
+ } catch (e) {
77
+ 设结果({ ok: false, error: e instanceof Error ? e.message : String(e) });
78
+ } finally { 设跑(false); }
79
+ };
80
+ const 去校验 = async () => {
81
+ if (!结果 || !结果.packageDir) return;
82
+ const r = await call('/verify', { method: 'POST', body: { packageDir: 结果.packageDir } });
83
+ 设校验(r);
84
+ };
85
+
86
+ return h('div', { className: 'dlp-root' }, [
87
+ h('style', { key: 'css' }, CSS),
88
+ h('div', { key: 'head', className: 'dlp-head' }, [
89
+ h(Ico, { key: 'i' }),
90
+ h('div', { key: 't' }, [
91
+ h('div', { key: 'a', className: 'dlp-title' }, '深鲸交付包'),
92
+ h('div', { key: 'b', className: 'dlp-sub' }, [
93
+ '把产出物打成一个可核对、可追溯的交付物(zip + 清单 + SHA256)',
94
+ 版本 ? h('span', { key: 'v', className: 'dlp-ver' }, 'v' + 版本) : null,
95
+ ]),
96
+ ]),
97
+ ]),
98
+ h('div', { key: 'form', className: 'dlp-card' }, [
99
+ h('div', { key: 'in', className: 'dlp-row' }, [
100
+ h('span', { key: 'l', className: 'dlp-label' }, '要交付的目录'),
101
+ h('input', { key: 'i', className: 'dlp-input', placeholder: '把目录路径粘进来(例如 /Users/mac/case_zhumei2)', value: input, onChange: (e) => 设input(e.target.value) }),
102
+ ]),
103
+ h('div', { key: 'out', className: 'dlp-row' }, [
104
+ h('span', { key: 'l', className: 'dlp-label' }, '输出到'),
105
+ h('input', { key: 'i', className: 'dlp-input', placeholder: '留空 = 输入目录的上级', value: out, onChange: (e) => 设out(e.target.value) }),
106
+ ]),
107
+ h('div', { key: 'name', className: 'dlp-row' }, [
108
+ h('span', { key: 'l', className: 'dlp-label' }, '包名'),
109
+ h('input', { key: 'i', className: 'dlp-input', placeholder: '留空 = 交付包-时间戳', value: name, onChange: (e) => 设name(e.target.value) }),
110
+ ]),
111
+ h('div', { key: 'exts', className: 'dlp-row' }, [
112
+ h('span', { key: 'l', className: 'dlp-label' }, '只收这些后缀'),
113
+ h('input', { key: 'i', className: 'dlp-input', placeholder: '留空 = docx/xlsx/pptx/pdf/图片/md/csv/zip', value: exts, onChange: (e) => 设exts(e.target.value) }),
114
+ ]),
115
+ h('div', { key: 'zip', className: 'dlp-row' }, [
116
+ h('span', { key: 'l', className: 'dlp-label' }, '打包 zip'),
117
+ h('input', { key: 'c', type: 'checkbox', checked: zip, onChange: (e) => 设zip(e.target.checked) }),
118
+ h('span', { key: 't', className: 'dlp-tip' }, '生成 <包名>.zip(对方一键收)'),
119
+ ]),
120
+ h('div', { key: 'act', className: 'dlp-row', style: { justifyContent: 'flex-end' } }, [
121
+ h('button', { key: 'b', className: 'dlp-btn', disabled: 跑, onClick: 执行 }, 跑 ? '正在打包…' : '生成交付包'),
122
+ ]),
123
+ ]),
124
+ 结果 ? h('div', { key: 'res', className: 'dlp-card' }, [
125
+ h('div', { key: 's', className: 结果.ok ? 'dlp-ok' : 'dlp-err' },
126
+ 结果.ok ? `完成:${结果.fileCount} 个文件 / ${(结果.totalBytes / 1024 / 1024).toFixed(2)} MB` : `失败:${结果.error || '未知错误'}`),
127
+ 结果.packageDir ? h('div', { key: 'o', className: 'dlp-out' }, [`包目录:${结果.packageDir}`, 结果.zip ? `zip:${结果.zip}` : '(未生成 zip)'].join('\n')) : null,
128
+ 结果.packageDir ? h('div', { key: 'v', className: 'dlp-row' }, [
129
+ h('button', { key: 'vb', className: 'dlp-btn', onClick: 去校验 }, '重新校验 SHA256'),
130
+ 校验 ? h('span', { key: 'vt', className: 校验.ok ? 'dlp-ok' : 'dlp-err' }, 校验.ok ? `✅ ${校验.checked} 个文件一致` : `❌ ${(校验.problems || []).join('; ')}`) : null,
131
+ ]) : null,
132
+ ]) : null,
133
+ h('div', { key: 'tip', className: 'dlp-tip' }, '产出:files/ + MANIFEST.json + CHECKSUMS.sha256 + 交付说明.md。全部本机完成,不上传任何文件。'),
134
+ h('a', { key: 'badge', className: 'dlp-badge', href: 'https://deepwhale.org.cn', target: '_blank', rel: 'noopener noreferrer', title: '深鲸桌面 · deepwhale.org.cn' },
135
+ h('img', { src: BADGE, width: 121, height: 20, alt: 'powered by dwh' })),
136
+ ]);
137
+ }
138
+
139
+ const inject = ['slots'];
140
+ function apply(ctx) {
141
+ const 试 = (名, fn) => { try { fn(); } catch (e) { try { console.warn('[deliverable-pack] 插槽注册失败:' + 名, e && e.message); } catch {} } };
142
+ 试('侧栏图标', () => ctx.slots.inject(SLOT_ICON, function* () { yield ctx.slots.register({ name: SLOT_ICON, id: PANEL_ID, order: 25, label: '深鲸交付包' }, Ico); }));
143
+ 试('主面板', () => ctx.slots.inject(SLOT_MAIN, function* () { yield ctx.slots.register({ name: SLOT_MAIN, key: PANEL_ID }, 面板); }));
144
+ }
145
+ exports.inject = inject;
146
+ exports.apply = apply;
147
+ return module.exports;
148
+ },
149
+ });
package/lib/index.js ADDED
@@ -0,0 +1,26 @@
1
+ import { createPlugin, pkgRootFrom } from '@deepwhale-cn/dsh-guard-core/plugin';
2
+ import { registerDeliverableApi } from './api.js';
3
+
4
+ const base = createPlugin({
5
+ name: 'deliverable-pack',
6
+ pkgRoot: pkgRootFrom(import.meta.url),
7
+ skillFile: 'skill/deliverable-pack.md',
8
+ binFile: 'bin/cli.mjs',
9
+ description: '深鲸交付包:把产出物打成一个可核对、可追溯的交付物(zip + 文件清单 + SHA256 校验)',
10
+ whenToUse: '当用户要把成果交给客户/对方/归档时使用:选一个目录(或文件),生成「files/ + MANIFEST.json + CHECKSUMS.sha256 + 交付说明.md」并打 zip;对方可用 shasum -c 核对。典型说法:「把这些文件打成一个交付包」「给客户发一份带校验的压缩包」「归档这次的产出」。',
11
+ });
12
+
13
+ export const name = base.name;
14
+ export const inject = [...base.inject, 'webServer'];
15
+
16
+ export function apply(ctx) {
17
+ base.apply(ctx);
18
+ try {
19
+ if (ctx.webServer && typeof ctx.webServer.register === 'function') {
20
+ registerDeliverableApi(ctx);
21
+ ctx.logger?.info?.('[deliverable-pack] 已挂面板出口 /dsh-deliverable-pack/api');
22
+ }
23
+ } catch (e) {
24
+ ctx.logger?.warn?.('[deliverable-pack] 面板出口注册失败(CLI/skill 不受影响): ' + (e?.message || e));
25
+ }
26
+ }
package/lib/pack.mjs ADDED
@@ -0,0 +1,138 @@
1
+ /**
2
+ * 深鲸交付包 · 内核(纯逻辑,CLI 与面板共用)
3
+ *
4
+ * 一次「交付」= 选目录/文件 → 复制进一个包目录 → 生成清单(含每个文件的 SHA256)→ 打包 zip。
5
+ * 目的:客户/对方拿到的是一个可核对、可追溯的交付物,而不是一堆散落文件。
6
+ */
7
+ import { createHash } from 'node:crypto';
8
+ import { spawnSync } from 'node:child_process';
9
+ import {
10
+ copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync,
11
+ } from 'node:fs';
12
+ import { basename, dirname, join, relative, resolve, sep } from 'node:path';
13
+
14
+ const SKIP_DIRS = new Set(['node_modules', '.git', '.svn', '__pycache__', '.DS_Store', 'dist', 'build']);
15
+ const DEFAULT_EXTS = ['docx', 'doc', 'xlsx', 'xls', 'pptx', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'md', 'txt', 'csv', 'zip'];
16
+
17
+ function sha256(file) {
18
+ return createHash('sha256').update(readFileSync(file)).digest('hex');
19
+ }
20
+
21
+ function walk(root, out, skipExt) {
22
+ for (const name of readdirSync(root)) {
23
+ if (name.startsWith('.')) continue;
24
+ const p = join(root, name);
25
+ let st;
26
+ try { st = statSync(p); } catch { continue; }
27
+ if (st.isDirectory()) {
28
+ if (SKIP_DIRS.has(name)) continue;
29
+ walk(p, out, skipExt);
30
+ continue;
31
+ }
32
+ if (!st.isFile()) continue;
33
+ if (skipExt && skipExt.length) {
34
+ const ext = name.toLowerCase().split('.').pop();
35
+ if (!skipExt.includes(ext)) continue;
36
+ }
37
+ out.push({ path: p, size: st.size, mtime: st.mtime.toISOString() });
38
+ }
39
+ }
40
+
41
+ /**
42
+ * 生成交付包。
43
+ * @param {object} o
44
+ * @param {string} o.input 目录(或单个文件)
45
+ * @param {string} [o.out] 输出目录(默认 input 的上级)
46
+ * @param {string} [o.name] 包名(默认 交付包-时间戳)
47
+ * @param {string[]} [o.exts] 只收这些后缀(默认常见办公/图片后缀)
48
+ * @param {boolean} [o.zip] 是否打 zip(默认 true)
49
+ */
50
+ export function buildDeliverable(o = {}) {
51
+ const input = resolve(String(o.input || ''));
52
+ if (!existsSync(input)) return { ok: false, error: `输入不存在:${input}` };
53
+ const st = statSync(input);
54
+ const exts = (o.exts && o.exts.length ? o.exts : DEFAULT_EXTS).map((e) => String(e).toLowerCase().replace(/^\./, ''));
55
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
56
+ const name = String(o.name || `交付包-${stamp}`).replace(/[\\/:*?"<>|]/g, '_');
57
+ const outRoot = resolve(String(o.out || (st.isDirectory() ? input : dirname(input))));
58
+ const pkgDir = join(outRoot, name);
59
+
60
+ const files = [];
61
+ if (st.isDirectory()) walk(input, files, exts);
62
+ else files.push({ path: input, size: st.size, mtime: st.mtime.toISOString() });
63
+
64
+ if (!files.length) return { ok: false, error: `没有符合条件的文件(后缀:${exts.join(', ')})` };
65
+
66
+ mkdirSync(pkgDir, { recursive: true });
67
+ const base = st.isDirectory() ? input : dirname(input);
68
+ const manifestFiles = [];
69
+ for (const f of files) {
70
+ const rel = st.isDirectory() ? relative(base, f.path) : basename(f.path);
71
+ const dest = join(pkgDir, 'files', rel);
72
+ mkdirSync(dirname(dest), { recursive: true });
73
+ copyFileSync(f.path, dest);
74
+ manifestFiles.push({ path: `files/${rel.split(sep).join('/')}`, bytes: f.size, sha256: sha256(dest), mtime: f.mtime });
75
+ }
76
+
77
+ const manifest = {
78
+ name,
79
+ generatedAt: new Date().toISOString(),
80
+ source: base,
81
+ fileCount: manifestFiles.length,
82
+ totalBytes: manifestFiles.reduce((s, f) => s + f.bytes, 0),
83
+ files: manifestFiles,
84
+ };
85
+ writeFileSync(join(pkgDir, 'MANIFEST.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8');
86
+ writeFileSync(
87
+ join(pkgDir, 'CHECKSUMS.sha256'),
88
+ manifestFiles.map((f) => `${f.sha256} ${f.path}`).join('\n') + '\n',
89
+ 'utf8',
90
+ );
91
+ writeFileSync(join(pkgDir, '交付说明.md'), [
92
+ `# ${name}`,
93
+ '',
94
+ `- 生成时间:${manifest.generatedAt}`,
95
+ `- 文件数:${manifest.fileCount}`,
96
+ `- 合计大小:${(manifest.totalBytes / 1024 / 1024).toFixed(2)} MB`,
97
+ '',
98
+ '## 怎么核对',
99
+ '',
100
+ '```bash',
101
+ 'shasum -a 256 -c CHECKSUMS.sha256 # macOS / Linux',
102
+ 'certutil -hashfile <文件> SHA256 # Windows 逐文件',
103
+ '```',
104
+ '',
105
+ '`MANIFEST.json` 里记录了每个文件的相对路径、字节数与 SHA256,可直接与对方核对。',
106
+ '',
107
+ ].join('\n'), 'utf8');
108
+
109
+ let zipPath = '';
110
+ if (o.zip !== false) {
111
+ const r = spawnSync('zip', ['-qr', `${name}.zip`, name], { cwd: outRoot, encoding: 'utf8' });
112
+ if (r.status === 0) zipPath = join(outRoot, `${name}.zip`);
113
+ }
114
+
115
+ return {
116
+ ok: true,
117
+ packageDir: pkgDir,
118
+ zip: zipPath,
119
+ fileCount: manifest.fileCount,
120
+ totalBytes: manifest.totalBytes,
121
+ manifest,
122
+ };
123
+ }
124
+
125
+ /** 校验一个交付包:按 MANIFEST 重算 SHA256,返回不一致清单 */
126
+ export function verifyDeliverable(pkgDir) {
127
+ const man = join(pkgDir, 'MANIFEST.json');
128
+ if (!existsSync(man)) return { ok: false, error: `没有 MANIFEST.json:${pkgDir}` };
129
+ const manifest = JSON.parse(readFileSync(man, 'utf8'));
130
+ const bad = [];
131
+ for (const f of manifest.files || []) {
132
+ const p = join(pkgDir, f.path);
133
+ if (!existsSync(p)) { bad.push(`${f.path}: 缺失`); continue; }
134
+ const h = sha256(p);
135
+ if (h !== f.sha256) bad.push(`${f.path}: 校验和不一致`);
136
+ }
137
+ return { ok: bad.length === 0, checked: (manifest.files || []).length, problems: bad };
138
+ }
package/package.json CHANGED
@@ -1,6 +1,68 @@
1
1
  {
2
2
  "name": "@deepwhale-cn/dsh-deliverable-pack",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "深鲸交付包:把产出物打成一个可核对、可追溯的交付物(zip + 文件清单 + SHA256 校验)",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "dsh",
9
+ "dsh-plugin",
10
+ "deepseek-harness",
11
+ "plugin",
12
+ "skill",
13
+ "deliverable",
14
+ "archive",
15
+ "sha256",
16
+ "交付",
17
+ "归档"
18
+ ],
19
+ "files": [
20
+ "lib",
21
+ "bin",
22
+ "skill",
23
+ "cordis.patch.yml",
24
+ "tests/fixtures",
25
+ "README.md",
26
+ "LICENSE"
27
+ ],
28
+ "main": "./lib/index.js",
29
+ "exports": {
30
+ ".": "./lib/index.js",
31
+ "./client": "./lib/client.js",
32
+ "./package.json": "./package.json"
33
+ },
34
+ "bin": {
35
+ "dsh-deliverable-pack": "./bin/cli.mjs"
36
+ },
37
+ "engines": {
38
+ "dsh": ">=0.1.0-rc.6 <0.3.0",
39
+ "node": ">=18"
40
+ },
41
+ "dsh": {
42
+ "bundle": {
43
+ "patch": "./cordis.patch.yml"
44
+ },
45
+ "client": {
46
+ "inject": [
47
+ "@deepseek-ai/dsh-client-runtime"
48
+ ],
49
+ "platform": "web",
50
+ "immediately": true
51
+ }
52
+ },
53
+ "dependencies": {
54
+ "@deepwhale-cn/dsh-guard-core": "^0.10.1"
55
+ },
56
+ "peerDependencies": {
57
+ "@deepseek-ai/dsh-skill": "*"
58
+ },
59
+ "repository": {
60
+ "type": "git",
61
+ "url": "git+https://github.com/feely0208/dsh-deliverable-pack.git"
62
+ },
63
+ "homepage": "https://www.deepwhale.org.cn/skills.html",
64
+ "bugs": {
65
+ "url": "https://github.com/feely0208/dsh-deliverable-pack/issues"
66
+ },
67
+ "author": "温州深鲸智能科技有限公司 <contact@deepwhale.org.cn>"
68
+ }
@@ -0,0 +1,28 @@
1
+ # 深鲸交付包(dsh-deliverable-pack)
2
+
3
+ **什么时候用**:把成果交给客户/对方/归档时。用户会说「打成交付包」「给客户发一份带校验的压缩包」「归档这次产出」。
4
+
5
+ ## 怎么用
6
+
7
+ ```bash
8
+ <cli 路径> --input <目录> [--out <目录>] [--name 名称] [--exts docx,pdf,png] [--no-zip]
9
+ <cli 路径> --verify <包目录> # 重算 SHA256 对照 MANIFEST
10
+ ```
11
+
12
+ 产出结构:
13
+
14
+ ```
15
+ <名称>/
16
+ files/... 原始产出物(保留相对路径;自动跳过 node_modules/.git/dist 等)
17
+ MANIFEST.json 每个文件的路径、字节数、SHA256、mtime
18
+ CHECKSUMS.sha256 shasum -a 256 -c 可直接核对
19
+ 交付说明.md 怎么核对
20
+ <名称>.zip 打包结果
21
+ ```
22
+
23
+ ## 规则
24
+
25
+ - 默认只收常见办公/图片后缀(docx/xlsx/pptx/pdf/图片/md/txt/csv/zip),可用 `--exts` 覆盖;
26
+ - 自动跳过隐藏文件与 node_modules/.git/dist/build/__pycache__;
27
+ - 不上传任何文件,全部在本机完成;
28
+ - 生成后建议再跑一次 `--verify`,确认清单与文件一致。