clawmoney 0.17.6 → 0.17.7

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.
@@ -0,0 +1 @@
1
+ export declare function marketSetupCommand(): Promise<void>;
@@ -0,0 +1,209 @@
1
+ import { intro, outro, select, text, confirm, spinner, isCancel, cancel, log, note, } from "@clack/prompts";
2
+ import chalk from "chalk";
3
+ import { apiPost } from "../utils/api.js";
4
+ import { loadConfig } from "../utils/config.js";
5
+ import { setupCommand } from "./setup.js";
6
+ const CATEGORIES = [
7
+ { value: "generation/image", routing: "instant", timeoutS: 120, suggestedPrice: 0.02, priceRange: [0.01, 0.50] },
8
+ { value: "generation/video", routing: "instant", timeoutS: 300, suggestedPrice: 0.10, priceRange: [0.05, 1.00] },
9
+ { value: "generation/video_long", routing: "escrow", timeoutS: null, suggestedPrice: 5.00, priceRange: [1.00, 50.00] },
10
+ { value: "generation/text", routing: "instant", timeoutS: 120, suggestedPrice: 0.01, priceRange: [0.005, 0.20] },
11
+ { value: "generation/audio", routing: "instant", timeoutS: 180, suggestedPrice: 0.05, priceRange: [0.02, 0.50] },
12
+ { value: "transformation/translate", routing: "instant", timeoutS: 60, suggestedPrice: 0.01, priceRange: [0.005, 0.10] },
13
+ { value: "transformation/tts", routing: "instant", timeoutS: 120, suggestedPrice: 0.02, priceRange: [0.01, 0.20] },
14
+ { value: "transformation/stt", routing: "instant", timeoutS: 120, suggestedPrice: 0.02, priceRange: [0.01, 0.20] },
15
+ { value: "search/web", routing: "instant", timeoutS: 60, suggestedPrice: 0.01, priceRange: [0.005, 0.10] },
16
+ { value: "analysis/data", routing: "instant", timeoutS: 180, suggestedPrice: 0.05, priceRange: [0.02, 0.50] },
17
+ { value: "coding/generation", routing: "instant", timeoutS: 240, suggestedPrice: 0.05, priceRange: [0.02, 0.50] },
18
+ { value: "coding/review", routing: "instant", timeoutS: 180, suggestedPrice: 0.05, priceRange: [0.02, 0.50] },
19
+ { value: "other", routing: "auto", timeoutS: null, suggestedPrice: 0.02, priceRange: [0.01, 1.00] },
20
+ ];
21
+ const PRICE_THRESHOLD_FOR_ESCROW = 1.0; // mirrors backend constant
22
+ function formatHint(row) {
23
+ if (row.routing === "escrow")
24
+ return "escrow · manual approve";
25
+ if (row.routing === "auto")
26
+ return "auto · by price";
27
+ return `instant · ${String(row.timeoutS).padStart(3, " ")}s timeout`;
28
+ }
29
+ // What skill_type will the backend resolve this to? Used for the review
30
+ // screen — the user gets to see and confirm the routing decision before
31
+ // commit. Keep this in sync with app/core/market_skill_routing.py.
32
+ function resolveSkillType(category, price) {
33
+ const row = CATEGORIES.find((c) => c.value === category);
34
+ if (!row)
35
+ return "instant";
36
+ if (row.routing === "instant")
37
+ return "instant";
38
+ if (row.routing === "escrow")
39
+ return "escrow";
40
+ return price > PRICE_THRESHOLD_FOR_ESCROW ? "escrow" : "instant";
41
+ }
42
+ function routingExplanation(skillType) {
43
+ if (skillType === "escrow") {
44
+ return [
45
+ "Callers fund the task up front, you accept, deliver,",
46
+ "and they approve to release funds. Good for tasks that",
47
+ "take minutes to hours.",
48
+ ].join("\n");
49
+ }
50
+ return [
51
+ "Callers invoke with x402 payment, you respond via WebSocket,",
52
+ "they poll for the result. Good for tasks that finish in",
53
+ "seconds to a few minutes.",
54
+ ].join("\n");
55
+ }
56
+ // ── Validators ──
57
+ // Skill names live in URLs (market/<agent_slug>/<skill_name>) and config
58
+ // files, so we keep them URL-safe and short. Same regex as backend slugs.
59
+ function validateSkillName(value) {
60
+ const v = value.trim();
61
+ if (!v)
62
+ return "Skill name is required";
63
+ if (v.length > 100)
64
+ return "Skill name must be 100 characters or fewer";
65
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(v)) {
66
+ return "Use lowercase letters, digits, and hyphens only (e.g. gen-image)";
67
+ }
68
+ return undefined;
69
+ }
70
+ function validateDescription(value) {
71
+ const v = value.trim();
72
+ if (!v)
73
+ return "Description is required";
74
+ if (v.length > 1000)
75
+ return "Description must be 1000 characters or fewer";
76
+ return undefined;
77
+ }
78
+ function validatePrice(value) {
79
+ const trimmed = value.trim();
80
+ if (!trimmed)
81
+ return "Price is required";
82
+ const n = Number(trimmed);
83
+ if (!Number.isFinite(n))
84
+ return "Price must be a number";
85
+ if (n < 0)
86
+ return "Price cannot be negative";
87
+ if (n > 10_000)
88
+ return "Price looks unreasonable (> $10,000)";
89
+ return undefined;
90
+ }
91
+ // ── Main wizard ──
92
+ export async function marketSetupCommand() {
93
+ // Step 0: ensure the agent is logged in. Mirrors relaySetupCommand's
94
+ // handoff to setupCommand so first-time users get a clean flow instead
95
+ // of "No config found" mid-wizard.
96
+ let existing = loadConfig();
97
+ if (!existing) {
98
+ await setupCommand();
99
+ existing = loadConfig();
100
+ if (!existing) {
101
+ console.log(chalk.red("\n Login did not complete. Run `clawmoney setup` manually, then re-run `clawmoney market setup`.\n"));
102
+ process.exit(1);
103
+ }
104
+ console.log("");
105
+ }
106
+ const config = existing;
107
+ intro(chalk.cyan(" ClawMoney Market Setup "));
108
+ log.message("Register a skill on the Market so other agents can call (and pay) you.");
109
+ // ── Step 1: category ──
110
+ const category = await select({
111
+ message: "Pick the skill category:",
112
+ options: CATEGORIES.map((row) => ({
113
+ value: row.value,
114
+ label: row.value,
115
+ hint: formatHint(row),
116
+ })),
117
+ initialValue: "generation/image",
118
+ });
119
+ if (isCancel(category)) {
120
+ cancel("Setup cancelled");
121
+ process.exit(0);
122
+ }
123
+ const categoryStr = category;
124
+ const categoryRow = CATEGORIES.find((c) => c.value === categoryStr);
125
+ // ── Step 2: skill name ──
126
+ const skillName = await text({
127
+ message: "Skill name (used in URLs, e.g. gen-image):",
128
+ placeholder: "gen-image",
129
+ validate: validateSkillName,
130
+ });
131
+ if (isCancel(skillName)) {
132
+ cancel("Setup cancelled");
133
+ process.exit(0);
134
+ }
135
+ const skillNameStr = skillName.trim();
136
+ // ── Step 3: description ──
137
+ const description = await text({
138
+ message: "One-line description (what does this skill do?):",
139
+ placeholder: "Generate a 1024x1024 image from a text prompt",
140
+ validate: validateDescription,
141
+ });
142
+ if (isCancel(description)) {
143
+ cancel("Setup cancelled");
144
+ process.exit(0);
145
+ }
146
+ const descriptionStr = description.trim();
147
+ // ── Step 4: price (suggested default per category) ──
148
+ const priceInput = await text({
149
+ message: `Price per call in USDC (suggested $${categoryRow.suggestedPrice.toFixed(2)}, range $${categoryRow.priceRange[0]}–$${categoryRow.priceRange[1]}):`,
150
+ placeholder: categoryRow.suggestedPrice.toFixed(2),
151
+ initialValue: categoryRow.suggestedPrice.toFixed(2),
152
+ validate: validatePrice,
153
+ });
154
+ if (isCancel(priceInput)) {
155
+ cancel("Setup cancelled");
156
+ process.exit(0);
157
+ }
158
+ const price = Number(priceInput.trim());
159
+ // ── Step 5: review (show the resolved skill_type so the user knows
160
+ // what they're agreeing to before commit) ──
161
+ const skillType = resolveSkillType(categoryStr, price);
162
+ const routingLabel = skillType === "escrow"
163
+ ? "escrow (manual approve required)"
164
+ : "instant (poll for result)";
165
+ note([
166
+ `Name: ${chalk.cyan(skillNameStr)}`,
167
+ `Category: ${chalk.cyan(categoryStr)}`,
168
+ `Price: ${chalk.green(`$${price.toFixed(2)} USDC / call`)}`,
169
+ `Description: "${descriptionStr}"`,
170
+ "",
171
+ `Routing: ${chalk.bold(routingLabel)}`,
172
+ chalk.dim(routingExplanation(skillType)),
173
+ ].join("\n"), "Review");
174
+ const proceed = await confirm({
175
+ message: "Confirm and register?",
176
+ initialValue: true,
177
+ });
178
+ if (isCancel(proceed) || !proceed) {
179
+ cancel("Setup cancelled — nothing was registered");
180
+ process.exit(0);
181
+ }
182
+ // ── Step 6: register ──
183
+ const submitSpin = spinner();
184
+ submitSpin.start("Registering skill...");
185
+ // Backend's AgentSkillCreate has extra='forbid', so we send ONLY the
186
+ // four allowed fields. skill_type is intentionally not sent — the server
187
+ // derives it from category and the routing rule we previewed above.
188
+ const resp = await apiPost("/api/v1/market/skills", {
189
+ skill_name: skillNameStr,
190
+ category: categoryStr,
191
+ description: descriptionStr,
192
+ price,
193
+ }, config.api_key);
194
+ if (!resp.ok) {
195
+ const raw = resp.data && typeof resp.data === "object" && "detail" in resp.data
196
+ ? resp.data.detail
197
+ : resp.data;
198
+ const detail = typeof raw === "string" ? raw : JSON.stringify(raw);
199
+ submitSpin.stop(chalk.red(`Failed (${resp.status}): ${detail}`));
200
+ process.exit(1);
201
+ }
202
+ submitSpin.stop(chalk.green("Skill registered."));
203
+ outro([
204
+ chalk.green("Done."),
205
+ "",
206
+ chalk.dim(`Next: run ${chalk.cyan("clawmoney market start")} to accept incoming calls in the background.`),
207
+ chalk.dim(` See your skill listed: ${chalk.cyan("clawmoney market skills")}`),
208
+ ].join("\n"));
209
+ }
package/dist/index.js CHANGED
@@ -228,6 +228,19 @@ program
228
228
  const market = program
229
229
  .command('market')
230
230
  .description('Agent Market: provide services, register skills');
231
+ market
232
+ .command('setup')
233
+ .description('Interactive: register a skill on the Market with a guided wizard (recommended for first-time setup)')
234
+ .action(async () => {
235
+ try {
236
+ const { marketSetupCommand } = await import('./commands/market-setup.js');
237
+ await marketSetupCommand();
238
+ }
239
+ catch (err) {
240
+ console.error(err.message);
241
+ process.exit(1);
242
+ }
243
+ });
231
244
  market
232
245
  .command('start')
233
246
  .description('Start Market Provider (background process)')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmoney",
3
- "version": "0.17.6",
3
+ "version": "0.17.7",
4
4
  "description": "ClawMoney CLI -- Earn rewards with your AI agent",
5
5
  "type": "module",
6
6
  "bin": {