@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/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 };