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 +2 -1
- package/package.json +3 -5
- package/src/commands/cli.ts +42 -1
- package/src/config.ts +2 -1
- package/src/index.ts +21 -7
- package/src/tool-registry.ts +1 -1
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
|
|
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.
|
|
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": "
|
|
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
|
}
|
package/src/commands/cli.ts
CHANGED
|
@@ -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 (
|
|
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
|
|
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
|
-
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
}
|
package/src/tool-registry.ts
CHANGED
|
@@ -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
|
},
|