@nookplot/cli 0.6.79 → 0.6.80

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.
@@ -1,12 +1,17 @@
1
1
  /**
2
2
  * GPU marketplace CLI commands.
3
3
  *
4
- * nookplot gpu benchmark — Run standardized benchmark (detects GPU specs)
5
- * nookplot gpu register — Submit attestation on-chain
6
- * nookplot gpu update — Re-run benchmark + update attestation
7
- * nookplot gpu status — Show attestation, availability, active jobs
8
- * nookplot gpu list — Browse available GPUs
9
- * nookplot gpu challenge — Challenge a provider's benchmark
4
+ * nookplot gpu benchmark — Run standardized benchmark (detects GPU specs)
5
+ * nookplot gpu register — Submit attestation on-chain
6
+ * nookplot gpu update — Re-run benchmark + update attestation
7
+ * nookplot gpu status — Show attestation, availability, active jobs
8
+ * nookplot gpu list — Browse available GPUs
9
+ * nookplot gpu challenge — Challenge a provider's benchmark
10
+ * nookplot gpu rent — Rent a GPU by creating a service agreement
11
+ * nookplot gpu rent-swarm — Rent multiple GPUs for a research swarm
12
+ * nookplot gpu provision — Interactive setup on current machine
13
+ * nookplot gpu setup-script — Generate a bash provisioning script
14
+ * nookplot gpu serve — Go online, send heartbeats, listen for jobs
10
15
  *
11
16
  * @module commands/gpu
12
17
  */
@@ -1,18 +1,25 @@
1
1
  /**
2
2
  * GPU marketplace CLI commands.
3
3
  *
4
- * nookplot gpu benchmark — Run standardized benchmark (detects GPU specs)
5
- * nookplot gpu register — Submit attestation on-chain
6
- * nookplot gpu update — Re-run benchmark + update attestation
7
- * nookplot gpu status — Show attestation, availability, active jobs
8
- * nookplot gpu list — Browse available GPUs
9
- * nookplot gpu challenge — Challenge a provider's benchmark
4
+ * nookplot gpu benchmark — Run standardized benchmark (detects GPU specs)
5
+ * nookplot gpu register — Submit attestation on-chain
6
+ * nookplot gpu update — Re-run benchmark + update attestation
7
+ * nookplot gpu status — Show attestation, availability, active jobs
8
+ * nookplot gpu list — Browse available GPUs
9
+ * nookplot gpu challenge — Challenge a provider's benchmark
10
+ * nookplot gpu rent — Rent a GPU by creating a service agreement
11
+ * nookplot gpu rent-swarm — Rent multiple GPUs for a research swarm
12
+ * nookplot gpu provision — Interactive setup on current machine
13
+ * nookplot gpu setup-script — Generate a bash provisioning script
14
+ * nookplot gpu serve — Go online, send heartbeats, listen for jobs
10
15
  *
11
16
  * @module commands/gpu
12
17
  */
13
18
  import chalk from "chalk";
14
19
  import ora from "ora";
20
+ import { writeFileSync } from "node:fs";
15
21
  import { ethers } from "ethers";
22
+ import inquirer from "inquirer";
16
23
  import { loadConfig, validateConfig } from "../config.js";
17
24
  import { gatewayRequest, isGatewayError } from "../utils/http.js";
18
25
  export function registerGpuCommand(program) {
@@ -135,6 +142,85 @@ export function registerGpuCommand(program) {
135
142
  }
136
143
  });
137
144
  // ============================================================
145
+ // nookplot gpu rent
146
+ // ============================================================
147
+ gpu
148
+ .command("rent <listingId>")
149
+ .description("Rent a GPU by creating a marketplace service agreement")
150
+ .option("--terms <string>", "Agreement terms")
151
+ .option("--duration <hours>", "Rental duration in hours", parseInt)
152
+ .option("--amount <usdc>", "Token amount to pay")
153
+ .option("--token <usdc|nook>", "Payment token (usdc or nook)")
154
+ .option("--json", "Output raw JSON")
155
+ .option("-y, --yes", "Skip confirmation prompt")
156
+ .action(async (listingId, opts) => {
157
+ try {
158
+ await runRent(program.opts(), listingId, opts);
159
+ }
160
+ catch (err) {
161
+ const msg = err instanceof Error ? err.message : String(err);
162
+ console.error(chalk.red(`\nRental failed: ${msg}`));
163
+ process.exit(1);
164
+ }
165
+ });
166
+ // ============================================================
167
+ // nookplot gpu setup-script
168
+ // ============================================================
169
+ gpu
170
+ .command("setup-script")
171
+ .description("Generate a bash setup script for GPU provisioning")
172
+ .option("--agreement-id <id>", "Service agreement ID")
173
+ .option("--output <file>", "Write script to file instead of stdout")
174
+ .action(async (opts) => {
175
+ try {
176
+ await runSetupScript(program.opts(), opts);
177
+ }
178
+ catch (err) {
179
+ const msg = err instanceof Error ? err.message : String(err);
180
+ console.error(chalk.red(`\nSetup script generation failed: ${msg}`));
181
+ process.exit(1);
182
+ }
183
+ });
184
+ // ============================================================
185
+ // nookplot gpu rent-swarm
186
+ // ============================================================
187
+ gpu
188
+ .command("rent-swarm")
189
+ .description("Rent multiple GPUs for a research swarm")
190
+ .option("--count <n>", "Number of GPUs to rent", parseInt)
191
+ .option("--min-vram <gb>", "Minimum VRAM in GB per GPU", parseInt)
192
+ .option("--gpu-model <model>", "Preferred GPU model (e.g. A100, H100)")
193
+ .option("--duration <hours>", "Rental duration in hours", parseInt)
194
+ .option("--json", "Output raw JSON")
195
+ .action(async (opts) => {
196
+ try {
197
+ await runRentSwarm(program.opts(), opts);
198
+ }
199
+ catch (err) {
200
+ const msg = err instanceof Error ? err.message : String(err);
201
+ console.error(chalk.red(`\nSwarm rental failed: ${msg}`));
202
+ process.exit(1);
203
+ }
204
+ });
205
+ // ============================================================
206
+ // nookplot gpu provision
207
+ // ============================================================
208
+ gpu
209
+ .command("provision")
210
+ .description("Interactive setup: detect GPU, install deps, configure autoresearch")
211
+ .option("--repo <path>", "Path to autoresearch git repo")
212
+ .option("--listing-id <id>", "GPU listing ID to validate against", parseInt)
213
+ .action(async (opts) => {
214
+ try {
215
+ await runProvision(program.opts(), opts);
216
+ }
217
+ catch (err) {
218
+ const msg = err instanceof Error ? err.message : String(err);
219
+ console.error(chalk.red(`\nProvisioning failed: ${msg}`));
220
+ process.exit(1);
221
+ }
222
+ });
223
+ // ============================================================
138
224
  // nookplot gpu serve
139
225
  // ============================================================
140
226
  gpu
@@ -546,4 +632,391 @@ async function runServe(globalOpts, cmdOpts) {
546
632
  // Keep process alive
547
633
  await new Promise(() => { });
548
634
  }
635
+ async function runRent(globalOpts, listingId, cmdOpts) {
636
+ const config = loadConfig({
637
+ configPath: globalOpts.config,
638
+ gatewayOverride: globalOpts.gateway,
639
+ apiKeyOverride: globalOpts.apiKey,
640
+ });
641
+ const errors = validateConfig(config);
642
+ if (errors.length > 0) {
643
+ for (const e of errors)
644
+ console.error(chalk.red(` \u2717 ${e}`));
645
+ process.exit(1);
646
+ }
647
+ if (!config.privateKey) {
648
+ console.error(chalk.red(" \u2717 Private key required. Set NOOKPLOT_AGENT_PRIVATE_KEY."));
649
+ process.exit(1);
650
+ }
651
+ const lid = parseInt(listingId, 10);
652
+ if (isNaN(lid) || lid <= 0) {
653
+ console.error(chalk.red(" \u2717 Invalid listing ID. Must be a positive integer."));
654
+ process.exit(1);
655
+ }
656
+ const durationHours = cmdOpts.duration ?? 24;
657
+ const tokenAmount = cmdOpts.amount ?? "0";
658
+ // Resolve token address
659
+ let tokenAddress;
660
+ if (cmdOpts.token === "nook") {
661
+ tokenAddress = "0xb233BDFFD437E60fA451F62c6c09D3804d285Ba3";
662
+ }
663
+ // Fetch listing details first
664
+ const spinner = ora("Fetching listing details...").start();
665
+ const listingRes = await gatewayRequest(config.gateway, "GET", `/v1/gpu/availability/${lid}`, { apiKey: config.apiKey });
666
+ if (isGatewayError(listingRes)) {
667
+ spinner.fail("Failed to fetch listing details");
668
+ console.error(chalk.red(` ${listingRes.error}`));
669
+ process.exit(1);
670
+ }
671
+ const listing = listingRes.data.availability;
672
+ spinner.stop();
673
+ // Show confirmation
674
+ console.log(chalk.bold("\n GPU Rental Summary\n"));
675
+ console.log(` Listing: #${lid}`);
676
+ console.log(` GPU: ${chalk.cyan(listing?.gpu_model || "Unknown")}`);
677
+ console.log(` VRAM: ${chalk.cyan((listing?.vram_gb ?? 0) + " GB")}`);
678
+ console.log(` Status: ${listing?.status === "online" ? chalk.green(listing.status) : chalk.yellow(listing?.status ?? "unknown")}`);
679
+ console.log(` Provider: ${chalk.dim(listing?.provider_address?.slice(0, 10) + "\u2026" || "unknown")}`);
680
+ console.log(` Duration: ${durationHours} hours`);
681
+ console.log(` Amount: ${tokenAmount} ${cmdOpts.token?.toUpperCase() ?? "USDC"}`);
682
+ console.log("");
683
+ if (!cmdOpts.yes) {
684
+ const { confirm } = await inquirer.prompt([{
685
+ type: "confirm",
686
+ name: "confirm",
687
+ message: "Proceed with GPU rental?",
688
+ default: false,
689
+ }]);
690
+ if (!confirm) {
691
+ console.log(chalk.dim("\n Rental cancelled.\n"));
692
+ return;
693
+ }
694
+ }
695
+ const wallet = new ethers.Wallet(config.privateKey);
696
+ const rentSpinner = ora("Creating GPU rental agreement on-chain...").start();
697
+ const deadline = Math.floor(Date.now() / 1000) + durationHours * 3600;
698
+ // Step 1: Prepare
699
+ const prepResult = await gatewayRequest(config.gateway, "POST", "/v1/prepare/service/agree", {
700
+ apiKey: config.apiKey,
701
+ body: {
702
+ listingId: lid,
703
+ terms: cmdOpts.terms ?? "GPU rental via Nookplot marketplace",
704
+ deadline: String(deadline),
705
+ tokenAmount,
706
+ tokenAddress,
707
+ },
708
+ });
709
+ if (isGatewayError(prepResult)) {
710
+ rentSpinner.fail("Failed to prepare rental agreement");
711
+ console.error(chalk.red(` ${prepResult.error}`));
712
+ process.exit(1);
713
+ }
714
+ // Step 2: Sign
715
+ const { forwardRequest, domain, types } = prepResult.data;
716
+ const sig = await wallet.signTypedData(domain, types, forwardRequest);
717
+ // Step 3: Relay
718
+ const relayResult = await gatewayRequest(config.gateway, "POST", "/v1/relay", { apiKey: config.apiKey, body: { ...forwardRequest, signature: sig } });
719
+ if (isGatewayError(relayResult)) {
720
+ rentSpinner.fail("Failed to relay rental agreement");
721
+ console.error(chalk.red(` ${relayResult.error}`));
722
+ process.exit(1);
723
+ }
724
+ if (cmdOpts.json) {
725
+ rentSpinner.stop();
726
+ console.log(JSON.stringify({
727
+ txHash: relayResult.data.txHash,
728
+ agreementId: relayResult.data.agreementId,
729
+ listingId: lid,
730
+ gpuModel: listing?.gpu_model,
731
+ durationHours,
732
+ }, null, 2));
733
+ return;
734
+ }
735
+ rentSpinner.succeed(chalk.green("GPU rental agreement created"));
736
+ console.log(` TX: ${relayResult.data.txHash}`);
737
+ if (relayResult.data.agreementId) {
738
+ console.log(` Agreement: #${relayResult.data.agreementId}`);
739
+ }
740
+ console.log(` GPU: ${listing?.gpu_model || "Unknown"}`);
741
+ console.log(` Duration: ${durationHours} hours`);
742
+ console.log("");
743
+ console.log(chalk.dim(" Use `nookplot gpu setup-script` to generate a provisioning script."));
744
+ console.log("");
745
+ }
746
+ async function runSetupScript(_globalOpts, cmdOpts) {
747
+ const script = `#!/usr/bin/env bash
748
+ # Nookplot GPU Provisioning Script
749
+ # Generated by: nookplot gpu setup-script
750
+ # ─────────────────────────────────────────────────────────────
751
+ set -euo pipefail
752
+
753
+ echo "=== Nookplot GPU Setup ==="
754
+ echo ""
755
+
756
+ # 1. Check Python version
757
+ PYTHON=""
758
+ for cmd in python3.12 python3.11 python3.10 python3; do
759
+ if command -v "$cmd" &> /dev/null; then
760
+ PYTHON="$cmd"
761
+ break
762
+ fi
763
+ done
764
+
765
+ if [ -z "$PYTHON" ]; then
766
+ echo "ERROR: Python 3.10+ is required. Install it first."
767
+ exit 1
768
+ fi
769
+
770
+ PY_VERSION=$($PYTHON -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
771
+ echo "Using $PYTHON (version $PY_VERSION)"
772
+
773
+ # 2. Check nvidia-smi
774
+ if ! command -v nvidia-smi &> /dev/null; then
775
+ echo "WARNING: nvidia-smi not found. GPU experiments may not work."
776
+ else
777
+ echo "GPU: $(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null || echo 'unknown')"
778
+ echo "VRAM: $(nvidia-smi --query-gpu=memory.total --format=csv,noheader 2>/dev/null || echo 'unknown')"
779
+ fi
780
+
781
+ # 3. Create virtualenv
782
+ VENV_DIR="\${HOME}/.nookplot/gpu-venv"
783
+ if [ ! -d "$VENV_DIR" ]; then
784
+ echo ""
785
+ echo "Creating virtualenv at $VENV_DIR..."
786
+ $PYTHON -m venv "$VENV_DIR"
787
+ fi
788
+ source "$VENV_DIR/bin/activate"
789
+
790
+ # 4. Install nookplot-autoresearch
791
+ echo ""
792
+ echo "Installing nookplot-autoresearch..."
793
+ pip install --upgrade pip > /dev/null 2>&1
794
+ pip install nookplot-autoresearch > /dev/null 2>&1 || {
795
+ echo "WARNING: nookplot-autoresearch not on PyPI yet."
796
+ echo "Install from source: pip install -e /path/to/integrations/autoresearch"
797
+ }
798
+
799
+ # 5. Copy credentials
800
+ CRED_DIR="\${HOME}/.nookplot"
801
+ mkdir -p "$CRED_DIR"
802
+
803
+ if [ -f "nookplot.yaml" ]; then
804
+ cp nookplot.yaml "$CRED_DIR/nookplot.yaml"
805
+ echo "Copied nookplot.yaml to $CRED_DIR"
806
+ fi
807
+
808
+ if [ -f ".env" ] && grep -q "NOOKPLOT_" ".env"; then
809
+ # Extract NOOKPLOT_ vars only
810
+ grep "^NOOKPLOT_" .env >> "$CRED_DIR/.env" 2>/dev/null || true
811
+ echo "Copied NOOKPLOT_ env vars to $CRED_DIR/.env"
812
+ fi
813
+
814
+ ${cmdOpts.agreementId ? `# 6. Record agreement ID
815
+ echo "${cmdOpts.agreementId}" > "$CRED_DIR/gpu-agreement-id"
816
+ echo "Agreement ID: ${cmdOpts.agreementId}"
817
+ ` : `# 6. Agreement ID (none specified)
818
+ # Pass --agreement-id <id> to nookplot gpu setup-script to auto-configure.
819
+ `}
820
+ # 7. Verify setup
821
+ echo ""
822
+ echo "=== Setup Complete ==="
823
+ echo ""
824
+ echo "Next steps:"
825
+ echo " 1. Activate the venv: source $VENV_DIR/bin/activate"
826
+ echo " 2. Run experiments: nookplot-autoresearch run --config research.yaml"
827
+ echo " 3. Monitor GPU: watch nvidia-smi"
828
+ echo ""
829
+ `;
830
+ if (cmdOpts.output) {
831
+ writeFileSync(cmdOpts.output, script, { mode: 0o755 });
832
+ console.log(chalk.green(`\n Setup script written to ${cmdOpts.output}`));
833
+ console.log(chalk.dim(` Run it with: bash ${cmdOpts.output}\n`));
834
+ }
835
+ else {
836
+ process.stdout.write(script);
837
+ }
838
+ }
839
+ async function runRentSwarm(globalOpts, cmdOpts) {
840
+ const config = loadConfig({
841
+ configPath: globalOpts.config,
842
+ gatewayOverride: globalOpts.gateway,
843
+ apiKeyOverride: globalOpts.apiKey,
844
+ });
845
+ const errors = validateConfig(config);
846
+ if (errors.length > 0) {
847
+ for (const e of errors)
848
+ console.error(chalk.red(` \u2717 ${e}`));
849
+ process.exit(1);
850
+ }
851
+ if (!config.privateKey) {
852
+ console.error(chalk.red(" \u2717 Private key required. Set NOOKPLOT_AGENT_PRIVATE_KEY."));
853
+ process.exit(1);
854
+ }
855
+ const count = cmdOpts.count ?? 3;
856
+ const durationHours = cmdOpts.duration ?? 24;
857
+ const wallet = new ethers.Wallet(config.privateKey);
858
+ // Search for available GPUs
859
+ const spinner = ora(`Searching for ${count} available GPUs...`).start();
860
+ const params = new URLSearchParams({ limit: String(count + 2), status: "online" });
861
+ if (cmdOpts.minVram)
862
+ params.set("minVram", String(cmdOpts.minVram));
863
+ if (cmdOpts.gpuModel)
864
+ params.set("gpuModel", cmdOpts.gpuModel);
865
+ const searchRes = await gatewayRequest(config.gateway, "GET", `/v1/gpu/availability?${params}`, { apiKey: config.apiKey });
866
+ if (isGatewayError(searchRes)) {
867
+ spinner.fail("Failed to search GPUs");
868
+ console.error(chalk.red(` ${searchRes.error}`));
869
+ process.exit(1);
870
+ }
871
+ const available = searchRes.data.gpus || [];
872
+ if (available.length < count) {
873
+ spinner.fail(`Only ${available.length} GPUs found (need ${count})`);
874
+ process.exit(1);
875
+ }
876
+ spinner.succeed(`Found ${available.length} available GPUs`);
877
+ // Rent each GPU
878
+ const agreements = [];
879
+ const deadline = Math.floor(Date.now() / 1000) + durationHours * 3600;
880
+ for (let i = 0; i < count; i++) {
881
+ const gpu = available[i];
882
+ const rentSpinner = ora(` Renting GPU #${gpu.listing_id} (${gpu.gpu_model}, ${gpu.vram_gb}GB)...`).start();
883
+ try {
884
+ const prepResult = await gatewayRequest(config.gateway, "POST", "/v1/prepare/service/agree", {
885
+ apiKey: config.apiKey,
886
+ body: {
887
+ listingId: gpu.listing_id,
888
+ terms: "GPU rental for autoresearch swarm via Nookplot",
889
+ deadline: String(deadline),
890
+ tokenAmount: "0",
891
+ },
892
+ });
893
+ if (isGatewayError(prepResult)) {
894
+ rentSpinner.fail(` GPU #${gpu.listing_id}: ${prepResult.error}`);
895
+ agreements.push({ listingId: gpu.listing_id, error: prepResult.error });
896
+ continue;
897
+ }
898
+ const { forwardRequest, domain, types } = prepResult.data;
899
+ const sig = await wallet.signTypedData(domain, types, forwardRequest);
900
+ const relayResult = await gatewayRequest(config.gateway, "POST", "/v1/relay", { apiKey: config.apiKey, body: { ...forwardRequest, signature: sig } });
901
+ if (isGatewayError(relayResult)) {
902
+ rentSpinner.fail(` GPU #${gpu.listing_id}: relay failed`);
903
+ agreements.push({ listingId: gpu.listing_id, error: relayResult.error });
904
+ continue;
905
+ }
906
+ rentSpinner.succeed(` GPU #${gpu.listing_id}: rented (tx: ${relayResult.data.txHash?.slice(0, 10)}...)`);
907
+ agreements.push({
908
+ listingId: gpu.listing_id,
909
+ txHash: relayResult.data.txHash,
910
+ agreementId: relayResult.data.agreementId,
911
+ });
912
+ }
913
+ catch (err) {
914
+ const msg = err instanceof Error ? err.message : String(err);
915
+ rentSpinner.fail(` GPU #${gpu.listing_id}: ${msg}`);
916
+ agreements.push({ listingId: gpu.listing_id, error: msg });
917
+ }
918
+ }
919
+ const succeeded = agreements.filter((a) => a.txHash);
920
+ const failed = agreements.filter((a) => a.error);
921
+ if (cmdOpts.json) {
922
+ console.log(JSON.stringify({ agreements, succeeded: succeeded.length, failed: failed.length }, null, 2));
923
+ return;
924
+ }
925
+ console.log(chalk.bold(`\n Swarm GPU Rental Summary\n`));
926
+ console.log(` Rented: ${chalk.green(String(succeeded.length))} / ${count}`);
927
+ if (failed.length > 0) {
928
+ console.log(` Failed: ${chalk.red(String(failed.length))}`);
929
+ }
930
+ console.log(` Duration: ${durationHours} hours`);
931
+ console.log("");
932
+ for (const a of succeeded) {
933
+ console.log(` #${a.listingId}: agreement ${a.agreementId ?? "pending"} (tx: ${a.txHash?.slice(0, 10)}...)`);
934
+ }
935
+ console.log("");
936
+ }
937
+ async function runProvision(globalOpts, cmdOpts) {
938
+ const config = loadConfig({
939
+ configPath: globalOpts.config,
940
+ gatewayOverride: globalOpts.gateway,
941
+ apiKeyOverride: globalOpts.apiKey,
942
+ });
943
+ console.log(chalk.bold("\n GPU Provision — Interactive Setup\n"));
944
+ // Step 1: Check Python
945
+ const { execSync } = await import("child_process");
946
+ let pythonCmd = "";
947
+ for (const cmd of ["python3.12", "python3.11", "python3.10", "python3"]) {
948
+ try {
949
+ execSync(`${cmd} --version`, { stdio: "pipe" });
950
+ pythonCmd = cmd;
951
+ break;
952
+ }
953
+ catch {
954
+ // try next
955
+ }
956
+ }
957
+ if (!pythonCmd) {
958
+ console.error(chalk.red(" \u2717 Python 3.10+ not found. Install it first."));
959
+ process.exit(1);
960
+ }
961
+ console.log(chalk.green(` \u2713 Python: ${pythonCmd}`));
962
+ // Step 2: Check nvidia-smi
963
+ try {
964
+ const gpuName = execSync("nvidia-smi --query-gpu=name --format=csv,noheader", { stdio: "pipe" }).toString().trim();
965
+ const gpuVram = execSync("nvidia-smi --query-gpu=memory.total --format=csv,noheader", { stdio: "pipe" }).toString().trim();
966
+ console.log(chalk.green(` \u2713 GPU: ${gpuName} (${gpuVram})`));
967
+ }
968
+ catch {
969
+ console.log(chalk.yellow(" \u26a0 nvidia-smi not found. GPU experiments may not work."));
970
+ }
971
+ // Step 3: Validate against listing if specified
972
+ if (cmdOpts.listingId) {
973
+ const valErrors = validateConfig(config);
974
+ if (valErrors.length === 0) {
975
+ const spinner = ora(" Validating GPU against listing...").start();
976
+ const listingRes = await gatewayRequest(config.gateway, "GET", `/v1/gpu/availability/${cmdOpts.listingId}`, { apiKey: config.apiKey });
977
+ if (isGatewayError(listingRes)) {
978
+ spinner.warn(` Could not validate listing: ${listingRes.error}`);
979
+ }
980
+ else {
981
+ const listing = listingRes.data.availability;
982
+ spinner.succeed(` Listing #${cmdOpts.listingId}: ${listing?.gpu_model || "Unknown"} (${listing?.vram_gb ?? 0}GB, ${listing?.status || "unknown"})`);
983
+ }
984
+ }
985
+ }
986
+ // Step 4: Check autoresearch repo
987
+ if (cmdOpts.repo) {
988
+ const { existsSync } = await import("fs");
989
+ if (!existsSync(cmdOpts.repo)) {
990
+ console.error(chalk.red(` \u2717 Repo path not found: ${cmdOpts.repo}`));
991
+ process.exit(1);
992
+ }
993
+ if (!existsSync(`${cmdOpts.repo}/train.py`)) {
994
+ console.log(chalk.yellow(` \u26a0 No train.py found in ${cmdOpts.repo} — may not be an autoresearch repo`));
995
+ }
996
+ else {
997
+ console.log(chalk.green(` \u2713 Autoresearch repo: ${cmdOpts.repo}`));
998
+ }
999
+ }
1000
+ // Step 5: Check pip install
1001
+ try {
1002
+ execSync(`${pythonCmd} -c "import nookplot_autoresearch"`, { stdio: "pipe" });
1003
+ console.log(chalk.green(" \u2713 nookplot-autoresearch: installed"));
1004
+ }
1005
+ catch {
1006
+ console.log(chalk.yellow(" \u26a0 nookplot-autoresearch not installed"));
1007
+ console.log(chalk.dim(" Install: pip install nookplot-autoresearch"));
1008
+ console.log(chalk.dim(" Or from source: pip install -e /path/to/integrations/autoresearch"));
1009
+ }
1010
+ // Summary
1011
+ console.log(chalk.bold("\n Next Steps:\n"));
1012
+ console.log(" 1. Install nookplot-autoresearch if not already installed");
1013
+ console.log(" 2. Set environment variables: NOOKPLOT_API_KEY, AGENT_PRIVATE_KEY");
1014
+ if (cmdOpts.repo) {
1015
+ console.log(` 3. Run: nookplot-autoresearch watch ${cmdOpts.repo}`);
1016
+ }
1017
+ else {
1018
+ console.log(" 3. Run: nookplot-autoresearch watch /path/to/autoresearch-repo");
1019
+ }
1020
+ console.log("");
1021
+ }
549
1022
  //# sourceMappingURL=gpu.js.map