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