@emblemvault/hustle-react 1.0.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 +856 -45
- package/dist/browser/hustle-react.js.map +1 -1
- package/dist/components/index.cjs +807 -45
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +807 -45
- 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 +856 -45
- 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 +856 -45
- 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 +777 -31
- package/dist/plugins/index.cjs.map +1 -1
- package/dist/plugins/index.d.cts +14 -2
- package/dist/plugins/index.d.ts +14 -2
- package/dist/plugins/index.js +777 -32
- 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
package/dist/plugins/index.js
CHANGED
|
@@ -676,12 +676,16 @@ function ensureModalStyles() {
|
|
|
676
676
|
left: 0;
|
|
677
677
|
right: 0;
|
|
678
678
|
bottom: 0;
|
|
679
|
-
background:
|
|
679
|
+
background: transparent;
|
|
680
680
|
display: flex;
|
|
681
681
|
align-items: center;
|
|
682
682
|
justify-content: center;
|
|
683
683
|
z-index: 10000;
|
|
684
|
-
|
|
684
|
+
pointer-events: none;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.user-question-modal {
|
|
688
|
+
pointer-events: auto;
|
|
685
689
|
}
|
|
686
690
|
|
|
687
691
|
@keyframes uqFadeIn {
|
|
@@ -794,6 +798,27 @@ function ensureModalStyles() {
|
|
|
794
798
|
color: #666;
|
|
795
799
|
cursor: not-allowed;
|
|
796
800
|
}
|
|
801
|
+
|
|
802
|
+
.user-question-custom-input {
|
|
803
|
+
width: 100%;
|
|
804
|
+
padding: 8px 12px;
|
|
805
|
+
margin-top: 8px;
|
|
806
|
+
background: #1a1a2e;
|
|
807
|
+
border: 1px solid #444;
|
|
808
|
+
border-radius: 6px;
|
|
809
|
+
color: #e0e0e0;
|
|
810
|
+
font-size: 14px;
|
|
811
|
+
box-sizing: border-box;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
.user-question-custom-input:focus {
|
|
815
|
+
outline: none;
|
|
816
|
+
border-color: #4a7aff;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
.user-question-custom-input::placeholder {
|
|
820
|
+
color: #666;
|
|
821
|
+
}
|
|
797
822
|
`;
|
|
798
823
|
document.head.appendChild(styles);
|
|
799
824
|
}
|
|
@@ -815,13 +840,17 @@ var askUserTool = {
|
|
|
815
840
|
allowMultiple: {
|
|
816
841
|
type: "boolean",
|
|
817
842
|
description: "If true, user can select multiple choices. Default: false"
|
|
843
|
+
},
|
|
844
|
+
allowCustom: {
|
|
845
|
+
type: "boolean",
|
|
846
|
+
description: 'If true, adds an "Other" option where user can type a custom response. Default: false'
|
|
818
847
|
}
|
|
819
848
|
},
|
|
820
849
|
required: ["question", "choices"]
|
|
821
850
|
}
|
|
822
851
|
};
|
|
823
852
|
var askUserExecutor = async (args2) => {
|
|
824
|
-
const { question, choices, allowMultiple = false } = args2;
|
|
853
|
+
const { question, choices, allowMultiple = false, allowCustom = false } = args2;
|
|
825
854
|
if (!question || !choices || !Array.isArray(choices) || choices.length === 0) {
|
|
826
855
|
return {
|
|
827
856
|
question: question || "",
|
|
@@ -853,6 +882,17 @@ var askUserExecutor = async (args2) => {
|
|
|
853
882
|
choicesDiv.className = "user-question-choices";
|
|
854
883
|
const inputType = allowMultiple ? "checkbox" : "radio";
|
|
855
884
|
const inputName = `uq-${Date.now()}`;
|
|
885
|
+
let customInput = null;
|
|
886
|
+
let isCustomSelected = false;
|
|
887
|
+
const submitBtn = document.createElement("button");
|
|
888
|
+
submitBtn.className = "user-question-btn user-question-btn-submit";
|
|
889
|
+
submitBtn.textContent = "Submit";
|
|
890
|
+
submitBtn.disabled = true;
|
|
891
|
+
const updateSubmitButton = () => {
|
|
892
|
+
const hasSelection = selected.size > 0;
|
|
893
|
+
const hasCustomValue = isCustomSelected && customInput && customInput.value.trim().length > 0;
|
|
894
|
+
submitBtn.disabled = !hasSelection && !hasCustomValue;
|
|
895
|
+
};
|
|
856
896
|
choices.forEach((choice, index) => {
|
|
857
897
|
const choiceDiv = document.createElement("div");
|
|
858
898
|
choiceDiv.className = "user-question-choice";
|
|
@@ -877,6 +917,8 @@ var askUserExecutor = async (args2) => {
|
|
|
877
917
|
}
|
|
878
918
|
} else {
|
|
879
919
|
selected.clear();
|
|
920
|
+
isCustomSelected = false;
|
|
921
|
+
if (customInput) customInput.value = "";
|
|
880
922
|
selected.add(choice);
|
|
881
923
|
choicesDiv.querySelectorAll(".user-question-choice").forEach((c) => c.classList.remove("selected"));
|
|
882
924
|
choiceDiv.classList.add("selected");
|
|
@@ -892,9 +934,62 @@ var askUserExecutor = async (args2) => {
|
|
|
892
934
|
});
|
|
893
935
|
choicesDiv.appendChild(choiceDiv);
|
|
894
936
|
});
|
|
937
|
+
if (allowCustom) {
|
|
938
|
+
const customChoiceDiv = document.createElement("div");
|
|
939
|
+
customChoiceDiv.className = "user-question-choice";
|
|
940
|
+
const customRadio = document.createElement("input");
|
|
941
|
+
customRadio.type = inputType;
|
|
942
|
+
customRadio.name = inputName;
|
|
943
|
+
customRadio.id = `${inputName}-custom`;
|
|
944
|
+
customRadio.value = "__custom__";
|
|
945
|
+
const customLabel = document.createElement("label");
|
|
946
|
+
customLabel.htmlFor = customRadio.id;
|
|
947
|
+
customLabel.textContent = "Other:";
|
|
948
|
+
customLabel.style.flexShrink = "0";
|
|
949
|
+
customInput = document.createElement("input");
|
|
950
|
+
customInput.type = "text";
|
|
951
|
+
customInput.className = "user-question-custom-input";
|
|
952
|
+
customInput.placeholder = "Type your answer...";
|
|
953
|
+
customInput.style.marginTop = "0";
|
|
954
|
+
customInput.style.marginLeft = "8px";
|
|
955
|
+
customInput.style.flex = "1";
|
|
956
|
+
customChoiceDiv.appendChild(customRadio);
|
|
957
|
+
customChoiceDiv.appendChild(customLabel);
|
|
958
|
+
customChoiceDiv.appendChild(customInput);
|
|
959
|
+
const handleCustomSelect = () => {
|
|
960
|
+
if (!allowMultiple) {
|
|
961
|
+
selected.clear();
|
|
962
|
+
choicesDiv.querySelectorAll(".user-question-choice").forEach((c) => c.classList.remove("selected"));
|
|
963
|
+
}
|
|
964
|
+
isCustomSelected = true;
|
|
965
|
+
customChoiceDiv.classList.add("selected");
|
|
966
|
+
customInput?.focus();
|
|
967
|
+
updateSubmitButton();
|
|
968
|
+
};
|
|
969
|
+
customRadio.addEventListener("change", handleCustomSelect);
|
|
970
|
+
customChoiceDiv.addEventListener("click", (e) => {
|
|
971
|
+
if (e.target !== customRadio && e.target !== customInput) {
|
|
972
|
+
customRadio.checked = true;
|
|
973
|
+
handleCustomSelect();
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
customInput.addEventListener("focus", () => {
|
|
977
|
+
if (!customRadio.checked) {
|
|
978
|
+
customRadio.checked = true;
|
|
979
|
+
handleCustomSelect();
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
customInput.addEventListener("input", updateSubmitButton);
|
|
983
|
+
choicesDiv.appendChild(customChoiceDiv);
|
|
984
|
+
}
|
|
895
985
|
modal.appendChild(choicesDiv);
|
|
896
986
|
const actions = document.createElement("div");
|
|
897
987
|
actions.className = "user-question-actions";
|
|
988
|
+
overlay.appendChild(modal);
|
|
989
|
+
document.body.appendChild(overlay);
|
|
990
|
+
const cleanup = () => {
|
|
991
|
+
overlay.remove();
|
|
992
|
+
};
|
|
898
993
|
const cancelBtn = document.createElement("button");
|
|
899
994
|
cancelBtn.className = "user-question-btn user-question-btn-cancel";
|
|
900
995
|
cancelBtn.textContent = "Skip";
|
|
@@ -906,29 +1001,21 @@ var askUserExecutor = async (args2) => {
|
|
|
906
1001
|
answered: false
|
|
907
1002
|
});
|
|
908
1003
|
};
|
|
909
|
-
const submitBtn = document.createElement("button");
|
|
910
|
-
submitBtn.className = "user-question-btn user-question-btn-submit";
|
|
911
|
-
submitBtn.textContent = "Submit";
|
|
912
|
-
submitBtn.disabled = true;
|
|
913
1004
|
submitBtn.onclick = () => {
|
|
914
1005
|
cleanup();
|
|
1006
|
+
const results = Array.from(selected);
|
|
1007
|
+
if (isCustomSelected && customInput && customInput.value.trim()) {
|
|
1008
|
+
results.push(customInput.value.trim());
|
|
1009
|
+
}
|
|
915
1010
|
resolve({
|
|
916
1011
|
question,
|
|
917
|
-
selectedChoices:
|
|
1012
|
+
selectedChoices: results,
|
|
918
1013
|
answered: true
|
|
919
1014
|
});
|
|
920
1015
|
};
|
|
921
|
-
const updateSubmitButton = () => {
|
|
922
|
-
submitBtn.disabled = selected.size === 0;
|
|
923
|
-
};
|
|
924
1016
|
actions.appendChild(cancelBtn);
|
|
925
1017
|
actions.appendChild(submitBtn);
|
|
926
1018
|
modal.appendChild(actions);
|
|
927
|
-
overlay.appendChild(modal);
|
|
928
|
-
document.body.appendChild(overlay);
|
|
929
|
-
const cleanup = () => {
|
|
930
|
-
overlay.remove();
|
|
931
|
-
};
|
|
932
1019
|
const handleEscape = (e) => {
|
|
933
1020
|
if (e.key === "Escape") {
|
|
934
1021
|
document.removeEventListener("keydown", handleEscape);
|
|
@@ -1097,6 +1184,11 @@ var screenshotTool = {
|
|
|
1097
1184
|
|
|
1098
1185
|
The screenshot captures the visible viewport of the page. The image is uploaded to the server and a permanent URL is returned.
|
|
1099
1186
|
|
|
1187
|
+
IMPORTANT: Before taking a screenshot, use the ask_user tool (if available) to ask which size they prefer:
|
|
1188
|
+
- "full" (100%) - highest quality, larger file
|
|
1189
|
+
- "half" (50%) - good balance of quality and size
|
|
1190
|
+
- "quarter" (25%) - smallest file, faster upload
|
|
1191
|
+
|
|
1100
1192
|
Use this when:
|
|
1101
1193
|
- User asks to see what's on their screen
|
|
1102
1194
|
- You need to analyze the current page visually
|
|
@@ -1107,12 +1199,24 @@ Use this when:
|
|
|
1107
1199
|
selector: {
|
|
1108
1200
|
type: "string",
|
|
1109
1201
|
description: "Optional CSS selector to capture a specific element instead of the full page. Leave empty for full page screenshot."
|
|
1202
|
+
},
|
|
1203
|
+
size: {
|
|
1204
|
+
type: "string",
|
|
1205
|
+
enum: ["full", "half", "quarter"],
|
|
1206
|
+
description: 'Image size: "full" (100%), "half" (50%), or "quarter" (25%). Ask the user which size they prefer before capturing.'
|
|
1110
1207
|
}
|
|
1111
1208
|
}
|
|
1112
1209
|
}
|
|
1113
1210
|
};
|
|
1114
1211
|
var screenshotExecutor = async (args2) => {
|
|
1115
1212
|
const selector = args2.selector;
|
|
1213
|
+
const size = args2.size || "full";
|
|
1214
|
+
const scaleMap = {
|
|
1215
|
+
full: 1,
|
|
1216
|
+
half: 0.5,
|
|
1217
|
+
quarter: 0.25
|
|
1218
|
+
};
|
|
1219
|
+
const scale = scaleMap[size] || 1;
|
|
1116
1220
|
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
1117
1221
|
return {
|
|
1118
1222
|
success: false,
|
|
@@ -1133,34 +1237,41 @@ var screenshotExecutor = async (args2) => {
|
|
|
1133
1237
|
if (!target) {
|
|
1134
1238
|
return { success: false, error: "Element not found: " + selector };
|
|
1135
1239
|
}
|
|
1136
|
-
const
|
|
1240
|
+
const fullCanvas = await window.html2canvas(target, {
|
|
1137
1241
|
useCORS: true,
|
|
1138
1242
|
allowTaint: true,
|
|
1139
1243
|
backgroundColor: "#000000",
|
|
1140
1244
|
scale: 1
|
|
1141
1245
|
});
|
|
1246
|
+
let finalCanvas = fullCanvas;
|
|
1247
|
+
if (scale < 1) {
|
|
1248
|
+
const resizedCanvas = document.createElement("canvas");
|
|
1249
|
+
resizedCanvas.width = Math.round(fullCanvas.width * scale);
|
|
1250
|
+
resizedCanvas.height = Math.round(fullCanvas.height * scale);
|
|
1251
|
+
const ctx = resizedCanvas.getContext("2d");
|
|
1252
|
+
if (ctx) {
|
|
1253
|
+
ctx.drawImage(fullCanvas, 0, 0, resizedCanvas.width, resizedCanvas.height);
|
|
1254
|
+
finalCanvas = resizedCanvas;
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1142
1257
|
const blob = await new Promise((resolve) => {
|
|
1143
|
-
|
|
1258
|
+
finalCanvas.toBlob(resolve, "image/png", 0.9);
|
|
1144
1259
|
});
|
|
1145
1260
|
if (!blob) {
|
|
1146
1261
|
return { success: false, error: "Failed to create image blob" };
|
|
1147
1262
|
}
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
method: "POST",
|
|
1152
|
-
body: formData
|
|
1153
|
-
});
|
|
1154
|
-
if (!response.ok) {
|
|
1155
|
-
const errorData = await response.json().catch(() => ({}));
|
|
1156
|
-
return { success: false, error: errorData.error || "Upload failed" };
|
|
1263
|
+
const uploadFn = window.__hustleUploadFile;
|
|
1264
|
+
if (!uploadFn) {
|
|
1265
|
+
return { success: false, error: "Upload not available. Make sure HustleProvider is mounted and client is ready." };
|
|
1157
1266
|
}
|
|
1158
|
-
const
|
|
1267
|
+
const fileName = "screenshot-" + Date.now() + ".png";
|
|
1268
|
+
const attachment = await uploadFn(blob, fileName);
|
|
1269
|
+
const sizeLabel = size === "full" ? "100%" : size === "half" ? "50%" : "25%";
|
|
1159
1270
|
return {
|
|
1160
1271
|
success: true,
|
|
1161
|
-
url:
|
|
1162
|
-
contentType:
|
|
1163
|
-
message:
|
|
1272
|
+
url: attachment.url,
|
|
1273
|
+
contentType: attachment.contentType || "image/png",
|
|
1274
|
+
message: `Screenshot captured at ${sizeLabel} size (${finalCanvas.width}x${finalCanvas.height}) and uploaded successfully`
|
|
1164
1275
|
};
|
|
1165
1276
|
} catch (e) {
|
|
1166
1277
|
const err = e;
|
|
@@ -1185,6 +1296,636 @@ var screenshotPlugin = {
|
|
|
1185
1296
|
}
|
|
1186
1297
|
};
|
|
1187
1298
|
|
|
1299
|
+
// src/plugins/pluginBuilder.ts
|
|
1300
|
+
var buildPluginTool = {
|
|
1301
|
+
name: "build_plugin",
|
|
1302
|
+
description: `Build a Hustle plugin definition. Use this tool to construct a plugin based on user requirements.
|
|
1303
|
+
|
|
1304
|
+
## Plugin Structure
|
|
1305
|
+
|
|
1306
|
+
A plugin consists of:
|
|
1307
|
+
- **name**: Unique identifier (lowercase, no spaces, e.g., "my-plugin")
|
|
1308
|
+
- **version**: Semantic version (e.g., "1.0.0")
|
|
1309
|
+
- **description**: What the plugin does
|
|
1310
|
+
- **tools**: Array of tool definitions the AI can call
|
|
1311
|
+
- **executorCode**: Object mapping tool names to JavaScript function code strings
|
|
1312
|
+
|
|
1313
|
+
## Tool Definition Format
|
|
1314
|
+
|
|
1315
|
+
Each tool needs:
|
|
1316
|
+
- **name**: Unique tool name (alphanumeric + underscore, e.g., "get_weather")
|
|
1317
|
+
- **description**: Clear description for the AI to understand when to use it
|
|
1318
|
+
- **parameters**: JSON Schema object defining the arguments
|
|
1319
|
+
|
|
1320
|
+
Example tool:
|
|
1321
|
+
{
|
|
1322
|
+
"name": "get_weather",
|
|
1323
|
+
"description": "Get current weather for a city",
|
|
1324
|
+
"parameters": {
|
|
1325
|
+
"type": "object",
|
|
1326
|
+
"properties": {
|
|
1327
|
+
"city": { "type": "string", "description": "City name" },
|
|
1328
|
+
"units": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "Temperature units" }
|
|
1329
|
+
},
|
|
1330
|
+
"required": ["city"]
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
## Executor Code Format
|
|
1335
|
+
|
|
1336
|
+
Executors are async JavaScript functions that receive args and return a result.
|
|
1337
|
+
Write them as arrow function strings that will be eval'd:
|
|
1338
|
+
|
|
1339
|
+
"async (args) => { const { city } = args; return { weather: 'sunny', city }; }"
|
|
1340
|
+
|
|
1341
|
+
The function receives args as Record<string, unknown> and should return the result.
|
|
1342
|
+
|
|
1343
|
+
## Available in Executor Scope
|
|
1344
|
+
|
|
1345
|
+
Executors run in the browser context with full access to:
|
|
1346
|
+
|
|
1347
|
+
### Browser APIs
|
|
1348
|
+
- **fetch(url, options)** - HTTP requests (subject to CORS)
|
|
1349
|
+
- **localStorage / sessionStorage** - Persistent storage
|
|
1350
|
+
- **document** - Full DOM access (create elements, modals, forms, etc.)
|
|
1351
|
+
- **window** - Global window object
|
|
1352
|
+
- **console** - Logging (log, warn, error, etc.)
|
|
1353
|
+
- **setTimeout / setInterval / clearTimeout / clearInterval** - Timers
|
|
1354
|
+
- **JSON** - Parse and stringify
|
|
1355
|
+
- **Date** - Date/time operations
|
|
1356
|
+
- **URL / URLSearchParams** - URL manipulation
|
|
1357
|
+
- **FormData / Blob / File / FileReader** - File handling
|
|
1358
|
+
- **crypto** - Cryptographic operations (crypto.randomUUID(), etc.)
|
|
1359
|
+
- **navigator** - Browser info, clipboard, geolocation, etc.
|
|
1360
|
+
- **location** - Current URL info
|
|
1361
|
+
- **history** - Browser history navigation
|
|
1362
|
+
- **WebSocket** - Real-time bidirectional communication
|
|
1363
|
+
- **EventSource** - Server-sent events
|
|
1364
|
+
- **indexedDB** - Client-side database for large data
|
|
1365
|
+
- **Notification** - Browser notifications (requires permission)
|
|
1366
|
+
- **performance** - Performance timing
|
|
1367
|
+
- **atob / btoa** - Base64 encoding/decoding
|
|
1368
|
+
- **TextEncoder / TextDecoder** - Text encoding
|
|
1369
|
+
- **AbortController** - Cancel fetch requests
|
|
1370
|
+
- **IntersectionObserver / MutationObserver / ResizeObserver** - DOM observers
|
|
1371
|
+
- **requestAnimationFrame** - Animation timing
|
|
1372
|
+
- **speechSynthesis** - Text-to-speech
|
|
1373
|
+
- **Audio / Image / Canvas** - Media APIs
|
|
1374
|
+
|
|
1375
|
+
### Hustle Plugin System Globals
|
|
1376
|
+
- **window.__hustleInstanceId** - Current Hustle instance ID
|
|
1377
|
+
- **window.__hustleRegisterPlugin(plugin, enabled)** - Install another plugin dynamically
|
|
1378
|
+
- **window.__hustleUnregisterPlugin(name)** - Uninstall a plugin by name
|
|
1379
|
+
- **window.__hustleUploadFile(file, fileName?)** - Upload a File/Blob to the server, returns { url, contentType }
|
|
1380
|
+
- **window.__hustleListPlugins()** - List all installed plugins
|
|
1381
|
+
- **window.__hustleGetPlugin(name)** - Get a specific plugin by name
|
|
1382
|
+
|
|
1383
|
+
### DOM Manipulation Examples
|
|
1384
|
+
Create a modal: document.createElement('div'), style it, append to document.body
|
|
1385
|
+
Add event listeners: element.addEventListener('click', handler)
|
|
1386
|
+
Query elements: document.querySelector(), document.querySelectorAll()
|
|
1387
|
+
|
|
1388
|
+
### Storage Patterns
|
|
1389
|
+
Store data: localStorage.setItem('key', JSON.stringify(data))
|
|
1390
|
+
Retrieve data: JSON.parse(localStorage.getItem('key') || '{}')
|
|
1391
|
+
Namespace your keys: Use plugin name prefix like "myplugin-settings"
|
|
1392
|
+
|
|
1393
|
+
### Async Patterns
|
|
1394
|
+
All executors should be async. Use await for promises:
|
|
1395
|
+
"async (args) => { const res = await fetch(url); return await res.json(); }"
|
|
1396
|
+
|
|
1397
|
+
## Lifecycle Hooks (Optional)
|
|
1398
|
+
|
|
1399
|
+
Hooks also have full access to the browser scope described above.
|
|
1400
|
+
|
|
1401
|
+
- **onRegisterCode**: Called once when plugin is registered/enabled. Good for initialization.
|
|
1402
|
+
**IMPORTANT: Always log when your plugin registers so users know it's active!**
|
|
1403
|
+
Example: "async () => { console.log('[MyPlugin] v1.0.0 registered'); }"
|
|
1404
|
+
|
|
1405
|
+
- **beforeRequestCode**: Modify messages before sending. Receives request object with { messages, model, ... }. Must return the modified request.
|
|
1406
|
+
Example: "async (req) => { req.messages = req.messages.map(m => ({...m, content: m.content.toUpperCase()})); return req; }"
|
|
1407
|
+
|
|
1408
|
+
- **afterResponseCode**: Process/modify response after receiving. Receives response object with { content, ... }.
|
|
1409
|
+
Example: "async (res) => { console.log('Response received:', res.content.substring(0, 100)); }"
|
|
1410
|
+
|
|
1411
|
+
- **onErrorCode**: Called on errors. Receives (error, context) where context has { phase: 'beforeRequest'|'execute'|'afterResponse' }.
|
|
1412
|
+
Example: "async (error, ctx) => { console.error('[MyPlugin] Error in', ctx.phase, ':', error.message); }"
|
|
1413
|
+
|
|
1414
|
+
## Best Practices
|
|
1415
|
+
|
|
1416
|
+
1. **Always add onRegisterCode** that logs the plugin name and version
|
|
1417
|
+
2. **Namespace console logs** with [PluginName] prefix for easy identification
|
|
1418
|
+
3. **Handle errors gracefully** in executors - return { error: message } instead of throwing
|
|
1419
|
+
|
|
1420
|
+
## Security Notes
|
|
1421
|
+
- Code runs in browser sandbox with same-origin policy
|
|
1422
|
+
- fetch() is subject to CORS restrictions
|
|
1423
|
+
- No direct filesystem access (use File API with user interaction)
|
|
1424
|
+
- Be careful with eval() on user input`,
|
|
1425
|
+
parameters: {
|
|
1426
|
+
type: "object",
|
|
1427
|
+
properties: {
|
|
1428
|
+
name: {
|
|
1429
|
+
type: "string",
|
|
1430
|
+
description: "Unique plugin identifier (lowercase, no spaces)"
|
|
1431
|
+
},
|
|
1432
|
+
version: {
|
|
1433
|
+
type: "string",
|
|
1434
|
+
description: 'Semantic version (e.g., "1.0.0")'
|
|
1435
|
+
},
|
|
1436
|
+
description: {
|
|
1437
|
+
type: "string",
|
|
1438
|
+
description: "What the plugin does"
|
|
1439
|
+
},
|
|
1440
|
+
tools: {
|
|
1441
|
+
type: "array",
|
|
1442
|
+
description: "Array of tool definitions",
|
|
1443
|
+
items: {
|
|
1444
|
+
type: "object",
|
|
1445
|
+
properties: {
|
|
1446
|
+
name: { type: "string", description: "Tool name" },
|
|
1447
|
+
description: { type: "string", description: "Tool description for AI" },
|
|
1448
|
+
parameters: { type: "object", description: "JSON Schema for arguments" }
|
|
1449
|
+
},
|
|
1450
|
+
required: ["name", "description", "parameters"]
|
|
1451
|
+
}
|
|
1452
|
+
},
|
|
1453
|
+
executorCode: {
|
|
1454
|
+
type: "object",
|
|
1455
|
+
description: "Object mapping tool names to executor function code strings"
|
|
1456
|
+
},
|
|
1457
|
+
beforeRequestCode: {
|
|
1458
|
+
type: "string",
|
|
1459
|
+
description: "Optional: Code for beforeRequest hook"
|
|
1460
|
+
},
|
|
1461
|
+
afterResponseCode: {
|
|
1462
|
+
type: "string",
|
|
1463
|
+
description: "Optional: Code for afterResponse hook"
|
|
1464
|
+
},
|
|
1465
|
+
onRegisterCode: {
|
|
1466
|
+
type: "string",
|
|
1467
|
+
description: "Optional: Code for onRegister hook"
|
|
1468
|
+
},
|
|
1469
|
+
onErrorCode: {
|
|
1470
|
+
type: "string",
|
|
1471
|
+
description: "Optional: Code for onError hook"
|
|
1472
|
+
}
|
|
1473
|
+
},
|
|
1474
|
+
required: ["name", "version", "description", "tools", "executorCode"]
|
|
1475
|
+
}
|
|
1476
|
+
};
|
|
1477
|
+
var savePluginTool = {
|
|
1478
|
+
name: "save_plugin",
|
|
1479
|
+
description: "Save a built plugin as a JSON file. Opens a download dialog for the user.",
|
|
1480
|
+
parameters: {
|
|
1481
|
+
type: "object",
|
|
1482
|
+
properties: {
|
|
1483
|
+
plugin: {
|
|
1484
|
+
type: "object",
|
|
1485
|
+
description: "The plugin object to save (from build_plugin result)"
|
|
1486
|
+
},
|
|
1487
|
+
filename: {
|
|
1488
|
+
type: "string",
|
|
1489
|
+
description: "Filename without extension (defaults to plugin name)"
|
|
1490
|
+
}
|
|
1491
|
+
},
|
|
1492
|
+
required: ["plugin"]
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
var installPluginTool = {
|
|
1496
|
+
name: "install_plugin",
|
|
1497
|
+
description: "Install a built plugin to browser storage so it persists and can be used.",
|
|
1498
|
+
parameters: {
|
|
1499
|
+
type: "object",
|
|
1500
|
+
properties: {
|
|
1501
|
+
plugin: {
|
|
1502
|
+
type: "object",
|
|
1503
|
+
description: "The plugin object to install (from build_plugin result)"
|
|
1504
|
+
},
|
|
1505
|
+
enabled: {
|
|
1506
|
+
type: "boolean",
|
|
1507
|
+
description: "Whether to enable the plugin immediately (default: true)"
|
|
1508
|
+
}
|
|
1509
|
+
},
|
|
1510
|
+
required: ["plugin"]
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
var uninstallPluginTool = {
|
|
1514
|
+
name: "uninstall_plugin",
|
|
1515
|
+
description: "Uninstall a plugin by name, removing it from browser storage.",
|
|
1516
|
+
parameters: {
|
|
1517
|
+
type: "object",
|
|
1518
|
+
properties: {
|
|
1519
|
+
name: {
|
|
1520
|
+
type: "string",
|
|
1521
|
+
description: "The name of the plugin to uninstall"
|
|
1522
|
+
}
|
|
1523
|
+
},
|
|
1524
|
+
required: ["name"]
|
|
1525
|
+
}
|
|
1526
|
+
};
|
|
1527
|
+
var listPluginsTool = {
|
|
1528
|
+
name: "list_plugins",
|
|
1529
|
+
description: "List all installed plugins with their enabled/disabled status.",
|
|
1530
|
+
parameters: {
|
|
1531
|
+
type: "object",
|
|
1532
|
+
properties: {},
|
|
1533
|
+
required: []
|
|
1534
|
+
}
|
|
1535
|
+
};
|
|
1536
|
+
var modifyPluginTool = {
|
|
1537
|
+
name: "modify_plugin",
|
|
1538
|
+
description: `Modify an existing installed plugin. Can update version, description, tools, executors, and hooks.
|
|
1539
|
+
|
|
1540
|
+
Use list_plugins first to see installed plugins, then modify by name.
|
|
1541
|
+
|
|
1542
|
+
You can:
|
|
1543
|
+
- Add new tools (provide tools array with new tools to add)
|
|
1544
|
+
- Update existing tools (provide tool with same name)
|
|
1545
|
+
- Remove tools (set removeTool to the tool name)
|
|
1546
|
+
- Update hooks (provide hook code)
|
|
1547
|
+
- Update version/description
|
|
1548
|
+
|
|
1549
|
+
Example: Add a new tool to existing plugin:
|
|
1550
|
+
{
|
|
1551
|
+
"name": "my-plugin",
|
|
1552
|
+
"addTools": [{ "name": "new_tool", "description": "...", "parameters": {...} }],
|
|
1553
|
+
"addExecutorCode": { "new_tool": "async (args) => { ... }" }
|
|
1554
|
+
}`,
|
|
1555
|
+
parameters: {
|
|
1556
|
+
type: "object",
|
|
1557
|
+
properties: {
|
|
1558
|
+
name: {
|
|
1559
|
+
type: "string",
|
|
1560
|
+
description: "Name of the plugin to modify (required)"
|
|
1561
|
+
},
|
|
1562
|
+
version: {
|
|
1563
|
+
type: "string",
|
|
1564
|
+
description: "New version string"
|
|
1565
|
+
},
|
|
1566
|
+
description: {
|
|
1567
|
+
type: "string",
|
|
1568
|
+
description: "New description"
|
|
1569
|
+
},
|
|
1570
|
+
addTools: {
|
|
1571
|
+
type: "array",
|
|
1572
|
+
description: "Tools to add or update",
|
|
1573
|
+
items: {
|
|
1574
|
+
type: "object",
|
|
1575
|
+
properties: {
|
|
1576
|
+
name: { type: "string" },
|
|
1577
|
+
description: { type: "string" },
|
|
1578
|
+
parameters: { type: "object" }
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
},
|
|
1582
|
+
addExecutorCode: {
|
|
1583
|
+
type: "object",
|
|
1584
|
+
description: "Executor code to add/update (tool name -> code string)"
|
|
1585
|
+
},
|
|
1586
|
+
removeTools: {
|
|
1587
|
+
type: "array",
|
|
1588
|
+
description: "Names of tools to remove",
|
|
1589
|
+
items: { type: "string" }
|
|
1590
|
+
},
|
|
1591
|
+
onRegisterCode: {
|
|
1592
|
+
type: "string",
|
|
1593
|
+
description: "New onRegister hook code"
|
|
1594
|
+
},
|
|
1595
|
+
beforeRequestCode: {
|
|
1596
|
+
type: "string",
|
|
1597
|
+
description: "New beforeRequest hook code"
|
|
1598
|
+
},
|
|
1599
|
+
afterResponseCode: {
|
|
1600
|
+
type: "string",
|
|
1601
|
+
description: "New afterResponse hook code"
|
|
1602
|
+
},
|
|
1603
|
+
onErrorCode: {
|
|
1604
|
+
type: "string",
|
|
1605
|
+
description: "New onError hook code"
|
|
1606
|
+
}
|
|
1607
|
+
},
|
|
1608
|
+
required: ["name"]
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
var buildPluginExecutor = async (args2) => {
|
|
1612
|
+
const {
|
|
1613
|
+
name,
|
|
1614
|
+
version,
|
|
1615
|
+
description,
|
|
1616
|
+
tools,
|
|
1617
|
+
executorCode,
|
|
1618
|
+
beforeRequestCode,
|
|
1619
|
+
afterResponseCode,
|
|
1620
|
+
onRegisterCode,
|
|
1621
|
+
onErrorCode
|
|
1622
|
+
} = args2;
|
|
1623
|
+
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
|
|
1624
|
+
return {
|
|
1625
|
+
success: false,
|
|
1626
|
+
error: "Plugin name must be lowercase, start with a letter, and contain only letters, numbers, and hyphens"
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
if (!/^\d+\.\d+\.\d+/.test(version)) {
|
|
1630
|
+
return {
|
|
1631
|
+
success: false,
|
|
1632
|
+
error: 'Version must be in semver format (e.g., "1.0.0")'
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
for (const tool of tools) {
|
|
1636
|
+
if (!executorCode[tool.name]) {
|
|
1637
|
+
return {
|
|
1638
|
+
success: false,
|
|
1639
|
+
error: `Missing executor code for tool: ${tool.name}`
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
const storedPlugin = {
|
|
1644
|
+
name,
|
|
1645
|
+
version,
|
|
1646
|
+
description,
|
|
1647
|
+
tools: tools.map((tool) => ({
|
|
1648
|
+
...tool,
|
|
1649
|
+
executorCode: executorCode[tool.name]
|
|
1650
|
+
})),
|
|
1651
|
+
enabled: true,
|
|
1652
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1653
|
+
};
|
|
1654
|
+
if (beforeRequestCode || afterResponseCode || onRegisterCode || onErrorCode) {
|
|
1655
|
+
storedPlugin.hooksCode = {};
|
|
1656
|
+
if (beforeRequestCode) storedPlugin.hooksCode.beforeRequestCode = beforeRequestCode;
|
|
1657
|
+
if (afterResponseCode) storedPlugin.hooksCode.afterResponseCode = afterResponseCode;
|
|
1658
|
+
if (onRegisterCode) storedPlugin.hooksCode.onRegisterCode = onRegisterCode;
|
|
1659
|
+
if (onErrorCode) storedPlugin.hooksCode.onErrorCode = onErrorCode;
|
|
1660
|
+
}
|
|
1661
|
+
return {
|
|
1662
|
+
success: true,
|
|
1663
|
+
plugin: storedPlugin,
|
|
1664
|
+
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.`
|
|
1665
|
+
};
|
|
1666
|
+
};
|
|
1667
|
+
function normalizePlugin(input) {
|
|
1668
|
+
const plugin = input;
|
|
1669
|
+
const tools = plugin.tools;
|
|
1670
|
+
const hasEmbeddedExecutors = tools?.[0]?.executorCode !== void 0;
|
|
1671
|
+
if (hasEmbeddedExecutors) {
|
|
1672
|
+
return {
|
|
1673
|
+
...plugin,
|
|
1674
|
+
enabled: plugin.enabled ?? true,
|
|
1675
|
+
installedAt: plugin.installedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
const executorCode = plugin.executorCode;
|
|
1679
|
+
const rawTools = tools || [];
|
|
1680
|
+
const hookKeys = ["onRegisterCode", "beforeRequestCode", "afterResponseCode", "onErrorCode"];
|
|
1681
|
+
const hooksCode = {};
|
|
1682
|
+
for (const key of hookKeys) {
|
|
1683
|
+
if (executorCode?.[key]) {
|
|
1684
|
+
hooksCode[key] = executorCode[key];
|
|
1685
|
+
}
|
|
1686
|
+
if (plugin[key]) {
|
|
1687
|
+
hooksCode[key] = plugin[key];
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
return {
|
|
1691
|
+
name: plugin.name,
|
|
1692
|
+
version: plugin.version,
|
|
1693
|
+
description: plugin.description,
|
|
1694
|
+
tools: rawTools.map((tool) => ({
|
|
1695
|
+
name: tool.name,
|
|
1696
|
+
description: tool.description,
|
|
1697
|
+
parameters: tool.parameters,
|
|
1698
|
+
executorCode: executorCode?.[tool.name]
|
|
1699
|
+
})),
|
|
1700
|
+
hooksCode: Object.keys(hooksCode).length > 0 ? hooksCode : void 0,
|
|
1701
|
+
enabled: true,
|
|
1702
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1705
|
+
var savePluginExecutor = async (args2) => {
|
|
1706
|
+
const { plugin: rawPlugin, filename } = args2;
|
|
1707
|
+
if (typeof window === "undefined") {
|
|
1708
|
+
return { success: false, error: "Cannot save files in server environment" };
|
|
1709
|
+
}
|
|
1710
|
+
try {
|
|
1711
|
+
const plugin = normalizePlugin(rawPlugin);
|
|
1712
|
+
const json = JSON.stringify(plugin, null, 2);
|
|
1713
|
+
const blob = new Blob([json], { type: "application/json" });
|
|
1714
|
+
const url = URL.createObjectURL(blob);
|
|
1715
|
+
const a = document.createElement("a");
|
|
1716
|
+
a.href = url;
|
|
1717
|
+
a.download = `${filename || plugin.name}.json`;
|
|
1718
|
+
document.body.appendChild(a);
|
|
1719
|
+
a.click();
|
|
1720
|
+
document.body.removeChild(a);
|
|
1721
|
+
URL.revokeObjectURL(url);
|
|
1722
|
+
return {
|
|
1723
|
+
success: true,
|
|
1724
|
+
message: `Plugin saved as ${filename || plugin.name}.json`
|
|
1725
|
+
};
|
|
1726
|
+
} catch (error2) {
|
|
1727
|
+
return {
|
|
1728
|
+
success: false,
|
|
1729
|
+
error: `Failed to save: ${error2 instanceof Error ? error2.message : "Unknown error"}`
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
};
|
|
1733
|
+
var installPluginExecutor = async (args2) => {
|
|
1734
|
+
const { plugin: rawPlugin, enabled = true } = args2;
|
|
1735
|
+
if (typeof window === "undefined") {
|
|
1736
|
+
return { success: false, error: "Cannot install plugins in server environment" };
|
|
1737
|
+
}
|
|
1738
|
+
try {
|
|
1739
|
+
const plugin = normalizePlugin(rawPlugin);
|
|
1740
|
+
const win = window;
|
|
1741
|
+
if (!win.__hustleRegisterPlugin) {
|
|
1742
|
+
return {
|
|
1743
|
+
success: false,
|
|
1744
|
+
error: "Plugin registration not available. Make sure HustleProvider is mounted."
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
await win.__hustleRegisterPlugin(plugin, enabled);
|
|
1748
|
+
return {
|
|
1749
|
+
success: true,
|
|
1750
|
+
message: `Plugin "${plugin.name}" installed${enabled ? " and enabled" : ""}.`
|
|
1751
|
+
};
|
|
1752
|
+
} catch (error2) {
|
|
1753
|
+
return {
|
|
1754
|
+
success: false,
|
|
1755
|
+
error: `Failed to install: ${error2 instanceof Error ? error2.message : "Unknown error"}`
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
};
|
|
1759
|
+
var uninstallPluginExecutor = async (args2) => {
|
|
1760
|
+
const { name } = args2;
|
|
1761
|
+
if (typeof window === "undefined") {
|
|
1762
|
+
return { success: false, error: "Cannot uninstall plugins in server environment" };
|
|
1763
|
+
}
|
|
1764
|
+
try {
|
|
1765
|
+
const win = window;
|
|
1766
|
+
if (!win.__hustleUnregisterPlugin) {
|
|
1767
|
+
return {
|
|
1768
|
+
success: false,
|
|
1769
|
+
error: "Plugin unregistration not available. Make sure HustleProvider is mounted."
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
await win.__hustleUnregisterPlugin(name);
|
|
1773
|
+
return {
|
|
1774
|
+
success: true,
|
|
1775
|
+
message: `Plugin "${name}" has been uninstalled.`
|
|
1776
|
+
};
|
|
1777
|
+
} catch (error2) {
|
|
1778
|
+
return {
|
|
1779
|
+
success: false,
|
|
1780
|
+
error: `Failed to uninstall: ${error2 instanceof Error ? error2.message : "Unknown error"}`
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
};
|
|
1784
|
+
var listPluginsExecutor = async () => {
|
|
1785
|
+
if (typeof window === "undefined") {
|
|
1786
|
+
return { success: false, error: "Cannot list plugins in server environment" };
|
|
1787
|
+
}
|
|
1788
|
+
try {
|
|
1789
|
+
const win = window;
|
|
1790
|
+
if (!win.__hustleListPlugins) {
|
|
1791
|
+
return {
|
|
1792
|
+
success: false,
|
|
1793
|
+
error: "Plugin listing not available. Make sure HustleProvider is mounted."
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
const plugins = win.__hustleListPlugins();
|
|
1797
|
+
return {
|
|
1798
|
+
success: true,
|
|
1799
|
+
plugins,
|
|
1800
|
+
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(", ")}`
|
|
1801
|
+
};
|
|
1802
|
+
} catch (error2) {
|
|
1803
|
+
return {
|
|
1804
|
+
success: false,
|
|
1805
|
+
error: `Failed to list plugins: ${error2 instanceof Error ? error2.message : "Unknown error"}`
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
};
|
|
1809
|
+
var modifyPluginExecutor = async (args2) => {
|
|
1810
|
+
const {
|
|
1811
|
+
name,
|
|
1812
|
+
version,
|
|
1813
|
+
description,
|
|
1814
|
+
addTools,
|
|
1815
|
+
addExecutorCode,
|
|
1816
|
+
removeTools,
|
|
1817
|
+
onRegisterCode,
|
|
1818
|
+
beforeRequestCode,
|
|
1819
|
+
afterResponseCode,
|
|
1820
|
+
onErrorCode
|
|
1821
|
+
} = args2;
|
|
1822
|
+
if (typeof window === "undefined") {
|
|
1823
|
+
return { success: false, error: "Cannot modify plugins in server environment" };
|
|
1824
|
+
}
|
|
1825
|
+
try {
|
|
1826
|
+
const win = window;
|
|
1827
|
+
if (!win.__hustleGetPlugin || !win.__hustleRegisterPlugin) {
|
|
1828
|
+
return {
|
|
1829
|
+
success: false,
|
|
1830
|
+
error: "Plugin modification not available. Make sure HustleProvider is mounted."
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
const existing = win.__hustleGetPlugin(name);
|
|
1834
|
+
if (!existing) {
|
|
1835
|
+
return {
|
|
1836
|
+
success: false,
|
|
1837
|
+
error: `Plugin "${name}" not found. Use list_plugins to see installed plugins.`
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
let tools = existing.tools ? [...existing.tools] : [];
|
|
1841
|
+
if (removeTools && removeTools.length > 0) {
|
|
1842
|
+
tools = tools.filter((t) => !removeTools.includes(t.name));
|
|
1843
|
+
}
|
|
1844
|
+
if (addTools && addTools.length > 0) {
|
|
1845
|
+
for (const newTool of addTools) {
|
|
1846
|
+
const existingIndex = tools.findIndex((t) => t.name === newTool.name);
|
|
1847
|
+
const toolWithExecutor = {
|
|
1848
|
+
...newTool,
|
|
1849
|
+
executorCode: addExecutorCode?.[newTool.name] || tools.find((t) => t.name === newTool.name)?.executorCode
|
|
1850
|
+
};
|
|
1851
|
+
if (existingIndex >= 0) {
|
|
1852
|
+
tools[existingIndex] = toolWithExecutor;
|
|
1853
|
+
} else {
|
|
1854
|
+
tools.push(toolWithExecutor);
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
if (addExecutorCode) {
|
|
1859
|
+
for (const [toolName, code2] of Object.entries(addExecutorCode)) {
|
|
1860
|
+
const tool = tools.find((t) => t.name === toolName);
|
|
1861
|
+
if (tool) {
|
|
1862
|
+
tool.executorCode = code2;
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
const modified = {
|
|
1867
|
+
...existing,
|
|
1868
|
+
tools
|
|
1869
|
+
};
|
|
1870
|
+
if (version) modified.version = version;
|
|
1871
|
+
if (description) modified.description = description;
|
|
1872
|
+
if (onRegisterCode || beforeRequestCode || afterResponseCode || onErrorCode) {
|
|
1873
|
+
modified.hooksCode = modified.hooksCode || {};
|
|
1874
|
+
if (onRegisterCode) modified.hooksCode.onRegisterCode = onRegisterCode;
|
|
1875
|
+
if (beforeRequestCode) modified.hooksCode.beforeRequestCode = beforeRequestCode;
|
|
1876
|
+
if (afterResponseCode) modified.hooksCode.afterResponseCode = afterResponseCode;
|
|
1877
|
+
if (onErrorCode) modified.hooksCode.onErrorCode = onErrorCode;
|
|
1878
|
+
}
|
|
1879
|
+
const unregisterFn = window.__hustleUnregisterPlugin;
|
|
1880
|
+
if (unregisterFn) {
|
|
1881
|
+
await unregisterFn(name);
|
|
1882
|
+
}
|
|
1883
|
+
await win.__hustleRegisterPlugin(modified, existing.enabled);
|
|
1884
|
+
const changes = [];
|
|
1885
|
+
if (version) changes.push(`version \u2192 ${version}`);
|
|
1886
|
+
if (description) changes.push("description updated");
|
|
1887
|
+
if (removeTools?.length) changes.push(`removed ${removeTools.length} tool(s)`);
|
|
1888
|
+
if (addTools?.length) changes.push(`added/updated ${addTools.length} tool(s)`);
|
|
1889
|
+
if (onRegisterCode) changes.push("onRegister hook updated");
|
|
1890
|
+
if (beforeRequestCode) changes.push("beforeRequest hook updated");
|
|
1891
|
+
if (afterResponseCode) changes.push("afterResponse hook updated");
|
|
1892
|
+
if (onErrorCode) changes.push("onError hook updated");
|
|
1893
|
+
return {
|
|
1894
|
+
success: true,
|
|
1895
|
+
message: `Plugin "${name}" modified: ${changes.join(", ")}`,
|
|
1896
|
+
plugin: {
|
|
1897
|
+
name: modified.name,
|
|
1898
|
+
version: modified.version,
|
|
1899
|
+
toolCount: tools.length
|
|
1900
|
+
}
|
|
1901
|
+
};
|
|
1902
|
+
} catch (error2) {
|
|
1903
|
+
return {
|
|
1904
|
+
success: false,
|
|
1905
|
+
error: `Failed to modify plugin: ${error2 instanceof Error ? error2.message : "Unknown error"}`
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
};
|
|
1909
|
+
var pluginBuilderPlugin = {
|
|
1910
|
+
name: "plugin-builder",
|
|
1911
|
+
version: "1.2.0",
|
|
1912
|
+
description: "Build custom plugins through conversation",
|
|
1913
|
+
tools: [buildPluginTool, savePluginTool, installPluginTool, uninstallPluginTool, listPluginsTool, modifyPluginTool],
|
|
1914
|
+
executors: {
|
|
1915
|
+
build_plugin: buildPluginExecutor,
|
|
1916
|
+
save_plugin: savePluginExecutor,
|
|
1917
|
+
install_plugin: installPluginExecutor,
|
|
1918
|
+
uninstall_plugin: uninstallPluginExecutor,
|
|
1919
|
+
list_plugins: listPluginsExecutor,
|
|
1920
|
+
modify_plugin: modifyPluginExecutor
|
|
1921
|
+
},
|
|
1922
|
+
hooks: {
|
|
1923
|
+
onRegister: () => {
|
|
1924
|
+
console.log("[Plugin Builder] Ready to help build custom plugins");
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
};
|
|
1928
|
+
|
|
1188
1929
|
// src/plugins/index.ts
|
|
1189
1930
|
var availablePlugins = [
|
|
1190
1931
|
{
|
|
@@ -1214,12 +1955,16 @@ var availablePlugins = [
|
|
|
1214
1955
|
{
|
|
1215
1956
|
...screenshotPlugin,
|
|
1216
1957
|
description: "Take screenshots of the current page"
|
|
1958
|
+
},
|
|
1959
|
+
{
|
|
1960
|
+
...pluginBuilderPlugin,
|
|
1961
|
+
description: "Build custom plugins through conversation with AI"
|
|
1217
1962
|
}
|
|
1218
1963
|
];
|
|
1219
1964
|
function getAvailablePlugin(name) {
|
|
1220
1965
|
return availablePlugins.find((p) => p.name === name);
|
|
1221
1966
|
}
|
|
1222
1967
|
|
|
1223
|
-
export { alertPlugin, availablePlugins, getAvailablePlugin, jsExecutorPlugin, migrateFunPlugin, piiProtectionPlugin, predictionMarketPlugin, screenshotPlugin, userQuestionPlugin };
|
|
1968
|
+
export { alertPlugin, availablePlugins, getAvailablePlugin, jsExecutorPlugin, migrateFunPlugin, piiProtectionPlugin, pluginBuilderPlugin, predictionMarketPlugin, screenshotPlugin, userQuestionPlugin };
|
|
1224
1969
|
//# sourceMappingURL=index.js.map
|
|
1225
1970
|
//# sourceMappingURL=index.js.map
|