openclaw-adspirer 0.2.1 → 0.3.0

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
@@ -13,7 +13,7 @@ OpenClaw plugin for [Adspirer](https://adspirer.com) — manage Google, Meta, Ti
13
13
  ## Installation
14
14
 
15
15
  ```bash
16
- openclaw plugin install openclaw-adspirer
16
+ openclaw plugins install openclaw-adspirer
17
17
  ```
18
18
 
19
19
  ## Setup
@@ -48,6 +48,7 @@ openclaw adspirer status
48
48
  | `openclaw adspirer status` | Show auth status, connections, tool count |
49
49
  | `openclaw adspirer accounts` | List connected ad accounts |
50
50
  | `openclaw adspirer tools` | List registered tools by platform |
51
+ | `openclaw adspirer connect` | Open Adspirer to connect ad platforms |
51
52
 
52
53
  ## Tool Groups
53
54
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-adspirer",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "OpenClaw plugin for Adspirer — manage Google, Meta, TikTok & LinkedIn ads via natural language",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -14,7 +14,7 @@
14
14
  ],
15
15
  "scripts": {
16
16
  "check-types": "tsc --noEmit",
17
- "test": "ESBUILD_BINARY_PATH=./node_modules/@esbuild/linux-x64/bin/esbuild tsx --test tests/*.test.ts",
17
+ "test": "tsx --test tests/*.test.ts",
18
18
  "build": "tsc"
19
19
  },
20
20
  "peerDependencies": {
@@ -26,10 +26,8 @@
26
26
  ]
27
27
  },
28
28
  "devDependencies": {
29
- "@esbuild/linux-x64": "^0.27.2",
30
29
  "@types/node": "^22.0.0",
31
30
  "tsx": "^4.21.0",
32
- "typescript": "^5.9.3",
33
- "vitest": "^3.0.0"
31
+ "typescript": "^5.9.3"
34
32
  }
35
33
  }
@@ -16,6 +16,7 @@ import { readFileSync, writeFileSync } from "node:fs";
16
16
  import { join } from "node:path";
17
17
 
18
18
  const PLUGIN_ID = "openclaw-adspirer";
19
+ const PLUGIN_VERSION = "0.3.0";
19
20
 
20
21
  function getConfigPath(): string {
21
22
  return join(process.env.HOME || process.env.USERPROFILE || ".", ".openclaw", "openclaw.json");
@@ -85,6 +86,13 @@ export function registerCommands(
85
86
  .action(async () => {
86
87
  await handleTools(config);
87
88
  });
89
+
90
+ cmd
91
+ .command("connect")
92
+ .description("Open Adspirer to connect ad platforms")
93
+ .action(async () => {
94
+ await handleConnect();
95
+ });
88
96
  },
89
97
  { commands: ["adspirer"] },
90
98
  );
@@ -121,6 +129,7 @@ async function handleLogin(api: OpenClawPluginApi, config: AdspirerConfig): Prom
121
129
 
122
130
  // Persist client_id so we don't re-register every login
123
131
  patchPluginConfig({ oauthClientId: clientId });
132
+ config.oauthClientId = clientId;
124
133
  } catch (regErr: any) {
125
134
  callbackServer.close();
126
135
  throw new Error(`Client registration failed: ${regErr.message}`);
@@ -159,7 +168,7 @@ async function handleLogin(api: OpenClawPluginApi, config: AdspirerConfig): Prom
159
168
  // Browser open failed, user can use the URL
160
169
  }
161
170
 
162
- console.log("⏳ Waiting for authentication (30s timeout)...\n");
171
+ console.log("⏳ Waiting for authentication (120s timeout)...\n");
163
172
 
164
173
  const { code, state: returnedState } = await callbackServer.waitForCallback();
165
174
  callbackServer.close();
@@ -185,6 +194,7 @@ async function handleLogin(api: OpenClawPluginApi, config: AdspirerConfig): Prom
185
194
  });
186
195
 
187
196
  console.log(`✅ Connected to Adspirer! Restart the gateway to load all tools.`);
197
+ console.log(`\n 📌 Connect your ad platforms at https://www.adspirer.com`);
188
198
  } catch (err: any) {
189
199
  console.log(`❌ Login failed: ${err.message}`);
190
200
  }
@@ -200,6 +210,8 @@ async function handleLogout(api: OpenClawPluginApi): Promise<void> {
200
210
  }
201
211
 
202
212
  async function handleStatus(client: AdspirerMCPClient, config: AdspirerConfig): Promise<void> {
213
+ console.log(`\n📦 openclaw-adspirer v${PLUGIN_VERSION}\n`);
214
+
203
215
  if (config.testMode) {
204
216
  console.log("🧪 Test Mode Active");
205
217
  console.log(` Server: ${config.serverUrl}`);
@@ -230,6 +242,16 @@ async function handleStatus(client: AdspirerMCPClient, config: AdspirerConfig):
230
242
  console.log("\n⚠️ Could not fetch connection status.");
231
243
  }
232
244
 
245
+ // Try to get usage/quota status
246
+ try {
247
+ const result = await client.callTool("get_usage_status", {});
248
+ if (result.content?.[0]?.text) {
249
+ console.log(`\n📊 Plan & Usage:\n${result.content[0].text}`);
250
+ }
251
+ } catch {
252
+ // Quota info not available
253
+ }
254
+
233
255
  // Show tool count
234
256
  const tools = config.enabledTools.length > 0
235
257
  ? config.enabledTools
@@ -276,3 +298,22 @@ async function handleTools(config: AdspirerConfig): Promise<void> {
276
298
 
277
299
  console.log(`\nTotal enabled: ${total}`);
278
300
  }
301
+
302
+ async function handleConnect(): Promise<void> {
303
+ const url = "https://www.adspirer.com";
304
+ console.log("🔗 Connect your ad platforms at Adspirer:\n");
305
+ console.log(` ${url}\n`);
306
+ console.log(" Supported platforms:");
307
+ console.log(" • Google Ads");
308
+ console.log(" • Meta Ads");
309
+ console.log(" • LinkedIn Ads");
310
+ console.log(" • TikTok Ads");
311
+
312
+ try {
313
+ const { execFile } = await import("node:child_process");
314
+ const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
315
+ execFile(openCmd, [url], (err) => { /* ignore browser open failures */ });
316
+ } catch {
317
+ // Browser open failed, user can visit the URL manually
318
+ }
319
+ }
package/src/config.ts CHANGED
@@ -41,7 +41,8 @@ export function parseConfig(raw: unknown): AdspirerConfig {
41
41
  export function isAuthenticated(config: AdspirerConfig): boolean {
42
42
  if (config.testMode) return true;
43
43
  if (!config.accessToken) return false;
44
- if (config.tokenExpiresAt && config.tokenExpiresAt < Date.now()) return false;
44
+ // Allow expired tokens through if we have a refresh token — getToken() will refresh
45
+ if (config.tokenExpiresAt && config.tokenExpiresAt < Date.now() && !config.refreshToken) return false;
45
46
  return true;
46
47
  }
47
48
 
package/src/index.ts CHANGED
@@ -71,6 +71,18 @@ export default {
71
71
  api.logger,
72
72
  );
73
73
 
74
+ // If token is expired but we have a refresh token, refresh before registering tools
75
+ if (isAuthenticated(config) && config.tokenExpiresAt && config.tokenExpiresAt < Date.now()) {
76
+ try {
77
+ api.logger.info("adspirer: token expired, refreshing...");
78
+ const tokens = await doRefresh();
79
+ saveTokens(api, config, tokens);
80
+ api.logger.info("adspirer: token refreshed successfully");
81
+ } catch (err) {
82
+ api.logger.warn(`adspirer: token refresh failed: ${err}`);
83
+ }
84
+ }
85
+
74
86
  // Register tools
75
87
  if (isAuthenticated(config)) {
76
88
  try {
@@ -100,13 +112,15 @@ function saveTokens(api: OpenClawPluginApi, config: AdspirerConfig, tokens: Toke
100
112
  const path = require("node:path");
101
113
  const configPath = path.join(process.env.HOME || ".", ".openclaw", "openclaw.json");
102
114
  const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
103
- const entry = raw?.plugins?.entries?.["openclaw-adspirer"];
104
- if (entry?.config) {
105
- entry.config.accessToken = tokens.accessToken;
106
- entry.config.refreshToken = tokens.refreshToken;
107
- entry.config.tokenExpiresAt = tokens.expiresAt;
108
- fs.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
109
- }
115
+ if (!raw.plugins) raw.plugins = {};
116
+ if (!raw.plugins.entries) raw.plugins.entries = {};
117
+ if (!raw.plugins.entries["openclaw-adspirer"]) raw.plugins.entries["openclaw-adspirer"] = { enabled: true, config: {} };
118
+ if (!raw.plugins.entries["openclaw-adspirer"].config) raw.plugins.entries["openclaw-adspirer"].config = {};
119
+ const entry = raw.plugins.entries["openclaw-adspirer"];
120
+ entry.config.accessToken = tokens.accessToken;
121
+ entry.config.refreshToken = tokens.refreshToken;
122
+ entry.config.tokenExpiresAt = tokens.expiresAt;
123
+ fs.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
110
124
  } catch {
111
125
  api.logger.warn("Could not persist tokens to config file");
112
126
  }
@@ -101,7 +101,7 @@ export function registerConnectTool(api: OpenClawPluginApi): void {
101
101
  execute: async () => ({
102
102
  content: [{
103
103
  type: "text" as const,
104
- text: "🔑 You're not connected to Adspirer yet.\n\nRun `openclaw adspirer login` in your terminal to authenticate.\n\nOnce connected, you'll have access to 103 ad management tools across Google, Meta, TikTok, and LinkedIn.",
104
+ text: "🔑 You're not connected to Adspirer yet.\n\nRun `openclaw adspirer login` in your terminal to authenticate.\n\nThen connect your ad platforms at https://www.adspirer.com\n\nOnce connected, you'll have access to 103 ad management tools across Google, Meta, TikTok, and LinkedIn.",
105
105
  }],
106
106
  }),
107
107
  },