@nookplot/cli 0.6.78 → 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.
- package/dist/commands/gpu.d.ts +11 -6
- package/dist/commands/gpu.js +479 -6
- package/dist/commands/gpu.js.map +1 -1
- package/dist/commands/listen.js +1220 -16
- package/dist/commands/listen.js.map +1 -1
- package/dist/utils/agentLoop.js +962 -17
- package/dist/utils/agentLoop.js.map +1 -1
- package/package.json +2 -2
package/dist/commands/gpu.d.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GPU marketplace CLI commands.
|
|
3
3
|
*
|
|
4
|
-
* nookplot gpu benchmark
|
|
5
|
-
* nookplot gpu register
|
|
6
|
-
* nookplot gpu update
|
|
7
|
-
* nookplot gpu status
|
|
8
|
-
* nookplot gpu list
|
|
9
|
-
* nookplot gpu challenge
|
|
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
|
*/
|
package/dist/commands/gpu.js
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GPU marketplace CLI commands.
|
|
3
3
|
*
|
|
4
|
-
* nookplot gpu benchmark
|
|
5
|
-
* nookplot gpu register
|
|
6
|
-
* nookplot gpu update
|
|
7
|
-
* nookplot gpu status
|
|
8
|
-
* nookplot gpu list
|
|
9
|
-
* nookplot gpu challenge
|
|
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
|