agent-hustle-demo 1.0.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 +429 -0
- package/dist/HustleChat-BC9wvWVA.d.ts +90 -0
- package/dist/HustleChat-BcrKkkyn.d.cts +90 -0
- package/dist/browser/hustle-react.js +14854 -0
- package/dist/browser/hustle-react.js.map +1 -0
- package/dist/components/index.cjs +3141 -0
- package/dist/components/index.cjs.map +1 -0
- package/dist/components/index.d.cts +20 -0
- package/dist/components/index.d.ts +20 -0
- package/dist/components/index.js +3112 -0
- package/dist/components/index.js.map +1 -0
- package/dist/hooks/index.cjs +845 -0
- package/dist/hooks/index.cjs.map +1 -0
- package/dist/hooks/index.d.cts +6 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/index.js +838 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hustle-Kj0X8qXC.d.cts +193 -0
- package/dist/hustle-Kj0X8qXC.d.ts +193 -0
- package/dist/index-ChUsRBwL.d.ts +152 -0
- package/dist/index-DE1N7C3W.d.cts +152 -0
- package/dist/index-DuPFrMZy.d.cts +214 -0
- package/dist/index-kFIdHjNw.d.ts +214 -0
- package/dist/index.cjs +3746 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +271 -0
- package/dist/index.d.ts +271 -0
- package/dist/index.js +3697 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/index.cjs +844 -0
- package/dist/providers/index.cjs.map +1 -0
- package/dist/providers/index.d.cts +5 -0
- package/dist/providers/index.d.ts +5 -0
- package/dist/providers/index.js +838 -0
- package/dist/providers/index.js.map +1 -0
- package/package.json +80 -0
- package/src/components/AuthStatus.tsx +352 -0
- package/src/components/ConnectButton.tsx +421 -0
- package/src/components/HustleChat.tsx +1273 -0
- package/src/components/MarkdownContent.tsx +431 -0
- package/src/components/index.ts +15 -0
- package/src/hooks/index.ts +40 -0
- package/src/hooks/useEmblemAuth.ts +27 -0
- package/src/hooks/useHustle.ts +36 -0
- package/src/hooks/usePlugins.ts +135 -0
- package/src/index.ts +142 -0
- package/src/plugins/index.ts +48 -0
- package/src/plugins/migrateFun.ts +211 -0
- package/src/plugins/predictionMarket.ts +411 -0
- package/src/providers/EmblemAuthProvider.tsx +319 -0
- package/src/providers/HustleProvider.tsx +540 -0
- package/src/providers/index.ts +6 -0
- package/src/styles/index.ts +2 -0
- package/src/styles/tokens.ts +447 -0
- package/src/types/auth.ts +85 -0
- package/src/types/hustle.ts +217 -0
- package/src/types/index.ts +49 -0
- package/src/types/plugin.ts +180 -0
- package/src/utils/index.ts +122 -0
- package/src/utils/pluginRegistry.ts +375 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useCallback, useState } from 'react';
|
|
4
|
+
import { useEmblemAuth } from '../providers/EmblemAuthProvider';
|
|
5
|
+
import { tokens, presets, animations } from '../styles';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Props for ConnectButton component
|
|
9
|
+
*/
|
|
10
|
+
export interface ConnectButtonProps {
|
|
11
|
+
/** Additional CSS classes */
|
|
12
|
+
className?: string;
|
|
13
|
+
/** Additional inline styles */
|
|
14
|
+
style?: React.CSSProperties;
|
|
15
|
+
/** Custom content when disconnected */
|
|
16
|
+
connectLabel?: React.ReactNode;
|
|
17
|
+
/** Custom content while loading */
|
|
18
|
+
loadingLabel?: React.ReactNode;
|
|
19
|
+
/** Callback after successful connection */
|
|
20
|
+
onConnect?: () => void;
|
|
21
|
+
/** Callback after disconnection */
|
|
22
|
+
onDisconnect?: () => void;
|
|
23
|
+
/** Show vault info dropdown when connected */
|
|
24
|
+
showVaultInfo?: boolean;
|
|
25
|
+
/** Disable the button */
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Truncate wallet address for display
|
|
31
|
+
*/
|
|
32
|
+
function truncateAddress(address: string): string {
|
|
33
|
+
if (!address || address.length < 10) return address || '';
|
|
34
|
+
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Copy text to clipboard
|
|
39
|
+
*/
|
|
40
|
+
async function copyToClipboard(text: string): Promise<boolean> {
|
|
41
|
+
try {
|
|
42
|
+
await navigator.clipboard.writeText(text);
|
|
43
|
+
return true;
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Styles using design tokens
|
|
50
|
+
const styles = {
|
|
51
|
+
wrapper: {
|
|
52
|
+
position: 'relative' as const,
|
|
53
|
+
display: 'inline-flex',
|
|
54
|
+
alignItems: 'center',
|
|
55
|
+
gap: tokens.spacing.sm,
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
button: {
|
|
59
|
+
...presets.button,
|
|
60
|
+
padding: `${tokens.spacing.sm} ${tokens.spacing.xl}`,
|
|
61
|
+
} as React.CSSProperties,
|
|
62
|
+
|
|
63
|
+
disconnected: {
|
|
64
|
+
background: tokens.colors.bgTertiary,
|
|
65
|
+
color: tokens.colors.textPrimary,
|
|
66
|
+
borderColor: tokens.colors.borderSecondary,
|
|
67
|
+
} as React.CSSProperties,
|
|
68
|
+
|
|
69
|
+
disconnectedHover: {
|
|
70
|
+
background: tokens.colors.bgHover,
|
|
71
|
+
borderColor: tokens.colors.borderHover,
|
|
72
|
+
} as React.CSSProperties,
|
|
73
|
+
|
|
74
|
+
connected: {
|
|
75
|
+
background: 'transparent',
|
|
76
|
+
color: tokens.colors.accentSuccess,
|
|
77
|
+
borderColor: tokens.colors.accentSuccess,
|
|
78
|
+
borderRadius: tokens.radius.pill,
|
|
79
|
+
} as React.CSSProperties,
|
|
80
|
+
|
|
81
|
+
connectedHover: {
|
|
82
|
+
background: tokens.colors.accentSuccessBg,
|
|
83
|
+
} as React.CSSProperties,
|
|
84
|
+
|
|
85
|
+
loading: {
|
|
86
|
+
background: tokens.colors.borderSecondary,
|
|
87
|
+
color: tokens.colors.textSecondary,
|
|
88
|
+
cursor: 'wait',
|
|
89
|
+
} as React.CSSProperties,
|
|
90
|
+
|
|
91
|
+
disabled: {
|
|
92
|
+
background: tokens.colors.borderSecondary,
|
|
93
|
+
color: tokens.colors.textTertiary,
|
|
94
|
+
cursor: 'not-allowed',
|
|
95
|
+
opacity: 0.5,
|
|
96
|
+
} as React.CSSProperties,
|
|
97
|
+
|
|
98
|
+
icon: {
|
|
99
|
+
fontSize: tokens.typography.fontSizeLg,
|
|
100
|
+
} as React.CSSProperties,
|
|
101
|
+
|
|
102
|
+
spinner: {
|
|
103
|
+
display: 'inline-block',
|
|
104
|
+
width: '14px',
|
|
105
|
+
height: '14px',
|
|
106
|
+
border: '2px solid currentColor',
|
|
107
|
+
borderTopColor: 'transparent',
|
|
108
|
+
borderRadius: tokens.radius.full,
|
|
109
|
+
animation: 'hustle-spin 0.8s linear infinite',
|
|
110
|
+
} as React.CSSProperties,
|
|
111
|
+
|
|
112
|
+
address: {
|
|
113
|
+
...presets.mono,
|
|
114
|
+
color: tokens.colors.textPrimary,
|
|
115
|
+
} as React.CSSProperties,
|
|
116
|
+
|
|
117
|
+
dot: {
|
|
118
|
+
color: tokens.colors.textSecondary,
|
|
119
|
+
} as React.CSSProperties,
|
|
120
|
+
|
|
121
|
+
check: {
|
|
122
|
+
color: tokens.colors.accentSuccess,
|
|
123
|
+
} as React.CSSProperties,
|
|
124
|
+
|
|
125
|
+
arrow: {
|
|
126
|
+
fontSize: '10px',
|
|
127
|
+
color: tokens.colors.textSecondary,
|
|
128
|
+
marginLeft: tokens.spacing.xs,
|
|
129
|
+
} as React.CSSProperties,
|
|
130
|
+
|
|
131
|
+
// Disconnect button
|
|
132
|
+
disconnectBtn: {
|
|
133
|
+
display: 'flex',
|
|
134
|
+
alignItems: 'center',
|
|
135
|
+
justifyContent: 'center',
|
|
136
|
+
width: '36px',
|
|
137
|
+
height: '36px',
|
|
138
|
+
background: 'transparent',
|
|
139
|
+
border: `1px solid ${tokens.colors.borderSecondary}`,
|
|
140
|
+
borderRadius: tokens.radius.lg,
|
|
141
|
+
color: tokens.colors.textSecondary,
|
|
142
|
+
cursor: 'pointer',
|
|
143
|
+
fontSize: '16px',
|
|
144
|
+
transition: `all ${tokens.transitions.normal}`,
|
|
145
|
+
} as React.CSSProperties,
|
|
146
|
+
|
|
147
|
+
disconnectBtnHover: {
|
|
148
|
+
borderColor: tokens.colors.accentError,
|
|
149
|
+
color: tokens.colors.accentError,
|
|
150
|
+
} as React.CSSProperties,
|
|
151
|
+
|
|
152
|
+
// Vault info dropdown
|
|
153
|
+
dropdown: {
|
|
154
|
+
position: 'absolute' as const,
|
|
155
|
+
top: '100%',
|
|
156
|
+
left: 0,
|
|
157
|
+
marginTop: tokens.spacing.xs,
|
|
158
|
+
background: tokens.colors.bgPrimary,
|
|
159
|
+
border: `1px solid ${tokens.colors.accentSuccess}`,
|
|
160
|
+
borderRadius: tokens.radius.xl,
|
|
161
|
+
padding: tokens.spacing.lg,
|
|
162
|
+
minWidth: '300px',
|
|
163
|
+
zIndex: tokens.zIndex.dropdown,
|
|
164
|
+
boxShadow: `0 8px 32px rgba(0,0,0,0.4), 0 0 0 1px ${tokens.colors.accentSuccessBg}`,
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
dropdownHeader: {
|
|
168
|
+
fontSize: tokens.typography.fontSizeXs,
|
|
169
|
+
fontWeight: tokens.typography.fontWeightSemibold,
|
|
170
|
+
color: tokens.colors.textSecondary,
|
|
171
|
+
letterSpacing: '0.5px',
|
|
172
|
+
marginBottom: tokens.spacing.lg,
|
|
173
|
+
textTransform: 'uppercase' as const,
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
dropdownRow: {
|
|
177
|
+
marginBottom: tokens.spacing.md,
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
dropdownLabel: {
|
|
181
|
+
display: 'block',
|
|
182
|
+
fontSize: tokens.typography.fontSizeXs,
|
|
183
|
+
color: tokens.colors.textTertiary,
|
|
184
|
+
marginBottom: tokens.spacing.xs,
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
dropdownValueRow: {
|
|
188
|
+
display: 'flex',
|
|
189
|
+
alignItems: 'center',
|
|
190
|
+
justifyContent: 'space-between',
|
|
191
|
+
gap: tokens.spacing.sm,
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
dropdownValue: {
|
|
195
|
+
fontSize: tokens.typography.fontSizeMd,
|
|
196
|
+
color: tokens.colors.textPrimary,
|
|
197
|
+
fontWeight: tokens.typography.fontWeightMedium,
|
|
198
|
+
flex: 1,
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
dropdownValueMono: {
|
|
202
|
+
...presets.mono,
|
|
203
|
+
wordBreak: 'break-all' as const,
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
copyBtn: {
|
|
207
|
+
background: 'transparent',
|
|
208
|
+
border: `1px solid ${tokens.colors.borderSecondary}`,
|
|
209
|
+
color: tokens.colors.textSecondary,
|
|
210
|
+
padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,
|
|
211
|
+
borderRadius: tokens.radius.sm,
|
|
212
|
+
cursor: 'pointer',
|
|
213
|
+
fontSize: tokens.typography.fontSizeXs,
|
|
214
|
+
transition: `all ${tokens.transitions.normal}`,
|
|
215
|
+
whiteSpace: 'nowrap' as const,
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
copyBtnHover: {
|
|
219
|
+
background: tokens.colors.bgHover,
|
|
220
|
+
borderColor: tokens.colors.accentPrimary,
|
|
221
|
+
color: tokens.colors.accentPrimary,
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
copyBtnCopied: {
|
|
225
|
+
background: tokens.colors.accentSuccess,
|
|
226
|
+
borderColor: tokens.colors.accentSuccess,
|
|
227
|
+
color: tokens.colors.textInverse,
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* ConnectButton - A button to trigger Emblem Auth connection
|
|
233
|
+
* When connected, shows vault info dropdown and separate disconnect button
|
|
234
|
+
*/
|
|
235
|
+
export function ConnectButton({
|
|
236
|
+
className = '',
|
|
237
|
+
style,
|
|
238
|
+
connectLabel = 'Connect',
|
|
239
|
+
loadingLabel = 'Connecting...',
|
|
240
|
+
onConnect,
|
|
241
|
+
onDisconnect,
|
|
242
|
+
showVaultInfo = true,
|
|
243
|
+
disabled = false,
|
|
244
|
+
}: ConnectButtonProps) {
|
|
245
|
+
const {
|
|
246
|
+
isAuthenticated,
|
|
247
|
+
isLoading,
|
|
248
|
+
walletAddress,
|
|
249
|
+
vaultId,
|
|
250
|
+
openAuthModal,
|
|
251
|
+
logout,
|
|
252
|
+
} = useEmblemAuth();
|
|
253
|
+
|
|
254
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
255
|
+
const [showDropdown, setShowDropdown] = useState(false);
|
|
256
|
+
const [disconnectHovered, setDisconnectHovered] = useState(false);
|
|
257
|
+
const [copiedField, setCopiedField] = useState<string | null>(null);
|
|
258
|
+
const [copyHovered, setCopyHovered] = useState<string | null>(null);
|
|
259
|
+
|
|
260
|
+
const handleClick = useCallback(async () => {
|
|
261
|
+
if (disabled) return;
|
|
262
|
+
|
|
263
|
+
if (!isAuthenticated && !isLoading) {
|
|
264
|
+
await openAuthModal();
|
|
265
|
+
onConnect?.();
|
|
266
|
+
}
|
|
267
|
+
// When connected, clicking toggles dropdown (handled by hover)
|
|
268
|
+
}, [disabled, isAuthenticated, isLoading, openAuthModal, onConnect]);
|
|
269
|
+
|
|
270
|
+
const handleDisconnect = useCallback(() => {
|
|
271
|
+
logout();
|
|
272
|
+
onDisconnect?.();
|
|
273
|
+
setShowDropdown(false);
|
|
274
|
+
}, [logout, onDisconnect]);
|
|
275
|
+
|
|
276
|
+
const handleCopy = useCallback(async (field: string, value: string) => {
|
|
277
|
+
const success = await copyToClipboard(value);
|
|
278
|
+
if (success) {
|
|
279
|
+
setCopiedField(field);
|
|
280
|
+
setTimeout(() => setCopiedField(null), 1500);
|
|
281
|
+
}
|
|
282
|
+
}, []);
|
|
283
|
+
|
|
284
|
+
// Build style based on state
|
|
285
|
+
let buttonStyle: React.CSSProperties = { ...styles.button };
|
|
286
|
+
let content: React.ReactNode = connectLabel;
|
|
287
|
+
|
|
288
|
+
if (disabled) {
|
|
289
|
+
buttonStyle = { ...buttonStyle, ...styles.disconnected, ...styles.disabled };
|
|
290
|
+
} else if (isLoading) {
|
|
291
|
+
buttonStyle = { ...buttonStyle, ...styles.disconnected, ...styles.loading };
|
|
292
|
+
content = (
|
|
293
|
+
<>
|
|
294
|
+
<span style={styles.spinner} />
|
|
295
|
+
{loadingLabel}
|
|
296
|
+
</>
|
|
297
|
+
);
|
|
298
|
+
} else if (isAuthenticated) {
|
|
299
|
+
buttonStyle = { ...buttonStyle, ...styles.connected };
|
|
300
|
+
if (isHovered || showDropdown) {
|
|
301
|
+
buttonStyle = { ...buttonStyle, ...styles.connectedHover };
|
|
302
|
+
}
|
|
303
|
+
const truncated = truncateAddress(walletAddress || '');
|
|
304
|
+
|
|
305
|
+
content = (
|
|
306
|
+
<>
|
|
307
|
+
<span style={styles.check}>✓</span>
|
|
308
|
+
<span>Connected</span>
|
|
309
|
+
<span style={styles.dot}>•</span>
|
|
310
|
+
<span style={styles.address}>{truncated}</span>
|
|
311
|
+
<span style={styles.arrow}>▾</span>
|
|
312
|
+
</>
|
|
313
|
+
);
|
|
314
|
+
} else {
|
|
315
|
+
buttonStyle = { ...buttonStyle, ...styles.disconnected };
|
|
316
|
+
if (isHovered) {
|
|
317
|
+
buttonStyle = { ...buttonStyle, ...styles.disconnectedHover };
|
|
318
|
+
}
|
|
319
|
+
content = (
|
|
320
|
+
<>
|
|
321
|
+
<span style={styles.icon}>→</span>
|
|
322
|
+
{connectLabel}
|
|
323
|
+
</>
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Merge with passed style
|
|
328
|
+
if (style) {
|
|
329
|
+
buttonStyle = { ...buttonStyle, ...style };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Render copy button helper
|
|
333
|
+
const renderCopyBtn = (field: string, value: string) => {
|
|
334
|
+
const isCopied = copiedField === field;
|
|
335
|
+
const isHover = copyHovered === field;
|
|
336
|
+
return (
|
|
337
|
+
<button
|
|
338
|
+
type="button"
|
|
339
|
+
onClick={(e) => { e.stopPropagation(); handleCopy(field, value); }}
|
|
340
|
+
style={{
|
|
341
|
+
...styles.copyBtn,
|
|
342
|
+
...(isCopied ? styles.copyBtnCopied : isHover ? styles.copyBtnHover : {}),
|
|
343
|
+
}}
|
|
344
|
+
onMouseEnter={() => setCopyHovered(field)}
|
|
345
|
+
onMouseLeave={() => setCopyHovered(null)}
|
|
346
|
+
>
|
|
347
|
+
{isCopied ? 'Copied!' : 'Copy'}
|
|
348
|
+
</button>
|
|
349
|
+
);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
return (
|
|
353
|
+
<>
|
|
354
|
+
<style>{animations}</style>
|
|
355
|
+
<div
|
|
356
|
+
style={styles.wrapper}
|
|
357
|
+
onMouseEnter={() => isAuthenticated && showVaultInfo && setShowDropdown(true)}
|
|
358
|
+
onMouseLeave={() => setShowDropdown(false)}
|
|
359
|
+
>
|
|
360
|
+
{/* Main button */}
|
|
361
|
+
<button
|
|
362
|
+
type="button"
|
|
363
|
+
onClick={handleClick}
|
|
364
|
+
disabled={disabled || isLoading}
|
|
365
|
+
className={className}
|
|
366
|
+
style={buttonStyle}
|
|
367
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
368
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
369
|
+
>
|
|
370
|
+
{content}
|
|
371
|
+
</button>
|
|
372
|
+
|
|
373
|
+
{/* Disconnect button (only when authenticated) */}
|
|
374
|
+
{isAuthenticated && (
|
|
375
|
+
<button
|
|
376
|
+
type="button"
|
|
377
|
+
onClick={handleDisconnect}
|
|
378
|
+
style={{
|
|
379
|
+
...styles.disconnectBtn,
|
|
380
|
+
...(disconnectHovered ? styles.disconnectBtnHover : {}),
|
|
381
|
+
}}
|
|
382
|
+
onMouseEnter={() => setDisconnectHovered(true)}
|
|
383
|
+
onMouseLeave={() => setDisconnectHovered(false)}
|
|
384
|
+
title="Disconnect"
|
|
385
|
+
>
|
|
386
|
+
⏻
|
|
387
|
+
</button>
|
|
388
|
+
)}
|
|
389
|
+
|
|
390
|
+
{/* Vault info dropdown */}
|
|
391
|
+
{isAuthenticated && showVaultInfo && showDropdown && (
|
|
392
|
+
<div style={styles.dropdown}>
|
|
393
|
+
<div style={styles.dropdownHeader}>Vault Information</div>
|
|
394
|
+
|
|
395
|
+
{/* Vault ID */}
|
|
396
|
+
<div style={styles.dropdownRow}>
|
|
397
|
+
<span style={styles.dropdownLabel}>Vault ID</span>
|
|
398
|
+
<div style={styles.dropdownValueRow}>
|
|
399
|
+
<span style={styles.dropdownValue}>#{vaultId}</span>
|
|
400
|
+
{renderCopyBtn('vaultId', vaultId || '')}
|
|
401
|
+
</div>
|
|
402
|
+
</div>
|
|
403
|
+
|
|
404
|
+
{/* Connected Wallet */}
|
|
405
|
+
<div style={{ ...styles.dropdownRow, marginBottom: 0 }}>
|
|
406
|
+
<span style={styles.dropdownLabel}>Connected Wallet</span>
|
|
407
|
+
<div style={styles.dropdownValueRow}>
|
|
408
|
+
<span style={{ ...styles.dropdownValue, ...styles.dropdownValueMono }}>
|
|
409
|
+
{walletAddress}
|
|
410
|
+
</span>
|
|
411
|
+
{renderCopyBtn('wallet', walletAddress || '')}
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
</div>
|
|
415
|
+
)}
|
|
416
|
+
</div>
|
|
417
|
+
</>
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export default ConnectButton;
|