bereach-openclaw 0.3.5 → 1.3.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/README.md +19 -19
- package/__tests__/helpers.test.ts +72 -27
- package/openclaw.plugin.json +3 -3
- package/package.json +2 -2
- package/skills/bereach/SKILL.md +181 -27
- package/skills/bereach/openclaw-optimization.md +8 -12
- package/skills/bereach/sdk-reference.md +1311 -134
- package/skills/bereach/sub/lead-gen.md +398 -0
- package/skills/bereach/sub/lead-magnet.md +152 -40
- package/skills/bereach/sub/outreach.md +333 -0
- package/src/commands/index.ts +6 -4
- package/src/index.ts +1 -1
- package/src/lead-magnet/helpers.ts +16 -11
- package/src/tools/definitions.ts +576 -16
- package/src/tools/index.ts +67 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# BeReach — OpenClaw Plugin
|
|
2
2
|
|
|
3
|
-
LinkedIn outreach automation via [BeReach](https://berea.ch). Registers
|
|
3
|
+
LinkedIn outreach automation via [BeReach](https://berea.ch). Registers 60+ in-process tools and instant status commands.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -16,13 +16,13 @@ openclaw plugins install bereach-openclaw
|
|
|
16
16
|
|
|
17
17
|
## Upgrade
|
|
18
18
|
|
|
19
|
-
`openclaw plugins update bereach` can leave `node_modules` and `extensions/` out of sync (version mismatch → trim/crash errors). **Use uninstall + reinstall** instead.
|
|
19
|
+
`openclaw plugins update bereach-openclaw` can leave `node_modules` and `extensions/` out of sync (version mismatch → trim/crash errors). **Use uninstall + reinstall** instead.
|
|
20
20
|
|
|
21
|
-
**Before upgrading:** note your `BEREACH_API_KEY` — uninstall may remove `plugins.entries.bereach`.
|
|
21
|
+
**Before upgrading:** note your `BEREACH_API_KEY` — uninstall may remove `plugins.entries.bereach-openclaw`.
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
# 1. Uninstall
|
|
25
|
-
openclaw plugins uninstall bereach
|
|
25
|
+
openclaw plugins uninstall bereach-openclaw
|
|
26
26
|
|
|
27
27
|
# 2. Reinstall latest
|
|
28
28
|
openclaw plugins install bereach-openclaw
|
|
@@ -35,7 +35,7 @@ openclaw plugins install bereach-openclaw
|
|
|
35
35
|
Verify versions match:
|
|
36
36
|
```bash
|
|
37
37
|
cat /data/.openclaw/node_modules/bereach-openclaw/package.json | grep version
|
|
38
|
-
cat /data/.openclaw/extensions/bereach/package.json | grep version
|
|
38
|
+
cat /data/.openclaw/extensions/bereach-openclaw/package.json | grep version
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
## Setup
|
|
@@ -47,9 +47,9 @@ The API key can be set in 3 ways (in order of precedence):
|
|
|
47
47
|
```json
|
|
48
48
|
{
|
|
49
49
|
"plugins": {
|
|
50
|
-
"allow": ["bereach"],
|
|
50
|
+
"allow": ["bereach-openclaw"],
|
|
51
51
|
"entries": {
|
|
52
|
-
"bereach": {
|
|
52
|
+
"bereach-openclaw": {
|
|
53
53
|
"enabled": true,
|
|
54
54
|
"config": {
|
|
55
55
|
"BEREACH_API_KEY": "brc_your_token_here"
|
|
@@ -104,13 +104,13 @@ If that doesn't work:
|
|
|
104
104
|
|
|
105
105
|
OpenClaw loads plugins from **two locations**:
|
|
106
106
|
- `node_modules/bereach-openclaw/` — the npm package (source)
|
|
107
|
-
- `extensions/bereach/` — the active copy OpenClaw uses at runtime
|
|
107
|
+
- `extensions/bereach-openclaw/` — the active copy OpenClaw uses at runtime
|
|
108
108
|
|
|
109
|
-
If `extensions/bereach/` is corrupt or incomplete, you get trim/undefined errors. Fix:
|
|
109
|
+
If `extensions/bereach-openclaw/` is corrupt or incomplete, you get trim/undefined errors. Fix:
|
|
110
110
|
|
|
111
111
|
**1. Backup and remove the active extension:**
|
|
112
112
|
```bash
|
|
113
|
-
mv /data/.openclaw/extensions/bereach /data/.openclaw/extensions/bereach.bak.$(date +%s)
|
|
113
|
+
mv /data/.openclaw/extensions/bereach-openclaw /data/.openclaw/extensions/bereach-openclaw.bak.$(date +%s)
|
|
114
114
|
```
|
|
115
115
|
|
|
116
116
|
**2. Reinstall from npm (inside the container):**
|
|
@@ -119,29 +119,29 @@ cd /data/.openclaw
|
|
|
119
119
|
npm i bereach-openclaw@latest
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
**3. Restart OpenClaw** so it rebuilds `extensions/bereach/` from the package.
|
|
122
|
+
**3. Restart OpenClaw** so it rebuilds `extensions/bereach-openclaw/` from the package.
|
|
123
123
|
|
|
124
|
-
**4. If `extensions/bereach/` is still incomplete after restart**, force a symlink:
|
|
124
|
+
**4. If `extensions/bereach-openclaw/` is still incomplete after restart**, force a symlink:
|
|
125
125
|
```bash
|
|
126
|
-
rm -rf /data/.openclaw/extensions/bereach
|
|
127
|
-
ln -s /data/.openclaw/node_modules/bereach-openclaw /data/.openclaw/extensions/bereach
|
|
126
|
+
rm -rf /data/.openclaw/extensions/bereach-openclaw
|
|
127
|
+
ln -s /data/.openclaw/node_modules/bereach-openclaw /data/.openclaw/extensions/bereach-openclaw
|
|
128
128
|
# then restart the container
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
**5. Verify** — compare the two manifests:
|
|
132
132
|
```bash
|
|
133
|
-
wc -l /data/.openclaw/extensions/bereach/openclaw.plugin.json
|
|
133
|
+
wc -l /data/.openclaw/extensions/bereach-openclaw/openclaw.plugin.json
|
|
134
134
|
wc -l /data/.openclaw/node_modules/bereach-openclaw/openclaw.plugin.json
|
|
135
135
|
```
|
|
136
136
|
They should match. Per [OpenClaw Plugin Manifest](https://docs.openclaw.ai/plugins/manifest), tools are registered at runtime via `api.registerTool()`.
|
|
137
137
|
|
|
138
138
|
## Usage
|
|
139
139
|
|
|
140
|
-
### Tools (
|
|
140
|
+
### Tools (60+ registered)
|
|
141
141
|
|
|
142
142
|
All BeReach operations are available as agent tools — the agent uses them automatically based on your requests. No MCP needed; tools run in-process via the `bereach` SDK.
|
|
143
143
|
|
|
144
|
-
###
|
|
144
|
+
### Instant status commands
|
|
145
145
|
|
|
146
146
|
These execute instantly without invoking the AI:
|
|
147
147
|
|
|
@@ -161,8 +161,8 @@ openclaw bereach status
|
|
|
161
161
|
|
|
162
162
|
| Component | Description |
|
|
163
163
|
| --- | --- |
|
|
164
|
-
| `src/tools/` |
|
|
165
|
-
| `src/commands/` |
|
|
164
|
+
| `src/tools/` | 60+ tool definitions generated from OpenAPI |
|
|
165
|
+
| `src/commands/` | Instant status commands + CLI |
|
|
166
166
|
| `skills/bereach/SKILL.md` | Main behavioral skill |
|
|
167
167
|
| `skills/bereach/sub/lead-magnet.md` | Lead magnet workflow |
|
|
168
168
|
| `skills/bereach/sdk-reference.md` | SDK method reference |
|
|
@@ -28,19 +28,22 @@ function createMockClient(overrides: Partial<Record<string, Record<string, unkno
|
|
|
28
28
|
return {
|
|
29
29
|
linkedinScrapers: {
|
|
30
30
|
collectComments: vi.fn(),
|
|
31
|
-
visitProfile: vi.fn(),
|
|
32
31
|
...overrides.linkedinScrapers,
|
|
33
32
|
},
|
|
33
|
+
scrapers: {
|
|
34
|
+
visitProfile: vi.fn(),
|
|
35
|
+
...overrides.scrapers,
|
|
36
|
+
},
|
|
34
37
|
linkedinActions: {
|
|
35
38
|
listInvitations: vi.fn(),
|
|
36
39
|
...overrides.linkedinActions,
|
|
37
40
|
},
|
|
38
|
-
|
|
41
|
+
chat: {
|
|
39
42
|
findConversation: vi.fn(),
|
|
40
|
-
...overrides.
|
|
43
|
+
...overrides.chat,
|
|
41
44
|
},
|
|
42
45
|
campaigns: {
|
|
43
|
-
|
|
46
|
+
sync: vi.fn(),
|
|
44
47
|
getStats: vi.fn(),
|
|
45
48
|
...overrides.campaigns,
|
|
46
49
|
},
|
|
@@ -230,20 +233,62 @@ describe("collectAllComments", () => {
|
|
|
230
233
|
expect(mock).toHaveBeenCalledWith({ postUrl: "https://post", count: 0 });
|
|
231
234
|
});
|
|
232
235
|
|
|
233
|
-
it("fetches
|
|
236
|
+
it("fetches from previousTotal offset when total differs", async () => {
|
|
234
237
|
const client = createMockClient();
|
|
235
238
|
const mock = vi.mocked(client.linkedinScrapers.collectComments);
|
|
236
239
|
|
|
237
240
|
mock
|
|
238
241
|
.mockResolvedValueOnce(commentsPage([], { total: 55, start: 0, hasMore: false }))
|
|
239
242
|
.mockResolvedValueOnce(
|
|
240
|
-
commentsPage([{ name: "
|
|
243
|
+
commentsPage([{ name: "new1" }, { name: "new2" }], { total: 55, start: 50, hasMore: false }),
|
|
241
244
|
);
|
|
242
245
|
|
|
243
246
|
const result = await collectAllComments(client, "https://post", "slug", { previousTotal: 50 });
|
|
244
247
|
|
|
245
248
|
expect(result.skipped).toBe(false);
|
|
249
|
+
expect(result.profiles).toHaveLength(2);
|
|
246
250
|
expect(mock).toHaveBeenCalledTimes(2);
|
|
251
|
+
expect(mock).toHaveBeenNthCalledWith(1, { postUrl: "https://post", count: 0 });
|
|
252
|
+
expect(mock).toHaveBeenNthCalledWith(2, { postUrl: "https://post", start: 50, count: 100, campaignSlug: "slug" });
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("fetches only new comments (previousTotal=500, total=513)", async () => {
|
|
256
|
+
const client = createMockClient();
|
|
257
|
+
const mock = vi.mocked(client.linkedinScrapers.collectComments);
|
|
258
|
+
|
|
259
|
+
const newProfiles = Array.from({ length: 13 }, (_, i) => ({ name: `new${i}` }));
|
|
260
|
+
|
|
261
|
+
mock
|
|
262
|
+
.mockResolvedValueOnce(commentsPage([], { total: 513, start: 0, hasMore: false }))
|
|
263
|
+
.mockResolvedValueOnce(commentsPage(newProfiles, { total: 513, start: 500, hasMore: false }));
|
|
264
|
+
|
|
265
|
+
const result = await collectAllComments(client, "https://post", "slug", { previousTotal: 500 });
|
|
266
|
+
|
|
267
|
+
expect(result.profiles).toHaveLength(13);
|
|
268
|
+
expect(result.total).toBe(513);
|
|
269
|
+
expect(mock).toHaveBeenCalledTimes(2);
|
|
270
|
+
expect(mock).toHaveBeenNthCalledWith(2, { postUrl: "https://post", start: 500, count: 100, campaignSlug: "slug" });
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("paginates from previousTotal across multiple pages (200→350)", async () => {
|
|
274
|
+
const client = createMockClient();
|
|
275
|
+
const mock = vi.mocked(client.linkedinScrapers.collectComments);
|
|
276
|
+
|
|
277
|
+
const page1 = Array.from({ length: 100 }, (_, i) => ({ name: `p${200 + i}` }));
|
|
278
|
+
const page2 = Array.from({ length: 50 }, (_, i) => ({ name: `p${300 + i}` }));
|
|
279
|
+
|
|
280
|
+
mock
|
|
281
|
+
.mockResolvedValueOnce(commentsPage([], { total: 350, start: 0, hasMore: false }))
|
|
282
|
+
.mockResolvedValueOnce(commentsPage(page1, { total: 350, start: 200, hasMore: true }))
|
|
283
|
+
.mockResolvedValueOnce(commentsPage(page2, { total: 350, start: 300, hasMore: false }));
|
|
284
|
+
|
|
285
|
+
const result = await collectAllComments(client, "https://post", "slug", { previousTotal: 200 });
|
|
286
|
+
|
|
287
|
+
expect(result.profiles).toHaveLength(150);
|
|
288
|
+
expect(result.total).toBe(350);
|
|
289
|
+
expect(mock).toHaveBeenCalledTimes(3);
|
|
290
|
+
expect(mock).toHaveBeenNthCalledWith(2, { postUrl: "https://post", start: 200, count: 100, campaignSlug: "slug" });
|
|
291
|
+
expect(mock).toHaveBeenNthCalledWith(3, { postUrl: "https://post", start: 300, count: 100, campaignSlug: "slug" });
|
|
247
292
|
});
|
|
248
293
|
|
|
249
294
|
it("handles empty post (0 comments)", async () => {
|
|
@@ -344,7 +389,7 @@ describe("listAllInvitations", () => {
|
|
|
344
389
|
describe("visitProfileIfNeeded", () => {
|
|
345
390
|
it("returns knownDistance without visiting when set", async () => {
|
|
346
391
|
const client = createMockClient();
|
|
347
|
-
const mock = vi.mocked(client.
|
|
392
|
+
const mock = vi.mocked(client.scrapers.visitProfile);
|
|
348
393
|
|
|
349
394
|
const result = await visitProfileIfNeeded(client, "u/alice", "slug", 1);
|
|
350
395
|
|
|
@@ -356,7 +401,7 @@ describe("visitProfileIfNeeded", () => {
|
|
|
356
401
|
|
|
357
402
|
it("returns knownDistance=0 without visiting (0 is valid)", async () => {
|
|
358
403
|
const client = createMockClient();
|
|
359
|
-
const mock = vi.mocked(client.
|
|
404
|
+
const mock = vi.mocked(client.scrapers.visitProfile);
|
|
360
405
|
|
|
361
406
|
const result = await visitProfileIfNeeded(client, "u/alice", "slug", 0);
|
|
362
407
|
|
|
@@ -367,7 +412,7 @@ describe("visitProfileIfNeeded", () => {
|
|
|
367
412
|
|
|
368
413
|
it("returns knownDistance=2 without visiting", async () => {
|
|
369
414
|
const client = createMockClient();
|
|
370
|
-
const mock = vi.mocked(client.
|
|
415
|
+
const mock = vi.mocked(client.scrapers.visitProfile);
|
|
371
416
|
|
|
372
417
|
const result = await visitProfileIfNeeded(client, "u/alice", "slug", 2);
|
|
373
418
|
|
|
@@ -376,9 +421,9 @@ describe("visitProfileIfNeeded", () => {
|
|
|
376
421
|
expect(mock).not.toHaveBeenCalled();
|
|
377
422
|
});
|
|
378
423
|
|
|
379
|
-
it("calls
|
|
424
|
+
it("calls scrapers.visitProfile when knownDistance is null", async () => {
|
|
380
425
|
const client = createMockClient();
|
|
381
|
-
const mock = vi.mocked(client.
|
|
426
|
+
const mock = vi.mocked(client.scrapers.visitProfile);
|
|
382
427
|
mock.mockResolvedValueOnce({
|
|
383
428
|
success: true,
|
|
384
429
|
firstName: "Alice",
|
|
@@ -407,7 +452,7 @@ describe("visitProfileIfNeeded", () => {
|
|
|
407
452
|
|
|
408
453
|
it("calls visitProfile when knownDistance is undefined", async () => {
|
|
409
454
|
const client = createMockClient();
|
|
410
|
-
const mock = vi.mocked(client.
|
|
455
|
+
const mock = vi.mocked(client.scrapers.visitProfile);
|
|
411
456
|
mock.mockResolvedValueOnce({
|
|
412
457
|
success: true,
|
|
413
458
|
firstName: "Bob",
|
|
@@ -432,7 +477,7 @@ describe("visitProfileIfNeeded", () => {
|
|
|
432
477
|
|
|
433
478
|
it("handles null memberDistance from visit response", async () => {
|
|
434
479
|
const client = createMockClient();
|
|
435
|
-
const mock = vi.mocked(client.
|
|
480
|
+
const mock = vi.mocked(client.scrapers.visitProfile);
|
|
436
481
|
mock.mockResolvedValueOnce({
|
|
437
482
|
success: true,
|
|
438
483
|
firstName: "X",
|
|
@@ -456,7 +501,7 @@ describe("visitProfileIfNeeded", () => {
|
|
|
456
501
|
|
|
457
502
|
it("propagates FatalError from visitProfile", async () => {
|
|
458
503
|
const client = createMockClient();
|
|
459
|
-
const mock = vi.mocked(client.
|
|
504
|
+
const mock = vi.mocked(client.scrapers.visitProfile);
|
|
460
505
|
mock.mockRejectedValueOnce(Object.assign(new Error("unauthorized"), { statusCode: 401 }));
|
|
461
506
|
|
|
462
507
|
await expect(
|
|
@@ -470,7 +515,7 @@ describe("visitProfileIfNeeded", () => {
|
|
|
470
515
|
describe("findConversationForDmGuard", () => {
|
|
471
516
|
it("returns skip=false when no conversation found", async () => {
|
|
472
517
|
const client = createMockClient();
|
|
473
|
-
const mock = vi.mocked(client.
|
|
518
|
+
const mock = vi.mocked(client.chat.findConversation);
|
|
474
519
|
mock.mockResolvedValueOnce({
|
|
475
520
|
success: true,
|
|
476
521
|
found: false,
|
|
@@ -488,8 +533,8 @@ describe("findConversationForDmGuard", () => {
|
|
|
488
533
|
|
|
489
534
|
it("returns skip=true when resourceLink found in messages", async () => {
|
|
490
535
|
const client = createMockClient();
|
|
491
|
-
const findMock = vi.mocked(client.
|
|
492
|
-
const syncMock = vi.mocked(client.campaigns.
|
|
536
|
+
const findMock = vi.mocked(client.chat.findConversation);
|
|
537
|
+
const syncMock = vi.mocked(client.campaigns.sync);
|
|
493
538
|
|
|
494
539
|
findMock.mockResolvedValueOnce({
|
|
495
540
|
success: true,
|
|
@@ -519,7 +564,7 @@ describe("findConversationForDmGuard", () => {
|
|
|
519
564
|
|
|
520
565
|
it("returns skip=false when conversation exists but link not in messages", async () => {
|
|
521
566
|
const client = createMockClient();
|
|
522
|
-
const findMock = vi.mocked(client.
|
|
567
|
+
const findMock = vi.mocked(client.chat.findConversation);
|
|
523
568
|
|
|
524
569
|
findMock.mockResolvedValueOnce({
|
|
525
570
|
success: true,
|
|
@@ -547,7 +592,7 @@ describe("findConversationForDmGuard", () => {
|
|
|
547
592
|
|
|
548
593
|
it("returns messages for tone adaptation when no link found", async () => {
|
|
549
594
|
const client = createMockClient();
|
|
550
|
-
const findMock = vi.mocked(client.
|
|
595
|
+
const findMock = vi.mocked(client.chat.findConversation);
|
|
551
596
|
|
|
552
597
|
findMock.mockResolvedValueOnce({
|
|
553
598
|
success: true,
|
|
@@ -584,7 +629,7 @@ describe("findConversationForDmGuard", () => {
|
|
|
584
629
|
|
|
585
630
|
it("returns skip=true (fail-safe) when findConversation throws", async () => {
|
|
586
631
|
const client = createMockClient();
|
|
587
|
-
const findMock = vi.mocked(client.
|
|
632
|
+
const findMock = vi.mocked(client.chat.findConversation);
|
|
588
633
|
findMock.mockRejectedValueOnce(Object.assign(new Error("network"), { statusCode: 500 }));
|
|
589
634
|
|
|
590
635
|
const result = await findConversationForDmGuard(client, "u/alice", "https://link", "slug");
|
|
@@ -593,10 +638,10 @@ describe("findConversationForDmGuard", () => {
|
|
|
593
638
|
expect(result.messages).toHaveLength(0);
|
|
594
639
|
});
|
|
595
640
|
|
|
596
|
-
it("still returns skip=true when
|
|
641
|
+
it("still returns skip=true when sync fails after finding link", async () => {
|
|
597
642
|
const client = createMockClient();
|
|
598
|
-
const findMock = vi.mocked(client.
|
|
599
|
-
const syncMock = vi.mocked(client.campaigns.
|
|
643
|
+
const findMock = vi.mocked(client.chat.findConversation);
|
|
644
|
+
const syncMock = vi.mocked(client.campaigns.sync);
|
|
600
645
|
|
|
601
646
|
findMock.mockResolvedValueOnce({
|
|
602
647
|
success: true,
|
|
@@ -624,7 +669,7 @@ describe("findConversationForDmGuard", () => {
|
|
|
624
669
|
|
|
625
670
|
it("handles conversation found but messages array is null", async () => {
|
|
626
671
|
const client = createMockClient();
|
|
627
|
-
const findMock = vi.mocked(client.
|
|
672
|
+
const findMock = vi.mocked(client.chat.findConversation);
|
|
628
673
|
|
|
629
674
|
findMock.mockResolvedValueOnce({
|
|
630
675
|
success: true,
|
|
@@ -643,7 +688,7 @@ describe("findConversationForDmGuard", () => {
|
|
|
643
688
|
|
|
644
689
|
it("handles conversation found but messages array is empty", async () => {
|
|
645
690
|
const client = createMockClient();
|
|
646
|
-
const findMock = vi.mocked(client.
|
|
691
|
+
const findMock = vi.mocked(client.chat.findConversation);
|
|
647
692
|
|
|
648
693
|
findMock.mockResolvedValueOnce({
|
|
649
694
|
success: true,
|
|
@@ -662,8 +707,8 @@ describe("findConversationForDmGuard", () => {
|
|
|
662
707
|
|
|
663
708
|
it("matches resourceLink as substring in message text", async () => {
|
|
664
709
|
const client = createMockClient();
|
|
665
|
-
const findMock = vi.mocked(client.
|
|
666
|
-
const syncMock = vi.mocked(client.campaigns.
|
|
710
|
+
const findMock = vi.mocked(client.chat.findConversation);
|
|
711
|
+
const syncMock = vi.mocked(client.campaigns.sync);
|
|
667
712
|
|
|
668
713
|
findMock.mockResolvedValueOnce({
|
|
669
714
|
success: true,
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "bereach",
|
|
2
|
+
"id": "bereach-openclaw",
|
|
3
3
|
"name": "BeReach",
|
|
4
|
-
"version": "
|
|
5
|
-
"description": "LinkedIn outreach automation —
|
|
4
|
+
"version": "1.3.1",
|
|
5
|
+
"description": "LinkedIn outreach automation — 60+ tools, instant status commands",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bereach-openclaw",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "BeReach LinkedIn automation plugin for OpenClaw",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"exports": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
]
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"bereach": "^
|
|
18
|
+
"bereach": "^1.3.2"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@types/node": "^22.19.3",
|