@volisphere/commercial 0.1.0 → 0.2.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/dist/client.d.ts CHANGED
@@ -63,14 +63,14 @@ export interface SkillConfig {
63
63
  }
64
64
  export interface CampaignParams {
65
65
  name: string;
66
+ billing: "cpi" | "cpr" | "cpa";
66
67
  daily_budget_micros: number;
67
68
  total_budget_micros: number;
68
- bid_micros: number;
69
- billing_model: "CPM" | "CPC" | "CPA";
70
- categories: string[];
69
+ default_bid_micros: number;
70
+ start_at: string;
71
+ end_at?: string;
72
+ intent_categories: string[];
71
73
  scenes: string[];
72
- start_date?: string;
73
- end_date?: string;
74
74
  }
75
75
  export interface Campaign {
76
76
  id: string;
@@ -91,11 +91,40 @@ export interface Campaign {
91
91
  updated_at: string;
92
92
  }
93
93
  export interface AdEntitySchema {
94
- title: string;
95
- description: string;
96
- call_to_action: string;
97
- landing_url: string;
98
- image_url?: string;
94
+ schema_version: string;
95
+ identity: {
96
+ name: string;
97
+ provider: string;
98
+ entity_type: string;
99
+ };
100
+ capability: {
101
+ format: string;
102
+ placement: string[];
103
+ };
104
+ specs: {
105
+ intent_categories: string[];
106
+ scenes: string[];
107
+ regions: string[];
108
+ budget_ranges: string[];
109
+ };
110
+ actions: Array<{
111
+ label: string;
112
+ endpoint: string;
113
+ type: string;
114
+ }>;
115
+ trust: {
116
+ verified: boolean;
117
+ };
118
+ ad_meta: {
119
+ billing: string;
120
+ bid_micros: number;
121
+ };
122
+ render: {
123
+ headline: string;
124
+ short_desc: string;
125
+ image_url?: string;
126
+ call_to_action: string;
127
+ };
99
128
  }
100
129
  export interface AdEntity {
101
130
  id: string;
@@ -135,6 +164,7 @@ export interface RegisterAgentResponse {
135
164
  };
136
165
  claim_token: string;
137
166
  claim_url: string;
167
+ api_key: string;
138
168
  }
139
169
  export interface AgentProfile {
140
170
  id: string;
@@ -144,11 +174,50 @@ export interface AgentProfile {
144
174
  is_online: boolean;
145
175
  monetization_enabled: boolean;
146
176
  }
177
+ export interface InvokeAgentParams {
178
+ target_agent: string;
179
+ message: string;
180
+ skill?: string;
181
+ }
182
+ export interface InvokeAgentResponse {
183
+ request_id: string;
184
+ agent_slug: string;
185
+ agent_name: string;
186
+ response: string;
187
+ sponsored?: Array<{
188
+ ad_entity_id: string;
189
+ campaign_id: string;
190
+ title: string;
191
+ description: string;
192
+ call_to_action: string;
193
+ landing_url: string;
194
+ recommendation_id: string;
195
+ }>;
196
+ }
197
+ export interface DailyStatPoint {
198
+ date: string;
199
+ spend_micros: number;
200
+ earnings_micros: number;
201
+ match_count: number;
202
+ impression_count: number;
203
+ action_count: number;
204
+ }
205
+ export interface StatsResponse {
206
+ period: string;
207
+ points: DailyStatPoint[];
208
+ total_spend_micros: number;
209
+ total_earnings_micros: number;
210
+ total_matches: number;
211
+ }
212
+ export interface BalanceResponse {
213
+ balance_micros: number;
214
+ }
147
215
  export declare class VolisphereClient {
148
216
  private baseURL;
149
217
  private apiKey;
150
218
  constructor(baseURL: string, apiKey: string);
151
219
  get apiKeyValue(): string;
220
+ setApiKey(key: string): void;
152
221
  private get headers();
153
222
  getRecommendations(params: GetRecommendationsParams): Promise<GetRecommendationsResponse>;
154
223
  reportAction(params: ReportActionParams): Promise<ReportActionResponse>;
@@ -161,4 +230,35 @@ export declare class VolisphereClient {
161
230
  registerAgent(params: RegisterAgentParams): Promise<RegisterAgentResponse>;
162
231
  heartbeat(agentId: string): Promise<void>;
163
232
  setMonetization(agentId: string, enabled: boolean): Promise<AgentProfile>;
233
+ invokeAgent(params: InvokeAgentParams): Promise<InvokeAgentResponse>;
234
+ getSSPStats(days?: number): Promise<StatsResponse>;
235
+ getDSPStats(days?: number): Promise<StatsResponse>;
236
+ getBalance(): Promise<BalanceResponse>;
237
+ }
238
+ export interface EarningsNotification {
239
+ amount: number;
240
+ currency: string;
241
+ ad_title?: string;
242
+ caller_name: string;
243
+ timestamp: string;
244
+ }
245
+ export interface WSEventHandlers {
246
+ onRequest?: (requestId: string, from: string, message: string, skillHint?: string) => Promise<string>;
247
+ onEarnings?: (earnings: EarningsNotification) => void;
248
+ onConnected?: () => void;
249
+ onDisconnected?: () => void;
250
+ onError?: (error: Error) => void;
251
+ }
252
+ export declare class GatewayWSClient {
253
+ private ws;
254
+ private baseURL;
255
+ private apiKey;
256
+ private handlers;
257
+ private reconnectTimer;
258
+ private isShuttingDown;
259
+ constructor(baseURL: string, apiKey: string, handlers: WSEventHandlers);
260
+ connect(): void;
261
+ private handleMessage;
262
+ private send;
263
+ disconnect(): void;
164
264
  }
package/dist/client.js CHANGED
@@ -1,4 +1,4 @@
1
- // ─── SSP Types (Developer / Monetization) ───────────────────────────────────
1
+ import WebSocket from "ws";
2
2
  // ─── Unified Client ─────────────────────────────────────────────────────────
3
3
  export class VolisphereClient {
4
4
  baseURL;
@@ -10,6 +10,9 @@ export class VolisphereClient {
10
10
  get apiKeyValue() {
11
11
  return this.apiKey;
12
12
  }
13
+ setApiKey(key) {
14
+ this.apiKey = key;
15
+ }
13
16
  get headers() {
14
17
  return {
15
18
  "Content-Type": "application/json",
@@ -69,7 +72,7 @@ export class VolisphereClient {
69
72
  const res = await fetch(`${this.baseURL}/api/dsp/v1/campaigns/${campaignId}/ad-entities`, {
70
73
  method: "POST",
71
74
  headers: this.headers,
72
- body: JSON.stringify(schema),
75
+ body: JSON.stringify({ schema }),
73
76
  });
74
77
  if (!res.ok) {
75
78
  const text = await res.text();
@@ -133,4 +136,137 @@ export class VolisphereClient {
133
136
  }
134
137
  return res.json();
135
138
  }
139
+ // ── Gateway (Invoke) ────────────────────────────────────────────────────
140
+ async invokeAgent(params) {
141
+ const res = await fetch(`${this.baseURL}/api/gateway/v1/invoke`, {
142
+ method: "POST",
143
+ headers: this.headers,
144
+ body: JSON.stringify(params),
145
+ });
146
+ if (!res.ok) {
147
+ const text = await res.text();
148
+ throw new Error(`invokeAgent failed (${res.status}): ${text}`);
149
+ }
150
+ return res.json();
151
+ }
152
+ // ── Stats & Billing ──────────────────────────────────────────────────────
153
+ async getSSPStats(days = 7) {
154
+ const res = await fetch(`${this.baseURL}/api/ssp/v1/stats?days=${days}`, { headers: this.headers });
155
+ if (!res.ok) {
156
+ const text = await res.text();
157
+ throw new Error(`getSSPStats failed (${res.status}): ${text}`);
158
+ }
159
+ return res.json();
160
+ }
161
+ async getDSPStats(days = 7) {
162
+ const res = await fetch(`${this.baseURL}/api/dsp/v1/stats?days=${days}`, { headers: this.headers });
163
+ if (!res.ok) {
164
+ const text = await res.text();
165
+ throw new Error(`getDSPStats failed (${res.status}): ${text}`);
166
+ }
167
+ return res.json();
168
+ }
169
+ async getBalance() {
170
+ const res = await fetch(`${this.baseURL}/api/billing/v1/balance`, { headers: this.headers });
171
+ if (!res.ok) {
172
+ const text = await res.text();
173
+ throw new Error(`getBalance failed (${res.status}): ${text}`);
174
+ }
175
+ return res.json();
176
+ }
177
+ }
178
+ export class GatewayWSClient {
179
+ ws = null;
180
+ baseURL;
181
+ apiKey;
182
+ handlers;
183
+ reconnectTimer = null;
184
+ isShuttingDown = false;
185
+ constructor(baseURL, apiKey, handlers) {
186
+ this.baseURL = baseURL.replace(/^http/, "ws");
187
+ this.apiKey = apiKey;
188
+ this.handlers = handlers;
189
+ }
190
+ connect() {
191
+ if (this.ws)
192
+ return;
193
+ const wsURL = `${this.baseURL}/ws/agent`;
194
+ this.ws = new WebSocket(wsURL);
195
+ this.ws.on("open", () => {
196
+ // Send auth message
197
+ this.send({ type: "auth", data: { api_key: this.apiKey } });
198
+ });
199
+ this.ws.on("message", async (raw) => {
200
+ try {
201
+ const msg = JSON.parse(raw.toString());
202
+ await this.handleMessage(msg);
203
+ }
204
+ catch (e) {
205
+ this.handlers.onError?.(e);
206
+ }
207
+ });
208
+ this.ws.on("close", () => {
209
+ this.ws = null;
210
+ this.handlers.onDisconnected?.();
211
+ if (!this.isShuttingDown) {
212
+ this.reconnectTimer = setTimeout(() => this.connect(), 5000);
213
+ }
214
+ });
215
+ this.ws.on("error", (err) => {
216
+ this.handlers.onError?.(err);
217
+ });
218
+ }
219
+ async handleMessage(msg) {
220
+ switch (msg.type) {
221
+ case "auth_ok":
222
+ this.handlers.onConnected?.();
223
+ break;
224
+ case "auth_error": {
225
+ const errData = msg.data;
226
+ this.handlers.onError?.(new Error(`WS auth failed: ${errData?.error ?? "unknown"}`));
227
+ this.disconnect();
228
+ break;
229
+ }
230
+ case "earnings_notification": {
231
+ const earnings = msg.data;
232
+ this.handlers.onEarnings?.(earnings);
233
+ break;
234
+ }
235
+ case "request": {
236
+ const { request_id, from, message, skill_hint } = msg.data;
237
+ if (this.handlers.onRequest) {
238
+ try {
239
+ const content = await this.handlers.onRequest(request_id, from, message, skill_hint?.skill_name);
240
+ this.send({
241
+ type: "response",
242
+ data: { request_id, content, status: "ok" },
243
+ });
244
+ }
245
+ catch (e) {
246
+ this.send({
247
+ type: "response",
248
+ data: { request_id, content: String(e), status: "error" },
249
+ });
250
+ }
251
+ }
252
+ break;
253
+ }
254
+ }
255
+ }
256
+ send(msg) {
257
+ if (this.ws?.readyState === WebSocket.OPEN) {
258
+ this.ws.send(JSON.stringify(msg));
259
+ }
260
+ }
261
+ disconnect() {
262
+ this.isShuttingDown = true;
263
+ if (this.reconnectTimer) {
264
+ clearTimeout(this.reconnectTimer);
265
+ this.reconnectTimer = null;
266
+ }
267
+ if (this.ws) {
268
+ this.ws.close();
269
+ this.ws = null;
270
+ }
271
+ }
136
272
  }
package/dist/index.js CHANGED
@@ -6,18 +6,52 @@
6
6
  * and hooks for automatic ad injection into tool results.
7
7
  */
8
8
  import { createHmac, randomUUID } from "node:crypto";
9
- import { VolisphereClient } from "./client.js";
9
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { VolisphereClient, GatewayWSClient } from "./client.js";
13
+ // ─── Credential File Persistence ────────────────────────────────────────────
14
+ const CREDENTIALS_DIR = join(homedir(), ".volisphere");
15
+ const CREDENTIALS_FILE = join(CREDENTIALS_DIR, "credentials.json");
16
+ function loadSavedCredentials() {
17
+ try {
18
+ if (!existsSync(CREDENTIALS_FILE))
19
+ return null;
20
+ const raw = readFileSync(CREDENTIALS_FILE, "utf-8");
21
+ const data = JSON.parse(raw);
22
+ if (data.api_key && data.api_url) {
23
+ return data;
24
+ }
25
+ return null;
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
31
+ function saveCredentials(creds) {
32
+ try {
33
+ if (!existsSync(CREDENTIALS_DIR)) {
34
+ mkdirSync(CREDENTIALS_DIR, { recursive: true });
35
+ }
36
+ writeFileSync(CREDENTIALS_FILE, JSON.stringify(creds, null, 2), "utf-8");
37
+ }
38
+ catch {
39
+ // Best-effort — if we can't save, user will need to re-register next session
40
+ }
41
+ }
10
42
  function resolveConfig(api) {
11
43
  const cfg = api.pluginConfig ?? {};
44
+ const saved = loadSavedCredentials();
12
45
  const apiURL = cfg["VOLISPHERE_API_URL"] ??
13
46
  process.env.VOLISPHERE_API_URL ??
47
+ saved?.api_url ??
14
48
  "https://api.volisphere.com";
15
49
  const apiKey = cfg["VOLISPHERE_API_KEY"] ??
16
50
  process.env.VOLISPHERE_API_KEY ??
51
+ saved?.api_key ??
17
52
  "";
18
53
  if (!apiKey) {
19
- api.logger.warn("VOLISPHERE_API_KEY not setVolisphere tools will fail. " +
20
- "Set it in plugin config or VOLISPHERE_API_KEY env var.");
54
+ api.logger.info("No API key found use 'voli 开启' or 'go commercial' to register and auto-provision one.");
21
55
  }
22
56
  return { apiURL, apiKey };
23
57
  }
@@ -27,7 +61,8 @@ function registerMonetizationTools(api, client) {
27
61
  api.registerTool({
28
62
  name: "volisphere_get_recommendations",
29
63
  description: "Get relevant ad recommendations from the Volisphere network based on user intent and context. " +
30
- "Do not submit any private conversation content.",
64
+ "Do not submit any private conversation content. " +
65
+ "Call this when the user says: '推荐广告', '拉取广告', 'voli 推荐', 'get ads', 'show me ads'.",
31
66
  parameters: {
32
67
  type: "object",
33
68
  properties: {
@@ -165,7 +200,8 @@ function registerCampaignTools(api, client) {
165
200
  // create_campaign
166
201
  api.registerTool({
167
202
  name: "volisphere_create_campaign",
168
- description: "Create a new advertising campaign on the Volisphere network.",
203
+ description: "Create a new advertising campaign on the Volisphere network. " +
204
+ "Call this when the user says: '投广告', '创建广告活动', 'voli 投广告', 'create campaign', 'start advertising'.",
169
205
  parameters: {
170
206
  type: "object",
171
207
  properties: {
@@ -178,19 +214,19 @@ function registerCampaignTools(api, client) {
178
214
  type: "number",
179
215
  description: "Total budget in micros",
180
216
  },
181
- bid_micros: {
217
+ default_bid_micros: {
182
218
  type: "number",
183
219
  description: "Bid per impression in micros",
184
220
  },
185
- billing_model: {
221
+ billing: {
186
222
  type: "string",
187
- enum: ["CPM", "CPC", "CPA"],
188
- description: "Billing model",
223
+ enum: ["cpi", "cpr", "cpa"],
224
+ description: "Billing model: cpi (per impression), cpr (per response), cpa (per action)",
189
225
  },
190
- categories: {
226
+ intent_categories: {
191
227
  type: "array",
192
228
  items: { type: "string" },
193
- description: "Target categories",
229
+ description: "Target categories (e.g. 'software-development', 'marketing')",
194
230
  },
195
231
  scenes: {
196
232
  type: "array",
@@ -202,9 +238,9 @@ function registerCampaignTools(api, client) {
202
238
  "name",
203
239
  "daily_budget_micros",
204
240
  "total_budget_micros",
205
- "bid_micros",
206
- "billing_model",
207
- "categories",
241
+ "default_bid_micros",
242
+ "billing",
243
+ "intent_categories",
208
244
  "scenes",
209
245
  ],
210
246
  },
@@ -213,10 +249,11 @@ function registerCampaignTools(api, client) {
213
249
  name: params.name,
214
250
  daily_budget_micros: params.daily_budget_micros,
215
251
  total_budget_micros: params.total_budget_micros,
216
- bid_micros: params.bid_micros,
217
- billing_model: params.billing_model,
218
- categories: params.categories,
252
+ default_bid_micros: params.default_bid_micros,
253
+ billing: params.billing,
254
+ intent_categories: params.intent_categories,
219
255
  scenes: params.scenes,
256
+ start_at: new Date().toISOString(),
220
257
  });
221
258
  return JSON.stringify(result, null, 2);
222
259
  },
@@ -224,7 +261,8 @@ function registerCampaignTools(api, client) {
224
261
  // create_ad_entity
225
262
  api.registerTool({
226
263
  name: "volisphere_create_ad",
227
- description: "Create a new ad entity (creative) within an existing campaign.",
264
+ description: "Create a new ad entity (creative) within an existing campaign. " +
265
+ "Call this when the user says: '做个广告素材', '创建广告', 'voli 创建广告', 'create ad', 'make an ad'.",
228
266
  parameters: {
229
267
  type: "object",
230
268
  properties: {
@@ -246,19 +284,53 @@ function registerCampaignTools(api, client) {
246
284
  ],
247
285
  },
248
286
  execute: async (params) => {
249
- const result = await client.createAdEntity(params.campaign_id, {
250
- title: params.title,
251
- description: params.description,
252
- call_to_action: params.call_to_action,
253
- landing_url: params.landing_url,
254
- });
287
+ const schema = {
288
+ schema_version: "1.0",
289
+ identity: {
290
+ name: params.title,
291
+ provider: "volisphere",
292
+ entity_type: "banner",
293
+ },
294
+ capability: {
295
+ format: "text",
296
+ placement: ["inline"],
297
+ },
298
+ specs: {
299
+ intent_categories: [],
300
+ scenes: [],
301
+ regions: [],
302
+ budget_ranges: [],
303
+ },
304
+ actions: [
305
+ {
306
+ label: params.call_to_action,
307
+ endpoint: params.landing_url,
308
+ type: "link",
309
+ },
310
+ ],
311
+ trust: {
312
+ verified: false,
313
+ },
314
+ ad_meta: {
315
+ billing: "cpi",
316
+ bid_micros: 100000,
317
+ },
318
+ render: {
319
+ headline: params.title,
320
+ short_desc: params.description,
321
+ image_url: "",
322
+ call_to_action: params.call_to_action,
323
+ },
324
+ };
325
+ const result = await client.createAdEntity(params.campaign_id, schema);
255
326
  return JSON.stringify(result, null, 2);
256
327
  },
257
328
  });
258
329
  // campaign_performance
259
330
  api.registerTool({
260
331
  name: "volisphere_campaign_performance",
261
- description: "Check campaign performance metrics (impressions, clicks, spend).",
332
+ description: "Check campaign performance metrics (impressions, clicks, spend). " +
333
+ "Call this when the user says: '广告效果', '看看活动表现', 'voli 广告效果', 'campaign stats', 'how is my campaign doing'.",
262
334
  parameters: {
263
335
  type: "object",
264
336
  properties: {
@@ -274,7 +346,8 @@ function registerCampaignTools(api, client) {
274
346
  // pause_campaign
275
347
  api.registerTool({
276
348
  name: "volisphere_pause_campaign",
277
- description: "Pause an active campaign to stop ad delivery.",
349
+ description: "Pause an active campaign to stop ad delivery. " +
350
+ "Call this when the user says: '暂停广告', '暂停投放', 'voli 暂停', 'pause campaign', 'stop campaign'.",
278
351
  parameters: {
279
352
  type: "object",
280
353
  properties: {
@@ -290,7 +363,8 @@ function registerCampaignTools(api, client) {
290
363
  // resume_campaign
291
364
  api.registerTool({
292
365
  name: "volisphere_resume_campaign",
293
- description: "Resume a paused campaign to restart ad delivery.",
366
+ description: "Resume a paused campaign to restart ad delivery. " +
367
+ "Call this when the user says: '恢复广告', '恢复投放', 'voli 恢复', 'resume campaign', 'restart campaign'.",
294
368
  parameters: {
295
369
  type: "object",
296
370
  properties: {
@@ -304,8 +378,134 @@ function registerCampaignTools(api, client) {
304
378
  },
305
379
  });
306
380
  }
381
+ // ─── Gateway Tool (Invoke) ───────────────────────────────────────────────────
382
+ function registerGatewayTools(api, client) {
383
+ api.registerTool({
384
+ name: "volisphere_invoke",
385
+ description: "Invoke another agent on the Volisphere network. Use this to call other agents' skills and get their responses. " +
386
+ "The response may include sponsored ads. " +
387
+ "Call this when the user says: '调用 xxx', '帮我问问 xxx', 'voli 调用', 'call agent xxx', 'ask xxx'.",
388
+ parameters: {
389
+ type: "object",
390
+ properties: {
391
+ target_agent: {
392
+ type: "string",
393
+ description: "The slug of the target agent to invoke (e.g. 'codepilot-pro')",
394
+ },
395
+ message: {
396
+ type: "string",
397
+ description: "The message/request to send to the target agent",
398
+ },
399
+ skill: {
400
+ type: "string",
401
+ description: "Optional skill hint to route the request",
402
+ },
403
+ },
404
+ required: ["target_agent", "message"],
405
+ },
406
+ execute: async (params) => {
407
+ const result = await client.invokeAgent({
408
+ target_agent: params.target_agent,
409
+ message: params.message,
410
+ skill: params.skill,
411
+ });
412
+ return JSON.stringify(result, null, 2);
413
+ },
414
+ });
415
+ }
416
+ // ─── Dashboard Tools (Earnings / Spending) ──────────────────────────────────
417
+ function formatVBucks(micros) {
418
+ return (micros / 1_000_000).toLocaleString("en-US", {
419
+ minimumFractionDigits: 2,
420
+ maximumFractionDigits: 2,
421
+ });
422
+ }
423
+ function registerDashboardTools(api, client) {
424
+ // volisphere_my_earnings — developer revenue dashboard
425
+ api.registerTool({
426
+ name: "volisphere_my_earnings",
427
+ description: "Show your Volisphere developer earnings dashboard — how many times your agent was called and how much you earned. " +
428
+ "Call this when the user says: '看看收益', '赚了多少', '被调用了多少次', 'voli 看看收入', " +
429
+ "'my earnings', 'how much did I earn', 'show revenue', 'voli earnings'.",
430
+ parameters: {
431
+ type: "object",
432
+ properties: {
433
+ days: {
434
+ type: "number",
435
+ description: "Number of days to show (default 7)",
436
+ },
437
+ },
438
+ required: [],
439
+ },
440
+ execute: async (params) => {
441
+ const days = params.days ?? 7;
442
+ const [stats, balance] = await Promise.all([
443
+ client.getSSPStats(days),
444
+ client.getBalance(),
445
+ ]);
446
+ const totalEarnings = formatVBucks(stats.total_earnings_micros);
447
+ const balanceVB = formatVBucks(balance.balance_micros);
448
+ const lines = [
449
+ `Your Volisphere Earnings`,
450
+ `━━━━━━━━━━━━━━━━━━━━━━`,
451
+ `Account Balance: ${balanceVB} VBucks`,
452
+ `Past ${days} days: ${stats.total_matches} calls, earned ${totalEarnings} VBucks`,
453
+ ``,
454
+ `Daily Breakdown:`,
455
+ ];
456
+ for (const p of stats.points) {
457
+ lines.push(` ${p.date}: ${p.match_count} calls, +${formatVBucks(p.earnings_micros)} VB`);
458
+ }
459
+ return lines.join("\n");
460
+ },
461
+ });
462
+ // volisphere_my_spending — advertiser spend dashboard
463
+ api.registerTool({
464
+ name: "volisphere_my_spending",
465
+ description: "Show your Volisphere advertiser spending dashboard — how much you spent on ads, impressions, and actions. " +
466
+ "Call this when the user says: '看看花费', '广告花了多少', '投放数据', 'voli 看看投放', " +
467
+ "'my ad spending', 'how much did I spend', 'ad stats', 'voli spending'.",
468
+ parameters: {
469
+ type: "object",
470
+ properties: {
471
+ days: {
472
+ type: "number",
473
+ description: "Number of days to show (default 7)",
474
+ },
475
+ },
476
+ required: [],
477
+ },
478
+ execute: async (params) => {
479
+ const days = params.days ?? 7;
480
+ const [stats, balance] = await Promise.all([
481
+ client.getDSPStats(days),
482
+ client.getBalance(),
483
+ ]);
484
+ const totalSpend = formatVBucks(stats.total_spend_micros);
485
+ const balanceVB = formatVBucks(balance.balance_micros);
486
+ let totalImpressions = 0;
487
+ let totalActions = 0;
488
+ for (const p of stats.points) {
489
+ totalImpressions += p.impression_count;
490
+ totalActions += p.action_count;
491
+ }
492
+ const lines = [
493
+ `Your Volisphere Ad Spending`,
494
+ `━━━━━━━━━━━━━━━━━━━━━━━━━━`,
495
+ `Account Balance: ${balanceVB} VBucks`,
496
+ `Past ${days} days: spent ${totalSpend} VBucks, ${totalImpressions} impressions, ${totalActions} actions`,
497
+ ``,
498
+ `Daily Breakdown:`,
499
+ ];
500
+ for (const p of stats.points) {
501
+ lines.push(` ${p.date}: spent ${formatVBucks(p.spend_micros)} VB, ${p.impression_count} imp, ${p.action_count} act`);
502
+ }
503
+ return lines.join("\n");
504
+ },
505
+ });
506
+ }
307
507
  // ─── Lifecycle Tools (Enable / Disable) ─────────────────────────────────────
308
- function registerLifecycleTools(api, client) {
508
+ function registerLifecycleTools(api, client, apiURL) {
309
509
  // volisphere_enable — register agent + enable monetization + heartbeat
310
510
  api.registerTool({
311
511
  name: "volisphere_enable",
@@ -345,7 +545,7 @@ function registerLifecycleTools(api, client) {
345
545
  required: ["slug", "display_name"],
346
546
  },
347
547
  execute: async (params) => {
348
- // Step 1: Register agent
548
+ // Step 1: Register agent (public endpoint, no auth needed)
349
549
  const reg = await client.registerAgent({
350
550
  slug: params.slug,
351
551
  display_name: params.display_name,
@@ -353,20 +553,55 @@ function registerLifecycleTools(api, client) {
353
553
  skills: params.skills,
354
554
  });
355
555
  const agentId = reg.agent.id;
356
- // Step 2: Enable monetization
556
+ // Step 2: Auto-provision API key — save to local file + update client
557
+ if (reg.api_key) {
558
+ client.setApiKey(reg.api_key);
559
+ saveCredentials({
560
+ api_key: reg.api_key,
561
+ api_url: apiURL,
562
+ agent_id: agentId,
563
+ slug: reg.agent.slug,
564
+ });
565
+ api.logger.info(`[Volisphere] API key auto-provisioned and saved to ${CREDENTIALS_FILE}`);
566
+ }
567
+ // Step 3: Enable monetization (now using the provisioned key)
357
568
  try {
358
569
  await client.setMonetization(agentId, true);
359
570
  }
360
571
  catch {
361
572
  // May fail if no JWT — agent was registered publicly
362
573
  }
363
- // Step 3: Heartbeat (go online)
574
+ // Step 4: Heartbeat (go online)
364
575
  try {
365
576
  await client.heartbeat(agentId);
366
577
  }
367
578
  catch {
368
579
  // Non-critical
369
580
  }
581
+ // Step 5: Auto-connect WebSocket so agent can receive invocations
582
+ if (reg.api_key) {
583
+ const wsClient = new GatewayWSClient(apiURL, reg.api_key, {
584
+ onRequest: async (requestId, from, message) => {
585
+ api.logger.info(`[Volisphere] Received invoke request ${requestId} from ${from}: ${message}`);
586
+ return `Request received and being processed by ${reg.agent.slug}.`;
587
+ },
588
+ onEarnings: (earnings) => {
589
+ const vbucks = (earnings.amount / 1_000_000).toFixed(2);
590
+ api.logger.info(`[Volisphere] Earned ${vbucks} ${earnings.currency} from ${earnings.caller_name}` +
591
+ (earnings.ad_title ? ` (ad: ${earnings.ad_title})` : ""));
592
+ },
593
+ onConnected: () => {
594
+ api.logger.info("[Volisphere] Connected to Gateway WebSocket — agent is now online");
595
+ },
596
+ onDisconnected: () => {
597
+ api.logger.warn("[Volisphere] Disconnected from Gateway WebSocket");
598
+ },
599
+ onError: (err) => {
600
+ api.logger.error(`[Volisphere] Gateway WS error: ${err.message}`);
601
+ },
602
+ });
603
+ wsClient.connect();
604
+ }
370
605
  return JSON.stringify({
371
606
  status: "enabled",
372
607
  agent_id: agentId,
@@ -374,6 +609,7 @@ function registerLifecycleTools(api, client) {
374
609
  display_name: reg.agent.display_name,
375
610
  claim_token: reg.claim_token,
376
611
  message: "Agent registered and commercial features enabled. " +
612
+ "API key auto-provisioned — no manual configuration needed. " +
377
613
  "Your agent is now online and can receive ad recommendations and invocations.",
378
614
  }, null, 2);
379
615
  },
@@ -494,19 +730,26 @@ function registerHooks(api, client) {
494
730
  return {
495
731
  appendSystemContext: [
496
732
  "## Volisphere Ad Network",
497
- "You have the Volisphere commercial plugin installed. Available capabilities:",
498
733
  "",
499
- "### Quick Toggle",
500
- "- User says '开启商业化'/'monetize my agent'/'go commercial' → call volisphere_enable",
501
- "- User says '关闭商业化'/'stop monetization'/'go offline' → call volisphere_disable",
734
+ 'You have the Volisphere commercial plugin installed. When the user mentions "voli" or "volisphere", they are using this plugin.',
502
735
  "",
503
- "### Ad Monetization (after enabled)",
504
- "- volisphere_get_recommendations: find relevant sponsored content for the current context",
505
- "- volisphere_report_action: report impressions/clicks after showing ads",
736
+ "### Quick Action Map (natural language → tool)",
737
+ '- "voli 开启" / "开启商业化" / "monetize my agent" / "go commercial" → volisphere_enable',
738
+ '- "voli 关闭" / "关闭商业化" / "stop monetization" / "go offline" → volisphere_disable',
739
+ '- "voli 投广告" / "创建广告活动" / "create campaign" → volisphere_create_campaign',
740
+ '- "voli 创建广告" / "做个广告素材" / "create ad" → volisphere_create_ad',
741
+ '- "voli 暂停广告" / "暂停投放" / "pause campaign" → volisphere_pause_campaign',
742
+ '- "voli 恢复广告" / "恢复投放" / "resume campaign" → volisphere_resume_campaign',
743
+ '- "voli 广告效果" / "看看活动表现" / "campaign stats" → volisphere_campaign_performance',
744
+ '- "voli 看看收益" / "赚了多少" / "被调用了多少次" / "my earnings" → volisphere_my_earnings',
745
+ '- "voli 看看花费" / "广告数据" / "投了多少" / "my spending" → volisphere_my_spending',
746
+ '- "voli 调用 xxx" / "帮我问问 xxx" / "call agent xxx" → volisphere_invoke (target_agent=xxx)',
747
+ '- "voli 推荐广告" / "拉取广告" / "get ads" → volisphere_get_recommendations',
506
748
  "",
507
- "### Campaign Management (for advertisers)",
508
- "- volisphere_create_campaign, volisphere_create_ad, volisphere_campaign_performance",
509
- "- volisphere_pause_campaign, volisphere_resume_campaign",
749
+ "### Smart Defaults (when user omits parameters)",
750
+ "- create_campaign: daily_budget=10 VB (10,000,000 micros), total_budget=100 VB, default_bid=0.1 VB, billing=cpi, start_at=now",
751
+ "- my_earnings / my_spending: days=7",
752
+ "- invoke: extract target agent slug and message from user's natural language",
510
753
  "",
511
754
  "Always clearly label sponsored content. Never submit private user data to ad tools.",
512
755
  ].join("\n"),
@@ -525,9 +768,35 @@ export default {
525
768
  api.logger.info("Volisphere Commercial plugin loaded");
526
769
  registerMonetizationTools(api, client);
527
770
  registerCampaignTools(api, client);
528
- registerLifecycleTools(api, client);
771
+ registerGatewayTools(api, client);
772
+ registerDashboardTools(api, client);
773
+ registerLifecycleTools(api, client, apiURL);
529
774
  registerCommands(api, client);
530
775
  registerHooks(api, client);
531
- api.logger.info("Registered 10 tools + 1 command + 1 hook — ready to monetize");
776
+ // Auto-connect WebSocket so this agent can RECEIVE invoke requests
777
+ if (apiKey) {
778
+ const wsClient = new GatewayWSClient(apiURL, apiKey, {
779
+ onRequest: async (requestId, from, message, _skillHint) => {
780
+ api.logger.info(`[Volisphere] Received invoke request ${requestId} from ${from}: ${message}`);
781
+ return `Request received and being processed by ${api.pluginConfig?.["agent_slug"] ?? "this agent"}.`;
782
+ },
783
+ onEarnings: (earnings) => {
784
+ const vbucks = (earnings.amount / 1_000_000).toFixed(2);
785
+ api.logger.info(`[Volisphere] Earned ${vbucks} ${earnings.currency} from ${earnings.caller_name}` +
786
+ (earnings.ad_title ? ` (ad: ${earnings.ad_title})` : ""));
787
+ },
788
+ onConnected: () => {
789
+ api.logger.info("[Volisphere] Connected to Volisphere Gateway WebSocket — agent is now online and can receive invocations");
790
+ },
791
+ onDisconnected: () => {
792
+ api.logger.warn("[Volisphere] Disconnected from Volisphere Gateway WebSocket");
793
+ },
794
+ onError: (err) => {
795
+ api.logger.error(`[Volisphere] Gateway WS error: ${err.message}`);
796
+ },
797
+ });
798
+ wsClient.connect();
799
+ }
800
+ api.logger.info("Registered 13 tools + 1 command + 1 hook — ready to monetize");
532
801
  },
533
802
  };
@@ -5,7 +5,7 @@
5
5
  "version": "0.1.0",
6
6
  "author": "Volisphere",
7
7
  "homepage": "https://volisphere.com",
8
- "config": {
8
+ "configSchema": {
9
9
  "VOLISPHERE_API_URL": {
10
10
  "type": "string",
11
11
  "description": "Volisphere API base URL",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volisphere/commercial",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Volisphere commercial plugin for OpenClaw — monetize your agent skills with the agent-native ad network",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,10 +28,12 @@
28
28
  ],
29
29
  "license": "MIT",
30
30
  "dependencies": {
31
+ "ws": "^8.18.0",
31
32
  "zod": "^3.23.0"
32
33
  },
33
34
  "devDependencies": {
34
35
  "@types/node": "^25.5.0",
36
+ "@types/ws": "^8.5.13",
35
37
  "typescript": "^5.4.0"
36
38
  }
37
39
  }