lightnode-sdk 0.7.19 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -484,11 +484,15 @@ npx lightnode add judge-web3 # evaluator UI, wallet-signed
484
484
  npx lightnode add wagmi-setup # wallet wiring: lib/wagmi + providers + connect button
485
485
  ```
486
486
 
487
- The `*-web3` scaffolders and `wagmi-setup` write Next.js pages, so run them
488
- inside a Next.js app (`npx create-next-app@latest .` first if you have none).
487
+ The `*-web3` scaffolders are one command end to end: run in an empty folder and
488
+ they scaffold a Next.js app, write the page with a wired Connect button, bundle
489
+ the wagmi config + providers + connect button, wrap your layout with
490
+ `<Providers>`, and `npm install` the deps. Run inside an existing Next.js app
491
+ and they skip the scaffold and just add what's missing. Opt out of the
492
+ automation with `--no-scaffold` and `--no-install`.
489
493
 
490
494
  All `add` commands accept `--template auto|nextjs-api|hono|node`,
491
- `--net testnet|mainnet`, and `--force`.
495
+ `--net testnet|mainnet`, `--force`, `--no-install`, and `--no-scaffold`.
492
496
 
493
497
  > If `add <name>` reports an unknown target, your `npx` cache is serving an
494
498
  > older CLI. Force the current release: `npx lightnode-sdk@latest add <name>`.
package/dist/add.d.ts CHANGED
@@ -109,6 +109,16 @@ export declare function addWagmiSetup(opts?: AddOpts): {
109
109
  template: Template;
110
110
  network: Network;
111
111
  };
112
+ export interface LayoutPatch {
113
+ path: string;
114
+ patched: boolean;
115
+ reason?: string;
116
+ }
117
+ /**
118
+ * Patch the project's root layout to import and wrap children in <Providers>.
119
+ * Returns what happened so the CLI can report it; never throws.
120
+ */
121
+ export declare function patchLayoutWithProviders(cwd?: string): LayoutPatch;
112
122
  export declare function addJudge(opts?: AddOpts): {
113
123
  written: WrittenFile[];
114
124
  install: string;
package/dist/add.js CHANGED
@@ -321,6 +321,17 @@ function depsNeeded(template) {
321
321
  // installed too, otherwise `tsx ...` fails with "command not found".
322
322
  return ["lightnode-sdk", "viem", "ws", "tsx"];
323
323
  }
324
+ /** Full `npm install` line for a template's next-steps, including the dev type
325
+ * packages an editor needs. The node/script template uses Node builtins
326
+ * (`node:process`, `node:readline`) and imports `ws`, so without @types/node
327
+ * and @types/ws a freshly-scaffolded file is a wall of red squiggles in any
328
+ * TypeScript-aware editor even though `tsx` runs it fine. */
329
+ function installLine(template) {
330
+ const runtime = `npm install ${depsNeeded(template).join(" ")}`;
331
+ if (template === "node")
332
+ return `${runtime} && npm install -D @types/node @types/ws`;
333
+ return runtime;
334
+ }
324
335
  /**
325
336
  * Implementation called by `lightnode add inference [...]`.
326
337
  * Returns the list of files written + the install command the user should run.
@@ -347,7 +358,7 @@ export function addInference(opts = {}) {
347
358
  written.push(writeFile(path.join(cwd, ".env.example"), ENV_EXAMPLE(network), force));
348
359
  return {
349
360
  written,
350
- install: `npm install ${depsNeeded(template).join(" ")}`,
361
+ install: installLine(template),
351
362
  template,
352
363
  network,
353
364
  };
@@ -953,6 +964,7 @@ const NEXTJS_CHAT_WEB3_PAGE = `// app/chat-web3/page.tsx
953
964
  import { useEffect, useState } from "react";
954
965
  import { useAccount, useWalletClient, usePublicClient } from "wagmi";
955
966
  import { siweSignIn, GatewayClient, runInference, estimateJobFee, NETWORKS } from "lightnode-sdk";
967
+ import { ConnectButton } from "@/components/connect-button";
956
968
 
957
969
  type Turn = {
958
970
  role: "user" | "assistant";
@@ -1063,16 +1075,19 @@ export default function ChatWeb3() {
1063
1075
 
1064
1076
  return (
1065
1077
  <main style={{ maxWidth: 720, margin: "32px auto", padding: 16, fontFamily: "system-ui" }}>
1066
- <h1>Chat (user-pays)</h1>
1078
+ <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }}>
1079
+ <h1>Chat (user-pays)</h1>
1080
+ <ConnectButton />
1081
+ </div>
1067
1082
  <p style={{ color: "#666" }}>
1068
1083
  Each turn signs one createSession transaction from your wallet on{" "}
1069
1084
  <code>{network ?? "(connect a wallet)"}</code>. Fee:{" "}
1070
1085
  <code>{feeLcai != null ? \`\${feeLcai} LCAI\` : "(fetching)"}</code> per turn plus a small gas amount.
1071
1086
  </p>
1072
1087
  {!address && (
1073
- <div style={{ border: "1px solid #ddd", borderRadius: 8, padding: 12, margin: "12px 0" }}>
1074
- Connect a wallet to start chatting. (Use whichever connector your app exposes - e.g. RainbowKit,
1075
- ConnectKit, Reown AppKit, or wagmi&apos;s useConnect directly.)
1088
+ <div style={{ border: "1px solid #ddd", borderRadius: 8, padding: 12, margin: "12px 0", display: "flex", alignItems: "center", gap: 12 }}>
1089
+ <span>Connect a wallet to start chatting.</span>
1090
+ <ConnectButton />
1076
1091
  </div>
1077
1092
  )}
1078
1093
 
@@ -1155,6 +1170,7 @@ const NEXTJS_INFERENCE_WEB3_PAGE = `// app/inference-web3/page.tsx
1155
1170
  import { useEffect, useState } from "react";
1156
1171
  import { useAccount, useWalletClient, usePublicClient } from "wagmi";
1157
1172
  import { siweSignIn, GatewayClient, runInference, estimateJobFee, NETWORKS } from "lightnode-sdk";
1173
+ import { ConnectButton } from "@/components/connect-button";
1158
1174
 
1159
1175
  type Result = {
1160
1176
  answer: string;
@@ -1243,15 +1259,19 @@ export default function InferenceWeb3() {
1243
1259
 
1244
1260
  return (
1245
1261
  <main style={{ maxWidth: 720, margin: "32px auto", padding: 16, fontFamily: "system-ui" }}>
1246
- <h1>Inference (user-pays)</h1>
1262
+ <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }}>
1263
+ <h1>Inference (user-pays)</h1>
1264
+ <ConnectButton />
1265
+ </div>
1247
1266
  <p style={{ color: "#666" }}>
1248
1267
  Signs one encrypted inference from your wallet on{" "}
1249
1268
  <code>{network ?? "(connect a wallet)"}</code>. Fee:{" "}
1250
1269
  <code>{feeLcai != null ? \`\${feeLcai} LCAI\` : "(fetching)"}</code> per call plus a small gas amount.
1251
1270
  </p>
1252
1271
  {!address && (
1253
- <div style={{ border: "1px solid #ddd", borderRadius: 8, padding: 12, margin: "12px 0" }}>
1254
- Connect a wallet to run inference. Drop &lt;ConnectButton /&gt; here or wherever your app exposes one.
1272
+ <div style={{ border: "1px solid #ddd", borderRadius: 8, padding: 12, margin: "12px 0", display: "flex", alignItems: "center", gap: 12 }}>
1273
+ <span>Connect a wallet to run inference.</span>
1274
+ <ConnectButton />
1255
1275
  </div>
1256
1276
  )}
1257
1277
 
@@ -1310,6 +1330,7 @@ const NEXTJS_JUDGE_WEB3_PAGE = `// app/judge-web3/page.tsx
1310
1330
  import { useEffect, useState } from "react";
1311
1331
  import { useAccount, useWalletClient, usePublicClient } from "wagmi";
1312
1332
  import { siweSignIn, GatewayClient, runInference, estimateJobFee, NETWORKS } from "lightnode-sdk";
1333
+ import { ConnectButton } from "@/components/connect-button";
1313
1334
 
1314
1335
  type Verdict = {
1315
1336
  passed: boolean;
@@ -1423,15 +1444,19 @@ Reply with STRICT JSON only, matching: { "passed": boolean, "confidence": 0-1, "
1423
1444
 
1424
1445
  return (
1425
1446
  <main style={{ maxWidth: 720, margin: "32px auto", padding: 16, fontFamily: "system-ui" }}>
1426
- <h1>AI Judge (user-pays)</h1>
1447
+ <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }}>
1448
+ <h1>AI Judge (user-pays)</h1>
1449
+ <ConnectButton />
1450
+ </div>
1427
1451
  <p style={{ color: "#666" }}>
1428
1452
  Each submission signs one inference from your wallet on{" "}
1429
1453
  <code>{network ?? "(connect a wallet)"}</code>. Cost:{" "}
1430
1454
  <code>{feeLcai != null ? \`\${feeLcai} LCAI\` : "(fetching)"}</code> plus gas. Verdict comes back with on-chain proof.
1431
1455
  </p>
1432
1456
  {!address && (
1433
- <div style={{ border: "1px solid #ddd", borderRadius: 8, padding: 12, margin: "12px 0" }}>
1434
- Connect a wallet to submit. Drop &lt;ConnectButton /&gt; here or wherever your app exposes one.
1457
+ <div style={{ border: "1px solid #ddd", borderRadius: 8, padding: 12, margin: "12px 0", display: "flex", alignItems: "center", gap: 12 }}>
1458
+ <span>Connect a wallet to submit.</span>
1459
+ <ConnectButton />
1435
1460
  </div>
1436
1461
  )}
1437
1462
 
@@ -1708,7 +1733,7 @@ export function addAgent(opts = {}) {
1708
1733
  written.push(writeFile(path.join(cwd, "agent.ts"), NODE_AGENT_SCRIPT, force));
1709
1734
  }
1710
1735
  written.push(writeFile(path.join(cwd, ".env.example"), ENV_EXAMPLE(network), force));
1711
- return { written, install: `npm install ${depsNeeded(template).join(" ")}`, template, network };
1736
+ return { written, install: installLine(template), template, network };
1712
1737
  }
1713
1738
  export function addChat(opts = {}) {
1714
1739
  const cwd = opts.cwd ?? process.cwd();
@@ -1732,7 +1757,7 @@ export function addChat(opts = {}) {
1732
1757
  written.push(writeFile(path.join(cwd, "chat-repl.ts"), NODE_CHAT_REPL, force));
1733
1758
  }
1734
1759
  written.push(writeFile(path.join(cwd, ".env.example"), ENV_EXAMPLE(network), force));
1735
- return { written, install: `npm install ${depsNeeded(template).join(" ")}`, template, network };
1760
+ return { written, install: installLine(template), template, network };
1736
1761
  }
1737
1762
  /**
1738
1763
  * `lightnode add chat-web3` - the user-pays counterpart to addChat.
@@ -1972,6 +1997,67 @@ export function addWagmiSetup(opts = {}) {
1972
1997
  network,
1973
1998
  };
1974
1999
  }
2000
+ function findLayoutFile(cwd) {
2001
+ const candidates = ["app/layout.tsx", "app/layout.jsx", "src/app/layout.tsx", "src/app/layout.jsx"];
2002
+ for (const rel of candidates) {
2003
+ const abs = path.join(cwd, rel);
2004
+ if (fs.existsSync(abs))
2005
+ return abs;
2006
+ }
2007
+ return null;
2008
+ }
2009
+ function withProvidersImport(source) {
2010
+ if (/from\s+["']\.\/providers["']/.test(source))
2011
+ return source;
2012
+ const importLine = `import { Providers } from "./providers";`;
2013
+ const lines = source.split("\n");
2014
+ let lastImport = -1;
2015
+ for (let i = 0; i < lines.length; i++) {
2016
+ if (/^\s*import\s/.test(lines[i]))
2017
+ lastImport = i;
2018
+ }
2019
+ if (lastImport === -1)
2020
+ return `${importLine}\n${source}`;
2021
+ return [...lines.slice(0, lastImport + 1), importLine, ...lines.slice(lastImport + 1)].join("\n");
2022
+ }
2023
+ function withWrappedChildren(source) {
2024
+ if (/<Providers>/.test(source))
2025
+ return source;
2026
+ if (!source.includes("{children}"))
2027
+ return null;
2028
+ return source.replace("{children}", "<Providers>{children}</Providers>");
2029
+ }
2030
+ /**
2031
+ * Patch the project's root layout to import and wrap children in <Providers>.
2032
+ * Returns what happened so the CLI can report it; never throws.
2033
+ */
2034
+ export function patchLayoutWithProviders(cwd = process.cwd()) {
2035
+ const abs = findLayoutFile(cwd);
2036
+ if (!abs)
2037
+ return { path: "app/layout.tsx", patched: false, reason: "no layout file found" };
2038
+ const rel = path.relative(cwd, abs) || abs;
2039
+ let source;
2040
+ try {
2041
+ source = fs.readFileSync(abs, "utf8");
2042
+ }
2043
+ catch (e) {
2044
+ return { path: rel, patched: false, reason: `could not read layout (${e.message})` };
2045
+ }
2046
+ if (/<Providers>/.test(source) && /from\s+["']\.\/providers["']/.test(source)) {
2047
+ return { path: rel, patched: false, reason: "already wrapped with <Providers>" };
2048
+ }
2049
+ const wrapped = withWrappedChildren(withProvidersImport(source));
2050
+ if (wrapped === null) {
2051
+ return { path: rel, patched: false, reason: "no {children} found - wrap with <Providers> manually" };
2052
+ }
2053
+ try {
2054
+ fs.writeFileSync(abs, wrapped);
2055
+ }
2056
+ catch (e) {
2057
+ return { path: rel, patched: false, reason: `could not write layout (${e.message})` };
2058
+ }
2059
+ return { path: rel, patched: true };
2060
+ }
1975
2061
  const NEXTJS_JUDGE_ROUTE = `// app/api/judge/route.ts
1976
2062
  // Generated by 'lightnode add judge'. See https://lightnode.app/build
1977
2063
  // The LightChallenge-style evaluator: post evidence + criteria, get a
@@ -2114,7 +2200,7 @@ export function addJudge(opts = {}) {
2114
2200
  written.push(writeFile(path.join(cwd, "judge.ts"), NODE_JUDGE_SCRIPT, force));
2115
2201
  }
2116
2202
  written.push(writeFile(path.join(cwd, ".env.example"), ENV_EXAMPLE(network), force));
2117
- return { written, install: `npm install ${depsNeeded(template).join(" ")}`, template, network };
2203
+ return { written, install: installLine(template), template, network };
2118
2204
  }
2119
2205
  export function addNftMint(opts = {}) {
2120
2206
  const cwd = opts.cwd ?? process.cwd();
@@ -2132,7 +2218,7 @@ export function addNftMint(opts = {}) {
2132
2218
  written.push(writeFile(path.join(cwd, ".env.example"), ENV_EXAMPLE(network), force));
2133
2219
  return {
2134
2220
  written,
2135
- install: `npm install ${depsNeeded(template).join(" ")}`,
2221
+ install: installLine(template),
2136
2222
  template,
2137
2223
  network,
2138
2224
  };
package/dist/cli.js CHANGED
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { LightNode, modelStatsCsv, workerStatsCsv, workerJobsCsv, runInferenceWithKey, runInferenceBatch, Agent, isStalledWorker, workerPreflight, workerWatch, WorkerOperator, isWorkerOpError, BRIDGE_ROUTE, DAO, DAO_ADDRESSES } from "./index.js";
3
- import { addInference, addInferenceWeb3, addJudgeWeb3, addAnalyticsDashboard, addNftMint, addChat, addChatWeb3, addAgent, addJudge, addWagmiSetup } from "./add.js";
2
+ import { LightNode, modelStatsCsv, workerStatsCsv, workerJobsCsv, runInferenceWithKey, runInferenceBatch, Agent, isStalledWorker, workerPreflight, workerWatch, WorkerOperator, isWorkerOpError, BRIDGE_ROUTE, DAO, DAO_ADDRESSES, SDK_VERSION } from "./index.js";
3
+ import { addInference, addInferenceWeb3, addJudgeWeb3, addAnalyticsDashboard, addNftMint, addChat, addChatWeb3, addAgent, addJudge, addWagmiSetup, patchLayoutWithProviders } from "./add.js";
4
4
  import { createPublicClient, createWalletClient, http, parseEther } from "viem";
5
5
  import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
6
+ import { existsSync, readdirSync, renameSync, rmSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { spawnSync } from "node:child_process";
6
9
  function flag(name) {
7
10
  const i = process.argv.indexOf(name);
8
11
  return i >= 0 ? process.argv[i + 1] : undefined;
@@ -17,6 +20,71 @@ function die(msg) {
17
20
  }
18
21
  const lcai = (wei) => (wei ? Number(BigInt(wei)) / 1e18 : 0);
19
22
  const rate = (r) => (r == null ? "-" : `${Math.round(r * 100)}%`);
23
+ // `add` targets that are always a Next.js client page. In a bare folder these
24
+ // get a real Next.js app scaffolded first so the generated page can render.
25
+ const NEXT_PAGE_TARGETS = new Set(["chat-web3", "inference-web3", "judge-web3", "wagmi-setup"]);
26
+ function printWritten(files) {
27
+ for (const f of files) {
28
+ if (f.skipped)
29
+ console.log(` ⤴ ${f.path} (skipped - ${f.reason})`);
30
+ else
31
+ console.log(` ✓ ${f.path}`);
32
+ }
33
+ }
34
+ /**
35
+ * Scaffold a Next.js app into cwd via create-next-app. Returns true on success.
36
+ *
37
+ * We scaffold into a fixed-name subfolder and move the files up rather than
38
+ * passing ".", because create-next-app derives the project name from the
39
+ * target and rejects names npm won't allow (capital letters, leading dots,
40
+ * etc.) - which would make this fail in any folder like "MyApp". The subfolder
41
+ * name is one we control, so that validation always passes.
42
+ */
43
+ function scaffoldNextApp(cwd, target) {
44
+ console.log(`\nNo Next.js app here yet - scaffolding one with create-next-app...\n`);
45
+ const stageName = "lightnode-next-app-stage";
46
+ const args = [
47
+ "--yes", "create-next-app@latest", stageName,
48
+ "--ts", "--app", "--no-src-dir", "--eslint", "--tailwind",
49
+ "--use-npm", "--no-turbopack", "--import-alias", "@/*",
50
+ ];
51
+ const r = spawnSync("npx", args, { cwd, stdio: "inherit" });
52
+ if (r.status !== 0) {
53
+ console.error(`\ncreate-next-app did not complete (exit ${r.status ?? "?"}).`);
54
+ console.error(`If this folder already had files, scaffold manually then re-run:`);
55
+ console.error(` npx create-next-app@latest . && npx lightnode-sdk@latest add ${target}`);
56
+ return false;
57
+ }
58
+ return relocateScaffold(join(cwd, stageName), cwd, target);
59
+ }
60
+ /** Move every entry from the staged scaffold dir up into cwd, then remove it. */
61
+ function relocateScaffold(from, cwd, target) {
62
+ try {
63
+ for (const entry of readdirSync(from)) {
64
+ const dest = join(cwd, entry);
65
+ if (existsSync(dest))
66
+ continue; // never clobber files the user already had
67
+ renameSync(join(from, entry), dest);
68
+ }
69
+ rmSync(from, { recursive: true, force: true });
70
+ return true;
71
+ }
72
+ catch (e) {
73
+ console.error(`\nScaffolded into ${from} but could not move it up (${e.message}).`);
74
+ console.error(`Move its contents into this folder, then re-run: npx lightnode-sdk@latest add ${target}`);
75
+ return false;
76
+ }
77
+ }
78
+ /** Run an `npm install ...` line in cwd. The line is an internal constant
79
+ * (never user input), so running it through a shell is safe here. */
80
+ function installDeps(installLine, cwd) {
81
+ console.log(`\nInstalling dependencies: ${installLine}\n`);
82
+ const r = spawnSync(installLine, { cwd, stdio: "inherit", shell: true });
83
+ if (r.status === 0)
84
+ return true;
85
+ console.error(`\nDependency install failed (exit ${r.status ?? "?"}). Run it yourself:\n ${installLine}`);
86
+ return false;
87
+ }
20
88
  const HELP = `lightnode <command> [--net mainnet|testnet]
21
89
 
22
90
  Run one inference (needs PRIVATE_KEY in env):
@@ -77,7 +145,12 @@ Scaffold templates into the current project (run inside a Next.js app):
77
145
  add wagmi-setup wallet wiring: lib/wagmi + providers + connect button
78
146
  (all add commands: [--template auto|nextjs-api|hono|node] [--net testnet|mainnet] [--force])
79
147
 
80
- To scaffold a new project instead, run: npm create lightnode-app my-app`;
148
+ To scaffold a new project instead, run: npm create lightnode-app my-app
149
+
150
+ Diagnostics:
151
+ version print this CLI's version (also: --version, -v)
152
+ (a missing 'add' target usually means an old install -
153
+ update with: npm install -g lightnode-sdk@latest)`;
81
154
  function pickKey() {
82
155
  const k = flag("--key") ?? process.env.PRIVATE_KEY;
83
156
  if (!k || !k.startsWith("0x") || k.length !== 66) {
@@ -120,6 +193,14 @@ async function workerJobIds(n, address) {
120
193
  return jobs.map((j) => Number(j.id)).filter((x) => Number.isFinite(x));
121
194
  }
122
195
  async function main() {
196
+ // Answer `version` / `--version` / `-v` before anything else so a user who
197
+ // suspects they're on a stale binary can confirm it without a network call
198
+ // or a funded key. This is the first thing to check when an `add` target
199
+ // "doesn't exist" - an old global install is the common cause.
200
+ if (cmd === "version" || process.argv.includes("--version") || process.argv.includes("-v")) {
201
+ console.log(SDK_VERSION);
202
+ return;
203
+ }
123
204
  const ln = new LightNode(net);
124
205
  switch (cmd) {
125
206
  case "chat": {
@@ -474,16 +555,34 @@ async function main() {
474
555
  const lines = [
475
556
  `usage: lightnode add <${known.join("|")}> [--template auto|nextjs-api|hono|node] [--net testnet|mainnet] [--force]`,
476
557
  ];
477
- // If the requested name is valid in a newer release but missing here,
478
- // the user is almost certainly running a stale npx-cached CLI. Point
479
- // them at @latest rather than letting them think the command is gone.
558
+ // A target that's missing here but valid in a newer release means the
559
+ // user is running an OLD lightnode-sdk. The usual cause is an outdated
560
+ // GLOBAL install (`npm i -g lightnode-sdk`) on PATH, which npx prefers
561
+ // over the registry - so `npx lightnode-sdk add ...` keeps hitting the
562
+ // stale binary. We can't know the latest version offline, but we can
563
+ // show what THIS binary is and the two commands that fix it. Listing
564
+ // the global update first because that's the one most people miss.
480
565
  if (sub) {
481
566
  lines.push("");
482
- lines.push(`unknown add target "${sub}". If you expected this to work, you may be on an`);
483
- lines.push(`older cached CLI. Force the current version: npx lightnode-sdk@latest add ${sub}`);
567
+ lines.push(`unknown add target "${sub}" - this CLI is lightnode-sdk v${SDK_VERSION}, which`);
568
+ lines.push(`does not have it. You're likely on an older install. Update, then retry:`);
569
+ lines.push(` npm install -g lightnode-sdk@latest # if 'lightnode' is on your PATH`);
570
+ lines.push(` npx lightnode-sdk@latest add ${sub} # or force the latest for one run`);
484
571
  }
485
572
  die(lines.join("\n"));
486
573
  }
574
+ // ---- one-command setup: flags + optional scaffold ----
575
+ const noInstall = process.argv.includes("--no-install");
576
+ const noScaffold = process.argv.includes("--no-scaffold");
577
+ const cwd = process.cwd();
578
+ const isWeb3Page = sub === "chat-web3" || sub === "inference-web3" || sub === "judge-web3";
579
+ // A Next.js client page needs a Next.js app to live in. In a bare folder,
580
+ // scaffold one first so the generated page renders instead of throwing
581
+ // "Cannot find module 'react'". Opt out with --no-scaffold.
582
+ if (NEXT_PAGE_TARGETS.has(sub ?? "") && !existsSync(join(cwd, "package.json")) && !noScaffold) {
583
+ scaffoldNextApp(cwd, sub ?? "");
584
+ }
585
+ // ---- write the requested files ----
487
586
  const result = sub === "analytics-dashboard" ? addAnalyticsDashboard({ template, network, force })
488
587
  : sub === "nft-mint-with-inference" ? addNftMint({ template, network, force })
489
588
  : sub === "chat-web3" ? addChatWeb3({ template, network, force })
@@ -495,61 +594,53 @@ async function main() {
495
594
  : sub === "judge" ? addJudge({ template, network, force })
496
595
  : addInference({ template, network, force });
497
596
  console.log(`▶ add ${sub} (${result.template} template, default network ${result.network})`);
498
- for (const f of result.written) {
499
- if (f.skipped)
500
- console.log(` ⤴ ${f.path} (skipped - ${f.reason})`);
597
+ printWritten(result.written);
598
+ // ---- web3 pages: bundle the wagmi wiring + wrap the root layout so the
599
+ // page's <ConnectButton /> and wagmi hooks have a provider to resolve. ----
600
+ let layout = null;
601
+ if (isWeb3Page) {
602
+ const wagmi = addWagmiSetup({ template: result.template, network, force });
603
+ printWritten(wagmi.written);
604
+ }
605
+ if (isWeb3Page || sub === "wagmi-setup") {
606
+ layout = patchLayoutWithProviders(cwd);
607
+ if (layout.patched)
608
+ console.log(` ✓ ${layout.path} (wrapped children with <Providers>)`);
501
609
  else
502
- console.log(` ${f.path}`);
503
- }
504
- const anyWritten = result.written.some((f) => !f.skipped);
505
- if (!anyWritten) {
506
- console.log("\nNothing to do - all target files already exist. Pass --force to overwrite.");
507
- }
508
- else {
509
- // The *-web3 pages and wagmi-setup are Next.js React files. If no
510
- // Next.js app was detected (e.g. an empty folder), nothing can render
511
- // what we just wrote - surface that before the numbered steps so the
512
- // user scaffolds an app first instead of chasing a non-running page.
513
- const isNextOnly = sub === "chat-web3" || sub === "inference-web3" || sub === "judge-web3" || sub === "wagmi-setup";
514
- if (isNextOnly && result.template !== "nextjs-api") {
515
- console.log(`\nNo Next.js app detected in this folder. ${sub} is a Next.js page, so`);
516
- console.log(`create one here first, then re-run this command:`);
517
- console.log(` npx create-next-app@latest .`);
610
+ console.log(` ${layout.path} (${layout.reason})`);
611
+ }
612
+ // ---- install dependencies (opt out with --no-install) ----
613
+ const installed = noInstall ? false : installDeps(result.install, cwd);
614
+ // ---- next steps ----
615
+ const layoutNeedsManual = layout != null && !layout.patched && !/already/.test(layout.reason ?? "");
616
+ if (isWeb3Page) {
617
+ const route = sub === "chat-web3" ? "/chat-web3" : sub === "inference-web3" ? "/inference-web3" : "/judge-web3";
618
+ const chainId = result.network === "mainnet" ? "9200" : "8200";
619
+ console.log(`\n${installed ? "✓ Done - deps installed, wagmi + layout wired. Just run it:" : "Files written. Next:"}`);
620
+ if (!installed)
621
+ console.log(` ${result.install}`);
622
+ console.log(` npm run dev`);
623
+ console.log(` open http://localhost:3000${route} and click Connect wallet (chainId ${chainId})`);
624
+ console.log(` ${result.network === "mainnet" ? "llama3-8b costs 0.02 LCAI per call" : "testnet is free"}`);
625
+ if (layoutNeedsManual) {
626
+ console.log(`\nHeads up: couldn't auto-wire the layout (${layout?.reason}).`);
627
+ console.log(`Wrap {children} with <Providers> in app/layout.tsx (import from "./providers").`);
518
628
  }
519
- console.log(`\nNext steps (these files were added to your CURRENT folder, not a new project):`);
520
- console.log(` 1. ${result.install}`);
521
- if (sub === "wagmi-setup") {
522
- console.log(` 2. Import Providers in app/layout.tsx and wrap children:`);
523
- console.log(` import { Providers } from "./providers";`);
524
- console.log(` // <body><Providers>{children}</Providers></body>`);
525
- console.log(` 3. Drop <ConnectButton /> anywhere you want a connect UI:`);
526
- console.log(` import { ConnectButton } from "@/components/connect-button";`);
527
- console.log(` 4. You can now use any wagmi hook (useAccount, useWalletClient, ...).`);
528
- console.log(` Wallets on chains other than 9200/8200 will be prompted to switch.`);
629
+ console.log(`\n No server-side route - deploy static (Vercel/Netlify/Cloudflare free tier all work).`);
630
+ }
631
+ else if (sub === "wagmi-setup") {
632
+ console.log(`\n${installed ? "✓ Done - deps installed and layout wired." : "Files written. Run: " + result.install}`);
633
+ console.log(`\nUse it: import { ConnectButton } from "@/components/connect-button"; drop <ConnectButton /> anywhere.`);
634
+ console.log(`Any wagmi hook (useAccount, useWalletClient, ...) now works app-wide. Off-network wallets get a switch prompt.`);
635
+ if (layoutNeedsManual) {
636
+ console.log(`\nHeads up: couldn't auto-wire the layout (${layout?.reason}). Wrap {children} with <Providers> in app/layout.tsx.`);
529
637
  }
530
- else if (sub === "chat-web3" || sub === "inference-web3" || sub === "judge-web3") {
531
- // *-web3 variants have no PRIVATE_KEY (each visitor pays their own way).
532
- const needsWagmi = result.needsWagmi;
533
- const route = sub === "chat-web3" ? "/chat-web3"
534
- : sub === "inference-web3" ? "/inference-web3"
535
- : "/judge-web3";
536
- if (needsWagmi) {
537
- console.log(` 2. Get wagmi wired up with one command:`);
538
- console.log(` npx lightnode add wagmi-setup`);
539
- console.log(` (drops lib/wagmi.ts + app/providers.tsx + components/connect-button.tsx)`);
540
- console.log(` 3. Wrap your layout with <Providers> and drop <ConnectButton /> on the page.`);
541
- console.log(` 4. npm run dev, open ${route}, connect on chainId ${result.network === "mainnet" ? "9200" : "8200"}.`);
542
- console.log(` Mainnet llama3-8b is 0.02 LCAI per call; testnet is free from https://lightfaucet.ai`);
543
- }
544
- else {
545
- console.log(` 2. npm run dev, open ${route}`);
546
- console.log(` 3. Connect a wallet on LightChain ${result.network === "mainnet" ? "mainnet (chainId 9200)" : "testnet (chainId 8200)"}.`);
547
- console.log(` Mainnet llama3-8b is 0.02 LCAI per call; testnet is free from https://lightfaucet.ai`);
548
- }
549
- console.log(`\n Note: ${sub} has NO server-side route, so it scales infinitely on`);
550
- console.log(` static hosting (Vercel/Netlify/Cloudflare Pages free tier all work).`);
551
- }
552
- else if (sub === "nft-mint-with-inference" || sub === "inference" || sub === "chat" || sub === "agent" || sub === "judge") {
638
+ }
639
+ else {
640
+ // server-paid + read-only targets keep the detailed guidance.
641
+ console.log(`\nNext steps:`);
642
+ console.log(` 1. ${installed ? "(done) " : ""}${result.install}`);
643
+ if (sub === "nft-mint-with-inference" || sub === "inference" || sub === "chat" || sub === "agent" || sub === "judge") {
553
644
  console.log(` 2. cp .env.example .env (and put a funded ${result.network} PRIVATE_KEY in it)`);
554
645
  if (sub === "agent" && result.template === "nextjs-api") {
555
646
  console.log(` 3. Set CRON_SECRET in your Vercel env vars + edit AGENT_TASK in .env`);
@@ -607,8 +698,6 @@ async function main() {
607
698
  }
608
699
  }
609
700
  // Hosting note: the Docker setup we shipped is the recommended path.
610
- // The managed platforms (Vercel etc.) are the fallback if a builder is
611
- // already committed to one.
612
701
  if (result.template === "nextjs-api"
613
702
  && (sub === "inference" || sub === "chat" || sub === "judge")) {
614
703
  console.log(`\n Hosting: a mainnet inference takes 60-90s. The Dockerfile + docker-compose.yml`);
@@ -617,13 +706,13 @@ async function main() {
617
706
  console.log(` Don't use Vercel Hobby (10s cap, every call times out). Vercel Pro works at`);
618
707
  console.log(` 60s if you'd rather stay on Vercel. See LIGHTNODE-HOSTING.md for the full table.`);
619
708
  }
620
- if (result.network === "testnet") {
621
- console.log(`\nNo wallet yet? Make one: npx lightnode wallet new then fund it free below.`);
622
- }
623
- console.log(`\nFree testnet LCAI: https://lightfaucet.ai`);
624
- console.log(`Builder docs: https://lightnode.app/build`);
625
- console.log(`New to all this? See GETTING-STARTED.md in the lightnode repo.`);
626
709
  }
710
+ if (result.network === "testnet") {
711
+ console.log(`\nNo wallet yet? Make one: npx lightnode wallet new then fund it free below.`);
712
+ }
713
+ console.log(`\nFree testnet LCAI: https://lightfaucet.ai`);
714
+ console.log(`Builder docs: https://lightnode.app/build`);
715
+ console.log(`New to all this? See GETTING-STARTED.md in the lightnode repo.`);
627
716
  break;
628
717
  }
629
718
  case "batch": {
package/dist/index.d.ts CHANGED
@@ -134,7 +134,7 @@ export declare class LightNode {
134
134
  * (especially in registry-proxy environments like StackBlitz where lockfiles
135
135
  * may pin an older minor than the local install command suggests).
136
136
  */
137
- export declare const SDK_VERSION = "0.7.18";
137
+ export declare const SDK_VERSION = "0.7.20";
138
138
  export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, resolveJobTransactions, siweSignIn, siweChallenge, siweVerify, fetchWorkerModels, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost, GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto, runInference, runInferenceWithKey, runInferenceStream, Conversation, chat, runInferenceBatch, Agent, parseAgentOutput, workerPreflight, workerWatch, Bridge, BRIDGE_ROUTE, HYPERLANE_ROUTER_ABI, ERC20_ABI, addressToBytes32, quoteBridgeFee, bridgeableBalance, bridgeAllowance, approveBridge, bridgeTransfer, DAO, DAO_ADDRESSES, ProposalState, PROPOSAL_STATE_LABEL, VoteSupport, GOVERNOR_ABI, VOTES_ABI, OnchainModelRegistry, AIVM_MODEL_REGISTRY_ABI, BENCHMARK_REGISTRY_ABI, ModelStatus, MODEL_STATUS_LABEL, StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker, WorkerOperator, WORKER_REGISTRY_ABI, JOB_REGISTRY_OPERATOR_ABI, AI_CONFIG_ABI, JOB_STATE, decodeWorkerError, WorkerOpError, isWorkerOpError, };
139
139
  export type { BearerSource, GatewayClientOptions, SelectSessionResult, PrepareSessionResult, UploadBlobResult, SessionTokenResult } from "./gateway.js";
140
140
  export type { SessionPreparation, RunInferenceArgs, RunInferenceResult, RunInferenceWithKeyArgs, RunInferenceStreamResult } from "./inference.js";
package/dist/index.js CHANGED
@@ -213,7 +213,7 @@ export class LightNode {
213
213
  * (especially in registry-proxy environments like StackBlitz where lockfiles
214
214
  * may pin an older minor than the local install command suggests).
215
215
  */
216
- export const SDK_VERSION = "0.7.18";
216
+ export const SDK_VERSION = "0.7.20";
217
217
  export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei,
218
218
  // v0.7.3 per-job transaction-hash resolver (lifts the upstream
219
219
  // subgraph's "block-only" Job entity to a deep-linkable Job + tx pair).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightnode-sdk",
3
- "version": "0.7.19",
3
+ "version": "0.8.0",
4
4
  "description": "Read-only TypeScript client for LightChain AI: workers, jobs, models, on-chain registration, and per-model network analytics. Independent, community-built (not an official LightChain package).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",