clawmoney 0.7.2 → 0.8.1
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/browse.js +19 -19
- package/dist/commands/hub.d.ts +14 -0
- package/dist/commands/hub.js +164 -0
- package/dist/commands/promote.d.ts +14 -0
- package/dist/commands/promote.js +169 -0
- package/dist/commands/setup.js +1 -1
- package/dist/hub/daemon.d.ts +7 -0
- package/dist/hub/daemon.js +18 -0
- package/dist/hub/dedup.d.ts +4 -0
- package/dist/hub/dedup.js +35 -0
- package/dist/hub/executor.d.ts +13 -0
- package/dist/hub/executor.js +164 -0
- package/dist/hub/logger.d.ts +5 -0
- package/dist/hub/logger.js +47 -0
- package/dist/hub/media.d.ts +11 -0
- package/dist/hub/media.js +70 -0
- package/dist/hub/poller.d.ts +14 -0
- package/dist/hub/poller.js +66 -0
- package/dist/hub/provider.d.ts +4 -0
- package/dist/hub/provider.js +163 -0
- package/dist/hub/types.d.ts +63 -0
- package/dist/hub/types.js +2 -0
- package/dist/hub/ws-client.d.ts +23 -0
- package/dist/hub/ws-client.js +123 -0
- package/dist/index.js +81 -11
- package/package.json +4 -2
package/dist/commands/browse.js
CHANGED
|
@@ -16,9 +16,9 @@ function truncate(str, maxLen) {
|
|
|
16
16
|
return '-';
|
|
17
17
|
return str.length > maxLen ? str.slice(0, maxLen - 1) + '...' : str;
|
|
18
18
|
}
|
|
19
|
-
function
|
|
19
|
+
function printEngageTable(tasks) {
|
|
20
20
|
if (tasks.length === 0) {
|
|
21
|
-
console.log(chalk.dim(' No
|
|
21
|
+
console.log(chalk.dim(' No engage tasks found.'));
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
// Header
|
|
@@ -34,9 +34,9 @@ function printBoostTable(tasks) {
|
|
|
34
34
|
console.log(` ${chalk.cyan(id.padEnd(8))} ${title.padEnd(30)} ${chalk.green(reward.padEnd(10))} ${budget.padEnd(10)} ${joined.padEnd(8)} ${status.padEnd(10)}`);
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
function
|
|
37
|
+
function printPromoteTable(tasks) {
|
|
38
38
|
if (tasks.length === 0) {
|
|
39
|
-
console.log(chalk.dim(' No
|
|
39
|
+
console.log(chalk.dim(' No promote tasks found.'));
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
// Header
|
|
@@ -55,46 +55,46 @@ function printHireTable(tasks) {
|
|
|
55
55
|
export async function browseCommand(options) {
|
|
56
56
|
const config = loadConfig();
|
|
57
57
|
const apiKey = config?.api_key;
|
|
58
|
-
const taskType = options.type || '
|
|
58
|
+
const taskType = options.type || 'engage';
|
|
59
59
|
const status = options.status || 'active';
|
|
60
60
|
const limit = parseInt(options.limit || '10', 10);
|
|
61
61
|
console.log('');
|
|
62
|
-
if (taskType === '
|
|
63
|
-
const
|
|
62
|
+
if (taskType === 'promote' || taskType === 'all') {
|
|
63
|
+
const promoteSpinner = ora('Fetching promote tasks...').start();
|
|
64
64
|
try {
|
|
65
|
-
const resp = await apiGet(`/api/v1/
|
|
65
|
+
const resp = await apiGet(`/api/v1/promote/?status=${status}&sort_by=total_budget&sort_order=desc&limit=${limit}`, apiKey);
|
|
66
66
|
if (!resp.ok) {
|
|
67
|
-
|
|
67
|
+
promoteSpinner.fail(`Failed to fetch promote tasks (${resp.status})`);
|
|
68
68
|
}
|
|
69
69
|
else {
|
|
70
70
|
const body = resp.data;
|
|
71
71
|
const tasks = (body.data || (Array.isArray(body) ? body : []));
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
promoteSpinner.succeed(`Promote Tasks (${tasks.length})`);
|
|
73
|
+
printPromoteTable(tasks);
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
catch (err) {
|
|
77
|
-
|
|
77
|
+
promoteSpinner.fail('Failed to fetch promote tasks');
|
|
78
78
|
console.error(chalk.red(err.message));
|
|
79
79
|
}
|
|
80
80
|
console.log('');
|
|
81
81
|
}
|
|
82
|
-
if (taskType === '
|
|
83
|
-
const
|
|
82
|
+
if (taskType === 'engage' || taskType === 'all') {
|
|
83
|
+
const engageSpinner = ora('Fetching engage tasks...').start();
|
|
84
84
|
try {
|
|
85
|
-
const resp = await apiGet(`/api/v1/
|
|
85
|
+
const resp = await apiGet(`/api/v1/engage/?status=${status}&limit=${limit}`, apiKey);
|
|
86
86
|
if (!resp.ok) {
|
|
87
|
-
|
|
87
|
+
engageSpinner.fail(`Failed to fetch engage tasks (${resp.status})`);
|
|
88
88
|
}
|
|
89
89
|
else {
|
|
90
90
|
const body = resp.data;
|
|
91
91
|
const tasks = (body.data || (Array.isArray(body) ? body : []));
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
engageSpinner.succeed(`Engage Tasks (${tasks.length})`);
|
|
93
|
+
printEngageTable(tasks);
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
catch (err) {
|
|
97
|
-
|
|
97
|
+
engageSpinner.fail('Failed to fetch engage tasks');
|
|
98
98
|
console.error(chalk.red(err.message));
|
|
99
99
|
}
|
|
100
100
|
console.log('');
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare function hubStartCommand(options: {
|
|
2
|
+
cli?: string;
|
|
3
|
+
}): Promise<void>;
|
|
4
|
+
export declare function hubStopCommand(): Promise<void>;
|
|
5
|
+
export declare function hubStatusCommand(): Promise<void>;
|
|
6
|
+
interface RegisterOptions {
|
|
7
|
+
name: string;
|
|
8
|
+
category: string;
|
|
9
|
+
description: string;
|
|
10
|
+
price: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function hubRegisterCommand(options: RegisterOptions): Promise<void>;
|
|
13
|
+
export declare function hubSkillsCommand(): Promise<void>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import ora from "ora";
|
|
6
|
+
import { requireConfig } from "../utils/config.js";
|
|
7
|
+
import { apiGet, apiPost } from "../utils/api.js";
|
|
8
|
+
import { readPid, isPidAlive, removePid } from "../hub/provider.js";
|
|
9
|
+
const LOG_FILE = join(homedir(), ".clawmoney", "provider.log");
|
|
10
|
+
// ── hub start ──
|
|
11
|
+
export async function hubStartCommand(options) {
|
|
12
|
+
const config = requireConfig();
|
|
13
|
+
// Check if already running
|
|
14
|
+
const existingPid = readPid();
|
|
15
|
+
if (existingPid && isPidAlive(existingPid)) {
|
|
16
|
+
console.log(chalk.yellow(`Hub Provider is already running (PID ${existingPid}). Use "clawmoney hub stop" first.`));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const spinner = ora("Starting Hub Provider...").start();
|
|
20
|
+
try {
|
|
21
|
+
// Spawn the daemon process using node with a special env flag
|
|
22
|
+
// The daemon will re-import provider.ts and call runProvider()
|
|
23
|
+
const daemonScript = join(import.meta.url.replace("file://", "").replace(/\/commands\/hub\.js$/, ""), "hub", "daemon.js");
|
|
24
|
+
const args = [daemonScript];
|
|
25
|
+
if (options.cli) {
|
|
26
|
+
args.push("--cli", options.cli);
|
|
27
|
+
}
|
|
28
|
+
const child = spawn(process.execPath, args, {
|
|
29
|
+
stdio: "ignore",
|
|
30
|
+
detached: true,
|
|
31
|
+
env: {
|
|
32
|
+
...process.env,
|
|
33
|
+
CLAWMONEY_DAEMON: "1",
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
child.unref();
|
|
37
|
+
// Give the daemon a moment to start and write PID
|
|
38
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
39
|
+
const pid = readPid();
|
|
40
|
+
if (pid && isPidAlive(pid)) {
|
|
41
|
+
spinner.succeed(chalk.green(`Hub Provider started (PID ${pid})`));
|
|
42
|
+
console.log(chalk.dim(` Log file: ${LOG_FILE}`));
|
|
43
|
+
console.log(chalk.dim(` CLI command: ${options.cli || "claude"}`));
|
|
44
|
+
console.log(chalk.dim(` API key: ${config.api_key.slice(0, 8)}...`));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
spinner.fail(chalk.red("Failed to start Hub Provider. Check logs at: " + LOG_FILE));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
spinner.fail(chalk.red("Failed to start Hub Provider"));
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// ── hub stop ──
|
|
57
|
+
export async function hubStopCommand() {
|
|
58
|
+
const pid = readPid();
|
|
59
|
+
if (!pid) {
|
|
60
|
+
console.log(chalk.dim("Hub Provider is not running (no PID file)."));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!isPidAlive(pid)) {
|
|
64
|
+
console.log(chalk.dim(`Hub Provider PID ${pid} is not alive. Cleaning up PID file.`));
|
|
65
|
+
removePid();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
process.kill(pid, "SIGTERM");
|
|
70
|
+
console.log(chalk.green(`Hub Provider stopped (PID ${pid}).`));
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
console.error(chalk.red(`Failed to stop process ${pid}:`), err.message);
|
|
74
|
+
}
|
|
75
|
+
// Wait briefly for cleanup, then ensure PID file is removed
|
|
76
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
77
|
+
removePid();
|
|
78
|
+
}
|
|
79
|
+
// ── hub status ──
|
|
80
|
+
export async function hubStatusCommand() {
|
|
81
|
+
const pid = readPid();
|
|
82
|
+
if (!pid) {
|
|
83
|
+
console.log(chalk.dim("Hub Provider is not running."));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (isPidAlive(pid)) {
|
|
87
|
+
console.log(chalk.green(`Hub Provider is running (PID ${pid}).`));
|
|
88
|
+
console.log(chalk.dim(` Log file: ${LOG_FILE}`));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
console.log(chalk.yellow(`Hub Provider PID ${pid} is not alive (stale PID file).`));
|
|
92
|
+
removePid();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export async function hubRegisterCommand(options) {
|
|
96
|
+
const config = requireConfig();
|
|
97
|
+
const price = parseFloat(options.price);
|
|
98
|
+
if (isNaN(price) || price < 0) {
|
|
99
|
+
console.error(chalk.red("Invalid price. Must be a non-negative number."));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
const spinner = ora("Registering skill...").start();
|
|
103
|
+
try {
|
|
104
|
+
const resp = await apiPost("/api/v1/hub/skills", {
|
|
105
|
+
name: options.name,
|
|
106
|
+
category: options.category,
|
|
107
|
+
description: options.description,
|
|
108
|
+
price_per_call: price,
|
|
109
|
+
}, config.api_key);
|
|
110
|
+
if (!resp.ok) {
|
|
111
|
+
const detail = resp.data && typeof resp.data === "object" && "detail" in resp.data
|
|
112
|
+
? resp.data.detail
|
|
113
|
+
: JSON.stringify(resp.data);
|
|
114
|
+
spinner.fail(chalk.red(`Failed to register skill (${resp.status}): ${detail}`));
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
spinner.succeed(chalk.green("Skill registered successfully!"));
|
|
118
|
+
console.log("");
|
|
119
|
+
console.log(` ${chalk.bold("Name:")} ${options.name}`);
|
|
120
|
+
console.log(` ${chalk.bold("Category:")} ${options.category}`);
|
|
121
|
+
console.log(` ${chalk.bold("Price:")} $${price.toFixed(2)}/call`);
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
spinner.fail(chalk.red("Failed to register skill"));
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
export async function hubSkillsCommand() {
|
|
129
|
+
const config = requireConfig();
|
|
130
|
+
const spinner = ora("Fetching skills...").start();
|
|
131
|
+
try {
|
|
132
|
+
const resp = await apiGet("/api/v1/hub/skills/mine", config.api_key);
|
|
133
|
+
if (!resp.ok) {
|
|
134
|
+
spinner.fail(chalk.red(`Failed to fetch skills (${resp.status})`));
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
const skills = Array.isArray(resp.data)
|
|
138
|
+
? resp.data
|
|
139
|
+
: resp.data.data ?? [];
|
|
140
|
+
spinner.succeed(`My Skills (${skills.length})`);
|
|
141
|
+
console.log("");
|
|
142
|
+
if (skills.length === 0) {
|
|
143
|
+
console.log(chalk.dim(' No skills registered. Use "clawmoney hub register" to add one.'));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// Table header
|
|
147
|
+
console.log(chalk.bold(` ${"Name".padEnd(20)} ${"Category".padEnd(20)} ${"Price".padEnd(10)} ${"Calls".padEnd(8)} ${"Status".padEnd(10)}`));
|
|
148
|
+
console.log(chalk.dim(" " + "-".repeat(70)));
|
|
149
|
+
for (const skill of skills) {
|
|
150
|
+
const name = (skill.name ?? "-").slice(0, 19);
|
|
151
|
+
const category = (skill.category ?? "-").slice(0, 19);
|
|
152
|
+
const price = skill.price_per_call !== undefined
|
|
153
|
+
? `$${skill.price_per_call.toFixed(2)}`
|
|
154
|
+
: "-";
|
|
155
|
+
const calls = String(skill.call_count ?? "-");
|
|
156
|
+
const status = skill.status ?? "-";
|
|
157
|
+
console.log(` ${chalk.cyan(name.padEnd(20))} ${category.padEnd(20)} ${chalk.green(price.padEnd(10))} ${calls.padEnd(8)} ${status.padEnd(10)}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
spinner.fail(chalk.red("Failed to fetch skills"));
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface SubmitOptions {
|
|
2
|
+
url: string;
|
|
3
|
+
platform?: string;
|
|
4
|
+
text?: string;
|
|
5
|
+
}
|
|
6
|
+
interface VerifyOptions {
|
|
7
|
+
witness?: boolean;
|
|
8
|
+
relevance: string;
|
|
9
|
+
quality: string;
|
|
10
|
+
vote?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function promoteSubmitCommand(taskId: string, options: SubmitOptions): Promise<void>;
|
|
13
|
+
export declare function promoteVerifyCommand(submissionId: string, options: VerifyOptions): Promise<void>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { apiGet, apiPost } from '../utils/api.js';
|
|
4
|
+
import { awalExec } from '../utils/awal.js';
|
|
5
|
+
import { requireConfig } from '../utils/config.js';
|
|
6
|
+
export async function promoteSubmitCommand(taskId, options) {
|
|
7
|
+
const config = requireConfig();
|
|
8
|
+
// 自动检测平台(从 task 获取)
|
|
9
|
+
let platform = options.platform;
|
|
10
|
+
if (!platform) {
|
|
11
|
+
try {
|
|
12
|
+
const taskResp = await apiGet(`/api/v1/promote/${taskId}`, config.api_key);
|
|
13
|
+
if (taskResp.ok && taskResp.data.platform) {
|
|
14
|
+
platform = taskResp.data.platform;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch { /* ignore */ }
|
|
18
|
+
}
|
|
19
|
+
if (!platform)
|
|
20
|
+
platform = 'twitter';
|
|
21
|
+
console.log('');
|
|
22
|
+
const spinner = ora(`Submitting proof for task ${taskId.slice(0, 8)}...`).start();
|
|
23
|
+
try {
|
|
24
|
+
const body = {
|
|
25
|
+
platform,
|
|
26
|
+
proof_url: options.url,
|
|
27
|
+
};
|
|
28
|
+
if (options.text) {
|
|
29
|
+
body.content_text = options.text;
|
|
30
|
+
}
|
|
31
|
+
const resp = await apiPost(`/api/v1/promote/${taskId}/submit`, body, config.api_key);
|
|
32
|
+
if (!resp.ok) {
|
|
33
|
+
spinner.fail('Submission failed');
|
|
34
|
+
const detail = typeof resp.data === 'object' ? (resp.data.detail || JSON.stringify(resp.data)) : String(resp.data);
|
|
35
|
+
console.error(chalk.red(` ${detail}`));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
spinner.succeed('Proof submitted');
|
|
39
|
+
if (resp.data.id) {
|
|
40
|
+
console.log(chalk.dim(` Submission ID: ${resp.data.id}`));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
spinner.fail('Submission failed');
|
|
45
|
+
console.error(chalk.red(err.message));
|
|
46
|
+
}
|
|
47
|
+
console.log('');
|
|
48
|
+
}
|
|
49
|
+
export async function promoteVerifyCommand(submissionId, options) {
|
|
50
|
+
const config = requireConfig();
|
|
51
|
+
console.log('');
|
|
52
|
+
if (options.witness) {
|
|
53
|
+
// Get submission to extract proof_url
|
|
54
|
+
const subSpinner = ora('Fetching submission...').start();
|
|
55
|
+
let proofUrl = '';
|
|
56
|
+
try {
|
|
57
|
+
// Try to get submission details - submissionId might be used directly
|
|
58
|
+
const resp = await apiGet(`/api/v1/promote/submissions/${submissionId}`, config.api_key);
|
|
59
|
+
if (resp.ok && resp.data.proof_url) {
|
|
60
|
+
proofUrl = resp.data.proof_url;
|
|
61
|
+
subSpinner.succeed(`Proof URL: ${proofUrl}`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
subSpinner.warn('Could not fetch submission, will need tweet ID');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
subSpinner.warn('Could not fetch submission');
|
|
69
|
+
}
|
|
70
|
+
// Extract tweet ID
|
|
71
|
+
let tweetId = '';
|
|
72
|
+
if (proofUrl) {
|
|
73
|
+
const match = proofUrl.match(/status\/(\d+)/);
|
|
74
|
+
if (match)
|
|
75
|
+
tweetId = match[1];
|
|
76
|
+
}
|
|
77
|
+
if (!tweetId) {
|
|
78
|
+
console.error(chalk.red(' Could not extract tweet ID from proof URL'));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Fetch witness proof via x402
|
|
82
|
+
const witnessSpinner = ora('Fetching witness proof via x402 ($0.01)...').start();
|
|
83
|
+
let witnessData;
|
|
84
|
+
try {
|
|
85
|
+
witnessData = await awalExec([
|
|
86
|
+
'x402', 'pay', `https://witness.bnbot.ai/x/${tweetId}`,
|
|
87
|
+
]);
|
|
88
|
+
witnessSpinner.succeed('Witness proof obtained');
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
witnessSpinner.fail('Witness fetch failed');
|
|
92
|
+
console.error(chalk.red(err.message));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Parse witness response — awalExec wraps: { success, data: { status, data: { code, data: {...}, proof: {...} } } }
|
|
96
|
+
const proof = witnessData?.data?.data?.proof || witnessData?.data?.proof || witnessData?.proof;
|
|
97
|
+
if (!proof) {
|
|
98
|
+
console.error(chalk.red(' No proof in witness response'));
|
|
99
|
+
console.log(chalk.dim(` Raw: ${JSON.stringify(witnessData).slice(0, 200)}`));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// Submit witness verification
|
|
103
|
+
const vote = options.vote || 'approve';
|
|
104
|
+
const relevanceScore = parseInt(options.relevance, 10);
|
|
105
|
+
const qualityScore = parseInt(options.quality, 10);
|
|
106
|
+
const verifySpinner = ora(`Submitting witness verification (${vote}, R:${relevanceScore} Q:${qualityScore})...`).start();
|
|
107
|
+
try {
|
|
108
|
+
const resp = await apiPost(`/api/v1/promote/submissions/${submissionId}/verify`, {
|
|
109
|
+
vote,
|
|
110
|
+
relevance_score: relevanceScore,
|
|
111
|
+
quality_score: qualityScore,
|
|
112
|
+
tweet_proof: {
|
|
113
|
+
payload: proof.payload,
|
|
114
|
+
signature: proof.signature,
|
|
115
|
+
signer: proof.signer,
|
|
116
|
+
timestamp: proof.timestamp,
|
|
117
|
+
},
|
|
118
|
+
}, config.api_key);
|
|
119
|
+
if (!resp.ok) {
|
|
120
|
+
verifySpinner.fail('Verification failed');
|
|
121
|
+
const detail = typeof resp.data === 'object' ? (resp.data.detail || JSON.stringify(resp.data)) : String(resp.data);
|
|
122
|
+
console.error(chalk.red(` ${detail}`));
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
verifySpinner.succeed('Witness verification submitted');
|
|
126
|
+
if (resp.data.id) {
|
|
127
|
+
console.log(chalk.dim(` Verification ID: ${resp.data.id}`));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
verifySpinner.fail('Verification failed');
|
|
133
|
+
console.error(chalk.red(err.message));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
// Manual verification
|
|
138
|
+
const vote = options.vote || 'approve';
|
|
139
|
+
const relevanceScore = parseInt(options.relevance, 10);
|
|
140
|
+
const qualityScore = parseInt(options.quality, 10);
|
|
141
|
+
const spinner = ora(`Submitting manual verification (${vote}, R:${relevanceScore} Q:${qualityScore})...`).start();
|
|
142
|
+
try {
|
|
143
|
+
const resp = await apiPost(`/api/v1/promote/submissions/${submissionId}/verify`, {
|
|
144
|
+
vote,
|
|
145
|
+
relevance_score: relevanceScore,
|
|
146
|
+
quality_score: qualityScore,
|
|
147
|
+
views: 0,
|
|
148
|
+
likes: 0,
|
|
149
|
+
comments: 0,
|
|
150
|
+
}, config.api_key);
|
|
151
|
+
if (!resp.ok) {
|
|
152
|
+
spinner.fail('Verification failed');
|
|
153
|
+
const detail = typeof resp.data === 'object' ? (resp.data.detail || JSON.stringify(resp.data)) : String(resp.data);
|
|
154
|
+
console.error(chalk.red(` ${detail}`));
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
spinner.succeed('Manual verification submitted');
|
|
158
|
+
if (resp.data.id) {
|
|
159
|
+
console.log(chalk.dim(` Verification ID: ${resp.data.id}`));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
spinner.fail('Verification failed');
|
|
165
|
+
console.error(chalk.red(err.message));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
console.log('');
|
|
169
|
+
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -203,6 +203,6 @@ export async function setupCommand() {
|
|
|
203
203
|
console.log(` Next steps:`);
|
|
204
204
|
console.log(` ${chalk.cyan('clawmoney browse')} Browse available tasks`);
|
|
205
205
|
console.log(` ${chalk.cyan('clawmoney wallet balance')} Check your wallet balance`);
|
|
206
|
-
console.log(` ${chalk.cyan('clawmoney
|
|
206
|
+
console.log(` ${chalk.cyan('clawmoney promote submit')} Submit a task proof`);
|
|
207
207
|
console.log('');
|
|
208
208
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Daemon entry point for the Hub Provider.
|
|
4
|
+
* This file is spawned as a detached child process by `clawmoney hub start`.
|
|
5
|
+
* It runs the provider main loop (WS + Poller + Executor).
|
|
6
|
+
*/
|
|
7
|
+
import { runProvider } from "./provider.js";
|
|
8
|
+
// Parse CLI args passed from the parent
|
|
9
|
+
let cliCommand;
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
for (let i = 0; i < args.length; i++) {
|
|
12
|
+
if (args[i] === "--cli" && args[i + 1]) {
|
|
13
|
+
cliCommand = args[i + 1];
|
|
14
|
+
i++;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// Run the provider (this blocks until shutdown signal)
|
|
18
|
+
runProvider(cliCommand);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { logger } from "./logger.js";
|
|
2
|
+
const TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
3
|
+
const CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
|
4
|
+
const seen = new Map();
|
|
5
|
+
let cleanupTimer = null;
|
|
6
|
+
export function isProcessed(orderId) {
|
|
7
|
+
return seen.has(orderId);
|
|
8
|
+
}
|
|
9
|
+
export function markProcessed(orderId) {
|
|
10
|
+
seen.set(orderId, Date.now());
|
|
11
|
+
}
|
|
12
|
+
function cleanup() {
|
|
13
|
+
const cutoff = Date.now() - TTL_MS;
|
|
14
|
+
let removed = 0;
|
|
15
|
+
for (const [id, ts] of seen) {
|
|
16
|
+
if (ts < cutoff) {
|
|
17
|
+
seen.delete(id);
|
|
18
|
+
removed++;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (removed > 0) {
|
|
22
|
+
logger.info(`Dedup cleanup: removed ${removed} stale entries, ${seen.size} remaining`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function startDedup() {
|
|
26
|
+
cleanupTimer = setInterval(cleanup, CLEANUP_INTERVAL_MS);
|
|
27
|
+
cleanupTimer.unref();
|
|
28
|
+
}
|
|
29
|
+
export function stopDedup() {
|
|
30
|
+
if (cleanupTimer) {
|
|
31
|
+
clearInterval(cleanupTimer);
|
|
32
|
+
cleanupTimer = null;
|
|
33
|
+
}
|
|
34
|
+
seen.clear();
|
|
35
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ProviderConfig, ServiceCallEvent, TestCallEvent, DeliverEvent, TestResponseEvent } from "./types.js";
|
|
2
|
+
type SendFn = (event: DeliverEvent | TestResponseEvent) => boolean;
|
|
3
|
+
export declare class Executor {
|
|
4
|
+
private config;
|
|
5
|
+
private send;
|
|
6
|
+
private activeTasks;
|
|
7
|
+
constructor(config: ProviderConfig, send: SendFn);
|
|
8
|
+
get activeCount(): number;
|
|
9
|
+
handleServiceCall(call: ServiceCallEvent): void;
|
|
10
|
+
handleTestCall(call: TestCallEvent): void;
|
|
11
|
+
private executeTask;
|
|
12
|
+
}
|
|
13
|
+
export {};
|