@flop-labs/cli 0.4.1 → 0.4.3

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/cli.js +362 -125
  3. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -7,6 +7,31 @@ and this package follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.3] - 2026-11-09
11
+
12
+ ### Fixed
13
+
14
+ - Package with SDK 0.5.2 as the minimum compatible SDK, including canonical session
15
+ pallet aliases. The 30-second challenge and 120-second enrollment deadlines
16
+ shipped in 0.4.2 are unchanged; packed offline regressions cover those paths.
17
+
18
+ ## [0.4.2] - 2026-11-09
19
+
20
+ ### Added
21
+
22
+ - `flop model backup <model_hash>` streams a registered `content-v1` checkpoint from its
23
+ Hugging Face mirror into the validators' DA copy under the running box's own DA key,
24
+ piping the packed image into `da-validator` instead of writing it out, so the box needs
25
+ room for the checkpoint rather than twice it. It refuses a root the hub table does not
26
+ reproduce, prints the plan and refundable DA deposit under `--dry-run`, and leaves the
27
+ `ModelWeightsRef` binding to the model publisher. Both the download and the DA push
28
+ resume on a rerun. Metadata-only onboarding now prints this command as its next step.
29
+
30
+ ### Fixed
31
+
32
+ - Wallet enrollment now waits up to 120 seconds for the gateway's answer, so a slow enrollment
33
+ completes and a refusal such as a full daily cap is reported instead of a timeout.
34
+
10
35
  ## [0.4.1] - 2026-11-08
11
36
 
12
37
  ### Added
package/dist/cli.js CHANGED
@@ -724,14 +724,14 @@ var init_task_runner = __esm({
724
724
  }
725
725
  }
726
726
  withTimeout(promise, timeoutMs) {
727
- return new Promise((resolve9, reject) => {
727
+ return new Promise((resolve10, reject) => {
728
728
  const timer = setTimeout(
729
729
  () => reject(new AgentError("T002" /* TASK_TIMEOUT */, `Task timed out after ${timeoutMs}ms`, true)),
730
730
  timeoutMs
731
731
  );
732
732
  promise.then((result) => {
733
733
  clearTimeout(timer);
734
- resolve9(result);
734
+ resolve10(result);
735
735
  }).catch((err) => {
736
736
  clearTimeout(timer);
737
737
  reject(err);
@@ -739,7 +739,7 @@ var init_task_runner = __esm({
739
739
  });
740
740
  }
741
741
  sleep(ms) {
742
- return new Promise((resolve9) => setTimeout(resolve9, ms));
742
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
743
743
  }
744
744
  };
745
745
  }
@@ -904,7 +904,7 @@ var init_worker = __esm({
904
904
  return this.identity;
905
905
  }
906
906
  sleep(ms) {
907
- return new Promise((resolve9) => setTimeout(resolve9, ms));
907
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
908
908
  }
909
909
  };
910
910
  }
@@ -912,10 +912,10 @@ var init_worker = __esm({
912
912
 
913
913
  // ../agent/src/chain-apis.ts
914
914
  async function submitTx(tx, signer) {
915
- return new Promise((resolve9, reject) => {
915
+ return new Promise((resolve10, reject) => {
916
916
  tx.signAndSend(signer, ({ status, dispatchError }) => {
917
917
  if (dispatchError) reject(new Error(`Tx failed: ${dispatchError.toString()}`));
918
- else if (status.isInBlock || status.isFinalized) resolve9(status.asFinalized.toString());
918
+ else if (status.isInBlock || status.isFinalized) resolve10(status.asFinalized.toString());
919
919
  }).catch(reject);
920
920
  });
921
921
  }
@@ -1219,7 +1219,7 @@ async function waitForTaskCompletion(api, taskId, timeoutMs = DEFAULT_TIMEOUT, p
1219
1219
  return receipt;
1220
1220
  }
1221
1221
  }
1222
- await new Promise((resolve9) => setTimeout(resolve9, pollIntervalMs));
1222
+ await new Promise((resolve10) => setTimeout(resolve10, pollIntervalMs));
1223
1223
  }
1224
1224
  throw new AgentError(
1225
1225
  "T002" /* TASK_TIMEOUT */,
@@ -1878,7 +1878,7 @@ async function waitForBalance(api, address, minBalance, timeoutMs = 6e4) {
1878
1878
  }
1879
1879
  } catch {
1880
1880
  }
1881
- await new Promise((resolve9) => setTimeout(resolve9, pollInterval));
1881
+ await new Promise((resolve10) => setTimeout(resolve10, pollInterval));
1882
1882
  }
1883
1883
  return false;
1884
1884
  }
@@ -4941,9 +4941,9 @@ var init_src = __esm({
4941
4941
  });
4942
4942
 
4943
4943
  // src/cli.ts
4944
- import { Command as Command48, Option as Option7 } from "commander";
4944
+ import { Command as Command49, Option as Option8 } from "commander";
4945
4945
  import { readFileSync as readFileSync2 } from "node:fs";
4946
- import { join as join10 } from "node:path";
4946
+ import { join as join11 } from "node:path";
4947
4947
 
4948
4948
  // src/output.ts
4949
4949
  var activeOutputMode = "human";
@@ -6127,12 +6127,12 @@ function pathCheck(id, target, required = true) {
6127
6127
  };
6128
6128
  }
6129
6129
  async function probeHttpUrl(id, url, required = true, timeoutMs = 3e3) {
6130
- return new Promise((resolve9) => {
6130
+ return new Promise((resolve10) => {
6131
6131
  let parsed;
6132
6132
  try {
6133
6133
  parsed = new URL(url);
6134
6134
  } catch {
6135
- resolve9({
6135
+ resolve10({
6136
6136
  id,
6137
6137
  status: "fail",
6138
6138
  required,
@@ -6145,7 +6145,7 @@ async function probeHttpUrl(id, url, required = true, timeoutMs = 3e3) {
6145
6145
  const request3 = transport.request(parsed, { method: "GET", timeout: timeoutMs }, (response) => {
6146
6146
  response.resume();
6147
6147
  const reachable = (response.statusCode ?? 500) < 500;
6148
- resolve9({
6148
+ resolve10({
6149
6149
  id,
6150
6150
  status: reachable ? "pass" : required ? "fail" : "warn",
6151
6151
  required,
@@ -6155,7 +6155,7 @@ async function probeHttpUrl(id, url, required = true, timeoutMs = 3e3) {
6155
6155
  });
6156
6156
  request3.on("timeout", () => request3.destroy(new Error("timeout")));
6157
6157
  request3.on("error", (error) => {
6158
- resolve9({
6158
+ resolve10({
6159
6159
  id,
6160
6160
  status: required ? "fail" : "warn",
6161
6161
  required,
@@ -6199,7 +6199,7 @@ function unwrapStorage(raw) {
6199
6199
 
6200
6200
  // src/commands/launch.ts
6201
6201
  function checkGateway(url) {
6202
- return new Promise((resolve9) => {
6202
+ return new Promise((resolve10) => {
6203
6203
  try {
6204
6204
  const parsedUrl = new URL(url);
6205
6205
  const req = http2.request(
@@ -6211,17 +6211,17 @@ function checkGateway(url) {
6211
6211
  timeout: 1e3
6212
6212
  },
6213
6213
  (res) => {
6214
- resolve9(res.statusCode === 200);
6214
+ resolve10(res.statusCode === 200);
6215
6215
  }
6216
6216
  );
6217
- req.on("error", () => resolve9(false));
6217
+ req.on("error", () => resolve10(false));
6218
6218
  req.on("timeout", () => {
6219
6219
  req.destroy();
6220
- resolve9(false);
6220
+ resolve10(false);
6221
6221
  });
6222
6222
  req.end();
6223
6223
  } catch {
6224
- resolve9(false);
6224
+ resolve10(false);
6225
6225
  }
6226
6226
  });
6227
6227
  }
@@ -6529,7 +6529,7 @@ async function withCredentialLock(path5, action) {
6529
6529
  continue;
6530
6530
  }
6531
6531
  if (Date.now() >= deadline) throw new Error(`FLOP credential lock is busy at ${lock}`);
6532
- await new Promise((resolve9) => setTimeout(resolve9, 100));
6532
+ await new Promise((resolve10) => setTimeout(resolve10, 100));
6533
6533
  }
6534
6534
  }
6535
6535
  try {
@@ -6589,6 +6589,8 @@ function validateWalletEnrollmentChallenge(message, address, gateway, genesis, n
6589
6589
  const issued = Date.parse(issuedText), expires = Date.parse(expiresText);
6590
6590
  if (!Number.isFinite(issued) || !Number.isFinite(expires) || lines[6] !== `Issued At: ${new Date(issued).toISOString()}` || lines[7] !== `Expiration Time: ${new Date(expires).toISOString()}` || issued > now || expires <= now || expires <= issued || expires - issued > 5 * 6e4) throw invalid();
6591
6591
  }
6592
+ var ENROLLMENT_CHALLENGE_TIMEOUT_MS = 3e4;
6593
+ var ENROLLMENT_SUBMIT_TIMEOUT_MS = 12e4;
6592
6594
  function walletEnrollment(options) {
6593
6595
  return async () => {
6594
6596
  if (options.purpose) await hermesEnrollmentOffer(options.gateway);
@@ -6605,12 +6607,12 @@ function walletEnrollment(options) {
6605
6607
  const deadline = Date.now() + (options.fundTimeoutMs ?? 12e4);
6606
6608
  while (await options.balance(pair.address) < minBalance) {
6607
6609
  if (Date.now() >= deadline) throw new Error("devnet faucet grant did not finalize in time; retry later");
6608
- await new Promise((resolve9) => setTimeout(resolve9, options.pollMs ?? 3e3));
6610
+ await new Promise((resolve10) => setTimeout(resolve10, options.pollMs ?? 3e3));
6609
6611
  }
6610
6612
  }
6611
6613
  const challenge = await fetch(
6612
6614
  `${options.gateway}/v1/enroll/wallet/challenge?address=${encodeURIComponent(pair.address)}${options.purpose ? `&purpose=${options.purpose}` : ""}`,
6613
- { redirect: "error", signal: AbortSignal.timeout(options.purpose ? 3e4 : 1e4) }
6615
+ { redirect: "error", signal: AbortSignal.timeout(ENROLLMENT_CHALLENGE_TIMEOUT_MS) }
6614
6616
  );
6615
6617
  if (!challenge.ok) throw await failure(challenge, "devnet enrollment challenge");
6616
6618
  const { message } = await challenge.json();
@@ -6621,7 +6623,7 @@ function walletEnrollment(options) {
6621
6623
  headers: { "content-type": "application/json" },
6622
6624
  body: JSON.stringify({ address: pair.address, message, signature }),
6623
6625
  redirect: "error",
6624
- signal: AbortSignal.timeout(options.purpose ? 3e4 : 1e4)
6626
+ signal: AbortSignal.timeout(ENROLLMENT_SUBMIT_TIMEOUT_MS)
6625
6627
  });
6626
6628
  };
6627
6629
  }
@@ -7012,14 +7014,14 @@ var CORS_HEADERS = {
7012
7014
  "Access-Control-Allow-Headers": "Authorization, Content-Type"
7013
7015
  };
7014
7016
  function readBody(req) {
7015
- return new Promise((resolve9) => {
7017
+ return new Promise((resolve10) => {
7016
7018
  let data = "";
7017
7019
  req.on("data", (c) => data += c);
7018
7020
  req.on("end", () => {
7019
7021
  try {
7020
- resolve9(data ? JSON.parse(data) : {});
7022
+ resolve10(data ? JSON.parse(data) : {});
7021
7023
  } catch {
7022
- resolve9({});
7024
+ resolve10({});
7023
7025
  }
7024
7026
  });
7025
7027
  });
@@ -7256,7 +7258,7 @@ async function startProxyServer(o) {
7256
7258
  embeddingModel: o.embeddingModel,
7257
7259
  embed: createLocalEmbedder(o.embeddingModel)
7258
7260
  });
7259
- await new Promise((resolve9) => server.listen(o.port, o.host, () => resolve9()));
7261
+ await new Promise((resolve10) => server.listen(o.port, o.host, () => resolve10()));
7260
7262
  const addr = server.address();
7261
7263
  const url = `http://${o.host}:${addr.port}`;
7262
7264
  const models = await listModels();
@@ -7393,9 +7395,9 @@ function launchExternal2(command, args, envOverrides) {
7393
7395
  function runHermesConfig(launcher, commands) {
7394
7396
  return commands.reduce(
7395
7397
  (p, args) => p.then(
7396
- () => new Promise((resolve9, reject) => {
7398
+ () => new Promise((resolve10, reject) => {
7397
7399
  const c = spawnLauncher(launcher, args, { stdio: "inherit" });
7398
- c.on("exit", (code) => code === 0 ? resolve9() : reject(new Error(`hermes ${args.join(" ")} exited ${code}`)));
7400
+ c.on("exit", (code) => code === 0 ? resolve10() : reject(new Error(`hermes ${args.join(" ")} exited ${code}`)));
7399
7401
  c.on("error", reject);
7400
7402
  })
7401
7403
  ),
@@ -7630,6 +7632,7 @@ import { basename as basename2, join as join6, resolve as resolve8 } from "node:
7630
7632
  import { resolve as resolve5, relative, sep } from "node:path";
7631
7633
  import { mkdir as mkdir7, readFile as readFile4, readdir, lstat as lstat3, writeFile as writeFile7 } from "node:fs/promises";
7632
7634
  import { execFile as execFile3, spawn as spawn4 } from "node:child_process";
7635
+ import { pipeline } from "node:stream/promises";
7633
7636
  import { promisify as promisify3 } from "node:util";
7634
7637
  function modelToOutput(model) {
7635
7638
  return {
@@ -7752,6 +7755,9 @@ async function daCommit(executable, blobFile) {
7752
7755
  const { stdout: stdout2 } = await run2(executable, ["commit", "--blob-file", blobFile, "--json"], {
7753
7756
  maxBuffer: 1024 * 1024
7754
7757
  });
7758
+ return parseDaLayout(stdout2);
7759
+ }
7760
+ function parseDaLayout(stdout2) {
7755
7761
  const line = lastJsonLine(stdout2);
7756
7762
  if (!line) throw new Error("da-validator commit returned no JSON result");
7757
7763
  const result = JSON.parse(line);
@@ -7765,6 +7771,62 @@ async function daCommit(executable, blobFile) {
7765
7771
  chunks: result.chunks
7766
7772
  };
7767
7773
  }
7774
+ async function runWithBlobOnStdin(executable, args, env, body) {
7775
+ const child = spawn4(executable, args, { stdio: ["pipe", "pipe", "inherit"], env });
7776
+ let stdout2 = "";
7777
+ child.stdout.setEncoding("utf8");
7778
+ child.stdout.on("data", (part) => {
7779
+ stdout2 += part;
7780
+ });
7781
+ let streamError;
7782
+ const fed = pipeline(body, child.stdin).catch((error) => {
7783
+ if (error.code === "EPIPE") return;
7784
+ streamError = error;
7785
+ child.kill("SIGKILL");
7786
+ });
7787
+ const code = await new Promise((resolveExit, rejectExit) => {
7788
+ child.on("error", rejectExit);
7789
+ child.on("close", resolveExit);
7790
+ });
7791
+ await fed;
7792
+ if (streamError) throw streamError;
7793
+ return { code, stdout: stdout2 };
7794
+ }
7795
+ async function daCommitStream(executable, blob) {
7796
+ const { code, stdout: stdout2 } = await runWithBlobOnStdin(
7797
+ executable,
7798
+ ["commit", "--blob-stdin", "--size", String(blob.sizeBytes), "--manifest-out", blob.manifestFile, "--json"],
7799
+ process.env,
7800
+ blob.open()
7801
+ );
7802
+ if (code !== 0) throw new Error(`da-validator commit failed (exit ${code}); see its log above`);
7803
+ return parseDaLayout(stdout2);
7804
+ }
7805
+ async function submitDaBlobStream(executable, rpc, blob, publisherKey) {
7806
+ const env = { ...process.env };
7807
+ if (publisherKey) env.DA_PUBLISHER_KEY = publisherKey;
7808
+ else delete env.DA_PUBLISHER_KEY;
7809
+ const { code, stdout: stdout2 } = await runWithBlobOnStdin(
7810
+ executable,
7811
+ [
7812
+ "submit",
7813
+ "--chain-rpc",
7814
+ rpc,
7815
+ "--blob-stdin",
7816
+ "--size",
7817
+ String(blob.sizeBytes),
7818
+ "--manifest-file",
7819
+ blob.manifestFile,
7820
+ "--json"
7821
+ ],
7822
+ env,
7823
+ blob.open()
7824
+ );
7825
+ if (code !== 0 && !lastJsonLine(stdout2)) {
7826
+ throw new Error(`da-validator submit failed (exit ${code}); see its log above`);
7827
+ }
7828
+ return parseCustodyResult(stdout2);
7829
+ }
7768
7830
  function parseCustodyResult(stdout2) {
7769
7831
  const line = lastJsonLine(stdout2);
7770
7832
  if (!line) throw new Error("da-validator returned no JSON custody result");
@@ -9289,7 +9351,9 @@ function modelOnboardCmd() {
9289
9351
  if (opts.approve && !state.approved) throw new Error("approval is not present in finalized registry state");
9290
9352
  if (metadataOnly && !state.weightsRef) {
9291
9353
  process.stderr.write(
9292
- `next: approval needs the DA backup of ${identity.modelHash} (packed image + file table, ${measured.imageSize} bytes in ${Math.max(1, Math.ceil(measured.imageSize / sdk.DA_CHUNK_SIZE))} chunk(s)); upload it with --download from a box with the disk
9354
+ `next: approval needs the DA backup of ${identity.modelHash} (packed image + file table, ${measured.imageSize} bytes in ${Math.max(1, Math.ceil(measured.imageSize / sdk.DA_CHUNK_SIZE))} chunk(s)). On a box with room for the checkpoint, run
9355
+ flop model backup ${identity.modelHash} --model-dir <path>${preflight ? ` --revision ${preflight.revision}` : ""}
9356
+ then bind the DataRef it prints. Rerunning this command with --download packs the image from disk instead, which needs about twice the checkpoint's size.
9293
9357
  `
9294
9358
  );
9295
9359
  }
@@ -9489,10 +9553,167 @@ function modelPreflightCmd() {
9489
9553
  });
9490
9554
  }
9491
9555
 
9556
+ // src/commands/model-backup.ts
9557
+ import { Command as Command33, Option as Option3 } from "commander";
9558
+ import { mkdir as mkdir12, rm as rm4, writeFile as writeFile10 } from "node:fs/promises";
9559
+ import { join as join8, resolve as resolve9 } from "node:path";
9560
+ var PRECISIONS3 = ["FP32", "FP16", "BF16", "INT8", "INT4", "Mixed"];
9561
+ async function finalizedLeasedRef2(api, commitment, size) {
9562
+ const hash2 = await api.rpc.chain.getFinalizedHead();
9563
+ const [at, header] = await Promise.all([api.at(hash2), api.rpc.chain.getHeader(hash2)]);
9564
+ return existingLeasedRef({ query: at.query, rpc: { chain: { getHeader: async () => header } } }, commitment, size);
9565
+ }
9566
+ var REGISTRATION_WAIT_POLLS = 30;
9567
+ var REGISTRATION_POLL_MS = 2e3;
9568
+ async function awaitFinalizedLeasedRef(api, commitment, size) {
9569
+ for (let poll = 0; ; poll++) {
9570
+ const ref = await finalizedLeasedRef2(api, commitment, size);
9571
+ if (ref || poll >= REGISTRATION_WAIT_POLLS) return ref;
9572
+ await new Promise((wake) => setTimeout(wake, REGISTRATION_POLL_MS));
9573
+ }
9574
+ }
9575
+ function registeredHubRepo(mirrors) {
9576
+ const repos = [...new Set(mirrors.filter((m) => m.kind === "huggingface").map((m) => m.repo))];
9577
+ if (repos.length > 1) {
9578
+ throw new Error(
9579
+ `model advertises ${repos.length} Hugging Face mirrors (${repos.join(", ")}); name the one to back up with --source`
9580
+ );
9581
+ }
9582
+ return repos[0];
9583
+ }
9584
+ function modelBackupCmd() {
9585
+ return new Command33("backup").description(
9586
+ "Stream a registered content-v1 checkpoint from Hugging Face into validator DA under this box's DA key, without ever writing the packed image out, and print the DataRefs its publisher then binds"
9587
+ ).argument("<model_hash>", "Registered model hash (0x-hex)").requiredOption("--model-dir <path>", "Directory the checkpoint streams into (needs room for it)").option("--source <hf-id>", "Hugging Face id (owner/model or hf:owner/model); the model's registered mirror when omitted").option("--revision <sha>", "Immutable 40-hex commit; main is resolved to one when omitted").addOption(new Option3("--precision <precision>", "Measured-root precision").choices(PRECISIONS3).default("FP16")).option("--da-validator <path>", "da-validator executable", "da-validator").option("--token <token>", "Hugging Face bearer token (prefer HF_TOKEN)").option("--state-dir <path>", "Download resume receipts (outside the checkpoint tree)").option("--dry-run", "Print the plan and the DA deposit it would lock, and stop before any transfer").option("--json", "Output as JSON").action(async (modelHash, opts, command) => {
9588
+ let client;
9589
+ let workDir;
9590
+ try {
9591
+ if (opts.revision && !/^[0-9a-f]{40}$/.test(opts.revision)) throw new Error("--revision must be an immutable 40-hex commit");
9592
+ const sdk = await import("@flop-labs/sdk");
9593
+ const rpc = getRpc(command);
9594
+ const precision = opts.precision;
9595
+ client = await createClient(rpc, getSigner(command));
9596
+ const api = client.api;
9597
+ const registered = await new sdk.OnChainModelRegistry(api).getModel(modelHash);
9598
+ if (!registered) throw new Error(`model not found: ${modelHash}`);
9599
+ const expected = await sdk.readMeasuredRoot(api, modelHash, precision);
9600
+ if (!expected) throw new Error(`no measured root for ${modelHash} at ${opts.precision}`);
9601
+ const hub = { revision: opts.revision, token: opts.token ?? process.env.HF_TOKEN };
9602
+ const repo = opts.source ? opts.source.startsWith("hf:") ? opts.source.slice(3) : opts.source : registeredHubRepo(await sdk.readModelMirrors(api, modelHash));
9603
+ if (!repo) {
9604
+ throw new Error(
9605
+ `model ${modelHash} advertises no Hugging Face mirror; pass --source <owner/model>, or back it up from disk with \`flop model upload --model-hash <hash> --path <dir>\``
9606
+ );
9607
+ }
9608
+ const preflight = await sdk.preflightHuggingFace(repo, hub);
9609
+ if (sdk.normalizeRoot(preflight.contentRoot) !== sdk.normalizeRoot(expected)) {
9610
+ throw new Error(
9611
+ `${repo} at ${preflight.revision} has content_root 0x${preflight.contentRoot}, not the registered root ${expected}. A verity-v1 model's root cannot be checked from hub metadata, and a repo that moved needs the registered --revision; back such a model up from disk with \`flop model upload --model-hash ${modelHash} --path <dir>\``
9612
+ );
9613
+ }
9614
+ const imageSize = sdk.packedImageSize(preflight.files);
9615
+ if (imageSize !== preflight.imageSize) throw new Error("packed image size disagrees with the preflight table");
9616
+ const tableBytes = sdk.encodeModelFileTable("0".repeat(64), preflight.files).length;
9617
+ const deposit = await sdk.da.leaseDeposit(api, imageSize + tableBytes);
9618
+ const plan = {
9619
+ model_hash: modelHash,
9620
+ precision: opts.precision,
9621
+ repo,
9622
+ revision: preflight.revision,
9623
+ content_root: `0x${preflight.contentRoot}`,
9624
+ scheme: "content-v1",
9625
+ files: preflight.files.length,
9626
+ image_size: imageSize,
9627
+ da_chunks: preflight.daChunks,
9628
+ da_deposit_plancks: deposit.toString(),
9629
+ publisher: client.signer.address
9630
+ };
9631
+ if (opts.dryRun) {
9632
+ print({ ...plan, dry_run: true, next: "rerun without --dry-run to transfer and register" }, getJson(command));
9633
+ return;
9634
+ }
9635
+ const publisherKey = await sr25519KeyHex(getSigner(command));
9636
+ const modelDir = resolve9(opts.modelDir);
9637
+ workDir = join8(resolve9(modelDir, ".."), `.flop-backup-${modelHash.replace(/^0x/, "").slice(0, 16)}`);
9638
+ await mkdir12(workDir, { recursive: true, mode: 448 });
9639
+ await mkdir12(modelDir, { recursive: true });
9640
+ const resumeState = opts.stateDir ? join8(resolve9(opts.stateDir), "download.json") : void 0;
9641
+ if (resumeState) await mkdir12(resolve9(opts.stateDir), { recursive: true, mode: 448 });
9642
+ process.stderr.write(
9643
+ `backing up ${repo}@${preflight.revision} (${preflight.files.length} files, image ${imageSize} bytes, ${preflight.daChunks} DA chunk(s)) as ${modelHash}
9644
+ `
9645
+ );
9646
+ await sdk.fetchHuggingFaceToDir(repo, modelDir, {
9647
+ ...hub,
9648
+ revision: preflight.revision,
9649
+ files: preflight.files,
9650
+ resumeState
9651
+ });
9652
+ const manifestFile = join8(workDir, "layout.bin");
9653
+ const image = {
9654
+ sizeBytes: imageSize,
9655
+ manifestFile,
9656
+ open: () => sdk.packedImageStream(modelDir, preflight.files)
9657
+ };
9658
+ const imageLayout = await daCommitStream(opts.daValidator, image);
9659
+ if (imageLayout.sizeBytes !== imageSize) throw new Error("streamed DA commitment covers the wrong image size");
9660
+ const tablePath = join8(workDir, "files.json");
9661
+ await writeFile10(tablePath, sdk.encodeModelFileTable(imageLayout.commitment, preflight.files), { mode: 384 });
9662
+ const tableLayout = await daCommit(opts.daValidator, tablePath);
9663
+ const refs = [];
9664
+ for (const blob of [
9665
+ { commitment: imageLayout.commitment, sizeBytes: imageLayout.sizeBytes, stream: image },
9666
+ { commitment: tableLayout.commitment, sizeBytes: tableLayout.sizeBytes, stream: void 0 }
9667
+ ]) {
9668
+ let ref = await finalizedLeasedRef2(api, blob.commitment, blob.sizeBytes);
9669
+ let registerTx;
9670
+ if (!ref) {
9671
+ const reg = await sdk.da.registerBlob(api, client.signer, {
9672
+ commitment: blob.commitment,
9673
+ sizeBytes: blob.sizeBytes
9674
+ });
9675
+ registerTx = reg.txHash;
9676
+ ref = await awaitFinalizedLeasedRef(api, blob.commitment, blob.sizeBytes);
9677
+ }
9678
+ if (!ref) {
9679
+ throw new Error(
9680
+ `DA registration for ${blob.commitment} did not finalize within ${REGISTRATION_WAIT_POLLS * REGISTRATION_POLL_MS / 1e3}s; rerun the same command once it has`
9681
+ );
9682
+ }
9683
+ const mine = sameAccount(ref.publisher, client.signer.address);
9684
+ const custody = blob.stream ? await submitDaBlobStream(opts.daValidator, rpc, blob.stream, mine ? publisherKey : void 0) : await submitDaBlob(opts.daValidator, rpc, tablePath, mine ? publisherKey : void 0);
9685
+ process.stderr.write(`${custodySummary(custody)}
9686
+ `);
9687
+ refs.push({ ref, custody, registerTx });
9688
+ }
9689
+ const [weights, table2] = refs;
9690
+ print({
9691
+ ...plan,
9692
+ weights_ref: weights.ref.commitment,
9693
+ weights_register_tx: weights.registerTx,
9694
+ weights_custody_shards: weights.custody.confirmedShards,
9695
+ table_ref: table2.ref.commitment,
9696
+ table_register_tx: table2.registerTx,
9697
+ // The binding is the model publisher's own transaction: this box holds no
9698
+ // model-registry authority, and `set_model_weights_ref` takes any Leased ref from it.
9699
+ next: `the model publisher binds the backup with \`flop model upload --model-hash ${modelHash} --commitment ${weights.ref.commitment} --size ${imageSize}\`, then advertises the file table as a da:${table2.ref.commitment} mirror with \`modelRegistry.setModelMirrors\``,
9700
+ recovery: `flop da withdraw --commitment ${weights.ref.commitment} (and ${table2.ref.commitment}) refunds both deposits while nothing pins them`
9701
+ }, getJson(command));
9702
+ } catch (error) {
9703
+ commandError(error, {
9704
+ next: "rerun the same command to resume the download and the DA push; to abandon a registered blob, run the printed `flop da withdraw` recovery"
9705
+ });
9706
+ } finally {
9707
+ if (workDir) await rm4(workDir, { recursive: true, force: true });
9708
+ await client?.disconnect();
9709
+ }
9710
+ });
9711
+ }
9712
+
9492
9713
  // src/commands/model-list.ts
9493
- import { Command as Command33 } from "commander";
9714
+ import { Command as Command34 } from "commander";
9494
9715
  function modelListCmd() {
9495
- return new Command33("list").description("List models registered on the selected FLOP chain").option("--name <text>", "Filter model names").option("--limit <n>", "Maximum rows returned", "100").option("--json", "Output as JSON").action(async (opts, command) => {
9716
+ return new Command34("list").description("List models registered on the selected FLOP chain").option("--name <text>", "Filter model names").option("--limit <n>", "Maximum rows returned", "100").option("--json", "Output as JSON").action(async (opts, command) => {
9496
9717
  let client;
9497
9718
  try {
9498
9719
  const sdk = await import("@flop-labs/sdk");
@@ -9522,9 +9743,9 @@ function modelListCmd() {
9522
9743
  }
9523
9744
 
9524
9745
  // src/commands/model-get.ts
9525
- import { Command as Command34 } from "commander";
9746
+ import { Command as Command35 } from "commander";
9526
9747
  function modelGetCmd() {
9527
- return new Command34("get").description("Get a registered model by hash or exact name").argument("<identifier>", "Model hash or name").option("--precision <precision>", "Measured-root precision", "FP16").option("--json", "Output as JSON").action(async (identifier, opts, command) => {
9748
+ return new Command35("get").description("Get a registered model by hash or exact name").argument("<identifier>", "Model hash or name").option("--precision <precision>", "Measured-root precision", "FP16").option("--json", "Output as JSON").action(async (identifier, opts, command) => {
9528
9749
  let client;
9529
9750
  try {
9530
9751
  const sdk = await import("@flop-labs/sdk");
@@ -9555,10 +9776,10 @@ function modelGetCmd() {
9555
9776
  }
9556
9777
 
9557
9778
  // src/commands/model-fetch.ts
9558
- import { Command as Command35, Option as Option3 } from "commander";
9559
- var PRECISIONS3 = ["FP32", "FP16", "BF16", "INT8", "INT4", "Mixed"];
9779
+ import { Command as Command36, Option as Option4 } from "commander";
9780
+ var PRECISIONS4 = ["FP32", "FP16", "BF16", "INT8", "INT4", "Mixed"];
9560
9781
  function modelFetchCmd() {
9561
- return new Command35("fetch").description("Fetch model weights and accept only a registered dm-verity root").argument("<identifier>", "Registered model hash or exact name").addOption(new Option3("--precision <precision>", "Weights precision").choices(PRECISIONS3).default("FP16")).option("--source <source...>", "Override mirrors: hf:<repo>, modelscope:<repo>, https://..., da:<commitment>").option("--da-endpoint <url>", "DA blob gateway exposing GET /blob/<commitment>").option("--output-dir <path>", "Destination directory", "./flop-model").option("--token <token>", "HF/ModelScope bearer token (prefer HF_TOKEN)").option("--json", "Output as JSON").action(async (identifier, opts, command) => {
9782
+ return new Command36("fetch").description("Fetch model weights and accept only a registered dm-verity root").argument("<identifier>", "Registered model hash or exact name").addOption(new Option4("--precision <precision>", "Weights precision").choices(PRECISIONS4).default("FP16")).option("--source <source...>", "Override mirrors: hf:<repo>, modelscope:<repo>, https://..., da:<commitment>").option("--da-endpoint <url>", "DA blob gateway exposing GET /blob/<commitment>").option("--output-dir <path>", "Destination directory", "./flop-model").option("--token <token>", "HF/ModelScope bearer token (prefer HF_TOKEN)").option("--json", "Output as JSON").action(async (identifier, opts, command) => {
9562
9783
  let client;
9563
9784
  try {
9564
9785
  const sdk = await import("@flop-labs/sdk");
@@ -9621,7 +9842,7 @@ function modelFetchCmd() {
9621
9842
 
9622
9843
  // src/commands/agent.ts
9623
9844
  import path2 from "node:path";
9624
- import { Command as Command36, Option as Option4 } from "commander";
9845
+ import { Command as Command37, Option as Option5 } from "commander";
9625
9846
  import { getFlopNetworkProfile as getFlopNetworkProfile3 } from "@flop-labs/sdk";
9626
9847
  var DEFAULT_MCP_URL = "https://mcp.flop.network/mcp";
9627
9848
  var DEVNET_MCP_URL = "https://mcp.flop.network/mcp";
@@ -9709,14 +9930,14 @@ function buildAgentInitPlan(client, mode, mcpUrl2 = DEFAULT_MCP_URL) {
9709
9930
  };
9710
9931
  }
9711
9932
  function agentInitCmd() {
9712
- return new Command36("init").description("Print a safe agent onboarding plan and MCP configuration").addOption(new Option4("--client <client>", "Agent client").choices([
9933
+ return new Command37("init").description("Print a safe agent onboarding plan and MCP configuration").addOption(new Option5("--client <client>", "Agent client").choices([
9713
9934
  "claude-code",
9714
9935
  "codex",
9715
9936
  "cursor",
9716
9937
  "windsurf",
9717
9938
  "zed",
9718
9939
  "generic"
9719
- ]).default("generic")).addOption(new Option4("--mode <mode>", "MCP mode").choices(["remote", "local"]).default("remote")).option("--mcp-url <url>", "Hosted MCP URL").option("--json", "Output as JSON").action((opts, command) => {
9940
+ ]).default("generic")).addOption(new Option5("--mode <mode>", "MCP mode").choices(["remote", "local"]).default("remote")).option("--mcp-url <url>", "Hosted MCP URL").option("--json", "Output as JSON").action((opts, command) => {
9720
9941
  const plan = buildAgentInitPlan(opts.client, opts.mode, mcpUrl(command, opts.mcpUrl));
9721
9942
  const output = getOutputMode(command);
9722
9943
  printRolePlan(plan, output);
@@ -9727,7 +9948,7 @@ function agentInitCmd() {
9727
9948
  });
9728
9949
  }
9729
9950
  function agentDoctorCmd() {
9730
- return new Command36("doctor").description("Check agent MCP, gateway, and optional chain connectivity").addOption(new Option4("--mode <mode>", "MCP mode").choices(["remote", "local"]).default("remote")).option("--mcp-url <url>", "MCP URL").option("--gateway <url>", "Optional OpenAI-compatible gateway URL").option("--repo <path>", "flop-core checkout for local mode", process.cwd()).option("--check-rpc", "Also require the configured chain RPC").option("--json", "Output as JSON").action(async (opts, command) => {
9951
+ return new Command37("doctor").description("Check agent MCP, gateway, and optional chain connectivity").addOption(new Option5("--mode <mode>", "MCP mode").choices(["remote", "local"]).default("remote")).option("--mcp-url <url>", "MCP URL").option("--gateway <url>", "Optional OpenAI-compatible gateway URL").option("--repo <path>", "flop-core checkout for local mode", process.cwd()).option("--check-rpc", "Also require the configured chain RPC").option("--json", "Output as JSON").action(async (opts, command) => {
9731
9952
  try {
9732
9953
  const checks = [executableCheck("node")];
9733
9954
  if (opts.mode === "remote") {
@@ -9754,7 +9975,7 @@ function agentDoctorCmd() {
9754
9975
  });
9755
9976
  }
9756
9977
  function agentCredentialCmd() {
9757
- return new Command36("credential").description("Provision or reuse a devnet gateway key for agent clients (wallet-signed by default)").option("--print-key", "Print only the bearer key for a local client auth helper").option("--tailnet", "Enroll through the private tailnet lane (approved Tailscale users only)").option("--json", "Output non-secret credential metadata as JSON").action(async (opts, command) => {
9978
+ return new Command37("credential").description("Provision or reuse a devnet gateway key for agent clients (wallet-signed by default)").option("--print-key", "Print only the bearer key for a local client auth helper").option("--tailnet", "Enroll through the private tailnet lane (approved Tailscale users only)").option("--json", "Output non-secret credential metadata as JSON").action(async (opts, command) => {
9758
9979
  try {
9759
9980
  if (getNetwork(command) !== "devnet") {
9760
9981
  throw new Error("agent credential requires --network devnet");
@@ -9787,20 +10008,20 @@ function agentCredentialCmd() {
9787
10008
  });
9788
10009
  }
9789
10010
  function agentCmd() {
9790
- const command = new Command36("agent").description("Onboard and operate FLOP-backed agents");
10011
+ const command = new Command37("agent").description("Onboard and operate FLOP-backed agents");
9791
10012
  command.addCommand(agentInitCmd());
9792
10013
  command.addCommand(agentDoctorCmd());
9793
10014
  command.addCommand(agentCredentialCmd());
9794
10015
  command.addCommand(launchCmd());
9795
10016
  command.addCommand(proxyCmd());
9796
10017
  command.addCommand(hermesCmd());
9797
- const keys = new Command36("keys").description("Manage agent keys");
10018
+ const keys = new Command37("keys").description("Manage agent keys");
9798
10019
  keys.addCommand(keysGenerateCmd());
9799
10020
  command.addCommand(keys);
9800
- const wallet = new Command36("wallet").description("Manage agent wallets");
10021
+ const wallet = new Command37("wallet").description("Manage agent wallets");
9801
10022
  wallet.addCommand(walletAgentCreateCmd());
9802
10023
  command.addCommand(wallet);
9803
- const session = new Command36("session").description("Manage agent session keys");
10024
+ const session = new Command37("session").description("Manage agent session keys");
9804
10025
  session.addCommand(sessionCreateCmd());
9805
10026
  session.addCommand(sessionInfoCmd());
9806
10027
  session.addCommand(sessionRevokeCmd());
@@ -9812,7 +10033,7 @@ function agentCmd() {
9812
10033
  // src/commands/miner.ts
9813
10034
  import { readFileSync } from "node:fs";
9814
10035
  import path3 from "node:path";
9815
- import { Command as Command37, Option as Option5 } from "commander";
10036
+ import { Command as Command38, Option as Option6 } from "commander";
9816
10037
  import { formatFlop as formatFlop2 } from "@flop-labs/sdk";
9817
10038
 
9818
10039
  // src/gpu-inventory-remark.ts
@@ -10030,7 +10251,7 @@ function buildMinerInitPlan(options) {
10030
10251
  };
10031
10252
  }
10032
10253
  function minerInitCmd() {
10033
- return new Command37("init").description("Print a phased miner onboarding plan").addOption(new Option5("--mode <mode>", "Miner mode").choices(["sim", "tdx", "cloud"]).default("sim")).option("--instance <number>", "Local guest instance number", "0").option("--provider <name>", "Cloud provider", "runpod").option(
10254
+ return new Command38("init").description("Print a phased miner onboarding plan").addOption(new Option6("--mode <mode>", "Miner mode").choices(["sim", "tdx", "cloud"]).default("sim")).option("--instance <number>", "Local guest instance number", "0").option("--provider <name>", "Cloud provider", "runpod").option(
10034
10255
  "--gpu <SKU[:COUNT]>",
10035
10256
  "Expected GPU inventory; repeat for mixed-SKU hosts",
10036
10257
  collectGpu,
@@ -10073,7 +10294,7 @@ async function gpuCheck(mode) {
10073
10294
  }
10074
10295
  }
10075
10296
  function minerDoctorCmd() {
10076
- return new Command37("doctor").description("Check miner runtime, hardware, chain, and serving prerequisites").addOption(new Option5("--mode <mode>", "Miner mode").choices(["sim", "tdx", "cloud"]).default("sim")).option("--repo <path>", "flop-core checkout", process.cwd()).option("--session-url <url>", "Optional miner session info URL").option("--offline", "Skip chain RPC check").option("--json", "Output as JSON").action(async (opts, command) => {
10297
+ return new Command38("doctor").description("Check miner runtime, hardware, chain, and serving prerequisites").addOption(new Option6("--mode <mode>", "Miner mode").choices(["sim", "tdx", "cloud"]).default("sim")).option("--repo <path>", "flop-core checkout", process.cwd()).option("--session-url <url>", "Optional miner session info URL").option("--offline", "Skip chain RPC check").option("--json", "Output as JSON").action(async (opts, command) => {
10077
10298
  try {
10078
10299
  const checks = [];
10079
10300
  if (opts.mode === "cloud") {
@@ -10110,7 +10331,7 @@ function minerDoctorCmd() {
10110
10331
  });
10111
10332
  }
10112
10333
  function minerStatusCmd() {
10113
- return new Command37("status").description("Show miner registration, calibration, and optional session status").argument("[address]", "Miner SS58 address; defaults to the configured signer").option("--session-url <url>", "Optional miner session info URL").option(
10334
+ return new Command38("status").description("Show miner registration, calibration, and optional session status").argument("[address]", "Miner SS58 address; defaults to the configured signer").option("--session-url <url>", "Optional miner session info URL").option(
10114
10335
  "--expect-gpu <SKU[:COUNT]>",
10115
10336
  "Expected inventory; repeat to check attested state",
10116
10337
  collectGpu,
@@ -10160,7 +10381,7 @@ function minerStatusCmd() {
10160
10381
  });
10161
10382
  }
10162
10383
  function minerSkusCmd() {
10163
- return new Command37("skus").description("List GPU SKUs with active governance calibration ceilings").option("--json", "Output as JSON").action(async (_opts, command) => {
10384
+ return new Command38("skus").description("List GPU SKUs with active governance calibration ceilings").option("--json", "Output as JSON").action(async (_opts, command) => {
10164
10385
  let client;
10165
10386
  try {
10166
10387
  const rpc = getRpc(command);
@@ -10184,7 +10405,7 @@ function minerSkusCmd() {
10184
10405
  });
10185
10406
  }
10186
10407
  function minerRegisterCmd() {
10187
- return new Command37("register").description("Register the signer as a miner and lock the runtime-defined self-stake").option("--commission <percent>", "Commission percent (0-20)", "10").option("--json", "Output as JSON").action(async (opts, command) => {
10408
+ return new Command38("register").description("Register the signer as a miner and lock the runtime-defined self-stake").option("--commission <percent>", "Commission percent (0-20)", "10").option("--json", "Output as JSON").action(async (opts, command) => {
10188
10409
  let client;
10189
10410
  try {
10190
10411
  const commission = Number(opts.commission);
@@ -10211,7 +10432,7 @@ function minerRegisterCmd() {
10211
10432
  });
10212
10433
  }
10213
10434
  function minerDeclareGpuCmd() {
10214
- return new Command37("declare-gpu").description("Publish the signer's self-reported GPU inventory as a flop.gpu-inventory.v1 remark (display-only)").option("--nvidia-smi <path>", "Output of `nvidia-smi --query-gpu=name,memory.total --format=csv,noheader,nounits` (- for stdin)").option("--detect", "Run nvidia-smi here (on the GPU host)").option("--retract", "Publish an empty inventory, withdrawing an earlier declaration").option("--dry-run", "Print the remark without signing").option("--json", "Output as JSON").action(async (opts, command) => {
10435
+ return new Command38("declare-gpu").description("Publish the signer's self-reported GPU inventory as a flop.gpu-inventory.v1 remark (display-only)").option("--nvidia-smi <path>", "Output of `nvidia-smi --query-gpu=name,memory.total --format=csv,noheader,nounits` (- for stdin)").option("--detect", "Run nvidia-smi here (on the GPU host)").option("--retract", "Publish an empty inventory, withdrawing an earlier declaration").option("--dry-run", "Print the remark without signing").option("--json", "Output as JSON").action(async (opts, command) => {
10215
10436
  let client;
10216
10437
  try {
10217
10438
  if ([opts.nvidiaSmi, opts.detect, opts.retract].filter(Boolean).length !== 1) {
@@ -10247,7 +10468,7 @@ function minerDeclareGpuCmd() {
10247
10468
  });
10248
10469
  }
10249
10470
  function minerCmd() {
10250
- const command = new Command37("miner").description("Onboard and operate inference miners");
10471
+ const command = new Command38("miner").description("Onboard and operate inference miners");
10251
10472
  command.addCommand(minerInitCmd());
10252
10473
  command.addCommand(minerDoctorCmd());
10253
10474
  command.addCommand(minerStatusCmd());
@@ -10260,7 +10481,7 @@ function minerCmd() {
10260
10481
 
10261
10482
  // src/commands/validator.ts
10262
10483
  import path4 from "node:path";
10263
- import { Command as Command38, Option as Option6 } from "commander";
10484
+ import { Command as Command39, Option as Option7 } from "commander";
10264
10485
  import { formatFlop as formatFlop3 } from "@flop-labs/sdk";
10265
10486
  function buildValidatorInitPlan(mode, minimumStake) {
10266
10487
  const bringUp = mode === "local" ? "just run-node" : "infra/vultr/create-vm.sh <args>; infra/vultr/deploy-core.sh --vm-ip <TAILSCALE-IP>";
@@ -10310,7 +10531,7 @@ function buildValidatorInitPlan(mode, minimumStake) {
10310
10531
  };
10311
10532
  }
10312
10533
  function validatorInitCmd() {
10313
- return new Command38("init").description("Print a validator onboarding plan with the live minimum stake").addOption(new Option6("--mode <mode>", "Validator deployment mode").choices(["local", "vultr"]).default("local")).option("--offline", "Do not query the configured chain").option("--json", "Output as JSON").action(async (opts, command) => {
10534
+ return new Command39("init").description("Print a validator onboarding plan with the live minimum stake").addOption(new Option7("--mode <mode>", "Validator deployment mode").choices(["local", "vultr"]).default("local")).option("--offline", "Do not query the configured chain").option("--json", "Output as JSON").action(async (opts, command) => {
10314
10535
  let client;
10315
10536
  try {
10316
10537
  let minimumStake;
@@ -10329,7 +10550,7 @@ function validatorInitCmd() {
10329
10550
  });
10330
10551
  }
10331
10552
  function validatorDoctorCmd() {
10332
- return new Command38("doctor").description("Check validator node and on-chain eligibility prerequisites").addOption(new Option6("--mode <mode>", "Validator deployment mode").choices(["local", "vultr"]).default("local")).option("--repo <path>", "flop-core checkout", process.cwd()).option("--offline", "Skip chain and account checks").option("--json", "Output as JSON").action(async (opts, command) => {
10553
+ return new Command39("doctor").description("Check validator node and on-chain eligibility prerequisites").addOption(new Option7("--mode <mode>", "Validator deployment mode").choices(["local", "vultr"]).default("local")).option("--repo <path>", "flop-core checkout", process.cwd()).option("--offline", "Skip chain and account checks").option("--json", "Output as JSON").action(async (opts, command) => {
10333
10554
  let client;
10334
10555
  try {
10335
10556
  const checks = [
@@ -10400,7 +10621,7 @@ function validatorDoctorCmd() {
10400
10621
  });
10401
10622
  }
10402
10623
  function validatorStatusCmd() {
10403
- return new Command38("status").description("Show validator stake, jail, performance, and verified-work state").argument("[address]", "Validator SS58 address; defaults to the configured signer").option("--json", "Output as JSON").action(async (address, opts, command) => {
10624
+ return new Command39("status").description("Show validator stake, jail, performance, and verified-work state").argument("[address]", "Validator SS58 address; defaults to the configured signer").option("--json", "Output as JSON").action(async (address, opts, command) => {
10404
10625
  let client;
10405
10626
  try {
10406
10627
  const rpc = getRpc(command);
@@ -10445,7 +10666,7 @@ function validatorStatusCmd() {
10445
10666
  });
10446
10667
  }
10447
10668
  function validatorRegisterCmd() {
10448
- return new Command38("register").description("Register the signer as a validator, bonding the effective minimum").option("--json", "Output as JSON").action(async (opts, command) => {
10669
+ return new Command39("register").description("Register the signer as a validator, bonding the effective minimum").option("--json", "Output as JSON").action(async (opts, command) => {
10449
10670
  let client;
10450
10671
  try {
10451
10672
  const rpc = getRpc(command);
@@ -10475,7 +10696,7 @@ function validatorRegisterCmd() {
10475
10696
  });
10476
10697
  }
10477
10698
  function validatorCommissionCmd() {
10478
- return new Command38("commission").description("Set validator commission").argument("<percent>", "Commission percent (0-100)").option("--json", "Output as JSON").action(async (percent, opts, command) => {
10699
+ return new Command39("commission").description("Set validator commission").argument("<percent>", "Commission percent (0-100)").option("--json", "Output as JSON").action(async (percent, opts, command) => {
10479
10700
  let client;
10480
10701
  try {
10481
10702
  const value = Number(percent);
@@ -10495,7 +10716,7 @@ function validatorCommissionCmd() {
10495
10716
  });
10496
10717
  }
10497
10718
  function validatorLifecycleCmd(name, description, sdkMethod) {
10498
- return new Command38(name).description(description).option("--json", "Output as JSON").action(async (opts, command) => {
10719
+ return new Command39(name).description(description).option("--json", "Output as JSON").action(async (opts, command) => {
10499
10720
  let client;
10500
10721
  try {
10501
10722
  const rpc = getRpc(command);
@@ -10511,7 +10732,7 @@ function validatorLifecycleCmd(name, description, sdkMethod) {
10511
10732
  });
10512
10733
  }
10513
10734
  function validatorCmd() {
10514
- const command = new Command38("validator").description("Onboard and operate validator nodes");
10735
+ const command = new Command39("validator").description("Onboard and operate validator nodes");
10515
10736
  command.addCommand(validatorInitCmd());
10516
10737
  command.addCommand(validatorDoctorCmd());
10517
10738
  command.addCommand(validatorStatusCmd());
@@ -10524,7 +10745,7 @@ function validatorCmd() {
10524
10745
  }
10525
10746
 
10526
10747
  // src/commands/network-status.ts
10527
- import { Command as Command39 } from "commander";
10748
+ import { Command as Command40 } from "commander";
10528
10749
 
10529
10750
  // src/network.ts
10530
10751
  async function probeRpcChain(network, rpc) {
@@ -10558,7 +10779,7 @@ function networkStatusCmd(probeRpc2 = probeRpcChain, probeFaucet = async (endpoi
10558
10779
  return { ...identity, ready: null, readinessError: error instanceof Error ? error.message : String(error) };
10559
10780
  }
10560
10781
  }) {
10561
- return new Command39("status").description("Show resolved network endpoints and live chain identity").option("--offline", "Resolve configured endpoints without connecting").option("--json", "Output as JSON").action(async (opts, command) => {
10782
+ return new Command40("status").description("Show resolved network endpoints and live chain identity").option("--offline", "Resolve configured endpoints without connecting").option("--json", "Output as JSON").action(async (opts, command) => {
10562
10783
  const resolved = resolveNetwork(command);
10563
10784
  let rpcInfo = null;
10564
10785
  let rpcError;
@@ -10612,9 +10833,9 @@ function networkStatusCmd(probeRpc2 = probeRpcChain, probeFaucet = async (endpoi
10612
10833
  }
10613
10834
 
10614
10835
  // src/commands/faucet-request.ts
10615
- import { Command as Command40 } from "commander";
10836
+ import { Command as Command41 } from "commander";
10616
10837
  function faucetRequestCmd(probeRpc2 = probeRpcChain) {
10617
- return new Command40("request").description("Request dev/test FLOP after verifying faucet and RPC chain identity").argument("<address>", "SS58 address to fund").option("--amount <flop>", "Requested FLOP amount; server policy may cap it").option("--endpoint <url>", "Explicit faucet endpoint override").option("--json", "Output as JSON").action(async (address, opts, command) => {
10838
+ return new Command41("request").description("Request dev/test FLOP after verifying faucet and RPC chain identity").argument("<address>", "SS58 address to fund").option("--amount <flop>", "Requested FLOP amount; server policy may cap it").option("--endpoint <url>", "Explicit faucet endpoint override").option("--json", "Output as JSON").action(async (address, opts, command) => {
10618
10839
  try {
10619
10840
  const resolved = resolveNetwork(command);
10620
10841
  const endpoint = opts.endpoint ?? resolved.profile.faucet;
@@ -10667,15 +10888,15 @@ function faucetRequestCmd(probeRpc2 = probeRpcChain) {
10667
10888
  }
10668
10889
 
10669
10890
  // src/commands/chat.ts
10670
- import { Command as Command41 } from "commander";
10891
+ import { Command as Command42 } from "commander";
10671
10892
  import { randomUUID as randomUUID4 } from "node:crypto";
10672
10893
  import { homedir as homedir4 } from "node:os";
10673
- import { join as join8 } from "node:path";
10894
+ import { join as join9 } from "node:path";
10674
10895
  import { createInterface } from "node:readline/promises";
10675
10896
  import { stdin, stdout } from "node:process";
10676
10897
 
10677
10898
  // src/conversations.ts
10678
- import { mkdir as mkdir12, readFile as readFile8, rename as rename8, writeFile as writeFile10 } from "node:fs/promises";
10899
+ import { mkdir as mkdir13, readFile as readFile8, rename as rename8, writeFile as writeFile11 } from "node:fs/promises";
10679
10900
  import { dirname as dirname6 } from "node:path";
10680
10901
  var MAX_CONVERSATIONS = 100;
10681
10902
  var MAX_CONVERSATION_MESSAGES = 100;
@@ -10695,9 +10916,9 @@ var ConversationStore = class {
10695
10916
  }
10696
10917
  }
10697
10918
  async write(data) {
10698
- await mkdir12(dirname6(this.path), { recursive: true, mode: 448 });
10919
+ await mkdir13(dirname6(this.path), { recursive: true, mode: 448 });
10699
10920
  const temp = `${this.path}.${process.pid}.tmp`;
10700
- await writeFile10(temp, `${JSON.stringify(data, null, 2)}
10921
+ await writeFile11(temp, `${JSON.stringify(data, null, 2)}
10701
10922
  `, { mode: 384 });
10702
10923
  await rename8(temp, this.path);
10703
10924
  }
@@ -10734,7 +10955,7 @@ function conversationPrompt(messages) {
10734
10955
 
10735
10956
  // src/commands/chat.ts
10736
10957
  function chatCmd() {
10737
- return new Command41("chat").description("Start or resume a persistent FLOP chat, optionally through a bounded session").argument("[prompt]", "One-shot prompt; omit for interactive chat").option("--conversation <id>", "Resume a conversation ID").option("--model <id>", "32-byte model hash or exact registered name from flop model list").option("--session-owner <address>", "Use --signer as this owner's session delegate").option("--max-flop <n>", "Per-turn FLOP cap", "5").option("--tee-requirements <hex>", "Registered HTLC appraisal commitments (100-byte SCALE hex)", process.env.FLOP_HTLC_TEE_REQUIREMENTS).option("--skip-model-check", "Allow an unregistered model (local development only)").option("--history", "List stored conversations without connecting").option("--delete <id>", "Delete a stored conversation without connecting").option("--store <path>", "Conversation store path", join8(homedir4(), ".flop", "conversations.json")).option("--json", "Output as JSON").action(async (prompt, opts, command) => {
10958
+ return new Command42("chat").description("Start or resume a persistent FLOP chat, optionally through a bounded session").argument("[prompt]", "One-shot prompt; omit for interactive chat").option("--conversation <id>", "Resume a conversation ID").option("--model <id>", "32-byte model hash or exact registered name from flop model list").option("--session-owner <address>", "Use --signer as this owner's session delegate").option("--max-flop <n>", "Per-turn FLOP cap", "5").option("--tee-requirements <hex>", "Registered HTLC appraisal commitments (100-byte SCALE hex)", process.env.FLOP_HTLC_TEE_REQUIREMENTS).option("--skip-model-check", "Allow an unregistered model (local development only)").option("--history", "List stored conversations without connecting").option("--delete <id>", "Delete a stored conversation without connecting").option("--store <path>", "Conversation store path", join9(homedir4(), ".flop", "conversations.json")).option("--json", "Output as JSON").action(async (prompt, opts, command) => {
10738
10959
  const store = new ConversationStore(opts.store);
10739
10960
  let agent;
10740
10961
  try {
@@ -10859,8 +11080,8 @@ function chatCmd() {
10859
11080
  }
10860
11081
 
10861
11082
  // src/commands/tx-build.ts
10862
- import { Command as Command42 } from "commander";
10863
- import { writeFile as writeFile11 } from "node:fs/promises";
11083
+ import { Command as Command43 } from "commander";
11084
+ import { writeFile as writeFile12 } from "node:fs/promises";
10864
11085
  import { execFile as execFile5 } from "node:child_process";
10865
11086
  import { promisify as promisify5 } from "node:util";
10866
11087
 
@@ -10926,7 +11147,7 @@ function assertEnvelopeChain(api, envelope) {
10926
11147
 
10927
11148
  // src/commands/tx-build.ts
10928
11149
  function txBuildCmd() {
10929
- return new Command42("build").description("Build an immortal unsigned transaction envelope for air-gapped signing").requiredOption("--address <address>", "Signing account").requiredOption("--call <hex>", "SCALE call hex").option("--out <path>", "Write envelope JSON").option("--qr", "Render the signing payload using installed qrencode").option("--json", "Output as JSON").action(async (opts, command) => {
11150
+ return new Command43("build").description("Build an immortal unsigned transaction envelope for air-gapped signing").requiredOption("--address <address>", "Signing account").requiredOption("--call <hex>", "SCALE call hex").option("--out <path>", "Write envelope JSON").option("--qr", "Render the signing payload using installed qrencode").option("--json", "Output as JSON").action(async (opts, command) => {
10930
11151
  let client;
10931
11152
  try {
10932
11153
  const resolved = resolveNetwork(command);
@@ -10939,7 +11160,7 @@ function txBuildCmd() {
10939
11160
  opts.call
10940
11161
  );
10941
11162
  if (opts.out) {
10942
- await writeFile11(opts.out, `${JSON.stringify(envelope, null, 2)}
11163
+ await writeFile12(opts.out, `${JSON.stringify(envelope, null, 2)}
10943
11164
  `, { mode: 384 });
10944
11165
  }
10945
11166
  if (opts.qr) {
@@ -10971,10 +11192,10 @@ function txBuildCmd() {
10971
11192
  }
10972
11193
 
10973
11194
  // src/commands/tx-broadcast.ts
10974
- import { Command as Command43 } from "commander";
11195
+ import { Command as Command44 } from "commander";
10975
11196
  import { readFile as readFile9 } from "node:fs/promises";
10976
11197
  function txBroadcastCmd() {
10977
- return new Command43("broadcast").description("Import an offline signature, reject chain drift, and broadcast").argument("<envelope>", "Unsigned envelope JSON").requiredOption("--signature <hex>", "SCALE MultiSignature from the offline signer").option("--allow-nonce-drift", "Broadcast even if account nonce has changed").option("--json", "Output as JSON").action(async (path5, opts, command) => {
11198
+ return new Command44("broadcast").description("Import an offline signature, reject chain drift, and broadcast").argument("<envelope>", "Unsigned envelope JSON").requiredOption("--signature <hex>", "SCALE MultiSignature from the offline signer").option("--allow-nonce-drift", "Broadcast even if account nonce has changed").option("--json", "Output as JSON").action(async (path5, opts, command) => {
10978
11199
  let client;
10979
11200
  try {
10980
11201
  if (!/^0x[0-9a-f]+$/i.test(opts.signature)) throw new Error("--signature must be 0x hex");
@@ -11010,7 +11231,7 @@ function txBroadcastCmd() {
11010
11231
  }
11011
11232
 
11012
11233
  // src/commands/multisig.ts
11013
- import { Command as Command44 } from "commander";
11234
+ import { Command as Command45 } from "commander";
11014
11235
  import { createKeyMulti, encodeAddress, sortAddresses } from "@polkadot/util-crypto";
11015
11236
  import { blake2AsHex } from "@polkadot/util-crypto";
11016
11237
  function otherSignatories(signer, values) {
@@ -11031,8 +11252,8 @@ function timepoint(opts) {
11031
11252
  return { height: parsePositiveInt(opts.height, "--height"), index };
11032
11253
  }
11033
11254
  function multisigCmd() {
11034
- const command = new Command44("multisig").description("Participate in Substrate multisig transactions");
11035
- command.addCommand(new Command44("address").description("Derive the deterministic multisig address").requiredOption("--threshold <n>", "Approval threshold").requiredOption("--signatory <address...>", "All signatory addresses").option("--json", "Output as JSON").action(async (opts, cmd) => {
11255
+ const command = new Command45("multisig").description("Participate in Substrate multisig transactions");
11256
+ command.addCommand(new Command45("address").description("Derive the deterministic multisig address").requiredOption("--threshold <n>", "Approval threshold").requiredOption("--signatory <address...>", "All signatory addresses").option("--json", "Output as JSON").action(async (opts, cmd) => {
11036
11257
  try {
11037
11258
  const threshold = parsePositiveInt(opts.threshold, "--threshold");
11038
11259
  const sorted = sortAddresses(opts.signatory, 42);
@@ -11047,7 +11268,7 @@ function multisigCmd() {
11047
11268
  commandError(error);
11048
11269
  }
11049
11270
  }));
11050
- command.addCommand(new Command44("propose").description("Create or execute a multisig call").requiredOption("--threshold <n>", "Approval threshold").requiredOption("--other <address...>", "Other signatories (exclude configured signer)").requiredOption("--call <hex>", "SCALE call hex").option("--max-ref-time <n>", "Maximum call refTime").option("--max-proof-size <n>", "Maximum call proofSize").option("--json", "Output as JSON").action(async (opts, cmd) => {
11271
+ command.addCommand(new Command45("propose").description("Create or execute a multisig call").requiredOption("--threshold <n>", "Approval threshold").requiredOption("--other <address...>", "Other signatories (exclude configured signer)").requiredOption("--call <hex>", "SCALE call hex").option("--max-ref-time <n>", "Maximum call refTime").option("--max-proof-size <n>", "Maximum call proofSize").option("--json", "Output as JSON").action(async (opts, cmd) => {
11051
11272
  let client;
11052
11273
  try {
11053
11274
  client = await createClient(getRpc(cmd), getSigner(cmd));
@@ -11070,7 +11291,7 @@ function multisigCmd() {
11070
11291
  await client?.disconnect();
11071
11292
  }
11072
11293
  }));
11073
- command.addCommand(new Command44("approve").description("Approve a pending multisig call hash").requiredOption("--threshold <n>", "Approval threshold").requiredOption("--other <address...>", "Other signatories").requiredOption("--call-hash <hex>", "Pending call hash").requiredOption("--height <n>", "Timepoint block height").requiredOption("--index <n>", "Timepoint extrinsic index").option("--max-ref-time <n>", "Maximum call refTime").option("--max-proof-size <n>", "Maximum call proofSize").option("--json", "Output as JSON").action(async (opts, cmd) => {
11294
+ command.addCommand(new Command45("approve").description("Approve a pending multisig call hash").requiredOption("--threshold <n>", "Approval threshold").requiredOption("--other <address...>", "Other signatories").requiredOption("--call-hash <hex>", "Pending call hash").requiredOption("--height <n>", "Timepoint block height").requiredOption("--index <n>", "Timepoint extrinsic index").option("--max-ref-time <n>", "Maximum call refTime").option("--max-proof-size <n>", "Maximum call proofSize").option("--json", "Output as JSON").action(async (opts, cmd) => {
11074
11295
  let client;
11075
11296
  try {
11076
11297
  client = await createClient(getRpc(cmd), getSigner(cmd));
@@ -11089,7 +11310,7 @@ function multisigCmd() {
11089
11310
  await client?.disconnect();
11090
11311
  }
11091
11312
  }));
11092
- command.addCommand(new Command44("cancel").description("Cancel a pending multisig as its depositor").requiredOption("--threshold <n>", "Approval threshold").requiredOption("--other <address...>", "Other signatories").requiredOption("--call-hash <hex>", "Pending call hash").requiredOption("--height <n>", "Timepoint block height").requiredOption("--index <n>", "Timepoint extrinsic index").option("--json", "Output as JSON").action(async (opts, cmd) => {
11313
+ command.addCommand(new Command45("cancel").description("Cancel a pending multisig as its depositor").requiredOption("--threshold <n>", "Approval threshold").requiredOption("--other <address...>", "Other signatories").requiredOption("--call-hash <hex>", "Pending call hash").requiredOption("--height <n>", "Timepoint block height").requiredOption("--index <n>", "Timepoint extrinsic index").option("--json", "Output as JSON").action(async (opts, cmd) => {
11093
11314
  let client;
11094
11315
  try {
11095
11316
  client = await createClient(getRpc(cmd), getSigner(cmd));
@@ -11106,7 +11327,7 @@ function multisigCmd() {
11106
11327
  await client?.disconnect();
11107
11328
  }
11108
11329
  }));
11109
- command.addCommand(new Command44("list").alias("status").description("List pending calls for a multisig account").requiredOption("--account <address>", "Multisig account").option("--json", "Output as JSON").action(async (opts, cmd) => {
11330
+ command.addCommand(new Command45("list").alias("status").description("List pending calls for a multisig account").requiredOption("--account <address>", "Multisig account").option("--json", "Output as JSON").action(async (opts, cmd) => {
11110
11331
  let client;
11111
11332
  try {
11112
11333
  client = await createClient(getRpc(cmd));
@@ -11126,10 +11347,10 @@ function multisigCmd() {
11126
11347
  }
11127
11348
 
11128
11349
  // src/commands/channel.ts
11129
- import { Command as Command45 } from "commander";
11350
+ import { Command as Command46 } from "commander";
11130
11351
  function channelCmd() {
11131
- const command = new Command45("channel").description("Inspect and finalize compute channels");
11132
- command.addCommand(new Command45("list").description("List on-chain compute channels").option("--agent <address>", "Filter by paying agent").option("--miner <address>", "Filter by serving miner").option("--include-closed", "Include terminal channels").option("--json", "Output as JSON").action(async (opts, cmd) => {
11352
+ const command = new Command46("channel").description("Inspect and finalize compute channels");
11353
+ command.addCommand(new Command46("list").description("List on-chain compute channels").option("--agent <address>", "Filter by paying agent").option("--miner <address>", "Filter by serving miner").option("--include-closed", "Include terminal channels").option("--json", "Output as JSON").action(async (opts, cmd) => {
11133
11354
  let client;
11134
11355
  try {
11135
11356
  client = await createClient(getRpc(cmd));
@@ -11157,7 +11378,7 @@ function channelCmd() {
11157
11378
  await client?.disconnect();
11158
11379
  }
11159
11380
  }));
11160
- command.addCommand(new Command45("finalize").description("Finalize a force-settled channel after its challenge window").argument("<channel-id>", "0x channel ID").option("--json", "Output as JSON").action(async (channelId, _opts, cmd) => {
11381
+ command.addCommand(new Command46("finalize").description("Finalize a force-settled channel after its challenge window").argument("<channel-id>", "0x channel ID").option("--json", "Output as JSON").action(async (channelId, _opts, cmd) => {
11161
11382
  let client;
11162
11383
  try {
11163
11384
  client = await createClient(getRpc(cmd), getSigner(cmd));
@@ -11603,8 +11824,8 @@ function generateGatewayConfig(input) {
11603
11824
  }
11604
11825
 
11605
11826
  // src/commands/gateway-allowlist.ts
11606
- import { Command as Command46 } from "commander";
11607
- var PRECISIONS4 = ["FP32", "FP16", "BF16", "INT8", "INT4", "Mixed"];
11827
+ import { Command as Command47 } from "commander";
11828
+ var PRECISIONS5 = ["FP32", "FP16", "BF16", "INT8", "INT4", "Mixed"];
11608
11829
  function field(value, camel, snake) {
11609
11830
  return value?.[camel] ?? value?.[snake];
11610
11831
  }
@@ -11641,7 +11862,7 @@ async function fetchMinerSessionInfo(endpoint, authToken, signal) {
11641
11862
  }
11642
11863
  async function readChainSnapshot(api, miner, pinnedHead) {
11643
11864
  const identity = miner.modelIdentity;
11644
- const precision = PRECISIONS4[identity.precision];
11865
+ const precision = PRECISIONS5[identity.precision];
11645
11866
  if (!precision) throw new Error(`miner precision is invalid: ${identity.precision}`);
11646
11867
  const finalizedHead = pinnedHead ?? await api.rpc.chain.getFinalizedHead();
11647
11868
  const at = await api.at(finalizedHead);
@@ -11692,7 +11913,7 @@ async function readChainSnapshot(api, miner, pinnedHead) {
11692
11913
  };
11693
11914
  }
11694
11915
  function gatewayAllowlistCmd() {
11695
- return new Command46("allowlist").description("Generate gateway allowlist and model catalog from a live miner and chain").requiredOption("--miner <url>", "Authenticated miner base URL").requiredOption("--auth-token-env <name>", "Environment variable containing the miner bearer token").requiredOption("--attestation <path>", "Operator/provider deployment, catalog, and price evidence JSON").option("--json", "Output as JSON").action(async (opts, command) => {
11916
+ return new Command47("allowlist").description("Generate gateway allowlist and model catalog from a live miner and chain").requiredOption("--miner <url>", "Authenticated miner base URL").requiredOption("--auth-token-env <name>", "Environment variable containing the miner bearer token").requiredOption("--attestation <path>", "Operator/provider deployment, catalog, and price evidence JSON").option("--json", "Output as JSON").action(async (opts, command) => {
11696
11917
  let client;
11697
11918
  try {
11698
11919
  const authToken = process.env[opts.authTokenEnv];
@@ -11718,7 +11939,7 @@ function gatewayAllowlistCmd() {
11718
11939
  }
11719
11940
 
11720
11941
  // src/commands/gateway-renew.ts
11721
- import { open as open3, readFile as readFile11, rename as rename9, rm as rm4, writeFile as writeFile12 } from "node:fs/promises";
11942
+ import { open as open3, readFile as readFile11, rename as rename9, rm as rm5, writeFile as writeFile13 } from "node:fs/promises";
11722
11943
  import { createHash as createHash6 } from "node:crypto";
11723
11944
  import { request as request2 } from "node:http";
11724
11945
  import { setTimeout as delay } from "node:timers/promises";
@@ -12074,7 +12295,7 @@ var MinerAdmissions = class {
12074
12295
 
12075
12296
  // ../gateway-core/src/miner-peer.ts
12076
12297
  import { BlockList, isIP } from "node:net";
12077
- import { lookup } from "node:dns/promises";
12298
+ import { resolve4 as resolve42, resolve6 as resolve62 } from "node:dns/promises";
12078
12299
  import { request } from "node:https";
12079
12300
  import { Readable } from "node:stream";
12080
12301
  var NOT_PUBLIC = new BlockList();
@@ -12121,17 +12342,32 @@ function isPublicAddress(ip) {
12121
12342
  }
12122
12343
  var EndpointRefusal = class extends Error {
12123
12344
  };
12124
- var systemResolve = (host) => lookup(host, { all: true, verbatim: true });
12125
- async function resolvePublic(host, resolve9 = systemResolve) {
12345
+ async function resolveDnsAddresses(host, v4 = resolve42, v6 = resolve62) {
12346
+ for (let attempt = 0; ; attempt++) {
12347
+ const results = await Promise.allSettled([v4(host), v6(host)]);
12348
+ const errors = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
12349
+ const code = (error) => error?.code;
12350
+ const fatal = errors.find((error) => code(error) !== "ENODATA" && code(error) !== "ENOTFOUND");
12351
+ if (fatal) throw fatal;
12352
+ const notFound = errors.find((error) => code(error) === "ENOTFOUND");
12353
+ if (notFound) {
12354
+ if (attempt === 0) continue;
12355
+ throw notFound;
12356
+ }
12357
+ return results.flatMap((result, index) => result.status === "fulfilled" ? result.value.map((address) => ({ address, family: index === 0 ? 4 : 6 })) : []);
12358
+ }
12359
+ }
12360
+ var systemResolve = resolveDnsAddresses;
12361
+ async function resolvePublic(host, resolve10 = systemResolve) {
12126
12362
  const family = isIP(host);
12127
- const addresses = family ? [{ address: host, family }] : await resolve9(host);
12363
+ const addresses = family ? [{ address: host, family }] : await resolve10(host);
12128
12364
  if (addresses.length === 0 || !addresses.every((a) => isPublicAddress(a.address))) {
12129
12365
  throw new EndpointRefusal(`${host} does not resolve to public addresses only`);
12130
12366
  }
12131
12367
  return addresses[0];
12132
12368
  }
12133
12369
  function minerPeerFetch(endpoint, dynamic, before = () => {
12134
- }, resolve9 = systemResolve, send2 = request) {
12370
+ }, resolve10 = systemResolve, send2 = request) {
12135
12371
  return async (input, init = {}) => {
12136
12372
  before();
12137
12373
  const url = new URL(String(input));
@@ -12142,7 +12378,7 @@ function minerPeerFetch(endpoint, dynamic, before = () => {
12142
12378
  const pinned = await new Promise((accept, reject) => {
12143
12379
  const abort = () => reject(signal.reason);
12144
12380
  signal.addEventListener("abort", abort, { once: true });
12145
- resolvePublic(url.hostname.replace(/^\[|\]$/g, ""), resolve9).then(accept, reject).finally(() => signal.removeEventListener("abort", abort));
12381
+ resolvePublic(url.hostname.replace(/^\[|\]$/g, ""), resolve10).then(accept, reject).finally(() => signal.removeEventListener("abort", abort));
12146
12382
  if (signal.aborted) abort();
12147
12383
  });
12148
12384
  before();
@@ -12178,7 +12414,7 @@ function minerPeerFetch(endpoint, dynamic, before = () => {
12178
12414
  }
12179
12415
 
12180
12416
  // src/commands/gateway-renew.ts
12181
- import { Command as Command47 } from "commander";
12417
+ import { Command as Command48 } from "commander";
12182
12418
 
12183
12419
  // src/commands/gateway-tools-qualification.ts
12184
12420
  import { decodeAddress } from "@polkadot/util-crypto";
@@ -12442,7 +12678,7 @@ function tailnetNodeRef(source) {
12442
12678
  return match ? { host: match[1], egress: match[2], asn: match[3] } : null;
12443
12679
  }
12444
12680
  function readTailscaleStatus(socketPath) {
12445
- return new Promise((resolve9, reject) => {
12681
+ return new Promise((resolve10, reject) => {
12446
12682
  const req = request2({
12447
12683
  socketPath,
12448
12684
  path: "/localapi/v0/status",
@@ -12457,7 +12693,7 @@ function readTailscaleStatus(socketPath) {
12457
12693
  res.on("end", () => {
12458
12694
  if (res.statusCode !== 200) return reject(new Error(`tailscaled status returned HTTP ${res.statusCode}`));
12459
12695
  try {
12460
- resolve9(JSON.parse(body));
12696
+ resolve10(JSON.parse(body));
12461
12697
  } catch {
12462
12698
  reject(new Error("tailscaled status is not JSON"));
12463
12699
  }
@@ -12671,12 +12907,12 @@ async function writeEvidenceFile(path5, evidence, appendQualification = false) {
12671
12907
  evidence = { ...prior, generated_at: evidence.generated_at, conformance: [...conformance, ...latest.values()] };
12672
12908
  }
12673
12909
  parseGatewayEvidence(evidence);
12674
- await writeFile12(temporary, `${JSON.stringify(evidence, null, 2)}
12910
+ await writeFile13(temporary, `${JSON.stringify(evidence, null, 2)}
12675
12911
  `, { mode: 420 });
12676
12912
  await rename9(temporary, path5);
12677
12913
  } finally {
12678
12914
  await lock.close();
12679
- await rm4(`${path5}.lock`);
12915
+ await rm5(`${path5}.lock`);
12680
12916
  }
12681
12917
  }
12682
12918
  async function finishRenewal(report, out, output) {
@@ -12957,7 +13193,7 @@ function envSecret(name) {
12957
13193
  return value;
12958
13194
  }
12959
13195
  function gatewayRenewCmd() {
12960
- return new Command47("renew").description("Re-observe allowlisted miners, re-run feature conformance, and write the gateway evidence file").option("--allowlist-env <name>", "Direct probe mode: environment variable holding GATEWAY_SESSION_ALLOWLIST (never printed)").requiredOption("--catalog-env <name>", "Environment variable holding GATEWAY_MODEL_CATALOG").option("--catalog-pricing <mode>", "The gateway's GATEWAY_CATALOG_PRICING: operator, or chain when the catalog omits prices", "operator").option("--provider-key-env <name>", "Direct probe mode: environment variable holding the GPU.ai API key").option("--tailscale-socket <path>", "tailscaled LocalAPI socket for tailnet-hosted miners", "/var/run/tailscale/tailscaled.sock").option("--probe-signer-env <name>", "Direct probe mode: environment variable holding the dedicated funded probe signer URI").option("--probe-escrow <flop>", "Direct probe mode: FLOP escrowed for each conformance probe channel").option("--qualification-receipt <path>", "Receipt-only mode: settled owner tools-qualification evidence; never runs another probe").option("--qualification-receipt-sha256 <hash>", "SHA-256 of the immutable owner qualification receipt").requiredOption("--out <path>", "Evidence file the gateway reads as GATEWAY_EVIDENCE_FILE").option("--json", "Output as JSON").action(async (opts, command) => {
13196
+ return new Command48("renew").description("Re-observe allowlisted miners, re-run feature conformance, and write the gateway evidence file").option("--allowlist-env <name>", "Direct probe mode: environment variable holding GATEWAY_SESSION_ALLOWLIST (never printed)").requiredOption("--catalog-env <name>", "Environment variable holding GATEWAY_MODEL_CATALOG").option("--catalog-pricing <mode>", "The gateway's GATEWAY_CATALOG_PRICING: operator, or chain when the catalog omits prices", "operator").option("--provider-key-env <name>", "Direct probe mode: environment variable holding the GPU.ai API key").option("--tailscale-socket <path>", "tailscaled LocalAPI socket for tailnet-hosted miners", "/var/run/tailscale/tailscaled.sock").option("--probe-signer-env <name>", "Direct probe mode: environment variable holding the dedicated funded probe signer URI").option("--probe-escrow <flop>", "Direct probe mode: FLOP escrowed for each conformance probe channel").option("--qualification-receipt <path>", "Receipt-only mode: settled owner tools-qualification evidence; never runs another probe").option("--qualification-receipt-sha256 <hash>", "SHA-256 of the immutable owner qualification receipt").requiredOption("--out <path>", "Evidence file the gateway reads as GATEWAY_EVIDENCE_FILE").option("--json", "Output as JSON").action(async (opts, command) => {
12961
13197
  let client;
12962
13198
  try {
12963
13199
  if (opts.catalogPricing !== "operator" && opts.catalogPricing !== "chain") {
@@ -13051,9 +13287,9 @@ function gatewayRenewCmd() {
13051
13287
  }
13052
13288
 
13053
13289
  // src/release-check.ts
13054
- import { mkdir as mkdir13, readFile as readFile12, rename as rename10, writeFile as writeFile13 } from "node:fs/promises";
13290
+ import { mkdir as mkdir14, readFile as readFile12, rename as rename10, writeFile as writeFile14 } from "node:fs/promises";
13055
13291
  import { homedir as homedir5 } from "node:os";
13056
- import { dirname as dirname7, join as join9 } from "node:path";
13292
+ import { dirname as dirname7, join as join10 } from "node:path";
13057
13293
  var DAY_MS = 24 * 60 * 60 * 1e3;
13058
13294
  var READ_ONLY = /* @__PURE__ */ new Set([
13059
13295
  "health",
@@ -13097,7 +13333,7 @@ async function checkRelease(options) {
13097
13333
  if (options.offline || (options.testing ?? (process.env.NODE_ENV === "test" || !!process.env.VITEST))) return;
13098
13334
  const warn = options.warn ?? ((message) => console.error(message));
13099
13335
  const now = options.now ?? Date.now();
13100
- const cacheFile = options.cacheFile ?? join9(process.env.XDG_CACHE_HOME || join9(homedir5(), ".cache"), "flop", "releases.json");
13336
+ const cacheFile = options.cacheFile ?? join10(process.env.XDG_CACHE_HOME || join10(homedir5(), ".cache"), "flop", "releases.json");
13101
13337
  let releases;
13102
13338
  try {
13103
13339
  const cached = JSON.parse(await readFile12(cacheFile, "utf8"));
@@ -13112,9 +13348,9 @@ async function checkRelease(options) {
13112
13348
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
13113
13349
  releases = parseOperatorReleases(await response.json());
13114
13350
  try {
13115
- await mkdir13(dirname7(cacheFile), { recursive: true });
13351
+ await mkdir14(dirname7(cacheFile), { recursive: true });
13116
13352
  const temporary = `${cacheFile}.${process.pid}.tmp`;
13117
- await writeFile13(temporary, JSON.stringify({ fetchedAt: now, releases }), { mode: 384 });
13353
+ await writeFile14(temporary, JSON.stringify({ fetchedAt: now, releases }), { mode: 384 });
13118
13354
  await rename10(temporary, cacheFile);
13119
13355
  } catch {
13120
13356
  warn("warning: could not cache operator releases");
@@ -13139,10 +13375,10 @@ async function checkRelease(options) {
13139
13375
  }
13140
13376
 
13141
13377
  // src/cli.ts
13142
- var program = new Command48();
13378
+ var program = new Command49();
13143
13379
  var outputMode = outputModeFromArgv(process.argv.slice(2));
13144
13380
  setOutputMode(outputMode);
13145
- program.name("flop").description("FLOP Network CLI").version(operatorVersion(JSON.parse(readFileSync2(join10(packageRoot, "package.json"), "utf8")).version)).option("--offline", "Skip the operator release check").addOption(new Option7("--network <network>", "Network profile").choices(["local", "devnet", "testnet", "mainnet"]).default("local")).option("--rpc <url>", "RPC endpoint override").option("--signer <uri>", "Signer URI (local/dev only; required outside local unless --keystore)").option("--keystore <path>", "Encrypted Polkadot JSON keystore").option("--password-env <name>", "Environment variable containing the keystore password").addOption(new Option7("--output <mode>", "Output mode").choices(["human", "json", "agent"]).default("human")).option("--json", "Output as JSON");
13381
+ program.name("flop").description("FLOP Network CLI").version(operatorVersion(JSON.parse(readFileSync2(join11(packageRoot, "package.json"), "utf8")).version)).option("--offline", "Skip the operator release check").addOption(new Option8("--network <network>", "Network profile").choices(["local", "devnet", "testnet", "mainnet"]).default("local")).option("--rpc <url>", "RPC endpoint override").option("--signer <uri>", "Signer URI (local/dev only; required outside local unless --keystore)").option("--keystore <path>", "Encrypted Polkadot JSON keystore").option("--password-env <name>", "Environment variable containing the keystore password").addOption(new Option8("--output <mode>", "Output mode").choices(["human", "json", "agent"]).default("human")).option("--json", "Output as JSON");
13146
13382
  program.addCommand(healthCmd());
13147
13383
  program.addCommand(balanceCmd());
13148
13384
  program.addCommand(gpuCmd());
@@ -13154,13 +13390,13 @@ program.addCommand(chatCmd());
13154
13390
  program.addCommand(agentCmd());
13155
13391
  program.addCommand(minerCmd());
13156
13392
  program.addCommand(validatorCmd());
13157
- var networkCmd = new Command48("network").description("Resolve and inspect network profiles");
13393
+ var networkCmd = new Command49("network").description("Resolve and inspect network profiles");
13158
13394
  networkCmd.addCommand(networkStatusCmd());
13159
13395
  program.addCommand(networkCmd);
13160
- var faucetCmd = new Command48("faucet").description("Request dev/test FLOP");
13396
+ var faucetCmd = new Command49("faucet").description("Request dev/test FLOP");
13161
13397
  faucetCmd.addCommand(faucetRequestCmd());
13162
13398
  program.addCommand(faucetCmd);
13163
- var sessionCmd = new Command48("session").description("Manage session keys");
13399
+ var sessionCmd = new Command49("session").description("Manage session keys");
13164
13400
  sessionCmd.addCommand(sessionRunCmd());
13165
13401
  sessionCmd.addCommand(sessionListCmd());
13166
13402
  sessionCmd.addCommand(sessionCreateCmd());
@@ -13168,7 +13404,7 @@ sessionCmd.addCommand(sessionInfoCmd());
13168
13404
  sessionCmd.addCommand(sessionRevokeCmd());
13169
13405
  sessionCmd.addCommand(sessionLimitsCmd());
13170
13406
  program.addCommand(sessionCmd);
13171
- var keysCmd = new Command48("keys").description("Manage keys");
13407
+ var keysCmd = new Command49("keys").description("Manage keys");
13172
13408
  keysCmd.addCommand(keysGenerateCmd());
13173
13409
  keysCmd.addCommand(keysInspectCmd());
13174
13410
  keysCmd.addCommand(keysHardwareCmd());
@@ -13176,44 +13412,45 @@ keysCmd.addCommand(keysImportCmd());
13176
13412
  keysCmd.addCommand(keysExportCmd());
13177
13413
  keysCmd.addCommand(keysClaudeProfileCmd());
13178
13414
  program.addCommand(keysCmd);
13179
- var walletCmd = new Command48("wallet").description("Manage wallets");
13180
- var agentWalletCmd = new Command48("agent").description("Agent wallet operations");
13415
+ var walletCmd = new Command49("wallet").description("Manage wallets");
13416
+ var agentWalletCmd = new Command49("agent").description("Agent wallet operations");
13181
13417
  agentWalletCmd.addCommand(walletAgentCreateCmd());
13182
13418
  walletCmd.addCommand(agentWalletCmd);
13183
13419
  program.addCommand(walletCmd);
13184
- var txCmd = new Command48("tx").description("Build, sign, and broadcast transactions");
13420
+ var txCmd = new Command49("tx").description("Build, sign, and broadcast transactions");
13185
13421
  txCmd.addCommand(txBuildCmd());
13186
13422
  txCmd.addCommand(txBroadcastCmd());
13187
13423
  program.addCommand(txCmd);
13188
13424
  program.addCommand(multisigCmd());
13189
13425
  program.addCommand(channelCmd());
13190
- var gatewayCmd = new Command48("gateway").description("Generate verified gateway configuration");
13426
+ var gatewayCmd = new Command49("gateway").description("Generate verified gateway configuration");
13191
13427
  gatewayCmd.addCommand(gatewayAllowlistCmd());
13192
13428
  gatewayCmd.addCommand(gatewayRenewCmd());
13193
13429
  program.addCommand(gatewayCmd);
13194
- var x402Cmd = new Command48("x402").description("x402 payment gateway operations");
13430
+ var x402Cmd = new Command49("x402").description("x402 payment gateway operations");
13195
13431
  x402Cmd.addCommand(x402SettleCmd());
13196
13432
  program.addCommand(x402Cmd);
13197
- var daCmd = new Command48("da").description("Sovereign DA registry (leases, deposits)");
13433
+ var daCmd = new Command49("da").description("Sovereign DA registry (leases, deposits)");
13198
13434
  daCmd.addCommand(daDepositCmd());
13199
13435
  daCmd.addCommand(daRegisterCmd());
13200
13436
  daCmd.addCommand(daTopupCmd());
13201
13437
  daCmd.addCommand(daWithdrawCmd());
13202
13438
  program.addCommand(daCmd);
13203
- var modelCmd = new Command48("model").description("Model registry operations");
13439
+ var modelCmd = new Command49("model").description("Model registry operations");
13204
13440
  modelCmd.addCommand(modelListCmd());
13205
13441
  modelCmd.addCommand(modelGetCmd());
13206
13442
  modelCmd.addCommand(modelFetchCmd());
13207
13443
  modelCmd.addCommand(modelUploadCmd());
13208
13444
  modelCmd.addCommand(modelPreflightCmd());
13209
13445
  modelCmd.addCommand(modelOnboardCmd());
13446
+ modelCmd.addCommand(modelBackupCmd());
13210
13447
  modelCmd.addCommand(modelAuditCmd());
13211
13448
  program.addCommand(modelCmd);
13212
13449
  program.hook("preAction", async (_root, action) => {
13213
13450
  const path5 = [];
13214
13451
  for (let command = action; command && command !== program; command = command.parent) path5.unshift(command.name());
13215
13452
  await checkRelease({
13216
- version: operatorVersion(JSON.parse(readFileSync2(join10(packageRoot, "package.json"), "utf8")).version),
13453
+ version: operatorVersion(JSON.parse(readFileSync2(join11(packageRoot, "package.json"), "utf8")).version),
13217
13454
  readOnly: isReadOnlyCommand(path5),
13218
13455
  offline: program.opts().offline,
13219
13456
  image: !!process.env.FLOP_IMAGE_VERSION
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flop-labs/cli",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "FLOP Network CLI — interact with FLOP from the terminal",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "repository": {
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@flop-labs/client": "^0.2.2",
26
- "@flop-labs/sdk": "^0.5.1",
26
+ "@flop-labs/sdk": "^0.5.2",
27
27
  "@polkadot/api": "^17.0.1",
28
28
  "@polkadot/keyring": "^14.0.3",
29
29
  "@polkadot/util-crypto": "^14.0.3",