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,375 @@
1
+ /**
2
+ * Plugin Registry
3
+ *
4
+ * Manages plugin storage and state in localStorage.
5
+ *
6
+ * Storage model:
7
+ * - Installed plugins are GLOBAL (hustle-plugins) - install once, available everywhere
8
+ * - Enabled/disabled state is INSTANCE-SCOPED (hustle-plugin-state-{instanceId})
9
+ *
10
+ * Executor functions are serialized as strings (executorCode) and
11
+ * reconstituted at runtime via new Function().
12
+ *
13
+ * SECURITY TODO: Add signature verification before executing stored code.
14
+ * Plugins should be signed by trusted publishers and verified before
15
+ * any eval/Function execution occurs.
16
+ */
17
+
18
+ import type {
19
+ StoredPlugin,
20
+ HustlePlugin,
21
+ HydratedPlugin,
22
+ ToolExecutor,
23
+ PluginHooks,
24
+ SerializedToolDefinition,
25
+ SerializedHooks,
26
+ } from '../types';
27
+
28
+ /**
29
+ * Storage keys:
30
+ * - PLUGINS_KEY: Global list of installed plugins (not instance-scoped)
31
+ * - getEnabledStateKey: Per-instance enabled/disabled states
32
+ */
33
+ const PLUGINS_KEY = 'hustle-plugins';
34
+
35
+ function getEnabledStateKey(instanceId: string): string {
36
+ return `hustle-plugin-state-${instanceId}`;
37
+ }
38
+
39
+ type PluginChangeCallback = (plugins: StoredPlugin[]) => void;
40
+
41
+ /** Stored enabled state per instance */
42
+ type EnabledState = Record<string, boolean>;
43
+
44
+ /**
45
+ * Serialize a function to a string for storage
46
+ */
47
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
+ function serializeFunction(fn: (...args: any[]) => any): string {
49
+ return fn.toString();
50
+ }
51
+
52
+ /**
53
+ * Deserialize a function string back to executable function
54
+ *
55
+ * FIXME: Add signature verification before execution
56
+ * This is a security-sensitive operation that executes stored code.
57
+ */
58
+ function deserializeExecutor(code: string): ToolExecutor {
59
+ // Extract function body - handles arrow functions and regular functions
60
+ // The stored code is the full function: "(args) => { ... }" or "async (args) => { ... }"
61
+ // We wrap it in parentheses and eval to get the function reference
62
+ try {
63
+ // eslint-disable-next-line no-eval
64
+ return eval(`(${code})`) as ToolExecutor;
65
+ } catch (err) {
66
+ console.error('[Hustle] Failed to deserialize executor:', err);
67
+ // Return a no-op executor that reports the error
68
+ return async () => ({ error: 'Failed to deserialize executor', code });
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Deserialize a hook function string
74
+ *
75
+ * FIXME: Add signature verification before execution
76
+ */
77
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
+ function deserializeHook<T extends (...args: any[]) => any>(code: string): T {
79
+ try {
80
+ // eslint-disable-next-line no-eval
81
+ return eval(`(${code})`) as T;
82
+ } catch (err) {
83
+ console.error('[Hustle] Failed to deserialize hook:', err);
84
+ return (() => {}) as T;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Serialize a plugin's executors to executorCode strings
90
+ */
91
+ function serializePluginTools(
92
+ tools: HustlePlugin['tools'],
93
+ executors: HustlePlugin['executors']
94
+ ): SerializedToolDefinition[] {
95
+ if (!tools) return [];
96
+
97
+ return tools.map((tool) => ({
98
+ ...tool,
99
+ executorCode: executors?.[tool.name]
100
+ ? serializeFunction(executors[tool.name])
101
+ : undefined,
102
+ }));
103
+ }
104
+
105
+ /**
106
+ * Serialize plugin hooks to code strings
107
+ */
108
+ function serializeHooks(hooks: PluginHooks | undefined): SerializedHooks | undefined {
109
+ if (!hooks) return undefined;
110
+
111
+ const serialized: SerializedHooks = {};
112
+
113
+ if (hooks.onRegister) {
114
+ serialized.onRegisterCode = serializeFunction(hooks.onRegister);
115
+ }
116
+ if (hooks.beforeRequest) {
117
+ serialized.beforeRequestCode = serializeFunction(hooks.beforeRequest);
118
+ }
119
+ if (hooks.afterResponse) {
120
+ serialized.afterResponseCode = serializeFunction(hooks.afterResponse);
121
+ }
122
+ if (hooks.onError) {
123
+ serialized.onErrorCode = serializeFunction(hooks.onError);
124
+ }
125
+
126
+ return Object.keys(serialized).length > 0 ? serialized : undefined;
127
+ }
128
+
129
+ /**
130
+ * Hydrate a stored plugin - reconstitute executors from executorCode
131
+ *
132
+ * FIXME: Add signature verification before execution
133
+ */
134
+ export function hydratePlugin(stored: StoredPlugin): HydratedPlugin {
135
+ // Reconstitute executors from executorCode strings
136
+ const executors: Record<string, ToolExecutor> = {};
137
+
138
+ if (stored.tools) {
139
+ for (const tool of stored.tools) {
140
+ if (tool.executorCode) {
141
+ executors[tool.name] = deserializeExecutor(tool.executorCode);
142
+ }
143
+ }
144
+ }
145
+
146
+ // Reconstitute hooks from hooksCode strings
147
+ let hooks: PluginHooks | undefined;
148
+
149
+ if (stored.hooksCode) {
150
+ hooks = {};
151
+ if (stored.hooksCode.onRegisterCode) {
152
+ hooks.onRegister = deserializeHook(stored.hooksCode.onRegisterCode);
153
+ }
154
+ if (stored.hooksCode.beforeRequestCode) {
155
+ hooks.beforeRequest = deserializeHook(stored.hooksCode.beforeRequestCode);
156
+ }
157
+ if (stored.hooksCode.afterResponseCode) {
158
+ hooks.afterResponse = deserializeHook(stored.hooksCode.afterResponseCode);
159
+ }
160
+ if (stored.hooksCode.onErrorCode) {
161
+ hooks.onError = deserializeHook(stored.hooksCode.onErrorCode);
162
+ }
163
+ }
164
+
165
+ return {
166
+ ...stored,
167
+ executors: Object.keys(executors).length > 0 ? executors : undefined,
168
+ hooks,
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Plugin Registry class
174
+ *
175
+ * Manages plugin persistence with:
176
+ * - Global plugin installations (with serialized executorCode)
177
+ * - Instance-scoped enabled/disabled state
178
+ */
179
+ class PluginRegistry {
180
+ private listeners: Map<string, Set<PluginChangeCallback>> = new Map();
181
+
182
+ /**
183
+ * Get listeners for a specific instance
184
+ */
185
+ private getListeners(instanceId: string): Set<PluginChangeCallback> {
186
+ if (!this.listeners.has(instanceId)) {
187
+ this.listeners.set(instanceId, new Set());
188
+ }
189
+ return this.listeners.get(instanceId)!;
190
+ }
191
+
192
+ /**
193
+ * Load installed plugins (global)
194
+ */
195
+ private loadInstalledPlugins(): Omit<StoredPlugin, 'enabled'>[] {
196
+ if (typeof window === 'undefined') return [];
197
+ try {
198
+ const stored = localStorage.getItem(PLUGINS_KEY);
199
+ return stored ? JSON.parse(stored) : [];
200
+ } catch {
201
+ return [];
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Save installed plugins (global)
207
+ * Serializes executors as executorCode strings
208
+ */
209
+ private saveInstalledPlugins(plugins: Omit<StoredPlugin, 'enabled'>[]): void {
210
+ if (typeof window === 'undefined') return;
211
+ localStorage.setItem(PLUGINS_KEY, JSON.stringify(plugins));
212
+ }
213
+
214
+ /**
215
+ * Load enabled state for an instance
216
+ */
217
+ private loadEnabledState(instanceId: string): EnabledState {
218
+ if (typeof window === 'undefined') return {};
219
+ try {
220
+ const stored = localStorage.getItem(getEnabledStateKey(instanceId));
221
+ return stored ? JSON.parse(stored) : {};
222
+ } catch {
223
+ return {};
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Save enabled state for an instance
229
+ */
230
+ private saveEnabledState(state: EnabledState, instanceId: string): void {
231
+ if (typeof window === 'undefined') return;
232
+ localStorage.setItem(getEnabledStateKey(instanceId), JSON.stringify(state));
233
+ }
234
+
235
+ /**
236
+ * Load plugins with instance-specific enabled state
237
+ * Combines global plugin list with per-instance enabled state
238
+ */
239
+ loadFromStorage(instanceId: string = 'default'): StoredPlugin[] {
240
+ const installed = this.loadInstalledPlugins();
241
+ const enabledState = this.loadEnabledState(instanceId);
242
+
243
+ return installed.map((plugin) => ({
244
+ ...plugin,
245
+ // Default to enabled if no state exists for this instance
246
+ enabled: enabledState[plugin.name] ?? true,
247
+ }));
248
+ }
249
+
250
+ /**
251
+ * Register a new plugin (global - available to all instances)
252
+ * Serializes executors as executorCode for persistence
253
+ *
254
+ * @param plugin The plugin to install
255
+ * @param enabled Initial enabled state for this instance (default: true)
256
+ * @param instanceId Instance to set initial enabled state for
257
+ */
258
+ register(plugin: HustlePlugin, enabled = true, instanceId: string = 'default'): void {
259
+ // Add to global installed list with serialized executors
260
+ const installed = this.loadInstalledPlugins();
261
+ const existing = installed.findIndex((p) => p.name === plugin.name);
262
+
263
+ const storedPlugin: Omit<StoredPlugin, 'enabled'> = {
264
+ name: plugin.name,
265
+ version: plugin.version,
266
+ description: plugin.description,
267
+ tools: serializePluginTools(plugin.tools, plugin.executors),
268
+ hooksCode: serializeHooks(plugin.hooks),
269
+ installedAt: new Date().toISOString(),
270
+ };
271
+
272
+ if (existing >= 0) {
273
+ installed[existing] = storedPlugin;
274
+ } else {
275
+ installed.push(storedPlugin);
276
+ }
277
+
278
+ this.saveInstalledPlugins(installed);
279
+
280
+ // Set initial enabled state for this instance
281
+ const enabledState = this.loadEnabledState(instanceId);
282
+ enabledState[plugin.name] = enabled;
283
+ this.saveEnabledState(enabledState, instanceId);
284
+
285
+ this.notifyListeners(instanceId);
286
+ }
287
+
288
+ /**
289
+ * Unregister a plugin (global - removes from all instances)
290
+ */
291
+ unregister(pluginName: string, instanceId: string = 'default'): void {
292
+ // Remove from global list
293
+ const installed = this.loadInstalledPlugins().filter((p) => p.name !== pluginName);
294
+ this.saveInstalledPlugins(installed);
295
+
296
+ // Clean up enabled state for this instance
297
+ const enabledState = this.loadEnabledState(instanceId);
298
+ delete enabledState[pluginName];
299
+ this.saveEnabledState(enabledState, instanceId);
300
+
301
+ this.notifyListeners(instanceId);
302
+ }
303
+
304
+ /**
305
+ * Enable or disable a plugin (instance-scoped)
306
+ */
307
+ setEnabled(pluginName: string, enabled: boolean, instanceId: string = 'default'): void {
308
+ const enabledState = this.loadEnabledState(instanceId);
309
+ enabledState[pluginName] = enabled;
310
+ this.saveEnabledState(enabledState, instanceId);
311
+ this.notifyListeners(instanceId);
312
+ }
313
+
314
+ /**
315
+ * Check if a plugin is installed (global)
316
+ */
317
+ isRegistered(pluginName: string): boolean {
318
+ return this.loadInstalledPlugins().some((p) => p.name === pluginName);
319
+ }
320
+
321
+ /**
322
+ * Get a specific plugin with instance-specific enabled state
323
+ */
324
+ getPlugin(pluginName: string, instanceId: string = 'default'): StoredPlugin | undefined {
325
+ return this.loadFromStorage(instanceId).find((p) => p.name === pluginName);
326
+ }
327
+
328
+ /**
329
+ * Get all enabled plugins for an instance (hydrated with executors)
330
+ */
331
+ getEnabledPlugins(instanceId: string = 'default'): HydratedPlugin[] {
332
+ return this.loadFromStorage(instanceId).filter((p) => p.enabled).map(hydratePlugin);
333
+ }
334
+
335
+ /**
336
+ * Subscribe to plugin changes for a specific instance
337
+ */
338
+ onChange(callback: PluginChangeCallback, instanceId: string = 'default'): () => void {
339
+ const listeners = this.getListeners(instanceId);
340
+ listeners.add(callback);
341
+ return () => listeners.delete(callback);
342
+ }
343
+
344
+ /**
345
+ * Notify all listeners for a specific instance
346
+ */
347
+ private notifyListeners(instanceId: string = 'default'): void {
348
+ const plugins = this.loadFromStorage(instanceId);
349
+ const listeners = this.getListeners(instanceId);
350
+ listeners.forEach((cb) => cb(plugins));
351
+ }
352
+
353
+ /**
354
+ * Clear enabled state for an instance (plugins remain installed globally)
355
+ */
356
+ clear(instanceId: string = 'default'): void {
357
+ if (typeof window === 'undefined') return;
358
+ localStorage.removeItem(getEnabledStateKey(instanceId));
359
+ this.notifyListeners(instanceId);
360
+ }
361
+
362
+ /**
363
+ * Clear all installed plugins globally
364
+ */
365
+ clearAll(): void {
366
+ if (typeof window === 'undefined') return;
367
+ localStorage.removeItem(PLUGINS_KEY);
368
+ // Note: This doesn't clear instance-specific enabled states
369
+ }
370
+ }
371
+
372
+ // Singleton instance
373
+ export const pluginRegistry = new PluginRegistry();
374
+
375
+ export default pluginRegistry;