Compare commits

..

3 Commits

Author SHA1 Message Date
renovate[bot]
54146f78cd fix(deps): update dependency zod to v4 2025-12-28 11:36:38 +00:00
renovate[bot]
41184969fa fix(deps): update dependency open to v11 (#439)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-28 20:35:42 +09:00
renovate[bot]
c92975f831 chore(deps): update docker/build-push-action action to v6 (#436)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-28 20:35:15 +09:00
9 changed files with 281 additions and 276 deletions

View File

@@ -54,7 +54,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}} type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v6
with: with:
context: . context: .
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}

View File

@@ -1,15 +1,22 @@
"use client" "use client"
import { Image as ImageIcon, Loader2, Send, Trash2 } from "lucide-react" import {
Download,
History,
Image as ImageIcon,
Loader2,
Send,
Trash2,
} from "lucide-react"
import type React from "react" import type React from "react"
import { useCallback, useEffect, useRef, useState } from "react" import { useCallback, useEffect, useRef, useState } from "react"
import { toast } from "sonner" import { toast } from "sonner"
import { ButtonWithTooltip } from "@/components/button-with-tooltip" import { ButtonWithTooltip } from "@/components/button-with-tooltip"
import { ErrorToast } from "@/components/error-toast" import { ErrorToast } from "@/components/error-toast"
import { HistoryDialog } from "@/components/history-dialog"
import { ModelSelector } from "@/components/model-selector" import { ModelSelector } from "@/components/model-selector"
import { ResetWarningModal } from "@/components/reset-warning-modal" import { ResetWarningModal } from "@/components/reset-warning-modal"
import { SaveDialog } from "@/components/save-dialog"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Switch } from "@/components/ui/switch" import { Switch } from "@/components/ui/switch"
import { Textarea } from "@/components/ui/textarea" import { Textarea } from "@/components/ui/textarea"
@@ -18,7 +25,7 @@ import {
TooltipContent, TooltipContent,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip" } from "@/components/ui/tooltip"
import { useDiagram } from "@/contexts/diagram-context"
import { useDictionary } from "@/hooks/use-dictionary" import { useDictionary } from "@/hooks/use-dictionary"
import { formatMessage } from "@/lib/i18n/utils" import { formatMessage } from "@/lib/i18n/utils"
import { isPdfFile, isTextFile } from "@/lib/pdf-utils" import { isPdfFile, isTextFile } from "@/lib/pdf-utils"
@@ -145,7 +152,8 @@ interface ChatInputProps {
File, File,
{ text: string; charCount: number; isExtracting: boolean } { text: string; charCount: number; isExtracting: boolean }
> >
showHistory?: boolean
onToggleHistory?: (show: boolean) => void
sessionId?: string sessionId?: string
error?: Error | null error?: Error | null
minimalStyle?: boolean minimalStyle?: boolean
@@ -167,7 +175,9 @@ export function ChatInput({
files = [], files = [],
onFileChange = () => {}, onFileChange = () => {},
pdfData = new Map(), pdfData = new Map(),
showHistory = false,
onToggleHistory = () => {},
sessionId,
error = null, error = null,
minimalStyle = false, minimalStyle = false,
onMinimalStyleChange = () => {}, onMinimalStyleChange = () => {},
@@ -178,7 +188,12 @@ export function ChatInput({
onConfigureModels = () => {}, onConfigureModels = () => {},
}: ChatInputProps) { }: ChatInputProps) {
const dict = useDictionary() const dict = useDictionary()
const {
diagramHistory,
saveDiagramToFile,
showSaveDialog,
setShowSaveDialog,
} = useDiagram()
const textareaRef = useRef<HTMLTextAreaElement>(null) const textareaRef = useRef<HTMLTextAreaElement>(null)
const fileInputRef = useRef<HTMLInputElement>(null) const fileInputRef = useRef<HTMLInputElement>(null)
const [isDragging, setIsDragging] = useState(false) const [isDragging, setIsDragging] = useState(false)
@@ -371,6 +386,11 @@ export function ChatInput({
onClear={handleClear} onClear={handleClear}
/> />
<HistoryDialog
showHistory={showHistory}
onToggleHistory={onToggleHistory}
/>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
@@ -401,29 +421,63 @@ export function ChatInput({
</div> </div>
<div className="flex items-center gap-1 overflow-hidden justify-end"> <div className="flex items-center gap-1 overflow-hidden justify-end">
<div className="flex items-center gap-1 overflow-x-hidden"> <ButtonWithTooltip
<ButtonWithTooltip type="button"
type="button" variant="ghost"
variant="ghost" size="sm"
size="sm" onClick={() => onToggleHistory(true)}
onClick={triggerFileInput} disabled={isDisabled || diagramHistory.length === 0}
disabled={isDisabled} tooltipContent={dict.chat.diagramHistory}
tooltipContent={dict.chat.uploadFile} className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground" >
> <History className="h-4 w-4" />
<ImageIcon className="h-4 w-4" /> </ButtonWithTooltip>
</ButtonWithTooltip>
<ButtonWithTooltip
type="button"
variant="ghost"
size="sm"
onClick={() => setShowSaveDialog(true)}
disabled={isDisabled}
tooltipContent={dict.chat.saveDiagram}
className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
>
<Download className="h-4 w-4" />
</ButtonWithTooltip>
<SaveDialog
open={showSaveDialog}
onOpenChange={setShowSaveDialog}
onSave={(filename, format) =>
saveDiagramToFile(filename, format, sessionId)
}
defaultFilename={`diagram-${new Date()
.toISOString()
.slice(0, 10)}`}
/>
<ButtonWithTooltip
type="button"
variant="ghost"
size="sm"
onClick={triggerFileInput}
disabled={isDisabled}
tooltipContent={dict.chat.uploadFile}
className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
>
<ImageIcon className="h-4 w-4" />
</ButtonWithTooltip>
<input
type="file"
ref={fileInputRef}
className="hidden"
onChange={handleFileChange}
accept="image/*,.pdf,application/pdf,text/*,.md,.markdown,.json,.csv,.xml,.yaml,.yml,.toml"
multiple
disabled={isDisabled}
/>
<input
type="file"
ref={fileInputRef}
className="hidden"
onChange={handleFileChange}
accept="image/*,.pdf,application/pdf,text/*,.md,.markdown,.json,.csv,.xml,.yaml,.yml,.toml"
multiple
disabled={isDisabled}
/>
</div>
<ModelSelector <ModelSelector
models={models} models={models}
selectedModelId={selectedModelId} selectedModelId={selectedModelId}
@@ -432,7 +486,9 @@ export function ChatInput({
disabled={isDisabled} disabled={isDisabled}
showUnvalidatedModels={showUnvalidatedModels} showUnvalidatedModels={showUnvalidatedModels}
/> />
<div className="w-px h-5 bg-border mx-1" /> <div className="w-px h-5 bg-border mx-1" />
<Button <Button
type="submit" type="submit"
disabled={isDisabled || !input.trim()} disabled={isDisabled || !input.trim()}

View File

@@ -154,6 +154,7 @@ export default function ChatPanel({
// File processing using extracted hook // File processing using extracted hook
const { files, pdfData, handleFileChange, setFiles } = useFileProcessor() const { files, pdfData, handleFileChange, setFiles } = useFileProcessor()
const [showHistory, setShowHistory] = useState(false)
const [showSettingsDialog, setShowSettingsDialog] = useState(false) const [showSettingsDialog, setShowSettingsDialog] = useState(false)
const [showModelConfigDialog, setShowModelConfigDialog] = useState(false) const [showModelConfigDialog, setShowModelConfigDialog] = useState(false)
@@ -1065,6 +1066,8 @@ export default function ChatPanel({
files={files} files={files}
onFileChange={handleFileChange} onFileChange={handleFileChange}
pdfData={pdfData} pdfData={pdfData}
showHistory={showHistory}
onToggleHistory={setShowHistory}
sessionId={sessionId} sessionId={sessionId}
error={error} error={error}
minimalStyle={minimalStyle} minimalStyle={minimalStyle}
@@ -1085,7 +1088,6 @@ export default function ChatPanel({
onToggleDrawioUi={onToggleDrawioUi} onToggleDrawioUi={onToggleDrawioUi}
darkMode={darkMode} darkMode={darkMode}
onToggleDarkMode={onToggleDarkMode} onToggleDarkMode={onToggleDarkMode}
sessionId={sessionId}
/> />
<ModelConfigDialog <ModelConfigDialog

View File

@@ -1,10 +1,8 @@
"use client" "use client"
import { Download, History, Moon, Sun } from "lucide-react" import { Moon, Sun } from "lucide-react"
import { usePathname, useRouter, useSearchParams } from "next/navigation" import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { Suspense, useEffect, useState } from "react" import { Suspense, useEffect, useState } from "react"
import { HistoryDialog } from "@/components/history-dialog"
import { type ExportFormat, SaveDialog } from "@/components/save-dialog"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { import {
Dialog, Dialog,
@@ -23,7 +21,6 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select" } from "@/components/ui/select"
import { Switch } from "@/components/ui/switch" import { Switch } from "@/components/ui/switch"
import { useDiagram } from "@/contexts/diagram-context"
import { useDictionary } from "@/hooks/use-dictionary" import { useDictionary } from "@/hooks/use-dictionary"
import { getApiEndpoint } from "@/lib/base-path" import { getApiEndpoint } from "@/lib/base-path"
import { i18n, type Locale } from "@/lib/i18n/config" import { i18n, type Locale } from "@/lib/i18n/config"
@@ -68,7 +65,6 @@ interface SettingsDialogProps {
onToggleDrawioUi: () => void onToggleDrawioUi: () => void
darkMode: boolean darkMode: boolean
onToggleDarkMode: () => void onToggleDarkMode: () => void
sessionId?: string
} }
export const STORAGE_ACCESS_CODE_KEY = "next-ai-draw-io-access-code" export const STORAGE_ACCESS_CODE_KEY = "next-ai-draw-io-access-code"
@@ -90,7 +86,6 @@ function SettingsContent({
onToggleDrawioUi, onToggleDrawioUi,
darkMode, darkMode,
onToggleDarkMode, onToggleDarkMode,
sessionId,
}: SettingsDialogProps) { }: SettingsDialogProps) {
const dict = useDictionary() const dict = useDictionary()
const router = useRouter() const router = useRouter()
@@ -100,21 +95,11 @@ function SettingsContent({
const [closeProtection, setCloseProtection] = useState(true) const [closeProtection, setCloseProtection] = useState(true)
const [isVerifying, setIsVerifying] = useState(false) const [isVerifying, setIsVerifying] = useState(false)
const [error, setError] = useState("") const [error, setError] = useState("")
const [showHistory, setShowHistory] = useState(false)
const [showSaveDialog, setShowSaveDialog] = useState(false)
const [accessCodeRequired, setAccessCodeRequired] = useState( const [accessCodeRequired, setAccessCodeRequired] = useState(
() => getStoredAccessCodeRequired() ?? false, () => getStoredAccessCodeRequired() ?? false,
) )
const [currentLang, setCurrentLang] = useState("en") const [currentLang, setCurrentLang] = useState("en")
// Get diagram context
const { diagramHistory, saveDiagramToFile } = useDiagram()
// Handler for saving diagram (RENAMED to avoid conflict)
const handleDiagramSave = (filename: string, format: ExportFormat) => {
saveDiagramToFile(filename, format, sessionId)
}
useEffect(() => { useEffect(() => {
// Only fetch if not cached in localStorage // Only fetch if not cached in localStorage
if (getStoredAccessCodeRequired() !== null) return if (getStoredAccessCodeRequired() !== null) return
@@ -221,211 +206,158 @@ function SettingsContent({
} }
return ( return (
<> <DialogContent className="sm:max-w-lg p-0 gap-0">
<DialogContent className="sm:max-w-lg p-0 gap-0"> {/* Header */}
{/* Header */} <DialogHeader className="px-6 pt-6 pb-4">
<DialogHeader className="px-6 pt-6 pb-4"> <DialogTitle>{dict.settings.title}</DialogTitle>
<DialogTitle>{dict.settings.title}</DialogTitle> <DialogDescription className="mt-1">
<DialogDescription className="mt-1"> {dict.settings.description}
{dict.settings.description} </DialogDescription>
</DialogDescription> </DialogHeader>
</DialogHeader>
{/* Content */} {/* Content */}
<div className="px-6 pb-6"> <div className="px-6 pb-6">
<div className="divide-y divide-border-subtle"> <div className="divide-y divide-border-subtle">
{/* Access Code (conditional) */} {/* Access Code (conditional) */}
{accessCodeRequired && ( {accessCodeRequired && (
<div className="py-4 first:pt-0 space-y-3"> <div className="py-4 first:pt-0 space-y-3">
<div className="space-y-0.5"> <div className="space-y-0.5">
<Label <Label
htmlFor="access-code" htmlFor="access-code"
className="text-sm font-medium" className="text-sm font-medium"
>
{dict.settings.accessCode}
</Label>
<p className="text-xs text-muted-foreground">
{dict.settings.accessCodeDescription}
</p>
</div>
<div className="flex gap-2">
<Input
id="access-code"
type="password"
value={accessCode}
onChange={(e) =>
setAccessCode(e.target.value)
}
onKeyDown={handleKeyDown}
placeholder={
dict.settings.accessCodePlaceholder
}
autoComplete="off"
className="h-9"
/>
<Button
onClick={handleSave}
disabled={
isVerifying || !accessCode.trim()
}
className="h-9 px-4 rounded-xl"
>
{isVerifying ? "..." : dict.common.save}
</Button>
</div>
{error && (
<p className="text-xs text-destructive">
{error}
</p>
)}
</div>
)}
{/* Language */}
<SettingItem
label={dict.settings.language}
description={dict.settings.languageDescription}
>
<Select
value={currentLang}
onValueChange={changeLanguage}
>
<SelectTrigger
id="language-select"
className="w-[120px] h-9 rounded-xl"
> >
<SelectValue /> {dict.settings.accessCode}
</SelectTrigger> </Label>
<SelectContent> <p className="text-xs text-muted-foreground">
{i18n.locales.map((locale) => ( {dict.settings.accessCodeDescription}
<SelectItem key={locale} value={locale}> </p>
{LANGUAGE_LABELS[locale]} </div>
</SelectItem>
))}
</SelectContent>
</Select>
</SettingItem>
{/* Theme */}
<SettingItem
label={dict.settings.theme}
description={dict.settings.themeDescription}
>
<Button
id="theme-toggle"
variant="outline"
size="icon"
onClick={onToggleDarkMode}
className="h-9 w-9 rounded-xl border-border-subtle hover:bg-interactive-hover"
>
{darkMode ? (
<Sun className="h-4 w-4" />
) : (
<Moon className="h-4 w-4" />
)}
</Button>
</SettingItem>
{/* Draw.io Style */}
<SettingItem
label={dict.settings.drawioStyle}
description={`${dict.settings.drawioStyleDescription} ${
drawioUi === "min"
? dict.settings.minimal
: dict.settings.sketch
}`}
>
<Button
id="drawio-ui"
variant="outline"
onClick={onToggleDrawioUi}
className="h-9 w-[120px] rounded-xl border-border-subtle hover:bg-interactive-hover font-normal"
>
{dict.settings.switchTo}{" "}
{drawioUi === "min"
? dict.settings.sketch
: dict.settings.minimal}
</Button>
</SettingItem>
{/* Close Protection */}
<SettingItem
label={dict.settings.closeProtection}
description={
dict.settings.closeProtectionDescription
}
>
<Switch
id="close-protection"
checked={closeProtection}
onCheckedChange={(checked) => {
setCloseProtection(checked)
localStorage.setItem(
STORAGE_CLOSE_PROTECTION_KEY,
checked.toString(),
)
onCloseProtectionChange?.(checked)
}}
/>
</SettingItem>
{/* Diagram Actions */}
<SettingItem
label={dict.settings.diagramActions}
description={
dict.settings.diagramActionsDescription
}
>
<div className="flex gap-2"> <div className="flex gap-2">
<Input
id="access-code"
type="password"
value={accessCode}
onChange={(e) =>
setAccessCode(e.target.value)
}
onKeyDown={handleKeyDown}
placeholder={
dict.settings.accessCodePlaceholder
}
autoComplete="off"
className="h-9"
/>
<Button <Button
id="history-button" onClick={handleSave}
variant="outline" disabled={isVerifying || !accessCode.trim()}
size="sm" className="h-9 px-4 rounded-xl"
onClick={() => setShowHistory(true)}
disabled={diagramHistory.length === 0}
className="h-9 rounded-xl border-border-subtle hover:bg-interactive-hover"
> >
<History className="h-4 w-4 mr-1.5" /> {isVerifying ? "..." : dict.common.save}
{dict.settings.history}
</Button>
<Button
id="download-button"
variant="outline"
size="sm"
onClick={() => setShowSaveDialog(true)}
className="h-9 rounded-xl border-border-subtle hover:bg-interactive-hover"
>
<Download className="h-4 w-4 mr-1.5" />
{dict.settings.download}
</Button> </Button>
</div> </div>
</SettingItem> {error && (
</div> <p className="text-xs text-destructive">
</div> {error}
</p>
)}
</div>
)}
{/* Footer */} {/* Language */}
<div className="px-6 py-4 border-t border-border-subtle bg-surface-1/50 rounded-b-2xl"> <SettingItem
<p className="text-xs text-muted-foreground text-center"> label={dict.settings.language}
Version {process.env.APP_VERSION} description={dict.settings.languageDescription}
</p> >
<Select
value={currentLang}
onValueChange={changeLanguage}
>
<SelectTrigger
id="language-select"
className="w-[120px] h-9 rounded-xl"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
{i18n.locales.map((locale) => (
<SelectItem key={locale} value={locale}>
{LANGUAGE_LABELS[locale]}
</SelectItem>
))}
</SelectContent>
</Select>
</SettingItem>
{/* Theme */}
<SettingItem
label={dict.settings.theme}
description={dict.settings.themeDescription}
>
<Button
id="theme-toggle"
variant="outline"
size="icon"
onClick={onToggleDarkMode}
className="h-9 w-9 rounded-xl border-border-subtle hover:bg-interactive-hover"
>
{darkMode ? (
<Sun className="h-4 w-4" />
) : (
<Moon className="h-4 w-4" />
)}
</Button>
</SettingItem>
{/* Draw.io Style */}
<SettingItem
label={dict.settings.drawioStyle}
description={`${dict.settings.drawioStyleDescription} ${
drawioUi === "min"
? dict.settings.minimal
: dict.settings.sketch
}`}
>
<Button
id="drawio-ui"
variant="outline"
onClick={onToggleDrawioUi}
className="h-9 w-[120px] rounded-xl border-border-subtle hover:bg-interactive-hover font-normal"
>
{dict.settings.switchTo}{" "}
{drawioUi === "min"
? dict.settings.sketch
: dict.settings.minimal}
</Button>
</SettingItem>
{/* Close Protection */}
<SettingItem
label={dict.settings.closeProtection}
description={dict.settings.closeProtectionDescription}
>
<Switch
id="close-protection"
checked={closeProtection}
onCheckedChange={(checked) => {
setCloseProtection(checked)
localStorage.setItem(
STORAGE_CLOSE_PROTECTION_KEY,
checked.toString(),
)
onCloseProtectionChange?.(checked)
}}
/>
</SettingItem>
</div> </div>
</DialogContent> </div>
;(
<HistoryDialog {/* Footer */}
showHistory={showHistory} <div className="px-6 py-4 border-t border-border-subtle bg-surface-1/50 rounded-b-2xl">
onToggleHistory={setShowHistory} <p className="text-xs text-muted-foreground text-center">
/> Version {process.env.APP_VERSION}
) </p>
<SaveDialog </div>
open={showSaveDialog} </DialogContent>
onOpenChange={setShowSaveDialog}
onSave={handleDiagramSave}
defaultFilename={`diagram-${new Date()
.toISOString()
.slice(0, 10)}
`}
/>
</>
) )
} }

View File

@@ -98,11 +98,7 @@
"minimal": "Minimal", "minimal": "Minimal",
"sketch": "Sketch", "sketch": "Sketch",
"closeProtection": "Close Protection", "closeProtection": "Close Protection",
"closeProtectionDescription": "Show confirmation when leaving the page.", "closeProtectionDescription": "Show confirmation when leaving the page."
"diagramActions": "Diagram Actions",
"diagramActionsDescription": "Manage diagram history and exports",
"history": "History",
"download": "Download"
}, },
"save": { "save": {
"title": "Save Diagram", "title": "Save Diagram",

View File

@@ -98,11 +98,7 @@
"minimal": "ミニマル", "minimal": "ミニマル",
"sketch": "スケッチ", "sketch": "スケッチ",
"closeProtection": "ページ離脱確認", "closeProtection": "ページ離脱確認",
"closeProtectionDescription": "ページを離れる際に確認を表示します。", "closeProtectionDescription": "ページを離れる際に確認を表示します。"
"diagramActions": "ダイアグラム操作",
"diagramActionsDescription": "ダイアグラムの履歴とエクスポートを管理",
"history": "履歴",
"download": "ダウンロード"
}, },
"save": { "save": {
"title": "ダイアグラムを保存", "title": "ダイアグラムを保存",

View File

@@ -98,11 +98,7 @@
"minimal": "简约", "minimal": "简约",
"sketch": "草图", "sketch": "草图",
"closeProtection": "关闭确认", "closeProtection": "关闭确认",
"closeProtectionDescription": "离开页面时显示确认。", "closeProtectionDescription": "离开页面时显示确认。"
"diagramActions": "图表操作",
"diagramActionsDescription": "管理图表历史记录和导出",
"history": "历史记录",
"download": "下载"
}, },
"save": { "save": {
"title": "保存图表", "title": "保存图表",

View File

@@ -11,8 +11,8 @@
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4", "@modelcontextprotocol/sdk": "^1.0.4",
"linkedom": "^0.18.0", "linkedom": "^0.18.0",
"open": "^10.1.0", "open": "^11.0.0",
"zod": "^3.24.0" "zod": "^4.0.0"
}, },
"bin": { "bin": {
"next-ai-drawio-mcp": "dist/index.js" "next-ai-drawio-mcp": "dist/index.js"
@@ -1372,6 +1372,18 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/is-in-ssh": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz",
"integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==",
"license": "MIT",
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-inside-container": { "node_modules/is-inside-container": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
@@ -1587,18 +1599,20 @@
} }
}, },
"node_modules/open": { "node_modules/open": {
"version": "10.2.0", "version": "11.0.0",
"resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz",
"integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"default-browser": "^5.2.1", "default-browser": "^5.4.0",
"define-lazy-prop": "^3.0.0", "define-lazy-prop": "^3.0.0",
"is-in-ssh": "^1.0.0",
"is-inside-container": "^1.0.0", "is-inside-container": "^1.0.0",
"wsl-utils": "^0.1.0" "powershell-utils": "^0.1.0",
"wsl-utils": "^0.3.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=20"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
@@ -1641,6 +1655,18 @@
"node": ">=16.20.0" "node": ">=16.20.0"
} }
}, },
"node_modules/powershell-utils": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz",
"integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==",
"license": "MIT",
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/proxy-addr": { "node_modules/proxy-addr": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -2009,24 +2035,25 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/wsl-utils": { "node_modules/wsl-utils": {
"version": "0.1.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.0.tgz",
"integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", "integrity": "sha512-3sFIGLiaDP7rTO4xh3g+b3AzhYDIUGGywE/WsmqzJWDxus5aJXVnPTNC/6L+r2WzrwXqVOdD262OaO+cEyPMSQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"is-wsl": "^3.1.0" "is-wsl": "^3.1.0",
"powershell-utils": "^0.1.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=20"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/zod": { "node_modules/zod": {
"version": "3.25.76", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"funding": { "funding": {

View File

@@ -38,8 +38,8 @@
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4", "@modelcontextprotocol/sdk": "^1.0.4",
"linkedom": "^0.18.0", "linkedom": "^0.18.0",
"open": "^10.1.0", "open": "^11.0.0",
"zod": "^3.24.0" "zod": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20", "@types/node": "^20",