clawmoney 0.17.8 → 0.17.10
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/commands/market-setup.d.ts +3 -1
- package/dist/commands/market-setup.js +32 -11
- package/dist/commands/provider-setup.d.ts +7 -0
- package/dist/commands/provider-setup.js +94 -0
- package/dist/commands/setup.js +21 -5
- package/dist/commands/verifier-setup.d.ts +3 -0
- package/dist/commands/verifier-setup.js +84 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -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,16 +104,28 @@ export async function marketSetupCommand() {
|
|
|
103
104
|
console.log("");
|
|
104
105
|
}
|
|
105
106
|
const config = existing;
|
|
106
|
-
|
|
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
|
|
109
|
-
//
|
|
111
|
+
// ── Step 1: multiselect categories. clack's multiselect has no native
|
|
112
|
+
// separators / disabled rows, so we don't try to draw section headers —
|
|
113
|
+
// any non-selectable row in the option list ends up looking selectable
|
|
114
|
+
// (with predictable user confusion). Instead we just sort so that all
|
|
115
|
+
// instant skills come first, then the single escrow, then the lone
|
|
116
|
+
// "auto" fallback, and embed the routing label directly into each row.
|
|
117
|
+
const orderedAll = [
|
|
118
|
+
...CATEGORIES.filter((c) => c.routing === "instant"),
|
|
119
|
+
...CATEGORIES.filter((c) => c.routing === "escrow"),
|
|
120
|
+
...CATEGORIES.filter((c) => c.routing === "auto"),
|
|
121
|
+
];
|
|
122
|
+
// Pad the category name column so the routing tag lines up vertically.
|
|
123
|
+
const nameColumnWidth = Math.max(...orderedAll.map((c) => c.value.length)) + 2;
|
|
110
124
|
const picked = await multiselect({
|
|
111
125
|
message: "Pick the skill categories to register (space to toggle, enter to confirm):",
|
|
112
|
-
options:
|
|
126
|
+
options: orderedAll.map((row) => ({
|
|
113
127
|
value: row.value,
|
|
114
|
-
label: row.value,
|
|
115
|
-
hint: formatHint(row),
|
|
128
|
+
label: `${row.value.padEnd(nameColumnWidth, " ")}${chalk.dim(formatHint(row))}`,
|
|
116
129
|
})),
|
|
117
130
|
required: true,
|
|
118
131
|
});
|
|
@@ -224,7 +237,7 @@ export async function marketSetupCommand() {
|
|
|
224
237
|
}
|
|
225
238
|
const okCount = results.filter((r) => r.ok).length;
|
|
226
239
|
const failCount = results.length - okCount;
|
|
227
|
-
|
|
240
|
+
const summary = [
|
|
228
241
|
failCount === 0
|
|
229
242
|
? chalk.green(`All ${okCount} skills registered.`)
|
|
230
243
|
: okCount === 0
|
|
@@ -233,8 +246,16 @@ export async function marketSetupCommand() {
|
|
|
233
246
|
"",
|
|
234
247
|
chalk.dim(`Next: run ${chalk.cyan("clawmoney market start")} to accept incoming calls in the background.`),
|
|
235
248
|
chalk.dim(` See your skills listed: ${chalk.cyan("clawmoney market skills")}`),
|
|
236
|
-
].join("\n")
|
|
237
|
-
if (
|
|
249
|
+
].join("\n");
|
|
250
|
+
if (opts.nested) {
|
|
251
|
+
// Don't close the parent wizard's intro frame — emit the summary as a
|
|
252
|
+
// log message and let the parent wrap up the whole flow at the end.
|
|
253
|
+
log.message(summary);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
outro(summary);
|
|
257
|
+
}
|
|
258
|
+
if (failCount > 0 && !opts.nested) {
|
|
238
259
|
process.exit(1);
|
|
239
260
|
}
|
|
240
261
|
}
|
|
@@ -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
|
+
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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,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-
|
|
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();
|