clawmoney 0.8.3 → 0.8.4

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.
@@ -41,8 +41,15 @@ export async function hubStartCommand(options) {
41
41
  const pid = readPid();
42
42
  if (pid && isPidAlive(pid)) {
43
43
  spinner.succeed(chalk.green(`Hub Provider started (PID ${pid})`));
44
+ const mode = config.provider?.execution_mode || "webhook";
44
45
  console.log(chalk.dim(` Log file: ${LOG_FILE}`));
45
- console.log(chalk.dim(` CLI command: ${options.cli || "claude"}`));
46
+ console.log(chalk.dim(` Mode: ${mode}`));
47
+ if (mode === "cli") {
48
+ console.log(chalk.dim(` CLI command: ${options.cli || config.provider?.cli_command || "claude"}`));
49
+ }
50
+ else {
51
+ console.log(chalk.dim(` Webhook: ${config.provider?.webhook_url || "http://127.0.0.1:18789/hooks/agent"}`));
52
+ }
46
53
  console.log(chalk.dim(` API key: ${config.api_key.slice(0, 8)}...`));
47
54
  }
48
55
  else {
@@ -155,7 +162,14 @@ export async function hubCallCommand(options) {
155
162
  const timeout = options.timeout ? parseInt(options.timeout, 10) : 60;
156
163
  const spinner = ora(`Calling ${options.agent}/${options.skill}...`).start();
157
164
  try {
158
- const resp = await apiPost(`/api/v1/hub/gateway/invoke?agent_id=${encodeURIComponent(options.agent)}&skill=${encodeURIComponent(options.skill)}&timeout=${timeout}&payment_method=ledger`, inputData, config.api_key);
165
+ // gateway/invoke takes agent_id, skill, timeout as query params; input_data as POST body
166
+ const qs = new URLSearchParams({
167
+ agent_id: options.agent,
168
+ skill: options.skill,
169
+ timeout: String(timeout),
170
+ payment_method: "ledger",
171
+ });
172
+ const resp = await apiPost(`/api/v1/hub/gateway/invoke?${qs}`, inputData, config.api_key);
159
173
  if (!resp.ok) {
160
174
  const raw = resp.data && typeof resp.data === "object" && "detail" in resp.data
161
175
  ? resp.data.detail
@@ -3,6 +3,7 @@ import { isProcessed, markProcessed } from "./dedup.js";
3
3
  import { replaceLocalPaths } from "./media.js";
4
4
  import { logger } from "./logger.js";
5
5
  const TIMEOUT_BUFFER_S = 15;
6
+ // ── Prompt builder ──
6
7
  function buildPrompt(call, config) {
7
8
  const skillConfig = config.provider.skills?.[call.skill];
8
9
  if (skillConfig?.prompt_template) {
@@ -13,13 +14,60 @@ function buildPrompt(call, config) {
13
14
  return [
14
15
  "You received a paid service request via ClawMoney Hub.",
15
16
  `Skill: ${call.skill}`,
17
+ `Category: ${call.category}`,
18
+ `From: ${call.from}`,
19
+ `Price: $${call.price}`,
16
20
  `Input: ${JSON.stringify(call.input, null, 2)}`,
17
21
  "",
18
22
  "Execute this task and return the result as JSON.",
19
- "If you generate any files, save them and include their paths in the output.",
23
+ "If you generate any files (images, videos, etc.), save them and include their file paths in the output.",
24
+ "Return ONLY the JSON result, no other text.",
20
25
  ].join("\n");
21
26
  }
22
- function runCli(command, prompt, timeoutMs) {
27
+ // ── OpenClaw Webhook execution ──
28
+ async function executeViaWebhook(call, config, prompt, timeoutS) {
29
+ const { webhook_url, webhook_token } = config.provider;
30
+ logger.info(`Executing via OpenClaw webhook: skill="${call.skill}" order=${call.order_id}`);
31
+ const controller = new AbortController();
32
+ const timer = setTimeout(() => controller.abort(), timeoutS * 1000);
33
+ try {
34
+ const resp = await fetch(webhook_url, {
35
+ method: "POST",
36
+ headers: {
37
+ "Content-Type": "application/json",
38
+ Authorization: `Bearer ${webhook_token}`,
39
+ },
40
+ body: JSON.stringify({
41
+ message: prompt,
42
+ name: `Hub: ${call.skill}`,
43
+ deliver: false, // don't send to messaging channel
44
+ timeoutSeconds: timeoutS,
45
+ }),
46
+ signal: controller.signal,
47
+ });
48
+ clearTimeout(timer);
49
+ if (!resp.ok) {
50
+ const text = await resp.text();
51
+ throw new Error(`Webhook returned ${resp.status}: ${text}`);
52
+ }
53
+ const text = await resp.text();
54
+ // Try to parse JSON from response
55
+ const parsed = parseJsonOutput(text);
56
+ if (parsed)
57
+ return parsed;
58
+ // If response is not JSON, wrap as text result
59
+ return { result: text.trim().slice(0, 5000) };
60
+ }
61
+ catch (err) {
62
+ clearTimeout(timer);
63
+ if (err instanceof Error && err.name === "AbortError") {
64
+ throw new Error(`Webhook timed out after ${timeoutS}s`);
65
+ }
66
+ throw err;
67
+ }
68
+ }
69
+ // ── CLI (claude -p) execution — fallback ──
70
+ function executeViaCli(command, prompt, timeoutMs) {
23
71
  return new Promise((resolve) => {
24
72
  const args = ["-p", prompt, "--output-format", "json"];
25
73
  const child = spawn(command, args, {
@@ -44,6 +92,7 @@ function runCli(command, prompt, timeoutMs) {
44
92
  });
45
93
  });
46
94
  }
95
+ // ── JSON parser ──
47
96
  function parseJsonOutput(raw) {
48
97
  try {
49
98
  return JSON.parse(raw);
@@ -62,6 +111,7 @@ function parseJsonOutput(raw) {
62
111
  }
63
112
  return null;
64
113
  }
114
+ // ── Executor ──
65
115
  export class Executor {
66
116
  config;
67
117
  send;
@@ -90,7 +140,7 @@ export class Executor {
90
140
  }
91
141
  markProcessed(call.order_id);
92
142
  this.activeTasks.add(call.order_id);
93
- logger.info(`Processing order=${call.order_id} skill="${call.skill}" from=${call.from}`);
143
+ logger.info(`Processing order=${call.order_id} skill="${call.skill}" from=${call.from} mode=${this.config.provider.execution_mode}`);
94
144
  this.executeTask(call).catch((err) => {
95
145
  logger.error(`Unhandled error in executeTask for ${call.order_id}:`, err);
96
146
  });
@@ -103,6 +153,7 @@ export class Executor {
103
153
  output: {
104
154
  echo: call.input,
105
155
  provider_status: "ok",
156
+ execution_mode: this.config.provider.execution_mode,
106
157
  active_tasks: this.activeTasks.size,
107
158
  max_concurrent: this.config.provider.max_concurrent,
108
159
  },
@@ -112,31 +163,32 @@ export class Executor {
112
163
  async executeTask(call) {
113
164
  try {
114
165
  const prompt = buildPrompt(call, this.config);
115
- const timeoutMs = Math.max((call.timeout - TIMEOUT_BUFFER_S) * 1000, 30_000);
116
- const command = this.config.provider.cli_command;
117
- logger.info(`Executing: ${command} for skill="${call.skill}" order=${call.order_id} (timeout=${Math.round(timeoutMs / 1000)}s)`);
118
- const { stdout, stderr, exitCode } = await runCli(command, prompt, timeoutMs);
119
- if (exitCode !== 0) {
120
- const errMsg = stderr.trim() || `CLI exited with code ${exitCode}`;
121
- logger.error(`CLI failed (code=${exitCode}):`, errMsg);
122
- this.send({
123
- event: "deliver",
124
- order_id: call.order_id,
125
- error: errMsg.slice(0, 2000),
126
- });
127
- return;
166
+ const timeoutS = Math.max(call.timeout - TIMEOUT_BUFFER_S, 30);
167
+ let output;
168
+ if (this.config.provider.execution_mode === "webhook") {
169
+ // Execute via OpenClaw webhook
170
+ output = await executeViaWebhook(call, this.config, prompt, timeoutS);
128
171
  }
129
- const parsed = parseJsonOutput(stdout);
130
- if (!parsed) {
131
- logger.warn("CLI output was not valid JSON, wrapping as text");
132
- this.send({
133
- event: "deliver",
134
- order_id: call.order_id,
135
- output: { result: stdout.trim().slice(0, 5000) },
136
- });
137
- return;
172
+ else {
173
+ // Execute via CLI (claude -p / openclaw -p)
174
+ const command = this.config.provider.cli_command;
175
+ logger.info(`Executing via CLI: ${command} for skill="${call.skill}" (timeout=${timeoutS}s)`);
176
+ const { stdout, stderr, exitCode } = await executeViaCli(command, prompt, timeoutS * 1000);
177
+ if (exitCode !== 0) {
178
+ const errMsg = stderr.trim() || `CLI exited with code ${exitCode}`;
179
+ logger.error(`CLI failed (code=${exitCode}):`, errMsg);
180
+ this.send({
181
+ event: "deliver",
182
+ order_id: call.order_id,
183
+ error: errMsg.slice(0, 2000),
184
+ });
185
+ return;
186
+ }
187
+ const parsed = parseJsonOutput(stdout);
188
+ output = parsed ?? { result: stdout.trim().slice(0, 5000) };
138
189
  }
139
- const output = await replaceLocalPaths(parsed, this.config);
190
+ // Upload local files to R2 if any
191
+ output = await replaceLocalPaths(output, this.config);
140
192
  const sent = this.send({
141
193
  event: "deliver",
142
194
  order_id: call.order_id,
@@ -11,7 +11,10 @@ const CONFIG_DIR = join(homedir(), ".clawmoney");
11
11
  const CONFIG_FILE = join(CONFIG_DIR, "config.yaml");
12
12
  const PID_FILE = join(CONFIG_DIR, "provider.pid");
13
13
  const DEFAULT_PROVIDER = {
14
+ execution_mode: "webhook",
14
15
  cli_command: "claude",
16
+ webhook_url: "http://127.0.0.1:18789/hooks/agent",
17
+ webhook_token: "",
15
18
  max_concurrent: 3,
16
19
  ws_url: "wss://api.bnbot.ai/api/v1/ws/agent",
17
20
  api_base_url: "https://api.bnbot.ai/api/v1",
@@ -73,7 +76,10 @@ function loadProviderConfig(cliCommand) {
73
76
  }
74
77
  const userProvider = (raw.provider ?? {});
75
78
  const provider = {
79
+ execution_mode: userProvider.execution_mode ?? DEFAULT_PROVIDER.execution_mode,
76
80
  cli_command: cliCommand ?? userProvider.cli_command ?? DEFAULT_PROVIDER.cli_command,
81
+ webhook_url: userProvider.webhook_url ?? DEFAULT_PROVIDER.webhook_url,
82
+ webhook_token: userProvider.webhook_token ?? DEFAULT_PROVIDER.webhook_token,
77
83
  max_concurrent: userProvider.max_concurrent ?? DEFAULT_PROVIDER.max_concurrent,
78
84
  ws_url: userProvider.ws_url ?? DEFAULT_PROVIDER.ws_url,
79
85
  api_base_url: userProvider.api_base_url ?? DEFAULT_PROVIDER.api_base_url,
@@ -159,5 +165,8 @@ export function runProvider(cliCommand) {
159
165
  wsClient.start();
160
166
  poller.start();
161
167
  logger.info("Hub Provider running. Listening for service calls...");
162
- logger.info(`Config: max_concurrent=${config.provider.max_concurrent}, cli=${config.provider.cli_command}`);
168
+ logger.info(`Config: mode=${config.provider.execution_mode}, max_concurrent=${config.provider.max_concurrent}` +
169
+ (config.provider.execution_mode === "webhook"
170
+ ? `, webhook=${config.provider.webhook_url}`
171
+ : `, cli=${config.provider.cli_command}`));
163
172
  }
@@ -38,7 +38,10 @@ export interface TestResponseEvent {
38
38
  }
39
39
  export type OutgoingEvent = DeliverEvent | TestResponseEvent;
40
40
  export interface ProviderSettings {
41
+ execution_mode: "webhook" | "cli";
41
42
  cli_command: string;
43
+ webhook_url: string;
44
+ webhook_token: string;
42
45
  max_concurrent: number;
43
46
  ws_url: string;
44
47
  api_base_url: string;
package/dist/utils/api.js CHANGED
@@ -39,12 +39,13 @@ export async function apiPost(path, body, apiKey) {
39
39
  headers: buildHeaders(apiKey),
40
40
  body: JSON.stringify(body),
41
41
  });
42
+ const text = await response.text();
42
43
  let data;
43
44
  try {
44
- data = (await response.json());
45
+ data = JSON.parse(text);
45
46
  }
46
47
  catch {
47
- data = (await response.text());
48
+ data = text;
48
49
  }
49
50
  return {
50
51
  ok: response.ok,
@@ -4,6 +4,14 @@ export interface ClawConfig {
4
4
  agent_slug: string;
5
5
  email?: string;
6
6
  wallet_address?: string;
7
+ provider?: {
8
+ execution_mode?: string;
9
+ cli_command?: string;
10
+ webhook_url?: string;
11
+ webhook_token?: string;
12
+ max_concurrent?: number;
13
+ [key: string]: unknown;
14
+ };
7
15
  }
8
16
  export declare function getConfigPath(): string;
9
17
  export declare function loadConfig(): ClawConfig | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmoney",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "ClawMoney CLI -- Earn rewards with your AI agent",
5
5
  "type": "module",
6
6
  "bin": {