clawmoney 0.15.10 → 0.15.12
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/relay-setup.d.ts +1 -0
- package/dist/commands/relay-setup.js +344 -0
- package/dist/index.js +13 -0
- package/package.json +2 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function relaySetupCommand(): Promise<void>;
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { intro, outro, multiselect, confirm, text, spinner, isCancel, cancel, log, } from "@clack/prompts";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { apiPost } from "../utils/api.js";
|
|
8
|
+
import { requireConfig } from "../utils/config.js";
|
|
9
|
+
import { API_PRICES, RELAY_DISCOUNT, PLATFORM_FEE } from "../relay/pricing.js";
|
|
10
|
+
// ── Per-cli_type model catalogs ──
|
|
11
|
+
//
|
|
12
|
+
// `RECOMMENDED_MODELS` is what gets registered when the user picks "all
|
|
13
|
+
// recommended" — it's a curated subset of API_PRICES that mirrors what
|
|
14
|
+
// each CLI's NATIVE /model picker exposes by default. Cross-checked
|
|
15
|
+
// against:
|
|
16
|
+
// - Claude Code 2.1.x /model menu (4 entries → 3 unique IDs;
|
|
17
|
+
// Sonnet 1M is the same model + context-1m beta header)
|
|
18
|
+
// - Codex CLI 0.117.x /model menu (4 entries: gpt-5.4 current,
|
|
19
|
+
// gpt-5.4-mini, gpt-5.3-codex, gpt-5.2)
|
|
20
|
+
// - sub2api backend/internal/pkg/gemini/models.go DefaultModels()
|
|
21
|
+
// for the Gemini CLI catalog
|
|
22
|
+
// - sub2api backend/internal/pkg/antigravity/claude_types.go
|
|
23
|
+
// claudeModels + geminiModels for the Antigravity catalog
|
|
24
|
+
//
|
|
25
|
+
// The "manual select" path (when the user says no to recommended)
|
|
26
|
+
// falls through to modelsForCli(cli) which returns EVERY priced
|
|
27
|
+
// model in that family.
|
|
28
|
+
const RECOMMENDED_MODELS = {
|
|
29
|
+
// Claude Code /model menu: Default(Sonnet 4.6) / Sonnet(1M) / Opus(1M) / Haiku
|
|
30
|
+
// → 3 unique model IDs (Sonnet 1M = same model + context-1m beta)
|
|
31
|
+
claude: ["claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5"],
|
|
32
|
+
// Codex CLI /model menu: gpt-5.4 (current frontier) / gpt-5.4-mini /
|
|
33
|
+
// gpt-5.3-codex (Codex-optimized) / gpt-5.2 (long-running pro)
|
|
34
|
+
codex: ["gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex", "gpt-5.2"],
|
|
35
|
+
// Gemini CLI exposes a long list; mainstream picks are the production-
|
|
36
|
+
// stable 2.5 line (pro + flash) and the latest 3.x preview (pro + flash).
|
|
37
|
+
// Image / thinking variants and lite/customtools are intentionally
|
|
38
|
+
// skipped from the recommended set — users can pick them manually.
|
|
39
|
+
gemini: [
|
|
40
|
+
"gemini-2.5-pro",
|
|
41
|
+
"gemini-2.5-flash",
|
|
42
|
+
"gemini-3-pro-preview",
|
|
43
|
+
"gemini-3-flash-preview",
|
|
44
|
+
],
|
|
45
|
+
// Antigravity exposes Claude Opus/Sonnet AND Gemini Pro/Flash via the
|
|
46
|
+
// SAME Google Antigravity OAuth token (one of its main selling points
|
|
47
|
+
// for providers). Recommended set spans both halves so a single
|
|
48
|
+
// antigravity provider serves both Anthropic and Google buyers.
|
|
49
|
+
antigravity: [
|
|
50
|
+
"antigravity-claude-sonnet-4-6",
|
|
51
|
+
"antigravity-claude-opus-4-6",
|
|
52
|
+
"antigravity-gemini-3-pro",
|
|
53
|
+
"antigravity-gemini-3-flash",
|
|
54
|
+
"antigravity-gemini-2.5-pro",
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
function modelsForCli(cli) {
|
|
58
|
+
const all = Object.keys(API_PRICES);
|
|
59
|
+
if (cli === "claude") {
|
|
60
|
+
return all.filter((m) => m.startsWith("claude-"));
|
|
61
|
+
}
|
|
62
|
+
if (cli === "codex") {
|
|
63
|
+
// Only gpt-5.x family — o3/o4 reasoning models are public Responses
|
|
64
|
+
// API only, NOT served by Codex CLI's internal ChatGPT WS path.
|
|
65
|
+
// They're in API_PRICES for OpenAI SDK callers via /v1/chat/completions
|
|
66
|
+
// but a `codex` daemon can't actually serve them.
|
|
67
|
+
return all.filter((m) => m.startsWith("gpt-"));
|
|
68
|
+
}
|
|
69
|
+
if (cli === "antigravity") {
|
|
70
|
+
return all.filter((m) => m.startsWith("antigravity-"));
|
|
71
|
+
}
|
|
72
|
+
if (cli === "gemini") {
|
|
73
|
+
// Exclude antigravity-prefixed gemini variants — those are served by
|
|
74
|
+
// the antigravity cli_type, not the standalone gemini cli_type.
|
|
75
|
+
return all.filter((m) => m.startsWith("gemini-") && !m.startsWith("antigravity-"));
|
|
76
|
+
}
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
function detectInstalledClis() {
|
|
80
|
+
const results = [];
|
|
81
|
+
// Binary-based CLIs: probe `which`. We intentionally don't try to
|
|
82
|
+
// validate OAuth state here — the daemon's preflight does that on
|
|
83
|
+
// first start, and probing OAuth from a sync setup wizard would be
|
|
84
|
+
// brittle (keychain prompts, refresh-token races, etc).
|
|
85
|
+
const binaries = [
|
|
86
|
+
{ cli: "claude", bin: "claude" },
|
|
87
|
+
{ cli: "codex", bin: "codex" },
|
|
88
|
+
{ cli: "gemini", bin: "gemini" },
|
|
89
|
+
];
|
|
90
|
+
for (const { cli, bin } of binaries) {
|
|
91
|
+
let installed = false;
|
|
92
|
+
try {
|
|
93
|
+
execSync(`command -v ${bin}`, { stdio: "pipe" });
|
|
94
|
+
installed = true;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
installed = false;
|
|
98
|
+
}
|
|
99
|
+
results.push({
|
|
100
|
+
cli,
|
|
101
|
+
available: installed,
|
|
102
|
+
hint: installed
|
|
103
|
+
? "binary in PATH (login state will be validated when daemon starts)"
|
|
104
|
+
: `${bin} not found in PATH`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
// Antigravity is OAuth-file based — there's no `antigravity` binary
|
|
108
|
+
// installed locally. We check for the OAuth credentials file that
|
|
109
|
+
// `clawmoney antigravity login` writes.
|
|
110
|
+
const antigravityFile = join(homedir(), ".clawmoney", "antigravity-accounts.json");
|
|
111
|
+
const antigravityAvailable = existsSync(antigravityFile);
|
|
112
|
+
results.push({
|
|
113
|
+
cli: "antigravity",
|
|
114
|
+
available: antigravityAvailable,
|
|
115
|
+
hint: antigravityAvailable
|
|
116
|
+
? "Google OAuth file present"
|
|
117
|
+
: "no Google OAuth linked (run `clawmoney antigravity login` first)",
|
|
118
|
+
});
|
|
119
|
+
return results;
|
|
120
|
+
}
|
|
121
|
+
// ── Helpers ──
|
|
122
|
+
function formatPrice(input, output) {
|
|
123
|
+
return `$${input}/$${output} per 1M`;
|
|
124
|
+
}
|
|
125
|
+
function formatBuyerPrice(input, output) {
|
|
126
|
+
const buyerInput = (input * RELAY_DISCOUNT).toFixed(3);
|
|
127
|
+
const buyerOutput = (output * RELAY_DISCOUNT).toFixed(3);
|
|
128
|
+
return `$${buyerInput}/$${buyerOutput} per 1M (after ${Math.round((1 - RELAY_DISCOUNT) * 100)}% relay discount)`;
|
|
129
|
+
}
|
|
130
|
+
// ── Main command ──
|
|
131
|
+
export async function relaySetupCommand() {
|
|
132
|
+
const config = requireConfig();
|
|
133
|
+
intro(chalk.cyan(" ClawMoney Relay Setup "));
|
|
134
|
+
log.message("Sell your idle Claude Max / ChatGPT Pro / Google subscription capacity to other AI agents.");
|
|
135
|
+
// ── Step 1: detect installed CLIs ──
|
|
136
|
+
const detectSpin = spinner();
|
|
137
|
+
detectSpin.start("Scanning for installed CLI clients...");
|
|
138
|
+
const detected = detectInstalledClis();
|
|
139
|
+
detectSpin.stop("Scan complete");
|
|
140
|
+
for (const d of detected) {
|
|
141
|
+
if (d.available) {
|
|
142
|
+
log.success(`${chalk.bold(d.cli.padEnd(12))} ${chalk.dim(d.hint)}`);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
log.warn(`${chalk.bold(d.cli.padEnd(12))} ${chalk.dim(d.hint)}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const available = detected.filter((d) => d.available);
|
|
149
|
+
if (available.length === 0) {
|
|
150
|
+
log.error("No supported CLI clients found locally. Install at least one of: " +
|
|
151
|
+
chalk.cyan("claude, codex, gemini") +
|
|
152
|
+
" — or run `clawmoney antigravity login` to link a Google account.");
|
|
153
|
+
cancel("Setup aborted");
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
// ── Step 2: pick which families to register ──
|
|
157
|
+
const familyChoice = await multiselect({
|
|
158
|
+
message: "Which CLI families do you want to provide? (space to toggle, enter to confirm)",
|
|
159
|
+
options: available.map((d) => ({
|
|
160
|
+
value: d.cli,
|
|
161
|
+
label: d.cli,
|
|
162
|
+
hint: d.hint,
|
|
163
|
+
})),
|
|
164
|
+
initialValues: available.map((d) => d.cli),
|
|
165
|
+
required: true,
|
|
166
|
+
});
|
|
167
|
+
if (isCancel(familyChoice)) {
|
|
168
|
+
cancel("Setup cancelled");
|
|
169
|
+
process.exit(0);
|
|
170
|
+
}
|
|
171
|
+
const selectedClis = familyChoice;
|
|
172
|
+
const registrations = [];
|
|
173
|
+
for (const cli of selectedClis) {
|
|
174
|
+
const allModels = modelsForCli(cli);
|
|
175
|
+
const recommended = (RECOMMENDED_MODELS[cli] ?? []).filter((m) => allModels.includes(m));
|
|
176
|
+
if (allModels.length === 0) {
|
|
177
|
+
log.warn(`${cli}: no models found in pricing table — skipping`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
log.step(`${chalk.bold(cli)}: choose models`);
|
|
181
|
+
const useRecommended = await confirm({
|
|
182
|
+
message: `Register the ${recommended.length} recommended ${cli} models? (${recommended.join(", ")})`,
|
|
183
|
+
initialValue: true,
|
|
184
|
+
});
|
|
185
|
+
if (isCancel(useRecommended)) {
|
|
186
|
+
cancel("Setup cancelled");
|
|
187
|
+
process.exit(0);
|
|
188
|
+
}
|
|
189
|
+
let chosen;
|
|
190
|
+
if (useRecommended) {
|
|
191
|
+
chosen = recommended;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
const picked = await multiselect({
|
|
195
|
+
message: `Pick ${cli} models to register:`,
|
|
196
|
+
options: allModels.map((m) => {
|
|
197
|
+
const p = API_PRICES[m];
|
|
198
|
+
return {
|
|
199
|
+
value: m,
|
|
200
|
+
label: m,
|
|
201
|
+
hint: formatPrice(p.input, p.output),
|
|
202
|
+
};
|
|
203
|
+
}),
|
|
204
|
+
initialValues: recommended,
|
|
205
|
+
required: true,
|
|
206
|
+
});
|
|
207
|
+
if (isCancel(picked)) {
|
|
208
|
+
cancel("Setup cancelled");
|
|
209
|
+
process.exit(0);
|
|
210
|
+
}
|
|
211
|
+
chosen = picked;
|
|
212
|
+
}
|
|
213
|
+
for (const model of chosen) {
|
|
214
|
+
const p = API_PRICES[model];
|
|
215
|
+
registrations.push({
|
|
216
|
+
cli,
|
|
217
|
+
model,
|
|
218
|
+
input: p.input,
|
|
219
|
+
output: p.output,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (registrations.length === 0) {
|
|
224
|
+
cancel("No models selected — nothing to register");
|
|
225
|
+
process.exit(0);
|
|
226
|
+
}
|
|
227
|
+
// ── Step 4: global concurrency + daily budget ──
|
|
228
|
+
const concurrencyAns = await text({
|
|
229
|
+
message: "Concurrency cap per provider? (1-5 recommended; higher looks less like a single power user to upstream fingerprint detection)",
|
|
230
|
+
placeholder: "5",
|
|
231
|
+
defaultValue: "5",
|
|
232
|
+
validate: (v) => {
|
|
233
|
+
const n = parseInt(v || "5", 10);
|
|
234
|
+
if (Number.isNaN(n) || n < 1 || n > 20) {
|
|
235
|
+
return "Must be a number between 1 and 20";
|
|
236
|
+
}
|
|
237
|
+
return undefined;
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
if (isCancel(concurrencyAns)) {
|
|
241
|
+
cancel("Setup cancelled");
|
|
242
|
+
process.exit(0);
|
|
243
|
+
}
|
|
244
|
+
const dailyLimitAns = await text({
|
|
245
|
+
message: "Daily API spend cap per provider in USD? (the daemon stops accepting requests when exceeded)",
|
|
246
|
+
placeholder: "15",
|
|
247
|
+
defaultValue: "15",
|
|
248
|
+
validate: (v) => {
|
|
249
|
+
const n = parseFloat(v || "15");
|
|
250
|
+
if (Number.isNaN(n) || n < 0) {
|
|
251
|
+
return "Must be a non-negative number";
|
|
252
|
+
}
|
|
253
|
+
return undefined;
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
if (isCancel(dailyLimitAns)) {
|
|
257
|
+
cancel("Setup cancelled");
|
|
258
|
+
process.exit(0);
|
|
259
|
+
}
|
|
260
|
+
const concurrency = parseInt(concurrencyAns, 10);
|
|
261
|
+
const dailyLimit = parseFloat(dailyLimitAns);
|
|
262
|
+
// ── Step 5: confirmation summary ──
|
|
263
|
+
log.step(chalk.bold("Summary"));
|
|
264
|
+
for (const r of registrations) {
|
|
265
|
+
log.message(` ${chalk.cyan(r.cli + "/" + r.model).padEnd(50)} ${chalk.dim(formatBuyerPrice(r.input, r.output))}`);
|
|
266
|
+
}
|
|
267
|
+
log.message(chalk.dim(` ${registrations.length} providers · concurrency=${concurrency} · daily_limit=$${dailyLimit}`));
|
|
268
|
+
log.message(chalk.dim(` You earn ~${Math.round((1 - PLATFORM_FEE) * 100)}% of what buyers pay (after platform fee)`));
|
|
269
|
+
const proceed = await confirm({
|
|
270
|
+
message: `Register all ${registrations.length} providers now?`,
|
|
271
|
+
initialValue: true,
|
|
272
|
+
});
|
|
273
|
+
if (isCancel(proceed) || !proceed) {
|
|
274
|
+
cancel("Setup cancelled");
|
|
275
|
+
process.exit(0);
|
|
276
|
+
}
|
|
277
|
+
// ── Step 6: register each (idempotent — "already registered" counts as success) ──
|
|
278
|
+
let succeeded = 0;
|
|
279
|
+
let failed = 0;
|
|
280
|
+
const failures = [];
|
|
281
|
+
for (const r of registrations) {
|
|
282
|
+
const regSpin = spinner();
|
|
283
|
+
regSpin.start(`Registering ${r.cli}/${r.model}...`);
|
|
284
|
+
try {
|
|
285
|
+
const body = {
|
|
286
|
+
cli_type: r.cli,
|
|
287
|
+
model: r.model,
|
|
288
|
+
mode: "chat",
|
|
289
|
+
concurrency,
|
|
290
|
+
daily_limit_usd: dailyLimit,
|
|
291
|
+
price_input_per_m: r.input,
|
|
292
|
+
price_output_per_m: r.output,
|
|
293
|
+
};
|
|
294
|
+
const resp = await apiPost("/api/v1/relay/providers", body, config.api_key);
|
|
295
|
+
if (resp.ok) {
|
|
296
|
+
regSpin.stop(`${chalk.green("✓")} ${r.cli}/${r.model}`);
|
|
297
|
+
succeeded++;
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
const raw = resp.data && typeof resp.data === "object" && "detail" in resp.data
|
|
301
|
+
? resp.data.detail
|
|
302
|
+
: resp.data;
|
|
303
|
+
const detail = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
304
|
+
// Already-registered is a soft success — idempotent re-run.
|
|
305
|
+
if (detail.includes("Already registered")) {
|
|
306
|
+
regSpin.stop(`${chalk.yellow("~")} ${r.cli}/${r.model} ${chalk.dim("(already registered, no change)")}`);
|
|
307
|
+
succeeded++;
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
regSpin.stop(`${chalk.red("✗")} ${r.cli}/${r.model} ${chalk.dim("(" + detail.slice(0, 80) + ")")}`);
|
|
311
|
+
failed++;
|
|
312
|
+
failures.push({ cli: r.cli, model: r.model, error: detail });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
const msg = err.message;
|
|
318
|
+
regSpin.stop(`${chalk.red("✗")} ${r.cli}/${r.model} ${chalk.dim("(" + msg + ")")}`);
|
|
319
|
+
failed++;
|
|
320
|
+
failures.push({ cli: r.cli, model: r.model, error: msg });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// ── Step 7: next steps ──
|
|
324
|
+
log.step(chalk.bold("Done"));
|
|
325
|
+
log.message(`${chalk.green(succeeded.toString())} providers registered`);
|
|
326
|
+
if (failed > 0) {
|
|
327
|
+
log.warn(`${failed} registrations failed`);
|
|
328
|
+
for (const f of failures) {
|
|
329
|
+
log.message(chalk.dim(` ${f.cli}/${f.model}: ${f.error.slice(0, 120)}`));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
log.message("");
|
|
333
|
+
log.message(chalk.bold("Next steps"));
|
|
334
|
+
log.message(" Start the daemon for each cli_type you registered:");
|
|
335
|
+
for (const cli of selectedClis) {
|
|
336
|
+
log.message(` ${chalk.cyan(`clawmoney relay start --cli ${cli}`)}`);
|
|
337
|
+
}
|
|
338
|
+
log.message("");
|
|
339
|
+
log.message(" Useful follow-up commands:");
|
|
340
|
+
log.message(` ${chalk.cyan("clawmoney relay status")} # check daemon health + provider list`);
|
|
341
|
+
log.message(` ${chalk.cyan("clawmoney relay credits")} # check earnings + payout balance`);
|
|
342
|
+
log.message(` ${chalk.cyan("clawmoney relay stop")} # stop the daemon`);
|
|
343
|
+
outro(chalk.green("Setup complete"));
|
|
344
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -458,6 +458,19 @@ gig
|
|
|
458
458
|
const relay = program
|
|
459
459
|
.command('relay')
|
|
460
460
|
.description('Relay marketplace: sell idle AI subscription capacity');
|
|
461
|
+
relay
|
|
462
|
+
.command('setup')
|
|
463
|
+
.description('Interactive: detect installed CLIs, pick models, register all in one go (recommended for first-time setup)')
|
|
464
|
+
.action(async () => {
|
|
465
|
+
try {
|
|
466
|
+
const { relaySetupCommand } = await import('./commands/relay-setup.js');
|
|
467
|
+
await relaySetupCommand();
|
|
468
|
+
}
|
|
469
|
+
catch (err) {
|
|
470
|
+
console.error(err.message);
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
});
|
|
461
474
|
relay
|
|
462
475
|
.command('register')
|
|
463
476
|
.description('Register as a relay provider')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawmoney",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.12",
|
|
4
4
|
"description": "ClawMoney CLI -- Earn rewards with your AI agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"awal": "^2.2.0",
|
|
22
22
|
"@bnbot/cli": "^0.3.0",
|
|
23
|
+
"@clack/prompts": "^0.7.0",
|
|
23
24
|
"chalk": "^5.3.0",
|
|
24
25
|
"commander": "^12.0.0",
|
|
25
26
|
"ora": "^8.0.0",
|