lightnode-sdk 0.8.1 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/add.d.ts CHANGED
@@ -119,7 +119,7 @@ export interface LayoutPatch {
119
119
  * Returns what happened so the CLI can report it; never throws.
120
120
  */
121
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";
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/* signature lcai gradient (primary buttons / accents) */\n.bg-gradient-primary {\n background-image: var(--color-gradient-primary);\n}\n.text-gradient {\n background: linear-gradient(94deg, #dd00ac 10%, #7130c3 53%, #7064e9 96%);\n -webkit-background-clip: text;\n background-clip: text;\n color: transparent;\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
123
  export interface ScaffoldWiring {
124
124
  written: WrittenFile[];
125
125
  /** The route now also served at `/` (e.g. "/chat-web3"), or null if nothing was wired. */
package/dist/add.js CHANGED
@@ -1074,83 +1074,99 @@ export default function ChatWeb3() {
1074
1074
  }
1075
1075
 
1076
1076
  return (
1077
- <main style={{ maxWidth: 720, margin: "32px auto", padding: 16, fontFamily: "system-ui" }}>
1078
- <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }}>
1079
- <h1>Chat (user-pays)</h1>
1080
- <ConnectButton />
1081
- </div>
1082
- <p style={{ color: "#666" }}>
1083
- Each turn signs one createSession transaction from your wallet on{" "}
1084
- <code>{network ?? "(connect a wallet)"}</code>. Fee:{" "}
1085
- <code>{feeLcai != null ? \`\${feeLcai} LCAI\` : "(fetching)"}</code> per turn plus a small gas amount.
1086
- </p>
1087
- {!address && (
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 />
1077
+ <main className="mx-auto flex min-h-screen w-full max-w-2xl flex-col px-4 py-6">
1078
+ <header className="flex items-center justify-between gap-3 border-b border-border pb-4">
1079
+ <div className="min-w-0">
1080
+ <h1 className="text-base font-semibold text-foreground">Chat</h1>
1081
+ <p className="truncate text-xs text-muted-foreground">
1082
+ {network ? (
1083
+ <>Signed from your wallet on {network} · {feeLcai != null ? feeLcai + " LCAI" : "..."}/turn + gas</>
1084
+ ) : (
1085
+ "Connect a wallet on LightChain to start"
1086
+ )}
1087
+ </p>
1091
1088
  </div>
1092
- )}
1089
+ <ConnectButton />
1090
+ </header>
1093
1091
 
1094
- <div style={{ display: "flex", flexDirection: "column", gap: 8, margin: "16px 0" }}>
1095
- {turns.map((t, i) => (
1096
- <div
1097
- key={i}
1098
- style={{
1099
- alignSelf: t.role === "user" ? "flex-end" : "flex-start",
1100
- maxWidth: "85%",
1101
- borderRadius: 12,
1102
- padding: "8px 12px",
1103
- background: t.role === "user" ? "#e9e7ff" : "#f5f5f7",
1104
- }}
1105
- >
1106
- <div style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>{t.text}</div>
1107
- {t.role === "assistant" && t.submitTx ? (
1108
- <div style={{ marginTop: 6, fontSize: 11, color: "#666", display: "flex", gap: 8, flexWrap: "wrap" }}>
1109
- {t.worker && (
1110
- <a href={\`https://\${network}.lightscan.app/address/\${t.worker}\`} target="_blank" rel="noopener noreferrer">
1111
- worker
1112
- </a>
1113
- )}
1114
- {t.jobId && <span>job #{t.jobId}</span>}
1115
- {t.submitTx && (
1116
- <a href={\`https://\${network}.lightscan.app/tx/\${t.submitTx}\`} target="_blank" rel="noopener noreferrer">
1117
- submitJob
1118
- </a>
1119
- )}
1120
- {t.jobCompletedTx && (
1121
- <a href={\`https://\${network}.lightscan.app/tx/\${t.jobCompletedTx}\`} target="_blank" rel="noopener noreferrer">
1122
- completed
1123
- </a>
1124
- )}
1092
+ <div className="flex flex-1 flex-col gap-3 overflow-y-auto py-6">
1093
+ {turns.length === 0 ? (
1094
+ <div className="flex flex-1 flex-col items-center justify-center gap-3 text-center">
1095
+ <h2 className="text-2xl font-medium text-foreground">How can I help?</h2>
1096
+ <p className="max-w-sm text-sm text-muted-foreground">
1097
+ Connect your wallet and send a message. Each turn is signed and paid from your
1098
+ own wallet, no backend required.
1099
+ </p>
1100
+ {!address && (
1101
+ <div className="mt-2">
1102
+ <ConnectButton />
1125
1103
  </div>
1126
- ) : null}
1104
+ )}
1127
1105
  </div>
1128
- ))}
1106
+ ) : (
1107
+ turns.map((t, i) => (
1108
+ <div
1109
+ key={i}
1110
+ className={
1111
+ "max-w-[85%] whitespace-pre-wrap break-words rounded-2xl px-4 py-2.5 text-sm " +
1112
+ (t.role === "user"
1113
+ ? "self-end rounded-br-md bg-primary text-primary-foreground"
1114
+ : "self-start rounded-bl-md border border-border bg-card text-foreground")
1115
+ }
1116
+ >
1117
+ <div>{t.text}</div>
1118
+ {t.role === "assistant" && t.submitTx ? (
1119
+ <div className="mt-2 flex flex-wrap gap-3 text-[11px] text-muted-foreground">
1120
+ {t.worker && (
1121
+ <a className="hover:text-foreground hover:underline" href={\`https://\${network}.lightscan.app/address/\${t.worker}\`} target="_blank" rel="noopener noreferrer">
1122
+ worker
1123
+ </a>
1124
+ )}
1125
+ {t.jobId && <span>job #{t.jobId}</span>}
1126
+ {t.submitTx && (
1127
+ <a className="hover:text-foreground hover:underline" href={\`https://\${network}.lightscan.app/tx/\${t.submitTx}\`} target="_blank" rel="noopener noreferrer">
1128
+ submitJob
1129
+ </a>
1130
+ )}
1131
+ {t.jobCompletedTx && (
1132
+ <a className="hover:text-foreground hover:underline" href={\`https://\${network}.lightscan.app/tx/\${t.jobCompletedTx}\`} target="_blank" rel="noopener noreferrer">
1133
+ completed
1134
+ </a>
1135
+ )}
1136
+ </div>
1137
+ ) : null}
1138
+ </div>
1139
+ ))
1140
+ )}
1129
1141
  </div>
1130
1142
 
1131
- <textarea
1132
- value={input}
1133
- onChange={(e) => setInput(e.target.value)}
1134
- onKeyDown={(e) => {
1135
- if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); if (!busy && input.trim()) send(); }
1136
- }}
1137
- rows={2}
1138
- placeholder={turns.length === 0 ? "Say hello (cmd+enter to send)" : "Reply..."}
1139
- style={{ width: "100%", padding: 8, fontFamily: "inherit" }}
1140
- />
1141
- <button
1142
- type="button"
1143
- onClick={() => send()}
1144
- disabled={busy || !input.trim() || !address || !network}
1145
- style={{ marginTop: 8, padding: "8px 16px" }}
1146
- >
1147
- {busy ? (busyStage || "Sending...") : (turns.length === 0 ? "Send first message" : "Send")}
1148
- </button>
1149
- {err && (
1150
- <p style={{ marginTop: 8, padding: "8px 12px", border: "1px solid #f5c2c7", background: "#f8d7da", color: "#842029", borderRadius: 6 }}>
1151
- {err}
1152
- </p>
1153
- )}
1143
+ <div className="border-t border-border pt-4">
1144
+ <div className="flex items-end gap-2 rounded-2xl border border-border bg-card p-2 transition focus-within:ring-2 focus-within:ring-primary">
1145
+ <textarea
1146
+ value={input}
1147
+ onChange={(e) => setInput(e.target.value)}
1148
+ onKeyDown={(e) => {
1149
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); if (!busy && input.trim()) send(); }
1150
+ }}
1151
+ rows={1}
1152
+ placeholder={turns.length === 0 ? "Say hello (cmd+enter to send)" : "Reply... (cmd+enter)"}
1153
+ className="max-h-40 min-h-[40px] flex-1 resize-none bg-transparent px-2 py-2 text-sm text-foreground outline-none placeholder:text-muted-foreground"
1154
+ />
1155
+ <button
1156
+ type="button"
1157
+ onClick={() => send()}
1158
+ disabled={busy || !input.trim() || !address || !network}
1159
+ className="shrink-0 rounded-xl bg-gradient-primary px-4 py-2 text-sm font-medium text-white transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40"
1160
+ >
1161
+ {busy ? (busyStage || "Sending...") : "Send"}
1162
+ </button>
1163
+ </div>
1164
+ {err && (
1165
+ <p className="mt-2 rounded-lg border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">
1166
+ {err}
1167
+ </p>
1168
+ )}
1169
+ </div>
1154
1170
  </main>
1155
1171
  );
1156
1172
  }
@@ -1258,59 +1274,67 @@ export default function InferenceWeb3() {
1258
1274
  }
1259
1275
 
1260
1276
  return (
1261
- <main style={{ maxWidth: 720, margin: "32px auto", padding: 16, fontFamily: "system-ui" }}>
1262
- <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }}>
1263
- <h1>Inference (user-pays)</h1>
1264
- <ConnectButton />
1265
- </div>
1266
- <p style={{ color: "#666" }}>
1267
- Signs one encrypted inference from your wallet on{" "}
1268
- <code>{network ?? "(connect a wallet)"}</code>. Fee:{" "}
1269
- <code>{feeLcai != null ? \`\${feeLcai} LCAI\` : "(fetching)"}</code> per call plus a small gas amount.
1270
- </p>
1271
- {!address && (
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 />
1277
+ <main className="mx-auto flex min-h-screen w-full max-w-2xl flex-col px-4 py-6">
1278
+ <header className="flex items-center justify-between gap-3 border-b border-border pb-4">
1279
+ <div className="min-w-0">
1280
+ <h1 className="text-base font-semibold text-foreground">Inference</h1>
1281
+ <p className="truncate text-xs text-muted-foreground">
1282
+ {network ? (
1283
+ <>Signed from your wallet on {network} · {feeLcai != null ? feeLcai + " LCAI" : "..."}/call + gas</>
1284
+ ) : (
1285
+ "Connect a wallet on LightChain to start"
1286
+ )}
1287
+ </p>
1275
1288
  </div>
1276
- )}
1289
+ <ConnectButton />
1290
+ </header>
1277
1291
 
1278
- <label style={{ display: "block", margin: "12px 0" }}>
1279
- <div style={{ fontSize: 11, textTransform: "uppercase", color: "#888", marginBottom: 4 }}>System prompt</div>
1280
- <textarea value={system} onChange={(e) => setSystem(e.target.value)} rows={2}
1281
- style={{ width: "100%", padding: 8, fontFamily: "monospace", fontSize: 12 }} />
1282
- </label>
1283
- <label style={{ display: "block", margin: "12px 0" }}>
1284
- <div style={{ fontSize: 11, textTransform: "uppercase", color: "#888", marginBottom: 4 }}>Prompt</div>
1285
- <textarea value={prompt} onChange={(e) => setPrompt(e.target.value)} rows={5}
1286
- style={{ width: "100%", padding: 8, fontFamily: "monospace", fontSize: 12 }} />
1287
- </label>
1288
- <button type="button" onClick={() => run()} disabled={busy || !prompt.trim() || !address || !network}
1289
- style={{ padding: "8px 16px", borderRadius: 8, cursor: busy ? "wait" : "pointer" }}>
1290
- {busy ? (busyStage || "Running...") : "Run inference"}
1291
- </button>
1292
+ <div className="flex flex-col gap-4 py-6">
1293
+ {!address && (
1294
+ <div className="flex items-center justify-between gap-3 rounded-xl border border-border bg-card px-4 py-3 text-sm text-muted-foreground">
1295
+ <span>Connect a wallet to run inference.</span>
1296
+ <ConnectButton />
1297
+ </div>
1298
+ )}
1292
1299
 
1293
- {err && (
1294
- <p style={{ marginTop: 12, padding: "8px 12px", border: "1px solid #f5c2c7", background: "#f8d7da", color: "#842029", borderRadius: 6 }}>
1295
- {err}
1296
- </p>
1297
- )}
1300
+ <label className="flex flex-col gap-1.5">
1301
+ <span className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">System prompt</span>
1302
+ <textarea value={system} onChange={(e) => setSystem(e.target.value)} rows={2}
1303
+ className="resize-none rounded-xl border border-border bg-card px-3 py-2 font-mono text-xs text-foreground outline-none focus:ring-2 focus:ring-primary" />
1304
+ </label>
1305
+ <label className="flex flex-col gap-1.5">
1306
+ <span className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">Prompt</span>
1307
+ <textarea value={prompt} onChange={(e) => setPrompt(e.target.value)} rows={5}
1308
+ className="resize-none rounded-xl border border-border bg-card px-3 py-2 font-mono text-xs text-foreground outline-none focus:ring-2 focus:ring-primary" />
1309
+ </label>
1310
+
1311
+ <button type="button" onClick={() => run()} disabled={busy || !prompt.trim() || !address || !network}
1312
+ className="self-start rounded-xl bg-gradient-primary px-4 py-2 text-sm font-medium text-white transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40">
1313
+ {busy ? (busyStage || "Running...") : "Run inference"}
1314
+ </button>
1298
1315
 
1299
- {result && (
1300
- <div style={{ marginTop: 16, padding: 16, border: "1px solid #ddd", borderRadius: 8 }}>
1301
- <div style={{ fontSize: 11, textTransform: "uppercase", color: "#888", marginBottom: 8 }}>Answer</div>
1302
- <pre style={{ whiteSpace: "pre-wrap", margin: 0, fontFamily: "inherit" }}>{result.answer}</pre>
1303
- <div style={{ marginTop: 12, fontSize: 12, color: "#666", display: "flex", gap: 12, flexWrap: "wrap" }}>
1304
- <span>elapsed {Math.round(result.elapsedMs / 1000)}s</span>
1305
- <span>job #{result.jobId}</span>
1306
- <a href={\`https://\${network}.lightscan.app/address/\${result.worker}\`} target="_blank" rel="noopener noreferrer">worker</a>
1307
- <a href={\`https://\${network}.lightscan.app/tx/\${result.submitJob}\`} target="_blank" rel="noopener noreferrer">submitJob</a>
1308
- {result.jobCompleted && (
1309
- <a href={\`https://\${network}.lightscan.app/tx/\${result.jobCompleted}\`} target="_blank" rel="noopener noreferrer">jobCompleted</a>
1310
- )}
1316
+ {err && (
1317
+ <p className="rounded-lg border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">
1318
+ {err}
1319
+ </p>
1320
+ )}
1321
+
1322
+ {result && (
1323
+ <div className="rounded-2xl border border-border bg-card p-4">
1324
+ <div className="mb-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">Answer</div>
1325
+ <pre className="m-0 whitespace-pre-wrap break-words font-sans text-sm text-foreground">{result.answer}</pre>
1326
+ <div className="mt-3 flex flex-wrap gap-3 text-xs text-muted-foreground">
1327
+ <span>elapsed {Math.round(result.elapsedMs / 1000)}s</span>
1328
+ <span>job #{result.jobId}</span>
1329
+ <a className="hover:text-foreground hover:underline" href={\`https://\${network}.lightscan.app/address/\${result.worker}\`} target="_blank" rel="noopener noreferrer">worker</a>
1330
+ <a className="hover:text-foreground hover:underline" href={\`https://\${network}.lightscan.app/tx/\${result.submitJob}\`} target="_blank" rel="noopener noreferrer">submitJob</a>
1331
+ {result.jobCompleted && (
1332
+ <a className="hover:text-foreground hover:underline" href={\`https://\${network}.lightscan.app/tx/\${result.jobCompleted}\`} target="_blank" rel="noopener noreferrer">jobCompleted</a>
1333
+ )}
1334
+ </div>
1311
1335
  </div>
1312
- </div>
1313
- )}
1336
+ )}
1337
+ </div>
1314
1338
  </main>
1315
1339
  );
1316
1340
  }
@@ -1443,72 +1467,80 @@ Reply with STRICT JSON only, matching: { "passed": boolean, "confidence": 0-1, "
1443
1467
  }
1444
1468
 
1445
1469
  return (
1446
- <main style={{ maxWidth: 720, margin: "32px auto", padding: 16, fontFamily: "system-ui" }}>
1447
- <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }}>
1448
- <h1>AI Judge (user-pays)</h1>
1449
- <ConnectButton />
1450
- </div>
1451
- <p style={{ color: "#666" }}>
1452
- Each submission signs one inference from your wallet on{" "}
1453
- <code>{network ?? "(connect a wallet)"}</code>. Cost:{" "}
1454
- <code>{feeLcai != null ? \`\${feeLcai} LCAI\` : "(fetching)"}</code> plus gas. Verdict comes back with on-chain proof.
1455
- </p>
1456
- {!address && (
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 />
1470
+ <main className="mx-auto flex min-h-screen w-full max-w-2xl flex-col px-4 py-6">
1471
+ <header className="flex items-center justify-between gap-3 border-b border-border pb-4">
1472
+ <div className="min-w-0">
1473
+ <h1 className="text-base font-semibold text-foreground">AI Judge</h1>
1474
+ <p className="truncate text-xs text-muted-foreground">
1475
+ {network ? (
1476
+ <>Signed from your wallet on {network} · {feeLcai != null ? feeLcai + " LCAI" : "..."} + gas · verdict has on-chain proof</>
1477
+ ) : (
1478
+ "Connect a wallet on LightChain to start"
1479
+ )}
1480
+ </p>
1460
1481
  </div>
1461
- )}
1482
+ <ConnectButton />
1483
+ </header>
1462
1484
 
1463
- <label style={{ display: "block", margin: "12px 0" }}>
1464
- <div style={{ fontSize: 11, textTransform: "uppercase", color: "#888", marginBottom: 4 }}>Criteria</div>
1465
- <textarea value={criteria} onChange={(e) => setCriteria(e.target.value)} rows={2}
1466
- style={{ width: "100%", padding: 8, fontFamily: "monospace", fontSize: 12 }} />
1467
- </label>
1468
- <label style={{ display: "block", margin: "12px 0" }}>
1469
- <div style={{ fontSize: 11, textTransform: "uppercase", color: "#888", marginBottom: 4 }}>Evidence (JSON)</div>
1470
- <textarea value={evidence} onChange={(e) => setEvidence(e.target.value)} rows={5}
1471
- style={{ width: "100%", padding: 8, fontFamily: "monospace", fontSize: 12 }} />
1472
- </label>
1473
- <button type="button" onClick={() => run()} disabled={busy || !criteria.trim() || !evidence.trim() || !address || !network}
1474
- style={{ padding: "8px 16px", borderRadius: 8, cursor: busy ? "wait" : "pointer" }}>
1475
- {busy ? (busyStage || "Judging...") : "Get AI verdict"}
1476
- </button>
1485
+ <div className="flex flex-col gap-4 py-6">
1486
+ {!address && (
1487
+ <div className="flex items-center justify-between gap-3 rounded-xl border border-border bg-card px-4 py-3 text-sm text-muted-foreground">
1488
+ <span>Connect a wallet to submit.</span>
1489
+ <ConnectButton />
1490
+ </div>
1491
+ )}
1477
1492
 
1478
- {err && (
1479
- <p style={{ marginTop: 12, padding: "8px 12px", border: "1px solid #f5c2c7", background: "#f8d7da", color: "#842029", borderRadius: 6 }}>
1480
- {err}
1481
- </p>
1482
- )}
1493
+ <label className="flex flex-col gap-1.5">
1494
+ <span className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">Criteria</span>
1495
+ <textarea value={criteria} onChange={(e) => setCriteria(e.target.value)} rows={2}
1496
+ className="resize-none rounded-xl border border-border bg-card px-3 py-2 font-mono text-xs text-foreground outline-none focus:ring-2 focus:ring-primary" />
1497
+ </label>
1498
+ <label className="flex flex-col gap-1.5">
1499
+ <span className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">Evidence (JSON)</span>
1500
+ <textarea value={evidence} onChange={(e) => setEvidence(e.target.value)} rows={5}
1501
+ className="resize-none rounded-xl border border-border bg-card px-3 py-2 font-mono text-xs text-foreground outline-none focus:ring-2 focus:ring-primary" />
1502
+ </label>
1503
+
1504
+ <button type="button" onClick={() => run()} disabled={busy || !criteria.trim() || !evidence.trim() || !address || !network}
1505
+ className="self-start rounded-xl bg-gradient-primary px-4 py-2 text-sm font-medium text-white transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40">
1506
+ {busy ? (busyStage || "Judging...") : "Get AI verdict"}
1507
+ </button>
1483
1508
 
1484
- {result && (
1485
- <div style={{ marginTop: 16, padding: 16, border: "1px solid #ddd", borderRadius: 8 }}>
1486
- <div style={{ fontSize: 11, textTransform: "uppercase", color: "#888", marginBottom: 8 }}>Verdict</div>
1487
- {result.verdict ? (
1488
- <div>
1489
- <div style={{ fontSize: 24, fontWeight: 600, color: result.verdict.passed ? "#2e7d32" : "#c62828" }}>
1490
- {result.verdict.passed ? "PASSED" : "FAILED"}
1491
- <span style={{ marginLeft: 12, fontSize: 14, color: "#666" }}>
1492
- confidence {Math.round(result.verdict.confidence * 100)}%
1493
- </span>
1509
+ {err && (
1510
+ <p className="rounded-lg border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">
1511
+ {err}
1512
+ </p>
1513
+ )}
1514
+
1515
+ {result && (
1516
+ <div className="rounded-2xl border border-border bg-card p-4">
1517
+ <div className="mb-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">Verdict</div>
1518
+ {result.verdict ? (
1519
+ <div>
1520
+ <div className={"flex items-baseline gap-3 text-2xl font-semibold " + (result.verdict.passed ? "text-success" : "text-destructive")}>
1521
+ {result.verdict.passed ? "PASSED" : "FAILED"}
1522
+ <span className="text-sm font-normal text-muted-foreground">
1523
+ confidence {Math.round(result.verdict.confidence * 100)}%
1524
+ </span>
1525
+ </div>
1526
+ <p className="mt-2 text-sm text-foreground">{result.verdict.reason}</p>
1494
1527
  </div>
1495
- <p style={{ marginTop: 8, color: "#444" }}>{result.verdict.reason}</p>
1496
- </div>
1497
- ) : (
1498
- <pre style={{ whiteSpace: "pre-wrap", margin: 0, fontFamily: "monospace", fontSize: 12, color: "#666" }}>
1499
- {result.raw}
1500
- </pre>
1501
- )}
1502
- <div style={{ marginTop: 12, fontSize: 12, color: "#666", display: "flex", gap: 12, flexWrap: "wrap" }}>
1503
- <span>job #{result.jobId}</span>
1504
- <a href={\`https://\${network}.lightscan.app/address/\${result.worker}\`} target="_blank" rel="noopener noreferrer">worker</a>
1505
- <a href={\`https://\${network}.lightscan.app/tx/\${result.submitJob}\`} target="_blank" rel="noopener noreferrer">submitJob</a>
1506
- {result.jobCompleted && (
1507
- <a href={\`https://\${network}.lightscan.app/tx/\${result.jobCompleted}\`} target="_blank" rel="noopener noreferrer">jobCompleted</a>
1528
+ ) : (
1529
+ <pre className="m-0 whitespace-pre-wrap break-words font-mono text-xs text-muted-foreground">
1530
+ {result.raw}
1531
+ </pre>
1508
1532
  )}
1533
+ <div className="mt-3 flex flex-wrap gap-3 text-xs text-muted-foreground">
1534
+ <span>job #{result.jobId}</span>
1535
+ <a className="hover:text-foreground hover:underline" href={\`https://\${network}.lightscan.app/address/\${result.worker}\`} target="_blank" rel="noopener noreferrer">worker</a>
1536
+ <a className="hover:text-foreground hover:underline" href={\`https://\${network}.lightscan.app/tx/\${result.submitJob}\`} target="_blank" rel="noopener noreferrer">submitJob</a>
1537
+ {result.jobCompleted && (
1538
+ <a className="hover:text-foreground hover:underline" href={\`https://\${network}.lightscan.app/tx/\${result.jobCompleted}\`} target="_blank" rel="noopener noreferrer">jobCompleted</a>
1539
+ )}
1540
+ </div>
1509
1541
  </div>
1510
- </div>
1511
- )}
1542
+ )}
1543
+ </div>
1512
1544
  </main>
1513
1545
  );
1514
1546
  }
@@ -2268,6 +2300,17 @@ body::before {
2268
2300
  radial-gradient(45% 45% at 50% 118%, rgba(112, 100, 233, 0.10), transparent 60%);
2269
2301
  }
2270
2302
 
2303
+ /* signature lcai gradient (primary buttons / accents) */
2304
+ .bg-gradient-primary {
2305
+ background-image: var(--color-gradient-primary);
2306
+ }
2307
+ .text-gradient {
2308
+ background: linear-gradient(94deg, #dd00ac 10%, #7130c3 53%, #7064e9 96%);
2309
+ -webkit-background-clip: text;
2310
+ background-clip: text;
2311
+ color: transparent;
2312
+ }
2313
+
2271
2314
  /* minimal scrollbar */
2272
2315
  ::-webkit-scrollbar {
2273
2316
  width: 6px;
@@ -2344,6 +2387,44 @@ function setDarkDefaultOnLayout(cwd) {
2344
2387
  }
2345
2388
  return true;
2346
2389
  }
2390
+ /** One-line description of what each scaffold target gives the dev. */
2391
+ const SCAFFOLD_README_WHAT = {
2392
+ "chat-web3": "A self-contained, wallet-signed chat you can drop into any project - for example, to give an agent a chat UI.",
2393
+ "inference-web3": "A self-contained, wallet-signed one-shot inference page you can drop into any project.",
2394
+ "judge-web3": "A self-contained, wallet-signed pass/fail evaluator page you can drop into any project.",
2395
+ };
2396
+ /** A real README for a freshly scaffolded app, replacing the create-next-app
2397
+ * default so the dev knows what they got and how to use it. */
2398
+ function scaffoldReadme(target, dir) {
2399
+ const what = SCAFFOLD_README_WHAT[target] ?? "A self-contained, wallet-signed page.";
2400
+ return `# LightNode ${dir}
2401
+
2402
+ Generated by \`lightnode add ${target}\`. ${what}
2403
+ No backend, no database, no API keys: each visitor signs and pays for their own
2404
+ turns from their own wallet on LightChain.
2405
+
2406
+ ## Run it
2407
+
2408
+ npm run dev
2409
+
2410
+ Open http://localhost:3000 and click **Connect wallet** (LightChain mainnet
2411
+ 9200 or testnet 8200). Free testnet LCAI: https://lightfaucet.ai
2412
+
2413
+ ## Where things live
2414
+
2415
+ - \`app/page.tsx\` - re-exports the page below as the homepage
2416
+ - \`app/${dir}/page.tsx\` - the UI (also served at /${dir}). Edit this.
2417
+ - \`app/providers.tsx\` + \`lib/wagmi.ts\` - wagmi + React Query setup
2418
+ - \`components/connect-button.tsx\` - the Connect wallet button
2419
+ - \`app/globals.css\` - the theme (light + dark design tokens)
2420
+
2421
+ ## Customize
2422
+
2423
+ It is a normal React client component using \`lightnode-sdk\` wired to the
2424
+ connected wallet. Change the model or system prompt, restyle it, or call it
2425
+ from your own agent. Builder docs: https://lightnode.app/build
2426
+ `;
2427
+ }
2347
2428
  /**
2348
2429
  * Wire a freshly scaffolded Next.js app so the generated -web3 page is the
2349
2430
  * homepage and the LightChain theme + dark default are in place. No-op for any
@@ -2359,7 +2440,9 @@ export function wireFreshScaffold(target, opts = {}) {
2359
2440
  written.push(writeFile(path.join(cwd, "app/globals.css"), SCAFFOLD_GLOBALS_CSS, true));
2360
2441
  // 2. Make the generated page the homepage (replaces the starter page.tsx).
2361
2442
  written.push(writeFile(path.join(cwd, "app/page.tsx"), homepageReexport(dir, target), true));
2362
- // 3. Keep dark as the default theme (matches lightnode.app).
2443
+ // 3. Replace create-next-app's default README with one about this scaffold.
2444
+ written.push(writeFile(path.join(cwd, "README.md"), scaffoldReadme(target, dir), true));
2445
+ // 4. Keep dark as the default theme (matches lightnode.app).
2363
2446
  const darkDefault = setDarkDefaultOnLayout(cwd);
2364
2447
  return { written, homepageRoute: `/${dir}`, darkDefault };
2365
2448
  }
package/dist/cli.js CHANGED
@@ -47,6 +47,10 @@ function scaffoldNextApp(cwd, target) {
47
47
  "--yes", "create-next-app@latest", stageName,
48
48
  "--ts", "--app", "--no-src-dir", "--eslint", "--tailwind",
49
49
  "--use-npm", "--no-turbopack", "--import-alias", "@/*",
50
+ // Don't dump create-next-app's AGENTS.md (the nextjs-agent-rules block)
51
+ // into the user's project. Supported by current create-next-app, which is
52
+ // what `@latest` resolves to here.
53
+ "--no-agents-md",
50
54
  ];
51
55
  const r = spawnSync("npx", args, { cwd, stdio: "inherit" });
52
56
  if (r.status !== 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightnode-sdk",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
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",