@demath-ai/cli 0.1.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.
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/SKILL.md +54 -0
- package/bin/demath.mjs +13 -0
- package/dist/cli.cjs +816 -0
- package/dist/cli.js +814 -0
- package/dist/index.cjs +823 -0
- package/dist/index.d.cts +88 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.js +814 -0
- package/package.json +63 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var CLI_VERSION = "0.1.0";
|
|
3
|
+
var USER_AGENT = `demath-cli-npm/${CLI_VERSION} (+https://github.com/demath-ai/proj-dmv0/tree/main/tools/demath-cli-npm)`;
|
|
4
|
+
var DEFAULT_API_URL = process.env.DEMATH_API_URL?.trim() || "https://api.demath.org";
|
|
5
|
+
var APPROVED_MODELS = [
|
|
6
|
+
"anthropic/claude-opus-4-7",
|
|
7
|
+
"openai/gpt-5.5",
|
|
8
|
+
"google/gemini-3-deep-think"
|
|
9
|
+
];
|
|
10
|
+
var POLL_INTERVAL_MS = 3e3;
|
|
11
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
12
|
+
"proof_complete",
|
|
13
|
+
"counterexample",
|
|
14
|
+
"breakthrough",
|
|
15
|
+
"substantial_progress",
|
|
16
|
+
"partial_progress",
|
|
17
|
+
"no_progress",
|
|
18
|
+
"stopped",
|
|
19
|
+
"error"
|
|
20
|
+
]);
|
|
21
|
+
var SUCCESS_STATUSES = /* @__PURE__ */ new Set([
|
|
22
|
+
"proof_complete",
|
|
23
|
+
"counterexample",
|
|
24
|
+
"breakthrough"
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
// src/errors.ts
|
|
28
|
+
var CliError = class extends Error {
|
|
29
|
+
exitCode;
|
|
30
|
+
constructor(message, exitCode = 1) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = "CliError";
|
|
33
|
+
this.exitCode = exitCode;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// src/http.ts
|
|
38
|
+
async function request(opts) {
|
|
39
|
+
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
40
|
+
const ctrl = new AbortController();
|
|
41
|
+
const timeoutId = setTimeout(() => ctrl.abort(new Error("request timed out")), timeoutMs);
|
|
42
|
+
const onParentAbort = () => ctrl.abort(opts.signal.reason);
|
|
43
|
+
if (opts.signal) {
|
|
44
|
+
if (opts.signal.aborted) ctrl.abort(opts.signal.reason);
|
|
45
|
+
else opts.signal.addEventListener("abort", onParentAbort, { once: true });
|
|
46
|
+
}
|
|
47
|
+
const headers = {
|
|
48
|
+
Accept: "application/json",
|
|
49
|
+
"User-Agent": USER_AGENT
|
|
50
|
+
};
|
|
51
|
+
let bodyInit;
|
|
52
|
+
if (opts.body !== void 0) {
|
|
53
|
+
bodyInit = JSON.stringify(opts.body);
|
|
54
|
+
headers["Content-Type"] = "application/json";
|
|
55
|
+
}
|
|
56
|
+
let resp;
|
|
57
|
+
try {
|
|
58
|
+
resp = await fetch(opts.url, {
|
|
59
|
+
method: opts.method,
|
|
60
|
+
headers,
|
|
61
|
+
body: bodyInit,
|
|
62
|
+
signal: ctrl.signal
|
|
63
|
+
});
|
|
64
|
+
} catch (err) {
|
|
65
|
+
clearTimeout(timeoutId);
|
|
66
|
+
if (opts.signal) opts.signal.removeEventListener("abort", onParentAbort);
|
|
67
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
68
|
+
throw new CliError(`network error reaching ${opts.url}: ${msg}`, 3);
|
|
69
|
+
} finally {
|
|
70
|
+
clearTimeout(timeoutId);
|
|
71
|
+
if (opts.signal) opts.signal.removeEventListener("abort", onParentAbort);
|
|
72
|
+
}
|
|
73
|
+
const rawText = await resp.text();
|
|
74
|
+
if (!resp.ok) {
|
|
75
|
+
let detail = `HTTP ${resp.status}`;
|
|
76
|
+
if (rawText) {
|
|
77
|
+
try {
|
|
78
|
+
const parsed = JSON.parse(rawText);
|
|
79
|
+
const d = parsed.detail ?? parsed.error;
|
|
80
|
+
if (typeof d === "string") detail = d;
|
|
81
|
+
else if (d !== void 0) detail = JSON.stringify(d);
|
|
82
|
+
} catch {
|
|
83
|
+
detail = rawText.slice(0, 400);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
throw new CliError(`backend error: ${detail}`, 2);
|
|
87
|
+
}
|
|
88
|
+
if (!rawText) return {};
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(rawText);
|
|
91
|
+
} catch {
|
|
92
|
+
throw new CliError(`backend returned non-JSON body: ${rawText.slice(0, 200)}`, 2);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/api.ts
|
|
97
|
+
var DemathClient = class {
|
|
98
|
+
constructor(cfg) {
|
|
99
|
+
this.cfg = cfg;
|
|
100
|
+
}
|
|
101
|
+
cfg;
|
|
102
|
+
listProblems() {
|
|
103
|
+
return request({
|
|
104
|
+
method: "GET",
|
|
105
|
+
url: `${this.cfg.apiUrl}/problems`,
|
|
106
|
+
signal: this.cfg.signal
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
probe(model, apiKey) {
|
|
110
|
+
return request({
|
|
111
|
+
method: "POST",
|
|
112
|
+
url: `${this.cfg.apiUrl}/providers/probe`,
|
|
113
|
+
body: { model, api_key: apiKey },
|
|
114
|
+
signal: this.cfg.signal
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
startAttempt(r) {
|
|
118
|
+
const body = {
|
|
119
|
+
problem_id: r.problemId,
|
|
120
|
+
model: r.model,
|
|
121
|
+
api_key: r.apiKey,
|
|
122
|
+
miner_address: r.walletAddress
|
|
123
|
+
};
|
|
124
|
+
if (r.maxIterations !== void 0) body.max_iterations = r.maxIterations;
|
|
125
|
+
if (r.maxUsd !== void 0) body.max_usd = r.maxUsd;
|
|
126
|
+
return request({
|
|
127
|
+
method: "POST",
|
|
128
|
+
url: `${this.cfg.apiUrl}/attempts`,
|
|
129
|
+
body,
|
|
130
|
+
signal: this.cfg.signal
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
getAttempt(attemptId) {
|
|
134
|
+
return request({
|
|
135
|
+
method: "GET",
|
|
136
|
+
url: `${this.cfg.apiUrl}/attempts/${encodeURIComponent(attemptId)}`,
|
|
137
|
+
signal: this.cfg.signal
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
stopAttempt(attemptId) {
|
|
141
|
+
return request({
|
|
142
|
+
method: "POST",
|
|
143
|
+
url: `${this.cfg.apiUrl}/attempts/${encodeURIComponent(attemptId)}/stop`,
|
|
144
|
+
body: {},
|
|
145
|
+
// Stops shouldn't get cancelled by the same Ctrl-C that initiated them.
|
|
146
|
+
signal: void 0
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// src/output.ts
|
|
152
|
+
function logErr(line = "") {
|
|
153
|
+
process.stderr.write(line + "\n");
|
|
154
|
+
}
|
|
155
|
+
function printJson(value) {
|
|
156
|
+
process.stdout.write(JSON.stringify(value, null, 2) + "\n");
|
|
157
|
+
}
|
|
158
|
+
function padRight(s, n) {
|
|
159
|
+
return s.length >= n ? s : s + " ".repeat(n - s.length);
|
|
160
|
+
}
|
|
161
|
+
function truncate(s, n) {
|
|
162
|
+
if (!s) return "";
|
|
163
|
+
return s.length <= n ? s : s.slice(0, Math.max(0, n - 1)) + "\u2026";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/parser.ts
|
|
167
|
+
function parseArgs(argv) {
|
|
168
|
+
const positional = [];
|
|
169
|
+
const flags = /* @__PURE__ */ new Map();
|
|
170
|
+
let i = 0;
|
|
171
|
+
while (i < argv.length) {
|
|
172
|
+
const a = argv[i];
|
|
173
|
+
if (a === "--") {
|
|
174
|
+
positional.push(...argv.slice(i + 1));
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
if (a.startsWith("--")) {
|
|
178
|
+
const eq = a.indexOf("=");
|
|
179
|
+
if (eq !== -1) {
|
|
180
|
+
flags.set(a.slice(2, eq), a.slice(eq + 1));
|
|
181
|
+
i++;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const key = a.slice(2);
|
|
185
|
+
const next = argv[i + 1];
|
|
186
|
+
if (next !== void 0 && !next.startsWith("-")) {
|
|
187
|
+
flags.set(key, next);
|
|
188
|
+
i += 2;
|
|
189
|
+
} else {
|
|
190
|
+
flags.set(key, true);
|
|
191
|
+
i++;
|
|
192
|
+
}
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (a.startsWith("-") && a.length > 1) {
|
|
196
|
+
flags.set(a.slice(1), true);
|
|
197
|
+
i++;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
positional.push(a);
|
|
201
|
+
i++;
|
|
202
|
+
}
|
|
203
|
+
return { positional, flags };
|
|
204
|
+
}
|
|
205
|
+
function getString(flags, key) {
|
|
206
|
+
const v = flags.get(key);
|
|
207
|
+
return typeof v === "string" ? v : void 0;
|
|
208
|
+
}
|
|
209
|
+
function requireString(flags, key) {
|
|
210
|
+
const v = getString(flags, key);
|
|
211
|
+
if (v === void 0) throw new CliError(`missing required flag --${key}`, 2);
|
|
212
|
+
return v;
|
|
213
|
+
}
|
|
214
|
+
function getBool(flags, key) {
|
|
215
|
+
return flags.has(key);
|
|
216
|
+
}
|
|
217
|
+
function getNumber(flags, key) {
|
|
218
|
+
const v = getString(flags, key);
|
|
219
|
+
if (v === void 0) return void 0;
|
|
220
|
+
const n = Number(v);
|
|
221
|
+
if (!Number.isFinite(n)) throw new CliError(`--${key} must be a number, got: ${v}`, 2);
|
|
222
|
+
return n;
|
|
223
|
+
}
|
|
224
|
+
var TOP_LEVEL_HELP = `demath ${CLI_VERSION} \u2014 mine $DEMATH from your terminal.
|
|
225
|
+
|
|
226
|
+
USAGE
|
|
227
|
+
demath <command> [options]
|
|
228
|
+
|
|
229
|
+
COMMANDS
|
|
230
|
+
problems List active problems
|
|
231
|
+
probe Verify your provider API key works (no spend)
|
|
232
|
+
mine Start an attempt and stream live progress
|
|
233
|
+
status Show the current state of an attempt by id
|
|
234
|
+
examples Print copy-pasteable example invocations
|
|
235
|
+
skill Print the SKILL.md doc to stdout (for AI agents)
|
|
236
|
+
--help Show this help
|
|
237
|
+
--version Print CLI version
|
|
238
|
+
|
|
239
|
+
GLOBAL OPTIONS
|
|
240
|
+
--api-url <url> Backend base URL (default: ${DEFAULT_API_URL})
|
|
241
|
+
Override with DEMATH_API_URL env var.
|
|
242
|
+
--json Machine-readable output on stdout (for AI agents).
|
|
243
|
+
Per-command; only some commands stream NDJSON.
|
|
244
|
+
|
|
245
|
+
APPROVED MODEL SLUGS
|
|
246
|
+
${APPROVED_MODELS.map((m) => ` - ${m}`).join("\n")}
|
|
247
|
+
|
|
248
|
+
PROVIDER API KEY HANDLING
|
|
249
|
+
DeMath never stores, persists, or logs your provider API key. The CLI
|
|
250
|
+
sends it once per attempt to the backend, which forwards it inline to
|
|
251
|
+
the upstream provider (Anthropic / OpenAI / Google / OpenRouter) and
|
|
252
|
+
drops it from memory the moment the attempt finishes. No disk write,
|
|
253
|
+
no log line, no telemetry, no IPFS bundle. Your billing relationship
|
|
254
|
+
with the upstream provider remains entirely yours.
|
|
255
|
+
|
|
256
|
+
LOCK-FOR-LIFE
|
|
257
|
+
Your wallet binds to a single team on its first attempt. The backend
|
|
258
|
+
rejects team switches with HTTP 409. Use a different wallet to mine
|
|
259
|
+
a different team. Same rule for web, CLI, and any bot client.
|
|
260
|
+
|
|
261
|
+
FOR AI AGENTS
|
|
262
|
+
Run \`demath skill\` to print the short, human-readable skill doc
|
|
263
|
+
(project intro + mine-in-three-steps + key constraints). The rest is
|
|
264
|
+
in this --help and the per-command --help; refresh both before every
|
|
265
|
+
invocation.
|
|
266
|
+
|
|
267
|
+
See \`demath examples\` for worked invocations.
|
|
268
|
+
See \`demath <command> --help\` for per-command flags.
|
|
269
|
+
`;
|
|
270
|
+
var PROBLEMS_HELP = `demath problems \u2014 list active problems.
|
|
271
|
+
|
|
272
|
+
USAGE
|
|
273
|
+
demath problems [--api-url <url>] [--json]
|
|
274
|
+
|
|
275
|
+
EXAMPLES
|
|
276
|
+
demath problems
|
|
277
|
+
demath problems --json | jq '.problems[].id'
|
|
278
|
+
|
|
279
|
+
EXIT CODES
|
|
280
|
+
0 success
|
|
281
|
+
2 backend error
|
|
282
|
+
3 network error
|
|
283
|
+
`;
|
|
284
|
+
var PROBE_HELP = `demath probe \u2014 verify an API key works for a model (no token spend
|
|
285
|
+
beyond a 1-token chat completion).
|
|
286
|
+
|
|
287
|
+
USAGE
|
|
288
|
+
demath probe --model <slug> --key <api-key> [--api-url <url>] [--json]
|
|
289
|
+
|
|
290
|
+
REQUIRED
|
|
291
|
+
--model one of: ${APPROVED_MODELS.join(", ")}
|
|
292
|
+
--key provider API key (OpenRouter / Anthropic / OpenAI / Gemini direct)
|
|
293
|
+
backend auto-detects the provider from the key prefix.
|
|
294
|
+
|
|
295
|
+
EXAMPLES
|
|
296
|
+
demath probe --model anthropic/claude-opus-4-7 --key sk-ant-...
|
|
297
|
+
demath probe --model openai/gpt-5.5 --key sk-or-v1-... --json
|
|
298
|
+
|
|
299
|
+
EXIT CODES
|
|
300
|
+
0 key works
|
|
301
|
+
2 probe failed / invalid model
|
|
302
|
+
3 network error
|
|
303
|
+
`;
|
|
304
|
+
var MINE_HELP = `demath mine \u2014 start an attempt and stream live progress until terminal.
|
|
305
|
+
|
|
306
|
+
USAGE
|
|
307
|
+
demath mine --problem <id> --model <slug> --key <api-key>
|
|
308
|
+
--wallet <0x...> [--max-iterations N] [--max-usd N]
|
|
309
|
+
[--api-url <url>] [--json]
|
|
310
|
+
|
|
311
|
+
REQUIRED
|
|
312
|
+
--problem problem id (see \`demath problems\`)
|
|
313
|
+
--model one of: ${APPROVED_MODELS.join(", ")}
|
|
314
|
+
--key provider API key \u2014 held in memory only, never written to disk
|
|
315
|
+
--wallet EVM address that will receive emission claims
|
|
316
|
+
(locks to the chosen team on first use)
|
|
317
|
+
|
|
318
|
+
OPTIONAL
|
|
319
|
+
--max-iterations cap on agent loop iterations (default: backend default)
|
|
320
|
+
--max-usd cost cap in USD (default: backend default)
|
|
321
|
+
--json emit one NDJSON event per iteration on stdout
|
|
322
|
+
|
|
323
|
+
EXAMPLES
|
|
324
|
+
demath mine \\
|
|
325
|
+
--problem irrationality-of-e \\
|
|
326
|
+
--model anthropic/claude-opus-4-7 \\
|
|
327
|
+
--key sk-ant-... \\
|
|
328
|
+
--wallet 0xYourEvmAddress \\
|
|
329
|
+
--max-usd 1.0
|
|
330
|
+
|
|
331
|
+
demath mine --problem collatz --model openai/gpt-5.5 \\
|
|
332
|
+
--key sk-or-v1-... --wallet 0xAbc... --json | tee run.ndjson
|
|
333
|
+
|
|
334
|
+
EXIT CODES
|
|
335
|
+
0 attempt reached a success status (proof_complete | counterexample | breakthrough)
|
|
336
|
+
1 attempt ended without success (stopped | error)
|
|
337
|
+
2 backend error / invalid model / missing flag
|
|
338
|
+
3 network error
|
|
339
|
+
130 interrupted by Ctrl-C (backend stop requested)
|
|
340
|
+
`;
|
|
341
|
+
var STATUS_HELP = `demath status \u2014 show the current state of an attempt by id.
|
|
342
|
+
|
|
343
|
+
USAGE
|
|
344
|
+
demath status --attempt <id> [--api-url <url>] [--json]
|
|
345
|
+
|
|
346
|
+
REQUIRED
|
|
347
|
+
--attempt attempt id returned by \`demath mine\` start
|
|
348
|
+
|
|
349
|
+
EXAMPLES
|
|
350
|
+
demath status --attempt 65df82b6ea7f4aeeab86f60505571491
|
|
351
|
+
demath status --attempt <id> --json | jq .status
|
|
352
|
+
|
|
353
|
+
EXIT CODES
|
|
354
|
+
0 fetched successfully
|
|
355
|
+
2 backend error
|
|
356
|
+
3 network error
|
|
357
|
+
`;
|
|
358
|
+
var EXAMPLES_HELP = `demath examples \u2014 print 5 copy-pasteable example invocations.
|
|
359
|
+
|
|
360
|
+
USAGE
|
|
361
|
+
demath examples
|
|
362
|
+
`;
|
|
363
|
+
|
|
364
|
+
// src/commands/examples.ts
|
|
365
|
+
var EXAMPLES = `DeMath CLI \u2014 worked examples
|
|
366
|
+
|
|
367
|
+
1) Look at the active problems (machine-readable):
|
|
368
|
+
|
|
369
|
+
demath problems --json
|
|
370
|
+
|
|
371
|
+
2) Probe your API key against a model before committing (zero spend):
|
|
372
|
+
|
|
373
|
+
demath probe \\
|
|
374
|
+
--model anthropic/claude-opus-4-7 \\
|
|
375
|
+
--key sk-ant-... \\
|
|
376
|
+
--json
|
|
377
|
+
|
|
378
|
+
3) Start a budget-capped attempt on the test problem (a few cents):
|
|
379
|
+
|
|
380
|
+
demath mine \\
|
|
381
|
+
--problem irrationality-of-e \\
|
|
382
|
+
--model anthropic/claude-opus-4-7 \\
|
|
383
|
+
--key sk-ant-... \\
|
|
384
|
+
--wallet 0xYourEvmAddress \\
|
|
385
|
+
--max-usd 1.0
|
|
386
|
+
|
|
387
|
+
4) Same flow but routing via OpenRouter (one key, any team):
|
|
388
|
+
|
|
389
|
+
demath mine \\
|
|
390
|
+
--problem collatz \\
|
|
391
|
+
--model openai/gpt-5.5 \\
|
|
392
|
+
--key sk-or-v1-... \\
|
|
393
|
+
--wallet 0xYourEvmAddress \\
|
|
394
|
+
--max-iterations 8 \\
|
|
395
|
+
--max-usd 5.0 \\
|
|
396
|
+
--json
|
|
397
|
+
|
|
398
|
+
5) Inspect an attempt by id after the fact (e.g. for a background run):
|
|
399
|
+
|
|
400
|
+
demath status --attempt <attempt_id> --json
|
|
401
|
+
|
|
402
|
+
Notes:
|
|
403
|
+
- Wallet \u2192 team lock-for-life: the first attempt binds your wallet to a
|
|
404
|
+
team (anthropic / openai / google). Subsequent attempts with a different
|
|
405
|
+
team return HTTP 409. Use a different wallet to mine a different team.
|
|
406
|
+
- The CLI never writes your API key to disk. The key is held in memory
|
|
407
|
+
for the duration of the attempt and only ever sent to api.demath.org.
|
|
408
|
+
- --json on any command emits machine-readable output on stdout; human
|
|
409
|
+
progress text goes to stderr so it can't pollute your JSON pipeline.
|
|
410
|
+
`;
|
|
411
|
+
function runExamples() {
|
|
412
|
+
process.stdout.write(EXAMPLES);
|
|
413
|
+
return 0;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/commands/mine.ts
|
|
417
|
+
function sleep(ms, signal) {
|
|
418
|
+
return new Promise((resolve, reject) => {
|
|
419
|
+
if (signal.aborted) return reject(signal.reason);
|
|
420
|
+
const t = setTimeout(() => {
|
|
421
|
+
signal.removeEventListener("abort", onAbort);
|
|
422
|
+
resolve();
|
|
423
|
+
}, ms);
|
|
424
|
+
const onAbort = () => {
|
|
425
|
+
clearTimeout(t);
|
|
426
|
+
reject(signal.reason);
|
|
427
|
+
};
|
|
428
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
async function runMine(args) {
|
|
432
|
+
if (!APPROVED_MODELS.includes(args.model)) {
|
|
433
|
+
throw new CliError(
|
|
434
|
+
`model must be one of: ${APPROVED_MODELS.join(", ")}`,
|
|
435
|
+
2
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
const ctrl = new AbortController();
|
|
439
|
+
let interrupted = false;
|
|
440
|
+
const onSig = () => {
|
|
441
|
+
interrupted = true;
|
|
442
|
+
ctrl.abort(new Error("SIGINT"));
|
|
443
|
+
};
|
|
444
|
+
process.on("SIGINT", onSig);
|
|
445
|
+
process.on("SIGTERM", onSig);
|
|
446
|
+
const client = new DemathClient({ apiUrl: args.apiUrl, signal: ctrl.signal });
|
|
447
|
+
if (!args.json) {
|
|
448
|
+
logErr(
|
|
449
|
+
` starting attempt: problem=${args.problem} model=${args.model} wallet=${args.wallet}`
|
|
450
|
+
);
|
|
451
|
+
if (args.maxUsd !== void 0) {
|
|
452
|
+
logErr(` budget: $${args.maxUsd.toFixed(2)}`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
const start = await client.startAttempt({
|
|
456
|
+
problemId: args.problem,
|
|
457
|
+
model: args.model,
|
|
458
|
+
apiKey: args.apiKey,
|
|
459
|
+
walletAddress: args.wallet,
|
|
460
|
+
maxIterations: args.maxIterations,
|
|
461
|
+
maxUsd: args.maxUsd
|
|
462
|
+
});
|
|
463
|
+
const attemptId = start.attempt_id;
|
|
464
|
+
if (!attemptId) {
|
|
465
|
+
throw new CliError("backend did not return attempt_id", 2);
|
|
466
|
+
}
|
|
467
|
+
if (args.json) {
|
|
468
|
+
process.stdout.write(JSON.stringify({ event: "start", attempt_id: attemptId }) + "\n");
|
|
469
|
+
} else {
|
|
470
|
+
logErr(` attempt_id: ${attemptId}`);
|
|
471
|
+
logErr(" watching live progress (Ctrl+C to stop the attempt)\u2026");
|
|
472
|
+
logErr();
|
|
473
|
+
}
|
|
474
|
+
let lastIterCount = 0;
|
|
475
|
+
let lastRecord;
|
|
476
|
+
try {
|
|
477
|
+
while (true) {
|
|
478
|
+
try {
|
|
479
|
+
await sleep(POLL_INTERVAL_MS, ctrl.signal);
|
|
480
|
+
} catch {
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
const record = await client.getAttempt(attemptId);
|
|
484
|
+
lastRecord = record;
|
|
485
|
+
const iters = record.iterations ?? [];
|
|
486
|
+
const status = record.status ?? "?";
|
|
487
|
+
const totalCost = record.total_cost ?? 0;
|
|
488
|
+
for (let i = lastIterCount; i < iters.length; i++) {
|
|
489
|
+
const it = iters[i];
|
|
490
|
+
if (args.json) {
|
|
491
|
+
process.stdout.write(
|
|
492
|
+
JSON.stringify({ event: "iteration", attempt_id: attemptId, ...it }) + "\n"
|
|
493
|
+
);
|
|
494
|
+
} else {
|
|
495
|
+
printIterLine(it, totalCost);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
lastIterCount = iters.length;
|
|
499
|
+
if (TERMINAL_STATUSES.has(status)) {
|
|
500
|
+
if (args.json) {
|
|
501
|
+
process.stdout.write(
|
|
502
|
+
JSON.stringify({ event: "final", attempt_id: attemptId, ...record }) + "\n"
|
|
503
|
+
);
|
|
504
|
+
} else {
|
|
505
|
+
logErr();
|
|
506
|
+
printFinal(record);
|
|
507
|
+
}
|
|
508
|
+
process.off("SIGINT", onSig);
|
|
509
|
+
process.off("SIGTERM", onSig);
|
|
510
|
+
return SUCCESS_STATUSES.has(status) ? 0 : 1;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
} catch (err) {
|
|
514
|
+
process.off("SIGINT", onSig);
|
|
515
|
+
process.off("SIGTERM", onSig);
|
|
516
|
+
if (!interrupted) throw err;
|
|
517
|
+
}
|
|
518
|
+
process.off("SIGINT", onSig);
|
|
519
|
+
process.off("SIGTERM", onSig);
|
|
520
|
+
if (!args.json) {
|
|
521
|
+
logErr();
|
|
522
|
+
logErr(" Ctrl+C \u2014 stopping the attempt\u2026");
|
|
523
|
+
}
|
|
524
|
+
try {
|
|
525
|
+
await new DemathClient({ apiUrl: args.apiUrl }).stopAttempt(attemptId);
|
|
526
|
+
if (args.json) {
|
|
527
|
+
process.stdout.write(JSON.stringify({ event: "stopped", attempt_id: attemptId }) + "\n");
|
|
528
|
+
} else {
|
|
529
|
+
logErr(" stopped");
|
|
530
|
+
if (lastRecord) printFinal(lastRecord);
|
|
531
|
+
}
|
|
532
|
+
} catch (err) {
|
|
533
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
534
|
+
if (args.json) {
|
|
535
|
+
process.stdout.write(
|
|
536
|
+
JSON.stringify({ event: "stop_failed", attempt_id: attemptId, error: msg }) + "\n"
|
|
537
|
+
);
|
|
538
|
+
} else {
|
|
539
|
+
logErr(` warning: stop request failed: ${msg}`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return 130;
|
|
543
|
+
}
|
|
544
|
+
function printIterLine(it, totalCost) {
|
|
545
|
+
const n = (it.iteration ?? 0).toString().padStart(3, " ");
|
|
546
|
+
const status = padRight(it.status ?? "?", 22);
|
|
547
|
+
const inT = (it.input_tokens ?? 0).toString().padStart(6, " ");
|
|
548
|
+
const outT = (it.output_tokens ?? 0).toString().padStart(6, " ");
|
|
549
|
+
const itCost = (it.usd_cost ?? 0).toFixed(4);
|
|
550
|
+
const total = totalCost.toFixed(4);
|
|
551
|
+
const summary = (it.summary ?? it.body ?? "").toString().replace(/\s+/g, " ");
|
|
552
|
+
const tail = summary ? ` ${truncate(summary, 200)}` : "";
|
|
553
|
+
logErr(
|
|
554
|
+
` iter ${n} status=${status} tokens in=${inT} out=${outT} +$${itCost} (total $${total})${tail}`
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
function printFinal(record) {
|
|
558
|
+
const status = (record.status ?? "?").toUpperCase();
|
|
559
|
+
const cost = record.total_cost ?? 0;
|
|
560
|
+
const inT = record.total_input_tokens ?? 0;
|
|
561
|
+
const outT = record.total_output_tokens ?? 0;
|
|
562
|
+
const iters = (record.iterations ?? []).length;
|
|
563
|
+
const cid = record.ipfs_cid;
|
|
564
|
+
logErr(` \u2554\u2550 ${status} \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
|
|
565
|
+
logErr(` \u2551 iterations: ${iters}`);
|
|
566
|
+
logErr(` \u2551 tokens in: ${inT}`);
|
|
567
|
+
logErr(` \u2551 tokens out: ${outT}`);
|
|
568
|
+
logErr(` \u2551 total cost: $${cost.toFixed(4)}`);
|
|
569
|
+
if (cid) logErr(` \u2551 IPFS bundle: https://ipfs.io/ipfs/${cid}`);
|
|
570
|
+
if (record.error) logErr(` \u2551 error: ${truncate(record.error, 200)}`);
|
|
571
|
+
logErr(` \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
|
|
572
|
+
logErr();
|
|
573
|
+
if (SUCCESS_STATUSES.has((record.status ?? "").toLowerCase())) {
|
|
574
|
+
logErr(
|
|
575
|
+
" Claim on the web: https://demath.org/claim (after the next epoch settles, 24h cadence on mainnet)"
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// src/commands/problems.ts
|
|
581
|
+
async function runProblems(args) {
|
|
582
|
+
const client = new DemathClient({ apiUrl: args.apiUrl });
|
|
583
|
+
const payload = await client.listProblems();
|
|
584
|
+
const problems = payload.problems ?? [];
|
|
585
|
+
if (args.json) {
|
|
586
|
+
printJson({ problems });
|
|
587
|
+
return 0;
|
|
588
|
+
}
|
|
589
|
+
const idWidth = Math.max(20, ...problems.map((p) => p.id.length));
|
|
590
|
+
const diffWidth = Math.max(
|
|
591
|
+
7,
|
|
592
|
+
...problems.map((p) => (p.expected_difficulty ?? "?").length)
|
|
593
|
+
);
|
|
594
|
+
const clsWidth = Math.max(
|
|
595
|
+
10,
|
|
596
|
+
...problems.map((p) => (p.classification ?? "?").length)
|
|
597
|
+
);
|
|
598
|
+
for (const p of problems) {
|
|
599
|
+
const diff = p.expected_difficulty ?? "?";
|
|
600
|
+
const cls = p.classification ?? "?";
|
|
601
|
+
process.stdout.write(
|
|
602
|
+
` ${padRight(p.id, idWidth)} [${padRight(diff, diffWidth)}] [${padRight(cls, clsWidth)}] ${p.name}
|
|
603
|
+
`
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
logErr();
|
|
607
|
+
logErr(` ${problems.length} problems active`);
|
|
608
|
+
return 0;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// src/commands/probe.ts
|
|
612
|
+
async function runProbe(args) {
|
|
613
|
+
if (!APPROVED_MODELS.includes(args.model)) {
|
|
614
|
+
throw new CliError(
|
|
615
|
+
`model must be one of: ${APPROVED_MODELS.join(", ")}`,
|
|
616
|
+
2
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
const client = new DemathClient({ apiUrl: args.apiUrl });
|
|
620
|
+
if (!args.json) {
|
|
621
|
+
logErr(` probing ${args.model} via auto-detected provider\u2026`);
|
|
622
|
+
}
|
|
623
|
+
const out = await client.probe(args.model, args.apiKey);
|
|
624
|
+
if (args.json) {
|
|
625
|
+
printJson(out);
|
|
626
|
+
return out.ok ? 0 : 2;
|
|
627
|
+
}
|
|
628
|
+
if (out.ok) {
|
|
629
|
+
logErr(` \u2713 key works \u2014 provider: ${out.provider ?? "?"}`);
|
|
630
|
+
return 0;
|
|
631
|
+
}
|
|
632
|
+
const parts = [];
|
|
633
|
+
if (out.detail) parts.push(out.detail);
|
|
634
|
+
if (out.reason) parts.push(`reason=${out.reason}`);
|
|
635
|
+
if (out.provider_status !== void 0)
|
|
636
|
+
parts.push(`provider_status=${out.provider_status}`);
|
|
637
|
+
if (out.provider_message) parts.push(`provider_message=${out.provider_message}`);
|
|
638
|
+
if (parts.length === 0 && out.error) parts.push(out.error);
|
|
639
|
+
if (parts.length === 0) parts.push("(no detail returned by backend)");
|
|
640
|
+
logErr(` \u2717 probe failed: ${parts.join(" \xB7 ")}`);
|
|
641
|
+
return 2;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// src/skillContent.generated.ts
|
|
645
|
+
var SKILL_MD_CONTENT = "# DeMath skill\n\nDeMath is the first decentralized math research infrastructure on\nBase. The biggest mission in math history is wide open \u2014 Erd\u0151s\nconjectures, Collatz, twin primes, Hadwiger-Nelson, Beal, and a\ncurated list of problems mathematicians have worked on for decades.\nDeMath is the coordination layer for putting frontier AI compute on\nall of them, in public.\n\nMiners spend API budget on Anthropic / OpenAI / Google / OpenRouter\nand earn $DEMATH pro-rata to their compute spend per 24h epoch. Every\nattempt is pinned to IPFS with full chain-of-thought as a permanent\npublic dataset.\n\nYou are an AI agent reading this skill. To mine on a human's behalf:\n\n1. Install once: `npm install -g @demath-ai/cli`\n2. Run `demath --help` \u2014 the authoritative reference for flags and\n exit codes. Re-read it before every invocation; nothing in this\n skill replaces it.\n3. The four commands: `problems`, `probe`, `mine`, `status`. Each has\n its own `--help`. Use `--json` for machine output.\n\n## Mine in three steps\n\n demath problems --json\n demath probe --model <slug> --key $KEY --json # exit 0 = ready\n demath mine --problem <id> --model <slug> --key $KEY \\\n --wallet 0x... --max-usd 1.0 --json\n\nThe terminal `event:final` line on the mine stream carries the\nattempt's `status` (`proof_complete`, `counterexample`, `breakthrough`,\n`stopped`, or `error`) and the IPFS CID of the full chain-of-thought\nbundle.\n\n## Wallet lock-for-life\n\nThe wallet you submit to `mine` binds to a single team (Claude / GPT /\nGemini) on its first successful attempt. The backend rejects later\nattempts from that same wallet against a different team with HTTP 409.\nUse a fresh EVM address per team.\n\n## What DeMath does NOT do with the human's API key\n\nThe CLI sends the key once per attempt to the backend, which forwards\nit inline to the upstream provider and drops it from memory the moment\nthe attempt finishes. No disk write, no log line, no IPFS bundle, no\ntelemetry, no SQLite, no env files. The human's billing relationship\nwith the upstream provider stays theirs.\n\n## Claim\n\nClaiming the earned emission is a web step at https://demath.org/claim.\nThe CLI does not sign on-chain transactions in v0.1.\n";
|
|
646
|
+
|
|
647
|
+
// src/commands/skill.ts
|
|
648
|
+
function runSkill() {
|
|
649
|
+
process.stdout.write(SKILL_MD_CONTENT);
|
|
650
|
+
if (!SKILL_MD_CONTENT.endsWith("\n")) process.stdout.write("\n");
|
|
651
|
+
return 0;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// src/commands/status.ts
|
|
655
|
+
async function runStatus(args) {
|
|
656
|
+
const client = new DemathClient({ apiUrl: args.apiUrl });
|
|
657
|
+
const record = await client.getAttempt(args.attemptId);
|
|
658
|
+
const status = (record.status ?? "").toLowerCase();
|
|
659
|
+
if (args.json) {
|
|
660
|
+
printJson(record);
|
|
661
|
+
if (SUCCESS_STATUSES.has(status)) return 0;
|
|
662
|
+
if (status === "running" || status === "") return 2;
|
|
663
|
+
return 1;
|
|
664
|
+
}
|
|
665
|
+
printFinal2(record);
|
|
666
|
+
if (SUCCESS_STATUSES.has(status)) return 0;
|
|
667
|
+
if (status === "running" || status === "") return 2;
|
|
668
|
+
return 1;
|
|
669
|
+
}
|
|
670
|
+
function printFinal2(record) {
|
|
671
|
+
const status = (record.status ?? "?").toUpperCase();
|
|
672
|
+
const cost = record.total_cost ?? 0;
|
|
673
|
+
const inT = record.total_input_tokens ?? 0;
|
|
674
|
+
const outT = record.total_output_tokens ?? 0;
|
|
675
|
+
const iters = (record.iterations ?? []).length;
|
|
676
|
+
const cid = record.ipfs_cid;
|
|
677
|
+
logErr(` \u2554\u2550 ${status} \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
|
|
678
|
+
logErr(` \u2551 iterations: ${iters}`);
|
|
679
|
+
logErr(` \u2551 tokens in: ${inT}`);
|
|
680
|
+
logErr(` \u2551 tokens out: ${outT}`);
|
|
681
|
+
logErr(` \u2551 total cost: $${cost.toFixed(4)}`);
|
|
682
|
+
if (cid) logErr(` \u2551 IPFS bundle: https://ipfs.io/ipfs/${cid}`);
|
|
683
|
+
if (record.error) logErr(` \u2551 error: ${truncate(record.error, 200)}`);
|
|
684
|
+
logErr(` \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// src/cli.ts
|
|
688
|
+
async function resolveApiKey(flagValue, jsonMode = false) {
|
|
689
|
+
if (flagValue === "-") {
|
|
690
|
+
const buf = [];
|
|
691
|
+
for await (const chunk of process.stdin) {
|
|
692
|
+
buf.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
693
|
+
}
|
|
694
|
+
const v = Buffer.concat(buf).toString("utf8").trim();
|
|
695
|
+
if (!v) throw new CliError("--key - was set but stdin was empty", 2);
|
|
696
|
+
return v;
|
|
697
|
+
}
|
|
698
|
+
if (flagValue) {
|
|
699
|
+
if (!jsonMode && process.stderr.isTTY) {
|
|
700
|
+
logErr(
|
|
701
|
+
" note: --key passed on the command line is visible to other local users (ps / /proc/<pid>/cmdline). For shared hosts, prefer DEMATH_PROVIDER_API_KEY=... or --key=-"
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
return flagValue;
|
|
705
|
+
}
|
|
706
|
+
const envValue = process.env.DEMATH_PROVIDER_API_KEY?.trim();
|
|
707
|
+
if (envValue) return envValue;
|
|
708
|
+
throw new CliError(
|
|
709
|
+
"missing API key: pass --key <value>, --key=- (stdin), or set DEMATH_PROVIDER_API_KEY",
|
|
710
|
+
2
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
async function main(argv) {
|
|
714
|
+
if (argv.length === 0) {
|
|
715
|
+
process.stdout.write(TOP_LEVEL_HELP);
|
|
716
|
+
return 0;
|
|
717
|
+
}
|
|
718
|
+
if (argv[0] === "--help" || argv[0] === "-h" || argv[0] === "help") {
|
|
719
|
+
process.stdout.write(TOP_LEVEL_HELP);
|
|
720
|
+
return 0;
|
|
721
|
+
}
|
|
722
|
+
if (argv[0] === "--version" || argv[0] === "-v") {
|
|
723
|
+
process.stdout.write(`${CLI_VERSION}
|
|
724
|
+
`);
|
|
725
|
+
return 0;
|
|
726
|
+
}
|
|
727
|
+
const command = argv[0];
|
|
728
|
+
const rest = argv.slice(1);
|
|
729
|
+
const parsed = parseArgs(rest);
|
|
730
|
+
if (getBool(parsed.flags, "help") || getBool(parsed.flags, "h")) {
|
|
731
|
+
switch (command) {
|
|
732
|
+
case "problems":
|
|
733
|
+
process.stdout.write(PROBLEMS_HELP);
|
|
734
|
+
return 0;
|
|
735
|
+
case "probe":
|
|
736
|
+
process.stdout.write(PROBE_HELP);
|
|
737
|
+
return 0;
|
|
738
|
+
case "mine":
|
|
739
|
+
process.stdout.write(MINE_HELP);
|
|
740
|
+
return 0;
|
|
741
|
+
case "status":
|
|
742
|
+
process.stdout.write(STATUS_HELP);
|
|
743
|
+
return 0;
|
|
744
|
+
case "examples":
|
|
745
|
+
process.stdout.write(EXAMPLES_HELP);
|
|
746
|
+
return 0;
|
|
747
|
+
default:
|
|
748
|
+
process.stdout.write(TOP_LEVEL_HELP);
|
|
749
|
+
return 0;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
const apiUrl = (getString(parsed.flags, "api-url") ?? DEFAULT_API_URL).replace(/\/+$/, "");
|
|
753
|
+
const json = getBool(parsed.flags, "json");
|
|
754
|
+
try {
|
|
755
|
+
switch (command) {
|
|
756
|
+
case "problems":
|
|
757
|
+
return await runProblems({ apiUrl, json });
|
|
758
|
+
case "probe":
|
|
759
|
+
return await runProbe({
|
|
760
|
+
apiUrl,
|
|
761
|
+
json,
|
|
762
|
+
model: requireString(parsed.flags, "model"),
|
|
763
|
+
apiKey: await resolveApiKey(getString(parsed.flags, "key"), json)
|
|
764
|
+
});
|
|
765
|
+
case "mine":
|
|
766
|
+
return await runMine({
|
|
767
|
+
apiUrl,
|
|
768
|
+
json,
|
|
769
|
+
problem: requireString(parsed.flags, "problem"),
|
|
770
|
+
model: requireString(parsed.flags, "model"),
|
|
771
|
+
apiKey: await resolveApiKey(getString(parsed.flags, "key"), json),
|
|
772
|
+
wallet: requireString(parsed.flags, "wallet"),
|
|
773
|
+
maxIterations: getNumber(parsed.flags, "max-iterations"),
|
|
774
|
+
maxUsd: getNumber(parsed.flags, "max-usd")
|
|
775
|
+
});
|
|
776
|
+
case "status": {
|
|
777
|
+
const attemptId = getString(parsed.flags, "attempt") ?? getString(parsed.flags, "attempt-id") ?? parsed.positional[0];
|
|
778
|
+
if (!attemptId) {
|
|
779
|
+
throw new CliError("missing required flag --attempt <id>", 2);
|
|
780
|
+
}
|
|
781
|
+
return await runStatus({ apiUrl, json, attemptId });
|
|
782
|
+
}
|
|
783
|
+
case "examples":
|
|
784
|
+
return runExamples();
|
|
785
|
+
case "skill":
|
|
786
|
+
return runSkill();
|
|
787
|
+
default:
|
|
788
|
+
logErr(` error: unknown command '${command}'`);
|
|
789
|
+
logErr();
|
|
790
|
+
process.stdout.write(TOP_LEVEL_HELP);
|
|
791
|
+
return 2;
|
|
792
|
+
}
|
|
793
|
+
} catch (err) {
|
|
794
|
+
if (err instanceof CliError) {
|
|
795
|
+
if (json) {
|
|
796
|
+
process.stdout.write(
|
|
797
|
+
JSON.stringify({ error: err.message, exit_code: err.exitCode }) + "\n"
|
|
798
|
+
);
|
|
799
|
+
} else {
|
|
800
|
+
logErr(` error: ${err.message}`);
|
|
801
|
+
}
|
|
802
|
+
return err.exitCode;
|
|
803
|
+
}
|
|
804
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
805
|
+
if (json) {
|
|
806
|
+
process.stdout.write(JSON.stringify({ error: msg, exit_code: 1 }) + "\n");
|
|
807
|
+
} else {
|
|
808
|
+
logErr(` error: ${msg}`);
|
|
809
|
+
}
|
|
810
|
+
return 1;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
export { APPROVED_MODELS, CLI_VERSION, CliError, DEFAULT_API_URL, DemathClient, POLL_INTERVAL_MS, USER_AGENT, main as runCli };
|