@tokamak-private-dapps/private-state-cli 0.1.2 → 0.1.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.3 - 2026-04-28
4
+
5
+ - Installed the Groth16 runtime during `private-state-cli --install` and reported Groth16 readiness from `--doctor`.
6
+ - Added live NVIDIA and Docker GPU probes to `--doctor`, with a hard failure when live Docker GPU readiness does not match the recorded Tokamak CLI metadata.
7
+ - Renamed `get-my-address` to `get-my-wallet-meta`, added `get-my-l1-address`, and added `list-local-wallets`.
8
+ - Documented the private-state CLI helper commands, common flow examples, and LLM agent guidance.
9
+
3
10
  ## 0.1.2 - 2026-04-28
4
11
 
5
12
  - Added `private-state-cli --doctor` to report CLI and install-time dependency versions through `tokamak-l2js`.
package/README.md CHANGED
@@ -8,7 +8,8 @@ Command-line client for the Tokamak private-state DApp.
8
8
  npm install -g @tokamak-private-dapps/private-state-cli
9
9
  ```
10
10
 
11
- Install the local Tokamak zk-EVM runtime workspace and public private-state deployment artifacts:
11
+ Install the local Tokamak zk-EVM runtime workspace, Groth16 runtime workspace, and public private-state deployment
12
+ artifacts:
12
13
 
13
14
  ```bash
14
15
  private-state-cli --install
@@ -35,7 +36,7 @@ private-state-cli --doctor
35
36
 
36
37
  ## Commands
37
38
 
38
- The normal private-state flow is:
39
+ A common private-state flow is:
39
40
 
40
41
  1. `create-channel`
41
42
  2. `deposit-bridge`
@@ -52,7 +53,21 @@ Use `private-state-cli --help` for the full command list and required options.
52
53
 
53
54
  `private-state-cli --doctor` reports the CLI package version, dependency versions recorded by the last
54
55
  `private-state-cli --install`, current dependency versions through `tokamak-l2js`, and Tokamak zk-EVM runtime
55
- install mode, Docker mode, and CUDA runtime metadata.
56
+ install mode, Docker mode, CUDA runtime metadata, live `nvidia-smi` and Docker GPU probe results, and Groth16
57
+ runtime health. The doctor check fails when the Tokamak Docker `useGpus` metadata does not match the live GPU probes.
58
+
59
+ Local helper commands:
60
+
61
+ ```bash
62
+ private-state-cli list-local-wallets --network sepolia --channel-name cuda
63
+ private-state-cli get-my-wallet-meta --wallet <WALLET_NAME> --password <PASSWORD> --network sepolia
64
+ private-state-cli get-my-l1-address --private-key <HEX>
65
+ ```
66
+
67
+ `list-local-wallets` reads only the local workspace and prints saved wallet names that can be reused with `--wallet`.
68
+ `get-my-wallet-meta` opens an encrypted local wallet and reports the stored L1/L2 identity metadata plus the current
69
+ on-chain channel registration match state. `get-my-l1-address` is a simple offline helper that derives the L1 address
70
+ for a private key.
56
71
 
57
72
  ## Workspace
58
73
 
@@ -64,6 +79,57 @@ The CLI stores user workspaces under:
64
79
 
65
80
  Wallet data is encrypted with the password supplied to `join-channel` or `recover-wallet`.
66
81
 
82
+ ## LLM Agent Guidance
83
+
84
+ You may act as an interactive guide for users who do not understand this CLI or the private-state DApp. Assume the
85
+ user only knows that the DApp is a way to send funds privately. Translate the user's intent into safe, step-by-step CLI
86
+ actions.
87
+
88
+ Primary goal: help the user privately send funds by guiding them through the required private-state CLI commands,
89
+ explaining each step only as much as needed to proceed safely.
90
+
91
+ Operating rules:
92
+
93
+ - Do not ask the user to reveal raw private keys in chat. Use environment variable placeholders such as `$ADDR6`,
94
+ `$CREATOR`, or `$PRIVATE_STATE_TEST_PK`.
95
+ - Prefer testnet examples unless the user explicitly asks for mainnet.
96
+ - Before any proof-backed or bridge-facing workflow, ask the user to run `private-state-cli --doctor` and inspect
97
+ whether the runtime, Docker mode, CUDA/GPU probes, Groth16 runtime, and deployment artifacts are healthy.
98
+ - Use `private-state-cli list-local-wallets` to discover local wallet names instead of asking the user to inspect
99
+ filesystem paths manually.
100
+ - Use `private-state-cli get-my-l1-address --private-key "$KEY_ENV"` to derive the L1 address for a private-key
101
+ environment variable when wallet ownership needs to be identified.
102
+ - Use `private-state-cli get-my-wallet-meta --wallet <WALLET> --password <PASSWORD> --network <NETWORK>` to inspect
103
+ local wallet metadata and on-chain channel registration state.
104
+ - Use `private-state-cli get-my-bridge-fund` and `private-state-cli get-my-channel-fund` to check balances before
105
+ telling the user to move funds.
106
+ - Explain that wallet names are local CLI identifiers, while private transfers use notes owned by L2 addresses
107
+ registered in the channel.
108
+ - Do not present one fixed command sequence as universally correct. Some flows start from an existing channel or wallet,
109
+ while others require creating or joining a channel first.
110
+ - When the user asks for a transfer, first determine whether the sender has minted notes available. If not, guide them
111
+ through funding the bridge, joining or recovering the channel wallet, depositing into the channel, and minting notes.
112
+ - When generating commands, use placeholders for secrets and explicit values for public fields. Show one command at a
113
+ time unless the user asks for a batch.
114
+
115
+ Suggested interaction flow:
116
+
117
+ 1. Identify the target network, usually `sepolia` for testing.
118
+ 2. Identify whether a channel already exists.
119
+ 3. Identify the sender and recipient wallets or private-key environment variables.
120
+ 4. Run `--doctor`.
121
+ 5. Run `list-local-wallets` and relevant metadata or balance checks.
122
+ 6. If needed, guide the user through `create-channel`, `deposit-bridge`, `join-channel`, `deposit-channel`, and
123
+ `mint-notes`.
124
+ 7. For a private transfer, select available note IDs from `get-my-notes`, find the recipient L2 address from
125
+ `get-my-wallet-meta`, then build `transfer-notes`.
126
+ 8. After transfer, guide the recipient to run `get-my-notes` to recover received notes from event logs.
127
+
128
+ Example style: if the user says, "ADDR6 sends 10 tokens privately to ADDR8", do not assume the required note exists.
129
+ First ask or check which channel and network to use, whether ADDR6 and ADDR8 are already joined, what the local wallet
130
+ names are, and whether ADDR6 has an unused note worth exactly 10 or notes that sum to 10. Then provide the next concrete
131
+ command.
132
+
67
133
  ## Artifacts
68
134
 
69
135
  Proof-backed commands require installed bridge, DApp, and Groth16 artifacts. Run `private-state-cli --install` before
@@ -81,7 +147,8 @@ Release order matters for npm publication. `@tokamak-private-dapps/common-librar
81
147
  ### What does this package install?
82
148
 
83
149
  It installs the `private-state-cli` terminal command and the local files needed by that command.
84
- It does not install bridge contracts, app contracts, or local deployment outputs.
150
+ It does not install bridge contracts, app contracts, or local deployment outputs. The `private-state-cli --install`
151
+ command provisions the local Tokamak zk-EVM and Groth16 runtime workspaces used by proof-backed commands.
85
152
 
86
153
  ### When should I run `private-state-cli --install`?
87
154
 
@@ -528,7 +528,9 @@
528
528
  { id: "get-my-bridge-fund", description: "Read the current bridge-vault balance.", fields: ["network", "privateKey", "alchemyApiKey"] },
529
529
  { id: "join-channel", description: "Bind the caller to a channel-specific L2 identity.", fields: ["channelName", "password", "network", "privateKey", "alchemyApiKey"] },
530
530
  { id: "recover-wallet", description: "Rebuild the recoverable portion of a wallet.", fields: ["channelName", "password", "network", "privateKey", "alchemyApiKey"] },
531
- { id: "get-my-address", description: "Check whether a saved wallet matches on-chain registration.", fields: ["wallet", "password", "network"] },
531
+ { id: "get-my-wallet-meta", description: "Check whether a saved wallet matches on-chain registration.", fields: ["wallet", "password", "network"] },
532
+ { id: "get-my-l1-address", description: "Derive the L1 address for a private key.", fields: ["privateKey"] },
533
+ { id: "list-local-wallets", description: "List saved local wallet names that can be reused with --wallet.", fields: ["network", "channelName"] },
532
534
  { id: "deposit-channel", description: "Move bridged funds into the channel L2 accounting balance.", fields: ["wallet", "password", "network", "amount"] },
533
535
  { id: "withdraw-channel", description: "Move channel L2 balance back into the shared bridge vault.", fields: ["wallet", "password", "network", "amount"] },
534
536
  { id: "get-my-channel-fund", description: "Read the current channel L2 accounting balance.", fields: ["wallet", "password", "network"] },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokamak-private-dapps/private-state-cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Command-line client for the Tokamak private-state DApp.",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "author": "Tokamak Network",
@@ -90,6 +90,8 @@ const tokamakCliInvocation = buildTokamakCliInvocation();
90
90
  const tokamakCliCommand = tokamakCliInvocation.command;
91
91
  const tokamakCliBaseArgs = tokamakCliInvocation.args;
92
92
  const flatDeploymentArtifactPathsByChainId = new Map();
93
+ const DOCKER_CUDA_PROBE_IMAGE = "nvidia/cuda:12.2.0-base-ubuntu22.04";
94
+ const DOCTOR_GPU_PROBE_TIMEOUT_MS = 120000;
93
95
 
94
96
  const abiCoder = AbiCoder.defaultAbiCoder();
95
97
  const erc20MetadataAbi = [
@@ -231,6 +233,18 @@ async function main() {
231
233
  return;
232
234
  }
233
235
 
236
+ if (args.command === "get-my-l1-address") {
237
+ assertGetMyL1AddressArgs(args);
238
+ handleGetMyL1Address({ args });
239
+ return;
240
+ }
241
+
242
+ if (args.command === "list-local-wallets") {
243
+ assertListLocalWalletsArgs(args);
244
+ handleListLocalWallets({ args });
245
+ return;
246
+ }
247
+
234
248
  const walletCommandHandlers = {
235
249
  "mint-notes": {
236
250
  assert: assertMintNotesArgs,
@@ -256,9 +270,9 @@ async function main() {
256
270
  assert: (parsedArgs) => assertWalletChannelMoveArgs(parsedArgs, "withdraw-channel"),
257
271
  run: ({ provider }) => handleGrothVaultMove({ args, provider, direction: "withdraw" }),
258
272
  },
259
- "get-my-address": {
260
- assert: assertGetMyAddressArgs,
261
- run: ({ provider }) => handleGetMyAddress({ args, provider }),
273
+ "get-my-wallet-meta": {
274
+ assert: assertGetMyWalletMetaArgs,
275
+ run: ({ provider }) => handleGetMyWalletMeta({ args, provider }),
262
276
  },
263
277
  "get-my-channel-fund": {
264
278
  assert: assertGetMyChannelFundArgs,
@@ -942,6 +956,9 @@ async function handleInstallZkEvm({ args }) {
942
956
  }
943
957
  run(tokamakCliCommand, installArgs);
944
958
  const tokamakRuntimeRoot = resolveTokamakCliRuntimeRoot();
959
+ const groth16Runtime = installGroth16RuntimeForPrivateState({
960
+ docker: Boolean(args.docker),
961
+ });
945
962
  const localDeploymentBaseRoot = args.includeLocalArtifacts ? process.cwd() : null;
946
963
  const deploymentArtifacts = await installPrivateStateCliArtifacts({
947
964
  dappName: PRIVATE_STATE_DAPP_LABEL,
@@ -952,11 +969,13 @@ async function handleInstallZkEvm({ args }) {
952
969
  includeLocalArtifacts: Boolean(args.includeLocalArtifacts),
953
970
  localDeploymentBaseRoot,
954
971
  deploymentArtifacts,
972
+ groth16Runtime,
955
973
  });
956
974
  printJson({
957
975
  action: "install",
958
976
  tokamakCli: tokamakCliBaseArgs[0],
959
977
  runtimeRoot: tokamakRuntimeRoot,
978
+ groth16Runtime,
960
979
  docker: Boolean(args.docker),
961
980
  includeLocalArtifacts: Boolean(args.includeLocalArtifacts),
962
981
  localDeploymentBaseRoot,
@@ -997,7 +1016,38 @@ async function handleDoctor() {
997
1016
  }
998
1017
  }
999
1018
 
1000
- async function handleGetMyAddress({ args, provider }) {
1019
+ function handleGetMyL1Address({ args }) {
1020
+ const privateKey = normalizePrivateKey(requireArg(args.privateKey, "--private-key"));
1021
+ const signer = new Wallet(privateKey);
1022
+ printJson({
1023
+ action: "get-my-l1-address",
1024
+ l1Address: signer.address,
1025
+ });
1026
+ }
1027
+
1028
+ function handleListLocalWallets({ args }) {
1029
+ const networkFilter = args.network ? requireNetworkName(args) : null;
1030
+ if (networkFilter) {
1031
+ resolveCliNetwork(networkFilter);
1032
+ }
1033
+ const channelFilter = args.channelName ? slugifyPathComponent(requireArg(args.channelName, "--channel-name")) : null;
1034
+ const wallets = listLocalWallets({
1035
+ networkFilter,
1036
+ channelFilter,
1037
+ });
1038
+
1039
+ printJson({
1040
+ action: "list-local-wallets",
1041
+ workspaceRoot,
1042
+ filters: {
1043
+ network: networkFilter,
1044
+ channelName: args.channelName ?? null,
1045
+ },
1046
+ wallets,
1047
+ });
1048
+ }
1049
+
1050
+ async function handleGetMyWalletMeta({ args, provider }) {
1001
1051
  const { wallet, walletMetadata } = loadUnlockedWalletWithMetadata(args);
1002
1052
  const { signer, l2Identity } = restoreWalletParticipant(wallet, provider);
1003
1053
  const context = await loadChannelContext({
@@ -1015,7 +1065,7 @@ async function handleGetMyAddress({ args, provider }) {
1015
1065
  === ethers.toBigInt(normalizeBytes32Hex(expectedStorageKey));
1016
1066
 
1017
1067
  printJson({
1018
- action: "get-my-address",
1068
+ action: "get-my-wallet-meta",
1019
1069
  wallet: wallet.walletName,
1020
1070
  network: walletMetadata.network,
1021
1071
  channelName: walletMetadata.channelName,
@@ -4377,6 +4427,48 @@ function resolveWalletPathCandidates(walletName) {
4377
4427
  return candidates;
4378
4428
  }
4379
4429
 
4430
+ function listLocalWallets({ networkFilter = null, channelFilter = null } = {}) {
4431
+ if (!fs.existsSync(workspaceRoot)) {
4432
+ return [];
4433
+ }
4434
+
4435
+ const wallets = [];
4436
+ for (const networkEntry of fs.readdirSync(workspaceRoot, { withFileTypes: true })) {
4437
+ if (!networkEntry.isDirectory() || (networkFilter && networkEntry.name !== slugifyPathComponent(networkFilter))) {
4438
+ continue;
4439
+ }
4440
+ const networkDir = path.join(workspaceRoot, networkEntry.name);
4441
+ for (const channelEntry of fs.readdirSync(networkDir, { withFileTypes: true })) {
4442
+ if (!channelEntry.isDirectory() || (channelFilter && channelEntry.name !== channelFilter)) {
4443
+ continue;
4444
+ }
4445
+ const walletsDir = path.join(networkDir, channelEntry.name, "wallets");
4446
+ if (!fs.existsSync(walletsDir)) {
4447
+ continue;
4448
+ }
4449
+ for (const walletEntry of fs.readdirSync(walletsDir, { withFileTypes: true })) {
4450
+ if (!walletEntry.isDirectory()) {
4451
+ continue;
4452
+ }
4453
+ const walletDir = path.join(walletsDir, walletEntry.name);
4454
+ wallets.push({
4455
+ wallet: walletEntry.name,
4456
+ network: networkEntry.name,
4457
+ channelName: channelEntry.name,
4458
+ walletDir,
4459
+ metadataPath: walletMetadataPath(walletDir),
4460
+ hasMetadata: fs.existsSync(walletMetadataPath(walletDir)),
4461
+ hasEncryptedWallet: walletConfigExists(walletDir),
4462
+ });
4463
+ }
4464
+ }
4465
+ }
4466
+ return wallets.sort((left, right) =>
4467
+ [left.network, left.channelName, left.wallet].join("\0")
4468
+ .localeCompare([right.network, right.channelName, right.wallet].join("\0")),
4469
+ );
4470
+ }
4471
+
4380
4472
  function channelDataPath(workspaceDir) {
4381
4473
  return workspaceChannelDir(workspaceDir);
4382
4474
  }
@@ -4566,8 +4658,33 @@ function assertJoinChannelArgs(args) {
4566
4658
  assertExplicitSignerPasswordCommandArgs(args, "join-channel");
4567
4659
  }
4568
4660
 
4569
- function assertGetMyAddressArgs(args) {
4570
- assertWalletPasswordArgs(args, "get-my-address", [], "--wallet, --password, and --network");
4661
+ function assertGetMyWalletMetaArgs(args) {
4662
+ assertWalletPasswordArgs(args, "get-my-wallet-meta", [], "--wallet, --password, and --network");
4663
+ }
4664
+
4665
+ function assertGetMyL1AddressArgs(args) {
4666
+ requireArg(args.privateKey, "--private-key");
4667
+ assertAllowedCommandKeys(
4668
+ args,
4669
+ "get-my-l1-address",
4670
+ new Set(["command", "positional", "privateKey"]),
4671
+ "--private-key",
4672
+ );
4673
+ }
4674
+
4675
+ function assertListLocalWalletsArgs(args) {
4676
+ if (args.network !== undefined) {
4677
+ requireNetworkName(args);
4678
+ }
4679
+ if (args.channelName !== undefined) {
4680
+ requireArg(args.channelName, "--channel-name");
4681
+ }
4682
+ assertAllowedCommandKeys(
4683
+ args,
4684
+ "list-local-wallets",
4685
+ new Set(["command", "positional", "network", "channelName"]),
4686
+ "optional --network and --channel-name",
4687
+ );
4571
4688
  }
4572
4689
 
4573
4690
  function assertWithdrawBridgeArgs(args) {
@@ -4629,8 +4746,8 @@ function printHelp() {
4629
4746
  console.log(`
4630
4747
  Commands:
4631
4748
  --install [--docker] [--include-local-artifacts]
4632
- Install the Tokamak zk-EVM CLI runtime workspace and private-state deployment artifacts
4633
- Use --docker on Linux to forward tokamak-cli --install --docker
4749
+ Install the Tokamak zk-EVM CLI runtime, Groth16 runtime, and private-state deployment artifacts
4750
+ Use --docker on Linux to forward Docker mode to the Tokamak zk-EVM and Groth16 runtimes
4634
4751
  Use --include-local-artifacts to also install local deployment/ artifacts from the current working directory
4635
4752
 
4636
4753
  uninstall-zk-evm
@@ -4660,9 +4777,15 @@ Commands:
4660
4777
  join-channel --channel-name <NAME> --password <PASSWORD> --network <NAME> --private-key <HEX> --alchemy-api-key <KEY>
4661
4778
  Pay the channel join fee and bind a wallet to a channel-specific L2 identity
4662
4779
 
4663
- get-my-address --wallet <NAME> --password <PASSWORD> --network <NAME>
4780
+ get-my-wallet-meta --wallet <NAME> --password <PASSWORD> --network <NAME>
4664
4781
  Check whether a wallet matches the on-chain channel registration
4665
4782
 
4783
+ get-my-l1-address --private-key <HEX>
4784
+ Derive the L1 address for a private key
4785
+
4786
+ list-local-wallets [--network <NAME>] [--channel-name <NAME>]
4787
+ List saved local wallet names that can be reused with --wallet
4788
+
4666
4789
  deposit-channel --wallet <NAME> --password <PASSWORD> --network <NAME> --amount <TOKENS>
4667
4790
  Move bridged funds into the channel L2 accounting balance
4668
4791
 
@@ -4927,6 +5050,7 @@ function writePrivateStateCliInstallManifest({
4927
5050
  includeLocalArtifacts,
4928
5051
  localDeploymentBaseRoot,
4929
5052
  deploymentArtifacts,
5053
+ groth16Runtime,
4930
5054
  }) {
4931
5055
  const manifestPath = privateStateCliInstallManifestPath(deploymentArtifacts.cacheBaseRoot);
4932
5056
  const manifest = {
@@ -4941,6 +5065,7 @@ function writePrivateStateCliInstallManifest({
4941
5065
  includeLocalArtifacts,
4942
5066
  localDeploymentBaseRoot,
4943
5067
  artifactCacheRoot: deploymentArtifacts.cacheBaseRoot,
5068
+ groth16Runtime,
4944
5069
  installedDeploymentArtifacts: deploymentArtifacts.installed.map((entry) => ({
4945
5070
  chainId: entry.chainId,
4946
5071
  source: entry.source,
@@ -4966,6 +5091,8 @@ function buildDoctorReport() {
4966
5091
  const installManifest = readJsonIfExists(installManifestPath);
4967
5092
  const dependencyReports = collectDependencyPackageReports(installManifest);
4968
5093
  const tokamakCli = inspectTokamakCliRuntime();
5094
+ const groth16Runtime = inspectGroth16Runtime();
5095
+ const gpuDockerReadiness = inspectGpuDockerReadiness(tokamakCli);
4969
5096
  const checks = [
4970
5097
  {
4971
5098
  name: "dependency package versions",
@@ -4993,6 +5120,28 @@ function buildDoctorReport() {
4993
5120
  })),
4994
5121
  },
4995
5122
  },
5123
+ {
5124
+ name: "tokamak docker gpu readiness",
5125
+ ok: gpuDockerReadiness.ok,
5126
+ details: {
5127
+ expectedUseGpus: gpuDockerReadiness.expectedUseGpus,
5128
+ liveUseGpus: gpuDockerReadiness.liveUseGpus,
5129
+ mismatch: gpuDockerReadiness.mismatch,
5130
+ mismatchError: gpuDockerReadiness.mismatchError,
5131
+ hostNvidiaSmi: summarizeProbeResult(gpuDockerReadiness.hostNvidiaSmi),
5132
+ dockerNvidiaSmi: summarizeProbeResult(gpuDockerReadiness.dockerNvidiaSmi),
5133
+ },
5134
+ },
5135
+ {
5136
+ name: "groth16 runtime",
5137
+ ok: groth16Runtime.installed,
5138
+ details: {
5139
+ packageRoot: groth16Runtime.packageRoot,
5140
+ workspaceRoot: groth16Runtime.workspaceRoot,
5141
+ doctorStatus: groth16Runtime.doctor.status,
5142
+ checks: groth16Runtime.checks,
5143
+ },
5144
+ },
4996
5145
  ];
4997
5146
 
4998
5147
  return {
@@ -5012,10 +5161,25 @@ function buildDoctorReport() {
5012
5161
  },
5013
5162
  dependencies: dependencyReports,
5014
5163
  tokamakCli,
5164
+ groth16Runtime,
5165
+ gpuDockerReadiness,
5015
5166
  checks,
5016
5167
  };
5017
5168
  }
5018
5169
 
5170
+ function installGroth16RuntimeForPrivateState({ docker }) {
5171
+ const packageRoot = resolveGroth16PackageRoot();
5172
+ const entryPath = resolveGroth16CliEntryPath(packageRoot);
5173
+ const args = [entryPath, "--install"];
5174
+ if (docker) {
5175
+ args.push("--docker");
5176
+ }
5177
+ run(process.execPath, args, { cwd: packageRoot });
5178
+ const runtime = inspectGroth16Runtime();
5179
+ expect(runtime.installed, "Groth16 runtime install completed, but tokamak-groth16 --doctor still reports an unhealthy runtime.");
5180
+ return runtime;
5181
+ }
5182
+
5019
5183
  function collectDependencyPackageReports(installManifest = null) {
5020
5184
  const installVersions = new Map(
5021
5185
  Array.isArray(installManifest?.dependencies)
@@ -5087,6 +5251,35 @@ function findPackageJsonForName(startDir, expectedName) {
5087
5251
  throw new Error(`Cannot locate package.json for ${expectedName} above ${startDir}.`);
5088
5252
  }
5089
5253
 
5254
+ function resolveGroth16PackageRoot() {
5255
+ const publicDriveCrsPath = require.resolve("@tokamak-private-dapps/groth16/public-drive-crs");
5256
+ return path.dirname(findPackageJsonForName(path.dirname(publicDriveCrsPath), "@tokamak-private-dapps/groth16"));
5257
+ }
5258
+
5259
+ function resolveGroth16CliEntryPath(packageRoot = resolveGroth16PackageRoot()) {
5260
+ return path.join(packageRoot, "cli", "tokamak-groth16-cli.mjs");
5261
+ }
5262
+
5263
+ function inspectGroth16Runtime() {
5264
+ const packageRoot = resolveGroth16PackageRoot();
5265
+ const entryPath = resolveGroth16CliEntryPath(packageRoot);
5266
+ const doctor = runCaptured(process.execPath, [entryPath, "--doctor", "--verbose"], { cwd: packageRoot });
5267
+ const stdout = stripAnsi(doctor.stdout).trim();
5268
+ const stderr = stripAnsi(doctor.stderr).trim();
5269
+ const report = parseJsonReport(stdout);
5270
+ return {
5271
+ installed: doctor.status === 0 && report?.ok === true,
5272
+ packageRoot,
5273
+ workspaceRoot: report?.workspaceRoot ?? null,
5274
+ checks: report?.checks ?? [],
5275
+ doctor: {
5276
+ status: doctor.status,
5277
+ stdout,
5278
+ stderr,
5279
+ },
5280
+ };
5281
+ }
5282
+
5090
5283
  function inspectTokamakCliRuntime() {
5091
5284
  const doctor = runCaptured(tokamakCliCommand, [...tokamakCliBaseArgs, "--doctor"], {
5092
5285
  cwd: resolveTokamakCliPackageRoot(),
@@ -5118,6 +5311,84 @@ function inspectTokamakCliRuntime() {
5118
5311
  };
5119
5312
  }
5120
5313
 
5314
+ function inspectGpuDockerReadiness(tokamakCli) {
5315
+ const hostNvidiaSmi = runProbe("nvidia-smi", ["--query-gpu=name,driver_version", "--format=csv,noheader"]);
5316
+ const dockerNvidiaSmi = runProbe("docker", [
5317
+ "run",
5318
+ "--rm",
5319
+ "--gpus",
5320
+ "all",
5321
+ DOCKER_CUDA_PROBE_IMAGE,
5322
+ "nvidia-smi",
5323
+ ]);
5324
+ const expectedUseGpus = Boolean(tokamakCli.cudaCompatible);
5325
+ const liveUseGpus = hostNvidiaSmi.ok && dockerNvidiaSmi.ok;
5326
+ const mismatch = expectedUseGpus !== liveUseGpus;
5327
+ return {
5328
+ ok: !mismatch,
5329
+ expectedUseGpus,
5330
+ liveUseGpus,
5331
+ mismatch,
5332
+ mismatchError: mismatch
5333
+ ? [
5334
+ "Tokamak CLI Docker GPU metadata does not match live NVIDIA/Docker GPU probes.",
5335
+ `metadata useGpus=${expectedUseGpus}; live useGpus=${liveUseGpus}.`,
5336
+ ].join(" ")
5337
+ : null,
5338
+ probeImage: DOCKER_CUDA_PROBE_IMAGE,
5339
+ hostNvidiaSmi,
5340
+ dockerNvidiaSmi,
5341
+ };
5342
+ }
5343
+
5344
+ function runProbe(command, args) {
5345
+ const result = spawnSync(command, args, {
5346
+ encoding: "utf8",
5347
+ timeout: DOCTOR_GPU_PROBE_TIMEOUT_MS,
5348
+ stdio: ["ignore", "pipe", "pipe"],
5349
+ });
5350
+ return {
5351
+ command,
5352
+ args,
5353
+ ok: !result.error && result.status === 0,
5354
+ status: result.status,
5355
+ signal: result.signal,
5356
+ error: result.error ? result.error.message : null,
5357
+ stdout: stripAnsi(result.stdout ?? "").trim(),
5358
+ stderr: stripAnsi(result.stderr ?? "").trim(),
5359
+ timedOut: result.error?.code === "ETIMEDOUT",
5360
+ };
5361
+ }
5362
+
5363
+ function summarizeProbeResult(result) {
5364
+ return {
5365
+ command: [result.command, ...result.args].join(" "),
5366
+ ok: result.ok,
5367
+ status: result.status,
5368
+ signal: result.signal,
5369
+ error: result.error,
5370
+ timedOut: result.timedOut,
5371
+ stdout: truncateText(result.stdout, 2000),
5372
+ stderr: truncateText(result.stderr, 2000),
5373
+ };
5374
+ }
5375
+
5376
+ function truncateText(value, maxLength) {
5377
+ const text = String(value ?? "");
5378
+ if (text.length <= maxLength) {
5379
+ return text;
5380
+ }
5381
+ return `${text.slice(0, maxLength)}...`;
5382
+ }
5383
+
5384
+ function parseJsonReport(value) {
5385
+ try {
5386
+ return JSON.parse(value);
5387
+ } catch {
5388
+ return null;
5389
+ }
5390
+ }
5391
+
5121
5392
  function resolveTokamakCliCacheRoot() {
5122
5393
  return path.resolve(process.env.TOKAMAK_ZKEVM_CLI_CACHE_DIR ?? path.join(os.homedir(), ".tokamak-zk-evm"));
5123
5394
  }