clawmoney 0.14.8 → 0.15.0

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.
@@ -3,7 +3,6 @@ import { join } from "node:path";
3
3
  import { homedir } from "node:os";
4
4
  import YAML from "yaml";
5
5
  import { RelayWsClient } from "./ws-client.js";
6
- import { spawnCli, buildCliArgs, parseCliOutput, ensureEmptyMcpConfig, ensureSandboxDir, } from "./executor.js";
7
6
  import { callClaudeApi, preflightClaudeApi, getRateGuardSnapshot as getClaudeRateGuardSnapshot, } from "./upstream/claude-api.js";
8
7
  import { callCodexApi, preflightCodexApi, getRateGuardSnapshot as getCodexRateGuardSnapshot, } from "./upstream/codex-api.js";
9
8
  import { callGeminiApi, preflightGeminiApi, getGeminiRateGuardSnapshot, } from "./upstream/gemini-api.js";
@@ -32,25 +31,8 @@ import { relayLogger as logger } from "./logger.js";
32
31
  const CONFIG_DIR = join(homedir(), ".clawmoney");
33
32
  const CONFIG_FILE = join(CONFIG_DIR, "config.yaml");
34
33
  const PID_FILE = join(CONFIG_DIR, "relay.pid");
35
- // Default execution mode is `api` as of 0.14.7. The `cli` fallback is still
36
- // supported — set `relay.execution_mode: cli` in ~/.clawmoney/config.yaml
37
- // or export CLAWMONEY_RELAY_EXECUTION_MODE=cli at launch — but new
38
- // providers get the direct-API path by default because:
39
- // - Every spawnCli() round-trip burns 2-5 seconds of cold start, which
40
- // is far too much for a request/response relay where buyers expect
41
- // sub-second handoff.
42
- // - Each subprocess consumes its own RAM + file handles; API mode runs
43
- // hundreds of concurrent calls out of one Node process.
44
- // - The fingerprint gap that used to make CLI mode "safer" is now
45
- // closed — 0.14.0–0.14.6 ported the real CLI's attribution hash,
46
- // streaming transport, thinking config, dynamic beta header, session
47
- // masking, Gemini startup warmup, and Codex per-turn prewarm. API
48
- // mode now matches real-CLI wire shape on every upstream.
49
- // CLI mode will be removed entirely in 0.15.0 once we've observed a
50
- // week of API-mode-default in production.
51
34
  const DEFAULT_RELAY = {
52
35
  cli_type: "claude",
53
- execution_mode: "api",
54
36
  model: "claude-opus-4-6",
55
37
  mode: "chat",
56
38
  concurrency: 5,
@@ -109,13 +91,8 @@ function loadRelayConfig(cliOverride) {
109
91
  process.exit(1);
110
92
  }
111
93
  const userRelay = (raw.relay ?? {});
112
- // Env override lets `CLAWMONEY_RELAY_EXECUTION_MODE=api` flip the mode
113
- // without editing config.yaml — useful for quick A/B and testing.
114
- const envMode = process.env.CLAWMONEY_RELAY_EXECUTION_MODE;
115
- const executionMode = envMode ?? userRelay.execution_mode ?? DEFAULT_RELAY.execution_mode;
116
94
  const relay = {
117
95
  cli_type: cliOverride ?? userRelay.cli_type ?? DEFAULT_RELAY.cli_type,
118
- execution_mode: executionMode,
119
96
  model: userRelay.model ?? DEFAULT_RELAY.model,
120
97
  mode: userRelay.mode ?? DEFAULT_RELAY.mode,
121
98
  concurrency: userRelay.concurrency ?? DEFAULT_RELAY.concurrency,
@@ -166,13 +143,6 @@ async function executeRelayRequest(request, config) {
166
143
  const model = request.model ?? config.relay.model;
167
144
  const stateful = request.stateful ?? false;
168
145
  const cliSessionId = request.cli_session_id ?? undefined;
169
- // api mode is supported for claude / codex / gemini / antigravity; anything
170
- // else falls back to spawning the local CLI subprocess. Antigravity is
171
- // api-only (there is no local CLI to spawn) so execution_mode is ignored
172
- // for it and we always route through the direct upstream handler.
173
- const useApiMode = (config.relay.execution_mode === "api" &&
174
- (cliType === "claude" || cliType === "codex" || cliType === "gemini")) ||
175
- cliType === "antigravity";
176
146
  // Build prompt from messages
177
147
  const prompt = request.messages
178
148
  ? messagesToPrompt(request.messages)
@@ -186,58 +156,44 @@ async function executeRelayRequest(request, config) {
186
156
  const modeLabel = stateful
187
157
  ? (cliSessionId ? `stateful[resume ${cliSessionId.slice(0, 8)}]` : "stateful[new]")
188
158
  : "stateless";
189
- const execLabel = useApiMode ? "api" : "cli";
190
159
  logger.info(` ┌─ Request ${request_id.slice(0, 8)}`);
191
- logger.info(` │ CLI: ${cliType} / ${model} (${modeLabel}, exec=${execLabel})`);
160
+ logger.info(` │ CLI: ${cliType} / ${model} (${modeLabel})`);
192
161
  logger.info(` │ Turns: ${turns}`);
193
162
  logger.info(` │ Prompt: ${String(lastUserMsg).slice(0, 80)}`);
194
163
  try {
195
164
  const startMs = Date.now();
196
165
  let parsed;
197
- if (useApiMode) {
198
- // Direct upstream HTTPS call no subprocess, no sandbox needed because
199
- // the only thing the upstream sees is the prompt text we pass in. The
200
- // right handler is picked by cli_type (claude → Anthropic, codex →
201
- // chatgpt.com, gemini → cloudcode-pa). Each handler has its own
202
- // fingerprint file and rate-guard instance.
203
- if (cliType === "codex") {
204
- parsed = await callCodexApi({
205
- prompt,
206
- model,
207
- maxTokens: max_budget_usd ? undefined : 4096,
208
- });
209
- }
210
- else if (cliType === "gemini") {
211
- parsed = await callGeminiApi({
212
- prompt,
213
- model,
214
- maxTokens: max_budget_usd ? undefined : 8192,
215
- });
216
- }
217
- else if (cliType === "antigravity") {
218
- parsed = await callAntigravityApi({
219
- prompt,
220
- model,
221
- maxTokens: max_budget_usd ? undefined : 8192,
222
- });
223
- }
224
- else {
225
- parsed = await callClaudeApi({
226
- prompt,
227
- model,
228
- maxTokens: max_budget_usd ? undefined : 4096,
229
- });
230
- }
166
+ // Direct upstream API call — the right handler is picked by cli_type
167
+ // (claude Anthropic, codex chatgpt.com WS, gemini cloudcode-pa,
168
+ // antigravity daily-cloudcode-pa). Each handler has its own
169
+ // fingerprint file and rate-guard instance.
170
+ if (cliType === "codex") {
171
+ parsed = await callCodexApi({
172
+ prompt,
173
+ model,
174
+ maxTokens: max_budget_usd ? undefined : 4096,
175
+ });
176
+ }
177
+ else if (cliType === "gemini") {
178
+ parsed = await callGeminiApi({
179
+ prompt,
180
+ model,
181
+ maxTokens: max_budget_usd ? undefined : 8192,
182
+ });
183
+ }
184
+ else if (cliType === "antigravity") {
185
+ parsed = await callAntigravityApi({
186
+ prompt,
187
+ model,
188
+ maxTokens: max_budget_usd ? undefined : 8192,
189
+ });
231
190
  }
232
191
  else {
233
- // In stateful mode, pass cli_session_id so buildCliArgs adds --resume
234
- const args = buildCliArgs(cliType, prompt, cliSessionId, max_budget_usd, model);
235
- // Spawn from an empty sandbox directory so Claude Code's auto-injected
236
- // cwd / CLAUDE.md / git-status context can't leak the provider's real
237
- // project data to the consumer.
238
- const sandbox = ensureSandboxDir();
239
- const raw = await spawnCli(cliType, args, undefined, sandbox);
240
- parsed = parseCliOutput(cliType, raw);
192
+ parsed = await callClaudeApi({
193
+ prompt,
194
+ model,
195
+ maxTokens: max_budget_usd ? undefined : 4096,
196
+ });
241
197
  }
242
198
  const elapsedMs = Date.now() - startMs;
243
199
  const answer = parsed.text.replace(/\n/g, " ").slice(0, 80);
@@ -250,21 +206,18 @@ async function executeRelayRequest(request, config) {
250
206
  logger.info(` │ Cost: input=$${cost.inputCost.toFixed(4)} cache_w=$${cost.cacheCreationCost.toFixed(4)} cache_r=$${cost.cacheReadCost.toFixed(4)} output=$${cost.outputCost.toFixed(4)}`);
251
207
  logger.info(` │ Total: API $${cost.apiCost.toFixed(4)} → Relay $${cost.relayCost.toFixed(4)} → Earn $${cost.providerEarn.toFixed(4)}`);
252
208
  logger.info(` └─ Done`);
253
- // When we're running in api mode, piggy-back the provider's current 5h
254
- // session-window snapshot onto the response so the Hub can use it for
255
- // predictive claim scheduling (avoid routing fresh work to a provider
256
- // whose window is already 90%+ saturated). Only populated if upstream
257
- // actually surfaced the headers this turn.
209
+ // Piggy-back the provider's current 5h session-window snapshot onto
210
+ // the response so the Hub can use it for predictive claim scheduling
211
+ // (avoid routing fresh work to a provider whose window is already
212
+ // 90%+ saturated).
258
213
  let sessionWindowTelemetry;
259
- if (useApiMode) {
260
- const snap = getRateGuardSnapshotForCli(cliType);
261
- if (snap?.sessionWindow) {
262
- sessionWindowTelemetry = {
263
- reset_at_ms: snap.sessionWindow.endMs,
264
- utilization: snap.sessionWindow.utilization,
265
- status: snap.sessionWindow.status,
266
- };
267
- }
214
+ const snap = getRateGuardSnapshotForCli(cliType);
215
+ if (snap?.sessionWindow) {
216
+ sessionWindowTelemetry = {
217
+ reset_at_ms: snap.sessionWindow.endMs,
218
+ utilization: snap.sessionWindow.utilization,
219
+ status: snap.sessionWindow.status,
220
+ };
268
221
  }
269
222
  return {
270
223
  event: "relay_response",
@@ -300,34 +253,24 @@ export function runRelayProvider(cliOverride) {
300
253
  // process.env.HTTPS_PROXY / http_proxy at init time. Must run BEFORE any
301
254
  // preflight call so the first outbound request already goes through it.
302
255
  applyProxyFromConfig(config);
303
- // Prepare relay sandbox assets once at startup.
304
- ensureEmptyMcpConfig();
305
- ensureSandboxDir();
306
- // If the operator picked api mode, validate the OAuth token + fingerprint
307
- // up-front so we fail fast instead of on the first inbound request. Each
308
- // cli_type has its own preflight path (different credential file, different
309
- // fingerprint schema, different rate-guard instance).
310
- // Antigravity is always api mode (no CLI to spawn), so force api execution
311
- // even if config.yaml says "cli". For the other CLIs, api mode is opt-in.
312
- if (config.relay.cli_type === "antigravity") {
313
- config.relay.execution_mode = "api";
314
- }
315
- if (config.relay.execution_mode === "api") {
316
- const preflightFn = config.relay.cli_type === "codex"
317
- ? preflightCodexApi
318
- : config.relay.cli_type === "gemini"
319
- ? preflightGeminiApi
320
- : config.relay.cli_type === "claude"
321
- ? preflightClaudeApi
322
- : config.relay.cli_type === "antigravity"
323
- ? preflightAntigravityApi
324
- : null;
325
- if (preflightFn) {
326
- preflightFn(config.relay.rate_guard).catch((err) => {
327
- logger.error(`${config.relay.cli_type} API preflight failed — falling back to CLI mode: ${err.message}`);
328
- config.relay.execution_mode = "cli";
329
- });
330
- }
256
+ // Validate the OAuth token + fingerprint up-front so we fail fast instead
257
+ // of on the first inbound request. Each cli_type has its own preflight
258
+ // path (different credential file, different fingerprint schema, different
259
+ // rate-guard instance).
260
+ const preflightFn = config.relay.cli_type === "codex"
261
+ ? preflightCodexApi
262
+ : config.relay.cli_type === "gemini"
263
+ ? preflightGeminiApi
264
+ : config.relay.cli_type === "claude"
265
+ ? preflightClaudeApi
266
+ : config.relay.cli_type === "antigravity"
267
+ ? preflightAntigravityApi
268
+ : null;
269
+ if (preflightFn) {
270
+ preflightFn(config.relay.rate_guard).catch((err) => {
271
+ logger.error(`${config.relay.cli_type} API preflight failed: ${err.message}`);
272
+ process.exit(1);
273
+ });
331
274
  }
332
275
  const activeTasks = new Set();
333
276
  // Create WS client
@@ -398,5 +341,5 @@ export function runRelayProvider(cliOverride) {
398
341
  writeRelayPid();
399
342
  wsClient.start();
400
343
  logger.info("Relay Provider running. Listening for relay requests...");
401
- logger.info(`Config: cli=${config.relay.cli_type}, exec=${config.relay.execution_mode ?? "cli"}, model=${config.relay.model}, mode=${config.relay.mode}, concurrency=${config.relay.concurrency}`);
344
+ logger.info(`Config: cli=${config.relay.cli_type}, model=${config.relay.model}, mode=${config.relay.mode}, concurrency=${config.relay.concurrency}`);
402
345
  }
@@ -69,7 +69,6 @@ export interface RelayRateGuardConfig {
69
69
  }
70
70
  export interface RelayProviderSettings {
71
71
  cli_type: string;
72
- execution_mode?: "cli" | "api";
73
72
  rate_guard?: RelayRateGuardConfig;
74
73
  model: string;
75
74
  mode: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmoney",
3
- "version": "0.14.8",
3
+ "version": "0.15.0",
4
4
  "description": "ClawMoney CLI -- Earn rewards with your AI agent",
5
5
  "type": "module",
6
6
  "bin": {