diff --git a/packages/ui/package.json b/packages/ui/package.json
index f3ed1bbda..44af1c6a7 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -68,6 +68,7 @@
"rehype-raw": "^7.0.0",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
+ "showdown": "^2.1.0",
"tippy.js": "^6.3.7",
"uuid": "^9.0.1",
"yup": "^0.32.9"
diff --git a/packages/ui/src/ui-component/dialog/AgentflowGeneratorDialog.jsx b/packages/ui/src/ui-component/dialog/AgentflowGeneratorDialog.jsx
index 2819e2e62..425c65aa9 100644
--- a/packages/ui/src/ui-component/dialog/AgentflowGeneratorDialog.jsx
+++ b/packages/ui/src/ui-component/dialog/AgentflowGeneratorDialog.jsx
@@ -15,7 +15,7 @@ import { Dropdown } from '@/ui-component/dropdown/Dropdown'
import { useTheme } from '@mui/material/styles'
import assistantsApi from '@/api/assistants'
import { baseURL } from '@/store/constant'
-import { initNode } from '@/utils/genericHelper'
+import { initNode, showHideInputParams } from '@/utils/genericHelper'
import DocStoreInputHandler from '@/views/docstore/DocStoreInputHandler'
import useApi from '@/hooks/useApi'
@@ -55,6 +55,15 @@ const AgentflowGeneratorDialog = ({ show, dialogProps, onCancel, onConfirm }) =>
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
+ const handleChatModelDataChange = ({ inputParam, newValue }) => {
+ setSelectedChatModel((prevData) => {
+ const updatedData = { ...prevData }
+ updatedData.inputs[inputParam.name] = newValue
+ updatedData.inputParams = showHideInputParams(updatedData)
+ return updatedData
+ })
+ }
+
useEffect(() => {
if (getChatModelsApi.data) {
setChatModelsComponents(getChatModelsApi.data)
@@ -303,10 +312,15 @@ const AgentflowGeneratorDialog = ({ show, dialogProps, onCancel, onConfirm }) =>
borderRadius: 2
}}
>
- {(selectedChatModel.inputParams ?? [])
- .filter((inputParam) => !inputParam.hidden)
+ {showHideInputParams(selectedChatModel)
+ .filter((inputParam) => !inputParam.hidden && inputParam.display !== false)
.map((inputParam, index) => (
-
+
))}
)}
diff --git a/packages/ui/src/ui-component/dialog/NodeInfoDialog.jsx b/packages/ui/src/ui-component/dialog/NodeInfoDialog.jsx
index 58affea28..2b640196d 100644
--- a/packages/ui/src/ui-component/dialog/NodeInfoDialog.jsx
+++ b/packages/ui/src/ui-component/dialog/NodeInfoDialog.jsx
@@ -81,7 +81,11 @@ const NodeInfoDialog = ({ show, dialogProps, onCancel }) => {
height: 50,
marginRight: 10,
borderRadius: '50%',
- backgroundColor: 'white'
+ backgroundColor: 'white',
+ flexShrink: 0,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center'
}}
>
{
width: 50,
height: 50,
borderRadius: '50%',
- backgroundColor: 'white'
+ backgroundColor: 'white',
+ flexShrink: 0,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center'
}}
>
{
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
+ const handleChatModelDataChange = ({ inputParam, newValue }) => {
+ setSelectedChatModel((prevData) => {
+ const updatedData = { ...prevData }
+ updatedData.inputs[inputParam.name] = newValue
+ updatedData.inputParams = showHideInputParams(updatedData)
+ return updatedData
+ })
+ }
+
+ const handleToolDataChange =
+ (toolIndex) =>
+ ({ inputParam, newValue }) => {
+ setSelectedTools((prevTools) => {
+ const updatedTools = [...prevTools]
+ const updatedTool = { ...updatedTools[toolIndex] }
+ updatedTool.inputs[inputParam.name] = newValue
+ updatedTool.inputParams = showHideInputParams(updatedTool)
+ updatedTools[toolIndex] = updatedTool
+ return updatedTools
+ })
+ }
+
const displayWarning = () => {
enqueueSnackbar({
message: 'Please fill in all mandatory fields.',
@@ -1126,13 +1148,14 @@ const CustomAssistantConfigurePreview = () => {
borderRadius: 2
}}
>
- {(selectedChatModel.inputParams ?? [])
- .filter((inputParam) => !inputParam.hidden)
+ {showHideInputParams(selectedChatModel)
+ .filter((inputParam) => !inputParam.hidden && inputParam.display !== false)
.map((inputParam, index) => (
))}
@@ -1217,13 +1240,16 @@ const CustomAssistantConfigurePreview = () => {
mb: 1
}}
>
- {(tool.inputParams ?? [])
- .filter((inputParam) => !inputParam.hidden)
- .map((inputParam, index) => (
+ {showHideInputParams(tool)
+ .filter(
+ (inputParam) => !inputParam.hidden && inputParam.display !== false
+ )
+ .map((inputParam, inputIndex) => (
))}
diff --git a/packages/ui/src/views/assistants/custom/CustomAssistantLayout.jsx b/packages/ui/src/views/assistants/custom/CustomAssistantLayout.jsx
index fcdb4f2b8..6597bfeb6 100644
--- a/packages/ui/src/views/assistants/custom/CustomAssistantLayout.jsx
+++ b/packages/ui/src/views/assistants/custom/CustomAssistantLayout.jsx
@@ -64,7 +64,9 @@ const CustomAssistantLayout = () => {
const getImages = (details) => {
const images = []
if (details && details.chatModel && details.chatModel.name) {
- images.push(`${baseURL}/api/v1/node-icon/${details.chatModel.name}`)
+ images.push({
+ imageSrc: `${baseURL}/api/v1/node-icon/${details.chatModel.name}`
+ })
}
return images
}
diff --git a/packages/ui/src/views/canvas/NodeInputHandler.jsx b/packages/ui/src/views/canvas/NodeInputHandler.jsx
index df22995c6..a3c963d1a 100644
--- a/packages/ui/src/views/canvas/NodeInputHandler.jsx
+++ b/packages/ui/src/views/canvas/NodeInputHandler.jsx
@@ -3,6 +3,7 @@ import { Handle, Position, useUpdateNodeInternals } from 'reactflow'
import { useEffect, useRef, useState, useContext } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { cloneDeep } from 'lodash'
+import showdown from 'showdown'
// material-ui
import { useTheme, styled } from '@mui/material/styles'
@@ -98,6 +99,13 @@ const StyledPopper = styled(Popper)({
}
})
+const markdownConverter = new showdown.Converter({
+ simplifiedAutoLink: true,
+ strikethrough: true,
+ tables: true,
+ tasklists: true
+})
+
// ===========================|| NodeInputHandler ||=========================== //
const NodeInputHandler = ({
@@ -1389,7 +1397,12 @@ const NodeInputHandler = ({
onCancel={() => setPromptGeneratorDialogOpen(false)}
onConfirm={(generatedInstruction) => {
try {
- data.inputs[inputParam.name] = generatedInstruction
+ if (inputParam?.acceptVariable && window.location.href.includes('v2/agentcanvas')) {
+ const htmlContent = markdownConverter.makeHtml(generatedInstruction)
+ data.inputs[inputParam.name] = htmlContent
+ } else {
+ data.inputs[inputParam.name] = generatedInstruction
+ }
setPromptGeneratorDialogOpen(false)
} catch (error) {
enqueueSnackbar({
diff --git a/packages/ui/src/views/credentials/CredentialListDialog.jsx b/packages/ui/src/views/credentials/CredentialListDialog.jsx
index d5d1058d2..edf5d6e23 100644
--- a/packages/ui/src/views/credentials/CredentialListDialog.jsx
+++ b/packages/ui/src/views/credentials/CredentialListDialog.jsx
@@ -140,7 +140,11 @@ const CredentialListDialog = ({ show, dialogProps, onCancel, onCredentialSelecte
width: 50,
height: 50,
borderRadius: '50%',
- backgroundColor: 'white'
+ backgroundColor: 'white',
+ flexShrink: 0,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center'
}}
>
{
+const DocStoreInputHandler = ({ inputParam, data, disabled = false, onNodeDataChange }) => {
const customization = useSelector((state) => state.customization)
+ const flowContextValue = useContext(flowContext)
+ const nodeDataChangeHandler = onNodeDataChange || flowContextValue?.onNodeDataChange
const [showExpandDialog, setShowExpandDialog] = useState(false)
const [expandDialogProps, setExpandDialogProps] = useState({})
@@ -35,6 +38,14 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false }) => {
const [manageScrapedLinksDialogProps, setManageScrapedLinksDialogProps] = useState({})
const [reloadTimestamp, setReloadTimestamp] = useState(Date.now().toString())
+ const handleDataChange = ({ inputParam, newValue }) => {
+ data.inputs[inputParam.name] = newValue
+ const allowedShowHideInputTypes = ['boolean', 'asyncOptions', 'asyncMultiOptions', 'options', 'multiOptions']
+ if (allowedShowHideInputTypes.includes(inputParam.type) && nodeDataChangeHandler) {
+ nodeDataChangeHandler({ nodeId: data.id, inputParam, newValue })
+ }
+ }
+
const onExpandDialogClicked = (value, inputParam) => {
const dialogProps = {
value,
@@ -149,7 +160,7 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false }) => {
{inputParam.type === 'boolean' && (
(data.inputs[inputParam.name] = newValue)}
+ onChange={(newValue) => handleDataChange({ inputParam, newValue })}
value={data.inputs[inputParam.name] ?? inputParam.default ?? false}
/>
)}
@@ -203,7 +214,7 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false }) => {
disabled={disabled}
name={inputParam.name}
options={inputParam.options}
- onSelect={(newValue) => (data.inputs[inputParam.name] = newValue)}
+ onSelect={(newValue) => handleDataChange({ inputParam, newValue })}
value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'}
/>
)}
@@ -213,7 +224,7 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false }) => {
disabled={disabled}
name={inputParam.name}
options={inputParam.options}
- onSelect={(newValue) => (data.inputs[inputParam.name] = newValue)}
+ onSelect={(newValue) => handleDataChange({ inputParam, newValue })}
value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'}
/>
)}
@@ -230,7 +241,7 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false }) => {
freeSolo={inputParam.freeSolo}
multiple={inputParam.type === 'asyncMultiOptions'}
value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'}
- onSelect={(newValue) => (data.inputs[inputParam.name] = newValue)}
+ onSelect={(newValue) => handleDataChange({ inputParam, newValue })}
onCreateNew={() => addAsyncOption(inputParam.name)}
/>
@@ -296,7 +307,8 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false }) => {
DocStoreInputHandler.propTypes = {
inputParam: PropTypes.object,
data: PropTypes.object,
- disabled: PropTypes.bool
+ disabled: PropTypes.bool,
+ onNodeDataChange: PropTypes.func
}
export default DocStoreInputHandler
diff --git a/packages/ui/src/views/docstore/DocumentLoaderListDialog.jsx b/packages/ui/src/views/docstore/DocumentLoaderListDialog.jsx
index a8722ed35..8a1caa38d 100644
--- a/packages/ui/src/views/docstore/DocumentLoaderListDialog.jsx
+++ b/packages/ui/src/views/docstore/DocumentLoaderListDialog.jsx
@@ -153,7 +153,11 @@ const DocumentLoaderListDialog = ({ show, dialogProps, onCancel, onDocLoaderSele
width: 50,
height: 50,
borderRadius: '50%',
- backgroundColor: 'white'
+ backgroundColor: 'white',
+ flexShrink: 0,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center'
}}
>
({
@@ -98,6 +98,24 @@ const LoaderConfigPreviewChunks = () => {
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
+ const handleDocumentLoaderDataChange = ({ inputParam, newValue }) => {
+ setSelectedDocumentLoader((prevData) => {
+ const updatedData = { ...prevData }
+ updatedData.inputs[inputParam.name] = newValue
+ updatedData.inputParams = showHideInputParams(updatedData)
+ return updatedData
+ })
+ }
+
+ const handleTextSplitterDataChange = ({ inputParam, newValue }) => {
+ setSelectedTextSplitter((prevData) => {
+ const updatedData = { ...prevData }
+ updatedData.inputs[inputParam.name] = newValue
+ updatedData.inputParams = showHideInputParams(updatedData)
+ return updatedData
+ })
+ }
+
const onSplitterChange = (name) => {
const textSplitter = (textSplitterNodes ?? []).find((splitter) => splitter.name === name)
if (textSplitter) {
@@ -452,13 +470,14 @@ const LoaderConfigPreviewChunks = () => {
{selectedDocumentLoader &&
Object.keys(selectedDocumentLoader).length > 0 &&
- (selectedDocumentLoader.inputParams ?? [])
- .filter((inputParam) => !inputParam.hidden)
+ showHideInputParams(selectedDocumentLoader)
+ .filter((inputParam) => !inputParam.hidden && inputParam.display !== false)
.map((inputParam, index) => (
))}
{textSplitterNodes && textSplitterNodes.length > 0 && (
@@ -511,10 +530,15 @@ const LoaderConfigPreviewChunks = () => {
>
)}
{Object.keys(selectedTextSplitter).length > 0 &&
- (selectedTextSplitter.inputParams ?? [])
- .filter((inputParam) => !inputParam.hidden)
+ showHideInputParams(selectedTextSplitter)
+ .filter((inputParam) => !inputParam.hidden && inputParam.display !== false)
.map((inputParam, index) => (
-
+
))}
diff --git a/packages/ui/src/views/docstore/VectorStoreConfigure.jsx b/packages/ui/src/views/docstore/VectorStoreConfigure.jsx
index e0cbcc4f7..f05759a8e 100644
--- a/packages/ui/src/views/docstore/VectorStoreConfigure.jsx
+++ b/packages/ui/src/views/docstore/VectorStoreConfigure.jsx
@@ -40,7 +40,7 @@ import Storage from '@mui/icons-material/Storage'
import DynamicFeed from '@mui/icons-material/Filter1'
// utils
-import { initNode } from '@/utils/genericHelper'
+import { initNode, showHideInputParams } from '@/utils/genericHelper'
import useNotifier from '@/utils/useNotifier'
// const
@@ -89,6 +89,33 @@ const VectorStoreConfigure = () => {
const [showUpsertHistoryDetailsDialog, setShowUpsertHistoryDetailsDialog] = useState(false)
const [upsertDetailsDialogProps, setUpsertDetailsDialogProps] = useState({})
+ const handleEmbeddingsProviderDataChange = ({ inputParam, newValue }) => {
+ setSelectedEmbeddingsProvider((prevData) => {
+ const updatedData = { ...prevData }
+ updatedData.inputs[inputParam.name] = newValue
+ updatedData.inputParams = showHideInputParams(updatedData)
+ return updatedData
+ })
+ }
+
+ const handleVectorStoreProviderDataChange = ({ inputParam, newValue }) => {
+ setSelectedVectorStoreProvider((prevData) => {
+ const updatedData = { ...prevData }
+ updatedData.inputs[inputParam.name] = newValue
+ updatedData.inputParams = showHideInputParams(updatedData)
+ return updatedData
+ })
+ }
+
+ const handleRecordManagerProviderDataChange = ({ inputParam, newValue }) => {
+ setSelectedRecordManagerProvider((prevData) => {
+ const updatedData = { ...prevData }
+ updatedData.inputs[inputParam.name] = newValue
+ updatedData.inputParams = showHideInputParams(updatedData)
+ return updatedData
+ })
+ }
+
const onEmbeddingsSelected = (component) => {
const nodeData = cloneDeep(initNode(component, uuidv4()))
if (!showEmbeddingsListDialog && documentStore.embeddingConfig) {
@@ -599,14 +626,17 @@ const VectorStoreConfigure = () => {
{selectedEmbeddingsProvider &&
Object.keys(selectedEmbeddingsProvider).length > 0 &&
- (selectedEmbeddingsProvider.inputParams ?? [])
- .filter((inputParam) => !inputParam.hidden)
+ showHideInputParams(selectedEmbeddingsProvider)
+ .filter(
+ (inputParam) => !inputParam.hidden && inputParam.display !== false
+ )
.map((inputParam, index) => (
))}
@@ -714,14 +744,17 @@ const VectorStoreConfigure = () => {
{selectedVectorStoreProvider &&
Object.keys(selectedVectorStoreProvider).length > 0 &&
- (selectedVectorStoreProvider.inputParams ?? [])
- .filter((inputParam) => !inputParam.hidden)
+ showHideInputParams(selectedVectorStoreProvider)
+ .filter(
+ (inputParam) => !inputParam.hidden && inputParam.display !== false
+ )
.map((inputParam, index) => (
))}
@@ -837,17 +870,18 @@ const VectorStoreConfigure = () => {
{selectedRecordManagerProvider &&
Object.keys(selectedRecordManagerProvider).length > 0 &&
- (selectedRecordManagerProvider.inputParams ?? [])
- .filter((inputParam) => !inputParam.hidden)
+ showHideInputParams(selectedRecordManagerProvider)
+ .filter(
+ (inputParam) => !inputParam.hidden && inputParam.display !== false
+ )
.map((inputParam, index) => (
- <>
-
- >
+
))}
diff --git a/packages/ui/src/views/docstore/VectorStoreQuery.jsx b/packages/ui/src/views/docstore/VectorStoreQuery.jsx
index 6e0fd1b0e..f0650ae41 100644
--- a/packages/ui/src/views/docstore/VectorStoreQuery.jsx
+++ b/packages/ui/src/views/docstore/VectorStoreQuery.jsx
@@ -31,7 +31,7 @@ import useApi from '@/hooks/useApi'
import { useAuth } from '@/hooks/useAuth'
import useNotifier from '@/utils/useNotifier'
import { baseURL } from '@/store/constant'
-import { initNode } from '@/utils/genericHelper'
+import { initNode, showHideInputParams } from '@/utils/genericHelper'
import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction } from '@/store/actions'
const CardWrapper = styled(MainCard)(({ theme }) => ({
@@ -84,6 +84,15 @@ const VectorStoreQuery = () => {
const getVectorStoreNodeDetailsApi = useApi(nodesApi.getSpecificNode)
const [selectedVectorStoreProvider, setSelectedVectorStoreProvider] = useState({})
+ const handleVectorStoreProviderDataChange = ({ inputParam, newValue }) => {
+ setSelectedVectorStoreProvider((prevData) => {
+ const updatedData = { ...prevData }
+ updatedData.inputs[inputParam.name] = newValue
+ updatedData.inputParams = showHideInputParams(updatedData)
+ return updatedData
+ })
+ }
+
const chunkSelected = (chunkId, selectedChunkNumber) => {
const selectedChunk = documentChunks.find((chunk) => chunk.id === chunkId)
const dialogProps = {
@@ -354,14 +363,15 @@ const VectorStoreQuery = () => {
{selectedVectorStoreProvider &&
Object.keys(selectedVectorStoreProvider).length > 0 &&
- (selectedVectorStoreProvider.inputParams ?? [])
- .filter((inputParam) => !inputParam.hidden)
+ showHideInputParams(selectedVectorStoreProvider)
+ .filter((inputParam) => !inputParam.hidden && inputParam.display !== false)
.map((inputParam, index) => (
))}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1ab10f735..7dc0a2897 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -726,7 +726,7 @@ importers:
version: 6.9.15
openai:
specifier: 4.96.0
- version: 4.96.0(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4))(zod@3.22.4)
+ version: 4.96.0(encoding@0.1.13)(ws@8.18.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))(zod@3.22.4)
passport:
specifier: ^0.7.0
version: 0.7.0
@@ -1067,6 +1067,9 @@ importers:
remark-math:
specifier: ^5.1.1
version: 5.1.1
+ showdown:
+ specifier: ^2.1.0
+ version: 2.1.0
tippy.js:
specifier: ^6.3.7
version: 6.3.7
@@ -16119,6 +16122,10 @@ packages:
shimmer@1.2.1:
resolution: { integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== }
+ showdown@2.1.0:
+ resolution: { integrity: sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ== }
+ hasBin: true
+
shx@0.3.4:
resolution: { integrity: sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g== }
engines: { node: '>=6' }
@@ -21653,7 +21660,7 @@ snapshots:
dotenv: 16.4.5
openai: 4.96.0(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4))(zod@3.22.4)
sharp: 0.33.5
- ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+ ws: 8.18.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)
zod: 3.22.4
zod-to-json-schema: 3.24.1(zod@3.22.4)
transitivePeerDependencies:
@@ -22402,7 +22409,7 @@ snapshots:
'@google/genai@0.7.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.4)':
dependencies:
google-auth-library: 9.15.1(encoding@0.1.13)
- ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+ ws: 8.18.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)
transitivePeerDependencies:
- bufferutil
- encoding
@@ -36019,6 +36026,21 @@ snapshots:
transitivePeerDependencies:
- encoding
+ openai@4.96.0(encoding@0.1.13)(ws@8.18.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))(zod@3.22.4):
+ dependencies:
+ '@types/node': 18.19.23
+ '@types/node-fetch': 2.6.12
+ abort-controller: 3.0.0
+ agentkeepalive: 4.5.0
+ form-data-encoder: 1.7.2
+ formdata-node: 4.4.1
+ node-fetch: 2.7.0(encoding@0.1.13)
+ optionalDependencies:
+ ws: 8.18.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+ zod: 3.22.4
+ transitivePeerDependencies:
+ - encoding
+
openapi-types@12.1.3: {}
openapi-typescript-fetch@1.1.3: {}
@@ -38734,6 +38756,10 @@ snapshots:
shimmer@1.2.1: {}
+ showdown@2.1.0:
+ dependencies:
+ commander: 9.5.0
+
shx@0.3.4:
dependencies:
minimist: 1.2.8