dsh-cite 0.3.0 → 0.3.2

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.en.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # dsh-cite
2
2
 
3
+ ![npm](https://img.shields.io/npm/v/dsh-cite) ![downloads](https://img.shields.io/npm/dm/dsh-cite) ![license](https://img.shields.io/github/license/STARDUSTLC666/dsh-cite) ![stars](https://img.shields.io/github/stars/STARDUSTLC666/dsh-cite?style=social)
4
+
3
5
  > Give it a DOI, get a proper citation — GB/T 7714 / APA / MLA / Chicago / BibTeX.
4
6
 
5
- A DeepSeek Harness plugin for bibliographic references: query Crossref and format citations. Four tools, zero runtime dependencies.
7
+ A DeepSeek Harness plugin for bibliographic references: query Crossref and format citations. Five tools (including the `cite_health` self-check), zero runtime dependencies.
6
8
 
7
9
  ## Tools
8
10
 
@@ -12,10 +14,11 @@ A DeepSeek Harness plugin for bibliographic references: query Crossref and forma
12
14
  | `cite_format` | Generate a formatted citation | `doi`; `style`: gb-t-7714 / apa / mla / chicago |
13
15
  | `cite_bibtex` | Generate a BibTeX entry | `doi`; `key` optional |
14
16
  | `cite_check` | Extract DOIs from text and validate them | `text`; `maxChecks` 1-50, default 10 |
17
+ | `cite_health` | Self-check: probe Crossref connectivity and report latency | none |
15
18
 
16
19
  ## Compatibility
17
20
 
18
- Verified against `@deepseek-ai/dsh@0.1.1-rc.2` on 2026-08-26. Built for the cordis patch-bundle plugin model (`cordis.patch.yml` + `dsh.bundle.patch`). No runtime imports of `@deepseek-ai/*` internals.
21
+ Verified against `@deepseek-ai/dsh@0.1.2-alpha.2` on 2026-08-31. Built for the cordis patch-bundle plugin model (`cordis.patch.yml` + `dsh.bundle.patch`). No runtime imports of `@deepseek-ai/*` internals.
19
22
 
20
23
  ## Install
21
24
 
@@ -23,8 +26,6 @@ Verified against `@deepseek-ai/dsh@0.1.1-rc.2` on 2026-08-26. Built for the cord
23
26
  dsh plugin --profile web add dsh-cite
24
27
  ```
25
28
 
26
- MIT
27
-
28
29
  ## Uninstall
29
30
 
30
31
  ```bash
package/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # dsh-cite
2
2
 
3
+ ![npm](https://img.shields.io/npm/v/dsh-cite) ![downloads](https://img.shields.io/npm/dm/dsh-cite) ![license](https://img.shields.io/github/license/STARDUSTLC666/dsh-cite) ![stars](https://img.shields.io/github/stars/STARDUSTLC666/dsh-cite?style=social)
4
+
3
5
  > 给一个 DOI,还你规范参考文献——GB/T 7714 / APA / MLA / Chicago / BibTeX。
4
6
 
5
- DeepSeek Harness 参考文献工具插件:通过 Crossref API 查询文献元数据并格式化引用。四个工具、零运行时依赖、全平台通用。
7
+ DeepSeek Harness 参考文献工具插件:通过 Crossref API 查询文献元数据并格式化引用。五个工具(含 `cite_health` 自检)、零运行时依赖、全平台通用。
6
8
 
7
9
  ## 工具
8
10
 
@@ -12,10 +14,11 @@ DeepSeek Harness 参考文献工具插件:通过 Crossref API 查询文献元
12
14
  | `cite_format` | 生成规范引文 | `doi` 必填;`style`:gb-t-7714 / apa / mla / chicago |
13
15
  | `cite_bibtex` | 生成 BibTeX 条目 | `doi` 必填;`key` 可选 |
14
16
  | `cite_check` | 从文本提取 DOI 并并发校验是否存在(并发 3,保持输入顺序) | `text` 必填;`maxChecks` 1-50 默认 10 |
17
+ | `cite_health` | 自检:探测 Crossref 连通性并报告延迟 | 无 |
15
18
 
16
19
  ## 兼容性
17
20
 
18
- 在 `@deepseek-ai/dsh@0.1.1-rc.2` 上验证(2026-08-26)。遵循 cordis 组合包补丁模型(`cordis.patch.yml` + `dsh.bundle.patch`),运行时不 import 任何 `@deepseek-ai/*` 内部模块。
21
+ 在 `@deepseek-ai/dsh@0.1.2-alpha.2` 上验证(2026-08-31)。遵循 cordis 组合包补丁模型(`cordis.patch.yml` + `dsh.bundle.patch`),运行时不 import 任何 `@deepseek-ai/*` 内部模块。
19
22
 
20
23
  ## 安装
21
24
 
package/lib/config.d.ts CHANGED
@@ -11,6 +11,7 @@ export interface ResolvedCiteConfig {
11
11
  timeoutMs: number;
12
12
  userAgent: string;
13
13
  }
14
+ export declare const DEFAULT_USER_AGENT = "dsh-cite/0.3.2 (DeepSeek Harness citation plugin; mailto:STARDUSTLC666@users.noreply.github.com)";
14
15
  export declare const CITE_TIMEOUT_ENV = "DSH_CITE_TIMEOUT_MS";
15
16
  export declare const CITE_USER_AGENT_ENV = "DSH_CITE_USER_AGENT";
16
17
  export declare function resolveConfig(config: CiteConfig | undefined | null, env?: NodeJS.ProcessEnv): ResolvedCiteConfig;
package/lib/config.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * @module dsh-cite/config
5
5
  */
6
6
  const DEFAULT_TIMEOUT_MS = 15000;
7
- const DEFAULT_USER_AGENT = 'dsh-cite/0.2.0 (DeepSeek Harness citation plugin; mailto:STARDUSTLC666@users.noreply.github.com)';
7
+ export const DEFAULT_USER_AGENT = 'dsh-cite/0.3.2 (DeepSeek Harness citation plugin; mailto:STARDUSTLC666@users.noreply.github.com)';
8
8
  export const CITE_TIMEOUT_ENV = 'DSH_CITE_TIMEOUT_MS';
9
9
  export const CITE_USER_AGENT_ENV = 'DSH_CITE_USER_AGENT';
10
10
  export function resolveConfig(config, env = process.env) {
package/lib/crossref.d.ts CHANGED
@@ -30,8 +30,8 @@ export type FetchLike = (url: string, init?: {
30
30
  }) => Promise<Response>;
31
31
  export declare function assertDoi(value: string): string;
32
32
  /** 按 DOI 查询 Crossref 并归一化。 */
33
- export declare function lookupDoi(doi: string, cfg: ResolvedCiteConfig, fetchImpl?: FetchLike): Promise<Work>;
33
+ export declare function lookupDoi(doi: string, cfg: ResolvedCiteConfig, fetchImpl?: FetchLike, signal?: AbortSignal): Promise<Work>;
34
34
  /** 按题录文本检索 Crossref,返回归一化结果。 */
35
- export declare function searchWorks(query: string, limit: number, cfg: ResolvedCiteConfig, fetchImpl?: FetchLike): Promise<Work[]>;
35
+ export declare function searchWorks(query: string, limit: number, cfg: ResolvedCiteConfig, fetchImpl?: FetchLike, signal?: AbortSignal): Promise<Work[]>;
36
36
  /** 从任意文本里提取 DOI。 */
37
37
  export declare function extractDois(text: string): string[];
package/lib/crossref.js CHANGED
@@ -6,7 +6,7 @@
6
6
  const CROSSREF_API = 'https://api.crossref.org/works/';
7
7
  const DOI_PATTERN = /^10\.\d{4,9}\/[-._;()/:A-Za-z0-9]+$/i;
8
8
  export function assertDoi(value) {
9
- const doi = value.trim().replace(/^https?:\/\/doi\.org\//i, '').replace(/^doi:\s*/i, '');
9
+ const doi = value.trim().replace(/^https?:\/\/(dx\.)?doi\.org\//i, '').replace(/^doi:\s*/i, '');
10
10
  if (!DOI_PATTERN.test(doi))
11
11
  throw new Error('DOI 格式不正确:' + value + '(示例:10.1038/nature12345)。');
12
12
  return doi;
@@ -63,18 +63,23 @@ function normalizeWork(raw) {
63
63
  function makeHeaders(cfg) {
64
64
  return { 'user-agent': cfg.userAgent, accept: 'application/json' };
65
65
  }
66
+ function requestSignal(timeoutMs, signal) {
67
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
68
+ return signal === undefined ? timeoutSignal : AbortSignal.any([signal, timeoutSignal]);
69
+ }
66
70
  /** 按 DOI 查询 Crossref 并归一化。 */
67
- export async function lookupDoi(doi, cfg, fetchImpl) {
71
+ export async function lookupDoi(doi, cfg, fetchImpl, signal) {
68
72
  const clean = assertDoi(doi);
69
73
  const fetcher = fetchImpl ?? globalThis.fetch;
70
74
  let response;
71
75
  try {
72
76
  response = await fetcher(CROSSREF_API + encodeURIComponent(clean), {
73
77
  headers: makeHeaders(cfg),
74
- signal: AbortSignal.timeout(cfg.timeoutMs),
78
+ signal: requestSignal(cfg.timeoutMs, signal),
75
79
  });
76
80
  }
77
81
  catch (error) {
82
+ signal?.throwIfAborted();
78
83
  throw new Error('Crossref 请求失败:' + (error instanceof Error ? error.message : String(error)));
79
84
  }
80
85
  if (!response.ok) {
@@ -98,7 +103,7 @@ export async function lookupDoi(doi, cfg, fetchImpl) {
98
103
  return work;
99
104
  }
100
105
  /** 按题录文本检索 Crossref,返回归一化结果。 */
101
- export async function searchWorks(query, limit, cfg, fetchImpl) {
106
+ export async function searchWorks(query, limit, cfg, fetchImpl, signal) {
102
107
  const q = query.trim();
103
108
  if (q === '')
104
109
  throw new Error('检索词不能为空。');
@@ -108,10 +113,11 @@ export async function searchWorks(query, limit, cfg, fetchImpl) {
108
113
  try {
109
114
  response = await fetcher(url, {
110
115
  headers: makeHeaders(cfg),
111
- signal: AbortSignal.timeout(cfg.timeoutMs),
116
+ signal: requestSignal(cfg.timeoutMs, signal),
112
117
  });
113
118
  }
114
119
  catch (error) {
120
+ signal?.throwIfAborted();
115
121
  throw new Error('Crossref 检索失败:' + (error instanceof Error ? error.message : String(error)));
116
122
  }
117
123
  if (!response.ok)
package/lib/format.js CHANGED
@@ -146,14 +146,15 @@ function buildApa(work) {
146
146
  text += '(' + work.issue + ')';
147
147
  if (work.page !== '')
148
148
  text += ', ' + work.page;
149
- text += '. ';
150
- text += work.doi !== '' ? 'https://doi.org/' + work.doi : work.url;
151
- return text;
149
+ const tail = work.doi !== '' ? 'https://doi.org/' + work.doi : work.url;
150
+ if (tail !== '')
151
+ return text + '. ' + tail;
152
+ return text + '.';
152
153
  }
153
154
  if (work.type === 'book' || work.type === 'monograph' || work.type === 'edited-book') {
154
- return authors + ' (' + year + '). ' + title + '. ' + (work.publisher !== '' ? work.publisher + '.' : '');
155
+ return authors + ' (' + year + '). ' + title + '.' + (work.publisher !== '' ? ' ' + work.publisher + '.' : '');
155
156
  }
156
- return authors + ' (' + year + '). ' + title + '. ' + (work.url !== '' ? work.url : '');
157
+ return authors + ' (' + year + '). ' + title + '.' + (work.url !== '' ? ' ' + work.url : '');
157
158
  }
158
159
  /** 生成 MLA 9 参考文献条目。 */
159
160
  function buildMla(work) {
@@ -241,7 +242,7 @@ function bibtexType(work) {
241
242
  export function buildBibtex(work, key) {
242
243
  const first = work.authors[0];
243
244
  const firstWord = titleOf(work).split(/[^\p{L}\p{N}]+/u).find((part) => part !== '') ?? 'work';
244
- const baseKey = key !== undefined && key.trim() !== '' ? sanitizeBibtexKey(key.trim()) : ((first?.family ?? first?.name ?? 'author') + yearText(work) + firstWord).toLowerCase();
245
+ const baseKey = key !== undefined && key.trim() !== '' ? sanitizeBibtexKey(key.trim()) : sanitizeBibtexKey(((first?.family || first?.name || 'author') + yearText(work) + firstWord).toLowerCase());
245
246
  const type = bibtexType(work);
246
247
  const fields = [];
247
248
  const authors = work.authors.map((author) => fullName(author)).join(' and ');
package/lib/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * dsh-cite —— 参考文献工具插件(node 半身,配置走 cordis.patch.yml)。
3
3
  *
4
- * 插件导出 apply(ctx, config):注册四个面向模型的工具(cite_lookup / cite_format /
5
- * cite_bibtex / cite_check),通过 Crossref API 查询元数据并生成 GB/T 7714 / APA /
6
- * MLA / Chicago 引文。零运行时依赖。
4
+ * 插件导出 apply(ctx, config):注册五个面向模型的工具(cite_lookup / cite_format /
5
+ * cite_bibtex / cite_check / cite_health),通过 Crossref API 查询元数据并生成
6
+ * GB/T 7714 / APA / MLA / Chicago 引文。零运行时依赖。
7
7
  *
8
8
  * @module dsh-cite
9
9
  */
@@ -14,9 +14,7 @@ export declare const name = "cite";
14
14
  export declare const inject: string[];
15
15
  export interface CitePluginContext {
16
16
  tools: {
17
- register(definition: CiteToolDefinition, options?: {
18
- prepend?: boolean;
19
- }): () => void;
17
+ register(definition: CiteToolDefinition): () => void;
20
18
  };
21
19
  on?(event: string, listener: () => void): () => void;
22
20
  }
package/lib/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * dsh-cite —— 参考文献工具插件(node 半身,配置走 cordis.patch.yml)。
3
3
  *
4
- * 插件导出 apply(ctx, config):注册四个面向模型的工具(cite_lookup / cite_format /
5
- * cite_bibtex / cite_check),通过 Crossref API 查询元数据并生成 GB/T 7714 / APA /
6
- * MLA / Chicago 引文。零运行时依赖。
4
+ * 插件导出 apply(ctx, config):注册五个面向模型的工具(cite_lookup / cite_format /
5
+ * cite_bibtex / cite_check / cite_health),通过 Crossref API 查询元数据并生成
6
+ * GB/T 7714 / APA / MLA / Chicago 引文。零运行时依赖。
7
7
  *
8
8
  * @module dsh-cite
9
9
  */
@@ -23,7 +23,7 @@ export function apply(ctx, config) {
23
23
  }
24
24
  const disposers = [];
25
25
  for (const definition of buildCiteTools(cfg)) {
26
- disposers.push(ctx.tools.register(definition, { prepend: true }));
26
+ disposers.push(ctx.tools.register(definition));
27
27
  }
28
28
  if (typeof ctx.on === 'function') {
29
29
  ctx.on('dispose', () => {
package/lib/tools.js CHANGED
@@ -74,11 +74,12 @@ const checkSchema = {
74
74
  additionalProperties: true,
75
75
  };
76
76
  /** 以固定并发度执行异步任务并保持原顺序。 */
77
- async function mapWithConcurrency(items, concurrency, fn) {
77
+ async function mapWithConcurrency(items, concurrency, fn, signal) {
78
78
  const results = new Array(items.length);
79
79
  let next = 0;
80
80
  const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
81
81
  while (true) {
82
+ signal?.throwIfAborted();
82
83
  const index = next;
83
84
  next += 1;
84
85
  if (index >= items.length)
@@ -89,6 +90,14 @@ async function mapWithConcurrency(items, concurrency, fn) {
89
90
  await Promise.all(workers);
90
91
  return results;
91
92
  }
93
+ function executionSignal(exec) {
94
+ if (typeof exec !== 'object' || exec === null)
95
+ return undefined;
96
+ const signal = exec.signal;
97
+ return signal instanceof AbortSignal ? signal : undefined;
98
+ }
99
+ const CHECK_CONCURRENCY = 3;
100
+ const MAX_CHECKS = 50;
92
101
  function workLabel(work) {
93
102
  const title = work.title !== '' ? work.title : '(无标题)';
94
103
  return title + '(' + (work.year > 0 ? work.year + ', ' : '') + (work.containerTitle !== '' ? work.containerTitle : work.publisher) + ')';
@@ -107,23 +116,23 @@ export function buildCiteTools(cfg, fetchImpl) {
107
116
  const works = Array.isArray(rec.works) ? rec.works : [];
108
117
  return [{ type: 'text', text: works.map((work, index) => (index + 1) + '. ' + workLabel(work)).join('\n') }];
109
118
  } },
110
- async execute(rawArgs) {
119
+ async execute(rawArgs, exec) {
111
120
  const args = (rawArgs ?? {});
112
121
  const doi = optionalString(args, 'doi');
113
122
  const query = optionalString(args, 'query');
114
123
  if (doi === undefined && query === undefined)
115
124
  throw new Error('cite_lookup 需要 doi 或 query 参数(至少一个)。');
116
125
  if (doi !== undefined) {
117
- const work = await lookupDoi(doi, cfg, fetchImpl);
126
+ const work = await lookupDoi(doi, cfg, fetchImpl, executionSignal(exec));
118
127
  return { count: 1, works: [work] };
119
128
  }
120
129
  const queryDoi = extractDois(query)[0];
121
130
  if (queryDoi !== undefined) {
122
- const work = await lookupDoi(queryDoi, cfg, fetchImpl);
131
+ const work = await lookupDoi(queryDoi, cfg, fetchImpl, executionSignal(exec));
123
132
  return { count: 1, works: [work], matchedDoi: queryDoi };
124
133
  }
125
134
  const limit = optionalInteger(args, 'limit', '返回条数', 1, 10, 5);
126
- const works = await searchWorks(query, limit, cfg, fetchImpl);
135
+ const works = await searchWorks(query, limit, cfg, fetchImpl, executionSignal(exec));
127
136
  if (works.length === 0)
128
137
  throw new Error('Crossref 没有找到匹配文献,请尝试更精确的标题/作者。');
129
138
  return { count: works.length, works };
@@ -142,12 +151,12 @@ export function buildCiteTools(cfg, fetchImpl) {
142
151
  const rec = (value ?? {});
143
152
  return [{ type: 'text', text: typeof rec.citation === 'string' ? rec.citation : '' }];
144
153
  } },
145
- async execute(rawArgs) {
154
+ async execute(rawArgs, exec) {
146
155
  const args = (rawArgs ?? {});
147
156
  const doi = requiredString(args, 'doi', 'DOI');
148
157
  const style = readStyle(args.style);
149
158
  const lang = args.lang === 'en' ? 'en' : 'zh';
150
- const work = await lookupDoi(doi, cfg, fetchImpl);
159
+ const work = await lookupDoi(doi, cfg, fetchImpl, executionSignal(exec));
151
160
  const citation = buildCitation(work, style, lang);
152
161
  return { doi: work.doi || doi, style, citation, work };
153
162
  },
@@ -164,13 +173,13 @@ export function buildCiteTools(cfg, fetchImpl) {
164
173
  const rec = (value ?? {});
165
174
  return [{ type: 'text', text: typeof rec.bibtex === 'string' ? rec.bibtex : '' }];
166
175
  } },
167
- async execute(rawArgs) {
176
+ async execute(rawArgs, exec) {
168
177
  const args = (rawArgs ?? {});
169
178
  const doi = requiredString(args, 'doi', 'DOI');
170
179
  const key = optionalString(args, 'key');
171
- const work = await lookupDoi(doi, cfg, fetchImpl);
180
+ const work = await lookupDoi(doi, cfg, fetchImpl, executionSignal(exec));
172
181
  const bibtex = buildBibtex(work, key);
173
- return { doi: work.doi || doi, key, bibtex };
182
+ return { doi: work.doi || doi, ...(key !== undefined ? { key } : {}), bibtex };
174
183
  },
175
184
  timeoutMs: cfg.timeoutMs + 2000,
176
185
  };
@@ -189,23 +198,25 @@ export function buildCiteTools(cfg, fetchImpl) {
189
198
  return (row.ok === true ? '✅' : '❌') + ' ' + row.doi + (typeof row.title === 'string' && row.title !== '' ? ' — ' + row.title : '');
190
199
  }).join('\n') }];
191
200
  } },
192
- async execute(rawArgs) {
201
+ async execute(rawArgs, exec) {
193
202
  const args = (rawArgs ?? {});
194
203
  const text = requiredString(args, 'text', '待检查文本');
195
204
  const maxChecks = optionalInteger(args, 'maxChecks', '校验数量', 1, 50, 10);
196
205
  const dois = extractDois(text).slice(0, maxChecks);
197
- const results = await mapWithConcurrency(dois, 3, async (doi) => {
206
+ const signal = executionSignal(exec);
207
+ const results = await mapWithConcurrency(dois, CHECK_CONCURRENCY, async (doi) => {
198
208
  try {
199
- const work = await lookupDoi(doi, cfg, fetchImpl);
209
+ const work = await lookupDoi(doi, cfg, fetchImpl, signal);
200
210
  return { doi, ok: true, title: work.title, url: work.url };
201
211
  }
202
212
  catch (error) {
213
+ signal?.throwIfAborted();
203
214
  return { doi, ok: false, title: '', url: '', error: error instanceof Error ? error.message : String(error) };
204
215
  }
205
- });
216
+ }, signal);
206
217
  return { count: results.length, results };
207
218
  },
208
- timeoutMs: cfg.timeoutMs * 3 + 5000,
219
+ timeoutMs: cfg.timeoutMs * Math.ceil(MAX_CHECKS / CHECK_CONCURRENCY) + 5000,
209
220
  };
210
221
  const citeHealth = {
211
222
  name: 'cite_health',
@@ -224,16 +235,20 @@ export function buildCiteTools(cfg, fetchImpl) {
224
235
  return [{ type: 'text', text: lines.join('\n') }];
225
236
  },
226
237
  },
227
- async execute() {
238
+ async execute(_rawArgs, exec) {
228
239
  const checks = [];
229
240
  let ok = true;
230
241
  const startedAt = Date.now();
242
+ const signal = executionSignal(exec);
231
243
  try {
232
244
  const fetcher = fetchImpl ?? globalThis.fetch;
233
245
  const response = await fetcher('https://api.crossref.org/works?rows=1&select=DOI', {
234
246
  headers: { 'user-agent': cfg.userAgent },
235
- signal: AbortSignal.timeout(Math.min(cfg.timeoutMs, 15000)),
247
+ signal: signal === undefined
248
+ ? AbortSignal.timeout(Math.min(cfg.timeoutMs, 15000))
249
+ : AbortSignal.any([signal, AbortSignal.timeout(Math.min(cfg.timeoutMs, 15000))]),
236
250
  });
251
+ signal?.throwIfAborted();
237
252
  if (response.ok) {
238
253
  checks.push({ name: 'Crossref API', ok: true, detail: 'HTTP ' + response.status + ',' + (Date.now() - startedAt) + 'ms' });
239
254
  }
@@ -243,6 +258,7 @@ export function buildCiteTools(cfg, fetchImpl) {
243
258
  }
244
259
  }
245
260
  catch (error) {
261
+ signal?.throwIfAborted();
246
262
  ok = false;
247
263
  checks.push({ name: 'Crossref API', ok: false, detail: (error instanceof Error ? error.message : String(error)) + '(网络需要特殊代理时请配置系统代理后重启)' });
248
264
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-cite",
3
- "version": "0.3.0",
4
- "description": "DSH 参考文献工具插件:cite_lookup/cite_format/cite_bibtex/cite_check 四工具,Crossref 元数据查询(含 DOI 精确解析与并发校验),GB/T 7714 / APA / MLA / Chicago 格式化,零运行时依赖。",
3
+ "version": "0.3.2",
4
+ "description": "DSH 参考文献工具插件:cite_lookup/cite_format/cite_bibtex/cite_check/cite_health 五工具(含自检),Crossref 元数据查询(含 DOI 精确解析与并发校验),GB/T 7714 / APA / MLA / Chicago 格式化,零运行时依赖。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",