bereach-openclaw 0.2.7 → 0.2.10

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 CHANGED
@@ -8,29 +8,111 @@ LinkedIn outreach automation via [BeReach](https://berea.ch). Registers 33 in-pr
8
8
  openclaw plugins install bereach-openclaw
9
9
  ```
10
10
 
11
+ ## Upgrade
12
+
13
+ `openclaw plugins update bereach` can leave `node_modules` and `extensions/` out of sync (version mismatch → trim/crash errors). **Use uninstall + reinstall** instead:
14
+
15
+ ```bash
16
+ openclaw plugins uninstall bereach
17
+ openclaw plugins install bereach-openclaw
18
+ ```
19
+
20
+ Then restart the Gateway. Verify versions match:
21
+ ```bash
22
+ cat /data/.openclaw/node_modules/bereach-openclaw/package.json | grep version
23
+ cat /data/.openclaw/extensions/bereach/package.json | grep version
24
+ ```
25
+
11
26
  ## Setup
12
27
 
13
- Set your API key in OpenClaw config (`~/.openclaw/openclaw.json`):
28
+ The API key can be set in 3 ways (in order of precedence):
29
+
30
+ **1. OpenClaw config** (recommended) — `~/.openclaw/openclaw.json` or `/data/.openclaw/openclaw.json`:
14
31
 
15
- ```json5
32
+ ```json
16
33
  {
17
- plugins: {
18
- entries: {
19
- bereach: {
20
- enabled: true,
21
- config: {
22
- BEREACH_API_KEY: "brc_your_token_here"
34
+ "plugins": {
35
+ "allow": ["bereach"],
36
+ "entries": {
37
+ "bereach": {
38
+ "enabled": true,
39
+ "config": {
40
+ "BEREACH_API_KEY": "brc_your_token_here"
23
41
  }
24
42
  }
25
43
  }
44
+ },
45
+ "tools": {
46
+ "allow": ["bereach"]
47
+ }
48
+ }
49
+ ```
50
+
51
+ > **Note:** `plugins.allow` loads the plugin; `tools.allow` enables the tools (they are registered with `optional: true`).
52
+
53
+ **Skills + model (optional)** — `skills.entries` has no `model` field; model is set at the agent level. To use Sonnet for BeReach:
54
+
55
+ ```json
56
+ {
57
+ "skills": {
58
+ "entries": {
59
+ "bereach": { "enabled": true }
60
+ }
61
+ },
62
+ "agents": {
63
+ "defaults": {
64
+ "model": { "primary": "anthropic/claude-sonnet-4-5" },
65
+ "models": {
66
+ "anthropic/claude-sonnet-4-5": { "alias": "sonnet" }
67
+ }
68
+ }
26
69
  }
27
70
  }
28
71
  ```
29
72
 
30
- Or pass it via Docker environment: `docker run -e BEREACH_API_KEY=brc_xxx ...`
73
+ **2. Environment variable** `BEREACH_API_KEY=brc_xxx` in your shell or `export BEREACH_API_KEY="brc_xxx"` in `~/.bashrc`.
74
+
75
+ **3. Docker** — pass via `-e` when launching the container: `docker run -e BEREACH_API_KEY=brc_xxx ...`
31
76
 
32
77
  Restart the Gateway after configuring.
33
78
 
79
+ ## Troubleshooting
80
+
81
+ ### "Cannot read properties of undefined (reading 'trim')" or "plugin disabled (not in allowlist)"
82
+
83
+ OpenClaw loads plugins from **two locations**:
84
+ - `node_modules/bereach-openclaw/` — the npm package (source)
85
+ - `extensions/bereach/` — the active copy OpenClaw uses at runtime
86
+
87
+ If `extensions/bereach/` is corrupt or incomplete, you get trim/undefined errors. Fix:
88
+
89
+ **1. Backup and remove the active extension:**
90
+ ```bash
91
+ mv /data/.openclaw/extensions/bereach /data/.openclaw/extensions/bereach.bak.$(date +%s)
92
+ ```
93
+
94
+ **2. Reinstall from npm (inside the container):**
95
+ ```bash
96
+ cd /data/.openclaw
97
+ npm i bereach-openclaw@latest
98
+ ```
99
+
100
+ **3. Restart OpenClaw** so it rebuilds `extensions/bereach/` from the package.
101
+
102
+ **4. If `extensions/bereach/` is still incomplete after restart**, force a symlink:
103
+ ```bash
104
+ rm -rf /data/.openclaw/extensions/bereach
105
+ ln -s /data/.openclaw/node_modules/bereach-openclaw /data/.openclaw/extensions/bereach
106
+ # then restart the container
107
+ ```
108
+
109
+ **5. Verify** — compare the two manifests:
110
+ ```bash
111
+ wc -l /data/.openclaw/extensions/bereach/openclaw.plugin.json
112
+ wc -l /data/.openclaw/node_modules/bereach-openclaw/openclaw.plugin.json
113
+ ```
114
+ They should match. Per [OpenClaw Plugin Manifest](https://docs.openclaw.ai/plugins/manifest), tools are registered at runtime via `api.registerTool()`.
115
+
34
116
  ## Usage
35
117
 
36
118
  ### Tools (33 registered)
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "id": "bereach",
3
3
  "name": "BeReach",
4
+ "version": "0.2.10",
4
5
  "description": "LinkedIn outreach automation — 33 tools, auto-reply commands, campaign monitoring",
5
6
  "configSchema": {
6
7
  "type": "object",
7
- "required": ["BEREACH_API_KEY"],
8
+ "required": [
9
+ "BEREACH_API_KEY"
10
+ ],
8
11
  "additionalProperties": false,
9
12
  "properties": {
10
13
  "BEREACH_API_KEY": {
@@ -20,5 +23,8 @@
20
23
  "sensitive": true,
21
24
  "placeholder": "brc_..."
22
25
  }
23
- }
26
+ },
27
+ "skills": [
28
+ "skills/bereach"
29
+ ]
24
30
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bereach-openclaw",
3
- "version": "0.2.7",
3
+ "version": "0.2.10",
4
4
  "description": "BeReach LinkedIn automation plugin for OpenClaw",
5
5
  "license": "AGPL-3.0",
6
6
  "scripts": {
@@ -15,6 +15,7 @@
15
15
  "bereach": "^0.1.4"
16
16
  },
17
17
  "devDependencies": {
18
+ "@types/node": "^22.10.0",
18
19
  "tsx": "^4.21.0",
19
20
  "typescript": "^5.9.3"
20
21
  }
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env tsx
2
+ /// <reference types="node" />
2
3
  /**
3
4
  * Simulates OpenClaw plugin registration to catch config/trim errors.
4
5
  * Run: pnpm exec tsx scripts/test-register.ts
@@ -10,13 +11,13 @@ import register from "../src/index";
10
11
  const apiKey = process.env.BEREACH_API_KEY || "brc_test_placeholder";
11
12
 
12
13
  const mockApi = {
13
- config: { BEREACH_API_KEY: apiKey },
14
+ pluginConfig: { BEREACH_API_KEY: apiKey },
14
15
  registerTool: () => {},
15
16
  registerCommand: () => {},
16
17
  registerCli: () => {},
17
18
  };
18
19
 
19
- console.log("Testing plugin register (simulates OpenClaw load)...");
20
+ console.log("Testing plugin register with config (simulates OpenClaw load)...");
20
21
  try {
21
22
  register(mockApi);
22
23
  console.log("✓ register() completed — no trim/undefined errors");
@@ -24,3 +25,14 @@ try {
24
25
  console.error("✗ register() failed:", err?.message || err);
25
26
  process.exit(1);
26
27
  }
28
+
29
+ console.log("\nTesting plugin register WITHOUT config (should load, fail on first use)...");
30
+ try {
31
+ register({ pluginConfig: undefined, registerTool: () => {}, registerCommand: () => {}, registerCli: () => {} });
32
+ console.log("✓ register() completed — plugin loads without config (lazy init)");
33
+ } catch (err: any) {
34
+ console.error("✗ register() failed with missing config:", err?.message || err);
35
+ process.exit(1);
36
+ }
37
+
38
+ console.log("\n✓ All tests passed");
package/src/client.ts CHANGED
@@ -1,12 +1,22 @@
1
1
  import { Bereach } from "bereach";
2
2
 
3
3
  let instance: Bereach | null = null;
4
+ let pendingKey: string | undefined;
5
+
6
+ function normalizeKey(raw: unknown): string | undefined {
7
+ if (raw == null) return undefined;
8
+ const s = typeof raw === "string" ? raw : String(raw);
9
+ const t = s.trim();
10
+ return t.length > 0 ? t : undefined;
11
+ }
12
+
13
+ export function setApiKey(key: string | undefined): void {
14
+ pendingKey = normalizeKey(key);
15
+ }
4
16
 
5
17
  export function createClient(key?: string): Bereach {
6
18
  if (!instance) {
7
- const raw = key ?? process.env.BEREACH_API_KEY;
8
- const apiKey =
9
- typeof raw === "string" && raw.trim().length > 0 ? raw.trim() : undefined;
19
+ const apiKey = normalizeKey(key ?? pendingKey ?? process.env.BEREACH_API_KEY);
10
20
  if (!apiKey) {
11
21
  throw new Error(
12
22
  "BEREACH_API_KEY is required (set in plugin config or environment)",
@@ -16,3 +26,7 @@ export function createClient(key?: string): Bereach {
16
26
  }
17
27
  return instance;
18
28
  }
29
+
30
+ export function getClient(): Bereach {
31
+ return createClient();
32
+ }
@@ -1,14 +1,16 @@
1
- import type { Bereach } from "bereach";
1
+ import { getClient } from "../client";
2
2
 
3
3
  /**
4
4
  * Registers auto-reply commands and CLI commands.
5
5
  * Auto-reply commands execute without invoking the LLM.
6
+ * Client is created lazily on first command invocation.
6
7
  */
7
- export function registerCommands(api: any, client: Bereach) {
8
+ export function registerCommands(api: any) {
8
9
  api.registerCommand({
9
10
  name: "bereach-credits",
10
11
  description: "Show current BeReach credit balance",
11
12
  handler: async () => {
13
+ const client = getClient();
12
14
  const res = await (client as any).profile.getCredits();
13
15
  const c = res.credits;
14
16
  return {
@@ -24,6 +26,7 @@ export function registerCommands(api: any, client: Bereach) {
24
26
  name: "bereach-status",
25
27
  description: "Show LinkedIn rate limit summary",
26
28
  handler: async () => {
29
+ const client = getClient();
27
30
  const res = await (client as any).profile.getLimits();
28
31
  const lines = ["BeReach Rate Limits:"];
29
32
  for (const [action, data] of Object.entries(res.limits) as [string, any][]) {
@@ -38,6 +41,7 @@ export function registerCommands(api: any, client: Bereach) {
38
41
  name: "bereach-limits",
39
42
  description: "Show detailed per-action LinkedIn rate limits",
40
43
  handler: async () => {
44
+ const client = getClient();
41
45
  const res = await (client as any).profile.getLimits();
42
46
  const lines = [`BeReach Rate Limits (multiplier: ${res.multiplier}x):`];
43
47
  for (const [action, data] of Object.entries(res.limits) as [string, any][]) {
@@ -62,6 +66,7 @@ export function registerCommands(api: any, client: Bereach) {
62
66
  .command("status")
63
67
  .description("Show LinkedIn rate limit summary")
64
68
  .action(async () => {
69
+ const client = getClient();
65
70
  const res = await (client as any).profile.getLimits();
66
71
  console.log("BeReach Rate Limits:");
67
72
  for (const [action, data] of Object.entries(res.limits) as [string, any][]) {
package/src/index.ts CHANGED
@@ -1,18 +1,18 @@
1
- import { createClient } from "./client";
1
+ import { setApiKey } from "./client";
2
2
  import { registerAllTools } from "./tools";
3
3
  import { registerCommands } from "./commands";
4
4
 
5
5
  function getApiKey(api: any): string | undefined {
6
- const cfg = api?.config;
7
6
  const key =
8
- cfg?.BEREACH_API_KEY ??
9
- cfg?.plugins?.entries?.bereach?.config?.BEREACH_API_KEY;
7
+ api?.pluginConfig?.BEREACH_API_KEY ?? // 1. OpenClaw config (plugins.entries.bereach.config)
8
+ api?.config?.BEREACH_API_KEY ?? // 2. Fallback config path
9
+ process.env.BEREACH_API_KEY; // 3. Env var (or ~/.bashrc export)
10
10
  return typeof key === "string" && key.trim().length > 0 ? key.trim() : undefined;
11
11
  }
12
12
 
13
13
  export default function register(api: any) {
14
14
  const apiKey = getApiKey(api);
15
- const client = createClient(apiKey);
16
- registerAllTools(api, client);
17
- registerCommands(api, client);
15
+ setApiKey(apiKey);
16
+ registerAllTools(api);
17
+ registerCommands(api);
18
18
  }
@@ -1,23 +1,28 @@
1
+ import { getClient } from "./../client";
1
2
  import { definitions } from "./definitions";
2
- import type { Bereach } from "bereach";
3
3
 
4
4
  /**
5
5
  * Registers all 33 BeReach tools with the OpenClaw agent.
6
- * Each tool delegates to the corresponding SDK method via dot-path resolution.
6
+ * Format per https://docs.openclaw.ai/plugins/agent-tools
7
+ * optional: true — tools have side effects (messages, connections) and require BEREACH_API_KEY
7
8
  */
8
- export function registerAllTools(api: any, client: Bereach) {
9
+ export function registerAllTools(api: any) {
9
10
  for (const def of definitions) {
10
11
  api.registerTool(
11
- def.name,
12
12
  {
13
+ name: def.name,
13
14
  description: def.description,
14
15
  parameters: def.parameters,
16
+ async execute(_id: string, params: Record<string, unknown>) {
17
+ const client = getClient();
18
+ const [resource, method] = def.handler.split(".");
19
+ const result = await (client as any)[resource][method](params);
20
+ return {
21
+ content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
22
+ };
23
+ },
15
24
  },
16
- async (params: Record<string, unknown>) => {
17
- const [resource, method] = def.handler.split(".");
18
- const result = await (client as any)[resource][method](params);
19
- return result;
20
- },
25
+ { optional: true },
21
26
  );
22
27
  }
23
28
  }
package/tsconfig.json CHANGED
@@ -8,7 +8,8 @@
8
8
  "skipLibCheck": true,
9
9
  "outDir": "dist",
10
10
  "rootDir": "src",
11
- "declaration": true
11
+ "declaration": true,
12
+ "types": ["node"]
12
13
  },
13
14
  "include": ["src/**/*.ts"],
14
15
  "exclude": ["__tests__", "node_modules", "dist"]