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.
Files changed (60) hide show
  1. package/README.md +429 -0
  2. package/dist/HustleChat-BC9wvWVA.d.ts +90 -0
  3. package/dist/HustleChat-BcrKkkyn.d.cts +90 -0
  4. package/dist/browser/hustle-react.js +14854 -0
  5. package/dist/browser/hustle-react.js.map +1 -0
  6. package/dist/components/index.cjs +3141 -0
  7. package/dist/components/index.cjs.map +1 -0
  8. package/dist/components/index.d.cts +20 -0
  9. package/dist/components/index.d.ts +20 -0
  10. package/dist/components/index.js +3112 -0
  11. package/dist/components/index.js.map +1 -0
  12. package/dist/hooks/index.cjs +845 -0
  13. package/dist/hooks/index.cjs.map +1 -0
  14. package/dist/hooks/index.d.cts +6 -0
  15. package/dist/hooks/index.d.ts +6 -0
  16. package/dist/hooks/index.js +838 -0
  17. package/dist/hooks/index.js.map +1 -0
  18. package/dist/hustle-Kj0X8qXC.d.cts +193 -0
  19. package/dist/hustle-Kj0X8qXC.d.ts +193 -0
  20. package/dist/index-ChUsRBwL.d.ts +152 -0
  21. package/dist/index-DE1N7C3W.d.cts +152 -0
  22. package/dist/index-DuPFrMZy.d.cts +214 -0
  23. package/dist/index-kFIdHjNw.d.ts +214 -0
  24. package/dist/index.cjs +3746 -0
  25. package/dist/index.cjs.map +1 -0
  26. package/dist/index.d.cts +271 -0
  27. package/dist/index.d.ts +271 -0
  28. package/dist/index.js +3697 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/providers/index.cjs +844 -0
  31. package/dist/providers/index.cjs.map +1 -0
  32. package/dist/providers/index.d.cts +5 -0
  33. package/dist/providers/index.d.ts +5 -0
  34. package/dist/providers/index.js +838 -0
  35. package/dist/providers/index.js.map +1 -0
  36. package/package.json +80 -0
  37. package/src/components/AuthStatus.tsx +352 -0
  38. package/src/components/ConnectButton.tsx +421 -0
  39. package/src/components/HustleChat.tsx +1273 -0
  40. package/src/components/MarkdownContent.tsx +431 -0
  41. package/src/components/index.ts +15 -0
  42. package/src/hooks/index.ts +40 -0
  43. package/src/hooks/useEmblemAuth.ts +27 -0
  44. package/src/hooks/useHustle.ts +36 -0
  45. package/src/hooks/usePlugins.ts +135 -0
  46. package/src/index.ts +142 -0
  47. package/src/plugins/index.ts +48 -0
  48. package/src/plugins/migrateFun.ts +211 -0
  49. package/src/plugins/predictionMarket.ts +411 -0
  50. package/src/providers/EmblemAuthProvider.tsx +319 -0
  51. package/src/providers/HustleProvider.tsx +540 -0
  52. package/src/providers/index.ts +6 -0
  53. package/src/styles/index.ts +2 -0
  54. package/src/styles/tokens.ts +447 -0
  55. package/src/types/auth.ts +85 -0
  56. package/src/types/hustle.ts +217 -0
  57. package/src/types/index.ts +49 -0
  58. package/src/types/plugin.ts +180 -0
  59. package/src/utils/index.ts +122 -0
  60. 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;