@emblemvault/hustle-react 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/hustle-react.js +602 -75
- package/dist/browser/hustle-react.js.map +1 -1
- package/dist/components/index.cjs +553 -75
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +553 -75
- package/dist/components/index.js.map +1 -1
- package/dist/hooks/index.cjs +80 -14
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.cts +3 -2
- package/dist/hooks/index.d.ts +3 -2
- package/dist/hooks/index.js +80 -14
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.cjs +602 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +602 -75
- package/dist/index.js.map +1 -1
- package/dist/{plugin-BUg7vMxe.d.cts → plugin-COr42J6-.d.cts} +3 -1
- package/dist/{plugin-BUg7vMxe.d.ts → plugin-COr42J6-.d.ts} +3 -1
- package/dist/plugins/index.cjs +522 -61
- package/dist/plugins/index.cjs.map +1 -1
- package/dist/plugins/index.d.cts +1 -1
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/index.js +522 -61
- package/dist/plugins/index.js.map +1 -1
- package/dist/providers/index.cjs +80 -14
- package/dist/providers/index.cjs.map +1 -1
- package/dist/providers/index.js +80 -14
- package/dist/providers/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -1820,35 +1820,52 @@ var pluginRegistry = new PluginRegistry();
|
|
|
1820
1820
|
function getStorageKey(instanceId) {
|
|
1821
1821
|
return `hustle-plugins-${instanceId}`;
|
|
1822
1822
|
}
|
|
1823
|
-
function
|
|
1823
|
+
function getInstanceId(providedId) {
|
|
1824
|
+
if (providedId) return providedId;
|
|
1825
|
+
if (typeof window !== "undefined") {
|
|
1826
|
+
const globalId = window.__hustleInstanceId;
|
|
1827
|
+
if (globalId) return globalId;
|
|
1828
|
+
}
|
|
1829
|
+
return "default";
|
|
1830
|
+
}
|
|
1831
|
+
function usePlugins(instanceId) {
|
|
1832
|
+
const [resolvedInstanceId] = useState(() => getInstanceId(instanceId));
|
|
1824
1833
|
const [plugins, setPlugins] = useState([]);
|
|
1825
1834
|
useEffect(() => {
|
|
1826
|
-
setPlugins(pluginRegistry.loadFromStorage(
|
|
1827
|
-
const unsubscribe = pluginRegistry.onChange(setPlugins,
|
|
1828
|
-
const storageKey = getStorageKey(
|
|
1835
|
+
setPlugins(pluginRegistry.loadFromStorage(resolvedInstanceId));
|
|
1836
|
+
const unsubscribe = pluginRegistry.onChange(setPlugins, resolvedInstanceId);
|
|
1837
|
+
const storageKey = getStorageKey(resolvedInstanceId);
|
|
1829
1838
|
const handleStorage = (e) => {
|
|
1830
1839
|
if (e.key === storageKey) {
|
|
1831
|
-
setPlugins(pluginRegistry.loadFromStorage(
|
|
1840
|
+
setPlugins(pluginRegistry.loadFromStorage(resolvedInstanceId));
|
|
1832
1841
|
}
|
|
1833
1842
|
};
|
|
1834
1843
|
window.addEventListener("storage", handleStorage);
|
|
1844
|
+
const handlePluginInstalled = (e) => {
|
|
1845
|
+
const customEvent = e;
|
|
1846
|
+
if (customEvent.detail.instanceId === resolvedInstanceId) {
|
|
1847
|
+
setPlugins(pluginRegistry.loadFromStorage(resolvedInstanceId));
|
|
1848
|
+
}
|
|
1849
|
+
};
|
|
1850
|
+
window.addEventListener("hustle-plugin-installed", handlePluginInstalled);
|
|
1835
1851
|
return () => {
|
|
1836
1852
|
unsubscribe();
|
|
1837
1853
|
window.removeEventListener("storage", handleStorage);
|
|
1854
|
+
window.removeEventListener("hustle-plugin-installed", handlePluginInstalled);
|
|
1838
1855
|
};
|
|
1839
|
-
}, [
|
|
1856
|
+
}, [resolvedInstanceId]);
|
|
1840
1857
|
const registerPlugin = useCallback((plugin) => {
|
|
1841
|
-
pluginRegistry.register(plugin, true,
|
|
1842
|
-
}, [
|
|
1858
|
+
pluginRegistry.register(plugin, true, resolvedInstanceId);
|
|
1859
|
+
}, [resolvedInstanceId]);
|
|
1843
1860
|
const unregisterPlugin = useCallback((name) => {
|
|
1844
|
-
pluginRegistry.unregister(name,
|
|
1845
|
-
}, [
|
|
1861
|
+
pluginRegistry.unregister(name, resolvedInstanceId);
|
|
1862
|
+
}, [resolvedInstanceId]);
|
|
1846
1863
|
const enablePlugin = useCallback((name) => {
|
|
1847
|
-
pluginRegistry.setEnabled(name, true,
|
|
1848
|
-
}, [
|
|
1864
|
+
pluginRegistry.setEnabled(name, true, resolvedInstanceId);
|
|
1865
|
+
}, [resolvedInstanceId]);
|
|
1849
1866
|
const disablePlugin = useCallback((name) => {
|
|
1850
|
-
pluginRegistry.setEnabled(name, false,
|
|
1851
|
-
}, [
|
|
1867
|
+
pluginRegistry.setEnabled(name, false, resolvedInstanceId);
|
|
1868
|
+
}, [resolvedInstanceId]);
|
|
1852
1869
|
const isRegistered = useCallback(
|
|
1853
1870
|
(name) => plugins.some((p) => p.name === name),
|
|
1854
1871
|
[plugins]
|
|
@@ -1903,6 +1920,42 @@ function HustleProvider({
|
|
|
1903
1920
|
}
|
|
1904
1921
|
};
|
|
1905
1922
|
}, [isAutoInstance, resolvedInstanceId]);
|
|
1923
|
+
useEffect(() => {
|
|
1924
|
+
if (typeof window !== "undefined") {
|
|
1925
|
+
const win = window;
|
|
1926
|
+
win.__hustleInstanceId = resolvedInstanceId;
|
|
1927
|
+
win.__hustleRegisterPlugin = async (plugin, enabled = true) => {
|
|
1928
|
+
const hydrated = hydratePlugin(plugin);
|
|
1929
|
+
pluginRegistry.register(hydrated, enabled, resolvedInstanceId);
|
|
1930
|
+
};
|
|
1931
|
+
win.__hustleUnregisterPlugin = async (name) => {
|
|
1932
|
+
pluginRegistry.unregister(name, resolvedInstanceId);
|
|
1933
|
+
};
|
|
1934
|
+
win.__hustleListPlugins = () => {
|
|
1935
|
+
const plugins = pluginRegistry.loadFromStorage(resolvedInstanceId);
|
|
1936
|
+
return plugins.map((p) => ({
|
|
1937
|
+
name: p.name,
|
|
1938
|
+
version: p.version,
|
|
1939
|
+
description: p.description || "",
|
|
1940
|
+
enabled: p.enabled
|
|
1941
|
+
}));
|
|
1942
|
+
};
|
|
1943
|
+
win.__hustleGetPlugin = (name) => {
|
|
1944
|
+
const plugins = pluginRegistry.loadFromStorage(resolvedInstanceId);
|
|
1945
|
+
return plugins.find((p) => p.name === name) || null;
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
return () => {
|
|
1949
|
+
if (typeof window !== "undefined") {
|
|
1950
|
+
const win = window;
|
|
1951
|
+
delete win.__hustleInstanceId;
|
|
1952
|
+
delete win.__hustleRegisterPlugin;
|
|
1953
|
+
delete win.__hustleUnregisterPlugin;
|
|
1954
|
+
delete win.__hustleListPlugins;
|
|
1955
|
+
delete win.__hustleGetPlugin;
|
|
1956
|
+
}
|
|
1957
|
+
};
|
|
1958
|
+
}, [resolvedInstanceId]);
|
|
1906
1959
|
const isApiKeyMode = Boolean(apiKey && vaultId);
|
|
1907
1960
|
const authContext = useEmblemAuthOptional();
|
|
1908
1961
|
const authSDK = isApiKeyMode ? null : authContext?.authSDK ?? null;
|
|
@@ -2050,6 +2103,19 @@ function HustleProvider({
|
|
|
2050
2103
|
};
|
|
2051
2104
|
registerPlugins();
|
|
2052
2105
|
}, [client, enabledPlugins, log]);
|
|
2106
|
+
useEffect(() => {
|
|
2107
|
+
if (typeof window !== "undefined" && client) {
|
|
2108
|
+
const win = window;
|
|
2109
|
+
win.__hustleUploadFile = async (file, fileName) => {
|
|
2110
|
+
return await client.uploadFile(file, fileName);
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
return () => {
|
|
2114
|
+
if (typeof window !== "undefined") {
|
|
2115
|
+
delete window.__hustleUploadFile;
|
|
2116
|
+
}
|
|
2117
|
+
};
|
|
2118
|
+
}, [client]);
|
|
2053
2119
|
const loadModels = useCallback(async () => {
|
|
2054
2120
|
if (!client) {
|
|
2055
2121
|
log("Cannot load models - client not ready");
|
|
@@ -2930,12 +2996,16 @@ function ensureModalStyles() {
|
|
|
2930
2996
|
left: 0;
|
|
2931
2997
|
right: 0;
|
|
2932
2998
|
bottom: 0;
|
|
2933
|
-
background:
|
|
2999
|
+
background: transparent;
|
|
2934
3000
|
display: flex;
|
|
2935
3001
|
align-items: center;
|
|
2936
3002
|
justify-content: center;
|
|
2937
3003
|
z-index: 10000;
|
|
2938
|
-
|
|
3004
|
+
pointer-events: none;
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
.user-question-modal {
|
|
3008
|
+
pointer-events: auto;
|
|
2939
3009
|
}
|
|
2940
3010
|
|
|
2941
3011
|
@keyframes uqFadeIn {
|
|
@@ -3048,6 +3118,27 @@ function ensureModalStyles() {
|
|
|
3048
3118
|
color: #666;
|
|
3049
3119
|
cursor: not-allowed;
|
|
3050
3120
|
}
|
|
3121
|
+
|
|
3122
|
+
.user-question-custom-input {
|
|
3123
|
+
width: 100%;
|
|
3124
|
+
padding: 8px 12px;
|
|
3125
|
+
margin-top: 8px;
|
|
3126
|
+
background: #1a1a2e;
|
|
3127
|
+
border: 1px solid #444;
|
|
3128
|
+
border-radius: 6px;
|
|
3129
|
+
color: #e0e0e0;
|
|
3130
|
+
font-size: 14px;
|
|
3131
|
+
box-sizing: border-box;
|
|
3132
|
+
}
|
|
3133
|
+
|
|
3134
|
+
.user-question-custom-input:focus {
|
|
3135
|
+
outline: none;
|
|
3136
|
+
border-color: #4a7aff;
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
.user-question-custom-input::placeholder {
|
|
3140
|
+
color: #666;
|
|
3141
|
+
}
|
|
3051
3142
|
`;
|
|
3052
3143
|
document.head.appendChild(styles2);
|
|
3053
3144
|
}
|
|
@@ -3069,13 +3160,17 @@ var askUserTool = {
|
|
|
3069
3160
|
allowMultiple: {
|
|
3070
3161
|
type: "boolean",
|
|
3071
3162
|
description: "If true, user can select multiple choices. Default: false"
|
|
3163
|
+
},
|
|
3164
|
+
allowCustom: {
|
|
3165
|
+
type: "boolean",
|
|
3166
|
+
description: 'If true, adds an "Other" option where user can type a custom response. Default: false'
|
|
3072
3167
|
}
|
|
3073
3168
|
},
|
|
3074
3169
|
required: ["question", "choices"]
|
|
3075
3170
|
}
|
|
3076
3171
|
};
|
|
3077
3172
|
var askUserExecutor = async (args2) => {
|
|
3078
|
-
const { question, choices, allowMultiple = false } = args2;
|
|
3173
|
+
const { question, choices, allowMultiple = false, allowCustom = false } = args2;
|
|
3079
3174
|
if (!question || !choices || !Array.isArray(choices) || choices.length === 0) {
|
|
3080
3175
|
return {
|
|
3081
3176
|
question: question || "",
|
|
@@ -3107,6 +3202,17 @@ var askUserExecutor = async (args2) => {
|
|
|
3107
3202
|
choicesDiv.className = "user-question-choices";
|
|
3108
3203
|
const inputType = allowMultiple ? "checkbox" : "radio";
|
|
3109
3204
|
const inputName = `uq-${Date.now()}`;
|
|
3205
|
+
let customInput = null;
|
|
3206
|
+
let isCustomSelected = false;
|
|
3207
|
+
const submitBtn = document.createElement("button");
|
|
3208
|
+
submitBtn.className = "user-question-btn user-question-btn-submit";
|
|
3209
|
+
submitBtn.textContent = "Submit";
|
|
3210
|
+
submitBtn.disabled = true;
|
|
3211
|
+
const updateSubmitButton = () => {
|
|
3212
|
+
const hasSelection = selected.size > 0;
|
|
3213
|
+
const hasCustomValue = isCustomSelected && customInput && customInput.value.trim().length > 0;
|
|
3214
|
+
submitBtn.disabled = !hasSelection && !hasCustomValue;
|
|
3215
|
+
};
|
|
3110
3216
|
choices.forEach((choice, index) => {
|
|
3111
3217
|
const choiceDiv = document.createElement("div");
|
|
3112
3218
|
choiceDiv.className = "user-question-choice";
|
|
@@ -3131,6 +3237,8 @@ var askUserExecutor = async (args2) => {
|
|
|
3131
3237
|
}
|
|
3132
3238
|
} else {
|
|
3133
3239
|
selected.clear();
|
|
3240
|
+
isCustomSelected = false;
|
|
3241
|
+
if (customInput) customInput.value = "";
|
|
3134
3242
|
selected.add(choice);
|
|
3135
3243
|
choicesDiv.querySelectorAll(".user-question-choice").forEach((c) => c.classList.remove("selected"));
|
|
3136
3244
|
choiceDiv.classList.add("selected");
|
|
@@ -3146,9 +3254,62 @@ var askUserExecutor = async (args2) => {
|
|
|
3146
3254
|
});
|
|
3147
3255
|
choicesDiv.appendChild(choiceDiv);
|
|
3148
3256
|
});
|
|
3257
|
+
if (allowCustom) {
|
|
3258
|
+
const customChoiceDiv = document.createElement("div");
|
|
3259
|
+
customChoiceDiv.className = "user-question-choice";
|
|
3260
|
+
const customRadio = document.createElement("input");
|
|
3261
|
+
customRadio.type = inputType;
|
|
3262
|
+
customRadio.name = inputName;
|
|
3263
|
+
customRadio.id = `${inputName}-custom`;
|
|
3264
|
+
customRadio.value = "__custom__";
|
|
3265
|
+
const customLabel = document.createElement("label");
|
|
3266
|
+
customLabel.htmlFor = customRadio.id;
|
|
3267
|
+
customLabel.textContent = "Other:";
|
|
3268
|
+
customLabel.style.flexShrink = "0";
|
|
3269
|
+
customInput = document.createElement("input");
|
|
3270
|
+
customInput.type = "text";
|
|
3271
|
+
customInput.className = "user-question-custom-input";
|
|
3272
|
+
customInput.placeholder = "Type your answer...";
|
|
3273
|
+
customInput.style.marginTop = "0";
|
|
3274
|
+
customInput.style.marginLeft = "8px";
|
|
3275
|
+
customInput.style.flex = "1";
|
|
3276
|
+
customChoiceDiv.appendChild(customRadio);
|
|
3277
|
+
customChoiceDiv.appendChild(customLabel);
|
|
3278
|
+
customChoiceDiv.appendChild(customInput);
|
|
3279
|
+
const handleCustomSelect = () => {
|
|
3280
|
+
if (!allowMultiple) {
|
|
3281
|
+
selected.clear();
|
|
3282
|
+
choicesDiv.querySelectorAll(".user-question-choice").forEach((c) => c.classList.remove("selected"));
|
|
3283
|
+
}
|
|
3284
|
+
isCustomSelected = true;
|
|
3285
|
+
customChoiceDiv.classList.add("selected");
|
|
3286
|
+
customInput?.focus();
|
|
3287
|
+
updateSubmitButton();
|
|
3288
|
+
};
|
|
3289
|
+
customRadio.addEventListener("change", handleCustomSelect);
|
|
3290
|
+
customChoiceDiv.addEventListener("click", (e) => {
|
|
3291
|
+
if (e.target !== customRadio && e.target !== customInput) {
|
|
3292
|
+
customRadio.checked = true;
|
|
3293
|
+
handleCustomSelect();
|
|
3294
|
+
}
|
|
3295
|
+
});
|
|
3296
|
+
customInput.addEventListener("focus", () => {
|
|
3297
|
+
if (!customRadio.checked) {
|
|
3298
|
+
customRadio.checked = true;
|
|
3299
|
+
handleCustomSelect();
|
|
3300
|
+
}
|
|
3301
|
+
});
|
|
3302
|
+
customInput.addEventListener("input", updateSubmitButton);
|
|
3303
|
+
choicesDiv.appendChild(customChoiceDiv);
|
|
3304
|
+
}
|
|
3149
3305
|
modal.appendChild(choicesDiv);
|
|
3150
3306
|
const actions = document.createElement("div");
|
|
3151
3307
|
actions.className = "user-question-actions";
|
|
3308
|
+
overlay.appendChild(modal);
|
|
3309
|
+
document.body.appendChild(overlay);
|
|
3310
|
+
const cleanup = () => {
|
|
3311
|
+
overlay.remove();
|
|
3312
|
+
};
|
|
3152
3313
|
const cancelBtn = document.createElement("button");
|
|
3153
3314
|
cancelBtn.className = "user-question-btn user-question-btn-cancel";
|
|
3154
3315
|
cancelBtn.textContent = "Skip";
|
|
@@ -3160,29 +3321,21 @@ var askUserExecutor = async (args2) => {
|
|
|
3160
3321
|
answered: false
|
|
3161
3322
|
});
|
|
3162
3323
|
};
|
|
3163
|
-
const submitBtn = document.createElement("button");
|
|
3164
|
-
submitBtn.className = "user-question-btn user-question-btn-submit";
|
|
3165
|
-
submitBtn.textContent = "Submit";
|
|
3166
|
-
submitBtn.disabled = true;
|
|
3167
3324
|
submitBtn.onclick = () => {
|
|
3168
3325
|
cleanup();
|
|
3326
|
+
const results = Array.from(selected);
|
|
3327
|
+
if (isCustomSelected && customInput && customInput.value.trim()) {
|
|
3328
|
+
results.push(customInput.value.trim());
|
|
3329
|
+
}
|
|
3169
3330
|
resolve({
|
|
3170
3331
|
question,
|
|
3171
|
-
selectedChoices:
|
|
3332
|
+
selectedChoices: results,
|
|
3172
3333
|
answered: true
|
|
3173
3334
|
});
|
|
3174
3335
|
};
|
|
3175
|
-
const updateSubmitButton = () => {
|
|
3176
|
-
submitBtn.disabled = selected.size === 0;
|
|
3177
|
-
};
|
|
3178
3336
|
actions.appendChild(cancelBtn);
|
|
3179
3337
|
actions.appendChild(submitBtn);
|
|
3180
3338
|
modal.appendChild(actions);
|
|
3181
|
-
overlay.appendChild(modal);
|
|
3182
|
-
document.body.appendChild(overlay);
|
|
3183
|
-
const cleanup = () => {
|
|
3184
|
-
overlay.remove();
|
|
3185
|
-
};
|
|
3186
3339
|
const handleEscape = (e) => {
|
|
3187
3340
|
if (e.key === "Escape") {
|
|
3188
3341
|
document.removeEventListener("keydown", handleEscape);
|
|
@@ -3351,6 +3504,11 @@ var screenshotTool = {
|
|
|
3351
3504
|
|
|
3352
3505
|
The screenshot captures the visible viewport of the page. The image is uploaded to the server and a permanent URL is returned.
|
|
3353
3506
|
|
|
3507
|
+
IMPORTANT: Before taking a screenshot, use the ask_user tool (if available) to ask which size they prefer:
|
|
3508
|
+
- "full" (100%) - highest quality, larger file
|
|
3509
|
+
- "half" (50%) - good balance of quality and size
|
|
3510
|
+
- "quarter" (25%) - smallest file, faster upload
|
|
3511
|
+
|
|
3354
3512
|
Use this when:
|
|
3355
3513
|
- User asks to see what's on their screen
|
|
3356
3514
|
- You need to analyze the current page visually
|
|
@@ -3361,12 +3519,24 @@ Use this when:
|
|
|
3361
3519
|
selector: {
|
|
3362
3520
|
type: "string",
|
|
3363
3521
|
description: "Optional CSS selector to capture a specific element instead of the full page. Leave empty for full page screenshot."
|
|
3522
|
+
},
|
|
3523
|
+
size: {
|
|
3524
|
+
type: "string",
|
|
3525
|
+
enum: ["full", "half", "quarter"],
|
|
3526
|
+
description: 'Image size: "full" (100%), "half" (50%), or "quarter" (25%). Ask the user which size they prefer before capturing.'
|
|
3364
3527
|
}
|
|
3365
3528
|
}
|
|
3366
3529
|
}
|
|
3367
3530
|
};
|
|
3368
3531
|
var screenshotExecutor = async (args2) => {
|
|
3369
3532
|
const selector = args2.selector;
|
|
3533
|
+
const size = args2.size || "full";
|
|
3534
|
+
const scaleMap = {
|
|
3535
|
+
full: 1,
|
|
3536
|
+
half: 0.5,
|
|
3537
|
+
quarter: 0.25
|
|
3538
|
+
};
|
|
3539
|
+
const scale = scaleMap[size] || 1;
|
|
3370
3540
|
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
3371
3541
|
return {
|
|
3372
3542
|
success: false,
|
|
@@ -3387,34 +3557,41 @@ var screenshotExecutor = async (args2) => {
|
|
|
3387
3557
|
if (!target) {
|
|
3388
3558
|
return { success: false, error: "Element not found: " + selector };
|
|
3389
3559
|
}
|
|
3390
|
-
const
|
|
3560
|
+
const fullCanvas = await window.html2canvas(target, {
|
|
3391
3561
|
useCORS: true,
|
|
3392
3562
|
allowTaint: true,
|
|
3393
3563
|
backgroundColor: "#000000",
|
|
3394
3564
|
scale: 1
|
|
3395
3565
|
});
|
|
3566
|
+
let finalCanvas = fullCanvas;
|
|
3567
|
+
if (scale < 1) {
|
|
3568
|
+
const resizedCanvas = document.createElement("canvas");
|
|
3569
|
+
resizedCanvas.width = Math.round(fullCanvas.width * scale);
|
|
3570
|
+
resizedCanvas.height = Math.round(fullCanvas.height * scale);
|
|
3571
|
+
const ctx = resizedCanvas.getContext("2d");
|
|
3572
|
+
if (ctx) {
|
|
3573
|
+
ctx.drawImage(fullCanvas, 0, 0, resizedCanvas.width, resizedCanvas.height);
|
|
3574
|
+
finalCanvas = resizedCanvas;
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3396
3577
|
const blob = await new Promise((resolve) => {
|
|
3397
|
-
|
|
3578
|
+
finalCanvas.toBlob(resolve, "image/png", 0.9);
|
|
3398
3579
|
});
|
|
3399
3580
|
if (!blob) {
|
|
3400
3581
|
return { success: false, error: "Failed to create image blob" };
|
|
3401
3582
|
}
|
|
3402
|
-
const
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
method: "POST",
|
|
3406
|
-
body: formData
|
|
3407
|
-
});
|
|
3408
|
-
if (!response.ok) {
|
|
3409
|
-
const errorData = await response.json().catch(() => ({}));
|
|
3410
|
-
return { success: false, error: errorData.error || "Upload failed" };
|
|
3583
|
+
const uploadFn = window.__hustleUploadFile;
|
|
3584
|
+
if (!uploadFn) {
|
|
3585
|
+
return { success: false, error: "Upload not available. Make sure HustleProvider is mounted and client is ready." };
|
|
3411
3586
|
}
|
|
3412
|
-
const
|
|
3587
|
+
const fileName = "screenshot-" + Date.now() + ".png";
|
|
3588
|
+
const attachment = await uploadFn(blob, fileName);
|
|
3589
|
+
const sizeLabel = size === "full" ? "100%" : size === "half" ? "50%" : "25%";
|
|
3413
3590
|
return {
|
|
3414
3591
|
success: true,
|
|
3415
|
-
url:
|
|
3416
|
-
contentType:
|
|
3417
|
-
message:
|
|
3592
|
+
url: attachment.url,
|
|
3593
|
+
contentType: attachment.contentType || "image/png",
|
|
3594
|
+
message: `Screenshot captured at ${sizeLabel} size (${finalCanvas.width}x${finalCanvas.height}) and uploaded successfully`
|
|
3418
3595
|
};
|
|
3419
3596
|
} catch (e) {
|
|
3420
3597
|
const err = e;
|
|
@@ -3482,21 +3659,89 @@ Write them as arrow function strings that will be eval'd:
|
|
|
3482
3659
|
"async (args) => { const { city } = args; return { weather: 'sunny', city }; }"
|
|
3483
3660
|
|
|
3484
3661
|
The function receives args as Record<string, unknown> and should return the result.
|
|
3485
|
-
|
|
3662
|
+
|
|
3663
|
+
## Available in Executor Scope
|
|
3664
|
+
|
|
3665
|
+
Executors run in the browser context with full access to:
|
|
3666
|
+
|
|
3667
|
+
### Browser APIs
|
|
3668
|
+
- **fetch(url, options)** - HTTP requests (subject to CORS)
|
|
3669
|
+
- **localStorage / sessionStorage** - Persistent storage
|
|
3670
|
+
- **document** - Full DOM access (create elements, modals, forms, etc.)
|
|
3671
|
+
- **window** - Global window object
|
|
3672
|
+
- **console** - Logging (log, warn, error, etc.)
|
|
3673
|
+
- **setTimeout / setInterval / clearTimeout / clearInterval** - Timers
|
|
3674
|
+
- **JSON** - Parse and stringify
|
|
3675
|
+
- **Date** - Date/time operations
|
|
3676
|
+
- **URL / URLSearchParams** - URL manipulation
|
|
3677
|
+
- **FormData / Blob / File / FileReader** - File handling
|
|
3678
|
+
- **crypto** - Cryptographic operations (crypto.randomUUID(), etc.)
|
|
3679
|
+
- **navigator** - Browser info, clipboard, geolocation, etc.
|
|
3680
|
+
- **location** - Current URL info
|
|
3681
|
+
- **history** - Browser history navigation
|
|
3682
|
+
- **WebSocket** - Real-time bidirectional communication
|
|
3683
|
+
- **EventSource** - Server-sent events
|
|
3684
|
+
- **indexedDB** - Client-side database for large data
|
|
3685
|
+
- **Notification** - Browser notifications (requires permission)
|
|
3686
|
+
- **performance** - Performance timing
|
|
3687
|
+
- **atob / btoa** - Base64 encoding/decoding
|
|
3688
|
+
- **TextEncoder / TextDecoder** - Text encoding
|
|
3689
|
+
- **AbortController** - Cancel fetch requests
|
|
3690
|
+
- **IntersectionObserver / MutationObserver / ResizeObserver** - DOM observers
|
|
3691
|
+
- **requestAnimationFrame** - Animation timing
|
|
3692
|
+
- **speechSynthesis** - Text-to-speech
|
|
3693
|
+
- **Audio / Image / Canvas** - Media APIs
|
|
3694
|
+
|
|
3695
|
+
### Hustle Plugin System Globals
|
|
3696
|
+
- **window.__hustleInstanceId** - Current Hustle instance ID
|
|
3697
|
+
- **window.__hustleRegisterPlugin(plugin, enabled)** - Install another plugin dynamically
|
|
3698
|
+
- **window.__hustleUnregisterPlugin(name)** - Uninstall a plugin by name
|
|
3699
|
+
- **window.__hustleUploadFile(file, fileName?)** - Upload a File/Blob to the server, returns { url, contentType }
|
|
3700
|
+
- **window.__hustleListPlugins()** - List all installed plugins
|
|
3701
|
+
- **window.__hustleGetPlugin(name)** - Get a specific plugin by name
|
|
3702
|
+
|
|
3703
|
+
### DOM Manipulation Examples
|
|
3704
|
+
Create a modal: document.createElement('div'), style it, append to document.body
|
|
3705
|
+
Add event listeners: element.addEventListener('click', handler)
|
|
3706
|
+
Query elements: document.querySelector(), document.querySelectorAll()
|
|
3707
|
+
|
|
3708
|
+
### Storage Patterns
|
|
3709
|
+
Store data: localStorage.setItem('key', JSON.stringify(data))
|
|
3710
|
+
Retrieve data: JSON.parse(localStorage.getItem('key') || '{}')
|
|
3711
|
+
Namespace your keys: Use plugin name prefix like "myplugin-settings"
|
|
3712
|
+
|
|
3713
|
+
### Async Patterns
|
|
3714
|
+
All executors should be async. Use await for promises:
|
|
3715
|
+
"async (args) => { const res = await fetch(url); return await res.json(); }"
|
|
3486
3716
|
|
|
3487
3717
|
## Lifecycle Hooks (Optional)
|
|
3488
3718
|
|
|
3489
|
-
|
|
3719
|
+
Hooks also have full access to the browser scope described above.
|
|
3720
|
+
|
|
3721
|
+
- **onRegisterCode**: Called once when plugin is registered/enabled. Good for initialization.
|
|
3722
|
+
**IMPORTANT: Always log when your plugin registers so users know it's active!**
|
|
3723
|
+
Example: "async () => { console.log('[MyPlugin] v1.0.0 registered'); }"
|
|
3724
|
+
|
|
3725
|
+
- **beforeRequestCode**: Modify messages before sending. Receives request object with { messages, model, ... }. Must return the modified request.
|
|
3490
3726
|
Example: "async (req) => { req.messages = req.messages.map(m => ({...m, content: m.content.toUpperCase()})); return req; }"
|
|
3491
3727
|
|
|
3492
|
-
- **afterResponseCode**: Process response after receiving. Receives response object.
|
|
3493
|
-
Example: "async (res) => { console.log('Response:', res.content); }"
|
|
3728
|
+
- **afterResponseCode**: Process/modify response after receiving. Receives response object with { content, ... }.
|
|
3729
|
+
Example: "async (res) => { console.log('Response received:', res.content.substring(0, 100)); }"
|
|
3730
|
+
|
|
3731
|
+
- **onErrorCode**: Called on errors. Receives (error, context) where context has { phase: 'beforeRequest'|'execute'|'afterResponse' }.
|
|
3732
|
+
Example: "async (error, ctx) => { console.error('[MyPlugin] Error in', ctx.phase, ':', error.message); }"
|
|
3733
|
+
|
|
3734
|
+
## Best Practices
|
|
3494
3735
|
|
|
3495
|
-
|
|
3496
|
-
|
|
3736
|
+
1. **Always add onRegisterCode** that logs the plugin name and version
|
|
3737
|
+
2. **Namespace console logs** with [PluginName] prefix for easy identification
|
|
3738
|
+
3. **Handle errors gracefully** in executors - return { error: message } instead of throwing
|
|
3497
3739
|
|
|
3498
|
-
|
|
3499
|
-
|
|
3740
|
+
## Security Notes
|
|
3741
|
+
- Code runs in browser sandbox with same-origin policy
|
|
3742
|
+
- fetch() is subject to CORS restrictions
|
|
3743
|
+
- No direct filesystem access (use File API with user interaction)
|
|
3744
|
+
- Be careful with eval() on user input`,
|
|
3500
3745
|
parameters: {
|
|
3501
3746
|
type: "object",
|
|
3502
3747
|
properties: {
|
|
@@ -3585,6 +3830,104 @@ var installPluginTool = {
|
|
|
3585
3830
|
required: ["plugin"]
|
|
3586
3831
|
}
|
|
3587
3832
|
};
|
|
3833
|
+
var uninstallPluginTool = {
|
|
3834
|
+
name: "uninstall_plugin",
|
|
3835
|
+
description: "Uninstall a plugin by name, removing it from browser storage.",
|
|
3836
|
+
parameters: {
|
|
3837
|
+
type: "object",
|
|
3838
|
+
properties: {
|
|
3839
|
+
name: {
|
|
3840
|
+
type: "string",
|
|
3841
|
+
description: "The name of the plugin to uninstall"
|
|
3842
|
+
}
|
|
3843
|
+
},
|
|
3844
|
+
required: ["name"]
|
|
3845
|
+
}
|
|
3846
|
+
};
|
|
3847
|
+
var listPluginsTool = {
|
|
3848
|
+
name: "list_plugins",
|
|
3849
|
+
description: "List all installed plugins with their enabled/disabled status.",
|
|
3850
|
+
parameters: {
|
|
3851
|
+
type: "object",
|
|
3852
|
+
properties: {},
|
|
3853
|
+
required: []
|
|
3854
|
+
}
|
|
3855
|
+
};
|
|
3856
|
+
var modifyPluginTool = {
|
|
3857
|
+
name: "modify_plugin",
|
|
3858
|
+
description: `Modify an existing installed plugin. Can update version, description, tools, executors, and hooks.
|
|
3859
|
+
|
|
3860
|
+
Use list_plugins first to see installed plugins, then modify by name.
|
|
3861
|
+
|
|
3862
|
+
You can:
|
|
3863
|
+
- Add new tools (provide tools array with new tools to add)
|
|
3864
|
+
- Update existing tools (provide tool with same name)
|
|
3865
|
+
- Remove tools (set removeTool to the tool name)
|
|
3866
|
+
- Update hooks (provide hook code)
|
|
3867
|
+
- Update version/description
|
|
3868
|
+
|
|
3869
|
+
Example: Add a new tool to existing plugin:
|
|
3870
|
+
{
|
|
3871
|
+
"name": "my-plugin",
|
|
3872
|
+
"addTools": [{ "name": "new_tool", "description": "...", "parameters": {...} }],
|
|
3873
|
+
"addExecutorCode": { "new_tool": "async (args) => { ... }" }
|
|
3874
|
+
}`,
|
|
3875
|
+
parameters: {
|
|
3876
|
+
type: "object",
|
|
3877
|
+
properties: {
|
|
3878
|
+
name: {
|
|
3879
|
+
type: "string",
|
|
3880
|
+
description: "Name of the plugin to modify (required)"
|
|
3881
|
+
},
|
|
3882
|
+
version: {
|
|
3883
|
+
type: "string",
|
|
3884
|
+
description: "New version string"
|
|
3885
|
+
},
|
|
3886
|
+
description: {
|
|
3887
|
+
type: "string",
|
|
3888
|
+
description: "New description"
|
|
3889
|
+
},
|
|
3890
|
+
addTools: {
|
|
3891
|
+
type: "array",
|
|
3892
|
+
description: "Tools to add or update",
|
|
3893
|
+
items: {
|
|
3894
|
+
type: "object",
|
|
3895
|
+
properties: {
|
|
3896
|
+
name: { type: "string" },
|
|
3897
|
+
description: { type: "string" },
|
|
3898
|
+
parameters: { type: "object" }
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
},
|
|
3902
|
+
addExecutorCode: {
|
|
3903
|
+
type: "object",
|
|
3904
|
+
description: "Executor code to add/update (tool name -> code string)"
|
|
3905
|
+
},
|
|
3906
|
+
removeTools: {
|
|
3907
|
+
type: "array",
|
|
3908
|
+
description: "Names of tools to remove",
|
|
3909
|
+
items: { type: "string" }
|
|
3910
|
+
},
|
|
3911
|
+
onRegisterCode: {
|
|
3912
|
+
type: "string",
|
|
3913
|
+
description: "New onRegister hook code"
|
|
3914
|
+
},
|
|
3915
|
+
beforeRequestCode: {
|
|
3916
|
+
type: "string",
|
|
3917
|
+
description: "New beforeRequest hook code"
|
|
3918
|
+
},
|
|
3919
|
+
afterResponseCode: {
|
|
3920
|
+
type: "string",
|
|
3921
|
+
description: "New afterResponse hook code"
|
|
3922
|
+
},
|
|
3923
|
+
onErrorCode: {
|
|
3924
|
+
type: "string",
|
|
3925
|
+
description: "New onError hook code"
|
|
3926
|
+
}
|
|
3927
|
+
},
|
|
3928
|
+
required: ["name"]
|
|
3929
|
+
}
|
|
3930
|
+
};
|
|
3588
3931
|
var buildPluginExecutor = async (args2) => {
|
|
3589
3932
|
const {
|
|
3590
3933
|
name,
|
|
@@ -3641,12 +3984,51 @@ var buildPluginExecutor = async (args2) => {
|
|
|
3641
3984
|
message: `Plugin "${name}" v${version} built successfully with ${tools.length} tool(s). Use save_plugin to download or install_plugin to add to browser storage.`
|
|
3642
3985
|
};
|
|
3643
3986
|
};
|
|
3987
|
+
function normalizePlugin(input) {
|
|
3988
|
+
const plugin = input;
|
|
3989
|
+
const tools = plugin.tools;
|
|
3990
|
+
const hasEmbeddedExecutors = tools?.[0]?.executorCode !== void 0;
|
|
3991
|
+
if (hasEmbeddedExecutors) {
|
|
3992
|
+
return {
|
|
3993
|
+
...plugin,
|
|
3994
|
+
enabled: plugin.enabled ?? true,
|
|
3995
|
+
installedAt: plugin.installedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
3996
|
+
};
|
|
3997
|
+
}
|
|
3998
|
+
const executorCode = plugin.executorCode;
|
|
3999
|
+
const rawTools = tools || [];
|
|
4000
|
+
const hookKeys = ["onRegisterCode", "beforeRequestCode", "afterResponseCode", "onErrorCode"];
|
|
4001
|
+
const hooksCode = {};
|
|
4002
|
+
for (const key of hookKeys) {
|
|
4003
|
+
if (executorCode?.[key]) {
|
|
4004
|
+
hooksCode[key] = executorCode[key];
|
|
4005
|
+
}
|
|
4006
|
+
if (plugin[key]) {
|
|
4007
|
+
hooksCode[key] = plugin[key];
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
return {
|
|
4011
|
+
name: plugin.name,
|
|
4012
|
+
version: plugin.version,
|
|
4013
|
+
description: plugin.description,
|
|
4014
|
+
tools: rawTools.map((tool) => ({
|
|
4015
|
+
name: tool.name,
|
|
4016
|
+
description: tool.description,
|
|
4017
|
+
parameters: tool.parameters,
|
|
4018
|
+
executorCode: executorCode?.[tool.name]
|
|
4019
|
+
})),
|
|
4020
|
+
hooksCode: Object.keys(hooksCode).length > 0 ? hooksCode : void 0,
|
|
4021
|
+
enabled: true,
|
|
4022
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4023
|
+
};
|
|
4024
|
+
}
|
|
3644
4025
|
var savePluginExecutor = async (args2) => {
|
|
3645
|
-
const { plugin, filename } = args2;
|
|
4026
|
+
const { plugin: rawPlugin, filename } = args2;
|
|
3646
4027
|
if (typeof window === "undefined") {
|
|
3647
4028
|
return { success: false, error: "Cannot save files in server environment" };
|
|
3648
4029
|
}
|
|
3649
4030
|
try {
|
|
4031
|
+
const plugin = normalizePlugin(rawPlugin);
|
|
3650
4032
|
const json2 = JSON.stringify(plugin, null, 2);
|
|
3651
4033
|
const blob = new Blob([json2], { type: "application/json" });
|
|
3652
4034
|
const url = URL.createObjectURL(blob);
|
|
@@ -3669,31 +4051,23 @@ var savePluginExecutor = async (args2) => {
|
|
|
3669
4051
|
}
|
|
3670
4052
|
};
|
|
3671
4053
|
var installPluginExecutor = async (args2) => {
|
|
3672
|
-
const { plugin, enabled = true } = args2;
|
|
4054
|
+
const { plugin: rawPlugin, enabled = true } = args2;
|
|
3673
4055
|
if (typeof window === "undefined") {
|
|
3674
4056
|
return { success: false, error: "Cannot install plugins in server environment" };
|
|
3675
4057
|
}
|
|
3676
4058
|
try {
|
|
3677
|
-
const
|
|
3678
|
-
const
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
enabled,
|
|
3685
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3686
|
-
};
|
|
3687
|
-
if (existingIndex >= 0) {
|
|
3688
|
-
plugins[existingIndex] = pluginToStore;
|
|
3689
|
-
} else {
|
|
3690
|
-
plugins.push(pluginToStore);
|
|
4059
|
+
const plugin = normalizePlugin(rawPlugin);
|
|
4060
|
+
const win = window;
|
|
4061
|
+
if (!win.__hustleRegisterPlugin) {
|
|
4062
|
+
return {
|
|
4063
|
+
success: false,
|
|
4064
|
+
error: "Plugin registration not available. Make sure HustleProvider is mounted."
|
|
4065
|
+
};
|
|
3691
4066
|
}
|
|
3692
|
-
|
|
4067
|
+
await win.__hustleRegisterPlugin(plugin, enabled);
|
|
3693
4068
|
return {
|
|
3694
4069
|
success: true,
|
|
3695
|
-
message: `Plugin "${plugin.name}" installed${enabled ? " and enabled" : ""}
|
|
3696
|
-
action: existingIndex >= 0 ? "updated" : "installed"
|
|
4070
|
+
message: `Plugin "${plugin.name}" installed${enabled ? " and enabled" : ""}.`
|
|
3697
4071
|
};
|
|
3698
4072
|
} catch (error2) {
|
|
3699
4073
|
return {
|
|
@@ -3702,15 +4076,168 @@ var installPluginExecutor = async (args2) => {
|
|
|
3702
4076
|
};
|
|
3703
4077
|
}
|
|
3704
4078
|
};
|
|
4079
|
+
var uninstallPluginExecutor = async (args2) => {
|
|
4080
|
+
const { name } = args2;
|
|
4081
|
+
if (typeof window === "undefined") {
|
|
4082
|
+
return { success: false, error: "Cannot uninstall plugins in server environment" };
|
|
4083
|
+
}
|
|
4084
|
+
try {
|
|
4085
|
+
const win = window;
|
|
4086
|
+
if (!win.__hustleUnregisterPlugin) {
|
|
4087
|
+
return {
|
|
4088
|
+
success: false,
|
|
4089
|
+
error: "Plugin unregistration not available. Make sure HustleProvider is mounted."
|
|
4090
|
+
};
|
|
4091
|
+
}
|
|
4092
|
+
await win.__hustleUnregisterPlugin(name);
|
|
4093
|
+
return {
|
|
4094
|
+
success: true,
|
|
4095
|
+
message: `Plugin "${name}" has been uninstalled.`
|
|
4096
|
+
};
|
|
4097
|
+
} catch (error2) {
|
|
4098
|
+
return {
|
|
4099
|
+
success: false,
|
|
4100
|
+
error: `Failed to uninstall: ${error2 instanceof Error ? error2.message : "Unknown error"}`
|
|
4101
|
+
};
|
|
4102
|
+
}
|
|
4103
|
+
};
|
|
4104
|
+
var listPluginsExecutor = async () => {
|
|
4105
|
+
if (typeof window === "undefined") {
|
|
4106
|
+
return { success: false, error: "Cannot list plugins in server environment" };
|
|
4107
|
+
}
|
|
4108
|
+
try {
|
|
4109
|
+
const win = window;
|
|
4110
|
+
if (!win.__hustleListPlugins) {
|
|
4111
|
+
return {
|
|
4112
|
+
success: false,
|
|
4113
|
+
error: "Plugin listing not available. Make sure HustleProvider is mounted."
|
|
4114
|
+
};
|
|
4115
|
+
}
|
|
4116
|
+
const plugins = win.__hustleListPlugins();
|
|
4117
|
+
return {
|
|
4118
|
+
success: true,
|
|
4119
|
+
plugins,
|
|
4120
|
+
summary: plugins.length === 0 ? "No plugins installed." : `${plugins.length} plugin(s) installed: ${plugins.map((p) => `${p.name} v${p.version} (${p.enabled ? "enabled" : "disabled"})`).join(", ")}`
|
|
4121
|
+
};
|
|
4122
|
+
} catch (error2) {
|
|
4123
|
+
return {
|
|
4124
|
+
success: false,
|
|
4125
|
+
error: `Failed to list plugins: ${error2 instanceof Error ? error2.message : "Unknown error"}`
|
|
4126
|
+
};
|
|
4127
|
+
}
|
|
4128
|
+
};
|
|
4129
|
+
var modifyPluginExecutor = async (args2) => {
|
|
4130
|
+
const {
|
|
4131
|
+
name,
|
|
4132
|
+
version,
|
|
4133
|
+
description,
|
|
4134
|
+
addTools,
|
|
4135
|
+
addExecutorCode,
|
|
4136
|
+
removeTools,
|
|
4137
|
+
onRegisterCode,
|
|
4138
|
+
beforeRequestCode,
|
|
4139
|
+
afterResponseCode,
|
|
4140
|
+
onErrorCode
|
|
4141
|
+
} = args2;
|
|
4142
|
+
if (typeof window === "undefined") {
|
|
4143
|
+
return { success: false, error: "Cannot modify plugins in server environment" };
|
|
4144
|
+
}
|
|
4145
|
+
try {
|
|
4146
|
+
const win = window;
|
|
4147
|
+
if (!win.__hustleGetPlugin || !win.__hustleRegisterPlugin) {
|
|
4148
|
+
return {
|
|
4149
|
+
success: false,
|
|
4150
|
+
error: "Plugin modification not available. Make sure HustleProvider is mounted."
|
|
4151
|
+
};
|
|
4152
|
+
}
|
|
4153
|
+
const existing = win.__hustleGetPlugin(name);
|
|
4154
|
+
if (!existing) {
|
|
4155
|
+
return {
|
|
4156
|
+
success: false,
|
|
4157
|
+
error: `Plugin "${name}" not found. Use list_plugins to see installed plugins.`
|
|
4158
|
+
};
|
|
4159
|
+
}
|
|
4160
|
+
let tools = existing.tools ? [...existing.tools] : [];
|
|
4161
|
+
if (removeTools && removeTools.length > 0) {
|
|
4162
|
+
tools = tools.filter((t) => !removeTools.includes(t.name));
|
|
4163
|
+
}
|
|
4164
|
+
if (addTools && addTools.length > 0) {
|
|
4165
|
+
for (const newTool of addTools) {
|
|
4166
|
+
const existingIndex = tools.findIndex((t) => t.name === newTool.name);
|
|
4167
|
+
const toolWithExecutor = {
|
|
4168
|
+
...newTool,
|
|
4169
|
+
executorCode: addExecutorCode?.[newTool.name] || tools.find((t) => t.name === newTool.name)?.executorCode
|
|
4170
|
+
};
|
|
4171
|
+
if (existingIndex >= 0) {
|
|
4172
|
+
tools[existingIndex] = toolWithExecutor;
|
|
4173
|
+
} else {
|
|
4174
|
+
tools.push(toolWithExecutor);
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
}
|
|
4178
|
+
if (addExecutorCode) {
|
|
4179
|
+
for (const [toolName, code2] of Object.entries(addExecutorCode)) {
|
|
4180
|
+
const tool = tools.find((t) => t.name === toolName);
|
|
4181
|
+
if (tool) {
|
|
4182
|
+
tool.executorCode = code2;
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
const modified = {
|
|
4187
|
+
...existing,
|
|
4188
|
+
tools
|
|
4189
|
+
};
|
|
4190
|
+
if (version) modified.version = version;
|
|
4191
|
+
if (description) modified.description = description;
|
|
4192
|
+
if (onRegisterCode || beforeRequestCode || afterResponseCode || onErrorCode) {
|
|
4193
|
+
modified.hooksCode = modified.hooksCode || {};
|
|
4194
|
+
if (onRegisterCode) modified.hooksCode.onRegisterCode = onRegisterCode;
|
|
4195
|
+
if (beforeRequestCode) modified.hooksCode.beforeRequestCode = beforeRequestCode;
|
|
4196
|
+
if (afterResponseCode) modified.hooksCode.afterResponseCode = afterResponseCode;
|
|
4197
|
+
if (onErrorCode) modified.hooksCode.onErrorCode = onErrorCode;
|
|
4198
|
+
}
|
|
4199
|
+
const unregisterFn = window.__hustleUnregisterPlugin;
|
|
4200
|
+
if (unregisterFn) {
|
|
4201
|
+
await unregisterFn(name);
|
|
4202
|
+
}
|
|
4203
|
+
await win.__hustleRegisterPlugin(modified, existing.enabled);
|
|
4204
|
+
const changes = [];
|
|
4205
|
+
if (version) changes.push(`version \u2192 ${version}`);
|
|
4206
|
+
if (description) changes.push("description updated");
|
|
4207
|
+
if (removeTools?.length) changes.push(`removed ${removeTools.length} tool(s)`);
|
|
4208
|
+
if (addTools?.length) changes.push(`added/updated ${addTools.length} tool(s)`);
|
|
4209
|
+
if (onRegisterCode) changes.push("onRegister hook updated");
|
|
4210
|
+
if (beforeRequestCode) changes.push("beforeRequest hook updated");
|
|
4211
|
+
if (afterResponseCode) changes.push("afterResponse hook updated");
|
|
4212
|
+
if (onErrorCode) changes.push("onError hook updated");
|
|
4213
|
+
return {
|
|
4214
|
+
success: true,
|
|
4215
|
+
message: `Plugin "${name}" modified: ${changes.join(", ")}`,
|
|
4216
|
+
plugin: {
|
|
4217
|
+
name: modified.name,
|
|
4218
|
+
version: modified.version,
|
|
4219
|
+
toolCount: tools.length
|
|
4220
|
+
}
|
|
4221
|
+
};
|
|
4222
|
+
} catch (error2) {
|
|
4223
|
+
return {
|
|
4224
|
+
success: false,
|
|
4225
|
+
error: `Failed to modify plugin: ${error2 instanceof Error ? error2.message : "Unknown error"}`
|
|
4226
|
+
};
|
|
4227
|
+
}
|
|
4228
|
+
};
|
|
3705
4229
|
var pluginBuilderPlugin = {
|
|
3706
4230
|
name: "plugin-builder",
|
|
3707
|
-
version: "1.
|
|
4231
|
+
version: "1.2.0",
|
|
3708
4232
|
description: "Build custom plugins through conversation",
|
|
3709
|
-
tools: [buildPluginTool, savePluginTool, installPluginTool],
|
|
4233
|
+
tools: [buildPluginTool, savePluginTool, installPluginTool, uninstallPluginTool, listPluginsTool, modifyPluginTool],
|
|
3710
4234
|
executors: {
|
|
3711
4235
|
build_plugin: buildPluginExecutor,
|
|
3712
4236
|
save_plugin: savePluginExecutor,
|
|
3713
|
-
install_plugin: installPluginExecutor
|
|
4237
|
+
install_plugin: installPluginExecutor,
|
|
4238
|
+
uninstall_plugin: uninstallPluginExecutor,
|
|
4239
|
+
list_plugins: listPluginsExecutor,
|
|
4240
|
+
modify_plugin: modifyPluginExecutor
|
|
3714
4241
|
},
|
|
3715
4242
|
hooks: {
|
|
3716
4243
|
onRegister: () => {
|