@shuffleio/shuffle-mcps 0.0.31 → 0.0.32

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/index.js CHANGED
@@ -422,7 +422,9 @@ __export(index_exports, {
422
422
  deleteDatastoreItem: () => deleteDatastoreItem,
423
423
  deleteDatastoreItems: () => deleteDatastoreItems,
424
424
  diagnoseOutputWarning: () => diagnoseOutputWarning,
425
+ extractActionAppNames: () => extractActionAppNames,
425
426
  extractValidatedIngestionApps: () => extractValidatedIngestionApps,
427
+ extractWorkflowActionAppNames: () => extractWorkflowActionAppNames,
426
428
  extractWorkflowAppNames: () => extractWorkflowAppNames,
427
429
  fetchApps: () => fetchApps,
428
430
  fetchAppsViaApiConfig: () => fetchAppsViaApiConfig,
@@ -473,13 +475,13 @@ __export(index_exports, {
473
475
  useSyncHostBaseUrl: () => useSyncHostBaseUrl
474
476
  });
475
477
  module.exports = __toCommonJS(index_exports);
476
- var import_shuffle_mcp3 = require("./shuffle-mcp-ER33P3W2.css");
478
+ var import_shuffle_mcp3 = require("./shuffle-mcp-TWJGPBOQ.css");
477
479
  var import_react22 = __toESM(require("react"));
478
480
 
479
481
  // ShuffleMcpThemeProvider.tsx
480
482
  var import_react = __toESM(require("react"));
481
483
  var import_material = require("@mui/material");
482
- var import_shuffle_mcp = require("./shuffle-mcp-ER33P3W2.css");
484
+ var import_shuffle_mcp = require("./shuffle-mcp-TWJGPBOQ.css");
483
485
  var import_jsx_runtime = require("react/jsx-runtime");
484
486
  var readHtmlDarkClass = () => {
485
487
  if (typeof document === "undefined") return false;
@@ -1319,6 +1321,65 @@ var isIngestionApp = (appName) => {
1319
1321
  return isEmailApp(appName) || CASES_PATTERNS.some((p) => name.includes(p)) || EDR_PATTERNS.some((p) => name.includes(p)) || SIEM_PATTERNS.some((p) => name.includes(p));
1320
1322
  };
1321
1323
  var normalizeAppName = (name) => name.toLowerCase().trim().replace(/[\s_\-]+/g, "_");
1324
+ var IGNORED_WORKFLOW_APP_NAMES = /* @__PURE__ */ new Set([
1325
+ "shuffle_tools",
1326
+ "singul",
1327
+ "integration",
1328
+ "ai_agent",
1329
+ "shuffle_agent",
1330
+ "shuffle_ai"
1331
+ ]);
1332
+ var isIgnoredWorkflowAppName = (name) => IGNORED_WORKFLOW_APP_NAMES.has(normalizeAppName(name));
1333
+ var looksLikeOpaqueId = (raw) => {
1334
+ const trimmed = raw.trim();
1335
+ if (/^[a-f0-9]{16,}$/i.test(trimmed)) return true;
1336
+ if (/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(trimmed)) return true;
1337
+ return false;
1338
+ };
1339
+ var addWorkflowAppName = (names, seen, raw) => {
1340
+ if (typeof raw !== "string" || !raw.trim()) return;
1341
+ const parts = raw.includes(",") ? raw.split(",") : [raw];
1342
+ for (const part of parts) {
1343
+ const trimmed = part.trim();
1344
+ if (!trimmed) continue;
1345
+ const key = normalizeAppName(trimmed);
1346
+ if (!key || isIgnoredWorkflowAppName(trimmed) || looksLikeOpaqueId(trimmed) || seen.has(key)) continue;
1347
+ seen.add(key);
1348
+ names.push(trimmed);
1349
+ }
1350
+ };
1351
+ var collectParameterAppNames = (input, names, seen, depth = 0) => {
1352
+ if (!input || depth > 4) return;
1353
+ if (Array.isArray(input)) {
1354
+ input.forEach((item) => collectParameterAppNames(item, names, seen, depth + 1));
1355
+ return;
1356
+ }
1357
+ if (typeof input !== "object") return;
1358
+ const obj = input;
1359
+ if (String(obj.name || "").toLowerCase() === "app_name") addWorkflowAppName(names, seen, obj.value);
1360
+ for (const [key, value] of Object.entries(obj)) {
1361
+ const lower = key.toLowerCase();
1362
+ if (lower === "app_name") addWorkflowAppName(names, seen, value);
1363
+ else if (lower === "parameters" || lower === "fields" || lower === "value") collectParameterAppNames(value, names, seen, depth + 1);
1364
+ }
1365
+ };
1366
+ function extractActionAppNames(action) {
1367
+ const names = [];
1368
+ const seen = /* @__PURE__ */ new Set();
1369
+ collectParameterAppNames(action?.parameters, names, seen);
1370
+ addWorkflowAppName(names, seen, action?.app_name);
1371
+ addWorkflowAppName(names, seen, action?.app_id);
1372
+ return names;
1373
+ }
1374
+ function extractWorkflowActionAppNames(workflow) {
1375
+ const names = [];
1376
+ const seen = /* @__PURE__ */ new Set();
1377
+ if (!workflow?.actions || !Array.isArray(workflow.actions)) return names;
1378
+ for (const action of workflow.actions) {
1379
+ for (const name of extractActionAppNames(action)) addWorkflowAppName(names, seen, name);
1380
+ }
1381
+ return names;
1382
+ }
1322
1383
  var getIngestionCategory = (appName, appCategories) => {
1323
1384
  const name = appName.toLowerCase();
1324
1385
  const categories = (appCategories || []).map((c) => c.toLowerCase());
@@ -1332,20 +1393,14 @@ var INGEST_TICKETS_WORKFLOW_NAME = "Ingest Tickets";
1332
1393
  var FORWARD_TICKETS_WORKFLOW_NAME = "Forward Tickets";
1333
1394
  function extractWorkflowAppNames(workflow) {
1334
1395
  const names = /* @__PURE__ */ new Set();
1335
- if (!workflow?.actions || !Array.isArray(workflow.actions)) return names;
1336
- for (const action of workflow.actions) {
1337
- if (action.app_name) {
1338
- names.add(normalizeAppName(action.app_name));
1339
- }
1340
- if (action.app_id) {
1341
- names.add(normalizeAppName(action.app_id));
1342
- }
1343
- if (action.parameters) {
1344
- for (const param of action.parameters) {
1345
- if (param.name === "app_name" && param.value) {
1346
- names.add(normalizeAppName(param.value));
1347
- }
1348
- }
1396
+ for (const raw of extractWorkflowActionAppNames(workflow)) {
1397
+ const parts = typeof raw === "string" && raw.includes(",") ? raw.split(",") : [raw];
1398
+ for (const part of parts) {
1399
+ const trimmed = (part || "").trim();
1400
+ if (!trimmed) continue;
1401
+ if (isIgnoredWorkflowAppName(trimmed) || looksLikeOpaqueId(trimmed)) continue;
1402
+ const key = normalizeAppName(trimmed);
1403
+ if (key) names.add(key);
1349
1404
  }
1350
1405
  }
1351
1406
  return names;
@@ -2060,6 +2115,7 @@ function AppTitleHeader({
2060
2115
  isActivated = null,
2061
2116
  activateLoading = false,
2062
2117
  onActivateToggle,
2118
+ highlightActivate = false,
2063
2119
  onAdd
2064
2120
  }) {
2065
2121
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_framer_motion.motion.div, { initial: { opacity: 0, y: 12 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.3 }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
@@ -2163,7 +2219,16 @@ function AppTitleHeader({
2163
2219
  py: 0.5,
2164
2220
  minHeight: 0,
2165
2221
  flexShrink: 0,
2166
- ...isActivated ? { color: "hsl(var(--muted-foreground))", borderColor: "hsl(var(--border))", "&:hover": { borderColor: "hsl(var(--destructive))", color: "hsl(var(--destructive))", bgcolor: "hsla(var(--destructive) / 0.08)" } } : { bgcolor: "hsl(var(--primary))", "&:hover": { bgcolor: "hsl(var(--primary) / 0.85)" } }
2222
+ ...isActivated ? { color: "hsl(var(--muted-foreground))", borderColor: "hsl(var(--border))", "&:hover": { borderColor: "hsl(var(--destructive))", color: "hsl(var(--destructive))", bgcolor: "hsla(var(--destructive) / 0.08)" } } : { bgcolor: "hsl(var(--primary))", "&:hover": { bgcolor: "hsl(var(--primary) / 0.85)" } },
2223
+ ...highlightActivate ? {
2224
+ boxShadow: "0 0 0 0 hsla(var(--primary) / 0.6)",
2225
+ animation: "appTitleActivatePulse 1.1s ease-out infinite",
2226
+ "@keyframes appTitleActivatePulse": {
2227
+ "0%": { boxShadow: "0 0 0 0 hsla(var(--primary) / 0.55)" },
2228
+ "70%": { boxShadow: "0 0 0 10px hsla(var(--primary) / 0)" },
2229
+ "100%": { boxShadow: "0 0 0 0 hsla(var(--primary) / 0)" }
2230
+ }
2231
+ } : {}
2167
2232
  },
2168
2233
  children: activateLoading ? "\u2026" : isActivated ? "Deactivate" : "Activate"
2169
2234
  }
@@ -5706,6 +5771,7 @@ function AppDetailDrawer({
5706
5771
  onAddToCanvas,
5707
5772
  isAuthenticated = true,
5708
5773
  activeOrgId,
5774
+ autoActivate = false,
5709
5775
  globalUrl,
5710
5776
  userdata,
5711
5777
  isLoaded,
@@ -5903,8 +5969,9 @@ function AppDetailDrawer({
5903
5969
  (0, import_react11.useEffect)(() => {
5904
5970
  lastValidAuthRef.current = null;
5905
5971
  }, [appName]);
5906
- const handleActivateToggle = async () => {
5972
+ const handleActivateToggle = async (opts) => {
5907
5973
  if (!appName || activateLoading) return;
5974
+ const silent = !!opts?.silent;
5908
5975
  const wasActivated = isActivated;
5909
5976
  const prevAppId = activatedAppId;
5910
5977
  setIsActivated(!wasActivated);
@@ -5918,7 +5985,7 @@ function AppDetailDrawer({
5918
5985
  });
5919
5986
  if (!res.ok) throw new Error("Deactivate failed");
5920
5987
  setActivatedAppId(null);
5921
- toast.success(`${displayName} deactivated`);
5988
+ if (!silent) toast.success(`${displayName} deactivated`);
5922
5989
  } else {
5923
5990
  const appId2 = resolvedAlgoliaId;
5924
5991
  if (!appId2) throw new Error("App ID not resolved yet");
@@ -5929,17 +5996,38 @@ function AppDetailDrawer({
5929
5996
  });
5930
5997
  if (!activateRes.ok) throw new Error(`Activate failed (${activateRes.status})`);
5931
5998
  setActivatedAppId(appId2);
5932
- toast.success(`${displayName} activated`);
5999
+ if (!silent) toast.success(`${displayName} activated`);
5933
6000
  }
5934
6001
  } catch (err) {
5935
6002
  console.error("[Activate] Error:", err);
5936
- toast.error(err?.message || "Activation failed");
6003
+ if (!silent) toast.error(err?.message || "Activation failed");
5937
6004
  setIsActivated(wasActivated);
5938
6005
  setActivatedAppId(prevAppId);
5939
6006
  } finally {
5940
6007
  setActivateLoading(false);
5941
6008
  }
5942
6009
  };
6010
+ const autoActivateFiredRef = (0, import_react11.useRef)(null);
6011
+ const [autoActivatePulse, setAutoActivatePulse] = (0, import_react11.useState)(false);
6012
+ (0, import_react11.useEffect)(() => {
6013
+ if (!open || !autoActivate || !appName) return;
6014
+ const key = `${appName}`;
6015
+ if (autoActivateFiredRef.current === key) return;
6016
+ if (isActivated !== false) return;
6017
+ if (!resolvedAlgoliaId) return;
6018
+ if (activateLoading) return;
6019
+ autoActivateFiredRef.current = key;
6020
+ setAutoActivatePulse(true);
6021
+ handleActivateToggle({ silent: true }).finally(() => {
6022
+ setTimeout(() => setAutoActivatePulse(false), 1200);
6023
+ });
6024
+ }, [open, autoActivate, appName, isActivated, resolvedAlgoliaId, activateLoading]);
6025
+ (0, import_react11.useEffect)(() => {
6026
+ if (!open) {
6027
+ autoActivateFiredRef.current = null;
6028
+ setAutoActivatePulse(false);
6029
+ }
6030
+ }, [open, appName]);
5943
6031
  const isLoadingAll = appLoading || isAuthenticated && appAuthLoading;
5944
6032
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
5945
6033
  import_material9.Drawer,
@@ -6015,7 +6103,8 @@ function AppDetailDrawer({
6015
6103
  categories: appInfo?.categories,
6016
6104
  isActivated: onAddToCanvas ? null : effectiveActivated,
6017
6105
  activateLoading,
6018
- onActivateToggle: handleActivateToggle,
6106
+ onActivateToggle: () => handleActivateToggle(),
6107
+ highlightActivate: autoActivatePulse,
6019
6108
  onAdd: onAddToCanvas && appName ? () => {
6020
6109
  onAddToCanvas({ name: appName, icon: resolvedImage || "", algoliaId: resolvedAlgoliaId });
6021
6110
  onClose();
@@ -6119,7 +6208,7 @@ function AppDetailDrawer({
6119
6208
  }
6120
6209
 
6121
6210
  // views/ShuffleMCP.tsx
6122
- var import_shuffle_mcp2 = require("./shuffle-mcp-ER33P3W2.css");
6211
+ var import_shuffle_mcp2 = require("./shuffle-mcp-TWJGPBOQ.css");
6123
6212
  init_appsCache();
6124
6213
 
6125
6214
  // components/SegmentedControl.tsx
@@ -6287,7 +6376,7 @@ function SegmentedControl({
6287
6376
  children: opt.label
6288
6377
  }
6289
6378
  ),
6290
- !isAction && typeof opt.count === "number" && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
6379
+ !isAction && (typeof opt.count === "number" || typeof opt.count === "string") && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
6291
6380
  "span",
6292
6381
  {
6293
6382
  style: {
@@ -6786,8 +6875,8 @@ var ShuffleMCP = import_react12.default.forwardRef(({
6786
6875
  value: sourceFilter,
6787
6876
  onChange: (v) => setSourceFilter(v),
6788
6877
  options: [
6789
- { value: "all", label: "All", count: results.length + filteredPrivateApps.length, title: "All available apps \u2014 both the public catalog and your private apps." },
6790
- { value: "public", label: "Public", count: results.length, title: "Public apps from the Shuffle catalog (powered by Algolia)." },
6878
+ { value: "all", label: "All", count: results.length >= hitsPerPage ? `${results.length + filteredPrivateApps.length}+` : results.length + filteredPrivateApps.length, title: "All available apps \u2014 both the public catalog and your private apps." },
6879
+ { value: "public", label: "Public", count: results.length >= hitsPerPage ? `${results.length}+` : results.length, title: "Public apps from the Shuffle catalog (powered by Algolia)." },
6791
6880
  { value: "private", label: "Private", count: filteredPrivateApps.length, title: "Private apps are apps you have activated in your organization, or your own custom apps \u2014 not just from the public Algolia catalog." },
6792
6881
  { value: "authenticated", label: "Authenticated", count: [...filteredPrivateApps, ...results].filter((a, i, arr) => arr.findIndex((x) => x.name?.toLowerCase() === a.name?.toLowerCase()) === i).filter(isAppConfigured).length, title: "Apps you have authenticated and that are ready to use." }
6793
6882
  ]
@@ -6963,9 +7052,11 @@ function AppSearchDrawer({
6963
7052
  pinnedApps,
6964
7053
  highlightAppName,
6965
7054
  highlightDelayMs = 5e3,
7055
+ realtimeHighlightAppNames,
6966
7056
  multiSelect = false,
6967
7057
  selectedApps,
6968
7058
  onSelectionChange,
7059
+ autoActivate = false,
6969
7060
  globalUrl,
6970
7061
  theme,
6971
7062
  colorMode
@@ -7224,6 +7315,41 @@ function AppSearchDrawer({
7224
7315
  "0%, 100%": { boxShadow: "0 0 0 2px hsl(var(--primary)), 0 0 12px 1px hsl(var(--primary) / 0.4)" },
7225
7316
  "50%": { boxShadow: "0 0 0 2px hsl(var(--primary)), 0 0 24px 4px hsl(var(--primary) / 0.75)" }
7226
7317
  }
7318
+ } : {},
7319
+ ...realtimeHighlightAppNames && realtimeHighlightAppNames.length > 0 ? {
7320
+ [realtimeHighlightAppNames.map((n) => `& .singul-dropdown-item[data-app-name="${n}"]`).join(", ")]: {
7321
+ position: "relative",
7322
+ zIndex: 2,
7323
+ borderColor: "hsl(var(--primary) / 0.78) !important",
7324
+ backgroundColor: "hsl(var(--primary) / 0.08) !important",
7325
+ outline: "none !important",
7326
+ boxShadow: "none !important",
7327
+ animation: "shuffleHighlightPulseStrong 1.5s ease-in-out infinite",
7328
+ transition: "border-color 120ms ease, background-color 120ms ease",
7329
+ "&::before": {
7330
+ content: '""',
7331
+ position: "absolute",
7332
+ left: "10px",
7333
+ top: "14px",
7334
+ bottom: "14px",
7335
+ width: "3px",
7336
+ borderRadius: "999px",
7337
+ backgroundColor: "hsl(var(--primary))"
7338
+ },
7339
+ "& .singul-app-info": {
7340
+ paddingLeft: "8px"
7341
+ }
7342
+ },
7343
+ "@keyframes shuffleHighlightPulseStrong": {
7344
+ "0%, 100%": {
7345
+ borderColor: "hsl(var(--primary) / 0.62)",
7346
+ backgroundColor: "hsl(var(--primary) / 0.06)"
7347
+ },
7348
+ "50%": {
7349
+ borderColor: "hsl(var(--primary) / 0.92)",
7350
+ backgroundColor: "hsl(var(--primary) / 0.12)"
7351
+ }
7352
+ }
7227
7353
  } : {}
7228
7354
  },
7229
7355
  children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_framer_motion8.AnimatePresence, { mode: "wait", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
@@ -7246,8 +7372,9 @@ function AppSearchDrawer({
7246
7372
  layout: "grid",
7247
7373
  gridColumns: 2,
7248
7374
  inline: true,
7375
+ initialQuery,
7249
7376
  initialFilterQuery: initialQuery,
7250
- hitsPerPage: 12,
7377
+ hitsPerPage: 20,
7251
7378
  showDescription: false,
7252
7379
  showCategories: true,
7253
7380
  showCheckbox: multiSelect,
@@ -7338,7 +7465,8 @@ function AppSearchDrawer({
7338
7465
  theme,
7339
7466
  colorMode,
7340
7467
  onRefresh: onClose,
7341
- onAddToCanvas
7468
+ onAddToCanvas,
7469
+ autoActivate
7342
7470
  }
7343
7471
  )
7344
7472
  ] });
@@ -7605,11 +7733,173 @@ function ShufflePipelinesBanner() {
7605
7733
  var ShufflePipelinesBanner_default = ShufflePipelinesBanner;
7606
7734
 
7607
7735
  // components/AgentUI.tsx
7608
- var import_react15 = require("react");
7736
+ var import_react15 = __toESM(require("react"));
7609
7737
  var import_lucide_react11 = require("lucide-react");
7610
7738
  var import_react_router_dom2 = require("react-router-dom");
7611
7739
  var import_material16 = require("@mui/material");
7612
7740
  var import_react_markdown4 = __toESM(require("react-markdown"));
7741
+
7742
+ // components/agentPromptSuggestions.ts
7743
+ var AGENT_PROMPT_SUGGESTIONS = [
7744
+ // — Email & comms ————————————————————————————————————————————————
7745
+ "Take care of my emails by pretending to be me and answering them",
7746
+ "Triage my inbox every hour and draft replies for anything urgent",
7747
+ "Summarise my unread emails from the last 24 hours and rank them by importance",
7748
+ "Forward any phishing-looking emails in my inbox to the security team and quarantine them",
7749
+ "Reply to scheduling requests in my inbox using my working hours and calendar availability",
7750
+ "Every morning, send me a digest of overnight emails grouped by sender and topic",
7751
+ "Auto-unsubscribe from marketing emails I have not opened in 60 days",
7752
+ "Snooze low-priority newsletters until Friday afternoon",
7753
+ "Flag and archive shipping notifications once the package is delivered",
7754
+ "Clean up my inbox by archiving anything older than 30 days that I have already read",
7755
+ // — Calendar & meetings ————————————————————————————————————————————
7756
+ "Block focus time on my calendar every weekday morning between 9 and 11",
7757
+ "Decline meetings that conflict with my focus blocks and propose alternatives",
7758
+ "Send me a daily agenda every weekday at 8am with prep notes per meeting",
7759
+ "Reschedule any meeting without an agenda 24 hours before it starts",
7760
+ "Send meeting notes and action items to all attendees after every call",
7761
+ "Find a 30-minute slot this week that works for me and the people I mention",
7762
+ "Cancel recurring meetings that I have skipped 3 times in a row",
7763
+ "Remind me 15 minutes before any meeting that I have not prepared for",
7764
+ // — Incidents & SOC operations ———————————————————————————————————
7765
+ "Triage new incidents every 15 minutes and assign severity, owner, and next steps",
7766
+ "Watch for new critical incidents and page the on-call engineer immediately",
7767
+ "Close incidents older than 30 days that have had no activity and no observables",
7768
+ "Merge duplicate incidents that share the same observable or hostname",
7769
+ "Enrich every new incident with threat intel and asset context within 2 minutes",
7770
+ "Auto-acknowledge incidents from known maintenance windows and link them to the change ticket",
7771
+ "Summarise yesterday's incidents into a daily standup brief for the SOC team",
7772
+ "Escalate any incident that breaches its SLA to the team lead",
7773
+ "Tag every incident with the MITRE ATT&CK technique that best matches its observables",
7774
+ "Watch the alert queue and group related alerts into a single incident",
7775
+ "Auto-resolve incidents whose root cause matches a previously closed incident",
7776
+ "Reopen any closed incident if the same observable shows up again within 7 days",
7777
+ "Send a weekly incident trend report every Monday morning to the security leadership",
7778
+ // — Threat intel & IOCs —————————————————————————————————————————
7779
+ "Pull new IOCs from my threat feeds every hour and push them to the firewall blocklist",
7780
+ "Check every external IP in today's incidents against VirusTotal and AbuseIPDB",
7781
+ "Sweep the environment for any host that has connected to a known bad domain in the last 7 days",
7782
+ "Subscribe to new CISA advisories and turn them into detection rules where possible",
7783
+ "Cross-reference observed file hashes against MalwareBazaar daily",
7784
+ "Watch for newly registered domains that look like our brand and report them",
7785
+ "Alert me whenever a new CVE with a known exploit affects software in our inventory",
7786
+ "Refresh the IOC datastore every 6 hours and prune entries older than 30 days",
7787
+ // — Detection engineering ———————————————————————————————————————
7788
+ "Review noisy detection rules every week and propose tuning suggestions",
7789
+ "Convert the latest Sigma rules from the public repo into our SIEM's format and stage them for review",
7790
+ "Tell me which detection rules have not fired in the last 90 days so I can retire them",
7791
+ "Audit MITRE ATT&CK coverage every Monday and flag techniques with zero detections",
7792
+ "Backtest new detection rules against the last 30 days of logs before promoting them to production",
7793
+ // — SIEM operations ———————————————————————————————————————————
7794
+ "Triage new Splunk alerts every 10 minutes and assign severity, owner, and next steps",
7795
+ "Watch Microsoft Sentinel for high-severity incidents and page the on-call analyst immediately",
7796
+ "Suppress noisy SIEM alerts that fire more than 50 times per hour and open a tuning ticket",
7797
+ "Correlate failed login spikes in the SIEM with VPN logs and flag possible brute-force attempts",
7798
+ "Hunt the SIEM every morning for sign-ins from countries we do not operate in",
7799
+ "Forward critical SIEM detections straight to Case Management and skip the alert queue",
7800
+ "Run a weekly SIEM data-source health check and alert me on any log source silent for over 1 hour",
7801
+ "Enrich every SIEM alert with asset owner, criticality, and recent vulnerabilities before triage",
7802
+ "Search the SIEM for the latest IOCs from threat intel feeds every 30 minutes",
7803
+ "Summarise top 10 noisiest SIEM rules every Friday and propose tuning candidates",
7804
+ "Detect lateral movement patterns in SIEM auth logs and open an incident with the affected hosts",
7805
+ "Watch the SIEM for any disable or delete of audit logging and treat it as a critical incident",
7806
+ // — EDR operations ————————————————————————————————————————————
7807
+ "Isolate any endpoint that triggers a critical CrowdStrike alert and notify the user's manager",
7808
+ "Kill any process on a flagged host that matches a known malware family in EDR telemetry",
7809
+ "Watch SentinelOne for ransomware behavior detections and auto-contain the host within 60 seconds",
7810
+ "Pull the process tree from EDR for every malware detection and attach it to the incident",
7811
+ "Sweep all endpoints for a given hash or filename when a new IOC arrives and report matches",
7812
+ "Detect newly created local admin accounts on any endpoint and require approval before keeping them",
7813
+ "Alert when an EDR agent goes offline for more than 1 hour and open a ticket against the asset owner",
7814
+ "Quarantine any binary flagged as suspicious by EDR and email the user with next steps",
7815
+ "Hunt EDR telemetry every night for LOLBins executed from user-writable directories",
7816
+ "Correlate EDR alerts with SIEM auth events to detect credential theft followed by execution",
7817
+ "Run a weekly EDR coverage report and list endpoints without an active agent",
7818
+ "Auto-resolve EDR alerts that match a known-good signed binary on our allowlist",
7819
+ // — Vulnerability management ————————————————————————————————————
7820
+ "Scan our asset inventory daily and open tickets for any new critical or high vulnerability",
7821
+ "Remind asset owners every Friday about open vulnerabilities older than 14 days",
7822
+ "Re-scan hosts that recently patched a critical vulnerability and close the ticket if clean",
7823
+ "Send the patch compliance report every Monday morning to IT and security leadership",
7824
+ "Correlate vulnerabilities with active exploits in the wild and re-prioritise the queue",
7825
+ // — Endpoint & host monitoring ———————————————————————————————————
7826
+ "Watch my host monitors and alert me if any agent goes offline for more than 1 hour",
7827
+ "Isolate any endpoint that triggers a critical EDR alert and notify the user's manager",
7828
+ "Kill any process on a flagged host that matches a known malware family",
7829
+ "Run an inventory of installed software on every endpoint every Sunday",
7830
+ "Detect new local admin accounts created on any endpoint and require approval",
7831
+ "Make sure full-disk encryption is on for every laptop and report exceptions weekly",
7832
+ // — Identity & access —————————————————————————————————————————
7833
+ "Review user access every quarter and flag anyone with privileges they have not used in 90 days",
7834
+ "Disable accounts that have not logged in for 60 days and notify their manager",
7835
+ "Watch for impossible travel sign-ins and force a password reset plus MFA re-enrolment",
7836
+ "Alert me whenever someone is added to an admin group and require justification within 24 hours",
7837
+ "Audit MFA enrolment weekly and email any user without a second factor",
7838
+ "Rotate service account credentials every 90 days and update the vault",
7839
+ "Detect dormant API keys older than 180 days and revoke them after notice",
7840
+ "Make sure new joiners get baseline access provisioned within 1 hour of their start date",
7841
+ "Make sure leavers have all access revoked within 15 minutes of their termination event",
7842
+ // — Cloud, infra & posture ————————————————————————————————————
7843
+ "Check our cloud accounts every hour for public S3 buckets and lock them down",
7844
+ "Detect new IAM roles with wildcard permissions and require review before they go live",
7845
+ "Watch for unencrypted databases in production and open a ticket against the owner",
7846
+ "Run a CIS benchmark scan against all production cloud accounts every Sunday",
7847
+ "Alert on any new security group that opens ports to 0.0.0.0/0",
7848
+ "Monitor cloud spend daily and warn me if any service's cost jumps more than 50% week-over-week",
7849
+ "Check certificate expiry across our domains weekly and renew anything within 30 days",
7850
+ "Watch our DNS records for unauthorised changes and revert them",
7851
+ // — Phishing & user reports ———————————————————————————————————
7852
+ "Investigate phishing reports from the abuse mailbox and respond to the reporter within 1 hour",
7853
+ "Detonate suspicious attachments in a sandbox and attach the verdict to the incident",
7854
+ "Pull URLs from reported phishing emails and submit them to URLhaus and PhishTank",
7855
+ "Send a phishing simulation to a sample of users every month and report results",
7856
+ // — Compliance, audit & reporting —————————————————————————————
7857
+ "Generate the SOC2 evidence pack at the start of every month",
7858
+ "Track changes to security policies and alert the GRC team when one is edited",
7859
+ "Send a weekly KPI report covering MTTD, MTTR, and open incident backlog",
7860
+ "Audit log retention every Sunday and confirm we still meet the 1-year requirement",
7861
+ "Generate a monthly executive summary of incidents, vulnerabilities, and detection coverage",
7862
+ // — Backups, DR & uptime ————————————————————————————————————
7863
+ "Verify nightly backups completed successfully and alert me if any failed",
7864
+ "Test restore from backup on a sample workload every month and report results",
7865
+ "Watch our public status page for incidents at our critical vendors and notify the team",
7866
+ "Ping our public endpoints every minute and open an incident if any return non-2xx for 3 minutes",
7867
+ // — Tickets, workflow & docs ——————————————————————————————————
7868
+ "Triage new Jira tickets in the security project every hour and assign by category",
7869
+ "Close tickets that have been waiting on the reporter for more than 14 days",
7870
+ "Summarise this week's closed tickets into release notes every Friday",
7871
+ "Pull action items out of meeting notes I share and create tickets for them",
7872
+ "Keep the on-call runbook in sync with the latest incident postmortems",
7873
+ "Review pull requests in our security repos and leave a summary comment within an hour",
7874
+ // — People, HR & onboarding ————————————————————————————————————
7875
+ "Send a welcome message and security training link to every new joiner on their start date",
7876
+ "Run the offboarding checklist whenever someone is marked as a leaver in HR",
7877
+ "Remind managers to complete quarterly performance reviews 1 week before the deadline",
7878
+ "Send birthday and work anniversary greetings from me on the right day",
7879
+ // — Personal / lightweight ops chores —————————————————————————
7880
+ "Order more office coffee whenever the inventory drops below 2 bags",
7881
+ "File my receipts every Friday and submit the expense report",
7882
+ "Track my open invoices and chase clients whose payment is more than 14 days overdue",
7883
+ "Send a polite follow-up to anyone I have emailed but who has not replied in 5 working days",
7884
+ "Read the security news feeds every morning and summarise the top 5 stories for me"
7885
+ ];
7886
+ var matchAgentPromptSuggestions = (input, limit = 8) => {
7887
+ const q = (input || "").trim().toLowerCase();
7888
+ if (!q) return [];
7889
+ const scored = [];
7890
+ for (const text of AGENT_PROMPT_SUGGESTIONS) {
7891
+ const lower = text.toLowerCase();
7892
+ const idx = lower.indexOf(q);
7893
+ if (idx < 0) continue;
7894
+ const score = idx === 0 ? -1e3 + text.length : idx * 10 + text.length;
7895
+ scored.push({ text, score });
7896
+ }
7897
+ scored.sort((a, b) => a.score - b.score);
7898
+ return scored.slice(0, limit).map((s) => s.text);
7899
+ };
7900
+ var DEFAULT_AGENT_PROMPT_PLACEHOLDER = "Take care of my emails by pretending to be me and answering them";
7901
+
7902
+ // components/AgentUI.tsx
7613
7903
  var import_remark_gfm4 = __toESM(require("remark-gfm"));
7614
7904
  var import_remark_breaks = __toESM(require("remark-breaks"));
7615
7905
  var import_react18_json_view2 = __toESM(require("react18-json-view"));
@@ -9547,7 +9837,7 @@ var AgentUI = ({
9547
9837
  autoLoadApps = true,
9548
9838
  title = "What do you want to do?",
9549
9839
  subtitle,
9550
- placeholder = 'Describe a task, e.g. "Get my emails for today and summarise them"',
9840
+ placeholder = DEFAULT_AGENT_PROMPT_PLACEHOLDER,
9551
9841
  defaultInput = "",
9552
9842
  autoSubmit = false,
9553
9843
  hideHeroIcon = false,
@@ -9599,6 +9889,29 @@ var AgentUI = ({
9599
9889
  const hasApiKey = !!apiKey || !!API_CONFIG.apiKey;
9600
9890
  const navigate = (0, import_react_router_dom2.useNavigate)();
9601
9891
  const [actionInput, setActionInput] = (0, import_react15.useState)(defaultInput);
9892
+ const promptAnchorRef = import_react15.default.useRef(null);
9893
+ const [suggestionsDismissed, setSuggestionsDismissed] = (0, import_react15.useState)(false);
9894
+ const [suggestionIndex, setSuggestionIndex] = (0, import_react15.useState)(-1);
9895
+ const promptSuggestions = (0, import_react15.useMemo)(
9896
+ () => matchAgentPromptSuggestions(actionInput, 8),
9897
+ [actionInput]
9898
+ );
9899
+ (0, import_react15.useEffect)(() => {
9900
+ setSuggestionIndex(-1);
9901
+ setSuggestionsDismissed(false);
9902
+ }, [actionInput]);
9903
+ const suggestionsOpen = promptSuggestions.length > 0 && !suggestionsDismissed;
9904
+ const acceptSuggestion = (0, import_react15.useCallback)((s) => {
9905
+ setActionInput(s);
9906
+ setSuggestionsDismissed(true);
9907
+ setSuggestionIndex(-1);
9908
+ requestAnimationFrame(() => {
9909
+ try {
9910
+ inputRef.current?.focus();
9911
+ } catch {
9912
+ }
9913
+ });
9914
+ }, []);
9602
9915
  const BUILTIN_DEFAULT_APPS = [
9603
9916
  { name: "http" },
9604
9917
  { name: "shuffle_tools" }
@@ -10592,6 +10905,34 @@ var AgentUI = ({
10592
10905
  }
10593
10906
  }, [submitOverride, actionInput, chosenApps, submitInput]);
10594
10907
  const onKeyDown = (e) => {
10908
+ if (suggestionsOpen && promptSuggestions.length > 0) {
10909
+ if (e.key === "ArrowDown") {
10910
+ e.preventDefault();
10911
+ setSuggestionIndex((i) => (i + 1) % promptSuggestions.length);
10912
+ return;
10913
+ }
10914
+ if (e.key === "ArrowUp") {
10915
+ e.preventDefault();
10916
+ setSuggestionIndex((i) => i <= 0 ? promptSuggestions.length - 1 : i - 1);
10917
+ return;
10918
+ }
10919
+ if (e.key === "Escape") {
10920
+ e.preventDefault();
10921
+ setSuggestionsDismissed(true);
10922
+ setSuggestionIndex(-1);
10923
+ return;
10924
+ }
10925
+ if (e.key === "Tab" && suggestionIndex >= 0) {
10926
+ e.preventDefault();
10927
+ acceptSuggestion(promptSuggestions[suggestionIndex]);
10928
+ return;
10929
+ }
10930
+ if (e.key === "Enter" && suggestionIndex >= 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey) {
10931
+ e.preventDefault();
10932
+ acceptSuggestion(promptSuggestions[suggestionIndex]);
10933
+ return;
10934
+ }
10935
+ }
10595
10936
  if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
10596
10937
  e.preventDefault();
10597
10938
  handlePrimarySubmit();
@@ -11249,7 +11590,7 @@ var AgentUI = ({
11249
11590
  mt: -1,
11250
11591
  maxWidth: 600
11251
11592
  }, children: subtitle }),
11252
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_material16.Box, { sx: {
11593
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_material16.Box, { ref: promptAnchorRef, sx: {
11253
11594
  width: "100%",
11254
11595
  display: "flex",
11255
11596
  flexDirection: "column",
@@ -11457,6 +11798,67 @@ var AgentUI = ({
11457
11798
  ) }) })
11458
11799
  ] })
11459
11800
  ] }),
11801
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
11802
+ import_material16.Popper,
11803
+ {
11804
+ open: suggestionsOpen,
11805
+ anchorEl: promptAnchorRef.current,
11806
+ placement: "bottom-start",
11807
+ style: { zIndex: 1300, width: promptAnchorRef.current?.offsetWidth },
11808
+ modifiers: [{ name: "offset", options: { offset: [0, 6] } }],
11809
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.ClickAwayListener, { onClickAway: () => setSuggestionsDismissed(true), children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
11810
+ import_material16.Paper,
11811
+ {
11812
+ elevation: 6,
11813
+ sx: {
11814
+ bgcolor: "hsl(var(--card))",
11815
+ border: "1px solid hsl(var(--border))",
11816
+ borderRadius: 2,
11817
+ overflow: "hidden",
11818
+ maxHeight: 320,
11819
+ overflowY: "auto"
11820
+ },
11821
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.MenuList, { dense: true, disablePadding: true, children: promptSuggestions.map((s, i) => {
11822
+ const q = actionInput.trim();
11823
+ const lower = s.toLowerCase();
11824
+ const idx = q ? lower.indexOf(q.toLowerCase()) : -1;
11825
+ const before = idx >= 0 ? s.slice(0, idx) : s;
11826
+ const match = idx >= 0 ? s.slice(idx, idx + q.length) : "";
11827
+ const after = idx >= 0 ? s.slice(idx + q.length) : "";
11828
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
11829
+ import_material16.MenuItem,
11830
+ {
11831
+ selected: i === suggestionIndex,
11832
+ onMouseEnter: () => setSuggestionIndex(i),
11833
+ onMouseDown: (e) => {
11834
+ e.preventDefault();
11835
+ acceptSuggestion(s);
11836
+ },
11837
+ sx: {
11838
+ fontSize: "0.88rem",
11839
+ color: "hsl(var(--foreground))",
11840
+ py: 0.75,
11841
+ px: 2,
11842
+ "&.Mui-selected, &.Mui-selected:hover": {
11843
+ bgcolor: "hsl(var(--muted) / 0.6)"
11844
+ }
11845
+ },
11846
+ children: [
11847
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react11.Search, { size: 14, style: { marginRight: 10, color: "hsl(var(--muted-foreground))", flexShrink: 0 } }),
11848
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_material16.Box, { component: "span", sx: { whiteSpace: "normal", lineHeight: 1.4 }, children: [
11849
+ before,
11850
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.Box, { component: "span", sx: { fontWeight: 700, color: "hsl(var(--foreground))" }, children: match }),
11851
+ after
11852
+ ] })
11853
+ ]
11854
+ },
11855
+ s
11856
+ );
11857
+ }) })
11858
+ }
11859
+ ) })
11860
+ }
11861
+ ),
11460
11862
  !hideAppPicker && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_material16.Box, { sx: { display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_material16.Box, { sx: {
11461
11863
  display: "inline-flex",
11462
11864
  flexWrap: "wrap",
@@ -12290,6 +12692,32 @@ var ENDPOINT_PRESETS = [
12290
12692
  { label: "LM Studio (localhost)", url: "http://localhost:1234/v1", apiKeyHint: "Local LM Studio does not require an API key \u2014 any non-empty value works." },
12291
12693
  { label: CUSTOM_PRESET, url: "" }
12292
12694
  ];
12695
+ var PROVIDER_DOMAINS = {
12696
+ "Shuffle AI": "shuffler.io",
12697
+ OpenAI: "openai.com",
12698
+ Anthropic: "anthropic.com",
12699
+ "Google Gemini": "gemini.google.com",
12700
+ Mistral: "mistral.ai",
12701
+ Groq: "groq.com",
12702
+ DeepSeek: "deepseek.com",
12703
+ "Together AI": "together.ai",
12704
+ OpenRouter: "openrouter.ai",
12705
+ "Ollama (localhost)": "ollama.com",
12706
+ "LM Studio (localhost)": "lmstudio.ai"
12707
+ };
12708
+ var ProviderLogo = ({ label, url }) => {
12709
+ const [errored, setErrored] = (0, import_react16.useState)(false);
12710
+ let domain = PROVIDER_DOMAINS[label];
12711
+ if (!domain && url) {
12712
+ try {
12713
+ domain = new URL(url).hostname.replace(/^www\./, "");
12714
+ } catch {
12715
+ }
12716
+ }
12717
+ const src = domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=64` : "";
12718
+ const initial = label.trim().charAt(0).toUpperCase() || "?";
12719
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_material18.Box, { sx: { width: 18, height: 18, borderRadius: "4px", display: "flex", alignItems: "center", justifyContent: "center", bgcolor: "hsl(var(--muted))", overflow: "hidden", flexShrink: 0 }, children: src && !errored ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("img", { src, alt: "", width: 18, height: 18, style: { display: "block" }, onError: () => setErrored(true) }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_material18.Typography, { sx: { fontSize: "0.65rem", fontWeight: 600, color: "hsl(var(--muted-foreground))", lineHeight: 1 }, children: initial }) });
12720
+ };
12293
12721
  var LocalLLMConfig = ({ compact, globalUrl, userdata, isLoaded, isLoggedIn, serverside, theme, colorMode }) => {
12294
12722
  useSyncHostBaseUrl(globalUrl);
12295
12723
  const { authStates, authenticatedApps, handleAuthChange, handleTestConnection, handleSaveAuth, refreshAuth } = useAppAuth();
@@ -12433,12 +12861,28 @@ var LocalLLMConfig = ({ compact, globalUrl, userdata, isLoaded, isLoggedIn, serv
12433
12861
  isOptionEqualToValue: (opt, val) => opt === val,
12434
12862
  renderOption: (props, option) => {
12435
12863
  const preset = ENDPOINT_PRESETS.find((p) => p.label === option);
12436
- return /* @__PURE__ */ (0, import_react17.createElement)("li", { ...props, key: option }, /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_material18.Box, { sx: { display: "flex", alignItems: "center", gap: 1, width: "100%", minWidth: 0 }, children: [
12864
+ return /* @__PURE__ */ (0, import_react17.createElement)("li", { ...props, key: option }, /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_material18.Box, { sx: { display: "flex", alignItems: "center", gap: 1.25, width: "100%", minWidth: 0 }, children: [
12865
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ProviderLogo, { label: option, url: preset?.url }),
12437
12866
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_material18.Typography, { sx: { fontSize: "0.85rem", color: "hsl(var(--popover-foreground))" }, children: option }),
12438
12867
  preset?.url && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_material18.Typography, { component: "span", sx: { ml: "auto", color: "hsl(var(--muted-foreground))", fontSize: "0.75rem", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: preset.url })
12439
12868
  ] }));
12440
12869
  },
12441
- renderInput: (params) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_material18.TextField, { ...params, placeholder: "Search a provider\u2026" }),
12870
+ renderInput: (params) => {
12871
+ const preset = ENDPOINT_PRESETS.find((p) => p.label === effectivePreset);
12872
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
12873
+ import_material18.TextField,
12874
+ {
12875
+ ...params,
12876
+ placeholder: "Search a provider\u2026",
12877
+ slotProps: {
12878
+ input: {
12879
+ ...params.InputProps,
12880
+ startAdornment: effectivePreset ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_material18.Box, { sx: { display: "flex", alignItems: "center", pl: 0.5, mr: 0.5 }, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ProviderLogo, { label: effectivePreset, url: preset?.url }) }) : void 0
12881
+ }
12882
+ }
12883
+ }
12884
+ );
12885
+ },
12442
12886
  slotProps: {
12443
12887
  paper: { sx: { bgcolor: "hsl(var(--popover))", color: "hsl(var(--popover-foreground))", border: "1px solid hsl(var(--border))" } },
12444
12888
  popper: { sx: { zIndex: 9999 } }
@@ -13401,17 +13845,20 @@ var AgentRunRow = ({ run, onClick, sx, appIcons, onAppClick }) => {
13401
13845
  ] })
13402
13846
  ] })
13403
13847
  ] }),
13404
- tools.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
13848
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
13405
13849
  import_material20.Box,
13406
13850
  {
13407
13851
  onClick: (e) => e.stopPropagation(),
13408
13852
  sx: {
13409
13853
  display: { xs: "none", sm: "flex" },
13410
13854
  alignItems: "center",
13855
+ justifyContent: "flex-end",
13411
13856
  flexShrink: 0,
13412
- ml: 1
13857
+ ml: 1,
13858
+ width: 140,
13859
+ minHeight: 28
13413
13860
  },
13414
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
13861
+ children: tools.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
13415
13862
  import_material20.AvatarGroup,
13416
13863
  {
13417
13864
  max: 5,
@@ -13460,6 +13907,17 @@ var AgentRunRow = ({ run, onClick, sx, appIcons, onAppClick }) => {
13460
13907
  ) }, t.name);
13461
13908
  })
13462
13909
  }
13910
+ ) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
13911
+ import_material20.Typography,
13912
+ {
13913
+ sx: {
13914
+ fontSize: "0.7rem",
13915
+ color: "hsl(var(--muted-foreground))",
13916
+ opacity: 0.5,
13917
+ fontStyle: "italic"
13918
+ },
13919
+ children: "No apps"
13920
+ }
13463
13921
  )
13464
13922
  }
13465
13923
  )
@@ -14677,7 +15135,9 @@ var LocalLLMConfig2 = withMcpTheme(LocalLLMConfig_default, "LocalLLMConfig");
14677
15135
  deleteDatastoreItem,
14678
15136
  deleteDatastoreItems,
14679
15137
  diagnoseOutputWarning,
15138
+ extractActionAppNames,
14680
15139
  extractValidatedIngestionApps,
15140
+ extractWorkflowActionAppNames,
14681
15141
  extractWorkflowAppNames,
14682
15142
  fetchApps,
14683
15143
  fetchAppsViaApiConfig,