clawmoney 0.8.2 → 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.
@@ -3,6 +3,21 @@ export declare function hubStartCommand(options: {
3
3
  }): Promise<void>;
4
4
  export declare function hubStopCommand(): Promise<void>;
5
5
  export declare function hubStatusCommand(): Promise<void>;
6
+ interface SearchOptions {
7
+ query?: string;
8
+ category?: string;
9
+ sort?: string;
10
+ limit?: string;
11
+ maxPrice?: string;
12
+ }
13
+ export declare function hubSearchCommand(options: SearchOptions): Promise<void>;
14
+ interface CallOptions {
15
+ agent: string;
16
+ skill: string;
17
+ input?: string;
18
+ timeout?: string;
19
+ }
20
+ export declare function hubCallCommand(options: CallOptions): Promise<void>;
6
21
  interface RegisterOptions {
7
22
  name: string;
8
23
  category: string;
@@ -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 {
@@ -94,6 +101,98 @@ export async function hubStatusCommand() {
94
101
  removePid();
95
102
  }
96
103
  }
104
+ export async function hubSearchCommand(options) {
105
+ const spinner = ora("Searching Hub...").start();
106
+ try {
107
+ const params = new URLSearchParams();
108
+ if (options.query)
109
+ params.set("q", options.query);
110
+ if (options.category)
111
+ params.set("category", options.category);
112
+ if (options.sort)
113
+ params.set("sort", options.sort);
114
+ if (options.limit)
115
+ params.set("limit", options.limit);
116
+ if (options.maxPrice)
117
+ params.set("max_price", options.maxPrice);
118
+ params.set("online_only", "true");
119
+ const resp = await apiGet(`/api/v1/hub/skills/search?${params}`);
120
+ if (!resp.ok) {
121
+ spinner.fail(chalk.red(`Search failed (${resp.status})`));
122
+ process.exit(1);
123
+ }
124
+ const skills = resp.data.data ?? [];
125
+ const count = resp.data.count ?? skills.length;
126
+ spinner.succeed(`Found ${count} service(s)`);
127
+ console.log("");
128
+ if (skills.length === 0) {
129
+ console.log(chalk.dim(" No services found. Try broader search terms."));
130
+ return;
131
+ }
132
+ console.log(chalk.bold(` ${"Agent".padEnd(18)} ${"Skill".padEnd(18)} ${"Category".padEnd(18)} ${"Price".padEnd(8)} ${"Rating".padEnd(8)} ${"Calls".padEnd(7)}`));
133
+ console.log(chalk.dim(" " + "-".repeat(79)));
134
+ for (const s of skills) {
135
+ const agent = (s.agent_name ?? s.agent_slug ?? "-").slice(0, 17);
136
+ const name = (s.skill_name ?? "-").slice(0, 17);
137
+ const category = (s.category ?? "-").slice(0, 17);
138
+ const price = s.price !== undefined ? `$${s.price.toFixed(3)}` : "-";
139
+ const rating = s.avg_rating != null ? s.avg_rating.toFixed(1) : "-";
140
+ const calls = String(s.total_calls ?? 0);
141
+ const online = s.agent_is_online ? chalk.green("●") : chalk.dim("○");
142
+ console.log(` ${online} ${chalk.cyan(agent.padEnd(17))} ${name.padEnd(18)} ${category.padEnd(18)} ${chalk.green(price.padEnd(8))} ${rating.padEnd(8)} ${calls.padEnd(7)}`);
143
+ }
144
+ }
145
+ catch (err) {
146
+ spinner.fail(chalk.red("Search failed"));
147
+ throw err;
148
+ }
149
+ }
150
+ export async function hubCallCommand(options) {
151
+ const config = requireConfig();
152
+ let inputData = {};
153
+ if (options.input) {
154
+ try {
155
+ inputData = JSON.parse(options.input);
156
+ }
157
+ catch {
158
+ console.error(chalk.red("Invalid JSON for --input. Example: '{\"prompt\":\"hello\"}'"));
159
+ process.exit(1);
160
+ }
161
+ }
162
+ const timeout = options.timeout ? parseInt(options.timeout, 10) : 60;
163
+ const spinner = ora(`Calling ${options.agent}/${options.skill}...`).start();
164
+ try {
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);
173
+ if (!resp.ok) {
174
+ const raw = resp.data && typeof resp.data === "object" && "detail" in resp.data
175
+ ? resp.data.detail
176
+ : resp.data;
177
+ const detail = typeof raw === "string" ? raw : JSON.stringify(raw);
178
+ spinner.fail(chalk.red(`Call failed (${resp.status}): ${detail}`));
179
+ process.exit(1);
180
+ }
181
+ const result = resp.data;
182
+ spinner.succeed(chalk.green("Call completed!"));
183
+ console.log("");
184
+ console.log(` ${chalk.bold("Order:")} ${result.id ?? "-"}`);
185
+ console.log(` ${chalk.bold("Duration:")} ${typeof result.duration === "number" ? result.duration.toFixed(1) + "s" : "-"}`);
186
+ console.log(` ${chalk.bold("Cost:")} $${typeof result.price === "number" ? result.price.toFixed(3) : "-"}`);
187
+ console.log("");
188
+ console.log(chalk.bold(" Output:"));
189
+ console.log(chalk.cyan(" " + JSON.stringify(result.output_data ?? result.output ?? {}, null, 2).replace(/\n/g, "\n ")));
190
+ }
191
+ catch (err) {
192
+ spinner.fail(chalk.red("Call failed"));
193
+ throw err;
194
+ }
195
+ }
97
196
  export async function hubRegisterCommand(options) {
98
197
  const config = requireConfig();
99
198
  const price = parseFloat(options.price);
@@ -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/index.js CHANGED
@@ -5,12 +5,12 @@ import { browseCommand } from './commands/browse.js';
5
5
  import { promoteSubmitCommand, promoteVerifyCommand } from './commands/promote.js';
6
6
  import { walletStatusCommand, walletBalanceCommand, walletAddressCommand, walletSendCommand, } from './commands/wallet.js';
7
7
  import { tweetCommand } from './commands/tweet.js';
8
- import { hubStartCommand, hubStopCommand, hubStatusCommand, hubRegisterCommand, hubSkillsCommand, } from './commands/hub.js';
8
+ import { hubStartCommand, hubStopCommand, hubStatusCommand, hubSearchCommand, hubCallCommand, hubRegisterCommand, hubSkillsCommand, } from './commands/hub.js';
9
9
  const program = new Command();
10
10
  program
11
11
  .name('clawmoney')
12
12
  .description('ClawMoney CLI -- Earn rewards with your AI agent')
13
- .version('0.1.0');
13
+ .version('0.8.2');
14
14
  // setup
15
15
  program
16
16
  .command('setup')
@@ -207,4 +207,37 @@ hub
207
207
  process.exit(1);
208
208
  }
209
209
  });
210
+ hub
211
+ .command('search')
212
+ .description('Search for agent services on the Hub')
213
+ .option('-q, --query <query>', 'Keyword search')
214
+ .option('-c, --category <category>', 'Category filter (e.g., generation/image)')
215
+ .option('-s, --sort <sort>', 'Sort by: rating, price, response_time', 'rating')
216
+ .option('-l, --limit <limit>', 'Number of results', '10')
217
+ .option('--max-price <price>', 'Maximum price filter')
218
+ .action(async (options) => {
219
+ try {
220
+ await hubSearchCommand(options);
221
+ }
222
+ catch (err) {
223
+ console.error(err.message);
224
+ process.exit(1);
225
+ }
226
+ });
227
+ hub
228
+ .command('call')
229
+ .description('Call another agent\'s service')
230
+ .requiredOption('-a, --agent <agent>', 'Target agent slug or ID')
231
+ .requiredOption('-s, --skill <skill>', 'Skill name to invoke')
232
+ .option('-i, --input <json>', 'Input parameters as JSON')
233
+ .option('-t, --timeout <seconds>', 'Timeout in seconds', '60')
234
+ .action(async (options) => {
235
+ try {
236
+ await hubCallCommand(options);
237
+ }
238
+ catch (err) {
239
+ console.error(err.message);
240
+ process.exit(1);
241
+ }
242
+ });
210
243
  program.parse();
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.2",
3
+ "version": "0.8.4",
4
4
  "description": "ClawMoney CLI -- Earn rewards with your AI agent",
5
5
  "type": "module",
6
6
  "bin": {