lightnode-sdk 0.7.20 → 0.8.1
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 +7 -3
- package/dist/add.d.ts +24 -0
- package/dist/add.js +390 -10
- package/dist/cli.js +145 -72
- package/package.json +1 -1
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
|
|
488
|
-
|
|
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 `--
|
|
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,30 @@ 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;
|
|
122
|
+
export declare const SCAFFOLD_GLOBALS_CSS = "@import \"tailwindcss\";\n\n/* dark mode via .dark class (we default the app to dark) */\n@custom-variant dark (&:is(.dark, .dark *));\n\n/* design tokens (light) - ported from lcai-chat-v2 */\n:root {\n font-family: var(--font-inter), ui-sans-serif, system-ui, sans-serif;\n\n --background: #ffffff;\n --primary: #6767e9;\n --primary-600: #5a4fd8;\n --foreground: #09090b;\n --card: #ffffff;\n --card-foreground: #09090b;\n --popover: #ffffff;\n --popover-foreground: hsl(240 10% 3.9%);\n --primary-foreground: #fafafa;\n --secondary: hsl(240 4.8% 95.9%);\n --secondary-foreground: hsl(240 5.9% 10%);\n --muted: hsl(240 4.8% 95.9%);\n --muted-foreground: hsl(240 3.8% 46.1%);\n --accent: hsl(240 4.8% 95.9%);\n --accent-foreground: hsl(240 5.9% 10%);\n --destructive: #ef4d6a;\n --destructive-foreground: hsl(0 0% 98%);\n --success: #15bd77;\n --warning: #eaa53d;\n --border: hsl(240 5.9% 90%);\n --input: hsl(240 5.9% 90%);\n --ring: hsl(240 10% 3.9%);\n --radius: 0.625rem;\n\n --surface-base-subtle: rgba(34, 35, 42, 0.02);\n --surface-base-faint: rgba(14, 18, 27, 0.04);\n --surface-base-light: rgba(204, 206, 239, 0.16);\n --surface-elevation-light: #ffffff;\n\n --content-primary: #0f0f14;\n --content-default: #373842;\n --content-soft: #656678;\n --content-extraLight: #9798b6;\n\n --border-soft: rgba(14, 18, 27, 0.08);\n --border-light: rgba(14, 18, 27, 0.06);\n}\n\n/* design tokens (dark) */\n.dark {\n --background: #070710;\n --foreground: hsl(0 0% 98%);\n --card: #0f0f14;\n --card-foreground: hsl(0 0% 98%);\n --popover: #0f0f14;\n --popover-foreground: hsl(0 0% 98%);\n --primary: #7064e9;\n --primary-600: #8c71f6;\n --primary-foreground: hsl(0 0% 98%);\n --secondary: hsl(240 3.7% 15.9%);\n --secondary-foreground: hsl(0 0% 98%);\n --muted: hsl(240 3.7% 15.9%);\n --muted-foreground: hsl(240 5% 64.9%);\n --accent: hsl(240 3.7% 15.9%);\n --accent-foreground: hsl(0 0% 98%);\n --destructive: #fb5a76;\n --destructive-foreground: hsl(0 0% 98%);\n --success: #22d68a;\n --warning: #f5be5c;\n --border: hsl(240 3.7% 15.9%);\n --input: hsl(240 3.7% 15.9%);\n --ring: hsl(240 4.9% 83.9%);\n\n --surface-base-subtle: rgba(204, 206, 239, 0.02);\n --surface-base-faint: rgba(204, 206, 239, 0.04);\n --surface-base-light: rgba(204, 206, 239, 0.08);\n --surface-elevation-light: #0f0f14;\n\n --content-primary: #cccef0;\n --content-default: #9798b6;\n --content-soft: rgba(154, 156, 207, 0.8);\n --content-extraLight: #9798b6;\n\n --border-soft: rgba(204, 206, 239, 0.12);\n --border-light: rgba(204, 206, 239, 0.08);\n}\n\n/* theme mapping (Tailwind v4 @theme) */\n@theme inline {\n --radius-md: calc(var(--radius) - 2px);\n --radius-sm: calc(var(--radius) - 4px);\n --radius-lg: var(--radius);\n\n --color-background: var(--background);\n --color-foreground: var(--foreground);\n --color-card: var(--card);\n --color-card-foreground: var(--card-foreground);\n --color-popover: var(--popover);\n --color-popover-foreground: var(--popover-foreground);\n --color-primary: var(--primary);\n --color-primary-600: var(--primary-600);\n --color-primary-foreground: var(--primary-foreground);\n --color-secondary: var(--secondary);\n --color-secondary-foreground: var(--secondary-foreground);\n --color-muted: var(--muted);\n --color-muted-foreground: var(--muted-foreground);\n --color-accent: var(--accent);\n --color-accent-foreground: var(--accent-foreground);\n --color-destructive: var(--destructive);\n --color-destructive-foreground: var(--destructive-foreground);\n --color-success: var(--success);\n --color-warning: var(--warning);\n --color-border: var(--border);\n --color-input: var(--input);\n --color-ring: var(--ring);\n\n --color-surface-base-subtle: var(--surface-base-subtle);\n --color-surface-base-faint: var(--surface-base-faint);\n --color-surface-base-light: var(--surface-base-light);\n --color-surface-elevation-light: var(--surface-elevation-light);\n --color-surface-base-brand-default: #693ee0;\n --color-surface-base-brand-strong: #8c71f6;\n\n --color-content-primary: var(--content-primary);\n --color-content-default: var(--content-default);\n --color-content-soft: var(--content-soft);\n --color-content-extraLight: var(--content-extraLight);\n\n --color-bdr-soft: var(--border-soft);\n --color-bdr-light: var(--border-light);\n\n --color-gradient-primary: linear-gradient(270deg, #7064e9 0%, #dd00ac 100%);\n}\n\n@layer base {\n * {\n border-color: var(--border);\n }\n body {\n background-color: var(--background);\n color: var(--foreground);\n overflow-x: hidden;\n }\n html {\n overflow-x: hidden;\n }\n button {\n cursor: pointer;\n }\n button:disabled {\n cursor: not-allowed;\n }\n /* visible keyboard focus across interactive elements */\n a:focus-visible,\n button:focus-visible,\n input:focus-visible,\n select:focus-visible,\n textarea:focus-visible {\n outline: 2px solid var(--primary);\n outline-offset: 2px;\n border-radius: 6px;\n }\n}\n\n/* respect reduced-motion: kill non-essential animation */\n@media (prefers-reduced-motion: reduce) {\n *,\n ::before,\n ::after {\n animation-duration: 0.001ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.001ms !important;\n scroll-behavior: auto !important;\n }\n}\n\n/* ambient app background (gradient mesh behind everything) */\nbody::before {\n content: \"\";\n position: fixed;\n inset: 0;\n z-index: -1;\n pointer-events: none;\n background:\n radial-gradient(60% 50% at 50% -6%, rgba(221, 0, 172, 0.10), transparent 60%),\n radial-gradient(55% 45% at 12% -8%, rgba(112, 100, 233, 0.14), transparent 60%),\n radial-gradient(50% 40% at 88% -2%, rgba(112, 100, 233, 0.12), transparent 60%),\n radial-gradient(45% 45% at 50% 115%, rgba(112, 100, 233, 0.07), transparent 60%);\n}\n.dark body::before {\n background:\n radial-gradient(60% 50% at 50% -6%, rgba(221, 0, 172, 0.12), transparent 60%),\n radial-gradient(55% 45% at 12% -8%, rgba(112, 100, 233, 0.18), transparent 60%),\n radial-gradient(50% 40% at 88% -2%, rgba(112, 100, 233, 0.14), transparent 60%),\n radial-gradient(45% 45% at 50% 118%, rgba(112, 100, 233, 0.10), transparent 60%);\n}\n\n/* minimal scrollbar */\n::-webkit-scrollbar {\n width: 6px;\n height: 6px;\n}\n::-webkit-scrollbar-track {\n background: transparent;\n}\n::-webkit-scrollbar-thumb {\n background: var(--border);\n border-radius: 3px;\n}\n* {\n scrollbar-width: thin;\n scrollbar-color: var(--border) transparent;\n}\n";
|
|
123
|
+
export interface ScaffoldWiring {
|
|
124
|
+
written: WrittenFile[];
|
|
125
|
+
/** The route now also served at `/` (e.g. "/chat-web3"), or null if nothing was wired. */
|
|
126
|
+
homepageRoute: string | null;
|
|
127
|
+
/** Whether the `dark` class was added to <html> (dark kept as the default). */
|
|
128
|
+
darkDefault: boolean;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Wire a freshly scaffolded Next.js app so the generated -web3 page is the
|
|
132
|
+
* homepage and the LightChain theme + dark default are in place. No-op for any
|
|
133
|
+
* target without a known route folder.
|
|
134
|
+
*/
|
|
135
|
+
export declare function wireFreshScaffold(target: string, opts?: AddOpts): ScaffoldWiring;
|
|
112
136
|
export declare function addJudge(opts?: AddOpts): {
|
|
113
137
|
written: WrittenFile[];
|
|
114
138
|
install: string;
|
package/dist/add.js
CHANGED
|
@@ -964,6 +964,7 @@ const NEXTJS_CHAT_WEB3_PAGE = `// app/chat-web3/page.tsx
|
|
|
964
964
|
import { useEffect, useState } from "react";
|
|
965
965
|
import { useAccount, useWalletClient, usePublicClient } from "wagmi";
|
|
966
966
|
import { siweSignIn, GatewayClient, runInference, estimateJobFee, NETWORKS } from "lightnode-sdk";
|
|
967
|
+
import { ConnectButton } from "@/components/connect-button";
|
|
967
968
|
|
|
968
969
|
type Turn = {
|
|
969
970
|
role: "user" | "assistant";
|
|
@@ -1074,16 +1075,19 @@ export default function ChatWeb3() {
|
|
|
1074
1075
|
|
|
1075
1076
|
return (
|
|
1076
1077
|
<main style={{ maxWidth: 720, margin: "32px auto", padding: 16, fontFamily: "system-ui" }}>
|
|
1077
|
-
<
|
|
1078
|
+
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }}>
|
|
1079
|
+
<h1>Chat (user-pays)</h1>
|
|
1080
|
+
<ConnectButton />
|
|
1081
|
+
</div>
|
|
1078
1082
|
<p style={{ color: "#666" }}>
|
|
1079
1083
|
Each turn signs one createSession transaction from your wallet on{" "}
|
|
1080
1084
|
<code>{network ?? "(connect a wallet)"}</code>. Fee:{" "}
|
|
1081
1085
|
<code>{feeLcai != null ? \`\${feeLcai} LCAI\` : "(fetching)"}</code> per turn plus a small gas amount.
|
|
1082
1086
|
</p>
|
|
1083
1087
|
{!address && (
|
|
1084
|
-
<div style={{ border: "1px solid #ddd", borderRadius: 8, padding: 12, margin: "12px 0" }}>
|
|
1085
|
-
Connect a wallet to start chatting
|
|
1086
|
-
|
|
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 />
|
|
1087
1091
|
</div>
|
|
1088
1092
|
)}
|
|
1089
1093
|
|
|
@@ -1166,6 +1170,7 @@ const NEXTJS_INFERENCE_WEB3_PAGE = `// app/inference-web3/page.tsx
|
|
|
1166
1170
|
import { useEffect, useState } from "react";
|
|
1167
1171
|
import { useAccount, useWalletClient, usePublicClient } from "wagmi";
|
|
1168
1172
|
import { siweSignIn, GatewayClient, runInference, estimateJobFee, NETWORKS } from "lightnode-sdk";
|
|
1173
|
+
import { ConnectButton } from "@/components/connect-button";
|
|
1169
1174
|
|
|
1170
1175
|
type Result = {
|
|
1171
1176
|
answer: string;
|
|
@@ -1254,15 +1259,19 @@ export default function InferenceWeb3() {
|
|
|
1254
1259
|
|
|
1255
1260
|
return (
|
|
1256
1261
|
<main style={{ maxWidth: 720, margin: "32px auto", padding: 16, fontFamily: "system-ui" }}>
|
|
1257
|
-
<
|
|
1262
|
+
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }}>
|
|
1263
|
+
<h1>Inference (user-pays)</h1>
|
|
1264
|
+
<ConnectButton />
|
|
1265
|
+
</div>
|
|
1258
1266
|
<p style={{ color: "#666" }}>
|
|
1259
1267
|
Signs one encrypted inference from your wallet on{" "}
|
|
1260
1268
|
<code>{network ?? "(connect a wallet)"}</code>. Fee:{" "}
|
|
1261
1269
|
<code>{feeLcai != null ? \`\${feeLcai} LCAI\` : "(fetching)"}</code> per call plus a small gas amount.
|
|
1262
1270
|
</p>
|
|
1263
1271
|
{!address && (
|
|
1264
|
-
<div style={{ border: "1px solid #ddd", borderRadius: 8, padding: 12, margin: "12px 0" }}>
|
|
1265
|
-
Connect a wallet to run inference
|
|
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 />
|
|
1266
1275
|
</div>
|
|
1267
1276
|
)}
|
|
1268
1277
|
|
|
@@ -1321,6 +1330,7 @@ const NEXTJS_JUDGE_WEB3_PAGE = `// app/judge-web3/page.tsx
|
|
|
1321
1330
|
import { useEffect, useState } from "react";
|
|
1322
1331
|
import { useAccount, useWalletClient, usePublicClient } from "wagmi";
|
|
1323
1332
|
import { siweSignIn, GatewayClient, runInference, estimateJobFee, NETWORKS } from "lightnode-sdk";
|
|
1333
|
+
import { ConnectButton } from "@/components/connect-button";
|
|
1324
1334
|
|
|
1325
1335
|
type Verdict = {
|
|
1326
1336
|
passed: boolean;
|
|
@@ -1434,15 +1444,19 @@ Reply with STRICT JSON only, matching: { "passed": boolean, "confidence": 0-1, "
|
|
|
1434
1444
|
|
|
1435
1445
|
return (
|
|
1436
1446
|
<main style={{ maxWidth: 720, margin: "32px auto", padding: 16, fontFamily: "system-ui" }}>
|
|
1437
|
-
<
|
|
1447
|
+
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }}>
|
|
1448
|
+
<h1>AI Judge (user-pays)</h1>
|
|
1449
|
+
<ConnectButton />
|
|
1450
|
+
</div>
|
|
1438
1451
|
<p style={{ color: "#666" }}>
|
|
1439
1452
|
Each submission signs one inference from your wallet on{" "}
|
|
1440
1453
|
<code>{network ?? "(connect a wallet)"}</code>. Cost:{" "}
|
|
1441
1454
|
<code>{feeLcai != null ? \`\${feeLcai} LCAI\` : "(fetching)"}</code> plus gas. Verdict comes back with on-chain proof.
|
|
1442
1455
|
</p>
|
|
1443
1456
|
{!address && (
|
|
1444
|
-
<div style={{ border: "1px solid #ddd", borderRadius: 8, padding: 12, margin: "12px 0" }}>
|
|
1445
|
-
Connect a wallet to submit
|
|
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 />
|
|
1446
1460
|
</div>
|
|
1447
1461
|
)}
|
|
1448
1462
|
|
|
@@ -1983,6 +1997,372 @@ export function addWagmiSetup(opts = {}) {
|
|
|
1983
1997
|
network,
|
|
1984
1998
|
};
|
|
1985
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
|
+
}
|
|
2061
|
+
// ---------------------------------------------------------------------------
|
|
2062
|
+
// Fresh-scaffold wiring: when `add <x>-web3` scaffolds a brand-new Next.js app
|
|
2063
|
+
// (bare folder), the create-next-app starter page sits at `/` and the generated
|
|
2064
|
+
// page sits at `/<x>-web3`, so `npm run dev` + localhost:3000 lands on the
|
|
2065
|
+
// starter, not the chat. This makes the generated page the homepage and ships
|
|
2066
|
+
// the LightChain theme so the first render is the real thing.
|
|
2067
|
+
//
|
|
2068
|
+
// Only ever invoked on a scaffold WE just created, so overwriting page.tsx and
|
|
2069
|
+
// globals.css is safe - there is no user content to clobber. In an existing app
|
|
2070
|
+
// none of this runs; the page keeps its dedicated /<x>-web3 route untouched.
|
|
2071
|
+
// ---------------------------------------------------------------------------
|
|
2072
|
+
// The LightChain chat theme (Tailwind v4 tokens, light + dark), ported from the
|
|
2073
|
+
// lightnode app's globals.css. Shipped as the scaffold's app/globals.css so
|
|
2074
|
+
// installs default to the real look instead of the create-next-app starter.
|
|
2075
|
+
export const SCAFFOLD_GLOBALS_CSS = `@import "tailwindcss";
|
|
2076
|
+
|
|
2077
|
+
/* dark mode via .dark class (we default the app to dark) */
|
|
2078
|
+
@custom-variant dark (&:is(.dark, .dark *));
|
|
2079
|
+
|
|
2080
|
+
/* design tokens (light) - ported from lcai-chat-v2 */
|
|
2081
|
+
:root {
|
|
2082
|
+
font-family: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
|
|
2083
|
+
|
|
2084
|
+
--background: #ffffff;
|
|
2085
|
+
--primary: #6767e9;
|
|
2086
|
+
--primary-600: #5a4fd8;
|
|
2087
|
+
--foreground: #09090b;
|
|
2088
|
+
--card: #ffffff;
|
|
2089
|
+
--card-foreground: #09090b;
|
|
2090
|
+
--popover: #ffffff;
|
|
2091
|
+
--popover-foreground: hsl(240 10% 3.9%);
|
|
2092
|
+
--primary-foreground: #fafafa;
|
|
2093
|
+
--secondary: hsl(240 4.8% 95.9%);
|
|
2094
|
+
--secondary-foreground: hsl(240 5.9% 10%);
|
|
2095
|
+
--muted: hsl(240 4.8% 95.9%);
|
|
2096
|
+
--muted-foreground: hsl(240 3.8% 46.1%);
|
|
2097
|
+
--accent: hsl(240 4.8% 95.9%);
|
|
2098
|
+
--accent-foreground: hsl(240 5.9% 10%);
|
|
2099
|
+
--destructive: #ef4d6a;
|
|
2100
|
+
--destructive-foreground: hsl(0 0% 98%);
|
|
2101
|
+
--success: #15bd77;
|
|
2102
|
+
--warning: #eaa53d;
|
|
2103
|
+
--border: hsl(240 5.9% 90%);
|
|
2104
|
+
--input: hsl(240 5.9% 90%);
|
|
2105
|
+
--ring: hsl(240 10% 3.9%);
|
|
2106
|
+
--radius: 0.625rem;
|
|
2107
|
+
|
|
2108
|
+
--surface-base-subtle: rgba(34, 35, 42, 0.02);
|
|
2109
|
+
--surface-base-faint: rgba(14, 18, 27, 0.04);
|
|
2110
|
+
--surface-base-light: rgba(204, 206, 239, 0.16);
|
|
2111
|
+
--surface-elevation-light: #ffffff;
|
|
2112
|
+
|
|
2113
|
+
--content-primary: #0f0f14;
|
|
2114
|
+
--content-default: #373842;
|
|
2115
|
+
--content-soft: #656678;
|
|
2116
|
+
--content-extraLight: #9798b6;
|
|
2117
|
+
|
|
2118
|
+
--border-soft: rgba(14, 18, 27, 0.08);
|
|
2119
|
+
--border-light: rgba(14, 18, 27, 0.06);
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
/* design tokens (dark) */
|
|
2123
|
+
.dark {
|
|
2124
|
+
--background: #070710;
|
|
2125
|
+
--foreground: hsl(0 0% 98%);
|
|
2126
|
+
--card: #0f0f14;
|
|
2127
|
+
--card-foreground: hsl(0 0% 98%);
|
|
2128
|
+
--popover: #0f0f14;
|
|
2129
|
+
--popover-foreground: hsl(0 0% 98%);
|
|
2130
|
+
--primary: #7064e9;
|
|
2131
|
+
--primary-600: #8c71f6;
|
|
2132
|
+
--primary-foreground: hsl(0 0% 98%);
|
|
2133
|
+
--secondary: hsl(240 3.7% 15.9%);
|
|
2134
|
+
--secondary-foreground: hsl(0 0% 98%);
|
|
2135
|
+
--muted: hsl(240 3.7% 15.9%);
|
|
2136
|
+
--muted-foreground: hsl(240 5% 64.9%);
|
|
2137
|
+
--accent: hsl(240 3.7% 15.9%);
|
|
2138
|
+
--accent-foreground: hsl(0 0% 98%);
|
|
2139
|
+
--destructive: #fb5a76;
|
|
2140
|
+
--destructive-foreground: hsl(0 0% 98%);
|
|
2141
|
+
--success: #22d68a;
|
|
2142
|
+
--warning: #f5be5c;
|
|
2143
|
+
--border: hsl(240 3.7% 15.9%);
|
|
2144
|
+
--input: hsl(240 3.7% 15.9%);
|
|
2145
|
+
--ring: hsl(240 4.9% 83.9%);
|
|
2146
|
+
|
|
2147
|
+
--surface-base-subtle: rgba(204, 206, 239, 0.02);
|
|
2148
|
+
--surface-base-faint: rgba(204, 206, 239, 0.04);
|
|
2149
|
+
--surface-base-light: rgba(204, 206, 239, 0.08);
|
|
2150
|
+
--surface-elevation-light: #0f0f14;
|
|
2151
|
+
|
|
2152
|
+
--content-primary: #cccef0;
|
|
2153
|
+
--content-default: #9798b6;
|
|
2154
|
+
--content-soft: rgba(154, 156, 207, 0.8);
|
|
2155
|
+
--content-extraLight: #9798b6;
|
|
2156
|
+
|
|
2157
|
+
--border-soft: rgba(204, 206, 239, 0.12);
|
|
2158
|
+
--border-light: rgba(204, 206, 239, 0.08);
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
/* theme mapping (Tailwind v4 @theme) */
|
|
2162
|
+
@theme inline {
|
|
2163
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
2164
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
2165
|
+
--radius-lg: var(--radius);
|
|
2166
|
+
|
|
2167
|
+
--color-background: var(--background);
|
|
2168
|
+
--color-foreground: var(--foreground);
|
|
2169
|
+
--color-card: var(--card);
|
|
2170
|
+
--color-card-foreground: var(--card-foreground);
|
|
2171
|
+
--color-popover: var(--popover);
|
|
2172
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
2173
|
+
--color-primary: var(--primary);
|
|
2174
|
+
--color-primary-600: var(--primary-600);
|
|
2175
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
2176
|
+
--color-secondary: var(--secondary);
|
|
2177
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
2178
|
+
--color-muted: var(--muted);
|
|
2179
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
2180
|
+
--color-accent: var(--accent);
|
|
2181
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
2182
|
+
--color-destructive: var(--destructive);
|
|
2183
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
2184
|
+
--color-success: var(--success);
|
|
2185
|
+
--color-warning: var(--warning);
|
|
2186
|
+
--color-border: var(--border);
|
|
2187
|
+
--color-input: var(--input);
|
|
2188
|
+
--color-ring: var(--ring);
|
|
2189
|
+
|
|
2190
|
+
--color-surface-base-subtle: var(--surface-base-subtle);
|
|
2191
|
+
--color-surface-base-faint: var(--surface-base-faint);
|
|
2192
|
+
--color-surface-base-light: var(--surface-base-light);
|
|
2193
|
+
--color-surface-elevation-light: var(--surface-elevation-light);
|
|
2194
|
+
--color-surface-base-brand-default: #693ee0;
|
|
2195
|
+
--color-surface-base-brand-strong: #8c71f6;
|
|
2196
|
+
|
|
2197
|
+
--color-content-primary: var(--content-primary);
|
|
2198
|
+
--color-content-default: var(--content-default);
|
|
2199
|
+
--color-content-soft: var(--content-soft);
|
|
2200
|
+
--color-content-extraLight: var(--content-extraLight);
|
|
2201
|
+
|
|
2202
|
+
--color-bdr-soft: var(--border-soft);
|
|
2203
|
+
--color-bdr-light: var(--border-light);
|
|
2204
|
+
|
|
2205
|
+
--color-gradient-primary: linear-gradient(270deg, #7064e9 0%, #dd00ac 100%);
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
@layer base {
|
|
2209
|
+
* {
|
|
2210
|
+
border-color: var(--border);
|
|
2211
|
+
}
|
|
2212
|
+
body {
|
|
2213
|
+
background-color: var(--background);
|
|
2214
|
+
color: var(--foreground);
|
|
2215
|
+
overflow-x: hidden;
|
|
2216
|
+
}
|
|
2217
|
+
html {
|
|
2218
|
+
overflow-x: hidden;
|
|
2219
|
+
}
|
|
2220
|
+
button {
|
|
2221
|
+
cursor: pointer;
|
|
2222
|
+
}
|
|
2223
|
+
button:disabled {
|
|
2224
|
+
cursor: not-allowed;
|
|
2225
|
+
}
|
|
2226
|
+
/* visible keyboard focus across interactive elements */
|
|
2227
|
+
a:focus-visible,
|
|
2228
|
+
button:focus-visible,
|
|
2229
|
+
input:focus-visible,
|
|
2230
|
+
select:focus-visible,
|
|
2231
|
+
textarea:focus-visible {
|
|
2232
|
+
outline: 2px solid var(--primary);
|
|
2233
|
+
outline-offset: 2px;
|
|
2234
|
+
border-radius: 6px;
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
/* respect reduced-motion: kill non-essential animation */
|
|
2239
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2240
|
+
*,
|
|
2241
|
+
::before,
|
|
2242
|
+
::after {
|
|
2243
|
+
animation-duration: 0.001ms !important;
|
|
2244
|
+
animation-iteration-count: 1 !important;
|
|
2245
|
+
transition-duration: 0.001ms !important;
|
|
2246
|
+
scroll-behavior: auto !important;
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
/* ambient app background (gradient mesh behind everything) */
|
|
2251
|
+
body::before {
|
|
2252
|
+
content: "";
|
|
2253
|
+
position: fixed;
|
|
2254
|
+
inset: 0;
|
|
2255
|
+
z-index: -1;
|
|
2256
|
+
pointer-events: none;
|
|
2257
|
+
background:
|
|
2258
|
+
radial-gradient(60% 50% at 50% -6%, rgba(221, 0, 172, 0.10), transparent 60%),
|
|
2259
|
+
radial-gradient(55% 45% at 12% -8%, rgba(112, 100, 233, 0.14), transparent 60%),
|
|
2260
|
+
radial-gradient(50% 40% at 88% -2%, rgba(112, 100, 233, 0.12), transparent 60%),
|
|
2261
|
+
radial-gradient(45% 45% at 50% 115%, rgba(112, 100, 233, 0.07), transparent 60%);
|
|
2262
|
+
}
|
|
2263
|
+
.dark body::before {
|
|
2264
|
+
background:
|
|
2265
|
+
radial-gradient(60% 50% at 50% -6%, rgba(221, 0, 172, 0.12), transparent 60%),
|
|
2266
|
+
radial-gradient(55% 45% at 12% -8%, rgba(112, 100, 233, 0.18), transparent 60%),
|
|
2267
|
+
radial-gradient(50% 40% at 88% -2%, rgba(112, 100, 233, 0.14), transparent 60%),
|
|
2268
|
+
radial-gradient(45% 45% at 50% 118%, rgba(112, 100, 233, 0.10), transparent 60%);
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
/* minimal scrollbar */
|
|
2272
|
+
::-webkit-scrollbar {
|
|
2273
|
+
width: 6px;
|
|
2274
|
+
height: 6px;
|
|
2275
|
+
}
|
|
2276
|
+
::-webkit-scrollbar-track {
|
|
2277
|
+
background: transparent;
|
|
2278
|
+
}
|
|
2279
|
+
::-webkit-scrollbar-thumb {
|
|
2280
|
+
background: var(--border);
|
|
2281
|
+
border-radius: 3px;
|
|
2282
|
+
}
|
|
2283
|
+
* {
|
|
2284
|
+
scrollbar-width: thin;
|
|
2285
|
+
scrollbar-color: var(--border) transparent;
|
|
2286
|
+
}
|
|
2287
|
+
`;
|
|
2288
|
+
/** Map an `add` target to the route folder its page lives in. */
|
|
2289
|
+
const WEB3_ROUTE_DIR = {
|
|
2290
|
+
"chat-web3": "chat-web3",
|
|
2291
|
+
"inference-web3": "inference-web3",
|
|
2292
|
+
"judge-web3": "judge-web3",
|
|
2293
|
+
};
|
|
2294
|
+
/** The app/page.tsx we drop in to make the generated page the homepage. It
|
|
2295
|
+
* re-exports the real page so its documented /<target> route still works. */
|
|
2296
|
+
function homepageReexport(dir, target) {
|
|
2297
|
+
return `// app/page.tsx
|
|
2298
|
+
// Generated by 'lightnode add ${target}'. Makes the ${dir} page the homepage so
|
|
2299
|
+
// 'npm run dev' + http://localhost:3000 lands on it directly. The page itself
|
|
2300
|
+
// still lives at app/${dir}/page.tsx and is also served at /${dir}.
|
|
2301
|
+
export { default } from "./${dir}/page";
|
|
2302
|
+
`;
|
|
2303
|
+
}
|
|
2304
|
+
/** Add the \`dark\` class to the layout's <html> element so dark stays the
|
|
2305
|
+
* default theme. Returns the patched source, or null if <html> was not found
|
|
2306
|
+
* or already carries a \`dark\` class. Handles className as a template literal,
|
|
2307
|
+
* a string literal, or absent. */
|
|
2308
|
+
function withDarkHtml(source) {
|
|
2309
|
+
const match = source.match(/<html\b[^>]*>/);
|
|
2310
|
+
if (!match)
|
|
2311
|
+
return null;
|
|
2312
|
+
const tag = match[0];
|
|
2313
|
+
// Already dark? Cover both className={`...dark...`} and className="...dark...".
|
|
2314
|
+
if (/className=\{`[^`]*\bdark\b/.test(tag) || /className=(["'])[^"']*\bdark\b/.test(tag))
|
|
2315
|
+
return null;
|
|
2316
|
+
if (/className=\{`/.test(tag)) {
|
|
2317
|
+
return source.replace(tag, tag.replace(/className=\{`/, "className={`dark "));
|
|
2318
|
+
}
|
|
2319
|
+
if (/className=(["'])/.test(tag)) {
|
|
2320
|
+
return source.replace(tag, tag.replace(/className=(["'])/, 'className=$1dark '));
|
|
2321
|
+
}
|
|
2322
|
+
// No className on <html>: add one.
|
|
2323
|
+
return source.replace(tag, tag.replace(/<html\b/, '<html className="dark"'));
|
|
2324
|
+
}
|
|
2325
|
+
function setDarkDefaultOnLayout(cwd) {
|
|
2326
|
+
const abs = findLayoutFile(cwd);
|
|
2327
|
+
if (!abs)
|
|
2328
|
+
return false;
|
|
2329
|
+
let source;
|
|
2330
|
+
try {
|
|
2331
|
+
source = fs.readFileSync(abs, "utf8");
|
|
2332
|
+
}
|
|
2333
|
+
catch {
|
|
2334
|
+
return false;
|
|
2335
|
+
}
|
|
2336
|
+
const patched = withDarkHtml(source);
|
|
2337
|
+
if (patched === null)
|
|
2338
|
+
return false;
|
|
2339
|
+
try {
|
|
2340
|
+
fs.writeFileSync(abs, patched);
|
|
2341
|
+
}
|
|
2342
|
+
catch {
|
|
2343
|
+
return false;
|
|
2344
|
+
}
|
|
2345
|
+
return true;
|
|
2346
|
+
}
|
|
2347
|
+
/**
|
|
2348
|
+
* Wire a freshly scaffolded Next.js app so the generated -web3 page is the
|
|
2349
|
+
* homepage and the LightChain theme + dark default are in place. No-op for any
|
|
2350
|
+
* target without a known route folder.
|
|
2351
|
+
*/
|
|
2352
|
+
export function wireFreshScaffold(target, opts = {}) {
|
|
2353
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
2354
|
+
const dir = WEB3_ROUTE_DIR[target];
|
|
2355
|
+
const written = [];
|
|
2356
|
+
if (!dir)
|
|
2357
|
+
return { written, homepageRoute: null, darkDefault: false };
|
|
2358
|
+
// 1. Ship the LightChain theme, replacing the create-next-app starter globals.
|
|
2359
|
+
written.push(writeFile(path.join(cwd, "app/globals.css"), SCAFFOLD_GLOBALS_CSS, true));
|
|
2360
|
+
// 2. Make the generated page the homepage (replaces the starter page.tsx).
|
|
2361
|
+
written.push(writeFile(path.join(cwd, "app/page.tsx"), homepageReexport(dir, target), true));
|
|
2362
|
+
// 3. Keep dark as the default theme (matches lightnode.app).
|
|
2363
|
+
const darkDefault = setDarkDefaultOnLayout(cwd);
|
|
2364
|
+
return { written, homepageRoute: `/${dir}`, darkDefault };
|
|
2365
|
+
}
|
|
1986
2366
|
const NEXTJS_JUDGE_ROUTE = `// app/api/judge/route.ts
|
|
1987
2367
|
// Generated by 'lightnode add judge'. See https://lightnode.app/build
|
|
1988
2368
|
// The LightChallenge-style evaluator: post evidence + criteria, get a
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
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 } from "./add.js";
|
|
3
|
+
import { addInference, addInferenceWeb3, addJudgeWeb3, addAnalyticsDashboard, addNftMint, addChat, addChatWeb3, addAgent, addJudge, addWagmiSetup, patchLayoutWithProviders, wireFreshScaffold } from "./add.js";
|
|
4
4
|
import { createPublicClient, createWalletClient, http, parseEther } from "viem";
|
|
5
5
|
import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
|
|
6
|
-
import { existsSync } from "node:fs";
|
|
6
|
+
import { existsSync, readdirSync, renameSync, rmSync } from "node:fs";
|
|
7
7
|
import { join } from "node:path";
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
8
9
|
function flag(name) {
|
|
9
10
|
const i = process.argv.indexOf(name);
|
|
10
11
|
return i >= 0 ? process.argv[i + 1] : undefined;
|
|
@@ -19,6 +20,71 @@ function die(msg) {
|
|
|
19
20
|
}
|
|
20
21
|
const lcai = (wei) => (wei ? Number(BigInt(wei)) / 1e18 : 0);
|
|
21
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
|
+
}
|
|
22
88
|
const HELP = `lightnode <command> [--net mainnet|testnet]
|
|
23
89
|
|
|
24
90
|
Run one inference (needs PRIVATE_KEY in env):
|
|
@@ -505,6 +571,18 @@ async function main() {
|
|
|
505
571
|
}
|
|
506
572
|
die(lines.join("\n"));
|
|
507
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
|
+
const didScaffold = NEXT_PAGE_TARGETS.has(sub ?? "") && !existsSync(join(cwd, "package.json")) && !noScaffold
|
|
583
|
+
? scaffoldNextApp(cwd, sub ?? "")
|
|
584
|
+
: false;
|
|
585
|
+
// ---- write the requested files ----
|
|
508
586
|
const result = sub === "analytics-dashboard" ? addAnalyticsDashboard({ template, network, force })
|
|
509
587
|
: sub === "nft-mint-with-inference" ? addNftMint({ template, network, force })
|
|
510
588
|
: sub === "chat-web3" ? addChatWeb3({ template, network, force })
|
|
@@ -516,71 +594,68 @@ async function main() {
|
|
|
516
594
|
: sub === "judge" ? addJudge({ template, network, force })
|
|
517
595
|
: addInference({ template, network, force });
|
|
518
596
|
console.log(`▶ add ${sub} (${result.template} template, default network ${result.network})`);
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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>)`);
|
|
522
609
|
else
|
|
523
|
-
console.log(`
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
console.log(
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
console.log(
|
|
555
|
-
console.log(`
|
|
556
|
-
console.log(` 3. Drop <ConnectButton /> anywhere you want a connect UI:`);
|
|
557
|
-
console.log(` import { ConnectButton } from "@/components/connect-button";`);
|
|
558
|
-
console.log(` 4. You can now use any wagmi hook (useAccount, useWalletClient, ...).`);
|
|
559
|
-
console.log(` Wallets on chains other than 9200/8200 will be prompted to switch.`);
|
|
610
|
+
console.log(` ⤴ ${layout.path} (${layout.reason})`);
|
|
611
|
+
}
|
|
612
|
+
// ---- fresh scaffold only: make the generated page the homepage and ship
|
|
613
|
+
// the LightChain theme so localhost:3000 lands on the chat, not the
|
|
614
|
+
// create-next-app starter. Skipped in an existing app (nothing to clobber).
|
|
615
|
+
let wiring = null;
|
|
616
|
+
if (didScaffold && isWeb3Page) {
|
|
617
|
+
wiring = wireFreshScaffold(sub, { cwd });
|
|
618
|
+
printWritten(wiring.written);
|
|
619
|
+
if (wiring.homepageRoute)
|
|
620
|
+
console.log(` ✓ app/page.tsx (chat is now the homepage at /)`);
|
|
621
|
+
if (wiring.darkDefault)
|
|
622
|
+
console.log(` ✓ app/layout.tsx (dark theme default)`);
|
|
623
|
+
}
|
|
624
|
+
// ---- install dependencies (opt out with --no-install) ----
|
|
625
|
+
const installed = noInstall ? false : installDeps(result.install, cwd);
|
|
626
|
+
// ---- next steps ----
|
|
627
|
+
const layoutNeedsManual = layout != null && !layout.patched && !/already/.test(layout.reason ?? "");
|
|
628
|
+
if (isWeb3Page) {
|
|
629
|
+
const route = sub === "chat-web3" ? "/chat-web3" : sub === "inference-web3" ? "/inference-web3" : "/judge-web3";
|
|
630
|
+
const chainId = result.network === "mainnet" ? "9200" : "8200";
|
|
631
|
+
console.log(`\n${installed ? "✓ Done - deps installed, wagmi + layout wired. Just run it:" : "Files written. Next:"}`);
|
|
632
|
+
if (!installed)
|
|
633
|
+
console.log(` ${result.install}`);
|
|
634
|
+
console.log(` npm run dev`);
|
|
635
|
+
const openPath = wiring ? "" : route; // fresh scaffold serves the page at /
|
|
636
|
+
console.log(` open http://localhost:3000${openPath} and click Connect wallet (chainId ${chainId})`);
|
|
637
|
+
if (wiring)
|
|
638
|
+
console.log(` (also reachable at ${route})`);
|
|
639
|
+
console.log(` ${result.network === "mainnet" ? "llama3-8b costs 0.02 LCAI per call" : "testnet is free"}`);
|
|
640
|
+
if (layoutNeedsManual) {
|
|
641
|
+
console.log(`\nHeads up: couldn't auto-wire the layout (${layout?.reason}).`);
|
|
642
|
+
console.log(`Wrap {children} with <Providers> in app/layout.tsx (import from "./providers").`);
|
|
560
643
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
console.log(` npx lightnode add wagmi-setup`);
|
|
570
|
-
console.log(` (drops lib/wagmi.ts + app/providers.tsx + components/connect-button.tsx)`);
|
|
571
|
-
console.log(` 3. Wrap your layout with <Providers> and drop <ConnectButton /> on the page.`);
|
|
572
|
-
console.log(` 4. npm run dev, open ${route}, connect on chainId ${result.network === "mainnet" ? "9200" : "8200"}.`);
|
|
573
|
-
console.log(` Mainnet llama3-8b is 0.02 LCAI per call; testnet is free from https://lightfaucet.ai`);
|
|
574
|
-
}
|
|
575
|
-
else {
|
|
576
|
-
console.log(` 2. npm run dev, open ${route}`);
|
|
577
|
-
console.log(` 3. Connect a wallet on LightChain ${result.network === "mainnet" ? "mainnet (chainId 9200)" : "testnet (chainId 8200)"}.`);
|
|
578
|
-
console.log(` Mainnet llama3-8b is 0.02 LCAI per call; testnet is free from https://lightfaucet.ai`);
|
|
579
|
-
}
|
|
580
|
-
console.log(`\n Note: ${sub} has NO server-side route, so it scales infinitely on`);
|
|
581
|
-
console.log(` static hosting (Vercel/Netlify/Cloudflare Pages free tier all work).`);
|
|
644
|
+
console.log(`\n No server-side route - deploy static (Vercel/Netlify/Cloudflare free tier all work).`);
|
|
645
|
+
}
|
|
646
|
+
else if (sub === "wagmi-setup") {
|
|
647
|
+
console.log(`\n${installed ? "✓ Done - deps installed and layout wired." : "Files written. Run: " + result.install}`);
|
|
648
|
+
console.log(`\nUse it: import { ConnectButton } from "@/components/connect-button"; drop <ConnectButton /> anywhere.`);
|
|
649
|
+
console.log(`Any wagmi hook (useAccount, useWalletClient, ...) now works app-wide. Off-network wallets get a switch prompt.`);
|
|
650
|
+
if (layoutNeedsManual) {
|
|
651
|
+
console.log(`\nHeads up: couldn't auto-wire the layout (${layout?.reason}). Wrap {children} with <Providers> in app/layout.tsx.`);
|
|
582
652
|
}
|
|
583
|
-
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
// server-paid + read-only targets keep the detailed guidance.
|
|
656
|
+
console.log(`\nNext steps:`);
|
|
657
|
+
console.log(` 1. ${installed ? "(done) " : ""}${result.install}`);
|
|
658
|
+
if (sub === "nft-mint-with-inference" || sub === "inference" || sub === "chat" || sub === "agent" || sub === "judge") {
|
|
584
659
|
console.log(` 2. cp .env.example .env (and put a funded ${result.network} PRIVATE_KEY in it)`);
|
|
585
660
|
if (sub === "agent" && result.template === "nextjs-api") {
|
|
586
661
|
console.log(` 3. Set CRON_SECRET in your Vercel env vars + edit AGENT_TASK in .env`);
|
|
@@ -638,8 +713,6 @@ async function main() {
|
|
|
638
713
|
}
|
|
639
714
|
}
|
|
640
715
|
// Hosting note: the Docker setup we shipped is the recommended path.
|
|
641
|
-
// The managed platforms (Vercel etc.) are the fallback if a builder is
|
|
642
|
-
// already committed to one.
|
|
643
716
|
if (result.template === "nextjs-api"
|
|
644
717
|
&& (sub === "inference" || sub === "chat" || sub === "judge")) {
|
|
645
718
|
console.log(`\n Hosting: a mainnet inference takes 60-90s. The Dockerfile + docker-compose.yml`);
|
|
@@ -648,13 +721,13 @@ async function main() {
|
|
|
648
721
|
console.log(` Don't use Vercel Hobby (10s cap, every call times out). Vercel Pro works at`);
|
|
649
722
|
console.log(` 60s if you'd rather stay on Vercel. See LIGHTNODE-HOSTING.md for the full table.`);
|
|
650
723
|
}
|
|
651
|
-
if (result.network === "testnet") {
|
|
652
|
-
console.log(`\nNo wallet yet? Make one: npx lightnode wallet new then fund it free below.`);
|
|
653
|
-
}
|
|
654
|
-
console.log(`\nFree testnet LCAI: https://lightfaucet.ai`);
|
|
655
|
-
console.log(`Builder docs: https://lightnode.app/build`);
|
|
656
|
-
console.log(`New to all this? See GETTING-STARTED.md in the lightnode repo.`);
|
|
657
724
|
}
|
|
725
|
+
if (result.network === "testnet") {
|
|
726
|
+
console.log(`\nNo wallet yet? Make one: npx lightnode wallet new then fund it free below.`);
|
|
727
|
+
}
|
|
728
|
+
console.log(`\nFree testnet LCAI: https://lightfaucet.ai`);
|
|
729
|
+
console.log(`Builder docs: https://lightnode.app/build`);
|
|
730
|
+
console.log(`New to all this? See GETTING-STARTED.md in the lightnode repo.`);
|
|
658
731
|
break;
|
|
659
732
|
}
|
|
660
733
|
case "batch": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lightnode-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
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",
|