clawmoney 0.17.8 → 0.17.9

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.
@@ -1 +1,3 @@
1
- export declare function marketSetupCommand(): Promise<void>;
1
+ export declare function marketSetupCommand(opts?: {
2
+ nested?: boolean;
3
+ }): Promise<void>;
@@ -88,10 +88,11 @@ function validatePrice(value) {
88
88
  return "Price looks unreasonable (> $10,000)";
89
89
  return undefined;
90
90
  }
91
- export async function marketSetupCommand() {
91
+ export async function marketSetupCommand(opts = {}) {
92
92
  // Step 0: ensure the agent is logged in. Mirrors relaySetupCommand's
93
93
  // handoff to setupCommand so first-time users get a clean flow instead
94
- // of "No config found" mid-wizard.
94
+ // of "No config found" mid-wizard. Skipped when nested under
95
+ // `clawmoney setup` since that command already guarantees a config.
95
96
  let existing = loadConfig();
96
97
  if (!existing) {
97
98
  await setupCommand();
@@ -103,24 +104,44 @@ export async function marketSetupCommand() {
103
104
  console.log("");
104
105
  }
105
106
  const config = existing;
106
- intro(chalk.cyan(" ClawMoney Market Setup "));
107
+ if (!opts.nested) {
108
+ intro(chalk.cyan(" ClawMoney Market Setup "));
109
+ }
107
110
  log.message("Register one or more skills on the Market so other agents can call (and pay) you.");
108
- // ── Step 1: multiselect categories (the big difference vs relay setup —
109
- // each picked category becomes one skill, no dupes within this run) ──
111
+ // ── Step 1: multiselect categories. Grouped visually: Instant first,
112
+ // then Escrow, then Auto. clack's multiselect has no native separators,
113
+ // so we fake them with disabled-looking header rows whose value is a
114
+ // sentinel — selecting one is a no-op (filtered out downstream). ──
115
+ const INSTANT_HEADER = "__hdr_instant__";
116
+ const ESCROW_HEADER = "__hdr_escrow__";
117
+ const AUTO_HEADER = "__hdr_auto__";
118
+ const HEADERS = new Set([INSTANT_HEADER, ESCROW_HEADER, AUTO_HEADER]);
119
+ const instantRows = CATEGORIES.filter((c) => c.routing === "instant");
120
+ const escrowRows = CATEGORIES.filter((c) => c.routing === "escrow");
121
+ const autoRows = CATEGORIES.filter((c) => c.routing === "auto");
122
+ const groupedOptions = [
123
+ { value: INSTANT_HEADER, label: chalk.dim("── Instant · poll for result ──"), hint: "" },
124
+ ...instantRows.map((row) => ({ value: row.value, label: ` ${row.value}`, hint: formatHint(row) })),
125
+ { value: ESCROW_HEADER, label: chalk.dim("── Escrow · manual approve ──"), hint: "" },
126
+ ...escrowRows.map((row) => ({ value: row.value, label: ` ${row.value}`, hint: formatHint(row) })),
127
+ { value: AUTO_HEADER, label: chalk.dim("── Auto · routed by price ──"), hint: "" },
128
+ ...autoRows.map((row) => ({ value: row.value, label: ` ${row.value}`, hint: formatHint(row) })),
129
+ ];
110
130
  const picked = await multiselect({
111
131
  message: "Pick the skill categories to register (space to toggle, enter to confirm):",
112
- options: CATEGORIES.map((row) => ({
113
- value: row.value,
114
- label: row.value,
115
- hint: formatHint(row),
116
- })),
132
+ options: groupedOptions,
117
133
  required: true,
118
134
  });
119
135
  if (isCancel(picked)) {
120
136
  cancel("Setup cancelled");
121
137
  process.exit(0);
122
138
  }
123
- const pickedCategories = picked;
139
+ // Strip header sentinels — they're visual-only group separators.
140
+ const pickedCategories = picked.filter((v) => !HEADERS.has(v));
141
+ if (pickedCategories.length === 0) {
142
+ cancel("No categories selected");
143
+ process.exit(0);
144
+ }
124
145
  // Preserve the canonical CATEGORIES order rather than the click order —
125
146
  // makes the per-skill prompts and the review table read consistently.
126
147
  const orderedRows = CATEGORIES.filter((c) => pickedCategories.includes(c.value));
@@ -224,7 +245,7 @@ export async function marketSetupCommand() {
224
245
  }
225
246
  const okCount = results.filter((r) => r.ok).length;
226
247
  const failCount = results.length - okCount;
227
- outro([
248
+ const summary = [
228
249
  failCount === 0
229
250
  ? chalk.green(`All ${okCount} skills registered.`)
230
251
  : okCount === 0
@@ -233,8 +254,16 @@ export async function marketSetupCommand() {
233
254
  "",
234
255
  chalk.dim(`Next: run ${chalk.cyan("clawmoney market start")} to accept incoming calls in the background.`),
235
256
  chalk.dim(` See your skills listed: ${chalk.cyan("clawmoney market skills")}`),
236
- ].join("\n"));
237
- if (failCount > 0) {
257
+ ].join("\n");
258
+ if (opts.nested) {
259
+ // Don't close the parent wizard's intro frame — emit the summary as a
260
+ // log message and let the parent wrap up the whole flow at the end.
261
+ log.message(summary);
262
+ }
263
+ else {
264
+ outro(summary);
265
+ }
266
+ if (failCount > 0 && !opts.nested) {
238
267
  process.exit(1);
239
268
  }
240
269
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Provider setup wizard. Assumes the agent is already registered
3
+ * (api_key in ~/.clawmoney/config.yaml). Callable on its own — used both
4
+ * as the post-register step of `clawmoney setup` and as a re-entry point
5
+ * for users who want to add roles after their first setup.
6
+ */
7
+ export declare function providerSetupWizard(): Promise<void>;
@@ -0,0 +1,94 @@
1
+ import { multiselect, isCancel, log, note, } from "@clack/prompts";
2
+ import chalk from "chalk";
3
+ import { loadConfig } from "../utils/config.js";
4
+ const ROLES = [
5
+ {
6
+ value: "market",
7
+ label: "Market skills",
8
+ hint: "image gen / code / translate / tts / ... — agents pay you per call",
9
+ },
10
+ {
11
+ value: "relay",
12
+ label: "Relay",
13
+ hint: "sell idle Claude Max / ChatGPT Pro / Gemini quota at 20% of API price",
14
+ },
15
+ {
16
+ value: "verifier",
17
+ label: "Verifier",
18
+ hint: "witness tweet promote tasks — $0.01 per verification, runs in background",
19
+ },
20
+ ];
21
+ /**
22
+ * Provider setup wizard. Assumes the agent is already registered
23
+ * (api_key in ~/.clawmoney/config.yaml). Callable on its own — used both
24
+ * as the post-register step of `clawmoney setup` and as a re-entry point
25
+ * for users who want to add roles after their first setup.
26
+ */
27
+ export async function providerSetupWizard() {
28
+ if (!loadConfig()) {
29
+ console.log(chalk.red("\n No agent config found. Run `clawmoney setup` first to register.\n"));
30
+ process.exit(1);
31
+ }
32
+ log.message(chalk.bold("Provider roles") +
33
+ chalk.dim(" — pick what you want to earn from. You can re-run this anytime."));
34
+ const picked = await multiselect({
35
+ message: "Provider roles (space to toggle, enter to confirm):",
36
+ options: ROLES.map((r) => ({
37
+ value: r.value,
38
+ label: r.label,
39
+ hint: r.hint,
40
+ })),
41
+ required: false, // user is allowed to skip — they may have just come to register
42
+ });
43
+ if (isCancel(picked)) {
44
+ log.message(chalk.dim("Skipped. Re-run `clawmoney setup` later to enable provider roles."));
45
+ return;
46
+ }
47
+ const roles = picked;
48
+ if (roles.length === 0) {
49
+ note([
50
+ chalk.dim("No roles enabled. You can still:"),
51
+ ` ${chalk.cyan("clawmoney browse")} browse engage tasks`,
52
+ ` ${chalk.cyan("clawmoney promote")} work on promote tasks`,
53
+ "",
54
+ chalk.dim("Or re-run `clawmoney setup` later to enable provider roles."),
55
+ ].join("\n"), "Done");
56
+ return;
57
+ }
58
+ // Sort picked roles in canonical ROLES order so the wizard always runs
59
+ // the same sequence (market → relay → verifier) regardless of click order.
60
+ const ordered = ROLES.filter((r) => roles.includes(r.value)).map((r) => r.value);
61
+ for (let i = 0; i < ordered.length; i++) {
62
+ const role = ordered[i];
63
+ log.step(`${chalk.bold(`[${i + 1}/${ordered.length}]`)} ${role}`);
64
+ try {
65
+ if (role === "market") {
66
+ const { marketSetupCommand } = await import("./market-setup.js");
67
+ await marketSetupCommand({ nested: true });
68
+ }
69
+ else if (role === "relay") {
70
+ const { relaySetupCommand } = await import("./relay-setup.js");
71
+ await relaySetupCommand();
72
+ }
73
+ else if (role === "verifier") {
74
+ const { verifierSetupCommand } = await import("./verifier-setup.js");
75
+ await verifierSetupCommand({ nested: true });
76
+ }
77
+ }
78
+ catch (err) {
79
+ log.error(`${role} setup failed: ${err.message}. ` +
80
+ `You can retry with \`clawmoney ${role} setup\` later.`);
81
+ // Don't abort the rest — let the user finish other roles. They can
82
+ // come back to the failed one separately.
83
+ }
84
+ }
85
+ note([
86
+ chalk.green(`${ordered.length} role${ordered.length === 1 ? "" : "s"} configured.`),
87
+ "",
88
+ chalk.dim("Useful next commands:"),
89
+ ` ${chalk.cyan("clawmoney market skills")} list your registered skills`,
90
+ ` ${chalk.cyan("clawmoney market start")} start the market provider daemon`,
91
+ ` ${chalk.cyan("clawmoney relay start")} start the relay daemon`,
92
+ ` ${chalk.cyan("tail -f ~/.clawmoney/*.log")} watch all daemons`,
93
+ ].join("\n"), "All done");
94
+ }
@@ -38,6 +38,22 @@ export async function setupCommand() {
38
38
  console.log('');
39
39
  return;
40
40
  }
41
+ // Short-circuit: if an agent is already configured, skip the OTP + claim
42
+ // flow and jump straight to provider role selection. This makes re-running
43
+ // `clawmoney setup` cheap — users add new provider roles without going
44
+ // through email verification every time.
45
+ const existing = loadConfig();
46
+ if (existing?.api_key && existing?.agent_slug) {
47
+ console.log(chalk.green(' Agent already configured.'));
48
+ console.log(chalk.dim(` Slug: ${existing.agent_slug}`));
49
+ if (existing.wallet_address) {
50
+ console.log(chalk.dim(` Wallet: ${existing.wallet_address}`));
51
+ }
52
+ console.log('');
53
+ const { providerSetupWizard } = await import('./provider-setup.js');
54
+ await providerSetupWizard();
55
+ return;
56
+ }
41
57
  // Step 1: Ask for email.
42
58
  const email = await prompt(chalk.cyan('? ') + 'Enter your email: ');
43
59
  if (!email || !email.includes('@')) {
@@ -228,9 +244,9 @@ export async function setupCommand() {
228
244
  console.log(chalk.dim(` Config: ${getConfigPath()}`));
229
245
  }
230
246
  console.log('');
231
- console.log(` Next steps:`);
232
- console.log(` ${chalk.cyan('clawmoney browse')} Browse available tasks`);
233
- console.log(` ${chalk.cyan('clawmoney wallet balance')} Check your wallet balance`);
234
- console.log(` ${chalk.cyan('clawmoney promote submit')} Submit a task proof`);
235
- console.log('');
247
+ // Continue into provider role selection. First-time users go straight
248
+ // from "agent claimed" → "pick what to earn from" without ever needing
249
+ // to remember a second command. They can still skip (no role = no role).
250
+ const { providerSetupWizard } = await import('./provider-setup.js');
251
+ await providerSetupWizard();
236
252
  }
@@ -0,0 +1,3 @@
1
+ export declare function verifierSetupCommand(opts?: {
2
+ nested?: boolean;
3
+ }): Promise<void>;
@@ -0,0 +1,84 @@
1
+ import { spawn } from "node:child_process";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { intro, outro, confirm, spinner, isCancel, log, note, } from "@clack/prompts";
5
+ import chalk from "chalk";
6
+ import { loadConfig } from "../utils/config.js";
7
+ const LOG_FILE = join(homedir(), ".clawmoney", "verifier.log");
8
+ // Auto-verifier polls every 15 minutes and verifies up to 3 tweets per cycle.
9
+ // Each verification pays $0.01 via x402 witness. Upper bound at 24/7 saturation
10
+ // is 3 × 4/hr × 24 × 30 = 8,640/mo, but real demand caps it much lower —
11
+ // we deliberately quote the per-cycle rate so users see honest numbers.
12
+ const VERIFICATIONS_PER_CYCLE = 3;
13
+ const POLL_INTERVAL_MIN = 15;
14
+ const PRICE_PER_VERIFICATION = 0.01;
15
+ function formatEarnings() {
16
+ const perHour = (60 / POLL_INTERVAL_MIN) * VERIFICATIONS_PER_CYCLE * PRICE_PER_VERIFICATION;
17
+ const perDay = perHour * 24;
18
+ const perMonth = perDay * 30;
19
+ return [
20
+ `Per cycle: $${(VERIFICATIONS_PER_CYCLE * PRICE_PER_VERIFICATION).toFixed(2)} (${VERIFICATIONS_PER_CYCLE} verifications × $${PRICE_PER_VERIFICATION})`,
21
+ `Cycle: every ${POLL_INTERVAL_MIN} minutes`,
22
+ `Upper bound: ~$${perHour.toFixed(2)}/hr · ~$${perDay.toFixed(2)}/day · ~$${perMonth.toFixed(0)}/mo`,
23
+ chalk.dim("Actual earnings depend on how many tasks are awaiting verification."),
24
+ ].join("\n");
25
+ }
26
+ export async function verifierSetupCommand(opts = {}) {
27
+ if (!loadConfig()) {
28
+ console.log(chalk.red("\n No config found. Run `clawmoney setup` first to register your agent.\n"));
29
+ process.exit(1);
30
+ }
31
+ if (!opts.nested) {
32
+ intro(chalk.cyan(" ClawMoney Verifier Setup "));
33
+ }
34
+ log.message("Run an auto-verifier daemon that earns by witnessing tweet promote tasks.");
35
+ note(formatEarnings(), "Earnings model");
36
+ const startNow = await confirm({
37
+ message: "Start the verifier daemon in the background now?",
38
+ initialValue: true,
39
+ });
40
+ if (isCancel(startNow)) {
41
+ log.message(chalk.dim("Skipped. Run `clawmoney promote auto-verify` later to start manually."));
42
+ if (!opts.nested)
43
+ outro("");
44
+ return;
45
+ }
46
+ if (!startNow) {
47
+ log.message(chalk.dim(`Skipped daemon launch. Manual start: ${chalk.cyan("clawmoney promote auto-verify")}`));
48
+ if (!opts.nested)
49
+ outro("");
50
+ return;
51
+ }
52
+ // Daemon launch: spawn detached so it survives this process exiting.
53
+ // stdout/stderr go to ~/.clawmoney/verifier.log — same pattern as relay
54
+ // daemon. We deliberately don't `setsid` here; users on macOS run from a
55
+ // GUI shell and the parent's session id is fine.
56
+ const claw = process.execPath; // node binary path; clawmoney bin script runs through it
57
+ const cliMain = process.argv[1]; // path to dist/index.js
58
+ const s = spinner();
59
+ s.start("Spawning verifier daemon...");
60
+ try {
61
+ const out = await import("node:fs").then((m) => m.openSync(LOG_FILE, "a"));
62
+ const child = spawn(claw, [cliMain, "promote", "auto-verify"], {
63
+ detached: true,
64
+ stdio: ["ignore", out, out],
65
+ env: process.env,
66
+ });
67
+ child.unref();
68
+ s.stop(`${chalk.green("✓")} Verifier daemon started (pid ${child.pid})`);
69
+ log.message([
70
+ chalk.dim(`Logs: ${LOG_FILE}`),
71
+ chalk.dim(`Tail: tail -f ${LOG_FILE}`),
72
+ chalk.dim(`Stop: kill ${child.pid}`),
73
+ "",
74
+ chalk.dim("Tip: to keep it running across reboots, wrap it in a launchd plist"),
75
+ chalk.dim("(same pattern as scripts/install-daemon-launchd.sh in this repo)."),
76
+ ].join("\n"));
77
+ }
78
+ catch (err) {
79
+ s.stop(chalk.red(`Failed to spawn verifier: ${err.message}`));
80
+ }
81
+ if (!opts.nested) {
82
+ outro(chalk.green("Verifier setup done."));
83
+ }
84
+ }
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ program
21
21
  // setup
22
22
  program
23
23
  .command('setup')
24
- .description('One-click agent onboarding: wallet + registration')
24
+ .description('One-stop setup: register agent (first run) + pick provider roles (market / relay / verifier)')
25
25
  .action(async () => {
26
26
  try {
27
27
  await setupCommand();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmoney",
3
- "version": "0.17.8",
3
+ "version": "0.17.9",
4
4
  "description": "ClawMoney CLI -- Earn rewards with your AI agent",
5
5
  "type": "module",
6
6
  "bin": {