dsh-quote-annotate-pro 0.2.1 → 0.2.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/CHANGELOG.md +7 -0
- package/README.en.md +18 -2
- package/README.md +40 -10
- package/lib/client.js +45 -46
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
本项目的所有重要变更都记在这里。版本号遵循 [SemVer](https://semver.org/lang/zh-CN/)。
|
|
4
4
|
|
|
5
|
+
## 0.2.2 — 引用界面改为紧凑式
|
|
6
|
+
|
|
7
|
+
- **输入框上方的引用块**:不再显示 `引用#N · 第N轮 · 角色` 这类标签,改为**只显示原文片段**(单行、超出省略);点一下即跳回原文并高亮,且**不再弹出"已跳转"提示**、取消常驻导航条 —— 输入区更干净
|
|
8
|
+
- 输入框内的 chip 标签简化为 `引用1` / `引用2`(唯一、简短)
|
|
9
|
+
- **定位能力不变**:锚点仍写入 `localStorage`(按会话,≤60 条),多轮对话里仍可精确跳回;发送的引用块仍以 `【引用 · 第N轮 · 角色】` 开头(模型侧定位不受影响)
|
|
10
|
+
- 引用块不提供删除按钮:要撤掉引用,请在输入框用 Backspace 删除对应 chip
|
|
11
|
+
|
|
5
12
|
## 0.2.1 — 文档与自动发版
|
|
6
13
|
|
|
7
14
|
- README / README.en:新增 npm 版本、CI、License 徽章;安装章节补上 `dsh plugin add dsh-quote-annotate-pro`(npm)与 `github:` 两种方式
|
package/README.en.md
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
| Capability | Detail |
|
|
17
17
|
| --- | --- |
|
|
18
18
|
| Select & annotate | Select text in the transcript → a bubble appears above the selection → write a comment (optional) → `Enter` |
|
|
19
|
-
| Quote chip | An atomic
|
|
20
|
-
| **Quote
|
|
19
|
+
| Quote chip | An atomic `引用1` chip (short label) lands in the composer; it expands to the full quote block on send |
|
|
20
|
+
| **Quote block** | While a quote is in the composer, a compact block above it shows the **quoted text snippet** (single line, ellipsised). Click it to jump back to the source and highlight it — no visible "jump" affordance, so the composer stays clean |
|
|
21
21
|
| **Turn-accurate context** | The quote block starts with `【引用 · 第3轮 · 你的消息】` followed by `> quoted text`, so the model knows exactly which turn and whose message |
|
|
22
22
|
| Turn-level quoting | Every finished turn gets `引用提问` / `引用回答` buttons that quote the whole question or answer |
|
|
23
23
|
| Anchors survive reload | The `ref → anchor` map is persisted in `localStorage` (per session, 60 entries max), so quotes still jump after a page refresh |
|
|
@@ -80,6 +80,22 @@ npm run diag # local diagnostics sink (http://127.0.0.1:47821)
|
|
|
80
80
|
|
|
81
81
|
`lib/client.js` is a hand-written bundle (no build step) — edit and save; the client HMR reloads it. See [README.md](./README.md) (Chinese) for the diagnostics channel and known limitations.
|
|
82
82
|
|
|
83
|
+
## Releasing (maintainers)
|
|
84
|
+
|
|
85
|
+
Releases go through **npm Trusted Publishing (OIDC)** — no token is stored in the repository and no one-time code is needed:
|
|
86
|
+
|
|
87
|
+
1. Bump `version` in `package.json` and note the change in `CHANGELOG.md`
|
|
88
|
+
2. Commit, tag and push:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
git tag -a v0.2.2 -m "v0.2.2"
|
|
92
|
+
git push origin main --follow-tags
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
3. [`release.yml`](.github/workflows/release.yml) then checks the tag against the version, runs the syntax/manifest checks, and publishes with `--provenance` (SLSA attestation). An already-published version is skipped, so the workflow is safe to re-run.
|
|
96
|
+
|
|
97
|
+
Trusted Publisher configured on npm: `zhao-guang-ai` / `dsh-quote-annotate-pro` / workflow `release.yml` / environment `npm`.
|
|
98
|
+
|
|
83
99
|
## License
|
|
84
100
|
|
|
85
101
|
MIT — see [LICENSE](./LICENSE). Upstream: [`wangwei-wade/dsh-quote-annotate`](https://github.com/wangwei-wade/dsh-quote-annotate) (MIT, © 2026 wangwei-wade).
|
package/README.md
CHANGED
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
| 能力 | 说明 |
|
|
17
17
|
|---|---|
|
|
18
18
|
| 划词批注 | 会话里选中任意文字 → 选区上方浮出气泡(预览 + 「批注」)→ 编辑框里写评语(可留空)→ `Enter` 插入 |
|
|
19
|
-
| 引用 chip | 输入框里出现
|
|
20
|
-
|
|
|
19
|
+
| 引用 chip | 输入框里出现 `引用1` 的原子 chip(短标签);发送时展开为完整引用块 |
|
|
20
|
+
| **引用块(输入框上方)** | 有引用时在输入框上方显示**原文片段**(单行、超出省略);**点一下即跳回原文并高亮** —— 不做任何可见的"跳转"提示,保持输入区干净 |
|
|
21
21
|
| **模型侧精确定位** | 引用块首行是 `【引用 · 第5轮 · 你的消息】`,后接 `> 原文`,让模型明确知道**哪一轮、谁说的** |
|
|
22
22
|
| 回合级引用 | 每条已完成回合尾部有「引用提问 / 引用回答」,整段引用该轮 |
|
|
23
|
-
| 锚点持久化 | `ref → 锚点` 映射写入 `localStorage`(按会话,最多 60
|
|
23
|
+
| 锚点持久化 | `ref → 锚点` 映射写入 `localStorage`(按会话,最多 60 条),**刷新页面后引用块仍能跳回原文** |
|
|
24
24
|
| 中英自适应 | 按钮文案跟随界面语言(沿用上游) |
|
|
25
25
|
|
|
26
26
|
## 与上游的差异
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
|
|
51
51
|
6. **引用块带轮次/角色** —— 上游只发 `> 原文`。→ 首行加 `【引用 · 第N轮 · 角色】`(轮次取自 `data-chat-turn`,角色取自 `data-chat-flow-kind`)。
|
|
52
52
|
|
|
53
|
-
7.
|
|
54
|
-
→ 改为读官方输入状态 `occurrences`(`source === 'quote-ref'
|
|
55
|
-
|
|
53
|
+
7. **输入框上方的引用块 + 锚点持久化** —— 本版本输入框 chip **没有 `data-*` 属性**(只有 CSS 类),上游"点 chip 跳转"不可能成立。
|
|
54
|
+
→ 改为读官方输入状态 `occurrences`(`source === 'quote-ref'`)渲染引用块,只显示**原文片段**(不显示标签/轮次,保持简洁);
|
|
55
|
+
`ref → 锚点` 落 `localStorage`,刷新后仍可跳。(输入框 chip 本体也支持点击跳转:按 `title` = label 反查 ref,不依赖 CSS 类顺序。)
|
|
56
56
|
|
|
57
57
|
## 安装
|
|
58
58
|
|
|
@@ -83,7 +83,8 @@ dsh plugin --profile <profile> add <本仓库绝对路径>
|
|
|
83
83
|
emit('slash/input-insert-reference', { reference, span }) ← 官方会话事件,会话作用域
|
|
84
84
|
│ 输入机 mint 一条 occurrence(source='quote-ref' / ref / label)
|
|
85
85
|
▼
|
|
86
|
-
输入框 chip(官方 Lexical
|
|
86
|
+
输入框 chip(官方 Lexical 装饰节点,标签 `引用N`)
|
|
87
|
+
│ 同时:输入框上方出现「引用块」(显示原文片段,点一下可跳回)
|
|
87
88
|
│ 发送
|
|
88
89
|
▼
|
|
89
90
|
本插件 codec.serialize(ref) → 完整引用块
|
|
@@ -93,7 +94,7 @@ emit('slash/input-insert-reference', { reference, span }) ← 官方会话
|
|
|
93
94
|
▼
|
|
94
95
|
模型收到"哪一轮 + 谁说的 + 原文"
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
跳回原文:引用块/锚点 → [data-chat-anchor-key] 行 → scrollIntoView + 高亮(无可见提示)
|
|
97
98
|
```
|
|
98
99
|
|
|
99
100
|
用到的官方接口:`slots`(`shell.overlay` / `conversation.chat.turnTail` / `conversation.input.dock`)、
|
|
@@ -137,12 +138,41 @@ turnTail:render 回合尾部按钮取文
|
|
|
137
138
|
|
|
138
139
|
不需要时把 `DIAG_ENABLED` 改成 `false` 即可(`console.log('[quote-pro]', …)` 会保留)。
|
|
139
140
|
|
|
141
|
+
## 发版(维护者)
|
|
142
|
+
|
|
143
|
+
发布走 **npm Trusted Publishing(OIDC)**,仓库里不保存任何 token,也不需要动态验证码:
|
|
144
|
+
|
|
145
|
+
1. 改 `package.json` 的 `version`,并在 `CHANGELOG.md` 记录变更
|
|
146
|
+
2. 提交、打标签、推送:
|
|
147
|
+
|
|
148
|
+
```sh
|
|
149
|
+
git tag -a v0.2.2 -m "v0.2.2"
|
|
150
|
+
git push origin main --follow-tags
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
3. GitHub Actions 的 [`release.yml`](.github/workflows/release.yml) 自动执行:
|
|
154
|
+
校验 tag 与版本一致 → 语法与清单自检 → `npm publish --provenance`(带 SLSA 签名)
|
|
155
|
+
4. 该版本已存在时自动跳过,工作流可安全重跑
|
|
156
|
+
|
|
157
|
+
已在 npm 包设置页配置的 Trusted Publisher:
|
|
158
|
+
|
|
159
|
+
| 字段 | 值 |
|
|
160
|
+
|---|---|
|
|
161
|
+
| Organization or user | `zhao-guang-ai` |
|
|
162
|
+
| Repository | `dsh-quote-annotate-pro` |
|
|
163
|
+
| Workflow filename | `release.yml` |
|
|
164
|
+
| Environment name | `npm` |
|
|
165
|
+
|
|
166
|
+
> 注:npm 现在对"绕过 2FA 的 token 直接发布"逐步收紧(账号变更已限制,直接发布 2027-01 起限制),
|
|
167
|
+
> 因此本仓库从一开始就只走 OIDC —— 这是官方推荐方向,也避免了 token 泄漏风险。
|
|
168
|
+
|
|
140
169
|
## 已知限制
|
|
141
170
|
|
|
142
171
|
- **输入框 chip 本体跳转依赖 `title`**:本版本 chip 无 `data-*` 属性,是按渲染出的 `title`(= 插入时写入的 label)反查 ref;
|
|
143
|
-
|
|
172
|
+
chip 标签为 `引用1`、`引用2` 这样的短标签(唯一),因此不再有歧义;上方引用块则始终精确。
|
|
144
173
|
- **跳转要求原文行在当前页面**:目标消息未加载(历史未翻到)时只能给出提示,不做自动翻页加载。
|
|
145
|
-
-
|
|
174
|
+
- **引用块在会话切换后重建**:锚点按会话存储;切到别的会话不会串。
|
|
175
|
+
- **引用块不提供"删除"按钮**:要撤掉引用,请在输入框里用 Backspace 删除对应 chip(官方输入的既有行为)。
|
|
146
176
|
- **回合尾部按钮在有交付文件的回合也会显示**:上游会隐藏、让位给官方「已生成文件」chips。
|
|
147
177
|
- **评语以纯文本追加在 chip 之后**:编辑 chip 文本请用 Backspace 删除。
|
|
148
178
|
|
package/lib/client.js
CHANGED
|
@@ -397,8 +397,8 @@ window.__ModuleLoader__.load({
|
|
|
397
397
|
notifyTimer = setTimeout(() => toastStore.hide(), 2600)
|
|
398
398
|
} catch (e) { /* ignore */ }
|
|
399
399
|
}
|
|
400
|
-
/** 跳到某个锚点行并高亮它。 */
|
|
401
|
-
function jumpToAnchorKey(anchorKey, pos) {
|
|
400
|
+
/** 跳到某个锚点行并高亮它。opts.silent=true 时不弹成功提示(高亮本身就是反馈)。 */
|
|
401
|
+
function jumpToAnchorKey(anchorKey, pos, opts) {
|
|
402
402
|
if (!anchorKey) return false
|
|
403
403
|
let row = null
|
|
404
404
|
for (const r of document.querySelectorAll('[data-chat-anchor-key]')) {
|
|
@@ -412,12 +412,12 @@ window.__ModuleLoader__.load({
|
|
|
412
412
|
row.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
|
413
413
|
row.classList.add('dshq-flash')
|
|
414
414
|
setTimeout(() => row.classList.remove('dshq-flash'), 1600)
|
|
415
|
-
notify('已跳转到原文 ✓', pos, true)
|
|
415
|
+
if (!(opts && opts.silent)) notify('已跳转到原文 ✓', pos, true)
|
|
416
416
|
report('jump:ok', { anchorKey })
|
|
417
417
|
return true
|
|
418
418
|
}
|
|
419
419
|
/** 按 ref 跳回原文:优先用锚点键,退化到按引用正文反查。 */
|
|
420
|
-
function jumpToRef(ref, pos) {
|
|
420
|
+
function jumpToRef(ref, pos, opts) {
|
|
421
421
|
const info = refTargets.get(ref)
|
|
422
422
|
if (!info) {
|
|
423
423
|
notify('跳转失败:找不到该引用(可能已发送或已删除)', pos, false)
|
|
@@ -442,7 +442,7 @@ window.__ModuleLoader__.load({
|
|
|
442
442
|
notify('跳转失败:没有定位键(选中时未识别到消息行)', pos, false)
|
|
443
443
|
return false
|
|
444
444
|
}
|
|
445
|
-
return jumpToAnchorKey(anchorKey, pos)
|
|
445
|
+
return jumpToAnchorKey(anchorKey, pos, opts)
|
|
446
446
|
}
|
|
447
447
|
|
|
448
448
|
/**
|
|
@@ -760,8 +760,8 @@ window.__ModuleLoader__.load({
|
|
|
760
760
|
const base = (st.draft || '').replace(/[\s\u00A0]+$/, '')
|
|
761
761
|
const commentTail = c === '' ? '' : '\n\n' + c
|
|
762
762
|
const ref = 'q' + REF_RUN + (++refSeq)
|
|
763
|
-
|
|
764
|
-
const chipLabel = '
|
|
763
|
+
// 输入框里的 chip 文案只要短、可区分即可(轮次/角色写在发送后的引用块里,不在输入框展示)
|
|
764
|
+
const chipLabel = '引用' + refSeq
|
|
765
765
|
refTargets.set(ref, { sessionId, anchorKey: item.anchorKey || null, body: quoteBody, label: chipLabel })
|
|
766
766
|
refCodecs.set(ref, { body: quoteBody, clipboardText: quoteBody })
|
|
767
767
|
saveRef(sessionId, ref, { anchorKey: item.anchorKey || null, body: quoteBody, label: chipLabel })
|
|
@@ -794,7 +794,7 @@ window.__ModuleLoader__.load({
|
|
|
794
794
|
report('insert:chip', { ref, anchorKey: item.anchorKey || null, header, chars: item.text.length })
|
|
795
795
|
setStatus({
|
|
796
796
|
ok: true,
|
|
797
|
-
text: '已插入引用
|
|
797
|
+
text: '已插入引用 ✓' + (item.anchorKey ? '(输入框上方的引用块可点回原文)' : '(未定位到消息行,可能无法跳回)'),
|
|
798
798
|
})
|
|
799
799
|
editorStore.close()
|
|
800
800
|
return
|
|
@@ -869,42 +869,39 @@ window.__ModuleLoader__.load({
|
|
|
869
869
|
}
|
|
870
870
|
|
|
871
871
|
// ============ 样式(主题令牌自适应明暗) ============
|
|
872
|
-
// ============
|
|
872
|
+
// ============ 组件六:输入框上方的引用块(紧凑样式;点击可跳回原文,但不显式提示) ============
|
|
873
873
|
// 本版本输入框里的引用 chip 没有 data 属性(只有 CSS 类),DOM 无法反查 occurrence;
|
|
874
|
-
//
|
|
875
|
-
//
|
|
876
|
-
|
|
874
|
+
// 因此这里直接读官方输入状态的 occurrences(source === 'quote-ref')来渲染。
|
|
875
|
+
// 展示的是**原文片段本身**(不再显示「引用#N · 第N轮 · 角色」这类标签),保持输入区干净;
|
|
876
|
+
// 定位能力保留:点一下就跳回原文并高亮,只是不做任何可见的"跳转"提示。
|
|
877
|
+
const SNIPPET_MAX = 90
|
|
878
|
+
const snippetOf = (body) => {
|
|
879
|
+
const raw = String(body || '')
|
|
880
|
+
.split('\n')
|
|
881
|
+
.filter((line) => line.trim() !== '' && !/^【引用/.test(line.trim()))
|
|
882
|
+
.map((line) => line.replace(/^>\s?/, ''))
|
|
883
|
+
.join(' ')
|
|
884
|
+
.replace(/\s+/g, ' ')
|
|
885
|
+
.trim()
|
|
886
|
+
return raw.length > SNIPPET_MAX ? raw.slice(0, SNIPPET_MAX) + '…' : raw
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const QuoteBlock = ({ item }) => {
|
|
877
890
|
const onJump = (e) => {
|
|
878
891
|
try {
|
|
879
892
|
const r = e.currentTarget.getBoundingClientRect()
|
|
880
893
|
report('dock:click', { ref: String(item.ref), anchorKey: item.anchorKey || null })
|
|
881
|
-
jumpToRef(item.ref, { left: r.left + 16, top: r.top })
|
|
882
|
-
} catch (err) { /* ignore */ }
|
|
883
|
-
}
|
|
884
|
-
const onOver = (e) => {
|
|
885
|
-
try {
|
|
886
|
-
const info = refTargets.get(item.ref)
|
|
887
|
-
if (!info || !info.body) return
|
|
888
|
-
const r = e.currentTarget.getBoundingClientRect()
|
|
889
|
-
tooltipStore.show({
|
|
890
|
-
text: String(info.body).replace(/^> ?/gm, '').trim(),
|
|
891
|
-
label: info.label || '引用',
|
|
892
|
-
rect: { left: r.left, top: r.top, right: r.right, bottom: r.bottom },
|
|
893
|
-
})
|
|
894
|
+
jumpToRef(item.ref, { left: r.left + 16, top: r.top }, { silent: true })
|
|
894
895
|
} catch (err) { /* ignore */ }
|
|
895
896
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
type: 'button',
|
|
899
|
-
className: 'dshq-dock-chip',
|
|
897
|
+
return React.createElement('div', {
|
|
898
|
+
className: 'dshq-quote-block',
|
|
900
899
|
onClick: onJump,
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
React.createElement('span', { className: 'dshq-dock-chip-label' }, item.label || ('引用#' + item.ref)),
|
|
907
|
-
)
|
|
900
|
+
onKeyDown: (e) => { if (e.key === 'Enter' || e.key === ' ') onJump(e) },
|
|
901
|
+
role: 'button',
|
|
902
|
+
tabIndex: 0,
|
|
903
|
+
title: '点击跳回原文',
|
|
904
|
+
}, React.createElement('span', { className: 'dshq-quote-block-text' }, item.snippet || '引用'))
|
|
908
905
|
}
|
|
909
906
|
|
|
910
907
|
const QuoteDock = (props) => {
|
|
@@ -930,7 +927,11 @@ window.__ModuleLoader__.load({
|
|
|
930
927
|
for (const occ of occurrences) {
|
|
931
928
|
if (!occ || occ.source !== 'quote-ref') continue
|
|
932
929
|
const info = refTargets.get(occ.ref)
|
|
933
|
-
next.push({
|
|
930
|
+
next.push({
|
|
931
|
+
ref: occ.ref,
|
|
932
|
+
anchorKey: (info && info.anchorKey) || null,
|
|
933
|
+
snippet: snippetOf(info && info.body),
|
|
934
|
+
})
|
|
934
935
|
}
|
|
935
936
|
setRows(next)
|
|
936
937
|
if (next.length > 0) report('dock:rows', { count: next.length })
|
|
@@ -949,8 +950,8 @@ window.__ModuleLoader__.load({
|
|
|
949
950
|
}
|
|
950
951
|
}, [sessionId])
|
|
951
952
|
if (!sessionId || rows.length === 0) return null
|
|
952
|
-
const children = [
|
|
953
|
-
for (const row of rows) children.push(React.createElement(
|
|
953
|
+
const children = []
|
|
954
|
+
for (const row of rows) children.push(React.createElement(QuoteBlock, { key: row.ref, item: row }))
|
|
954
955
|
return React.createElement('div', { className: 'dshq-dock' }, children)
|
|
955
956
|
}
|
|
956
957
|
|
|
@@ -985,13 +986,11 @@ window.__ModuleLoader__.load({
|
|
|
985
986
|
/* 根因:textarea(透明文字、absolute、inset:0)渲染在 backdrop 之后,整层盖住 chip。
|
|
986
987
|
给 chip 提升 z-index 使其浮到 textarea 之上,点击才能落到 chip 上。 */
|
|
987
988
|
[data-occurrence]{pointer-events:auto;cursor:pointer;position:relative;z-index:1}
|
|
988
|
-
/*
|
|
989
|
-
.dshq-dock{display:flex;
|
|
990
|
-
.dshq-
|
|
991
|
-
.dshq-
|
|
992
|
-
.dshq-
|
|
993
|
-
.dshq-dock-chip-icon{opacity:.55}
|
|
994
|
-
.dshq-dock-chip-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
989
|
+
/* 引用块:输入框上方,紧凑单行(点一下跳回原文,但不显示任何"跳转"提示) */
|
|
990
|
+
.dshq-dock{display:flex;flex-direction:column;gap:4px;padding:0 2px;min-width:0}
|
|
991
|
+
.dshq-quote-block{display:flex;align-items:center;min-width:0;max-width:100%;box-sizing:border-box;padding:5px 10px;border-radius:8px;background:var(--dsw-alias-bg-layer-2);background:color-mix(in oklab,var(--dsw-alias-brand-primary) 8%,var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;cursor:pointer;transition:background .12s ease,color .12s ease}
|
|
992
|
+
.dshq-quote-block:hover{color:var(--dsw-alias-label-primary)}
|
|
993
|
+
.dshq-quote-block-text{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
995
994
|
`
|
|
996
995
|
|
|
997
996
|
// ============ 插件主体 ============
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-quote-annotate-pro",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "选区引用与锚点批注(增强 fork):划词 → 批注 →
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "选区引用与锚点批注(增强 fork):划词 → 批注 → 带精确锚点的引用;输入框上方只显示原文片段,点一下跳回原文;发送时把轮次/角色写入引用块,让模型精确知道你指的是哪一处。",
|
|
5
5
|
"keywords": ["dsh", "dsh-plugin", "deepseek-harness", "quote", "annotate", "selection", "reference", "anchor"],
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|