dsh-data-cleaning-agent 0.2.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 +31 -0
- package/CONTRIBUTING.md +47 -0
- package/LICENSE +21 -0
- package/README.en.md +117 -0
- package/README.md +108 -0
- package/cordis.patch.yml +6 -0
- package/docs/COMPATIBILITY.md +45 -0
- package/docs/FIRST-CONTRIBUTION.md +43 -0
- package/docs/USER-GUIDE.md +57 -0
- package/install.sh +66 -0
- package/lib/client.js +38 -0
- package/lib/engine.js +290 -0
- package/lib/index.js +73 -0
- package/lib/jobs.js +144 -0
- package/lib/skill.js +32 -0
- package/lib/tools.js +144 -0
- package/lib/web.js +343 -0
- package/marketing/metadata.json +84 -0
- package/package.json +87 -0
package/lib/web.js
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host 半区路由:上传解析 / 同步清洗补全(含 CSV 下载)/ 异步任务 / UI 页面。
|
|
3
|
+
* 路径沿用 spike 系列的 `/data-cleaning/...` 前缀;本 MVP 使用 `/data-cleaning/api/mvp/*`。
|
|
4
|
+
*
|
|
5
|
+
* 安全:
|
|
6
|
+
* - 同源守卫(isTrusted):cross-site fetch 直接 403。
|
|
7
|
+
* - 上传体大小上限(parseBody)。
|
|
8
|
+
* - 同步接口返回明细行仅供「已授权同源 UI」下载,不面向模型。
|
|
9
|
+
*/
|
|
10
|
+
import { parseCsv, parseXlsx, parseJson, detectFormat, toCsv } from './engine.js';
|
|
11
|
+
import { runSync, DataCleaningJobs } from './jobs.js';
|
|
12
|
+
|
|
13
|
+
const MAX_BODY = 16 * 1024 * 1024; // 16 MiB 上传上限(MVP)
|
|
14
|
+
|
|
15
|
+
function isTrusted(req) {
|
|
16
|
+
const ffs = String(req.headers['sec-fetch-site'] ?? '');
|
|
17
|
+
if (ffs === 'cross-site') return false;
|
|
18
|
+
const origin = req.headers.origin;
|
|
19
|
+
if (origin) {
|
|
20
|
+
try {
|
|
21
|
+
const o = new URL(origin);
|
|
22
|
+
if (o.hostname !== '127.0.0.1' && o.hostname !== 'localhost') return false;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function writeJson(res, status, payload) {
|
|
31
|
+
res.writeHead(status, {
|
|
32
|
+
'content-type': 'application/json; charset=utf-8',
|
|
33
|
+
'cache-control': 'no-store',
|
|
34
|
+
'x-content-type-options': 'nosniff',
|
|
35
|
+
'referrer-policy': 'no-referrer',
|
|
36
|
+
});
|
|
37
|
+
res.end(JSON.stringify(payload));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function readBody(req, max = MAX_BODY) {
|
|
41
|
+
const chunks = [];
|
|
42
|
+
let total = 0;
|
|
43
|
+
for await (const chunk of req) {
|
|
44
|
+
total += chunk.length;
|
|
45
|
+
if (total > max) {
|
|
46
|
+
const err = new Error(`body exceeds ${max} bytes`);
|
|
47
|
+
err.code = 'DC_TOO_LARGE';
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
50
|
+
chunks.push(chunk);
|
|
51
|
+
}
|
|
52
|
+
return Buffer.concat(chunks);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 从 JSON 协议解析上传:{ filename, content }。content 为字符串;xlsx 时为 base64。 */
|
|
56
|
+
async function parseUpload(body) {
|
|
57
|
+
let payload;
|
|
58
|
+
try {
|
|
59
|
+
payload = JSON.parse(body.toString('utf8'));
|
|
60
|
+
} catch {
|
|
61
|
+
const err = new Error('body must be JSON: { filename, content }');
|
|
62
|
+
err.code = 'DC_BAD_JSON';
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
const filename = String(payload?.filename ?? 'data.csv');
|
|
66
|
+
const content = payload?.content ?? '';
|
|
67
|
+
const fmt = detectFormat(filename);
|
|
68
|
+
if (fmt === 'xlsx') {
|
|
69
|
+
const buf = Buffer.from(String(content), 'base64');
|
|
70
|
+
return { fmt, ...(await parseXlsx(buf)) };
|
|
71
|
+
}
|
|
72
|
+
if (fmt === 'json') return { fmt, ...parseJson(String(content)) };
|
|
73
|
+
return { fmt, ...parseCsv(String(content)) };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const UI_HTML = `<!doctype html>
|
|
77
|
+
<html lang="zh-CN">
|
|
78
|
+
<head>
|
|
79
|
+
<meta charset="utf-8">
|
|
80
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
81
|
+
<title>数据清洗补全智能体 · MVP</title>
|
|
82
|
+
<style>
|
|
83
|
+
:root { color-scheme: light dark; }
|
|
84
|
+
body { font: 14px/1.6 system-ui, -apple-system, "PingFang SC", sans-serif; max-width: 960px; margin: 0 auto; padding: 24px; }
|
|
85
|
+
h1 { font-size: 20px; }
|
|
86
|
+
textarea { width: 100%; box-sizing: border-box; min-height: 140px; font: 12px/1.4 ui-monospace, SFMono-Regular, monospace; }
|
|
87
|
+
.row { display: flex; gap: 8px; align-items: center; margin: 8px 0; flex-wrap: wrap; }
|
|
88
|
+
button { padding: 6px 14px; cursor: pointer; }
|
|
89
|
+
pre { background: rgba(128,128,128,.12); padding: 12px; border-radius: 6px; overflow: auto; }
|
|
90
|
+
.muted { opacity: .7; }
|
|
91
|
+
table { border-collapse: collapse; width: 100%; font-size: 12px; }
|
|
92
|
+
th, td { border: 1px solid rgba(128,128,128,.4); padding: 3px 8px; text-align: left; }
|
|
93
|
+
</style>
|
|
94
|
+
</head>
|
|
95
|
+
<body>
|
|
96
|
+
<h1>数据清洗补全智能体 <span class="muted">· MVP</span></h1>
|
|
97
|
+
<p class="muted">粘贴 CSV(含表头)或上传 CSV/XLSX,然后清洗 / 补全 / 概览。</p>
|
|
98
|
+
|
|
99
|
+
<div class="row">
|
|
100
|
+
<input id="file" type="file" accept=".csv,.txt,.xlsx,.xls,.json">
|
|
101
|
+
<button id="upload">上传并解析</button>
|
|
102
|
+
</div>
|
|
103
|
+
<textarea id="src" placeholder="name,phone,amount 张三,13800000001,100 李四,13800000002,-20"></textarea>
|
|
104
|
+
|
|
105
|
+
<div class="row">
|
|
106
|
+
<button id="parse">解析预览</button>
|
|
107
|
+
<button id="clean">清洗</button>
|
|
108
|
+
<button id="complete">补全</button>
|
|
109
|
+
<button id="profile">概览</button>
|
|
110
|
+
<button id="job">后台任务</button>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div id="out"><pre class="muted">结果将显示在这里。</pre></div>
|
|
114
|
+
|
|
115
|
+
<script>
|
|
116
|
+
const $ = (id) => document.getElementById(id);
|
|
117
|
+
const out = (obj) => { $('out').innerHTML = '<pre></pre>'; $('out').querySelector('pre').textContent = JSON.stringify(obj, null, 2); };
|
|
118
|
+
|
|
119
|
+
async function call(path, body) {
|
|
120
|
+
const res = await fetch(path, {
|
|
121
|
+
method: body ? 'POST' : 'GET',
|
|
122
|
+
headers: body ? { 'content-type': 'application/json' } : undefined,
|
|
123
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
124
|
+
});
|
|
125
|
+
return res.json();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function currentText() {
|
|
129
|
+
const f = $('file').files[0];
|
|
130
|
+
if (f) return { filename: f.name, content: null, file: f };
|
|
131
|
+
return { filename: 'data.csv', content: $('src').value, file: null };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function withRows() {
|
|
135
|
+
const c = currentText();
|
|
136
|
+
if (c.file) {
|
|
137
|
+
const isXlsx = /\\.(xlsx|xls)$/i.test(c.file.name);
|
|
138
|
+
if (isXlsx) {
|
|
139
|
+
const buf = await c.file.arrayBuffer();
|
|
140
|
+
const b64 = btoa(String.fromCharCode(...new Uint8Array(buf)));
|
|
141
|
+
const p = await call('/data-cleaning/api/mvp/parse', { filename: c.file.name, content: b64 });
|
|
142
|
+
if (!p.ok) throw new Error(JSON.stringify(p));
|
|
143
|
+
return { rows: p.rows, headers: p.headers };
|
|
144
|
+
}
|
|
145
|
+
const txt = await c.file.text();
|
|
146
|
+
const p = await call('/data-cleaning/api/mvp/parse', { filename: c.file.name, content: txt });
|
|
147
|
+
if (!p.ok) throw new Error(JSON.stringify(p));
|
|
148
|
+
return { rows: p.rows, headers: p.headers };
|
|
149
|
+
}
|
|
150
|
+
return { rows: JSON.parse($('src').value || '[]'), headers: null };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
$('parse').onclick = async () => {
|
|
154
|
+
try {
|
|
155
|
+
const c = currentText();
|
|
156
|
+
if (c.file) {
|
|
157
|
+
const isXlsx = /\\.(xlsx|xls)$/i.test(c.file.name);
|
|
158
|
+
let content;
|
|
159
|
+
if (isXlsx) {
|
|
160
|
+
const buf = await c.file.arrayBuffer();
|
|
161
|
+
content = btoa(String.fromCharCode(...new Uint8Array(buf)));
|
|
162
|
+
} else content = await c.file.text();
|
|
163
|
+
out(await call('/data-cleaning/api/mvp/parse', { filename: c.file.name, content }));
|
|
164
|
+
} else {
|
|
165
|
+
out(await call('/data-cleaning/api/mvp/parse', { filename: 'data.csv', content: $('src').value }));
|
|
166
|
+
}
|
|
167
|
+
} catch (e) { out({ ok: false, error: String(e) }); }
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
async function runOp(path) {
|
|
171
|
+
try {
|
|
172
|
+
const { rows, headers } = await withRows();
|
|
173
|
+
const res = await call(path, { rows, headers });
|
|
174
|
+
if (res && res.csv) {
|
|
175
|
+
out({ ...res, csvPreview: res.csv.slice(0, 500) + (res.csv.length > 500 ? '…' : '') });
|
|
176
|
+
const blob = new Blob([res.csv], { type: 'text/csv;charset=utf-8' });
|
|
177
|
+
const a = document.createElement('a');
|
|
178
|
+
a.href = URL.createObjectURL(blob);
|
|
179
|
+
a.download = res.downloadName || (path.includes('clean') ? 'cleaned.csv' : 'completed.csv');
|
|
180
|
+
a.click();
|
|
181
|
+
} else out(res);
|
|
182
|
+
} catch (e) { out({ ok: false, error: String(e) }); }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
$('clean').onclick = () => runOp('/data-cleaning/api/mvp/clean');
|
|
186
|
+
$('complete').onclick = () => runOp('/data-cleaning/api/mvp/complete');
|
|
187
|
+
$('profile').onclick = () => runOp('/data-cleaning/api/mvp/profile');
|
|
188
|
+
|
|
189
|
+
$('job').onclick = async () => {
|
|
190
|
+
try {
|
|
191
|
+
const { rows, headers } = await withRows();
|
|
192
|
+
const started = await call('/data-cleaning/api/mvp/jobs', { kind: 'clean', rows, headers });
|
|
193
|
+
out(started);
|
|
194
|
+
if (started && started.id) {
|
|
195
|
+
let t = 0;
|
|
196
|
+
const timer = setInterval(async () => {
|
|
197
|
+
const st = await call('/data-cleaning/api/mvp/job/' + started.id);
|
|
198
|
+
if (!st || st.state === 'completed' || st.state === 'failed' || st.state === 'killed' || t++ > 20) {
|
|
199
|
+
clearInterval(timer);
|
|
200
|
+
out(st);
|
|
201
|
+
}
|
|
202
|
+
}, 500);
|
|
203
|
+
}
|
|
204
|
+
} catch (e) { out({ ok: false, error: String(e) }); }
|
|
205
|
+
};
|
|
206
|
+
</script>
|
|
207
|
+
</body>
|
|
208
|
+
</html>`;
|
|
209
|
+
|
|
210
|
+
export function mountWebRoutes(wctx, { logger, report, TOOL_NAME, SKILL_NAME }) {
|
|
211
|
+
const server = wctx.webServer;
|
|
212
|
+
const tools = wctx.tools;
|
|
213
|
+
const skills = wctx.skills;
|
|
214
|
+
const disposers = [];
|
|
215
|
+
let state = null; // DataCleaningJobs,惰性初始化
|
|
216
|
+
let stateReady = null;
|
|
217
|
+
|
|
218
|
+
const register = (path, handler) => {
|
|
219
|
+
disposers.push(server.register({ kind: 'prefix', path, handler }));
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const getState = () => {
|
|
223
|
+
if (wctx.jobs && wctx.storageDomain) {
|
|
224
|
+
if (!stateReady) {
|
|
225
|
+
state = new DataCleaningJobs({ jobs: wctx.jobs, storageDomain: wctx.storageDomain, logger });
|
|
226
|
+
stateReady = state.init();
|
|
227
|
+
}
|
|
228
|
+
return stateReady;
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
register('/data-cleaning/', (req, res) => {
|
|
234
|
+
if (!isTrusted(req)) { res.writeHead(403); return res.end('untrusted origin'); }
|
|
235
|
+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' });
|
|
236
|
+
res.end(UI_HTML);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
register('/data-cleaning/api/mvp/seam', (req, res) => {
|
|
240
|
+
if (!isTrusted(req)) return writeJson(res, 403, { ok: false, error: 'untrusted origin' });
|
|
241
|
+
writeJson(res, 200, {
|
|
242
|
+
ok: true,
|
|
243
|
+
marker: 'mvp-seam',
|
|
244
|
+
report,
|
|
245
|
+
capabilities: {
|
|
246
|
+
toolRegistered: Boolean(tools.get(TOOL_NAME)),
|
|
247
|
+
tools: [TOOL_NAME, 'data_complete_rows', 'data_profile'].map((n) => ({ name: n, registered: Boolean(tools.get(n)) })),
|
|
248
|
+
skillListed: null,
|
|
249
|
+
jobs: Boolean(wctx.jobs),
|
|
250
|
+
storageDomain: Boolean(wctx.storageDomain),
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
register('/data-cleaning/api/mvp/parse', async (req, res) => {
|
|
256
|
+
if (!isTrusted(req)) return writeJson(res, 403, { ok: false, error: 'untrusted origin' });
|
|
257
|
+
try {
|
|
258
|
+
const body = await readBody(req);
|
|
259
|
+
const { fmt, headers, rows } = await parseUpload(body);
|
|
260
|
+
writeJson(res, 200, {
|
|
261
|
+
ok: true,
|
|
262
|
+
fmt,
|
|
263
|
+
headers,
|
|
264
|
+
rowCount: rows.length,
|
|
265
|
+
preview: rows.slice(0, 5),
|
|
266
|
+
rows,
|
|
267
|
+
});
|
|
268
|
+
} catch (error) {
|
|
269
|
+
writeJson(res, 400, { ok: false, code: error?.code ?? 'DC_PARSE', message: error instanceof Error ? error.message : String(error) });
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const syncOp = (kind) => async (req, res) => {
|
|
274
|
+
if (!isTrusted(req)) return writeJson(res, 403, { ok: false, error: 'untrusted origin' });
|
|
275
|
+
try {
|
|
276
|
+
const body = await readBody(req);
|
|
277
|
+
const payload = JSON.parse(body.toString('utf8'));
|
|
278
|
+
const rows = Array.isArray(payload?.rows) ? payload.rows : [];
|
|
279
|
+
const headers = Array.isArray(payload?.headers) ? payload.headers : [];
|
|
280
|
+
const result = runSync(kind, rows, { headers });
|
|
281
|
+
const csv = result.rows.length ? toCsv(headers.length ? headers : result.rows[0] ? Object.keys(result.rows[0]) : [], result.rows) : '';
|
|
282
|
+
writeJson(res, 200, {
|
|
283
|
+
ok: true,
|
|
284
|
+
kind,
|
|
285
|
+
summary: result.summary,
|
|
286
|
+
rowCount: result.rows.length,
|
|
287
|
+
csv,
|
|
288
|
+
downloadName: kind === 'clean' ? 'cleaned.csv' : kind === 'complete' ? 'completed.csv' : null,
|
|
289
|
+
});
|
|
290
|
+
} catch (error) {
|
|
291
|
+
writeJson(res, 400, { ok: false, code: error?.code ?? 'DC_SYNC', message: error instanceof Error ? error.message : String(error) });
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
register('/data-cleaning/api/mvp/clean', syncOp('clean'));
|
|
296
|
+
register('/data-cleaning/api/mvp/complete', syncOp('complete'));
|
|
297
|
+
register('/data-cleaning/api/mvp/profile', syncOp('profile'));
|
|
298
|
+
|
|
299
|
+
register('/data-cleaning/api/mvp/jobs', async (req, res) => {
|
|
300
|
+
if (!isTrusted(req)) return writeJson(res, 403, { ok: false, error: 'untrusted origin' });
|
|
301
|
+
try {
|
|
302
|
+
if (req.method === 'GET') {
|
|
303
|
+
const ready = getState();
|
|
304
|
+
if (!ready) return writeJson(res, 503, { ok: false, error: 'jobs/storage unavailable in this composition' });
|
|
305
|
+
await ready;
|
|
306
|
+
const list = await state.list();
|
|
307
|
+
return writeJson(res, 200, { ok: true, jobs: list });
|
|
308
|
+
}
|
|
309
|
+
const body = await readBody(req);
|
|
310
|
+
const payload = JSON.parse(body.toString('utf8'));
|
|
311
|
+
const ready = getState();
|
|
312
|
+
if (!ready) return writeJson(res, 503, { ok: false, error: 'jobs/storage unavailable in this composition' });
|
|
313
|
+
await ready;
|
|
314
|
+
const id = await state.start({
|
|
315
|
+
kind: payload?.kind === 'complete' || payload?.kind === 'profile' ? payload.kind : 'clean',
|
|
316
|
+
rows: Array.isArray(payload?.rows) ? payload.rows : [],
|
|
317
|
+
headers: Array.isArray(payload?.headers) ? payload.headers : [],
|
|
318
|
+
});
|
|
319
|
+
writeJson(res, 202, { ok: true, id });
|
|
320
|
+
} catch (error) {
|
|
321
|
+
writeJson(res, 400, { ok: false, code: error?.code ?? 'DC_JOB', message: error instanceof Error ? error.message : String(error) });
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
register('/data-cleaning/api/mvp/job', async (req, res) => {
|
|
326
|
+
if (!isTrusted(req)) return writeJson(res, 403, { ok: false, error: 'untrusted origin' });
|
|
327
|
+
try {
|
|
328
|
+
const ready = getState();
|
|
329
|
+
if (!ready) return writeJson(res, 503, { ok: false, error: 'jobs/storage unavailable in this composition' });
|
|
330
|
+
await ready;
|
|
331
|
+
const id = String((req.url ?? '').split('/').filter(Boolean).pop() ?? '');
|
|
332
|
+
const rec = await state.get(id);
|
|
333
|
+
writeJson(res, 200, rec ?? { ok: false, error: 'not found', id });
|
|
334
|
+
} catch (error) {
|
|
335
|
+
writeJson(res, 400, { ok: false, code: error?.code ?? 'DC_JOB', message: error instanceof Error ? error.message : String(error) });
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
return () => {
|
|
340
|
+
if (state) { state.dispose().catch(() => {}); }
|
|
341
|
+
for (const dispose of disposers) dispose();
|
|
342
|
+
};
|
|
343
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"repository": "duhu2000/dsh-data-cleaning-agent",
|
|
4
|
+
"packageName": "dsh-data-cleaning-agent",
|
|
5
|
+
"npm": {
|
|
6
|
+
"description": "Clean, complete, and profile enterprise name lists in DeepSeek Harness — a data cleaning & completion agent plugin with a local CSV/XLSX/JSON engine and optional Qichacha (QCC) MCP enrichment. Maintained by Qichacha/QCC.",
|
|
7
|
+
"requiredKeywords": [
|
|
8
|
+
"deepseek-harness",
|
|
9
|
+
"deepseek",
|
|
10
|
+
"harness",
|
|
11
|
+
"dsh",
|
|
12
|
+
"dsh-plugin",
|
|
13
|
+
"cordis",
|
|
14
|
+
"plugin",
|
|
15
|
+
"extension",
|
|
16
|
+
"data-cleaning",
|
|
17
|
+
"data-completion",
|
|
18
|
+
"data-profiling",
|
|
19
|
+
"data-quality",
|
|
20
|
+
"csv",
|
|
21
|
+
"xlsx",
|
|
22
|
+
"json",
|
|
23
|
+
"deduplication",
|
|
24
|
+
"enterprise-data",
|
|
25
|
+
"company-list",
|
|
26
|
+
"lead-cleaning",
|
|
27
|
+
"qichacha",
|
|
28
|
+
"qcc",
|
|
29
|
+
"mcp",
|
|
30
|
+
"ai-tools",
|
|
31
|
+
"llm-tools",
|
|
32
|
+
"ai-agents",
|
|
33
|
+
"agent-skills"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"github": {
|
|
37
|
+
"description": "DeepSeek Harness 数据清洗补全智能体插件:清洗、补全、画像企业名单数据,本地 CSV/XLSX/JSON 引擎 + 可选企查查 MCP 企业数据补全。由企查查(Qichacha/QCC)团队发起维护。A data cleaning & completion agent plugin for DeepSeek Harness.",
|
|
38
|
+
"topics": [
|
|
39
|
+
"deepseek-harness",
|
|
40
|
+
"deepseek",
|
|
41
|
+
"harness",
|
|
42
|
+
"dsh",
|
|
43
|
+
"dsh-plugin",
|
|
44
|
+
"cordis",
|
|
45
|
+
"plugin",
|
|
46
|
+
"extension",
|
|
47
|
+
"data-cleaning",
|
|
48
|
+
"data-completion",
|
|
49
|
+
"data-profiling",
|
|
50
|
+
"data-quality",
|
|
51
|
+
"csv",
|
|
52
|
+
"xlsx",
|
|
53
|
+
"json",
|
|
54
|
+
"deduplication",
|
|
55
|
+
"enterprise-data",
|
|
56
|
+
"company-list",
|
|
57
|
+
"lead-cleaning",
|
|
58
|
+
"qichacha",
|
|
59
|
+
"qcc",
|
|
60
|
+
"mcp",
|
|
61
|
+
"ai-tools",
|
|
62
|
+
"llm-tools",
|
|
63
|
+
"ai-agents",
|
|
64
|
+
"agent-skills"
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
"readme": {
|
|
68
|
+
"heroZh": "> 在 DeepSeek Harness 中清洗、补全、画像企业名单数据的智能体插件:本地 CSV/XLSX/JSON 引擎 + 可选企查查 MCP 企业数据补全,由企查查(Qichacha/QCC)团队发起并维护",
|
|
69
|
+
"heroEn": "> A data cleaning & completion agent plugin for DeepSeek Harness: local CSV/XLSX/JSON engine plus optional Qichacha (QCC) MCP enterprise-data enrichment, initiated and maintained by the Qichacha (QCC) team",
|
|
70
|
+
"ctaZh": "如果它帮你更快地清洗企业名单数据,欢迎 [GitHub 点个 Star](https://github.com/duhu2000/dsh-data-cleaning-agent/stargazers)、[提交 Issue](https://github.com/duhu2000/dsh-data-cleaning-agent/issues)或[参与贡献](CONTRIBUTING.md)。",
|
|
71
|
+
"ctaEn": "If the plugin helps you clean company lists faster, consider [starring the repository](https://github.com/duhu2000/dsh-data-cleaning-agent/stargazers), [filing an issue](https://github.com/duhu2000/dsh-data-cleaning-agent/issues), or [contributing a fix](CONTRIBUTING.md)."
|
|
72
|
+
},
|
|
73
|
+
"externalListing": {
|
|
74
|
+
"en": "Data cleaning & completion agent plugin for DeepSeek Harness with a local CSV/XLSX/JSON engine (clean / complete / profile / deduplicate), built-in Skill, async job state machine, and optional Qichacha (QCC) MCP enterprise-data enrichment.",
|
|
75
|
+
"zh": "DeepSeek Harness 数据清洗补全智能体插件:本地 CSV/XLSX/JSON 引擎(清洗/补全/画像/去重)、内嵌 Skill、异步任务状态机,可选企查查 MCP 企业数据补全。"
|
|
76
|
+
},
|
|
77
|
+
"links": {
|
|
78
|
+
"repository": "https://github.com/duhu2000/dsh-data-cleaning-agent",
|
|
79
|
+
"stars": "https://github.com/duhu2000/dsh-data-cleaning-agent/stargazers",
|
|
80
|
+
"issues": "https://github.com/duhu2000/dsh-data-cleaning-agent/issues",
|
|
81
|
+
"firstIssues": "https://github.com/duhu2000/dsh-data-cleaning-agent/labels/good%20first%20issue",
|
|
82
|
+
"contributing": "https://github.com/duhu2000/dsh-data-cleaning-agent/blob/main/CONTRIBUTING.md"
|
|
83
|
+
}
|
|
84
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-data-cleaning-agent",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Clean, complete, and profile enterprise name lists in DeepSeek Harness — a data cleaning & completion agent plugin with local CSV/XLSX/JSON engine and optional Qichacha (QCC) MCP enrichment. Maintained by Qichacha/QCC.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./client": "./lib/client.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"lib",
|
|
14
|
+
"cordis.patch.yml",
|
|
15
|
+
"README.md",
|
|
16
|
+
"README.en.md",
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"CHANGELOG.md",
|
|
19
|
+
"CONTRIBUTING.md",
|
|
20
|
+
"install.sh",
|
|
21
|
+
"marketing",
|
|
22
|
+
"docs/USER-GUIDE.md",
|
|
23
|
+
"docs/FIRST-CONTRIBUTION.md",
|
|
24
|
+
"docs/COMPATIBILITY.md"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"test": "node --test",
|
|
28
|
+
"lint": "node --check lib/index.js && node --check lib/engine.js && node --check lib/tools.js && node --check lib/skill.js && node --check lib/jobs.js && node --check lib/web.js && node --check lib/client.js",
|
|
29
|
+
"docs:check": "node scripts/check-readme-version.mjs",
|
|
30
|
+
"marketing:check": "node scripts/check-marketing.mjs",
|
|
31
|
+
"verify-pack": "node scripts/verify-pack.mjs",
|
|
32
|
+
"check": "npm run lint && npm run docs:check && npm run marketing:check && npm run verify-pack && npm test",
|
|
33
|
+
"prepublishOnly": "npm run check"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"deepseek-harness",
|
|
37
|
+
"deepseek",
|
|
38
|
+
"harness",
|
|
39
|
+
"dsh",
|
|
40
|
+
"dsh-plugin",
|
|
41
|
+
"cordis",
|
|
42
|
+
"plugin",
|
|
43
|
+
"extension",
|
|
44
|
+
"data-cleaning",
|
|
45
|
+
"data-completion",
|
|
46
|
+
"data-profiling",
|
|
47
|
+
"data-quality",
|
|
48
|
+
"csv",
|
|
49
|
+
"xlsx",
|
|
50
|
+
"json",
|
|
51
|
+
"deduplication",
|
|
52
|
+
"enterprise-data",
|
|
53
|
+
"company-list",
|
|
54
|
+
"lead-cleaning",
|
|
55
|
+
"qichacha",
|
|
56
|
+
"qcc",
|
|
57
|
+
"mcp",
|
|
58
|
+
"ai-tools",
|
|
59
|
+
"llm-tools",
|
|
60
|
+
"ai-agents",
|
|
61
|
+
"agent-skills"
|
|
62
|
+
],
|
|
63
|
+
"license": "MIT",
|
|
64
|
+
"repository": {
|
|
65
|
+
"type": "git",
|
|
66
|
+
"url": "git+https://github.com/duhu2000/dsh-data-cleaning-agent.git"
|
|
67
|
+
},
|
|
68
|
+
"homepage": "https://github.com/duhu2000/dsh-data-cleaning-agent#readme",
|
|
69
|
+
"bugs": {
|
|
70
|
+
"url": "https://github.com/duhu2000/dsh-data-cleaning-agent/issues"
|
|
71
|
+
},
|
|
72
|
+
"engines": {
|
|
73
|
+
"node": ">=20"
|
|
74
|
+
},
|
|
75
|
+
"dependencies": {
|
|
76
|
+
"xlsx": "^0.18.5"
|
|
77
|
+
},
|
|
78
|
+
"dsh": {
|
|
79
|
+
"bundle": {
|
|
80
|
+
"patch": "./cordis.patch.yml"
|
|
81
|
+
},
|
|
82
|
+
"client": {
|
|
83
|
+
"inject": [],
|
|
84
|
+
"platform": "web"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|