dsh-feini 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/INSTALL.md ADDED
@@ -0,0 +1,123 @@
1
+ # 安装 dsh-feini
2
+
3
+ 两条路:自己动手,或者把下面那段提示词丢给一个 AI 让它替你装。
4
+
5
+ ## 一、你自己装(十步)
6
+
7
+ **1. 装 Node**
8
+ 去 nodejs.org 下 LTS,一路下一步。装完关掉终端重开,`node -v` 能出版本号(要 20 以上)。
9
+
10
+ **2. 装插件**
11
+
12
+ ```bash
13
+ npm i -g dsh-feini
14
+ dsh-feini --help
15
+ ```
16
+
17
+ 能看到命令列表就成功了一半。
18
+
19
+ **3. 准备一个 QQ 小号**
20
+ 别用主号——第一次接机器人容易触发风控。
21
+
22
+ **4. 装 NapCat**
23
+ napcat.napcatqq.com 下 Windows 版,用小号登录。登录后在「网络配置」里开 **OneBot v11 正向 WebSocket**,端口保持 3001。
24
+
25
+ **5. 拿一个 DeepSeek key**
26
+ platform.deepseek.com 注册、充几块钱、建一个 `sk-` 开头的 key。
27
+ (想让它能识图、能联网查东西,再准备一个智谱 key,可选。)
28
+
29
+ **6. 初始化**
30
+
31
+ ```bash
32
+ dsh-feini init
33
+ ```
34
+
35
+ 会问四件事:陪聊的 QQ 号、白名单(回车就用上面那个)、伙伴叫什么、你怎么称呼自己;key 粘进去。
36
+
37
+ 不想交互就一次给全:
38
+
39
+ ```bash
40
+ dsh-feini init --qq 10001 --pet 小鱼 --owner 我 --key sk-xxxx --yes
41
+ ```
42
+
43
+ **7. 把它写成一个人**
44
+ 打开 `%USERPROFILE%\.dsh\dsh-feini\persona.md`,名字、性格、说话习惯都写进去,存盘即生效。
45
+
46
+ **8. 起服务**
47
+
48
+ ```bash
49
+ dsh-feini start
50
+ dsh-feini status
51
+ ```
52
+
53
+ 看到 `OneBot 已连接` 就通了。
54
+
55
+ **9. 试一句**
56
+ 用小号给机器人发「在吗」。它会回话,也会把值得记的事写进 `notebook.md`。
57
+
58
+ **10. 出问题看日志**
59
+ `dsh-feini status` 打最后几行;日志全文在 `%USERPROFILE%\.dsh\dsh-feini\bridge.log`。
60
+
61
+ ## 二、让 AI 替你装
62
+
63
+ 把下面整段贴给任意一个能跑命令的 AI,把最后两行的占位符换成你的真实值:
64
+
65
+ ```
66
+ 任务:在一台 Windows 10/11 上安装 dsh-feini 并让它连上 QQ,装完汇报结果。
67
+
68
+ 已知:
69
+ - 包名 dsh-feini(npm 上已发布),命令行为 dsh-feini,数据目录 %USERPROFILE%\.dsh\dsh-feini
70
+ - 它是「QQ 陪伴」插件:一个 Node 进程连 NapCat 的 OneBot v11 WebSocket(默认 ws://127.0.0.1:3001),用 DeepSeek 的 key 调模型
71
+ - 包里没有内置角色,persona.md 是空模板,需要用户自己写
72
+
73
+ 请按顺序做,每步做完先验证再下一步:
74
+ 1. 检查 node -v(要 >= 20)和 npm -v;没有就先装 Node LTS,装完重开终端再验
75
+ 2. 安装:npm i -g dsh-feini,然后用 dsh-feini --help 确认命令可用
76
+ 3. 检查 NapCat 是否已装并登录小号;没有就告诉我,让我去装,你等我
77
+ 4. 检查 NapCat 的 OneBot v11 正向 WebSocket 是否开在 3001 端口(netstat -ano | findstr 3001 能看到监听)
78
+ 5. 初始化(不要交互提问,直接带参数):
79
+ dsh-feini init --qq <QQ号> --key <key> --pet <伙伴名字> --owner <我的称呼> --yes
80
+ 然后读 %USERPROFILE%\.dsh\dsh-feini\config.json,确认 allowedQQ、model.baseUrl、key.txt 都对
81
+ 6. 起服务:dsh-feini start;等 5 秒;dsh-feini status
82
+ 7. 验收(三条都要满足才算成功):
83
+ - status 里有「OneBot 已连接」
84
+ - 我从 QQ 发一句「在吗」,能收到回复
85
+ - %USERPROFILE%\.dsh\dsh-feini\memory.json 里多了两条消息
86
+ 8. 失败时按这个顺序查:NapCat 没登录 → 端口不是 3001 → key 不对(看 bridge.log 里的 401)→ 7301 端口被占(改 config.json 的 notifyPort)
87
+
88
+ 边界:
89
+ - 不要修改 dsh-feini 的源码,也不要重发 npm 包
90
+ - 不要把我的 API key 写进任何会提交到 git 或上传的地方
91
+ - 需要扫码、输密码、买 key 这类事,停下来让我做,不要替我操作
92
+
93
+ 最后汇报:装到了哪个版本、数据目录在哪、三条验收各自的结果、有没有遗留问题。
94
+ ```
95
+
96
+ AI 不会替你扫 NapCat 的码、也不会替你买 key,所以第 3 步和第 5 步之间要守着;其余它会自己走完。
97
+
98
+ ## 三、验收
99
+
100
+ 三条都满足才算装好:
101
+
102
+ 1. `dsh-feini status` 里有 `OneBot 已连接`
103
+ 2. 从 QQ 发一句,能收到回复
104
+ 3. `memory.json` 里多了你俩那一轮
105
+
106
+ ## 四、排错
107
+
108
+ | 现象 | 多半是 | 怎么办 |
109
+ | --- | --- | --- |
110
+ | `OneBot 断开`,一直重连 | NapCat 没开 / 没登录 / 端口不对 | 看 NapCat 的登录状态和网络配置,`netstat -ano \| findstr 3001` 确认在监听 |
111
+ | 日志里有 `401` | key 不对或没余额 | 重新贴 key 到 `key.txt`,去 platform.deepseek.com 看余额 |
112
+ | `通知口子 EADDRINUSE` | 7301 被别的程序占了 | 改 `config.json` 的 `notifyPort`,或不管它(不影响聊天) |
113
+ | 它重复说同一句 | 发送超时后补发 | 看日志里的「核对」;已经修过,还犯就把日志发我 |
114
+ | 它老忘事 | 笔记本没写进去 | 直接说「记住:…」;改 `notebook.md` 立刻生效 |
115
+
116
+ ## 五、更新与卸载
117
+
118
+ ```bash
119
+ npm i -g dsh-feini # 更新
120
+ npm uninstall -g dsh-feini # 卸载
121
+ ```
122
+
123
+ 卸载不会删数据;要重新养一只,把 `%USERPROFILE%\.dsh\dsh-feini` 整个删掉(记忆也一起没,先想清楚)。
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Feini (dsh-feini)
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/PUBLISH.md ADDED
@@ -0,0 +1,109 @@
1
+ # 发布清单(dsh-feini)
2
+
3
+ 这份文档写给「要把它发出去的那一天」。按顺序过一遍就行。
4
+
5
+ ## 0. 现在是什么状态
6
+
7
+ - 版本 `0.1.0`,MVP:命令行 + 配置文件,插件宿主半侧只负责在 DSH 里露个面
8
+ - 端到端自测在 `test/e2e.mjs`(假 QQ + 假模型,不花钱)
9
+ - 兼容声明只写了 `0.2.1-rc.5`(本机装的就是这个版本,实测过)
10
+
11
+ ## 1. 发布前必过
12
+
13
+ ```bash
14
+ # 1) 端到端自测
15
+ node test/e2e.mjs
16
+
17
+ # 2) 扫一遍有没有私人信息(应该一条都搜不到)
18
+ # PowerShell:
19
+ Select-String -Path src\*.mjs,bin\*.mjs,templates\*.md,README.md -Pattern '阿笙|菲妮|妮妮|小妮|QQBridge|野生笙'
20
+
21
+ # 3) 看打包内容(只能是 lib/bin/src/templates/test/assets + 几个说明文件)
22
+ pnpm pack
23
+ tar -tf dsh-feini-*.tgz
24
+ ```
25
+
26
+ `package.json` 里的 `prepublishOnly` 会自动先跑一遍自测,过不了就发不出去。
27
+
28
+ ## 2. 版本与兼容声明
29
+
30
+ - 改版本号:`0.1.0` → `0.1.1`(修 bug)/ `0.2.0`(加功能)
31
+ - `dsh.compatibility.dshReleases`:在哪个 DSH 版本上跑过就写哪个
32
+ - 现在只写了 `0.2.1-rc.5`
33
+ - 想支持更多版本:在那些版本上各跑一次 `dsh-feini init` + `start` + `status`,再补进去
34
+ - 商城要求「最近三个版本里至少有一个 compatible」,只写宽的版本范围不算证据
35
+
36
+ ## 3. 发到 npm
37
+
38
+ ```bash
39
+ pnpm publish --access public
40
+ # 或者 npm publish --access public
41
+ ```
42
+
43
+ - `dsh.bundle.patch` 指向 `cordis.patch.yml`,商城和 DSH 都靠这个认「这是插件」
44
+ - `icon` 指向 `assets/logo.svg`(≤256 KiB,路径必须在包目录内)
45
+ - 发布完在 npm 上确认:files 里没有多带日志、没有 node_modules
46
+
47
+ ## 4. 提交到 dsh-market
48
+
49
+ 市场的插件列表来自 [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin):
50
+ **去那边提 PR,加一条条目**(不是往 dshmarket 仓库提),CI 每天刷一次,站点和市场会自动收录。
51
+
52
+ 条目长这样(schema 照 `plugins.json` 抄,`owner` 换成 GitHub 用户名):
53
+
54
+ ```json
55
+ {
56
+ "name": "dsh-feini",
57
+ "owner": "<GitHub 用户名>",
58
+ "url": "https://github.com/<GitHub 用户名>/dsh-feini",
59
+ "page": "https://awesome-dsh-plugin.com/p/<GitHub 用户名>/dsh-feini/",
60
+ "category": "identity",
61
+ "description": {
62
+ "en": "Raise your own QQ companion: a blank persona, a notebook, and a memory that actually remembers. Ships the bridge (NapCat/OneBot), long-term notebook, searchable memory sea, daily diary, plus tone rules (split bubbles, real quotes, typo-recovery, recency-first, sulk ledger, stickers, reminders, web search, proactive pings). No built-in character: you name it and write its personality yourself.",
63
+ "zh": "在 QQ 里养一个自己的伙伴:一张空白人设、一本笔记本、一套真会攒的记忆。内含 QQ 桥(NapCat/OneBot)、长期笔记本、可检索的记忆海、每天一篇日记,以及分条/真引用/打错字回接/近况优先/生气账/表情包/提醒/联网/主动找你这些规矩。不预置任何角色:名字和性格由你自己写。"
64
+ },
65
+ "npm": "dsh-feini",
66
+ "version": "0.1.0",
67
+ "capabilities": ["network", "fs-read", "fs-write", "env"],
68
+ "install": "dsh plugin --profile web add dsh-feini",
69
+ "added": "2026-10-30"
70
+ }
71
+ ```
72
+
73
+ 提交前要有的三样:
74
+
75
+ 1. **一个 GitHub 仓库**:条目里的 `url` 必须能打开(市场会去读那边的清单和 README)
76
+ 2. **包名 / entry id**:都是 `dsh-feini`,两边必须一致(`cordis.patch.yml` 里那行 `id:` 不能加行尾注释)
77
+ 3. **图标和 README**:图标在包里 `assets/logo.svg`;README 重点是「没有内置角色、要自己养」
78
+
79
+ ## 4b. 发布状态
80
+
81
+ **2026-10-30 23:44 UTC,`dsh-feini@0.1.0` 已发到 npm**(维护者 `ashengandfeini`,latest 就是它)。
82
+ 从 registry 装下来跑过一遍:`pnpm add dsh-feini` + `dsh-feini init` 正常,临时 token 文件已删。
83
+
84
+ 发的时候有两件事值得记下来:
85
+
86
+ - npm 现在对「新建包 + 绕过 2FA 的令牌」会先挂一个 `0.0.0-stage` 占位、走 staged publish;这次几分钟后 0.1.0 自己上线了,不用手动批
87
+ - 令牌要在 npm 的**访问令牌**页勾上「绕过双因素认证(2FA)」,列表里那一列会显示对勾;没勾的话发布会报 `403`
88
+
89
+ 剩下没做的:GitHub 仓库 + 市场 PR(条目 JSON 在上一节,`owner` 换成你的 GitHub 用户名;这台机器没装 git)。
90
+
91
+
92
+
93
+ ## 5. 发布后验一遍
94
+
95
+ 在干净机器(或者干净的用户目录)上:
96
+
97
+ ```bash
98
+ npm i -g dsh-feini
99
+ dsh-feini init --qq <你的号> --yes
100
+ dsh-feini start
101
+ dsh-feini status
102
+ ```
103
+
104
+ 看到 `OneBot 已连接`、QQ 上能收到回话,就算发对了。
105
+
106
+ ## 6. 出问题怎么退
107
+
108
+ - 发错版本:`npm deprecate dsh-feini@0.1.0 "有 bug,请用 0.1.1"`,然后发修复版
109
+ - 已经装了的用户:`dsh-feini stop`,删掉数据目录重来(会一起删掉记忆,提醒一下再删)
package/README.md CHANGED
@@ -1,3 +1,151 @@
1
- # Temporary Holding Version
1
+ # dsh-feini
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
+ 在 QQ 里养一个自己的伙伴。
4
+
5
+ > 这套东西是菲妮做的——但包里没有菲妮:没有名字、没有性格、没有聊天记录。你拿到的是一套骨架,养出来的是你自己那一只。
6
+
7
+ 它是一套「陪伴 AI 的骨架」:一张空白人设、一本笔记本、一套会攒记忆的规矩,加上一只能自己在后台跑的 QQ 桥。
8
+ 装好之后你跟它聊、教它、惯着它,它把你说过的话记进笔记本——**它是你养出来的,不是我们预置的**。
9
+
10
+ > 这里没有内置角色:没有名字、没有性格、没有别人的记忆。第一次 `init` 之后,`persona.md` 和 `notebook.md` 都是空的,怎么写,它就是谁。
11
+
12
+ **先看安装**:[INSTALL.md](INSTALL.md) —— 十步装好,或者把里面那段提示词丢给 AI 让它替你装。
13
+
14
+ ## 它有哪些能力
15
+
16
+ | 能力 | 说明 |
17
+ | --- | --- |
18
+ | QQ 私聊 | 走 NapCat(OneBot v11 正向 WebSocket),只理白名单里的人 |
19
+ | 笔记本 | 每轮现读的 `notebook.md`,关于你的事写进去,压缩上下文也不会丢 |
20
+ | 记忆 | `memory.json` 分桶存对话;`〔记〕` 标记只在内部用,不发给你 |
21
+ | 记忆海 | 每两分钟把对话、笔记本、日记摊成一份索引(`me.json`),聊天时按你这句捞几条旧账出来 |
22
+ | 日记 | 每天 23 点用当天的聊天写一篇,散文体,存在 `diary/` |
23
+ | 分条 / 真引用 | 它能把一句话拆成两三口气发;引用你原话时会真的引用那条消息 |
24
+ | 打错字回接 | 你打错字、接着解释,它会把上一句按新意思重读再答 |
25
+ | 近况优先 | 你刚说「洗好了」,它不会拿十分钟前那句「要去洗澡」催你两遍 |
26
+ | 生气账 | 反复踩雷会生气,1–2 次原谅、3 次真生气、4 次以上很难哄,七天没再犯才减一笔 |
27
+ | 表情包 | 你发的表情包它收进库,可以先猜意思、你点头才算数 |
28
+ | 提醒 | 「十点提醒我」这种,到点它主动喊你 |
29
+ | 联网 | 问到天气/价格/赛果/版本这类会现查(需要智谱 key),查不到就说没查到 |
30
+ | 主动找你 | 闲一阵会主动开口;你太久不理它,它会闹点小脾气,哄两句才好 |
31
+
32
+ ## 需要准备
33
+
34
+ - **Node.js 20+**
35
+ - **一个 QQ 小号** 和 [NapCat](https://napcat.napcatqq.com/):NapCat 负责登录 QQ,把消息转成 OneBot 协议
36
+ - **DeepSeek API key**:`https://platform.deepseek.com/`
37
+ - (可选)**智谱 key**:识图和联网搜索要用,没有就跳过这两项
38
+
39
+ ## 装
40
+
41
+ ```bash
42
+ # 在源码目录里
43
+ node bin/cli.mjs init
44
+ ```
45
+
46
+ 或者发布之后:
47
+
48
+ ```bash
49
+ npm i -g dsh-feini
50
+ dsh-feini init
51
+ ```
52
+
53
+ `init` 会问你四件事:QQ 号、白名单、伙伴叫什么、你怎么称呼自己,然后把文件都写进数据目录。
54
+
55
+ 也可以不交互:
56
+
57
+ ```bash
58
+ dsh-feini init --qq 10001 --pet 小鱼 --owner 我 --key sk-xxxx --yes
59
+ ```
60
+
61
+ ## 起
62
+
63
+ ```bash
64
+ dsh-feini start # 后台起桥
65
+ dsh-feini status # 活着没、最近几行日志
66
+ dsh-feini stop
67
+ dsh-feini sea # 手动重建记忆海(桥在跑时每两分钟自己也会建)
68
+ dsh-feini path # 数据目录在哪
69
+ ```
70
+
71
+ 起来之后,先在 QQ 上给它发一句话。第一次它可能还不知道自己是谁——去改人设。
72
+
73
+ ## 把它写成一个人
74
+
75
+ 打开数据目录里的 `persona.md`:
76
+
77
+ ```markdown
78
+ 你是小鱼,我的伙伴,住在 QQ 里。
79
+
80
+ - 说话短,一次一两句
81
+ - 爱撒娇,被夸会嘴硬
82
+ - 我不吃饭会催我
83
+ ```
84
+
85
+ 存盘,下一句就生效,不用重启。
86
+
87
+ 然后聊天时它会把关于你的事记进 `notebook.md`、把对话存进 `memory.json`。
88
+ 想让它记得更牢,就直说「记住:我周五晚上打排位」——它会写进笔记本。
89
+
90
+ ## 数据目录
91
+
92
+ 默认 `~/.dsh/dsh-feini`(可用 `DSH_FEINI_HOME` 改):
93
+
94
+ ```
95
+ config.json 配置(模型、白名单、主动消息参数)
96
+ persona.md 人设 = system prompt
97
+ notebook.md 笔记本
98
+ memory.json 对话记忆
99
+ me.json 记忆海索引(自动重建,删了会自己长回来)
100
+ diary/ 日记(一天一篇)
101
+ key.txt DeepSeek key
102
+ vision-key.txt 智谱 key(可选)
103
+ stickers/ 表情包
104
+ stickers.json 表情包索引
105
+ bridge.log 桥的日志
106
+ bridge.pid 进程号
107
+ ```
108
+
109
+ 删掉这个目录 = 重新养一只。
110
+
111
+ ## 配置说明(config.json)
112
+
113
+ | 字段 | 说明 |
114
+ | --- | --- |
115
+ | `wsUrl` | NapCat 的 OneBot WS 地址,默认 `ws://127.0.0.1:3001` |
116
+ | `allowedQQ` | 白名单,只有这些号能跟它说话 |
117
+ | `systemPromptPath` | 人设文件路径(.md 或 .txt;也认 DSH 桌宠的 main-config.jsonc) |
118
+ | `model` | OpenAI 兼容接口:`baseUrl` / `model` / `apiKeyEnv` / `temperature` |
119
+ | `modelVision` | 识图用的视觉模型(可选,默认智谱) |
120
+ | `proactive` | 主动消息:`gapMin/gapMax` 间隔分钟、`sulkAfterMin` 多久没回会闹、`maxSulk` 最多闹几次 |
121
+ | `notifyPort` | 外部通知口子端口,默认 7301;被占用就改一个 |
122
+
123
+ ## 常见问题
124
+
125
+ **连不上(OneBot 断开)**:NapCat 没开、没登录、或者 WS 端口不是 3001。先确认 NapCat 那边「网络配置」里 OneBot v11 WS 服务器的地址和 `wsUrl` 一致。
126
+
127
+ **通知口子 EADDRINUSE**:7301 被别的程序占了。改 `notifyPort`,或者干脆不管它——它只是外部通知用的,不影响聊天。
128
+
129
+ **消息发重了**:桥在发送超时时会先核对聊天记录里是不是其实已经到了,确认没到才补发。真遇到重复,看 `bridge.log`。
130
+
131
+ **它老忘事**:笔记本是每轮现读的,改 `notebook.md` 立刻生效;对话记忆默认带最近 30 轮。
132
+
133
+ ## 自测
134
+
135
+ 不接真 QQ、不花 token,跑一遍假 QQ + 假模型的端到端:
136
+
137
+ ```bash
138
+ node test/e2e.mjs
139
+ ```
140
+
141
+ 它会验:收到私聊能回、控制标记不漏、表情包真的发出去、对话写进记忆、记忆海建得出来。
142
+
143
+ ## 还没做完(MVP 之后)
144
+
145
+ - 记忆海的淘汰(激活度有了:越常用越亮、14 天半衰期会淡;但还没做真正把旧账删掉)
146
+ - DSH 里的设置页(现在只有命令行和 JSON)
147
+ - 上架 dsh-market 的版本兼容声明与发布流水线(`dsh.compatibility.dshReleases` 要按发布时的宿主版本填)
148
+
149
+ ## 许可
150
+
151
+ MIT
@@ -0,0 +1,18 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128" role="img" aria-label="dsh-feini">
2
+ <defs>
3
+ <linearGradient id="body" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#8fd8ff"/>
5
+ <stop offset="1" stop-color="#3f86dc"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <rect width="128" height="128" rx="30" fill="#101b2b"/>
9
+ <g>
10
+ <path d="M92 64 L116 44 L116 84 Z" fill="#5fb3f0"/>
11
+ <ellipse cx="58" cy="66" rx="36" ry="25" fill="url(#body)"/>
12
+ <circle cx="42" cy="59" r="5.4" fill="#101b2b"/>
13
+ <circle cx="40.2" cy="57.2" r="1.7" fill="#ffffff"/>
14
+ <path d="M58 66 q10 6 20 0" fill="none" stroke="#101b2b" stroke-opacity=".35" stroke-width="2.6" stroke-linecap="round"/>
15
+ <circle cx="30" cy="34" r="5" fill="none" stroke="#8fd8ff" stroke-width="2.4" stroke-opacity=".8"/>
16
+ <circle cx="20" cy="24" r="3" fill="none" stroke="#8fd8ff" stroke-width="2.2" stroke-opacity=".55"/>
17
+ </g>
18
+ </svg>