@volisphere/commercial 0.2.0 → 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/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;
@@ -187,6 +217,7 @@ export declare class VolisphereClient {
187
217
  private apiKey;
188
218
  constructor(baseURL: string, apiKey: string);
189
219
  get apiKeyValue(): string;
220
+ setApiKey(key: string): void;
190
221
  private get headers();
191
222
  getRecommendations(params: GetRecommendationsParams): Promise<GetRecommendationsResponse>;
192
223
  reportAction(params: ReportActionParams): Promise<ReportActionResponse>;
@@ -204,8 +235,16 @@ export declare class VolisphereClient {
204
235
  getDSPStats(days?: number): Promise<StatsResponse>;
205
236
  getBalance(): Promise<BalanceResponse>;
206
237
  }
238
+ export interface EarningsNotification {
239
+ amount: number;
240
+ currency: string;
241
+ ad_title?: string;
242
+ caller_name: string;
243
+ timestamp: string;
244
+ }
207
245
  export interface WSEventHandlers {
208
246
  onRequest?: (requestId: string, from: string, message: string, skillHint?: string) => Promise<string>;
247
+ onEarnings?: (earnings: EarningsNotification) => void;
209
248
  onConnected?: () => void;
210
249
  onDisconnected?: () => void;
211
250
  onError?: (error: Error) => void;
package/dist/client.js CHANGED
@@ -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();
@@ -224,6 +227,11 @@ export class GatewayWSClient {
224
227
  this.disconnect();
225
228
  break;
226
229
  }
230
+ case "earnings_notification": {
231
+ const earnings = msg.data;
232
+ this.handlers.onEarnings?.(earnings);
233
+ break;
234
+ }
227
235
  case "request": {
228
236
  const { request_id, from, message, skill_hint } = msg.data;
229
237
  if (this.handlers.onRequest) {
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 { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { join } from "node:path";
9
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
  }
@@ -180,19 +214,19 @@ function registerCampaignTools(api, client) {
180
214
  type: "number",
181
215
  description: "Total budget in micros",
182
216
  },
183
- bid_micros: {
217
+ default_bid_micros: {
184
218
  type: "number",
185
219
  description: "Bid per impression in micros",
186
220
  },
187
- billing_model: {
221
+ billing: {
188
222
  type: "string",
189
- enum: ["CPM", "CPC", "CPA"],
190
- description: "Billing model",
223
+ enum: ["cpi", "cpr", "cpa"],
224
+ description: "Billing model: cpi (per impression), cpr (per response), cpa (per action)",
191
225
  },
192
- categories: {
226
+ intent_categories: {
193
227
  type: "array",
194
228
  items: { type: "string" },
195
- description: "Target categories",
229
+ description: "Target categories (e.g. 'software-development', 'marketing')",
196
230
  },
197
231
  scenes: {
198
232
  type: "array",
@@ -204,9 +238,9 @@ function registerCampaignTools(api, client) {
204
238
  "name",
205
239
  "daily_budget_micros",
206
240
  "total_budget_micros",
207
- "bid_micros",
208
- "billing_model",
209
- "categories",
241
+ "default_bid_micros",
242
+ "billing",
243
+ "intent_categories",
210
244
  "scenes",
211
245
  ],
212
246
  },
@@ -215,10 +249,11 @@ function registerCampaignTools(api, client) {
215
249
  name: params.name,
216
250
  daily_budget_micros: params.daily_budget_micros,
217
251
  total_budget_micros: params.total_budget_micros,
218
- bid_micros: params.bid_micros,
219
- billing_model: params.billing_model,
220
- categories: params.categories,
252
+ default_bid_micros: params.default_bid_micros,
253
+ billing: params.billing,
254
+ intent_categories: params.intent_categories,
221
255
  scenes: params.scenes,
256
+ start_at: new Date().toISOString(),
222
257
  });
223
258
  return JSON.stringify(result, null, 2);
224
259
  },
@@ -249,12 +284,45 @@ function registerCampaignTools(api, client) {
249
284
  ],
250
285
  },
251
286
  execute: async (params) => {
252
- const result = await client.createAdEntity(params.campaign_id, {
253
- title: params.title,
254
- description: params.description,
255
- call_to_action: params.call_to_action,
256
- landing_url: params.landing_url,
257
- });
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);
258
326
  return JSON.stringify(result, null, 2);
259
327
  },
260
328
  });
@@ -437,7 +505,7 @@ function registerDashboardTools(api, client) {
437
505
  });
438
506
  }
439
507
  // ─── Lifecycle Tools (Enable / Disable) ─────────────────────────────────────
440
- function registerLifecycleTools(api, client) {
508
+ function registerLifecycleTools(api, client, apiURL) {
441
509
  // volisphere_enable — register agent + enable monetization + heartbeat
442
510
  api.registerTool({
443
511
  name: "volisphere_enable",
@@ -477,7 +545,7 @@ function registerLifecycleTools(api, client) {
477
545
  required: ["slug", "display_name"],
478
546
  },
479
547
  execute: async (params) => {
480
- // Step 1: Register agent
548
+ // Step 1: Register agent (public endpoint, no auth needed)
481
549
  const reg = await client.registerAgent({
482
550
  slug: params.slug,
483
551
  display_name: params.display_name,
@@ -485,20 +553,55 @@ function registerLifecycleTools(api, client) {
485
553
  skills: params.skills,
486
554
  });
487
555
  const agentId = reg.agent.id;
488
- // 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)
489
568
  try {
490
569
  await client.setMonetization(agentId, true);
491
570
  }
492
571
  catch {
493
572
  // May fail if no JWT — agent was registered publicly
494
573
  }
495
- // Step 3: Heartbeat (go online)
574
+ // Step 4: Heartbeat (go online)
496
575
  try {
497
576
  await client.heartbeat(agentId);
498
577
  }
499
578
  catch {
500
579
  // Non-critical
501
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
+ }
502
605
  return JSON.stringify({
503
606
  status: "enabled",
504
607
  agent_id: agentId,
@@ -506,6 +609,7 @@ function registerLifecycleTools(api, client) {
506
609
  display_name: reg.agent.display_name,
507
610
  claim_token: reg.claim_token,
508
611
  message: "Agent registered and commercial features enabled. " +
612
+ "API key auto-provisioned — no manual configuration needed. " +
509
613
  "Your agent is now online and can receive ad recommendations and invocations.",
510
614
  }, null, 2);
511
615
  },
@@ -643,7 +747,7 @@ function registerHooks(api, client) {
643
747
  '- "voli 推荐广告" / "拉取广告" / "get ads" → volisphere_get_recommendations',
644
748
  "",
645
749
  "### Smart Defaults (when user omits parameters)",
646
- "- create_campaign: daily_budget=10 VB (10,000,000 micros), total_budget=100 VB, bid=0.1 VB, billing_model=CPM",
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",
647
751
  "- my_earnings / my_spending: days=7",
648
752
  "- invoke: extract target agent slug and message from user's natural language",
649
753
  "",
@@ -654,7 +758,7 @@ function registerHooks(api, client) {
654
758
  }
655
759
  // ─── Entry Point ────────────────────────────────────────────────────────────
656
760
  export default {
657
- id: "volisphere-commercial",
761
+ id: "commercial",
658
762
  name: "Volisphere Commercial",
659
763
  description: "Monetize your OpenClaw agent with the Volisphere ad network",
660
764
  version: "0.1.0",
@@ -666,7 +770,7 @@ export default {
666
770
  registerCampaignTools(api, client);
667
771
  registerGatewayTools(api, client);
668
772
  registerDashboardTools(api, client);
669
- registerLifecycleTools(api, client);
773
+ registerLifecycleTools(api, client, apiURL);
670
774
  registerCommands(api, client);
671
775
  registerHooks(api, client);
672
776
  // Auto-connect WebSocket so this agent can RECEIVE invoke requests
@@ -676,6 +780,11 @@ export default {
676
780
  api.logger.info(`[Volisphere] Received invoke request ${requestId} from ${from}: ${message}`);
677
781
  return `Request received and being processed by ${api.pluginConfig?.["agent_slug"] ?? "this agent"}.`;
678
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
+ },
679
788
  onConnected: () => {
680
789
  api.logger.info("[Volisphere] Connected to Volisphere Gateway WebSocket — agent is now online and can receive invocations");
681
790
  },
@@ -1,5 +1,5 @@
1
1
  {
2
- "id": "volisphere-commercial",
2
+ "id": "commercial",
3
3
  "name": "Volisphere Commercial",
4
4
  "description": "Monetize your OpenClaw agent with the Volisphere ad network. Provides tools for ad recommendations, action reporting, and campaign management.",
5
5
  "version": "0.1.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volisphere/commercial",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
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",