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,844 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var emblemAuthSdk = require('emblem-auth-sdk');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+ var hustleIncognito = require('hustle-incognito');
7
+
8
+ var globalSDKInstance = null;
9
+ var isSDKInitializing = false;
10
+ var EmblemAuthContext = react.createContext(void 0);
11
+ function EmblemAuthProvider({
12
+ children,
13
+ appId,
14
+ apiUrl,
15
+ modalUrl,
16
+ debug = false
17
+ }) {
18
+ const [session, setSession] = react.useState(null);
19
+ const [isAuthenticated, setIsAuthenticated] = react.useState(false);
20
+ const [isLoading, setIsLoading] = react.useState(false);
21
+ const [error, setError] = react.useState(null);
22
+ const [vaultInfo, setVaultInfo] = react.useState(null);
23
+ const [authSDK, setAuthSDK] = react.useState(globalSDKInstance);
24
+ const initialized = react.useRef(false);
25
+ const log = react.useCallback(
26
+ (message, ...args) => {
27
+ if (debug) {
28
+ console.log(`[EmblemAuth] ${message}`, ...args);
29
+ }
30
+ },
31
+ [debug]
32
+ );
33
+ const fetchVaultInfo = react.useCallback(
34
+ async (sdk) => {
35
+ try {
36
+ const info = await sdk.getVaultInfo();
37
+ if (info) {
38
+ setVaultInfo(info);
39
+ log("Vault info loaded:", info);
40
+ }
41
+ } catch (err) {
42
+ log("Failed to fetch vault info:", err);
43
+ }
44
+ },
45
+ [log]
46
+ );
47
+ const handleAuthSuccess = react.useCallback(
48
+ (newSession, sdk) => {
49
+ log("Auth success - session:", newSession);
50
+ setSession(newSession);
51
+ setIsAuthenticated(true);
52
+ setIsLoading(false);
53
+ setError(null);
54
+ fetchVaultInfo(sdk);
55
+ },
56
+ [log, fetchVaultInfo]
57
+ );
58
+ const handleAuthError = react.useCallback(
59
+ (err) => {
60
+ log("Auth error:", err);
61
+ setError(err);
62
+ setIsLoading(false);
63
+ setIsAuthenticated(false);
64
+ setSession(null);
65
+ },
66
+ [log]
67
+ );
68
+ const handleSessionExpired = react.useCallback(() => {
69
+ log("Session expired");
70
+ setSession(null);
71
+ setIsAuthenticated(false);
72
+ setVaultInfo(null);
73
+ }, [log]);
74
+ react.useEffect(() => {
75
+ if (initialized.current || globalSDKInstance || isSDKInitializing) {
76
+ if (globalSDKInstance && !authSDK) {
77
+ setAuthSDK(globalSDKInstance);
78
+ const existingSession2 = globalSDKInstance.getSession();
79
+ if (existingSession2) {
80
+ handleAuthSuccess(existingSession2, globalSDKInstance);
81
+ }
82
+ }
83
+ return;
84
+ }
85
+ initialized.current = true;
86
+ isSDKInitializing = true;
87
+ log("Initializing SDK with appId:", appId);
88
+ const sdk = new emblemAuthSdk.EmblemAuthSDK({
89
+ appId,
90
+ apiUrl,
91
+ modalUrl,
92
+ onSuccess: (newSession) => {
93
+ handleAuthSuccess(newSession, sdk);
94
+ },
95
+ onError: (err) => {
96
+ handleAuthError(err);
97
+ }
98
+ });
99
+ globalSDKInstance = sdk;
100
+ isSDKInitializing = false;
101
+ setAuthSDK(sdk);
102
+ const existingSession = sdk.getSession();
103
+ if (existingSession) {
104
+ log("Found existing session");
105
+ handleAuthSuccess(existingSession, sdk);
106
+ }
107
+ const handleSessionUpdate = (updatedSession) => {
108
+ if (updatedSession) {
109
+ setSession(updatedSession);
110
+ setIsAuthenticated(true);
111
+ } else {
112
+ handleSessionExpired();
113
+ }
114
+ };
115
+ sdk.on("session", handleSessionUpdate);
116
+ sdk.on("sessionExpired", handleSessionExpired);
117
+ return () => {
118
+ sdk.off("session", handleSessionUpdate);
119
+ sdk.off("sessionExpired", handleSessionExpired);
120
+ };
121
+ }, [appId, apiUrl, modalUrl, log, handleAuthSuccess, handleAuthError, handleSessionExpired, authSDK]);
122
+ const openAuthModal = react.useCallback(async () => {
123
+ if (!authSDK) {
124
+ setError(new Error("Auth SDK not initialized"));
125
+ return;
126
+ }
127
+ log("Opening auth modal");
128
+ setIsLoading(true);
129
+ setError(null);
130
+ try {
131
+ await authSDK.openAuthModal();
132
+ } catch (err) {
133
+ setIsLoading(false);
134
+ setError(err instanceof Error ? err : new Error("Failed to open auth modal"));
135
+ }
136
+ }, [authSDK, log]);
137
+ const logout = react.useCallback(() => {
138
+ if (!authSDK) return;
139
+ log("Logging out");
140
+ authSDK.logout();
141
+ setSession(null);
142
+ setIsAuthenticated(false);
143
+ setVaultInfo(null);
144
+ setError(null);
145
+ }, [authSDK, log]);
146
+ const refreshSession = react.useCallback(async () => {
147
+ if (!authSDK) return null;
148
+ log("Refreshing session");
149
+ try {
150
+ const refreshedSession = await authSDK.refreshSession();
151
+ if (refreshedSession) {
152
+ setSession(refreshedSession);
153
+ setIsAuthenticated(true);
154
+ return refreshedSession;
155
+ }
156
+ return null;
157
+ } catch (err) {
158
+ log("Failed to refresh session:", err);
159
+ setError(err instanceof Error ? err : new Error("Failed to refresh session"));
160
+ return null;
161
+ }
162
+ }, [authSDK, log]);
163
+ const vaultId = session?.user?.vaultId ?? null;
164
+ const walletAddress = session?.user?.evmAddress ?? null;
165
+ const value = {
166
+ // State
167
+ session,
168
+ isAuthenticated,
169
+ isLoading,
170
+ error,
171
+ vaultInfo,
172
+ // Derived
173
+ vaultId,
174
+ walletAddress,
175
+ // Actions
176
+ openAuthModal,
177
+ logout,
178
+ refreshSession,
179
+ // For Hustle integration
180
+ authSDK
181
+ };
182
+ return /* @__PURE__ */ jsxRuntime.jsx(EmblemAuthContext.Provider, { value, children });
183
+ }
184
+ function useEmblemAuth() {
185
+ const context = react.useContext(EmblemAuthContext);
186
+ if (context === void 0) {
187
+ throw new Error("useEmblemAuth must be used within an EmblemAuthProvider");
188
+ }
189
+ return context;
190
+ }
191
+ function resetAuthSDK() {
192
+ globalSDKInstance = null;
193
+ isSDKInitializing = false;
194
+ }
195
+
196
+ // src/utils/pluginRegistry.ts
197
+ var PLUGINS_KEY = "hustle-plugins";
198
+ function getEnabledStateKey(instanceId) {
199
+ return `hustle-plugin-state-${instanceId}`;
200
+ }
201
+ function serializeFunction(fn) {
202
+ return fn.toString();
203
+ }
204
+ function deserializeExecutor(code) {
205
+ try {
206
+ return eval(`(${code})`);
207
+ } catch (err) {
208
+ console.error("[Hustle] Failed to deserialize executor:", err);
209
+ return async () => ({ error: "Failed to deserialize executor", code });
210
+ }
211
+ }
212
+ function deserializeHook(code) {
213
+ try {
214
+ return eval(`(${code})`);
215
+ } catch (err) {
216
+ console.error("[Hustle] Failed to deserialize hook:", err);
217
+ return (() => {
218
+ });
219
+ }
220
+ }
221
+ function serializePluginTools(tools, executors) {
222
+ if (!tools) return [];
223
+ return tools.map((tool) => ({
224
+ ...tool,
225
+ executorCode: executors?.[tool.name] ? serializeFunction(executors[tool.name]) : void 0
226
+ }));
227
+ }
228
+ function serializeHooks(hooks) {
229
+ if (!hooks) return void 0;
230
+ const serialized = {};
231
+ if (hooks.onRegister) {
232
+ serialized.onRegisterCode = serializeFunction(hooks.onRegister);
233
+ }
234
+ if (hooks.beforeRequest) {
235
+ serialized.beforeRequestCode = serializeFunction(hooks.beforeRequest);
236
+ }
237
+ if (hooks.afterResponse) {
238
+ serialized.afterResponseCode = serializeFunction(hooks.afterResponse);
239
+ }
240
+ if (hooks.onError) {
241
+ serialized.onErrorCode = serializeFunction(hooks.onError);
242
+ }
243
+ return Object.keys(serialized).length > 0 ? serialized : void 0;
244
+ }
245
+ function hydratePlugin(stored) {
246
+ const executors = {};
247
+ if (stored.tools) {
248
+ for (const tool of stored.tools) {
249
+ if (tool.executorCode) {
250
+ executors[tool.name] = deserializeExecutor(tool.executorCode);
251
+ }
252
+ }
253
+ }
254
+ let hooks;
255
+ if (stored.hooksCode) {
256
+ hooks = {};
257
+ if (stored.hooksCode.onRegisterCode) {
258
+ hooks.onRegister = deserializeHook(stored.hooksCode.onRegisterCode);
259
+ }
260
+ if (stored.hooksCode.beforeRequestCode) {
261
+ hooks.beforeRequest = deserializeHook(stored.hooksCode.beforeRequestCode);
262
+ }
263
+ if (stored.hooksCode.afterResponseCode) {
264
+ hooks.afterResponse = deserializeHook(stored.hooksCode.afterResponseCode);
265
+ }
266
+ if (stored.hooksCode.onErrorCode) {
267
+ hooks.onError = deserializeHook(stored.hooksCode.onErrorCode);
268
+ }
269
+ }
270
+ return {
271
+ ...stored,
272
+ executors: Object.keys(executors).length > 0 ? executors : void 0,
273
+ hooks
274
+ };
275
+ }
276
+ var PluginRegistry = class {
277
+ constructor() {
278
+ this.listeners = /* @__PURE__ */ new Map();
279
+ }
280
+ /**
281
+ * Get listeners for a specific instance
282
+ */
283
+ getListeners(instanceId) {
284
+ if (!this.listeners.has(instanceId)) {
285
+ this.listeners.set(instanceId, /* @__PURE__ */ new Set());
286
+ }
287
+ return this.listeners.get(instanceId);
288
+ }
289
+ /**
290
+ * Load installed plugins (global)
291
+ */
292
+ loadInstalledPlugins() {
293
+ if (typeof window === "undefined") return [];
294
+ try {
295
+ const stored = localStorage.getItem(PLUGINS_KEY);
296
+ return stored ? JSON.parse(stored) : [];
297
+ } catch {
298
+ return [];
299
+ }
300
+ }
301
+ /**
302
+ * Save installed plugins (global)
303
+ * Serializes executors as executorCode strings
304
+ */
305
+ saveInstalledPlugins(plugins) {
306
+ if (typeof window === "undefined") return;
307
+ localStorage.setItem(PLUGINS_KEY, JSON.stringify(plugins));
308
+ }
309
+ /**
310
+ * Load enabled state for an instance
311
+ */
312
+ loadEnabledState(instanceId) {
313
+ if (typeof window === "undefined") return {};
314
+ try {
315
+ const stored = localStorage.getItem(getEnabledStateKey(instanceId));
316
+ return stored ? JSON.parse(stored) : {};
317
+ } catch {
318
+ return {};
319
+ }
320
+ }
321
+ /**
322
+ * Save enabled state for an instance
323
+ */
324
+ saveEnabledState(state, instanceId) {
325
+ if (typeof window === "undefined") return;
326
+ localStorage.setItem(getEnabledStateKey(instanceId), JSON.stringify(state));
327
+ }
328
+ /**
329
+ * Load plugins with instance-specific enabled state
330
+ * Combines global plugin list with per-instance enabled state
331
+ */
332
+ loadFromStorage(instanceId = "default") {
333
+ const installed = this.loadInstalledPlugins();
334
+ const enabledState = this.loadEnabledState(instanceId);
335
+ return installed.map((plugin) => ({
336
+ ...plugin,
337
+ // Default to enabled if no state exists for this instance
338
+ enabled: enabledState[plugin.name] ?? true
339
+ }));
340
+ }
341
+ /**
342
+ * Register a new plugin (global - available to all instances)
343
+ * Serializes executors as executorCode for persistence
344
+ *
345
+ * @param plugin The plugin to install
346
+ * @param enabled Initial enabled state for this instance (default: true)
347
+ * @param instanceId Instance to set initial enabled state for
348
+ */
349
+ register(plugin, enabled = true, instanceId = "default") {
350
+ const installed = this.loadInstalledPlugins();
351
+ const existing = installed.findIndex((p) => p.name === plugin.name);
352
+ const storedPlugin = {
353
+ name: plugin.name,
354
+ version: plugin.version,
355
+ description: plugin.description,
356
+ tools: serializePluginTools(plugin.tools, plugin.executors),
357
+ hooksCode: serializeHooks(plugin.hooks),
358
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
359
+ };
360
+ if (existing >= 0) {
361
+ installed[existing] = storedPlugin;
362
+ } else {
363
+ installed.push(storedPlugin);
364
+ }
365
+ this.saveInstalledPlugins(installed);
366
+ const enabledState = this.loadEnabledState(instanceId);
367
+ enabledState[plugin.name] = enabled;
368
+ this.saveEnabledState(enabledState, instanceId);
369
+ this.notifyListeners(instanceId);
370
+ }
371
+ /**
372
+ * Unregister a plugin (global - removes from all instances)
373
+ */
374
+ unregister(pluginName, instanceId = "default") {
375
+ const installed = this.loadInstalledPlugins().filter((p) => p.name !== pluginName);
376
+ this.saveInstalledPlugins(installed);
377
+ const enabledState = this.loadEnabledState(instanceId);
378
+ delete enabledState[pluginName];
379
+ this.saveEnabledState(enabledState, instanceId);
380
+ this.notifyListeners(instanceId);
381
+ }
382
+ /**
383
+ * Enable or disable a plugin (instance-scoped)
384
+ */
385
+ setEnabled(pluginName, enabled, instanceId = "default") {
386
+ const enabledState = this.loadEnabledState(instanceId);
387
+ enabledState[pluginName] = enabled;
388
+ this.saveEnabledState(enabledState, instanceId);
389
+ this.notifyListeners(instanceId);
390
+ }
391
+ /**
392
+ * Check if a plugin is installed (global)
393
+ */
394
+ isRegistered(pluginName) {
395
+ return this.loadInstalledPlugins().some((p) => p.name === pluginName);
396
+ }
397
+ /**
398
+ * Get a specific plugin with instance-specific enabled state
399
+ */
400
+ getPlugin(pluginName, instanceId = "default") {
401
+ return this.loadFromStorage(instanceId).find((p) => p.name === pluginName);
402
+ }
403
+ /**
404
+ * Get all enabled plugins for an instance (hydrated with executors)
405
+ */
406
+ getEnabledPlugins(instanceId = "default") {
407
+ return this.loadFromStorage(instanceId).filter((p) => p.enabled).map(hydratePlugin);
408
+ }
409
+ /**
410
+ * Subscribe to plugin changes for a specific instance
411
+ */
412
+ onChange(callback, instanceId = "default") {
413
+ const listeners = this.getListeners(instanceId);
414
+ listeners.add(callback);
415
+ return () => listeners.delete(callback);
416
+ }
417
+ /**
418
+ * Notify all listeners for a specific instance
419
+ */
420
+ notifyListeners(instanceId = "default") {
421
+ const plugins = this.loadFromStorage(instanceId);
422
+ const listeners = this.getListeners(instanceId);
423
+ listeners.forEach((cb) => cb(plugins));
424
+ }
425
+ /**
426
+ * Clear enabled state for an instance (plugins remain installed globally)
427
+ */
428
+ clear(instanceId = "default") {
429
+ if (typeof window === "undefined") return;
430
+ localStorage.removeItem(getEnabledStateKey(instanceId));
431
+ this.notifyListeners(instanceId);
432
+ }
433
+ /**
434
+ * Clear all installed plugins globally
435
+ */
436
+ clearAll() {
437
+ if (typeof window === "undefined") return;
438
+ localStorage.removeItem(PLUGINS_KEY);
439
+ }
440
+ };
441
+ var pluginRegistry = new PluginRegistry();
442
+
443
+ // src/hooks/usePlugins.ts
444
+ function getStorageKey(instanceId) {
445
+ return `hustle-plugins-${instanceId}`;
446
+ }
447
+ function usePlugins(instanceId = "default") {
448
+ const [plugins, setPlugins] = react.useState([]);
449
+ react.useEffect(() => {
450
+ setPlugins(pluginRegistry.loadFromStorage(instanceId));
451
+ const unsubscribe = pluginRegistry.onChange(setPlugins, instanceId);
452
+ const storageKey = getStorageKey(instanceId);
453
+ const handleStorage = (e) => {
454
+ if (e.key === storageKey) {
455
+ setPlugins(pluginRegistry.loadFromStorage(instanceId));
456
+ }
457
+ };
458
+ window.addEventListener("storage", handleStorage);
459
+ return () => {
460
+ unsubscribe();
461
+ window.removeEventListener("storage", handleStorage);
462
+ };
463
+ }, [instanceId]);
464
+ const registerPlugin = react.useCallback((plugin) => {
465
+ pluginRegistry.register(plugin, true, instanceId);
466
+ }, [instanceId]);
467
+ const unregisterPlugin = react.useCallback((name) => {
468
+ pluginRegistry.unregister(name, instanceId);
469
+ }, [instanceId]);
470
+ const enablePlugin = react.useCallback((name) => {
471
+ pluginRegistry.setEnabled(name, true, instanceId);
472
+ }, [instanceId]);
473
+ const disablePlugin = react.useCallback((name) => {
474
+ pluginRegistry.setEnabled(name, false, instanceId);
475
+ }, [instanceId]);
476
+ const isRegistered = react.useCallback(
477
+ (name) => plugins.some((p) => p.name === name),
478
+ [plugins]
479
+ );
480
+ const isEnabled = react.useCallback(
481
+ (name) => plugins.some((p) => p.name === name && p.enabled),
482
+ [plugins]
483
+ );
484
+ const enabledPlugins = plugins.filter((p) => p.enabled).map(hydratePlugin);
485
+ return {
486
+ plugins,
487
+ enabledPlugins,
488
+ registerPlugin,
489
+ unregisterPlugin,
490
+ enablePlugin,
491
+ disablePlugin,
492
+ isRegistered,
493
+ isEnabled
494
+ };
495
+ }
496
+ var HustleContext = react.createContext(void 0);
497
+ var DEFAULT_HUSTLE_API_URL = "https://agenthustle.ai";
498
+ var instanceCounter = 0;
499
+ var mountedAutoInstances = /* @__PURE__ */ new Set();
500
+ function HustleProvider({
501
+ children,
502
+ hustleApiUrl = DEFAULT_HUSTLE_API_URL,
503
+ debug = false,
504
+ instanceId: explicitInstanceId
505
+ }) {
506
+ const [resolvedInstanceId] = react.useState(() => {
507
+ if (explicitInstanceId) {
508
+ return explicitInstanceId;
509
+ }
510
+ const autoId = `instance-${++instanceCounter}`;
511
+ mountedAutoInstances.add(autoId);
512
+ return autoId;
513
+ });
514
+ const isAutoInstance = !explicitInstanceId;
515
+ react.useEffect(() => {
516
+ if (isAutoInstance && mountedAutoInstances.size > 1 && process.env.NODE_ENV !== "production") {
517
+ console.warn(
518
+ `[Hustle] Multiple HustleProviders detected without explicit instanceId. For stable settings persistence, consider adding instanceId prop:
519
+ <HustleProvider instanceId="my-chat-name">`
520
+ );
521
+ }
522
+ return () => {
523
+ if (isAutoInstance) {
524
+ mountedAutoInstances.delete(resolvedInstanceId);
525
+ }
526
+ };
527
+ }, [isAutoInstance, resolvedInstanceId]);
528
+ const { authSDK, isAuthenticated } = useEmblemAuth();
529
+ const { enabledPlugins } = usePlugins(resolvedInstanceId);
530
+ const [isLoading, setIsLoading] = react.useState(false);
531
+ const [error, setError] = react.useState(null);
532
+ const [models, setModels] = react.useState([]);
533
+ const registeredPluginsRef = react.useRef(/* @__PURE__ */ new Set());
534
+ const SETTINGS_KEY = `hustle-settings-${resolvedInstanceId}`;
535
+ const loadSettings = () => {
536
+ if (typeof window === "undefined") return { selectedModel: "", systemPrompt: "", skipServerPrompt: false };
537
+ try {
538
+ const stored = localStorage.getItem(SETTINGS_KEY);
539
+ if (stored) {
540
+ return JSON.parse(stored);
541
+ }
542
+ } catch {
543
+ }
544
+ return { selectedModel: "", systemPrompt: "", skipServerPrompt: false };
545
+ };
546
+ const initialSettings = loadSettings();
547
+ const [selectedModel, setSelectedModelState] = react.useState(initialSettings.selectedModel);
548
+ const [systemPrompt, setSystemPromptState] = react.useState(initialSettings.systemPrompt);
549
+ const [skipServerPrompt, setSkipServerPromptState] = react.useState(initialSettings.skipServerPrompt);
550
+ const saveSettings = react.useCallback((settings) => {
551
+ if (typeof window === "undefined") return;
552
+ try {
553
+ localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
554
+ } catch {
555
+ }
556
+ }, []);
557
+ const setSelectedModel = react.useCallback((value2) => {
558
+ setSelectedModelState(value2);
559
+ saveSettings({ selectedModel: value2, systemPrompt, skipServerPrompt });
560
+ }, [systemPrompt, skipServerPrompt, saveSettings]);
561
+ const setSystemPrompt = react.useCallback((value2) => {
562
+ setSystemPromptState(value2);
563
+ saveSettings({ selectedModel, systemPrompt: value2, skipServerPrompt });
564
+ }, [selectedModel, skipServerPrompt, saveSettings]);
565
+ const setSkipServerPrompt = react.useCallback((value2) => {
566
+ setSkipServerPromptState(value2);
567
+ saveSettings({ selectedModel, systemPrompt, skipServerPrompt: value2 });
568
+ }, [selectedModel, systemPrompt, saveSettings]);
569
+ const log = react.useCallback(
570
+ (message, ...args) => {
571
+ if (debug) {
572
+ console.log(`[Hustle] ${message}`, ...args);
573
+ }
574
+ },
575
+ [debug]
576
+ );
577
+ const client = react.useMemo(() => {
578
+ if (!authSDK || !isAuthenticated) {
579
+ log("Client not created - auth not ready");
580
+ return null;
581
+ }
582
+ log("Creating HustleIncognitoClient with auth SDK");
583
+ try {
584
+ const hustleClient = new hustleIncognito.HustleIncognitoClient({
585
+ sdk: authSDK,
586
+ // CORRECT: Pass auth SDK instance, NOT apiKey
587
+ hustleApiUrl,
588
+ debug
589
+ });
590
+ hustleClient.on("tool_start", (event) => {
591
+ log("Tool start:", event);
592
+ });
593
+ hustleClient.on("tool_end", (event) => {
594
+ log("Tool end:", event);
595
+ });
596
+ hustleClient.on("stream_end", (event) => {
597
+ log("Stream end:", event);
598
+ });
599
+ return hustleClient;
600
+ } catch (err) {
601
+ log("Failed to create client:", err);
602
+ setError(err instanceof Error ? err : new Error("Failed to create Hustle client"));
603
+ return null;
604
+ }
605
+ }, [authSDK, isAuthenticated, hustleApiUrl, debug, log]);
606
+ const isReady = client !== null;
607
+ react.useEffect(() => {
608
+ if (!client) return;
609
+ const registerPlugins = async () => {
610
+ const enabledNames = new Set(enabledPlugins.map((p) => p.name));
611
+ for (const name of registeredPluginsRef.current) {
612
+ if (!enabledNames.has(name)) {
613
+ log("Unregistering plugin:", name);
614
+ try {
615
+ await client.unuse(name);
616
+ registeredPluginsRef.current.delete(name);
617
+ log("Plugin unregistered:", name);
618
+ } catch (err) {
619
+ log("Failed to unregister plugin:", name, err);
620
+ }
621
+ }
622
+ }
623
+ for (const plugin of enabledPlugins) {
624
+ if (!registeredPluginsRef.current.has(plugin.name)) {
625
+ log("Registering plugin:", plugin.name);
626
+ try {
627
+ if (plugin.executors || plugin.hooks) {
628
+ await client.use({
629
+ name: plugin.name,
630
+ version: plugin.version,
631
+ tools: plugin.tools,
632
+ executors: plugin.executors,
633
+ hooks: plugin.hooks
634
+ });
635
+ registeredPluginsRef.current.add(plugin.name);
636
+ log("Plugin registered:", plugin.name);
637
+ } else {
638
+ log("Plugin has no executors/hooks, skipping registration:", plugin.name);
639
+ }
640
+ } catch (err) {
641
+ log("Failed to register plugin:", plugin.name, err);
642
+ }
643
+ }
644
+ }
645
+ };
646
+ registerPlugins();
647
+ }, [client, enabledPlugins, log]);
648
+ const loadModels = react.useCallback(async () => {
649
+ if (!client) {
650
+ log("Cannot load models - client not ready");
651
+ return [];
652
+ }
653
+ log("Loading models");
654
+ setIsLoading(true);
655
+ try {
656
+ const modelList = await client.getModels();
657
+ setModels(modelList);
658
+ log("Loaded models:", modelList.length);
659
+ return modelList;
660
+ } catch (err) {
661
+ log("Failed to load models:", err);
662
+ setError(err instanceof Error ? err : new Error("Failed to load models"));
663
+ return [];
664
+ } finally {
665
+ setIsLoading(false);
666
+ }
667
+ }, [client, log]);
668
+ react.useEffect(() => {
669
+ if (client) {
670
+ loadModels();
671
+ }
672
+ }, [client, loadModels]);
673
+ const chat = react.useCallback(
674
+ async (options) => {
675
+ if (!client) {
676
+ throw new Error("Hustle client not ready. Please authenticate first.");
677
+ }
678
+ log("Chat request:", options.messages.length, "messages");
679
+ setIsLoading(true);
680
+ setError(null);
681
+ try {
682
+ const effectiveSystemPrompt = options.systemPrompt || systemPrompt;
683
+ const messagesWithSystem = [];
684
+ if (effectiveSystemPrompt) {
685
+ messagesWithSystem.push({ role: "system", content: effectiveSystemPrompt });
686
+ }
687
+ messagesWithSystem.push(...options.messages);
688
+ const sdkOptions = {
689
+ messages: messagesWithSystem,
690
+ processChunks: true
691
+ };
692
+ if (options.model || selectedModel) {
693
+ sdkOptions.model = options.model || selectedModel;
694
+ }
695
+ if (options.overrideSystemPrompt ?? skipServerPrompt) {
696
+ sdkOptions.overrideSystemPrompt = true;
697
+ }
698
+ if (options.attachments) {
699
+ sdkOptions.attachments = options.attachments;
700
+ }
701
+ const response = await client.chat(sdkOptions);
702
+ log("Chat response received");
703
+ return response;
704
+ } catch (err) {
705
+ log("Chat error:", err);
706
+ const error2 = err instanceof Error ? err : new Error("Chat request failed");
707
+ setError(error2);
708
+ throw error2;
709
+ } finally {
710
+ setIsLoading(false);
711
+ }
712
+ },
713
+ [client, selectedModel, systemPrompt, skipServerPrompt, log]
714
+ );
715
+ const chatStreamImpl = react.useCallback(
716
+ (options) => {
717
+ if (!client) {
718
+ return {
719
+ [Symbol.asyncIterator]: async function* () {
720
+ yield { type: "error", value: { message: "Hustle client not ready. Please authenticate first." } };
721
+ }
722
+ };
723
+ }
724
+ log("Chat stream request:", options.messages.length, "messages");
725
+ setError(null);
726
+ const effectiveSystemPrompt = options.systemPrompt || systemPrompt;
727
+ const messagesWithSystem = [];
728
+ if (effectiveSystemPrompt) {
729
+ messagesWithSystem.push({ role: "system", content: effectiveSystemPrompt });
730
+ }
731
+ messagesWithSystem.push(...options.messages);
732
+ const sdkOptions = {
733
+ messages: messagesWithSystem,
734
+ processChunks: options.processChunks ?? true
735
+ };
736
+ if (options.model || selectedModel) {
737
+ sdkOptions.model = options.model || selectedModel;
738
+ }
739
+ if (options.overrideSystemPrompt ?? skipServerPrompt) {
740
+ sdkOptions.overrideSystemPrompt = true;
741
+ }
742
+ if (options.attachments) {
743
+ sdkOptions.attachments = options.attachments;
744
+ }
745
+ const stream = client.chatStream(sdkOptions);
746
+ return {
747
+ [Symbol.asyncIterator]: async function* () {
748
+ try {
749
+ for await (const chunk of stream) {
750
+ const typedChunk = chunk;
751
+ if (typedChunk.type === "text") {
752
+ const textValue = typedChunk.value;
753
+ log("Stream text chunk:", textValue?.substring(0, 50));
754
+ yield { type: "text", value: textValue };
755
+ } else if (typedChunk.type === "tool_call") {
756
+ log("Stream tool call:", typedChunk.value);
757
+ yield { type: "tool_call", value: typedChunk.value };
758
+ } else if (typedChunk.type === "tool_result") {
759
+ log("Stream tool result");
760
+ yield { type: "tool_result", value: typedChunk.value };
761
+ } else if (typedChunk.type === "error") {
762
+ const errorValue = typedChunk.value;
763
+ log("Stream error:", errorValue);
764
+ setError(new Error(errorValue?.message || "Stream error"));
765
+ yield { type: "error", value: { message: errorValue?.message || "Stream error" } };
766
+ } else {
767
+ yield chunk;
768
+ }
769
+ }
770
+ } catch (err) {
771
+ log("Stream error:", err);
772
+ const error2 = err instanceof Error ? err : new Error("Stream failed");
773
+ setError(error2);
774
+ yield { type: "error", value: { message: error2.message } };
775
+ }
776
+ }
777
+ };
778
+ },
779
+ [client, selectedModel, systemPrompt, skipServerPrompt, log]
780
+ );
781
+ const uploadFile = react.useCallback(
782
+ async (file) => {
783
+ if (!client) {
784
+ throw new Error("Hustle client not ready. Please authenticate first.");
785
+ }
786
+ log("Uploading file:", file.name);
787
+ setIsLoading(true);
788
+ try {
789
+ const attachment = await client.uploadFile(file);
790
+ log("File uploaded:", attachment);
791
+ return attachment;
792
+ } catch (err) {
793
+ log("Upload error:", err);
794
+ const error2 = err instanceof Error ? err : new Error("File upload failed");
795
+ setError(error2);
796
+ throw error2;
797
+ } finally {
798
+ setIsLoading(false);
799
+ }
800
+ },
801
+ [client, log]
802
+ );
803
+ const value = {
804
+ // Instance ID for scoped storage
805
+ instanceId: resolvedInstanceId,
806
+ // State
807
+ isReady,
808
+ isLoading,
809
+ error,
810
+ models,
811
+ // Client (for advanced use)
812
+ client,
813
+ // Chat methods
814
+ chat,
815
+ chatStream: chatStreamImpl,
816
+ // File upload
817
+ uploadFile,
818
+ // Data fetching
819
+ loadModels,
820
+ // Settings
821
+ selectedModel,
822
+ setSelectedModel,
823
+ systemPrompt,
824
+ setSystemPrompt,
825
+ skipServerPrompt,
826
+ setSkipServerPrompt
827
+ };
828
+ return /* @__PURE__ */ jsxRuntime.jsx(HustleContext.Provider, { value, children });
829
+ }
830
+ function useHustle() {
831
+ const context = react.useContext(HustleContext);
832
+ if (context === void 0) {
833
+ throw new Error("useHustle must be used within a HustleProvider (which requires EmblemAuthProvider)");
834
+ }
835
+ return context;
836
+ }
837
+
838
+ exports.EmblemAuthProvider = EmblemAuthProvider;
839
+ exports.HustleProvider = HustleProvider;
840
+ exports.resetAuthSDK = resetAuthSDK;
841
+ exports.useEmblemAuth = useEmblemAuth;
842
+ exports.useHustle = useHustle;
843
+ //# sourceMappingURL=index.cjs.map
844
+ //# sourceMappingURL=index.cjs.map