bereach-openclaw 1.5.3 → 1.5.5

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.
@@ -2055,4 +2055,23 @@ export const definitions: ToolDefinition[] = [
2055
2055
  properties: {},
2056
2056
  },
2057
2057
  },
2058
+
2059
+ // ── API Key Setup ────────────────────────────────────────────────
2060
+
2061
+ {
2062
+ name: "bereach_set_api_key",
2063
+ description: "Save the BeReach API key in plugin configuration. Call this when the user provides their API key (starts with brc_). After saving, tell the user to restart the gateway with 'openclaw gateway restart' and start a new chat with /new.",
2064
+ handler: "__set_api_key__",
2065
+ parameters: {
2066
+ type: "object",
2067
+ required: ["apiKey"],
2068
+ properties: {
2069
+ apiKey: {
2070
+ type: "string",
2071
+ description: "The BeReach API key (must start with brc_)",
2072
+ },
2073
+ },
2074
+ additionalProperties: false,
2075
+ },
2076
+ },
2058
2077
  ];
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "bereach-openclaw",
3
3
  "name": "BeReach",
4
- "version": "1.5.3",
4
+ "version": "1.5.5",
5
5
  "description": "LinkedIn outreach automation — 75+ tools, hook-based enforcement, dynamic context",
6
6
  "configSchema": {
7
7
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bereach-openclaw",
3
- "version": "1.5.3",
3
+ "version": "1.5.5",
4
4
  "description": "BeReach LinkedIn automation plugin for OpenClaw",
5
5
  "license": "AGPL-3.0",
6
6
  "exports": {
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: bereach
3
3
  description: "Automate LinkedIn outreach via BeReach (bereach.ai). Use when: prospecting, engaging posts, scraping engagement, searching LinkedIn, managing inbox, running campaigns, managing invitations, analytics, company pages, Sales Navigator, content engagement, feed monitoring. Requires BEREACH_API_KEY."
4
- lastUpdatedAt: 1775561761
4
+ lastUpdatedAt: 1775565279
5
5
  metadata: { "openclaw": { "requires": { "env": ["BEREACH_API_KEY"] }, "primaryEnv": "BEREACH_API_KEY" } }
6
6
  ---
7
7
 
@@ -45,13 +45,13 @@ At install or upgrade, set up the workspace with these templates:
45
45
 
46
46
  When the user provides their BeReach API key (`brc_...`):
47
47
 
48
- 1. Tell them to paste this command in chat: `/bereach-set-key brc_THEIR_KEY` (replacing with their actual key)
48
+ 1. Call the `bereach_set_api_key` tool with their key
49
49
  2. Tell them to restart the gateway: `openclaw gateway restart`
50
50
  3. After restart, start a new chat with `/new`
51
51
 
52
52
  If the user doesn't have a key, direct them to https://bereach.ai/token
53
53
 
54
- **Never** try to set the key via environment variables, auth profiles, or manual config edits. Always use `/bereach-set-key`.
54
+ **Never** use config.patch, environment variables, or manual config edits. Always use the `bereach_set_api_key` tool.
55
55
 
56
56
  ## Two Modes of Operation
57
57
 
@@ -374,7 +374,7 @@ function formatCampaignDispatch(
374
374
  const shown = Math.min(ranked.length, 7);
375
375
  for (let i = 0; i < shown; i++) {
376
376
  const { campaign: c } = ranked[i];
377
- const pipelineUrl = `${CHAT_BASE}/pipeline?campaign=${c.id}`;
377
+ const pipelineUrl = `${CHAT_BASE}/campaigns/${c.id}/pipeline`;
378
378
  const s = c.stageCounts;
379
379
  const funnel = s && (s.lead || s.qualified || s.approved || s.rejected)
380
380
  ? ` — ${s.lead ?? 0}L / ${s.qualified ?? 0}Q / ${s.approved ?? 0}A / ${s.rejected ?? 0}R`
@@ -479,7 +479,7 @@ function formatOnboardingDirective(state: SessionState, data: CacheStore, apiKey
479
479
  "",
480
480
  "Search 5 matching prospects, visit top 3. Present: name, title, company, why they fit, suggested approach.",
481
481
  "Offer first action: connect, warm up (like/comment), or save to campaign.",
482
- "Execute, then link: [Pipeline](${CHAT_BASE}/pipeline) / [Campaigns](${CHAT_BASE}/campaigns)",
482
+ "Execute, then link: [Campaigns](${CHAT_BASE}/campaigns)",
483
483
  '- `bereach_state_patch({ key: "onboarding", data: { quickWinDone: true } })`',
484
484
  "",
485
485
  );
@@ -576,7 +576,7 @@ function formatLiveStatus(state: SessionState, data: CacheStore, apiKey?: string
576
576
  lines.push("");
577
577
  }
578
578
  lines.push("### Dashboard Links");
579
- lines.push(`[Pipeline](${CHAT_BASE}/pipeline) | [Campaigns](${CHAT_BASE}/campaigns) | [Drafts](${CHAT_BASE}/drafts) | [Context](${CHAT_BASE}/context) | [Settings](${CHAT_BASE}/settings) | [Pricing](${PRICING_URL})`);
579
+ lines.push(`[Campaigns](${CHAT_BASE}/campaigns) | [Activity](${CHAT_BASE}/activity) | [Context](${CHAT_BASE}/context) | [Settings](${CHAT_BASE}/settings) | [Pricing](${PRICING_URL})`);
580
580
  lines.push("");
581
581
  log(`live-status: ONBOARDING mode (${lines.join("\n").length} chars)`);
582
582
  return lines.join("\n");
@@ -733,7 +733,7 @@ function formatLiveStatus(state: SessionState, data: CacheStore, apiKey?: string
733
733
  if (upgradeBlock) lines.push(upgradeBlock);
734
734
 
735
735
  // Dashboard links
736
- lines.push(`**Links**: [Pipeline](${CHAT_BASE}/pipeline) | [Campaigns](${CHAT_BASE}/campaigns) | [Drafts](${CHAT_BASE}/drafts) | [Context](${CHAT_BASE}/context) | [Settings](${CHAT_BASE}/settings) | [Pricing](${PRICING_URL})`);
736
+ lines.push(`**Links**: [Campaigns](${CHAT_BASE}/campaigns) | [Activity](${CHAT_BASE}/activity) | [Context](${CHAT_BASE}/context) | [Settings](${CHAT_BASE}/settings) | [Pricing](${PRICING_URL})`);
737
737
  lines.push("");
738
738
 
739
739
  log(`live-status: ACTIVE mode, campaigns=${hasCampaigns} contacts=${hasContacts} pending=${data.pendingDrafts > 0 || data.failedDrafts > 0 || data.unreadDMs > 0} (${lines.join("\n").length} chars)`);
@@ -1015,16 +1015,16 @@ export function registerContextHook(api: any, apiKey: string | undefined, state:
1015
1015
  "IMPORTANT: BeReach plugin is installed but no API key is configured.",
1016
1016
  "",
1017
1017
  "If the user provides their BeReach API key (a string starting with brc_):",
1018
- "- Tell them to paste this command in chat: /bereach-set-key brc_THEIR_KEY",
1019
- "- Then restart the gateway: openclaw gateway restart",
1018
+ "- Call the bereach_set_api_key tool with their key",
1019
+ "- Then tell them to restart the gateway: openclaw gateway restart",
1020
1020
  "- Then start a new chat: /new",
1021
1021
  "",
1022
1022
  "If the user has NOT provided a key yet:",
1023
1023
  "- Ask them for their BeReach API key",
1024
1024
  "- They can get one at https://bereach.ai/token",
1025
1025
  "",
1026
- "The /bereach-set-key command saves the key securely in the plugin config.",
1027
- "Do NOT suggest environment variables, config.patch, or any other method.",
1026
+ "ALWAYS use the bereach_set_api_key tool to save the key.",
1027
+ "Do NOT use config.patch, environment variables, or any other method.",
1028
1028
  ].join("\n") };
1029
1029
  }
1030
1030
 
@@ -592,11 +592,14 @@ export function registerEnforcementHook(
592
592
  const toolName: string = ctx?.toolName ?? ctx?.name ?? "";
593
593
  if (!toolName.startsWith("bereach_")) return;
594
594
 
595
+ // Allow set_api_key and self_upgrade without an API key
596
+ if (toolName === "bereach_set_api_key" || toolName === "bereach_self_upgrade") return;
597
+
595
598
  // Resolve API key dynamically
596
599
  const resolvedKey = api?.pluginConfig?.BEREACH_API_KEY ?? api?.pluginConfig?.apiKey ?? apiKey;
597
600
  const key = typeof resolvedKey === "string" && resolvedKey.trim() ? resolvedKey.trim() : undefined;
598
601
  if (!key) {
599
- return { block: true, blockReason: "BLOCKED: BeReach API key not configured. Set it via /bereach-setup or plugin settings, then restart the gateway." };
602
+ return { block: true, blockReason: "BLOCKED: BeReach API key not configured. Provide your key so the agent can save it with bereach_set_api_key. Get a key at https://bereach.ai/token" };
600
603
  }
601
604
 
602
605
  const params: Record<string, unknown> = ctx?.params ?? ctx?.input ?? {};
package/src/index.ts CHANGED
@@ -17,6 +17,7 @@ const log = createLogger("init");
17
17
  /** Guard against multiple register() calls with the same api instance */
18
18
  const registeredApis = new WeakSet<object>();
19
19
  let registeredCount = 0;
20
+ let autoDetectDone = false;
20
21
 
21
22
  /**
22
23
  * Resolve the BeReach API key from all supported sources (in priority order):
@@ -32,6 +33,86 @@ export function resolveApiKey(api: any): string | undefined {
32
33
  return typeof key === "string" && key.trim().length > 0 ? key.trim() : undefined;
33
34
  }
34
35
 
36
+ /**
37
+ * Auto-detect LLM provider from available API keys and set workspace defaults.
38
+ *
39
+ * Runs once at install time. If the workspace still has Anthropic defaults but
40
+ * no ANTHROPIC_API_KEY is available (only GEMINI_API_KEY), switches to Gemini
41
+ * models automatically. This prevents tasks from failing because the default
42
+ * models require an API key the user doesn't have.
43
+ *
44
+ * Does nothing if:
45
+ * - Already ran this session (idempotent guard)
46
+ * - ANTHROPIC_API_KEY is available (defaults are correct)
47
+ * - No GEMINI_API_KEY available (nothing to switch to)
48
+ * - User already changed models from defaults (respects manual choice)
49
+ */
50
+ async function autoDetectModels(apiKey: string): Promise<void> {
51
+ if (autoDetectDone) return;
52
+ autoDetectDone = true;
53
+
54
+ const hasAnthropic = !!readEnv("ANTHROPIC_API_KEY");
55
+ const hasGemini = !!(readEnv("GEMINI_API_KEY") || readEnv("GOOGLE_API_KEY"));
56
+
57
+ // If Anthropic key exists, defaults are already correct
58
+ if (hasAnthropic) {
59
+ log("model auto-detect: Anthropic key available, keeping defaults");
60
+ return;
61
+ }
62
+
63
+ // If no Gemini key either, nothing we can switch to
64
+ if (!hasGemini) {
65
+ log("model auto-detect: no LLM provider keys found, skipping");
66
+ return;
67
+ }
68
+
69
+ // Gemini key available, no Anthropic key - check if we need to switch
70
+ try {
71
+ const res = await fetch(`${API_BASE}/me/settings`, {
72
+ headers: { Authorization: `Bearer ${apiKey}` },
73
+ signal: AbortSignal.timeout(5000),
74
+ });
75
+ if (!res.ok) {
76
+ log(`model auto-detect: settings GET failed (HTTP ${res.status}), skipping`);
77
+ return;
78
+ }
79
+
80
+ const data = await res.json() as {
81
+ aiModels?: { fast?: string; creative?: string };
82
+ };
83
+ const currentFast = data.aiModels?.fast;
84
+ const currentCreative = data.aiModels?.creative;
85
+
86
+ // Only auto-switch if still on Anthropic defaults (respect manual choices)
87
+ if (
88
+ currentFast === "anthropic/claude-haiku-4-5" &&
89
+ currentCreative === "anthropic/claude-sonnet-4-6"
90
+ ) {
91
+ const patchRes = await fetch(`${API_BASE}/me/settings`, {
92
+ method: "PATCH",
93
+ headers: {
94
+ Authorization: `Bearer ${apiKey}`,
95
+ "Content-Type": "application/json",
96
+ },
97
+ body: JSON.stringify({
98
+ aiFastModel: "google/gemini-2.5-flash",
99
+ aiCreativeModel: "google/gemini-2.5-pro",
100
+ }),
101
+ signal: AbortSignal.timeout(5000),
102
+ });
103
+ if (patchRes.ok) {
104
+ log("model auto-detect: no Anthropic key, Gemini key found - switched to Gemini Flash (fast) + Gemini Pro (creative)");
105
+ } else {
106
+ log(`model auto-detect: PATCH failed (HTTP ${patchRes.status})`);
107
+ }
108
+ } else {
109
+ log("model auto-detect: models already customized, skipping");
110
+ }
111
+ } catch (err) {
112
+ log(`model auto-detect: ${err instanceof Error ? err.message : String(err)}`);
113
+ }
114
+ }
115
+
35
116
  export default function register(api: any) {
36
117
  if (api && typeof api === "object" && registeredApis.has(api)) {
37
118
  log(`skip duplicate register() call (same api instance, call #${registeredCount + 1})`);
@@ -127,6 +208,15 @@ export default function register(api: any) {
127
208
  }
128
209
  }
129
210
 
211
+ // Auto-detect LLM provider and set workspace model defaults.
212
+ // Runs before connector start so tasks use the correct models from the first poll.
213
+ // Fire-and-forget — non-critical, must not block registration.
214
+ if (apiKey) {
215
+ autoDetectModels(apiKey).catch((err) => {
216
+ log(`model auto-detect error: ${err instanceof Error ? err.message : String(err)}`);
217
+ });
218
+ }
219
+
130
220
  // Auto-start connector if API key is present and connector is enabled.
131
221
  // Skip when running as an agent child process (openclaw agent) — each child
132
222
  // would start its own polling loop, stealing tasks from the main connector.
@@ -127,7 +127,7 @@ export function registerAllTools(api: any, taskType?: string) {
127
127
  let skipped = 0;
128
128
 
129
129
  for (const def of definitions) {
130
- if (!def.apiPath && def.handler !== "__self_upgrade__" && def.handler !== "__local__") {
130
+ if (!def.apiPath && def.handler !== "__self_upgrade__" && def.handler !== "__local__" && def.handler !== "__set_api_key__") {
131
131
  console.warn(`[bereach:tools] SKIP ${def.name}: no apiPath defined`);
132
132
  continue;
133
133
  }
@@ -148,10 +148,29 @@ export function registerAllTools(api: any, taskType?: string) {
148
148
  return executeSelfUpgrade();
149
149
  }
150
150
 
151
+ if (def.handler === "__set_api_key__") {
152
+ const key = String(params.apiKey ?? "").trim();
153
+ if (!key || !key.startsWith("brc_")) {
154
+ return {
155
+ content: [{ type: "text" as const, text: JSON.stringify({ success: false, error: "Invalid key format. Must start with brc_. Get your key at https://bereach.ai/token" }) }],
156
+ };
157
+ }
158
+
159
+ if (typeof api?.config?.set !== "function") {
160
+ return {
161
+ content: [{ type: "text" as const, text: JSON.stringify({ success: false, error: "Config API not available. Set BEREACH_API_KEY as an environment variable instead." }) }],
162
+ };
163
+ }
164
+ api.config.set("plugins.entries.bereach-openclaw.config.BEREACH_API_KEY", key);
165
+ return {
166
+ content: [{ type: "text" as const, text: JSON.stringify({ success: true, message: `API key saved (…${key.slice(-6)}). Tell the user to run: openclaw gateway restart, then /new` }) }],
167
+ };
168
+ }
169
+
151
170
  const apiKey = resolveApiKey(api);
152
171
  if (!apiKey) {
153
172
  return {
154
- content: [{ type: "text" as const, text: JSON.stringify({ success: false, error: "BEREACH_API_KEY not configured. The user should get their key at https://bereach.ai/token and set it with: /bereach-set-key brc_THEIR_KEY" }) }],
173
+ content: [{ type: "text" as const, text: JSON.stringify({ success: false, error: "BEREACH_API_KEY not configured. The user should provide their key so you can call bereach_set_api_key. Get a key at https://bereach.ai/token" }) }],
155
174
  };
156
175
  }
157
176