diff --git a/.gitignore b/.gitignore index 20669448..71954155 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,10 @@ workspace.xml # VitePress / frontend build artifacts www/.vitepress/cache/ www/.vitepress/dist/ + +# Web frontend build artifacts +web/dist/ +web/.vite/ +web/build/ +web/release/ +web/dist-electron/ diff --git a/docs/reference/gateway-rpc-api.md b/docs/reference/gateway-rpc-api.md index 47d4d7f7..3fc19a47 100644 --- a/docs/reference/gateway-rpc-api.md +++ b/docs/reference/gateway-rpc-api.md @@ -303,6 +303,7 @@ type RunParams struct { InputText string `json:"input_text,omitempty"` // 与 input_parts 至少一个非空 InputParts []RunInputPart `json:"input_parts,omitempty"` // text|image Workdir string `json:"workdir,omitempty"` // 请求级工作目录覆盖 + Mode string `json:"mode,omitempty"` // Agent 工作模式:build|plan,可选,默认沿用 session 当前 mode } type RunInputPart struct { @@ -320,6 +321,7 @@ type RunInputPart struct { 2. `type=image` 时 `media.uri` 与 `media.mime_type` `MUST` 非空。 3. 未知字段会因严格解码触发 `invalid_frame`。 4. `run_id` 归一化顺序为:显式 `run_id` > `request_id` > 网关生成 `run_`。 +5. `mode` 可选值为 `"build"` 或 `"plan"`,为空时默认沿用 session 当前 mode(新会话默认为 `"build"`)。切换 mode 后,后端会更新 session 并影响后续运行的工具可用性和 prompt 策略。 Response Schema: diff --git a/internal/app/bootstrap.go b/internal/app/bootstrap.go index 8e0e5ad8..3c14f030 100644 --- a/internal/app/bootstrap.go +++ b/internal/app/bootstrap.go @@ -2,6 +2,7 @@ package app import ( "context" + "fmt" "log" "os" "path/filepath" @@ -123,6 +124,7 @@ type RuntimeBundle struct { Runtime agentruntime.Runtime SessionStore *agentsession.SQLiteStore ProviderSelection *configstate.Service + ToolRegistry *tools.Registry MemoService *memo.Service Close func() error // 用于清理 bundle 运行期间拉起的系统资源 } @@ -270,6 +272,7 @@ func BuildGatewayServerDeps(ctx context.Context, opts BootstrapOptions) (Runtime Runtime: runtimeImpl, SessionStore: sessionStore, ProviderSelection: sharedDeps.ProviderSelection, + ToolRegistry: toolRegistry, MemoService: memoSvc, Close: closeBundle, }, nil @@ -405,6 +408,7 @@ func BuildTUIClientDeps(ctx context.Context, opts BootstrapOptions) (RuntimeBund Config: sharedDeps.Config, ConfigManager: sharedDeps.ConfigManager, ProviderSelection: sharedDeps.ProviderSelection, + ToolRegistry: nil, MemoService: nil, Close: nil, }, nil @@ -452,7 +456,7 @@ func buildToolRegistry(cfg config.Config) (*tools.Registry, func() error, error) })) toolRegistry.Register(todo.New()) toolRegistry.Register(spawnsubagent.New()) - mcpRegistry, err := buildMCPRegistry(cfg) + mcpRegistry, err := BuildMCPRegistry(cfg) if err != nil { return nil, nil, err } @@ -471,6 +475,27 @@ func buildToolRegistry(cfg config.Config) (*tools.Registry, func() error, error) } // buildSkillsRegistry 负责按“项目优先全局”顺序构建本地 skills registry。 +// RebuildMCPServersForRegistry 根据最新配置重建指定 registry 中的 MCP server;失败时保留旧 registry 不替换。 +func RebuildMCPServersForRegistry(registry *tools.Registry, cfg config.Config) error { + if registry == nil { + return nil + } + newMcpRegistry, err := BuildMCPRegistry(cfg) + if err != nil { + return fmt.Errorf("app: build mcp registry: %w", err) + } + var filter mcp.ExposureFilter + if newMcpRegistry != nil { + filter = mcp.NewExposureFilter(mcp.ExposureFilterConfig{ + Allowlist: cfg.Tools.MCP.Exposure.Allowlist, + Denylist: cfg.Tools.MCP.Exposure.Denylist, + Agents: buildMCPAgentExposureRules(cfg.Tools.MCP.Exposure.Agents), + }) + } + registry.ReplaceMCPRegistry(newMcpRegistry, filter) + return nil +} + func buildSkillsRegistry(ctx context.Context, baseDir string, workdir string) skills.Registry { loaders := make([]skills.Loader, 0, 2) projectRoot := resolveWorkspaceSkillsRoot(workdir) diff --git a/internal/app/bootstrap_test.go b/internal/app/bootstrap_test.go index 17a11d5d..17924471 100644 --- a/internal/app/bootstrap_test.go +++ b/internal/app/bootstrap_test.go @@ -386,9 +386,9 @@ func TestBuildMCPRegistryFromConfig(t *testing.T) { return registry.RefreshServerTools(context.Background(), server.ID) } - registry, err := buildMCPRegistry(cfg) + registry, err := BuildMCPRegistry(cfg) if err != nil { - t.Fatalf("buildMCPRegistry() error = %v", err) + t.Fatalf("BuildMCPRegistry() error = %v", err) } if registry == nil { t.Fatalf("expected non-nil mcp registry") @@ -415,7 +415,7 @@ func TestBuildMCPRegistryUnsupportedSource(t *testing.T) { }, } - registry, err := buildMCPRegistry(cfg) + registry, err := BuildMCPRegistry(cfg) if err == nil { t.Fatalf("expected unsupported source error") } @@ -735,9 +735,9 @@ func TestBuildMCPRegistryNoEnabledServerReturnsNil(t *testing.T) { {ID: "docs", Enabled: false, Source: "stdio"}, } - registry, err := buildMCPRegistry(cfg) + registry, err := BuildMCPRegistry(cfg) if err != nil { - t.Fatalf("buildMCPRegistry() error = %v", err) + t.Fatalf("BuildMCPRegistry() error = %v", err) } if registry != nil { t.Fatalf("expected nil registry when no enabled server") @@ -757,7 +757,7 @@ func TestBuildMCPRegistryRegisterError(t *testing.T) { return errors.New("register failed") } - _, err := buildMCPRegistry(cfg) + _, err := BuildMCPRegistry(cfg) if err == nil || !strings.Contains(err.Error(), "register failed") { t.Fatalf("expected wrapped register error, got %v", err) } @@ -789,7 +789,7 @@ func TestBuildMCPRegistryRollbackRegisteredServersOnFailure(t *testing.T) { return nil } - registry, err := buildMCPRegistry(cfg) + registry, err := BuildMCPRegistry(cfg) if err == nil || !strings.Contains(err.Error(), "search register failed") { t.Fatalf("expected wrapped register error, got %v", err) } diff --git a/internal/app/mcp_bootstrap.go b/internal/app/mcp_bootstrap.go index d0253b0c..7a671889 100644 --- a/internal/app/mcp_bootstrap.go +++ b/internal/app/mcp_bootstrap.go @@ -15,8 +15,8 @@ import ( var newMCPStdioClient = mcp.NewStdIOClient var registerMCPStdioServer = defaultRegisterMCPStdioServer -// buildMCPRegistry 按配置构建并初始化 MCP registry;若无启用 server 则返回 nil。 -func buildMCPRegistry(cfg config.Config) (*mcp.Registry, error) { +// BuildMCPRegistry 按配置构建并初始化 MCP registry;若无启用 server 则返回 nil。 +func BuildMCPRegistry(cfg config.Config) (*mcp.Registry, error) { if len(cfg.Tools.MCP.Servers) == 0 { return nil, nil } diff --git a/internal/cli/gateway_commands.go b/internal/cli/gateway_commands.go index a997d5c2..ccd4e4ed 100644 --- a/internal/cli/gateway_commands.go +++ b/internal/cli/gateway_commands.go @@ -56,6 +56,7 @@ type gatewayCommandOptions struct { MetricsEnabled bool MetricsEnabledOverridden bool + SkipIPC bool } // defaultNewAuthManager 创建默认网关认证器,并把具体持久化实现收敛在 CLI 装配层内部。 @@ -181,8 +182,18 @@ func mustReadInheritedWorkdir(cmd *cobra.Command) string { // defaultGatewayCommandRunner 使用网关服务骨架启动本地 IPC 监听并处理中断退出。 func defaultGatewayCommandRunner(ctx context.Context, options gatewayCommandOptions) error { + return startGatewayServer(ctx, options, "", nil) +} + +// startGatewayServer 启动网关服务的共享实现,staticFileDir 非空时同时提供 SPA 静态文件服务。 +// onNetworkReady 在网络服务器开始监听后回调,传出实际监听地址。 +func startGatewayServer(ctx context.Context, options gatewayCommandOptions, staticFileDir string, onNetworkReady func(address string)) error { logger := log.New(os.Stderr, "neocode-gateway: ", log.LstdFlags) - logger.Printf("starting gateway (log-level=%s)", options.LogLevel) + logPrefix := "starting gateway" + if staticFileDir != "" { + logPrefix = "starting gateway with web UI" + } + logger.Printf("%s (log-level=%s)", logPrefix, options.LogLevel) signalContext, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM) defer stop() @@ -233,24 +244,34 @@ func defaultGatewayCommandRunner(ctx context.Context, options gatewayCommandOpti idleCloser := newGatewayIdleShutdownController(logger, cancelRuntime) defer idleCloser.close() - ipcServer, err := newGatewayServer(gateway.ServerOptions{ - ListenAddress: options.ListenAddress, - Logger: logger, - MaxConnections: gatewayConfig.Limits.IPCMaxConnections, - MaxFrameSize: int64(gatewayConfig.Limits.MaxFrameBytes), - ReadTimeout: time.Duration(gatewayConfig.Timeouts.IPCReadSec) * time.Second, - WriteTimeout: time.Duration(gatewayConfig.Timeouts.IPCWriteSec) * time.Second, - Relay: relay, - Authenticator: authManager, - ACL: acl, - Metrics: metrics, - ConnectionCountChanged: func(active int) { - idleCloser.observe(active) - }, - }) - if err != nil { - return err + type transportAdapterEntry struct { + name string + adapter gateway.TransportAdapter } + var transportAdapters []transportAdapterEntry + + if !options.SkipIPC { + ipcServer, err := newGatewayServer(gateway.ServerOptions{ + ListenAddress: options.ListenAddress, + Logger: logger, + MaxConnections: gatewayConfig.Limits.IPCMaxConnections, + MaxFrameSize: int64(gatewayConfig.Limits.MaxFrameBytes), + ReadTimeout: time.Duration(gatewayConfig.Timeouts.IPCReadSec) * time.Second, + WriteTimeout: time.Duration(gatewayConfig.Timeouts.IPCWriteSec) * time.Second, + Relay: relay, + Authenticator: authManager, + ACL: acl, + Metrics: metrics, + ConnectionCountChanged: func(active int) { + idleCloser.observe(active) + }, + }) + if err != nil { + return err + } + transportAdapters = append(transportAdapters, transportAdapterEntry{name: "ipc", adapter: ipcServer}) + } + networkServer, err := newGatewayNetwork(gateway.NetworkServerOptions{ ListenAddress: options.HTTPAddress, Logger: logger, @@ -264,22 +285,19 @@ func defaultGatewayCommandRunner(ctx context.Context, options gatewayCommandOpti ACL: acl, Metrics: metrics, AllowedOrigins: gatewayConfig.Security.AllowOrigins, + StaticFileDir: staticFileDir, ConnectionCountChanged: func(active int) { idleCloser.observe(active) }, }) if err != nil { - _ = ipcServer.Close(context.Background()) + for _, entry := range transportAdapters { + _ = entry.adapter.Close(context.Background()) + } return err } - type transportAdapterEntry struct { - name string - adapter gateway.TransportAdapter - } - transportAdapters := []transportAdapterEntry{ - {name: "ipc", adapter: ipcServer}, - {name: "network", adapter: networkServer}, - } + transportAdapters = append(transportAdapters, transportAdapterEntry{name: "network", adapter: networkServer}) + defer func() { relay.Stop() for index := len(transportAdapters) - 1; index >= 0; index-- { @@ -291,6 +309,11 @@ func defaultGatewayCommandRunner(ctx context.Context, options gatewayCommandOpti logger.Printf("gateway %s listen address: %s", entry.name, entry.adapter.ListenAddress()) } + // 网络服务器就绪后通知调用方(用于打开浏览器) + if onNetworkReady != nil { + onNetworkReady(networkServer.ListenAddress()) + } + for index, entry := range transportAdapters { if index == 0 { continue @@ -299,8 +322,9 @@ func defaultGatewayCommandRunner(ctx context.Context, options gatewayCommandOpti serveErr := networkAdapter.Serve(runtimeContext, runtimePort) if serveErr != nil && runtimeContext.Err() == nil { logger.Printf( - "warning: HTTP server failed to start on %s (port in use?), but IPC server is still running: %v", + "warning: %s server failed to start on %s: %v", networkAdapter.ListenAddress(), + entry.name, serveErr, ) } diff --git a/internal/cli/gateway_runtime_bridge.go b/internal/cli/gateway_runtime_bridge.go index 79b0ba03..97bdcdf6 100644 --- a/internal/cli/gateway_runtime_bridge.go +++ b/internal/cli/gateway_runtime_bridge.go @@ -61,13 +61,37 @@ type bridgeSessionLoader interface { } // defaultBuildGatewayRuntimePort 构建网关运行时 RuntimePort 适配器,并返回对应资源清理函数。 +// 当启用多工作区时,返回 MultiWorkspaceRuntime 路由代理,每个工作区拥有独立的 RuntimeBundle。 func defaultBuildGatewayRuntimePort(ctx context.Context, workdir string) (gateway.RuntimePort, func() error, error) { - bundle, err := app.BuildGatewayServerDeps(ctx, app.BootstrapOptions{Workdir: strings.TrimSpace(workdir)}) + trimmedWorkdir := strings.TrimSpace(workdir) + + // 先构建默认工作区的 bundle,用于获取 baseDir 和共享组件。 + bundle, err := app.BuildGatewayServerDeps(ctx, app.BootstrapOptions{Workdir: trimmedWorkdir}) if err != nil { return nil, nil, err } - bridge, err := newGatewayRuntimePortBridge(ctx, bundle.Runtime, bundle.SessionStore, bundle.ConfigManager, bundle.ProviderSelection) + baseDir := bundle.ConfigManager.BaseDir() + index := agentsession.NewWorkspaceIndex(baseDir) + if err := index.Load(); err != nil { + _ = bundle.Close() + return nil, nil, err + } + + defaultHash := "" + if trimmedWorkdir != "" { + defaultHash = agentsession.HashWorkspaceRoot(trimmedWorkdir) + if _, err := index.Register(trimmedWorkdir, ""); err != nil { + _ = bundle.Close() + return nil, nil, err + } + if err := index.Save(); err != nil { + _ = bundle.Close() + return nil, nil, err + } + } + + bridge, err := newGatewayRuntimePortBridge(ctx, bundle.Runtime, bundle.SessionStore, bundle.ConfigManager, bundle.ProviderSelection, bundle.ToolRegistry) if err != nil { if bundle.Close != nil { _ = bundle.Close() @@ -75,7 +99,37 @@ func defaultBuildGatewayRuntimePort(ctx context.Context, workdir string) (gatewa return nil, nil, err } - cleanup := func() error { + buildPort := func(ctx context.Context, wd string) (gateway.RuntimePort, func() error, error) { + trimmedWd := strings.TrimSpace(wd) + if trimmedWd != "" { + _ = os.MkdirAll(trimmedWd, 0o755) + } + b, err := app.BuildGatewayServerDeps(ctx, app.BootstrapOptions{Workdir: trimmedWd}) + if err != nil { + return nil, nil, err + } + br, err := newGatewayRuntimePortBridge(ctx, b.Runtime, b.SessionStore, b.ConfigManager, b.ProviderSelection, b.ToolRegistry) + if err != nil { + if b.Close != nil { + _ = b.Close() + } + return nil, nil, err + } + cleanup := func() error { + var closeErr error + if br != nil { + closeErr = errors.Join(closeErr, br.Close()) + } + if b.Close != nil { + closeErr = errors.Join(closeErr, b.Close()) + } + return closeErr + } + return br, cleanup, nil + } + + mw := gateway.NewMultiWorkspaceRuntime(index, defaultHash, buildPort) + mw.PreloadWorkspaceBundle(defaultHash, bridge, func() error { var closeErr error if bridge != nil { closeErr = errors.Join(closeErr, bridge.Close()) @@ -84,9 +138,10 @@ func defaultBuildGatewayRuntimePort(ctx context.Context, workdir string) (gatewa closeErr = errors.Join(closeErr, bundle.Close()) } return closeErr - } + }) + mw.SetManagementPort(bridge) - return bridge, cleanup, nil + return mw, mw.Close, nil } // configManagerPort 定义桥接层对配置管理器的最小需求。 @@ -111,6 +166,7 @@ type gatewayRuntimePortBridge struct { sessionStore bridgeSessionStore configManager configManagerPort providerSelection providerSelectorPort + toolRegistry *tools.Registry events chan gateway.RuntimeEvent stopOnce sync.Once @@ -132,12 +188,15 @@ func newGatewayRuntimePortBridge( } var cm configManagerPort var ps providerSelectorPort + var tr *tools.Registry for _, extra := range extras { switch typed := extra.(type) { case configManagerPort: cm = typed case providerSelectorPort: ps = typed + case *tools.Registry: + tr = typed } } @@ -146,6 +205,7 @@ func newGatewayRuntimePortBridge( sessionStore: store, configManager: cm, providerSelection: ps, + toolRegistry: tr, events: make(chan gateway.RuntimeEvent, 128), stopCh: make(chan struct{}), } @@ -158,7 +218,22 @@ func (b *gatewayRuntimePortBridge) Run(ctx context.Context, input gateway.RunInp if err := b.ensureRuntimeAccess(input.SubjectID); err != nil { return err } - return b.runtime.Submit(ctx, convertGatewayRunInput(input)) + err := b.runtime.Submit(ctx, convertGatewayRunInput(input)) + if err != nil && isRuntimeNotFoundError(err) { + sessionID := strings.TrimSpace(input.SessionID) + if sessionID == "" { + return err + } + creator, ok := b.runtime.(runtimeSessionCreator) + if !ok { + return err + } + if _, createErr := creator.CreateSession(ctx, sessionID); createErr != nil { + return err + } + return b.runtime.Submit(ctx, convertGatewayRunInput(input)) + } + return err } // Compact 将 gateway.compact 请求映射到 runtime 紧凑化能力并回填统一结果。 @@ -512,21 +587,24 @@ func (b *gatewayRuntimePortBridge) ListFiles(ctx context.Context, input gateway. return result, nil } -// ListModels 列出可用模型。 +// ListModels 列出可用模型(仅返回当前选中 provider 的模型)。 func (b *gatewayRuntimePortBridge) ListModels(ctx context.Context, input gateway.ListModelsInput) ([]gateway.ModelEntry, error) { if err := b.ensureRuntimeAccess(input.SubjectID); err != nil { return nil, err } - if b.providerSelection == nil { - return nil, fmt.Errorf("gateway runtime bridge: provider selection is unavailable") - } - options, err := b.providerSelection.ListProviderOptions(ctx) + + // 复用 ListProviders 的 Selected 标记逻辑,避免与 cfg.SelectedProvider 单独比较产生不一致 + providers, err := b.ListProviders(ctx, gateway.ListProvidersInput{SubjectID: input.SubjectID}) if err != nil { return nil, err } + models := make([]gateway.ModelEntry, 0) - for _, option := range options { - for _, model := range option.Models { + for _, p := range providers { + if !p.Selected { + continue + } + for _, model := range p.Models { id := strings.TrimSpace(model.ID) if id == "" { continue @@ -538,7 +616,7 @@ func (b *gatewayRuntimePortBridge) ListModels(ctx context.Context, input gateway models = append(models, gateway.ModelEntry{ ID: id, Name: name, - Provider: strings.TrimSpace(option.ID), + Provider: strings.TrimSpace(p.ID), }) } } @@ -726,7 +804,7 @@ func (b *gatewayRuntimePortBridge) UpsertMCPServer(ctx context.Context, input ga } server := input.Server.Clone() server.ID = strings.TrimSpace(server.ID) - return b.configManager.Update(ctx, func(cfg *config.Config) error { + if err := b.configManager.Update(ctx, func(cfg *config.Config) error { servers := cfg.Tools.MCP.Clone().Servers replaced := false for index := range servers { @@ -741,7 +819,13 @@ func (b *gatewayRuntimePortBridge) UpsertMCPServer(ctx context.Context, input ga } cfg.Tools.MCP.Servers = servers return nil - }) + }); err != nil { + return err + } + if err := app.RebuildMCPServersForRegistry(b.toolRegistry, b.configManager.Get()); err != nil { + return fmt.Errorf("gateway runtime bridge: rebuild mcp servers after upsert: %w", err) + } + return nil } // SetMCPServerEnabled 修改 MCP server 启用状态。 @@ -753,7 +837,7 @@ func (b *gatewayRuntimePortBridge) SetMCPServerEnabled(ctx context.Context, inpu return fmt.Errorf("gateway runtime bridge: config manager is unavailable") } id := strings.TrimSpace(input.ID) - return b.configManager.Update(ctx, func(cfg *config.Config) error { + if err := b.configManager.Update(ctx, func(cfg *config.Config) error { servers := cfg.Tools.MCP.Clone().Servers for index := range servers { if strings.EqualFold(strings.TrimSpace(servers[index].ID), id) { @@ -763,7 +847,13 @@ func (b *gatewayRuntimePortBridge) SetMCPServerEnabled(ctx context.Context, inpu } } return fmt.Errorf("%w: mcp server %q not found", gateway.ErrRuntimeResourceNotFound, id) - }) + }); err != nil { + return err + } + if err := app.RebuildMCPServersForRegistry(b.toolRegistry, b.configManager.Get()); err != nil { + return fmt.Errorf("gateway runtime bridge: rebuild mcp servers after set enabled: %w", err) + } + return nil } // DeleteMCPServer 删除 MCP server 配置。 @@ -775,7 +865,7 @@ func (b *gatewayRuntimePortBridge) DeleteMCPServer(ctx context.Context, input ga return fmt.Errorf("gateway runtime bridge: config manager is unavailable") } id := strings.TrimSpace(input.ID) - return b.configManager.Update(ctx, func(cfg *config.Config) error { + if err := b.configManager.Update(ctx, func(cfg *config.Config) error { servers := cfg.Tools.MCP.Clone().Servers next := servers[:0] removed := false @@ -791,7 +881,13 @@ func (b *gatewayRuntimePortBridge) DeleteMCPServer(ctx context.Context, input ga } cfg.Tools.MCP.Servers = next return nil - }) + }); err != nil { + return err + } + if err := app.RebuildMCPServersForRegistry(b.toolRegistry, b.configManager.Get()); err != nil { + return fmt.Errorf("gateway runtime bridge: rebuild mcp servers after delete: %w", err) + } + return nil } // Close 主动停止桥接事件泵,避免网关关闭后后台协程悬挂。 @@ -877,6 +973,7 @@ func convertGatewayRunInput(input gateway.RunInput) agentruntime.PrepareInput { SessionID: strings.TrimSpace(input.SessionID), RunID: runID, Workdir: strings.TrimSpace(input.Workdir), + Mode: strings.TrimSpace(input.Mode), Text: strings.Join(textParts, "\n"), Images: images, } @@ -1052,6 +1149,7 @@ func convertRuntimeSessionToGatewaySession(session agentsession.Session) gateway Workdir: strings.TrimSpace(session.Workdir), Provider: strings.TrimSpace(session.Provider), Model: strings.TrimSpace(session.Model), + AgentMode: strings.TrimSpace(string(session.AgentMode)), Messages: convertSessionMessages(session.Messages), } } @@ -1321,6 +1419,23 @@ func (b *gatewayRuntimePortBridge) modelDisplayName(ctx context.Context, provide return modelID } +// ReloadConfig 从磁盘重新加载内存配置快照,使管理端口的写入对其他工作区可见。 +func (b *gatewayRuntimePortBridge) ReloadConfig(ctx context.Context) error { + if b == nil || b.configManager == nil { + return nil + } + _, err := b.configManager.Load(ctx) + return err +} + +// RebuildMCPServers 根据当前配置重建 MCP Server 工具注册表。 +func (b *gatewayRuntimePortBridge) RebuildMCPServers() error { + if b == nil || b.toolRegistry == nil || b.configManager == nil { + return nil + } + return app.RebuildMCPServersForRegistry(b.toolRegistry, b.configManager.Get()) +} + // currentConfig 返回当前配置快照;桥接器未绑定配置管理器时退回静态默认值。 func (b *gatewayRuntimePortBridge) currentConfig() config.Config { if b == nil || b.configManager == nil { diff --git a/internal/cli/gateway_runtime_bridge_test.go b/internal/cli/gateway_runtime_bridge_test.go index f9b48123..3ae60863 100644 --- a/internal/cli/gateway_runtime_bridge_test.go +++ b/internal/cli/gateway_runtime_bridge_test.go @@ -1783,8 +1783,9 @@ func TestGatewayRuntimePortBridgeListModelsNameFallback(t *testing.T) { }, }, } + cm := &configManagerStub{cfg: config.Config{SelectedProvider: "openai"}} stub := &runtimeStub{eventsCh: make(chan agentruntime.RuntimeEvent, 1)} - bridge, _ := newGatewayRuntimePortBridge(context.Background(), stub, testSessionStore, nil, ps) + bridge, _ := newGatewayRuntimePortBridge(context.Background(), stub, testSessionStore, cm, ps) defer bridge.Close() models, err := bridge.ListModels(context.Background(), gateway.ListModelsInput{SubjectID: testBridgeSubjectID}) diff --git a/internal/cli/root.go b/internal/cli/root.go index cf02bd9f..5b2db1d6 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -98,6 +98,7 @@ func NewRootCommand() *cobra.Command { _ = settings.BindPFlag("wake-input-b64", cmd.PersistentFlags().Lookup("wake-input-b64")) cmd.AddCommand( newGatewayCommand(), + newWebCommand(), newDaemonCommand(), newShellCommand(), newDiagCommand(), diff --git a/internal/cli/web_command.go b/internal/cli/web_command.go new file mode 100644 index 00000000..68cb3ab1 --- /dev/null +++ b/internal/cli/web_command.go @@ -0,0 +1,362 @@ +package cli + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + "time" + + "github.com/spf13/cobra" +) + +type webCommandOptions struct { + HTTPAddress string + LogLevel string + StaticDir string + OpenBrowser bool + SkipBuild bool + Workdir string + TokenFile string +} + +// newWebCommand 创建并返回根命令下的 web 子命令,负责构建前端并启动带 Web UI 的 Gateway。 +func newWebCommand() *cobra.Command { + options := &webCommandOptions{} + + cmd := &cobra.Command{ + Use: "web", + Short: "Start NeoCode with Web UI", + Long: "Build frontend assets (if needed) and start the gateway with an integrated web UI.\nOpen http://127.0.0.1:8080 in your browser to use the interactive coding agent.", + SilenceUsage: true, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + options.Workdir = mustReadInheritedWorkdir(cmd) + return runWebCommand(cmd.Context(), *options) + }, + } + + cmd.Flags().StringVar(&options.HTTPAddress, "http-listen", "127.0.0.1:8080", "web UI listen address") + cmd.Flags().StringVar(&options.LogLevel, "log-level", "info", "gateway log level: debug|info|warn|error") + cmd.Flags().StringVar(&options.StaticDir, "static-dir", "", "frontend static files directory override") + cmd.Flags().BoolVar(&options.OpenBrowser, "open-browser", true, "open browser automatically") + cmd.Flags().BoolVar(&options.SkipBuild, "skip-build", false, "skip frontend build (error if dist/ missing)") + cmd.Flags().StringVar(&options.TokenFile, "token-file", "", "gateway auth token file path") + + return cmd +} + +// runWebCommand 执行 web 子命令:解析前端目录 → 构建前端(可选) → 启动 Gateway → 打开浏览器。 +func runWebCommand(ctx context.Context, options webCommandOptions) error { + logger := log.New(os.Stderr, "neocode-web: ", log.LstdFlags) + + // 如果未指定 workdir,默认使用当前工作目录 + if strings.TrimSpace(options.Workdir) == "" { + if cwd, err := os.Getwd(); err == nil { + options.Workdir = cwd + } + } + + // 1. 解析前端静态文件目录 + staticDir, err := resolveWebStaticDir(options.StaticDir) + needBuild := err != nil + + // 检查源码是否比 dist 更新(仅在 dist 存在且未指定 --static-dir 时) + if err == nil && options.StaticDir == "" { + webDir := findWebSourceDir() + if webDir != "" && isStaleFrontendBuild(webDir) { + logger.Println("frontend source is newer than build output, rebuilding...") + needBuild = true + } + } + + if needBuild { + if options.SkipBuild { + return fmt.Errorf("frontend needs rebuild and --skip-build is set") + } + webDir := findWebSourceDir() + if webDir == "" { + if err != nil { + return fmt.Errorf("frontend not found: %w", err) + } + return fmt.Errorf("web source directory not found; run from project root or set --static-dir") + } + if buildErr := buildFrontend(webDir, logger); buildErr != nil { + return fmt.Errorf("frontend build failed: %w", buildErr) + } + // 构建后重新解析 + staticDir, err = resolveWebStaticDir(options.StaticDir) + if err != nil { + return fmt.Errorf("frontend dist not found after build: %w", err) + } + } + logger.Printf("serving web UI from %s", staticDir) + + // 2. 启动 Gateway(复用共享启动逻辑,Web 模式跳过 IPC) + gatewayOpts := gatewayCommandOptions{ + HTTPAddress: resolveWebListenAddress(options.HTTPAddress), + LogLevel: options.LogLevel, + Workdir: options.Workdir, + TokenFile: options.TokenFile, + SkipIPC: true, + } + + // 网络服务器就绪后打开浏览器 + var onNetworkReady func(address string) + if options.OpenBrowser { + onNetworkReady = func(address string) { + go waitForGatewayAndOpenBrowser(ctx, address, logger) + } + } + + return startGatewayServer(ctx, gatewayOpts, staticDir, onNetworkReady) +} + +// resolveWebStaticDir 按 --static-dir → /web/dist → /web/dist 顺序查找前端静态文件。 +func resolveWebStaticDir(override string) (string, error) { + if override != "" { + return validateStaticDir(override) + } + + // 相对于当前工作目录(适用于 go run ./cmd/neocode web) + if dir, err := validateStaticDir(filepath.Join(".", "web", "dist")); err == nil { + return dir, nil + } + + // 相对于可执行文件(适用于安装的二进制) + if exe, err := os.Executable(); err == nil { + exeDir := filepath.Dir(exe) + if dir, err := validateStaticDir(filepath.Join(exeDir, "web", "dist")); err == nil { + return dir, nil + } + if dir, err := validateStaticDir(filepath.Join(exeDir, "..", "web", "dist")); err == nil { + return dir, nil + } + } + + return "", fmt.Errorf("web/dist not found; run from project root or build frontend with 'cd web && npm install && npm run build'") +} + +// validateStaticDir 验证目录存在且包含 index.html。 +func validateStaticDir(dir string) (string, error) { + absDir, err := filepath.Abs(dir) + if err != nil { + return "", err + } + info, err := os.Stat(filepath.Join(absDir, "index.html")) + if err != nil { + return "", fmt.Errorf("index.html not found in %s: %w", absDir, err) + } + if info.IsDir() { + return "", fmt.Errorf("index.html in %s is a directory", absDir) + } + return absDir, nil +} + +// findWebSourceDir 查找 web 源码目录(包含 package.json)。 +func findWebSourceDir() string { + candidates := []string{ + filepath.Join(".", "web"), + } + if exe, err := os.Executable(); err == nil { + exeDir := filepath.Dir(exe) + candidates = append(candidates, + filepath.Join(exeDir, "web"), + filepath.Join(exeDir, "..", "web"), + ) + } + for _, dir := range candidates { + if _, err := os.Stat(filepath.Join(dir, "package.json")); err == nil { + absDir, _ := filepath.Abs(dir) + return absDir + } + } + return "" +} + +// isStaleFrontendBuild 检查前端源码是否比 dist 构建产物更新。 +func isStaleFrontendBuild(webDir string) bool { + distIndex := filepath.Join(webDir, "dist", "index.html") + distInfo, err := os.Stat(distIndex) + if err != nil { + return true + } + distModTime := distInfo.ModTime() + + for _, f := range []string{ + filepath.Join(webDir, "package.json"), + filepath.Join(webDir, "vite.config.ts"), + filepath.Join(webDir, "tsconfig.json"), + } { + if info, statErr := os.Stat(f); statErr == nil && info.ModTime().After(distModTime) { + return true + } + } + + stale := false + filepath.Walk(filepath.Join(webDir, "src"), func(path string, info os.FileInfo, walkErr error) error { + if walkErr != nil || info.IsDir() { + return nil + } + if info.ModTime().After(distModTime) { + stale = true + return filepath.SkipAll + } + return nil + }) + return stale +} + +// buildFrontend 在 webDir 中执行 npm install && npm run build。 +func buildFrontend(webDir string, logger *log.Logger) error { + npmBin, err := findNPMBinary() + if err != nil { + return err + } + + logger.Printf("running npm install in %s ...", webDir) + installCmd := exec.Command(npmBin, "install") + installCmd.Dir = webDir + installCmd.Stdout = os.Stderr + installCmd.Stderr = os.Stderr + if err := installCmd.Run(); err != nil { + return fmt.Errorf("npm install failed: %w", err) + } + + logger.Println("running npm run build ...") + buildCmd := exec.Command(npmBin, "run", "build") + buildCmd.Dir = webDir + buildCmd.Stdout = os.Stderr + buildCmd.Stderr = os.Stderr + if err := buildCmd.Run(); err != nil { + return fmt.Errorf("npm run build failed: %w", err) + } + + // 验证构建产物 + distDir := filepath.Join(webDir, "dist") + if _, err := os.Stat(filepath.Join(distDir, "index.html")); err != nil { + return fmt.Errorf("build completed but dist/index.html not found: %w", err) + } + + logger.Println("frontend build completed successfully") + return nil +} + +// findNPMBinary 查找系统中的 npm 可执行文件。 +func findNPMBinary() (string, error) { + name := "npm" + if runtime.GOOS == "windows" { + name = "npm.cmd" + } + path, err := exec.LookPath(name) + if err != nil { + return "", fmt.Errorf("npm not found on PATH; install Node.js to build the frontend, or use --static-dir to specify pre-built assets") + } + return path, nil +} + +// waitForGatewayAndOpenBrowser 轮询 Gateway 健康检查,就绪后打开浏览器并附带认证 token。 +func waitForGatewayAndOpenBrowser(ctx context.Context, address string, logger *log.Logger) { + baseURL := fmt.Sprintf("http://%s", address) + token := readGatewayToken() + maxRetries := 30 + + for i := 0; i < maxRetries; i++ { + select { + case <-ctx.Done(): + return + default: + } + + resp, err := http.Get(baseURL + "/healthz") + if err == nil { + resp.Body.Close() + if resp.StatusCode == http.StatusOK { + browserURL := baseURL + if token != "" { + browserURL += "/?token=" + token + } + logger.Printf("gateway is ready, opening browser: %s", baseURL) + if openErr := openBrowser(browserURL); openErr != nil { + logger.Printf("failed to open browser: %v (open %s manually)", openErr, browserURL) + } + return + } + } + time.Sleep(500 * time.Millisecond) + } + logger.Printf("gateway health check timed out, open %s manually", baseURL) +} + +// readGatewayToken 从 ~/.neocode/auth.json 读取认证 token。 +func readGatewayToken() string { + homeDir, err := os.UserHomeDir() + if err != nil { + return "" + } + authPath := filepath.Join(homeDir, ".neocode", "auth.json") + data, err := os.ReadFile(authPath) + if err != nil { + return "" + } + var auth struct { + Token string `json:"token"` + } + if err := json.Unmarshal(data, &auth); err != nil { + return "" + } + return strings.TrimSpace(auth.Token) +} + +// openBrowser 使用系统默认浏览器打开 URL。 +func openBrowser(url string) error { + var cmd *exec.Cmd + switch runtime.GOOS { + case "darwin": + cmd = exec.Command("open", url) + case "windows": + cmd = exec.Command("cmd", "/c", "start", strings.ReplaceAll(url, "&", "^&")) + default: + cmd = exec.Command("xdg-open", url) + } + return cmd.Start() +} + +// resolveWebListenAddress 解析 Web 监听地址。如果默认端口不可用,自动尝试后续端口。 +func resolveWebListenAddress(preferred string) string { + host, portStr, err := net.SplitHostPort(strings.TrimSpace(preferred)) + if err != nil { + return preferred + } + port, err := strconv.Atoi(portStr) + if err != nil { + return preferred + } + + // 尝试首选端口 + addr := fmt.Sprintf("%s:%d", host, port) + if listener, err := net.Listen("tcp", addr); err == nil { + listener.Close() + return addr + } + + // 首选不可用,依次尝试后续端口 + for offset := 1; offset <= 10; offset++ { + candidate := fmt.Sprintf("%s:%d", host, port+offset) + if listener, err := net.Listen("tcp", candidate); err == nil { + listener.Close() + return candidate + } + } + + // 全部不可用,返回首选让后续报错 + return preferred +} diff --git a/internal/config/gateway.go b/internal/config/gateway.go index d8fb2ad2..9b92b78d 100644 --- a/internal/config/gateway.go +++ b/internal/config/gateway.go @@ -304,7 +304,7 @@ func (c GatewayObservabilityConfig) Enabled() bool { // defaultGatewayAllowOrigins 返回网关默认允许的本地来源。 func defaultGatewayAllowOrigins() []string { - return []string{"http://localhost", "http://127.0.0.1", "http://[::1]", "app://"} + return []string{"http://localhost", "http://127.0.0.1", "http://[::1]", "app://", "file://", "null"} } // normalizeGatewayAllowOrigins 归一化 allow_origins,去除空项与空白。 diff --git a/internal/config/provider.go b/internal/config/provider.go index 8a758d5d..061b8265 100644 --- a/internal/config/provider.go +++ b/internal/config/provider.go @@ -580,6 +580,7 @@ func newBuiltinOpenAICompatProvider(name, baseURL, model, apiKeyEnv string) Prov APIKeyEnv: apiKeyEnv, ChatAPIMode: provider.ChatAPIModeChatCompletions, ChatEndpointPath: "/chat/completions", + ModelSource: ModelSourceDiscover, Source: ProviderSourceBuiltin, } } @@ -601,6 +602,7 @@ func GeminiProvider() ProviderConfig { APIKeyEnv: GeminiDefaultAPIKeyEnv, ChatEndpointPath: "", Models: cloneBuiltinModels(geminiStaticModels), + ModelSource: ModelSourceDiscover, Source: ProviderSourceBuiltin, } } diff --git a/internal/config/state/model.go b/internal/config/state/model.go index a8c18c77..0fe27e4d 100644 --- a/internal/config/state/model.go +++ b/internal/config/state/model.go @@ -102,9 +102,7 @@ func catalogInputFromProvider(cfg config.ProviderConfig) (provider.CatalogInput, input := provider.CatalogInput{ Identity: identity, ConfiguredModels: providertypes.CloneModelDescriptors(cloned.Models), - DisableDiscovery: cloned.Source == config.ProviderSourceBuiltin || - (cloned.Source == config.ProviderSourceCustom && - config.NormalizeModelSource(cloned.ModelSource) == config.ModelSourceManual), + DisableDiscovery: config.NormalizeModelSource(cloned.ModelSource) == config.ModelSourceManual, ResolveDiscoveryConfig: func() (provider.RuntimeConfig, error) { resolved, err := cloned.Resolve() if err != nil { diff --git a/internal/config/state/model_test.go b/internal/config/state/model_test.go index 28e8c96c..0e153da0 100644 --- a/internal/config/state/model_test.go +++ b/internal/config/state/model_test.go @@ -9,7 +9,7 @@ import ( providertypes "neo-code/internal/provider/types" ) -func TestCatalogInputFromProviderBuiltinUsesStaticModelsAndDisablesDiscovery(t *testing.T) { +func TestCatalogInputFromProviderBuiltinUsesStaticModelsAndEnablesDiscovery(t *testing.T) { t.Setenv("CATALOG_PROVIDER_API_KEY", "secret-key") cfg := configpkg.ProviderConfig{ @@ -47,8 +47,8 @@ func TestCatalogInputFromProviderBuiltinUsesStaticModelsAndDisablesDiscovery(t * if len(input.ConfiguredModels) != 1 || input.ConfiguredModels[0].ID != "model-a" { t.Fatalf("expected configured models to be normalized, got %+v", input.ConfiguredModels) } - if !input.DisableDiscovery { - t.Fatal("expected builtin provider to disable discovery") + if input.DisableDiscovery { + t.Fatal("expected builtin provider to enable discovery") } cfg.Models[0].ID = "mutated" diff --git a/internal/gateway/bootstrap.go b/internal/gateway/bootstrap.go index 8e59d68f..20a09f33 100644 --- a/internal/gateway/bootstrap.go +++ b/internal/gateway/bootstrap.go @@ -3,6 +3,8 @@ package gateway import ( "bytes" "context" + crypto_rand "crypto/rand" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -249,9 +251,22 @@ func dispatchRunFrameWithSubjectID( InputText: strings.TrimSpace(frame.InputText), InputParts: append([]InputPart(nil), frame.InputParts...), Workdir: strings.TrimSpace(frame.Workdir), + Mode: strings.TrimSpace(frame.Mode), } frame.RunID = input.RunID + // new_session 模式:预生成 session ID,确保 ACK 和事件路由正确 + if frame.SkipSessionHydration && strings.TrimSpace(frame.SessionID) == "" { + newID := generateNewSessionID() + frame.SessionID = newID + input.SessionID = newID + if relay, ok := StreamRelayFromContext(ctx); ok { + if connID, connOK := ConnectionIDFromContext(ctx); connOK { + relay.AutoBindFromFrame(connID, frame) + } + } + } + runExecutionContext := deriveRuntimeExecutionContext(ctx) callCtx, cancel := withRuntimeOperationTimeout(runExecutionContext) frameSnapshot := frame @@ -1299,6 +1314,13 @@ func runtimeCallFailedFrame(ctx context.Context, frame MessageFrame, err error, return errorFrame(frame, NewFrameError(errorCode, message)) } +// generateNewSessionID 生成格式为 "session_<16hex>" 的随机会话 ID。 +func generateNewSessionID() string { + buf := make([]byte, 8) + _, _ = crypto_rand.Read(buf) + return "session_" + hex.EncodeToString(buf) +} + type bindStreamParams struct { SessionID string RunID string diff --git a/internal/gateway/contracts.go b/internal/gateway/contracts.go index f229769e..7de807f6 100644 --- a/internal/gateway/contracts.go +++ b/internal/gateway/contracts.go @@ -59,6 +59,8 @@ type RunInput struct { InputParts []InputPart // Workdir 是请求级工作目录覆盖值。 Workdir string + // Mode 是请求级 Agent 工作模式(build / plan)。 + Mode string } // CompactInput 表示网关向下游运行端口发起 compact 动作时的输入。 @@ -524,6 +526,8 @@ type Session struct { Provider string `json:"provider,omitempty"` // Model 是会话当前模型。 Model string `json:"model,omitempty"` + // AgentMode 是会话当前 Agent 工作模式。 + AgentMode string `json:"agent_mode,omitempty"` // Messages 是会话消息快照。 Messages []SessionMessage `json:"messages,omitempty"` } @@ -538,6 +542,8 @@ type SessionSummary struct { CreatedAt time.Time `json:"created_at"` // UpdatedAt 是会话更新时间。 UpdatedAt time.Time `json:"updated_at"` + // AgentMode 是会话当前 Agent 工作模式。 + AgentMode string `json:"agent_mode,omitempty"` } // TodoViewItem 描述会话 Todo 单项快照。 diff --git a/internal/gateway/coverage_boost_test.go b/internal/gateway/coverage_boost_test.go index 2015531e..499368d4 100644 --- a/internal/gateway/coverage_boost_test.go +++ b/internal/gateway/coverage_boost_test.go @@ -440,8 +440,8 @@ func TestStreamRelayMatchingAndLifecycleBranches(t *testing.T) { } func TestRPCDispatchAdditionalBranches(t *testing.T) { - if !requiresSession(FrameActionRun) { - t.Fatal("run should require session") + if requiresSession(FrameActionRun) { + t.Fatal("run should not require session at dispatch layer (runtime handles empty session_id)") } if requiresSession(FrameActionPing) { t.Fatal("ping should not require session") diff --git a/internal/gateway/multi_workspace_runtime.go b/internal/gateway/multi_workspace_runtime.go new file mode 100644 index 00000000..7f1e5e4a --- /dev/null +++ b/internal/gateway/multi_workspace_runtime.go @@ -0,0 +1,573 @@ +package gateway + +import ( + "context" + "fmt" + "log" + "strings" + "sync" + + agentsession "neo-code/internal/session" + "neo-code/internal/tools" +) + +// MultiWorkspaceRuntime 将多个工作区的 runtime 聚合为单个 gateway.RuntimePort。 +// 根据连接上下文中的 workspaceHash 路由到对应工作区的 runtime。 +type MultiWorkspaceRuntime struct { + index *agentsession.WorkspaceIndex + bundles map[string]*workspaceBundle + mu sync.RWMutex + buildPort func(ctx context.Context, workdir string) (RuntimePort, func() error, error) + defaultHash string + managementPort ManagementRuntimePort + + events chan RuntimeEvent + eventSubs map[string]chan<- RuntimeEvent + eventMu sync.Mutex + stopCh chan struct{} +} + +type workspaceBundle struct { + port RuntimePort + cleanup func() error +} + +// NewMultiWorkspaceRuntime 创建多工作区路由代理。 +// defaultHash 是默认工作区哈希(启动时传入的 workdir),在没有显式切换前使用。 +func NewMultiWorkspaceRuntime( + index *agentsession.WorkspaceIndex, + defaultHash string, + buildPort func(ctx context.Context, workdir string) (RuntimePort, func() error, error), +) *MultiWorkspaceRuntime { + return &MultiWorkspaceRuntime{ + index: index, + bundles: make(map[string]*workspaceBundle), + buildPort: buildPort, + defaultHash: strings.TrimSpace(defaultHash), + events: make(chan RuntimeEvent, 128), + eventSubs: make(map[string]chan<- RuntimeEvent), + stopCh: make(chan struct{}), + } +} + +// getPort 根据上下文中的 workspaceHash 获取对应工作区的 RuntimePort。 +// 若上下文中无 workspaceHash,则回退到 defaultHash。 +func (m *MultiWorkspaceRuntime) getPort(ctx context.Context) (RuntimePort, error) { + hash := WorkspaceHashFromContext(ctx) + if hash == "" { + hash = m.defaultHash + } + if hash == "" { + records := m.index.List() + if len(records) > 0 { + hash = records[0].Hash + } + } + if hash == "" { + return nil, fmt.Errorf("workspace hash is empty and no default configured") + } + return m.getPortForHash(hash) +} + +func (m *MultiWorkspaceRuntime) getPortForHash(hash string) (RuntimePort, error) { + m.mu.RLock() + b := m.bundles[hash] + m.mu.RUnlock() + if b != nil { + return b.port, nil + } + + m.mu.Lock() + defer m.mu.Unlock() + b = m.bundles[hash] + if b != nil { + return b.port, nil + } + + record, ok := m.index.Get(hash) + if !ok { + return nil, fmt.Errorf("workspace %s not found", hash) + } + + port, cleanup, err := m.buildPort(context.Background(), record.Path) + if err != nil { + return nil, fmt.Errorf("build workspace runtime for %s: %w", hash, err) + } + + b = &workspaceBundle{port: port, cleanup: cleanup} + m.bundles[hash] = b + m.startEventForwarder(hash, port) + return port, nil +} + +// PreloadWorkspaceBundle 预加载指定工作区的 bundle,避免默认工作区被重复构建。 +func (m *MultiWorkspaceRuntime) PreloadWorkspaceBundle(hash string, port RuntimePort, cleanup func() error) { + m.mu.Lock() + defer m.mu.Unlock() + if m.bundles[hash] != nil { + return + } + m.bundles[hash] = &workspaceBundle{port: port, cleanup: cleanup} + m.startEventForwarder(hash, port) +} + +func (m *MultiWorkspaceRuntime) startEventForwarder(hash string, port RuntimePort) { + src := port.Events() + if src == nil { + return + } + go func() { + for { + select { + case <-m.stopCh: + return + case ev, ok := <-src: + if !ok { + return + } + select { + case <-m.stopCh: + return + case m.events <- ev: + } + } + } + }() +} + +// Close 优雅关闭所有已加载的工作区 runtime。 +func (m *MultiWorkspaceRuntime) Close() error { + close(m.stopCh) + + m.mu.Lock() + bundles := make(map[string]*workspaceBundle, len(m.bundles)) + for k, v := range m.bundles { + bundles[k] = v + } + m.mu.Unlock() + + var firstErr error + for hash, b := range bundles { + if err := b.cleanup(); err != nil && firstErr == nil { + firstErr = fmt.Errorf("close workspace %s: %w", hash, err) + } + } + return firstErr +} + +// ---- Workspace management (non-RuntimePort) ---- + +// SwitchWorkspace 将指定连接的当前工作区切换到目标哈希。 +func (m *MultiWorkspaceRuntime) SwitchWorkspace(ctx context.Context, hash string) error { + _, ok := m.index.Get(hash) + if !ok { + return fmt.Errorf("workspace %s not found", hash) + } + // 预加载对应 runtime,确保后续请求可用 + if _, err := m.getPortForHash(hash); err != nil { + return err + } + return nil +} + +// ListWorkspaces 返回索引中的所有工作区。 +func (m *MultiWorkspaceRuntime) ListWorkspaces() []agentsession.WorkspaceRecord { + return m.index.List() +} + +// CreateWorkspace 注册一个新工作区到索引。 +func (m *MultiWorkspaceRuntime) CreateWorkspace(path, name string) (agentsession.WorkspaceRecord, error) { + record, err := m.index.Register(path, name) + if err != nil { + return agentsession.WorkspaceRecord{}, err + } + if saveErr := m.index.Save(); saveErr != nil { + log.Printf("workspace index save after create error: %v", saveErr) + } + return record, nil +} + +// RenameWorkspace 重命名工作区。 +func (m *MultiWorkspaceRuntime) RenameWorkspace(hash, name string) error { + if _, err := m.index.Rename(hash, name); err != nil { + return err + } + if saveErr := m.index.Save(); saveErr != nil { + log.Printf("workspace index save after rename error: %v", saveErr) + } + return nil +} + +// DeleteWorkspace 删除工作区。 +func (m *MultiWorkspaceRuntime) DeleteWorkspace(hash string, removeData bool) error { + _, err := m.index.Delete(hash, removeData) + if err != nil { + return err + } + + m.mu.Lock() + b, ok := m.bundles[hash] + if ok { + delete(m.bundles, hash) + } + m.mu.Unlock() + + if ok && b != nil && b.cleanup != nil { + if cerr := b.cleanup(); cerr != nil { + log.Printf("workspace cleanup %s error: %v", hash, cerr) + } + } + if saveErr := m.index.Save(); saveErr != nil { + log.Printf("workspace index save after delete error: %v", saveErr) + } + return nil +} + +// ---- RuntimePort implementation ---- + +func (m *MultiWorkspaceRuntime) Run(ctx context.Context, input RunInput) error { + port, err := m.getPort(ctx) + if err != nil { + return err + } + return port.Run(ctx, input) +} + +func (m *MultiWorkspaceRuntime) Compact(ctx context.Context, input CompactInput) (CompactResult, error) { + port, err := m.getPort(ctx) + if err != nil { + return CompactResult{}, err + } + return port.Compact(ctx, input) +} + +func (m *MultiWorkspaceRuntime) ExecuteSystemTool(ctx context.Context, input ExecuteSystemToolInput) (tools.ToolResult, error) { + port, err := m.getPort(ctx) + if err != nil { + return tools.ToolResult{}, err + } + return port.ExecuteSystemTool(ctx, input) +} + +func (m *MultiWorkspaceRuntime) ActivateSessionSkill(ctx context.Context, input SessionSkillMutationInput) error { + port, err := m.getPort(ctx) + if err != nil { + return err + } + return port.ActivateSessionSkill(ctx, input) +} + +func (m *MultiWorkspaceRuntime) DeactivateSessionSkill(ctx context.Context, input SessionSkillMutationInput) error { + port, err := m.getPort(ctx) + if err != nil { + return err + } + return port.DeactivateSessionSkill(ctx, input) +} + +func (m *MultiWorkspaceRuntime) ListSessionSkills(ctx context.Context, input ListSessionSkillsInput) ([]SessionSkillState, error) { + port, err := m.getPort(ctx) + if err != nil { + return nil, err + } + return port.ListSessionSkills(ctx, input) +} + +func (m *MultiWorkspaceRuntime) ListAvailableSkills(ctx context.Context, input ListAvailableSkillsInput) ([]AvailableSkillState, error) { + port, err := m.getPort(ctx) + if err != nil { + return nil, err + } + return port.ListAvailableSkills(ctx, input) +} + +func (m *MultiWorkspaceRuntime) ResolvePermission(ctx context.Context, input PermissionResolutionInput) error { + port, err := m.getPort(ctx) + if err != nil { + return err + } + return port.ResolvePermission(ctx, input) +} + +func (m *MultiWorkspaceRuntime) CancelRun(ctx context.Context, input CancelInput) (bool, error) { + port, err := m.getPort(ctx) + if err != nil { + return false, err + } + return port.CancelRun(ctx, input) +} + +func (m *MultiWorkspaceRuntime) Events() <-chan RuntimeEvent { + return m.events +} + +func (m *MultiWorkspaceRuntime) ListSessions(ctx context.Context) ([]SessionSummary, error) { + port, err := m.getPort(ctx) + if err != nil { + return nil, err + } + return port.ListSessions(ctx) +} + +func (m *MultiWorkspaceRuntime) LoadSession(ctx context.Context, input LoadSessionInput) (Session, error) { + port, err := m.getPort(ctx) + if err != nil { + return Session{}, err + } + return port.LoadSession(ctx, input) +} + +func (m *MultiWorkspaceRuntime) CreateSession(ctx context.Context, input CreateSessionInput) (string, error) { + port, err := m.getPort(ctx) + if err != nil { + return "", err + } + return port.CreateSession(ctx, input) +} + +func (m *MultiWorkspaceRuntime) DeleteSession(ctx context.Context, input DeleteSessionInput) (bool, error) { + port, err := m.getPort(ctx) + if err != nil { + return false, err + } + return port.DeleteSession(ctx, input) +} + +func (m *MultiWorkspaceRuntime) RenameSession(ctx context.Context, input RenameSessionInput) error { + port, err := m.getPort(ctx) + if err != nil { + return err + } + return port.RenameSession(ctx, input) +} + +func (m *MultiWorkspaceRuntime) ListFiles(ctx context.Context, input ListFilesInput) ([]FileEntry, error) { + port, err := m.getPort(ctx) + if err != nil { + return nil, err + } + return port.ListFiles(ctx, input) +} + +func (m *MultiWorkspaceRuntime) ListModels(ctx context.Context, input ListModelsInput) ([]ModelEntry, error) { + port, err := m.getPort(ctx) + if err != nil { + return nil, err + } + return port.ListModels(ctx, input) +} + +func (m *MultiWorkspaceRuntime) SetSessionModel(ctx context.Context, input SetSessionModelInput) error { + port, err := m.getPort(ctx) + if err != nil { + return err + } + return port.SetSessionModel(ctx, input) +} + +func (m *MultiWorkspaceRuntime) GetSessionModel(ctx context.Context, input GetSessionModelInput) (SessionModelResult, error) { + port, err := m.getPort(ctx) + if err != nil { + return SessionModelResult{}, err + } + return port.GetSessionModel(ctx, input) +} + +func (m *MultiWorkspaceRuntime) ListCheckpoints(ctx context.Context, input ListCheckpointsInput) ([]CheckpointEntry, error) { + port, err := m.getPort(ctx) + if err != nil { + return nil, err + } + return port.ListCheckpoints(ctx, input) +} + +func (m *MultiWorkspaceRuntime) RestoreCheckpoint(ctx context.Context, input CheckpointRestoreInput) (CheckpointRestoreResult, error) { + port, err := m.getPort(ctx) + if err != nil { + return CheckpointRestoreResult{}, err + } + return port.RestoreCheckpoint(ctx, input) +} + +func (m *MultiWorkspaceRuntime) UndoRestore(ctx context.Context, input UndoRestoreInput) (CheckpointRestoreResult, error) { + port, err := m.getPort(ctx) + if err != nil { + return CheckpointRestoreResult{}, err + } + return port.UndoRestore(ctx, input) +} + +func (m *MultiWorkspaceRuntime) CheckpointDiff(ctx context.Context, input CheckpointDiffInput) (CheckpointDiffResult, error) { + port, err := m.getPort(ctx) + if err != nil { + return CheckpointDiffResult{}, err + } + return port.CheckpointDiff(ctx, input) +} + +func (m *MultiWorkspaceRuntime) ListSessionTodos(ctx context.Context, input ListSessionTodosInput) (TodoSnapshot, error) { + port, err := m.getPort(ctx) + if err != nil { + return TodoSnapshot{}, err + } + return port.ListSessionTodos(ctx, input) +} + +func (m *MultiWorkspaceRuntime) GetRuntimeSnapshot(ctx context.Context, input GetRuntimeSnapshotInput) (RuntimeSnapshot, error) { + port, err := m.getPort(ctx) + if err != nil { + return RuntimeSnapshot{}, err + } + return port.GetRuntimeSnapshot(ctx, input) +} + +// SetManagementPort sets the management port for delegating ManagementRuntimePort calls. +func (m *MultiWorkspaceRuntime) SetManagementPort(port ManagementRuntimePort) { + m.mu.Lock() + defer m.mu.Unlock() + m.managementPort = port +} + +func (m *MultiWorkspaceRuntime) getManagementPort() (ManagementRuntimePort, error) { + m.mu.RLock() + mp := m.managementPort + m.mu.RUnlock() + if mp == nil { + return nil, fmt.Errorf("management port is unavailable") + } + return mp, nil +} + +// syncAllWorkspaceConfigs 让所有已加载工作区从磁盘重新加载配置快照。 +// 在管理操作写入配置后调用,确保运行时端口能看到最新状态。 +func (m *MultiWorkspaceRuntime) syncAllWorkspaceConfigs(ctx context.Context) { + m.mu.RLock() + bundles := make([]*workspaceBundle, 0, len(m.bundles)) + for _, b := range m.bundles { + bundles = append(bundles, b) + } + m.mu.RUnlock() + + for _, b := range bundles { + type configReloader interface { + ReloadConfig(ctx context.Context) error + } + if reloader, ok := b.port.(configReloader); ok { + _ = reloader.ReloadConfig(ctx) + } + } +} + +// syncAllWorkspaceMCP 让所有已加载工作区根据当前配置重建 MCP Server 注册表。 +func (m *MultiWorkspaceRuntime) syncAllWorkspaceMCP() { + m.mu.RLock() + bundles := make([]*workspaceBundle, 0, len(m.bundles)) + for _, b := range m.bundles { + bundles = append(bundles, b) + } + m.mu.RUnlock() + + for _, b := range bundles { + type mcpRebuilder interface { + RebuildMCPServers() error + } + if rebuilder, ok := b.port.(mcpRebuilder); ok { + _ = rebuilder.RebuildMCPServers() + } + } +} + +// ---- ManagementRuntimePort implementation ---- + +func (m *MultiWorkspaceRuntime) ListProviders(ctx context.Context, input ListProvidersInput) ([]ProviderOption, error) { + mp, err := m.getManagementPort() + if err != nil { + return nil, err + } + return mp.ListProviders(ctx, input) +} + +func (m *MultiWorkspaceRuntime) CreateProvider(ctx context.Context, input CreateProviderInput) (ProviderSelectionResult, error) { + mp, err := m.getManagementPort() + if err != nil { + return ProviderSelectionResult{}, err + } + result, err := mp.CreateProvider(ctx, input) + if err != nil { + return ProviderSelectionResult{}, err + } + m.syncAllWorkspaceConfigs(ctx) + return result, nil +} + +func (m *MultiWorkspaceRuntime) DeleteProvider(ctx context.Context, input DeleteProviderInput) error { + mp, err := m.getManagementPort() + if err != nil { + return err + } + if err := mp.DeleteProvider(ctx, input); err != nil { + return err + } + m.syncAllWorkspaceConfigs(ctx) + return nil +} + +func (m *MultiWorkspaceRuntime) SelectProviderModel(ctx context.Context, input SelectProviderModelInput) (ProviderSelectionResult, error) { + mp, err := m.getManagementPort() + if err != nil { + return ProviderSelectionResult{}, err + } + result, err := mp.SelectProviderModel(ctx, input) + if err != nil { + return ProviderSelectionResult{}, err + } + m.syncAllWorkspaceConfigs(ctx) + return result, nil +} + +func (m *MultiWorkspaceRuntime) ListMCPServers(ctx context.Context, input ListMCPServersInput) ([]MCPServerEntry, error) { + mp, err := m.getManagementPort() + if err != nil { + return nil, err + } + return mp.ListMCPServers(ctx, input) +} + +func (m *MultiWorkspaceRuntime) UpsertMCPServer(ctx context.Context, input UpsertMCPServerInput) error { + mp, err := m.getManagementPort() + if err != nil { + return err + } + if err := mp.UpsertMCPServer(ctx, input); err != nil { + return err + } + m.syncAllWorkspaceConfigs(ctx) + m.syncAllWorkspaceMCP() + return nil +} + +func (m *MultiWorkspaceRuntime) SetMCPServerEnabled(ctx context.Context, input SetMCPServerEnabledInput) error { + mp, err := m.getManagementPort() + if err != nil { + return err + } + if err := mp.SetMCPServerEnabled(ctx, input); err != nil { + return err + } + m.syncAllWorkspaceConfigs(ctx) + m.syncAllWorkspaceMCP() + return nil +} + +func (m *MultiWorkspaceRuntime) DeleteMCPServer(ctx context.Context, input DeleteMCPServerInput) error { + mp, err := m.getManagementPort() + if err != nil { + return err + } + if err := mp.DeleteMCPServer(ctx, input); err != nil { + return err + } + m.syncAllWorkspaceConfigs(ctx) + m.syncAllWorkspaceMCP() + return nil +} diff --git a/internal/gateway/multi_workspace_runtime_test.go b/internal/gateway/multi_workspace_runtime_test.go new file mode 100644 index 00000000..b0702eed --- /dev/null +++ b/internal/gateway/multi_workspace_runtime_test.go @@ -0,0 +1,678 @@ +package gateway + +import ( + "context" + "errors" + "os" + "path/filepath" + "sync" + "sync/atomic" + "testing" + "time" + + agentsession "neo-code/internal/session" + "neo-code/internal/tools" +) + +// recordingPort is a RuntimePort fake that tracks which port handled each call. +// It satisfies all RuntimePort methods so we can verify MultiWorkspaceRuntime +// routes by workspaceHash to the correct underlying bundle. +type recordingPort struct { + id string + events chan RuntimeEvent + + runCalls atomic.Int32 + listSessionsCalls atomic.Int32 + executeSysCalls atomic.Int32 + cancelCalls atomic.Int32 + closed atomic.Int32 + closeOnce sync.Once + + closeErr error +} + +func newRecordingPort(id string) *recordingPort { + return &recordingPort{ + id: id, + events: make(chan RuntimeEvent, 8), + } +} + +func (p *recordingPort) cleanup() error { + p.closeOnce.Do(func() { + p.closed.Add(1) + close(p.events) + }) + return p.closeErr +} + +func (p *recordingPort) Run(_ context.Context, _ RunInput) error { + p.runCalls.Add(1) + return nil +} + +func (p *recordingPort) Compact(_ context.Context, _ CompactInput) (CompactResult, error) { + return CompactResult{}, nil +} + +func (p *recordingPort) ExecuteSystemTool(_ context.Context, _ ExecuteSystemToolInput) (tools.ToolResult, error) { + p.executeSysCalls.Add(1) + return tools.ToolResult{Content: p.id}, nil +} + +func (p *recordingPort) ActivateSessionSkill(_ context.Context, _ SessionSkillMutationInput) error { + return nil +} + +func (p *recordingPort) DeactivateSessionSkill(_ context.Context, _ SessionSkillMutationInput) error { + return nil +} + +func (p *recordingPort) ListSessionSkills(_ context.Context, _ ListSessionSkillsInput) ([]SessionSkillState, error) { + return nil, nil +} + +func (p *recordingPort) ListAvailableSkills(_ context.Context, _ ListAvailableSkillsInput) ([]AvailableSkillState, error) { + return nil, nil +} + +func (p *recordingPort) ResolvePermission(_ context.Context, _ PermissionResolutionInput) error { + return nil +} + +func (p *recordingPort) CancelRun(_ context.Context, _ CancelInput) (bool, error) { + p.cancelCalls.Add(1) + return true, nil +} + +func (p *recordingPort) Events() <-chan RuntimeEvent { + return p.events +} + +func (p *recordingPort) ListSessions(_ context.Context) ([]SessionSummary, error) { + p.listSessionsCalls.Add(1) + return []SessionSummary{{ID: p.id}}, nil +} + +func (p *recordingPort) LoadSession(_ context.Context, _ LoadSessionInput) (Session, error) { + return Session{ID: p.id}, nil +} + +func (p *recordingPort) ListSessionTodos(_ context.Context, _ ListSessionTodosInput) (TodoSnapshot, error) { + return TodoSnapshot{}, nil +} + +func (p *recordingPort) GetRuntimeSnapshot(_ context.Context, _ GetRuntimeSnapshotInput) (RuntimeSnapshot, error) { + return RuntimeSnapshot{SessionID: p.id}, nil +} + +func (p *recordingPort) CreateSession(_ context.Context, _ CreateSessionInput) (string, error) { + return p.id, nil +} + +func (p *recordingPort) DeleteSession(_ context.Context, _ DeleteSessionInput) (bool, error) { + return true, nil +} + +func (p *recordingPort) RenameSession(_ context.Context, _ RenameSessionInput) error { + return nil +} + +func (p *recordingPort) ListFiles(_ context.Context, _ ListFilesInput) ([]FileEntry, error) { + return nil, nil +} + +func (p *recordingPort) ListModels(_ context.Context, _ ListModelsInput) ([]ModelEntry, error) { + return nil, nil +} + +func (p *recordingPort) SetSessionModel(_ context.Context, _ SetSessionModelInput) error { + return nil +} + +func (p *recordingPort) GetSessionModel(_ context.Context, _ GetSessionModelInput) (SessionModelResult, error) { + return SessionModelResult{}, nil +} + +func (p *recordingPort) ListCheckpoints(_ context.Context, _ ListCheckpointsInput) ([]CheckpointEntry, error) { + return nil, nil +} + +func (p *recordingPort) RestoreCheckpoint(_ context.Context, _ CheckpointRestoreInput) (CheckpointRestoreResult, error) { + return CheckpointRestoreResult{}, nil +} + +func (p *recordingPort) UndoRestore(_ context.Context, _ UndoRestoreInput) (CheckpointRestoreResult, error) { + return CheckpointRestoreResult{}, nil +} + +func (p *recordingPort) CheckpointDiff(_ context.Context, _ CheckpointDiffInput) (CheckpointDiffResult, error) { + return CheckpointDiffResult{}, nil +} + +// ManagementRuntimePort stubs +func (p *recordingPort) ListProviders(_ context.Context, _ ListProvidersInput) ([]ProviderOption, error) { + return nil, nil +} +func (p *recordingPort) CreateProvider(_ context.Context, _ CreateProviderInput) (ProviderSelectionResult, error) { + return ProviderSelectionResult{}, nil +} +func (p *recordingPort) DeleteProvider(_ context.Context, _ DeleteProviderInput) error { + return nil +} +func (p *recordingPort) SelectProviderModel(_ context.Context, _ SelectProviderModelInput) (ProviderSelectionResult, error) { + return ProviderSelectionResult{}, nil +} +func (p *recordingPort) ListMCPServers(_ context.Context, _ ListMCPServersInput) ([]MCPServerEntry, error) { + return nil, nil +} +func (p *recordingPort) UpsertMCPServer(_ context.Context, _ UpsertMCPServerInput) error { + return nil +} +func (p *recordingPort) SetMCPServerEnabled(_ context.Context, _ SetMCPServerEnabledInput) error { + return nil +} +func (p *recordingPort) DeleteMCPServer(_ context.Context, _ DeleteMCPServerInput) error { + return nil +} + +// testBuilder captures the workdirs that buildPort was invoked with so tests can +// distinguish lazy builds from cache hits. +type testBuilder struct { + mu sync.Mutex + workdirs []string + ports map[string]*recordingPort + buildErr error + buildErrs map[string]error +} + +func newTestBuilder() *testBuilder { + return &testBuilder{ + ports: make(map[string]*recordingPort), + buildErrs: make(map[string]error), + } +} + +func (b *testBuilder) build(_ context.Context, workdir string) (RuntimePort, func() error, error) { + b.mu.Lock() + defer b.mu.Unlock() + + if b.buildErr != nil { + return nil, nil, b.buildErr + } + if err, ok := b.buildErrs[workdir]; ok { + return nil, nil, err + } + + b.workdirs = append(b.workdirs, workdir) + port := newRecordingPort(workdir) + b.ports[workdir] = port + return port, port.cleanup, nil +} + +func (b *testBuilder) callCount() int { + b.mu.Lock() + defer b.mu.Unlock() + return len(b.workdirs) +} + +func (b *testBuilder) portFor(workdir string) *recordingPort { + b.mu.Lock() + defer b.mu.Unlock() + return b.ports[workdir] +} + +// setupIndex registers two workspaces (alpha, beta) under a temp baseDir and +// returns the index along with the registered records. +func setupIndex(t *testing.T) (*agentsession.WorkspaceIndex, agentsession.WorkspaceRecord, agentsession.WorkspaceRecord) { + t.Helper() + base := t.TempDir() + alphaDir := filepath.Join(base, "alpha") + betaDir := filepath.Join(base, "beta") + for _, d := range []string{alphaDir, betaDir} { + if err := os.MkdirAll(d, 0o755); err != nil { + t.Fatalf("mkdir %s: %v", d, err) + } + } + idx := agentsession.NewWorkspaceIndex(base) + alpha, err := idx.Register(alphaDir, "alpha") + if err != nil { + t.Fatalf("register alpha: %v", err) + } + beta, err := idx.Register(betaDir, "beta") + if err != nil { + t.Fatalf("register beta: %v", err) + } + return idx, alpha, beta +} + +func ctxWithHash(t *testing.T, hash string) context.Context { + t.Helper() + state := NewConnectionWorkspaceState() + state.SetWorkspaceHash(hash) + return WithConnectionWorkspaceState(context.Background(), state) +} + +func TestMultiWorkspaceRuntime_DefaultHashRouting(t *testing.T) { + idx, alpha, _ := setupIndex(t) + builder := newTestBuilder() + + mw := NewMultiWorkspaceRuntime(idx, alpha.Hash, builder.build) + t.Cleanup(func() { _ = mw.Close() }) + + // Bare context (no workspace hash): falls back to defaultHash → alpha. + if _, err := mw.ListSessions(context.Background()); err != nil { + t.Fatalf("ListSessions: %v", err) + } + if got := builder.callCount(); got != 1 { + t.Fatalf("buildPort calls = %d, want 1 lazy build for default hash", got) + } + port := builder.portFor(alpha.Path) + if port == nil { + t.Fatalf("expected port built for path %q", alpha.Path) + } + if got := port.listSessionsCalls.Load(); got != 1 { + t.Fatalf("alpha port listSessions calls = %d, want 1", got) + } +} + +func TestMultiWorkspaceRuntime_NoHashConfigured(t *testing.T) { + idx := agentsession.NewWorkspaceIndex(t.TempDir()) + builder := newTestBuilder() + + // No defaultHash, no context hash → must error. + mw := NewMultiWorkspaceRuntime(idx, "", builder.build) + t.Cleanup(func() { _ = mw.Close() }) + + if _, err := mw.ListSessions(context.Background()); err == nil { + t.Fatalf("expected error when no hash is configured") + } + if got := builder.callCount(); got != 0 { + t.Fatalf("buildPort should not be called when no hash, got %d", got) + } +} + +func TestMultiWorkspaceRuntime_ContextHashOverridesDefault(t *testing.T) { + idx, alpha, beta := setupIndex(t) + builder := newTestBuilder() + + mw := NewMultiWorkspaceRuntime(idx, alpha.Hash, builder.build) + t.Cleanup(func() { _ = mw.Close() }) + + // Context hash points to beta — defaultHash is alpha but should be ignored. + if _, err := mw.ListSessions(ctxWithHash(t, beta.Hash)); err != nil { + t.Fatalf("ListSessions on beta: %v", err) + } + if builder.portFor(alpha.Path) != nil { + t.Fatalf("alpha port should not be built when ctx hash is beta") + } + betaPort := builder.portFor(beta.Path) + if betaPort == nil { + t.Fatalf("beta port should be built") + } + if got := betaPort.listSessionsCalls.Load(); got != 1 { + t.Fatalf("beta listSessions = %d, want 1", got) + } + + // Subsequent call on alpha builds a separate bundle. + if _, err := mw.ListSessions(ctxWithHash(t, alpha.Hash)); err != nil { + t.Fatalf("ListSessions on alpha: %v", err) + } + if builder.portFor(alpha.Path) == nil { + t.Fatalf("alpha port should now be built") + } + if got := builder.callCount(); got != 2 { + t.Fatalf("buildPort calls = %d, want 2 (one per workspace)", got) + } +} + +func TestMultiWorkspaceRuntime_CachesBundle(t *testing.T) { + idx, alpha, _ := setupIndex(t) + builder := newTestBuilder() + + mw := NewMultiWorkspaceRuntime(idx, alpha.Hash, builder.build) + t.Cleanup(func() { _ = mw.Close() }) + + for i := 0; i < 4; i++ { + if _, err := mw.ListSessions(ctxWithHash(t, alpha.Hash)); err != nil { + t.Fatalf("ListSessions[%d]: %v", i, err) + } + } + if got := builder.callCount(); got != 1 { + t.Fatalf("buildPort calls = %d, want 1 (cached)", got) + } + if got := builder.portFor(alpha.Path).listSessionsCalls.Load(); got != 4 { + t.Fatalf("listSessions calls = %d, want 4", got) + } +} + +func TestMultiWorkspaceRuntime_UnknownHashErrors(t *testing.T) { + idx, alpha, _ := setupIndex(t) + builder := newTestBuilder() + + mw := NewMultiWorkspaceRuntime(idx, alpha.Hash, builder.build) + t.Cleanup(func() { _ = mw.Close() }) + + _, err := mw.ListSessions(ctxWithHash(t, "deadbeef")) + if err == nil { + t.Fatalf("expected error for unknown hash") + } + if got := builder.callCount(); got != 0 { + t.Fatalf("buildPort should not be invoked for unknown hash; got %d", got) + } +} + +func TestMultiWorkspaceRuntime_BuildErrorPropagates(t *testing.T) { + idx, alpha, _ := setupIndex(t) + builder := newTestBuilder() + builder.buildErrs[alpha.Path] = errors.New("boom") + + mw := NewMultiWorkspaceRuntime(idx, alpha.Hash, builder.build) + t.Cleanup(func() { _ = mw.Close() }) + + _, err := mw.ListSessions(ctxWithHash(t, alpha.Hash)) + if err == nil { + t.Fatalf("expected build error to surface") + } +} + +func TestMultiWorkspaceRuntime_PreloadSkipsRebuild(t *testing.T) { + idx, alpha, _ := setupIndex(t) + builder := newTestBuilder() + + mw := NewMultiWorkspaceRuntime(idx, alpha.Hash, builder.build) + t.Cleanup(func() { _ = mw.Close() }) + + pre := newRecordingPort("preloaded") + mw.PreloadWorkspaceBundle(alpha.Hash, pre, pre.cleanup) + + if _, err := mw.ListSessions(ctxWithHash(t, alpha.Hash)); err != nil { + t.Fatalf("ListSessions: %v", err) + } + if got := builder.callCount(); got != 0 { + t.Fatalf("buildPort should not be called for preloaded bundle; got %d", got) + } + if got := pre.listSessionsCalls.Load(); got != 1 { + t.Fatalf("preloaded port listSessions = %d, want 1", got) + } +} + +func TestMultiWorkspaceRuntime_PreloadIgnoresDuplicate(t *testing.T) { + idx, alpha, _ := setupIndex(t) + builder := newTestBuilder() + mw := NewMultiWorkspaceRuntime(idx, alpha.Hash, builder.build) + t.Cleanup(func() { _ = mw.Close() }) + + first := newRecordingPort("first") + second := newRecordingPort("second") + mw.PreloadWorkspaceBundle(alpha.Hash, first, first.cleanup) + mw.PreloadWorkspaceBundle(alpha.Hash, second, second.cleanup) + + if _, err := mw.ListSessions(ctxWithHash(t, alpha.Hash)); err != nil { + t.Fatalf("ListSessions: %v", err) + } + if got := first.listSessionsCalls.Load(); got != 1 { + t.Fatalf("first port should still be active, calls = %d", got) + } + if got := second.listSessionsCalls.Load(); got != 0 { + t.Fatalf("second preload must be ignored, calls = %d", got) + } +} + +func TestMultiWorkspaceRuntime_SwitchWorkspaceValidates(t *testing.T) { + idx, alpha, beta := setupIndex(t) + builder := newTestBuilder() + mw := NewMultiWorkspaceRuntime(idx, alpha.Hash, builder.build) + t.Cleanup(func() { _ = mw.Close() }) + + if err := mw.SwitchWorkspace(context.Background(), beta.Hash); err != nil { + t.Fatalf("SwitchWorkspace: %v", err) + } + // SwitchWorkspace pre-loads the target's runtime. + if builder.portFor(beta.Path) == nil { + t.Fatalf("beta port should be built after SwitchWorkspace") + } + + if err := mw.SwitchWorkspace(context.Background(), "missing"); err == nil { + t.Fatalf("SwitchWorkspace should reject unknown hash") + } +} + +func TestMultiWorkspaceRuntime_CreatePersistsIndex(t *testing.T) { + idx, alpha, _ := setupIndex(t) + builder := newTestBuilder() + mw := NewMultiWorkspaceRuntime(idx, alpha.Hash, builder.build) + t.Cleanup(func() { _ = mw.Close() }) + + newDir := t.TempDir() + rec, err := mw.CreateWorkspace(newDir, "Gamma") + if err != nil { + t.Fatalf("CreateWorkspace: %v", err) + } + if rec.Name != "Gamma" { + t.Fatalf("name = %q, want Gamma", rec.Name) + } + + // Index must persist on Save. baseDir of the index is alpha.Path's parent. + persisted := agentsession.NewWorkspaceIndex(filepath.Dir(alpha.Path)) + if err := persisted.Load(); err != nil { + t.Fatalf("reload: %v", err) + } + if _, ok := persisted.Get(rec.Hash); !ok { + t.Fatalf("CreateWorkspace did not persist index entry %s", rec.Hash) + } +} + +func TestMultiWorkspaceRuntime_RenameAndDeletePersist(t *testing.T) { + idx, alpha, beta := setupIndex(t) + builder := newTestBuilder() + mw := NewMultiWorkspaceRuntime(idx, alpha.Hash, builder.build) + t.Cleanup(func() { _ = mw.Close() }) + + if err := mw.RenameWorkspace(beta.Hash, "Beta-Renamed"); err != nil { + t.Fatalf("Rename: %v", err) + } + + // Trigger lazy build of beta then delete it. + if _, err := mw.ListSessions(ctxWithHash(t, beta.Hash)); err != nil { + t.Fatalf("ListSessions beta: %v", err) + } + betaPort := builder.portFor(beta.Path) + if betaPort == nil { + t.Fatalf("beta port not built") + } + + if err := mw.DeleteWorkspace(beta.Hash, false); err != nil { + t.Fatalf("Delete: %v", err) + } + // Bundle cleanup should have been invoked. + deadline := time.Now().Add(time.Second) + for time.Now().Before(deadline) { + if betaPort.closed.Load() == 1 { + break + } + time.Sleep(10 * time.Millisecond) + } + if got := betaPort.closed.Load(); got != 1 { + t.Fatalf("beta cleanup should be invoked, got closed=%d", got) + } + + // Persisted index reflects rename + delete. + persisted := agentsession.NewWorkspaceIndex(filepath.Dir(alpha.Path)) + if err := persisted.Load(); err != nil { + t.Fatalf("reload: %v", err) + } + if _, ok := persisted.Get(beta.Hash); ok { + t.Fatalf("beta should be removed from persisted index") + } + if _, ok := persisted.Get(alpha.Hash); !ok { + t.Fatalf("alpha should remain in persisted index") + } +} + +func TestMultiWorkspaceRuntime_DeleteUnknownErrors(t *testing.T) { + idx, alpha, _ := setupIndex(t) + builder := newTestBuilder() + mw := NewMultiWorkspaceRuntime(idx, alpha.Hash, builder.build) + t.Cleanup(func() { _ = mw.Close() }) + + if err := mw.DeleteWorkspace("missing", false); err == nil { + t.Fatalf("expected error for unknown delete") + } +} + +func TestMultiWorkspaceRuntime_CloseCleansAllBundles(t *testing.T) { + idx, alpha, beta := setupIndex(t) + builder := newTestBuilder() + mw := NewMultiWorkspaceRuntime(idx, alpha.Hash, builder.build) + + if _, err := mw.ListSessions(ctxWithHash(t, alpha.Hash)); err != nil { + t.Fatalf("alpha lazy build: %v", err) + } + if _, err := mw.ListSessions(ctxWithHash(t, beta.Hash)); err != nil { + t.Fatalf("beta lazy build: %v", err) + } + + if err := mw.Close(); err != nil { + t.Fatalf("Close: %v", err) + } + for _, ws := range []agentsession.WorkspaceRecord{alpha, beta} { + port := builder.portFor(ws.Path) + if port == nil { + t.Fatalf("port not built for %s", ws.Path) + } + if got := port.closed.Load(); got != 1 { + t.Fatalf("port %s closed=%d, want 1", ws.Path, got) + } + } +} + +func TestMultiWorkspaceRuntime_CloseReturnsFirstError(t *testing.T) { + idx, alpha, _ := setupIndex(t) + builder := newTestBuilder() + mw := NewMultiWorkspaceRuntime(idx, alpha.Hash, builder.build) + + if _, err := mw.ListSessions(ctxWithHash(t, alpha.Hash)); err != nil { + t.Fatalf("lazy build: %v", err) + } + port := builder.portFor(alpha.Path) + port.closeErr = errors.New("disk full") + + err := mw.Close() + if err == nil { + t.Fatalf("expected close error to surface") + } +} + +func TestMultiWorkspaceRuntime_EventsForwarded(t *testing.T) { + idx, alpha, _ := setupIndex(t) + builder := newTestBuilder() + mw := NewMultiWorkspaceRuntime(idx, alpha.Hash, builder.build) + t.Cleanup(func() { _ = mw.Close() }) + + // Trigger lazy build so the forwarder goroutine attaches. + if _, err := mw.ListSessions(ctxWithHash(t, alpha.Hash)); err != nil { + t.Fatalf("lazy build: %v", err) + } + port := builder.portFor(alpha.Path) + + go func() { + port.events <- RuntimeEvent{Type: "session.update", SessionID: "s-1"} + }() + + select { + case ev := <-mw.Events(): + if ev.SessionID != "s-1" { + t.Fatalf("expected session_id s-1, got %q", ev.SessionID) + } + case <-time.After(2 * time.Second): + t.Fatalf("event not forwarded within 2s") + } +} + +func TestMultiWorkspaceRuntime_RoutingMatrix(t *testing.T) { + idx, alpha, beta := setupIndex(t) + builder := newTestBuilder() + mw := NewMultiWorkspaceRuntime(idx, alpha.Hash, builder.build) + t.Cleanup(func() { _ = mw.Close() }) + + alphaCtx := ctxWithHash(t, alpha.Hash) + betaCtx := ctxWithHash(t, beta.Hash) + + // Run() routes by ctx. + if err := mw.Run(alphaCtx, RunInput{}); err != nil { + t.Fatalf("Run alpha: %v", err) + } + if err := mw.Run(betaCtx, RunInput{}); err != nil { + t.Fatalf("Run beta: %v", err) + } + // CancelRun + if _, err := mw.CancelRun(betaCtx, CancelInput{}); err != nil { + t.Fatalf("CancelRun beta: %v", err) + } + // ExecuteSystemTool + if _, err := mw.ExecuteSystemTool(alphaCtx, ExecuteSystemToolInput{}); err != nil { + t.Fatalf("ExecuteSystemTool alpha: %v", err) + } + + alphaPort := builder.portFor(alpha.Path) + betaPort := builder.portFor(beta.Path) + if alphaPort == nil || betaPort == nil { + t.Fatalf("ports not built; alpha=%v beta=%v", alphaPort, betaPort) + } + if got := alphaPort.runCalls.Load(); got != 1 { + t.Fatalf("alpha Run calls = %d, want 1", got) + } + if got := betaPort.runCalls.Load(); got != 1 { + t.Fatalf("beta Run calls = %d, want 1", got) + } + if got := betaPort.cancelCalls.Load(); got != 1 { + t.Fatalf("beta cancel calls = %d, want 1", got) + } + if got := alphaPort.executeSysCalls.Load(); got != 1 { + t.Fatalf("alpha ExecuteSystemTool calls = %d, want 1", got) + } +} + +func TestMultiWorkspaceRuntime_ListWorkspacesMatchesIndex(t *testing.T) { + idx, alpha, beta := setupIndex(t) + builder := newTestBuilder() + mw := NewMultiWorkspaceRuntime(idx, alpha.Hash, builder.build) + t.Cleanup(func() { _ = mw.Close() }) + + got := mw.ListWorkspaces() + if len(got) != 2 { + t.Fatalf("expected 2 workspaces, got %d", len(got)) + } + have := map[string]bool{} + for _, r := range got { + have[r.Hash] = true + } + if !have[alpha.Hash] || !have[beta.Hash] { + t.Fatalf("missing workspace; got %v", have) + } +} + +// guard against future drift: MultiWorkspaceRuntime must implement RuntimePort and ManagementRuntimePort. +var _ RuntimePort = (*MultiWorkspaceRuntime)(nil) +var _ ManagementRuntimePort = (*MultiWorkspaceRuntime)(nil) + +// guard helper: ensure recordingPort builds correctly under sync access. +func TestRecordingPort_Concurrent(t *testing.T) { + p := newRecordingPort("c") + wg := sync.WaitGroup{} + for i := 0; i < 16; i++ { + wg.Add(1) + go func() { + defer wg.Done() + _, _ = p.ListSessions(context.Background()) + }() + } + wg.Wait() + if got := p.listSessionsCalls.Load(); got != 16 { + t.Fatalf("expected 16 calls, got %d", got) + } + if err := p.cleanup(); err != nil { + t.Fatalf("cleanup: %v", err) + } +} diff --git a/internal/gateway/network_server.go b/internal/gateway/network_server.go index 6241c590..ef99fa66 100644 --- a/internal/gateway/network_server.go +++ b/internal/gateway/network_server.go @@ -66,7 +66,9 @@ type NetworkServerOptions struct { AllowedOrigins []string // ConnectionCountChanged 在活跃长连接数变化时回调当前总数,用于空闲退出治理。 ConnectionCountChanged func(active int) - listenFn func(network, address string) (net.Listener, error) + // StaticFileDir 可选:如果非空,从该目录提供 SPA 静态文件服务。 + StaticFileDir string + listenFn func(network, address string) (net.Listener, error) } // NetworkServer 提供 HTTP/WebSocket/SSE 网络访问面的统一入口服务。 @@ -87,7 +89,8 @@ type NetworkServer struct { metrics *GatewayMetrics allowedOrigins []string connectionCountChanged func(active int) - startedAt time.Time + staticFileDir string + startedAt time.Time mu sync.Mutex server *http.Server @@ -185,7 +188,8 @@ func NewNetworkServer(options NetworkServerOptions) (*NetworkServer, error) { metrics: metrics, allowedOrigins: allowedOrigins, connectionCountChanged: options.ConnectionCountChanged, - startedAt: time.Now().UTC(), + staticFileDir: options.StaticFileDir, + startedAt: time.Now().UTC(), wsConns: make(map[*websocket.Conn]context.CancelFunc), sseCancels: make(map[int]context.CancelFunc), }, nil @@ -361,12 +365,20 @@ func (s *NetworkServer) buildHandler(runtimePort RuntimePort) http.Handler { mux.HandleFunc("/sse", func(writer http.ResponseWriter, request *http.Request) { s.handleSSERequest(writer, request, runtimePort) }) + if s.staticFileDir != "" { + return WithStaticFileHandler(mux, s.staticFileDir, s.logger) + } return mux } // withCORS 为网络入口注入 CORS 头,仅对白名单 Origin 回显允许值。 +// WebSocket 升级请求不受 CORS 约束,直接放行交予 WS 握手阶段的 Origin 校验。 func (s *NetworkServer) withCORS(next http.Handler) http.Handler { return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + if strings.EqualFold(request.Header.Get("Upgrade"), "websocket") { + next.ServeHTTP(writer, request) + return + } origin := strings.TrimSpace(request.Header.Get("Origin")) if origin != "" { if !s.isAllowedOrigin(origin) { @@ -926,6 +938,7 @@ func (s *NetworkServer) decorateRequestContext(base context.Context, source Requ ctx := WithRequestSource(base, source) authState := NewConnectionAuthState() ctx = WithConnectionAuthState(ctx, authState) + ctx = WithConnectionWorkspaceState(ctx, NewConnectionWorkspaceState()) trimmedToken := strings.TrimSpace(token) if trimmedToken != "" { @@ -1099,6 +1112,7 @@ func (s *NetworkServer) isAllowedOrigin(origin string) bool { } // validateWebSocketOrigin 在握手阶段基于实例 allowlist 校验 WebSocket 来源。 +// Electron 的 file:// 协议产生 opaque origin (null),此类来源不受 CORS allowlist 约束。 func (s *NetworkServer) validateWebSocketOrigin(request *http.Request) error { if request == nil { return errors.New("invalid websocket request") @@ -1107,14 +1121,24 @@ func (s *NetworkServer) validateWebSocketOrigin(request *http.Request) error { if origin == "" { return nil } + if isElectronCompatibleOrigin(origin) { + return nil + } if !s.isAllowedOrigin(origin) { return fmt.Errorf("websocket origin %q is not allowed", origin) } return nil } +// isElectronCompatibleOrigin 放行 Electron/Chromium 的 file:// 协议来源, +// 包括 "null"(opaque origin 的序列化值)和任何以 file:// 开头的 Origin。 +func isElectronCompatibleOrigin(origin string) bool { + normalized := strings.ToLower(strings.TrimSpace(origin)) + return normalized == "null" || strings.HasPrefix(normalized, "file://") +} + func defaultControlPlaneOrigins() []string { - return []string{"http://localhost", "http://127.0.0.1", "http://[::1]", "app://"} + return []string{"http://localhost", "http://127.0.0.1", "http://[::1]", "app://", "file://", "null"} } func normalizeControlPlaneOrigins(origins []string) []string { diff --git a/internal/gateway/network_server_test.go b/internal/gateway/network_server_test.go index f3659708..96301525 100644 --- a/internal/gateway/network_server_test.go +++ b/internal/gateway/network_server_test.go @@ -58,6 +58,8 @@ func TestOriginAllowlist(t *testing.T) { "http://[::1]:3000", "http://[::1]", "app://desktop-client", + "file://local", + "null", } for _, origin := range allowed { if !isAllowedControlPlaneOrigin(origin) { @@ -69,7 +71,6 @@ func TestOriginAllowlist(t *testing.T) { "", "https://localhost:3000", "http://evil.example.com", - "file://local", } for _, origin := range disallowed { if isAllowedControlPlaneOrigin(origin) { diff --git a/internal/gateway/protocol/jsonrpc.go b/internal/gateway/protocol/jsonrpc.go index 12fab386..d1b675e4 100644 --- a/internal/gateway/protocol/jsonrpc.go +++ b/internal/gateway/protocol/jsonrpc.go @@ -79,7 +79,12 @@ const ( MethodGatewayEvent = "gateway.event" // MethodWakeOpenURL 表示 URL Scheme 唤醒方法。 MethodWakeOpenURL = "wake.openUrl" -) + MethodGatewayListWorkspaces = "gateway.listWorkspaces" + MethodGatewayCreateWorkspace = "gateway.createWorkspace" + MethodGatewaySwitchWorkspace = "gateway.switchWorkspace" + MethodGatewayRenameWorkspace = "gateway.renameWorkspace" + MethodGatewayDeleteWorkspace = "gateway.deleteWorkspace" + ) const ( // JSONRPCCodeParseError 表示请求体不是合法 JSON。 @@ -161,6 +166,7 @@ type NormalizedRequest struct { SessionID string RunID string Workdir string + Mode string Payload any } @@ -193,10 +199,12 @@ type RunInputPart struct { // RunParams 表示 gateway.run 的参数载荷。 type RunParams struct { SessionID string `json:"session_id,omitempty"` + NewSession bool `json:"new_session,omitempty"` RunID string `json:"run_id,omitempty"` InputText string `json:"input_text,omitempty"` InputParts []RunInputPart `json:"input_parts,omitempty"` Workdir string `json:"workdir,omitempty"` + Mode string `json:"mode,omitempty"` } // CancelParams 表示 gateway.cancel 可选参数。 @@ -382,6 +390,32 @@ type DeleteMCPServerParams struct { ID string `json:"id"` } +// ListWorkspacesParams 表示 gateway.listWorkspaces 参数。 +type ListWorkspacesParams struct{} + +// CreateWorkspaceParams 表示 gateway.createWorkspace 参数。 +type CreateWorkspaceParams struct { + Path string `json:"path"` + Name string `json:"name,omitempty"` +} + +// SwitchWorkspaceParams 表示 gateway.switchWorkspace 参数。 +type SwitchWorkspaceParams struct { + WorkspaceHash string `json:"workspace_hash"` +} + +// RenameWorkspaceParams 表示 gateway.renameWorkspace 参数。 +type RenameWorkspaceParams struct { + WorkspaceHash string `json:"workspace_hash"` + Name string `json:"name"` +} + +// DeleteWorkspaceParams 表示 gateway.deleteWorkspace 参数。 +type DeleteWorkspaceParams struct { + WorkspaceHash string `json:"workspace_hash"` + RemoveData bool `json:"remove_data,omitempty"` +} + // NormalizeJSONRPCRequest 将 JSON-RPC 请求归一化为内部请求模型,并做方法级参数解析。 func NormalizeJSONRPCRequest(request JSONRPCRequest) (NormalizedRequest, *JSONRPCError) { normalized := NormalizedRequest{} @@ -441,6 +475,7 @@ func NormalizeJSONRPCRequest(request JSONRPCRequest) (NormalizedRequest, *JSONRP normalized.SessionID = strings.TrimSpace(params.SessionID) normalized.RunID = strings.TrimSpace(params.RunID) normalized.Workdir = strings.TrimSpace(params.Workdir) + normalized.Mode = strings.TrimSpace(params.Mode) normalized.Payload = params return normalized, nil case MethodGatewayCompact: @@ -667,6 +702,41 @@ func NormalizeJSONRPCRequest(request JSONRPCRequest) (NormalizedRequest, *JSONRP normalized.Workdir = strings.TrimSpace(intent.Workdir) normalized.Payload = intent return normalized, nil + case MethodGatewayListWorkspaces: + normalized.Action = "workspace.list" + return normalized, nil + case MethodGatewayCreateWorkspace: + params, parseErr := decodeCreateWorkspaceParams(request.Params) + if parseErr != nil { + return normalized, parseErr + } + normalized.Action = "workspace.create" + normalized.Payload = params + return normalized, nil + case MethodGatewaySwitchWorkspace: + params, parseErr := decodeSwitchWorkspaceParams(request.Params) + if parseErr != nil { + return normalized, parseErr + } + normalized.Action = "workspace.switch" + normalized.Payload = params + return normalized, nil + case MethodGatewayRenameWorkspace: + params, parseErr := decodeRenameWorkspaceParams(request.Params) + if parseErr != nil { + return normalized, parseErr + } + normalized.Action = "workspace.rename" + normalized.Payload = params + return normalized, nil + case MethodGatewayDeleteWorkspace: + params, parseErr := decodeDeleteWorkspaceParams(request.Params) + if parseErr != nil { + return normalized, parseErr + } + normalized.Action = "workspace.delete" + normalized.Payload = params + return normalized, nil default: return normalized, NewJSONRPCError( JSONRPCCodeMethodNotFound, @@ -1216,6 +1286,58 @@ func decodeDeleteMCPServerParams(raw json.RawMessage) (DeleteMCPServerParams, *J } // decodeParams 是各 decodeXxxParams 的泛型骨架:检查空值、严格反序列化、执行校验。 + +func decodeListWorkspacesParams(raw json.RawMessage) (ListWorkspacesParams, *JSONRPCError) { + return decodeParamsOptional(raw, "gateway.listWorkspaces", func(p *ListWorkspacesParams) *JSONRPCError { + return nil + }) +} + +func decodeCreateWorkspaceParams(raw json.RawMessage) (CreateWorkspaceParams, *JSONRPCError) { + return decodeParams(raw, "gateway.createWorkspace", func(p *CreateWorkspaceParams) *JSONRPCError { + p.Path = strings.TrimSpace(p.Path) + p.Name = strings.TrimSpace(p.Name) + if p.Path == "" { + return NewJSONRPCError(JSONRPCCodeInvalidParams, "missing required field: params.path", GatewayCodeMissingRequiredField) + } + return nil + }) +} + +func decodeSwitchWorkspaceParams(raw json.RawMessage) (SwitchWorkspaceParams, *JSONRPCError) { + return decodeParams(raw, "gateway.switchWorkspace", func(p *SwitchWorkspaceParams) *JSONRPCError { + p.WorkspaceHash = strings.TrimSpace(p.WorkspaceHash) + if p.WorkspaceHash == "" { + return NewJSONRPCError(JSONRPCCodeInvalidParams, "missing required field: params.workspace_hash", GatewayCodeMissingRequiredField) + } + return nil + }) +} + +func decodeRenameWorkspaceParams(raw json.RawMessage) (RenameWorkspaceParams, *JSONRPCError) { + return decodeParams(raw, "gateway.renameWorkspace", func(p *RenameWorkspaceParams) *JSONRPCError { + p.WorkspaceHash = strings.TrimSpace(p.WorkspaceHash) + p.Name = strings.TrimSpace(p.Name) + if p.WorkspaceHash == "" { + return NewJSONRPCError(JSONRPCCodeInvalidParams, "missing required field: params.workspace_hash", GatewayCodeMissingRequiredField) + } + if p.Name == "" { + return NewJSONRPCError(JSONRPCCodeInvalidParams, "missing required field: params.name", GatewayCodeMissingRequiredField) + } + return nil + }) +} + +func decodeDeleteWorkspaceParams(raw json.RawMessage) (DeleteWorkspaceParams, *JSONRPCError) { + return decodeParams(raw, "gateway.deleteWorkspace", func(p *DeleteWorkspaceParams) *JSONRPCError { + p.WorkspaceHash = strings.TrimSpace(p.WorkspaceHash) + if p.WorkspaceHash == "" { + return NewJSONRPCError(JSONRPCCodeInvalidParams, "missing required field: params.workspace_hash", GatewayCodeMissingRequiredField) + } + return nil + }) +} + func decodeParams[T any](raw json.RawMessage, name string, validate func(*T) *JSONRPCError) (T, *JSONRPCError) { return decodeParamsInternal(raw, name, validate, false) } diff --git a/internal/gateway/rpc_dispatch.go b/internal/gateway/rpc_dispatch.go index e7ef5311..18a6c186 100644 --- a/internal/gateway/rpc_dispatch.go +++ b/internal/gateway/rpc_dispatch.go @@ -62,6 +62,7 @@ func dispatchRPCRequest(ctx context.Context, request protocol.JSONRPCRequest, ru SessionID: normalized.SessionID, RunID: normalized.RunID, Workdir: normalized.Workdir, + Mode: normalized.Mode, Payload: normalized.Payload, } @@ -259,6 +260,10 @@ func hydrateFrameSessionFromConnection(ctx context.Context, frame MessageFrame) if strings.TrimSpace(frame.SessionID) != "" { return frame } + // new_session 请求跳过绑定回填,由下游创建全新会话 + if frame.SkipSessionHydration { + return frame + } payloadSessionID := strings.TrimSpace(extractSessionIDFromPayload(frame.Payload)) if payloadSessionID != "" { @@ -295,6 +300,11 @@ func hydrateFrameRunPayload(frame MessageFrame) MessageFrame { return frame } + // new_session=true 时跳过连接绑定的 session_id 回填,让后端创建全新会话 + if params.NewSession { + frame.SkipSessionHydration = true + } + if strings.TrimSpace(frame.SessionID) == "" { frame.SessionID = strings.TrimSpace(params.SessionID) } @@ -304,6 +314,9 @@ func hydrateFrameRunPayload(frame MessageFrame) MessageFrame { if strings.TrimSpace(frame.Workdir) == "" { frame.Workdir = strings.TrimSpace(params.Workdir) } + if strings.TrimSpace(frame.Mode) == "" { + frame.Mode = strings.TrimSpace(params.Mode) + } if strings.TrimSpace(frame.InputText) == "" { frame.InputText = strings.TrimSpace(params.InputText) } @@ -341,7 +354,6 @@ func convertProtocolRunInputParts(parts []protocol.RunInputPart) []InputPart { func requiresSession(action FrameAction) bool { switch action { case FrameActionBindStream, - FrameActionRun, FrameActionCompact, FrameActionLoadSession, FrameActionListSessionTodos, diff --git a/internal/gateway/rpc_dispatch_test.go b/internal/gateway/rpc_dispatch_test.go index 0edd72a3..65ba32b3 100644 --- a/internal/gateway/rpc_dispatch_test.go +++ b/internal/gateway/rpc_dispatch_test.go @@ -486,7 +486,7 @@ func TestDispatchRPCRequestMissingSessionAndAuthHelpers(t *testing.T) { } } -func TestDispatchRPCRequestRunMissingSessionAtDispatchLayer(t *testing.T) { +func TestDispatchRPCRequestRunDoesNotRequireSessionAtDispatchLayer(t *testing.T) { metrics := NewGatewayMetrics() ctx := WithRequestSource(context.Background(), RequestSourceHTTP) ctx = WithGatewayMetrics(ctx, metrics) @@ -494,15 +494,13 @@ func TestDispatchRPCRequestRunMissingSessionAtDispatchLayer(t *testing.T) { response := dispatchRPCRequest(ctx, protocol.JSONRPCRequest{ JSONRPC: protocol.JSONRPCVersion, - ID: json.RawMessage(`"req-run-missing-session"`), + ID: json.RawMessage(`"req-run-no-session"`), Method: protocol.MethodGatewayRun, Params: json.RawMessage(`{"input_text":"hello"}`), }, &runtimePortCompileStub{}) - if response.Error == nil { - t.Fatal("expected missing session error at dispatch layer") - } - if gatewayCode := protocol.GatewayCodeFromJSONRPCError(response.Error); gatewayCode != protocol.GatewayCodeMissingRequiredField { - t.Fatalf("gateway_code = %q, want %q", gatewayCode, protocol.GatewayCodeMissingRequiredField) + // run 不再在 dispatch 层要求 session_id;runtime 的 loadOrCreateSession 处理空值 + if response.Error != nil { + t.Fatalf("run should not require session_id at dispatch layer, got error: %v", response.Error) } } diff --git a/internal/gateway/security.go b/internal/gateway/security.go index 48284612..033e9347 100644 --- a/internal/gateway/security.go +++ b/internal/gateway/security.go @@ -76,6 +76,11 @@ func fullControlPlaneMethods() map[string]struct{} { "gateway.upsertMCPServer", "gateway.setMCPServerEnabled", "gateway.deleteMCPServer", + "gateway.listWorkspaces", + "gateway.createWorkspace", + "gateway.switchWorkspace", + "gateway.renameWorkspace", + "gateway.deleteWorkspace", "wake.openUrl", } return normalizedMethodSet(methods...) diff --git a/internal/gateway/server.go b/internal/gateway/server.go index 015bde93..31adeee5 100644 --- a/internal/gateway/server.go +++ b/internal/gateway/server.go @@ -324,6 +324,7 @@ func (s *Server) handleConnection(ctx context.Context, conn net.Conn, runtimePor connectionContext = WithStreamRelay(connectionContext, relay) connectionContext = WithRequestSource(connectionContext, RequestSourceIPC) connectionContext = WithConnectionAuthState(connectionContext, NewConnectionAuthState()) + connectionContext = WithConnectionWorkspaceState(connectionContext, NewConnectionWorkspaceState()) if s.authenticator != nil { connectionContext = WithTokenAuthenticator(connectionContext, s.authenticator) } diff --git a/internal/gateway/static_files.go b/internal/gateway/static_files.go new file mode 100644 index 00000000..d1139af2 --- /dev/null +++ b/internal/gateway/static_files.go @@ -0,0 +1,88 @@ +package gateway + +import ( + "log" + "net/http" + "os" + "path" + "path/filepath" + "strings" +) + +// knownAPIPrefixes 定义属于 Gateway API 的路径前缀,静态文件中间件不会拦截这些路径。 +var knownAPIPrefixes = map[string]bool{ + "/healthz": true, + "/version": true, + "/rpc": true, + "/ws": true, + "/sse": true, + "/metrics": true, + "/metrics.json": true, +} + +// WithStaticFileHandler 返回一个 http.Handler,将 API 请求转发给 apiHandler, +// 其余请求从 staticDir 提供静态文件。对于 SPA 路由,不存在的路径会回退到 index.html。 +func WithStaticFileHandler(apiHandler http.Handler, staticDir string, logger *log.Logger) http.Handler { + return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + cleanPath := path.Clean("/" + request.URL.Path) + + // API 路径直接转发 + if isAPIPath(cleanPath) { + apiHandler.ServeHTTP(writer, request) + return + } + + // 根路径 → index.html + relPath := strings.TrimPrefix(cleanPath, "/") + if relPath == "" { + relPath = "index.html" + } + + // 检查文件是否存在 + fullPath := filepath.Join(staticDir, filepath.FromSlash(relPath)) + info, err := os.Stat(fullPath) + if err == nil && !info.IsDir() { + setCacheHeaders(writer, relPath) + http.ServeFile(writer, request, fullPath) + return + } + + // SPA fallback:文件不存在时返回 index.html + indexPath := filepath.Join(staticDir, "index.html") + if _, statErr := os.Stat(indexPath); statErr == nil { + writer.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") + http.ServeFile(writer, request, indexPath) + return + } + + if logger != nil { + logger.Printf("static files: index.html not found in %s", staticDir) + } + http.NotFound(writer, request) + }) +} + +// isAPIPath 判断请求路径是否属于 Gateway API。 +func isAPIPath(cleanPath string) bool { + if knownAPIPrefixes[cleanPath] { + return true + } + for prefix := range knownAPIPrefixes { + if strings.HasPrefix(cleanPath, prefix+"/") { + return true + } + } + return false +} + +// setCacheHeaders 根据文件名设置缓存策略。 +// Vite hashed assets(如 assets/index-BzA30N4.js)使用 immutable 缓存, +// 其他文件使用 no-cache。 +func setCacheHeaders(writer http.ResponseWriter, relPath string) { + base := path.Base(relPath) + if strings.Contains(base, "-") && !strings.HasSuffix(base, ".html") { + writer.Header().Set("Cache-Control", "public, max-age=31536000, immutable") + } else { + writer.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") + } +} diff --git a/internal/gateway/stream_relay.go b/internal/gateway/stream_relay.go index 930201e4..584422af 100644 --- a/internal/gateway/stream_relay.go +++ b/internal/gateway/stream_relay.go @@ -489,6 +489,31 @@ func (r *StreamRelay) RefreshConnectionBindings(connectionID ConnectionID) bool return refreshed } +// ClearConnectionBindings 清除指定连接的所有 session 绑定,用于工作区切换等场景。 +func (r *StreamRelay) ClearConnectionBindings(connectionID ConnectionID) { + if r == nil { + return + } + + normalizedConnectionID := NormalizeConnectionID(connectionID) + if normalizedConnectionID == "" { + return + } + + r.mu.Lock() + connectionBindingMap := r.connectionBindings[normalizedConnectionID] + for key, state := range connectionBindingMap { + if state != nil { + r.removeConnectionFromIndexesLocked(normalizedConnectionID, state.sessionID, state.runID) + } + delete(connectionBindingMap, key) + } + if len(connectionBindingMap) == 0 { + delete(r.connectionBindings, normalizedConnectionID) + } + r.mu.Unlock() +} + // AutoBindFromFrame 根据请求帧中的 session/run 信息执行自动续绑。 func (r *StreamRelay) AutoBindFromFrame(connectionID ConnectionID, frame MessageFrame) { if r == nil { diff --git a/internal/gateway/types.go b/internal/gateway/types.go index 53e5db8d..dde345b0 100644 --- a/internal/gateway/types.go +++ b/internal/gateway/types.go @@ -88,6 +88,16 @@ const ( FrameActionUndoRestore FrameAction = "checkpoint_undo_restore" // FrameActionCheckpointDiff 表示查询两个相邻代码检查点之间的差异。 FrameActionCheckpointDiff FrameAction = "checkpoint_diff" + // FrameActionWorkspaceList 表示列出所有工作区。 + FrameActionWorkspaceList FrameAction = "workspace.list" + // FrameActionWorkspaceCreate 表示创建/注册一个新工作区。 + FrameActionWorkspaceCreate FrameAction = "workspace.create" + // FrameActionWorkspaceSwitch 表示切换当前连接的工作区。 + FrameActionWorkspaceSwitch FrameAction = "workspace.switch" + // FrameActionWorkspaceRename 表示重命名工作区。 + FrameActionWorkspaceRename FrameAction = "workspace.rename" + // FrameActionWorkspaceDelete 表示删除工作区。 + FrameActionWorkspaceDelete FrameAction = "workspace.delete" ) // InputPartType 表示多模态输入分片类型。 @@ -146,8 +156,12 @@ type MessageFrame struct { InputParts []InputPart `json:"input_parts,omitempty"` // Workdir 是本次请求的工作目录覆盖值。 Workdir string `json:"workdir,omitempty"` + // Mode 是本次请求的 Agent 工作模式(build / plan)。 + Mode string `json:"mode,omitempty"` // Payload 是动作扩展负载或事件负载。 Payload any `json:"payload,omitempty"` // Error 是错误帧负载。 Error *FrameError `json:"error,omitempty"` + // SkipSessionHydration 为 true 时跳过基于连接绑定的 session_id 回填,用于新建会话场景。 + SkipSessionHydration bool `json:"-"` } diff --git a/internal/gateway/validate.go b/internal/gateway/validate.go index e8748f42..96562924 100644 --- a/internal/gateway/validate.go +++ b/internal/gateway/validate.go @@ -73,6 +73,16 @@ func validateRequestFrame(frame MessageFrame) *FrameError { } case FrameActionListFiles: // listFiles 不强制 session_id,workdir 可独立使用 + case FrameActionWorkspaceList: + return nil + case FrameActionWorkspaceCreate, + FrameActionWorkspaceSwitch, + FrameActionWorkspaceRename, + FrameActionWorkspaceDelete: + if frame.Payload == nil { + return NewMissingRequiredFieldError("payload") + } + return nil case FrameActionResolvePermission: return validateResolvePermissionFrame(frame) default: @@ -442,7 +452,12 @@ func isValidFrameAction(action FrameAction) bool { FrameActionListMCPServers, FrameActionUpsertMCPServer, FrameActionSetMCPServerEnabled, - FrameActionDeleteMCPServer: + FrameActionDeleteMCPServer, + FrameActionWorkspaceList, + FrameActionWorkspaceCreate, + FrameActionWorkspaceSwitch, + FrameActionWorkspaceRename, + FrameActionWorkspaceDelete: return true default: return false diff --git a/internal/gateway/workspace_context.go b/internal/gateway/workspace_context.go new file mode 100644 index 00000000..61dfc4c9 --- /dev/null +++ b/internal/gateway/workspace_context.go @@ -0,0 +1,69 @@ +package gateway + +import ( + "context" + "strings" + "sync" +) + +type workspaceHashContextKey struct{} +type connectionWorkspaceStateContextKey struct{} + +// ConnectionWorkspaceState 维护连接当前活跃的工作区哈希。 +type ConnectionWorkspaceState struct { + mu sync.RWMutex + workspaceHash string +} + +// NewConnectionWorkspaceState 创建连接工作区状态对象。 +func NewConnectionWorkspaceState() *ConnectionWorkspaceState { + return &ConnectionWorkspaceState{} +} + +// SetWorkspaceHash 设置当前连接的工作区哈希。 +func (s *ConnectionWorkspaceState) SetWorkspaceHash(hash string) { + if s == nil { + return + } + s.mu.Lock() + s.workspaceHash = strings.TrimSpace(hash) + s.mu.Unlock() +} + +// GetWorkspaceHash 返回当前连接的工作区哈希。 +func (s *ConnectionWorkspaceState) GetWorkspaceHash() string { + if s == nil { + return "" + } + s.mu.RLock() + defer s.mu.RUnlock() + return s.workspaceHash +} + +// WithConnectionWorkspaceState 将连接工作区状态注入上下文。 +func WithConnectionWorkspaceState(ctx context.Context, state *ConnectionWorkspaceState) context.Context { + if ctx == nil { + ctx = context.Background() + } + return context.WithValue(ctx, connectionWorkspaceStateContextKey{}, state) +} + +// ConnectionWorkspaceStateFromContext 从上下文读取连接工作区状态。 +func ConnectionWorkspaceStateFromContext(ctx context.Context) (*ConnectionWorkspaceState, bool) { + if ctx == nil { + return nil, false + } + state, ok := ctx.Value(connectionWorkspaceStateContextKey{}).(*ConnectionWorkspaceState) + if !ok || state == nil { + return nil, false + } + return state, true +} + +// WorkspaceHashFromContext 从上下文读取当前工作区哈希(快捷方法)。 +func WorkspaceHashFromContext(ctx context.Context) string { + if state, ok := ConnectionWorkspaceStateFromContext(ctx); ok { + return state.GetWorkspaceHash() + } + return "" +} diff --git a/internal/gateway/workspace_handlers.go b/internal/gateway/workspace_handlers.go new file mode 100644 index 00000000..ffcfc93f --- /dev/null +++ b/internal/gateway/workspace_handlers.go @@ -0,0 +1,273 @@ +package gateway + +import ( + "context" + "strings" + + "neo-code/internal/gateway/protocol" +) + +func init() { + MustRegisterAction(FrameActionWorkspaceList, handleWorkspaceListFrame) + MustRegisterAction(FrameActionWorkspaceCreate, handleWorkspaceCreateFrame) + MustRegisterAction(FrameActionWorkspaceSwitch, handleWorkspaceSwitchFrame) + MustRegisterAction(FrameActionWorkspaceRename, handleWorkspaceRenameFrame) + MustRegisterAction(FrameActionWorkspaceDelete, handleWorkspaceDeleteFrame) +} + +func requireMultiWorkspaceRuntime(port RuntimePort) (*MultiWorkspaceRuntime, *FrameError) { + mw, ok := port.(*MultiWorkspaceRuntime) + if !ok { + return nil, NewFrameError(ErrorCodeUnsupportedAction, "multi-workspace runtime is not available") + } + return mw, nil +} + +func handleWorkspaceListFrame(ctx context.Context, frame MessageFrame, runtimePort RuntimePort) MessageFrame { + mw, err := requireMultiWorkspaceRuntime(runtimePort) + if err != nil { + return errorFrame(frame, err) + } + + records := mw.ListWorkspaces() + payload := make([]map[string]any, 0, len(records)) + for _, r := range records { + payload = append(payload, map[string]any{ + "hash": r.Hash, + "path": r.Path, + "name": r.Name, + "created_at": r.CreatedAt, + "updated_at": r.UpdatedAt, + }) + } + + return MessageFrame{ + Type: FrameTypeAck, + Action: FrameActionWorkspaceList, + RequestID: frame.RequestID, + Payload: map[string]any{"workspaces": payload}, + } +} + +func handleWorkspaceCreateFrame(ctx context.Context, frame MessageFrame, runtimePort RuntimePort) MessageFrame { + mw, mwErr := requireMultiWorkspaceRuntime(runtimePort) + if mwErr != nil { + return errorFrame(frame, mwErr) + } + + params, err := decodeWorkspaceCreatePayload(frame.Payload) + if err != nil { + return errorFrame(frame, err) + } + + record, createErr := mw.CreateWorkspace(params.Path, params.Name) + if createErr != nil { + return errorFrame(frame, NewFrameError(ErrorCodeInternalError, createErr.Error())) + } + + return MessageFrame{ + Type: FrameTypeAck, + Action: FrameActionWorkspaceCreate, + RequestID: frame.RequestID, + Payload: map[string]any{ + "workspace": map[string]any{ + "hash": record.Hash, + "path": record.Path, + "name": record.Name, + "created_at": record.CreatedAt, + "updated_at": record.UpdatedAt, + }, + }, + } +} + +func handleWorkspaceSwitchFrame(ctx context.Context, frame MessageFrame, runtimePort RuntimePort) MessageFrame { + mw, mwErr := requireMultiWorkspaceRuntime(runtimePort) + if mwErr != nil { + return errorFrame(frame, mwErr) + } + + params, err := decodeWorkspaceSwitchPayload(frame.Payload) + if err != nil { + return errorFrame(frame, err) + } + + if switchErr := mw.SwitchWorkspace(ctx, params.Hash); switchErr != nil { + return errorFrame(frame, NewFrameError(ErrorCodeInternalError, switchErr.Error())) + } + + // 更新连接级工作区状态 + if wsState, ok := ConnectionWorkspaceStateFromContext(ctx); ok { + wsState.SetWorkspaceHash(params.Hash) + } + + // 清除旧工作区的 session 绑定,避免 fallback 到旧 session + if relay, ok := StreamRelayFromContext(ctx); ok { + if connID, connOK := ConnectionIDFromContext(ctx); connOK { + relay.ClearConnectionBindings(connID) + } + } + + return MessageFrame{ + Type: FrameTypeAck, + Action: FrameActionWorkspaceSwitch, + RequestID: frame.RequestID, + Payload: map[string]any{"workspace_hash": params.Hash}, + } +} + +func handleWorkspaceRenameFrame(ctx context.Context, frame MessageFrame, runtimePort RuntimePort) MessageFrame { + mw, mwErr := requireMultiWorkspaceRuntime(runtimePort) + if mwErr != nil { + return errorFrame(frame, mwErr) + } + + params, err := decodeWorkspaceRenamePayload(frame.Payload) + if err != nil { + return errorFrame(frame, err) + } + + if renameErr := mw.RenameWorkspace(params.Hash, params.Name); renameErr != nil { + return errorFrame(frame, NewFrameError(ErrorCodeInternalError, renameErr.Error())) + } + + return MessageFrame{ + Type: FrameTypeAck, + Action: FrameActionWorkspaceRename, + RequestID: frame.RequestID, + Payload: map[string]any{"hash": params.Hash, "name": params.Name}, + } +} + +func handleWorkspaceDeleteFrame(ctx context.Context, frame MessageFrame, runtimePort RuntimePort) MessageFrame { + mw, mwErr := requireMultiWorkspaceRuntime(runtimePort) + if mwErr != nil { + return errorFrame(frame, mwErr) + } + + params, err := decodeWorkspaceDeletePayload(frame.Payload) + if err != nil { + return errorFrame(frame, err) + } + + if deleteErr := mw.DeleteWorkspace(params.Hash, params.RemoveData); deleteErr != nil { + return errorFrame(frame, NewFrameError(ErrorCodeInternalError, deleteErr.Error())) + } + + return MessageFrame{ + Type: FrameTypeAck, + Action: FrameActionWorkspaceDelete, + RequestID: frame.RequestID, + Payload: map[string]any{"hash": params.Hash}, + } +} + +// ---- payload decode ---- + +type workspaceCreateParams struct { + Path string + Name string +} + +type workspaceSwitchParams struct { + Hash string +} + +type workspaceRenameParams struct { + Hash string + Name string +} + +type workspaceDeleteParams struct { + Hash string + RemoveData bool +} + +func decodeWorkspaceCreatePayload(payload any) (workspaceCreateParams, *FrameError) { + switch typed := payload.(type) { + case protocol.CreateWorkspaceParams: + return workspaceCreateParams{Path: strings.TrimSpace(typed.Path), Name: strings.TrimSpace(typed.Name)}, nil + case *protocol.CreateWorkspaceParams: + if typed == nil { + return workspaceCreateParams{}, NewMissingRequiredFieldError("payload.path") + } + return workspaceCreateParams{Path: strings.TrimSpace(typed.Path), Name: strings.TrimSpace(typed.Name)}, nil + case map[string]any: + path := readStringValue(typed, "path") + if path == "" { + return workspaceCreateParams{}, NewMissingRequiredFieldError("payload.path") + } + return workspaceCreateParams{Path: path, Name: readStringValue(typed, "name")}, nil + default: + return workspaceCreateParams{}, NewFrameError(ErrorCodeInvalidFrame, "invalid workspace.create payload") + } +} + +func decodeWorkspaceSwitchPayload(payload any) (workspaceSwitchParams, *FrameError) { + switch typed := payload.(type) { + case protocol.SwitchWorkspaceParams: + return workspaceSwitchParams{Hash: strings.TrimSpace(typed.WorkspaceHash)}, nil + case *protocol.SwitchWorkspaceParams: + if typed == nil { + return workspaceSwitchParams{}, NewMissingRequiredFieldError("payload.workspace_hash") + } + return workspaceSwitchParams{Hash: strings.TrimSpace(typed.WorkspaceHash)}, nil + case map[string]any: + hash := readStringValue(typed, "workspace_hash") + if hash == "" { + return workspaceSwitchParams{}, NewMissingRequiredFieldError("payload.workspace_hash") + } + return workspaceSwitchParams{Hash: hash}, nil + default: + return workspaceSwitchParams{}, NewFrameError(ErrorCodeInvalidFrame, "invalid workspace.switch payload") + } +} + +func decodeWorkspaceRenamePayload(payload any) (workspaceRenameParams, *FrameError) { + switch typed := payload.(type) { + case protocol.RenameWorkspaceParams: + return workspaceRenameParams{Hash: strings.TrimSpace(typed.WorkspaceHash), Name: strings.TrimSpace(typed.Name)}, nil + case *protocol.RenameWorkspaceParams: + if typed == nil { + return workspaceRenameParams{}, NewMissingRequiredFieldError("payload.workspace_hash") + } + return workspaceRenameParams{Hash: strings.TrimSpace(typed.WorkspaceHash), Name: strings.TrimSpace(typed.Name)}, nil + case map[string]any: + hash := readStringValue(typed, "workspace_hash") + name := readStringValue(typed, "name") + if hash == "" { + return workspaceRenameParams{}, NewMissingRequiredFieldError("payload.workspace_hash") + } + if name == "" { + return workspaceRenameParams{}, NewMissingRequiredFieldError("payload.name") + } + return workspaceRenameParams{Hash: hash, Name: name}, nil + default: + return workspaceRenameParams{}, NewFrameError(ErrorCodeInvalidFrame, "invalid workspace.rename payload") + } +} + +func decodeWorkspaceDeletePayload(payload any) (workspaceDeleteParams, *FrameError) { + switch typed := payload.(type) { + case protocol.DeleteWorkspaceParams: + return workspaceDeleteParams{Hash: strings.TrimSpace(typed.WorkspaceHash), RemoveData: typed.RemoveData}, nil + case *protocol.DeleteWorkspaceParams: + if typed == nil { + return workspaceDeleteParams{}, NewMissingRequiredFieldError("payload.workspace_hash") + } + return workspaceDeleteParams{Hash: strings.TrimSpace(typed.WorkspaceHash), RemoveData: typed.RemoveData}, nil + case map[string]any: + hash := readStringValue(typed, "workspace_hash") + if hash == "" { + return workspaceDeleteParams{}, NewMissingRequiredFieldError("payload.workspace_hash") + } + removeData := false + if v, ok := typed["remove_data"].(bool); ok { + removeData = v + } + return workspaceDeleteParams{Hash: hash, RemoveData: removeData}, nil + default: + return workspaceDeleteParams{}, NewFrameError(ErrorCodeInvalidFrame, "invalid workspace.delete payload") + } +} + diff --git a/internal/provider/catalog/service_test.go b/internal/provider/catalog/service_test.go index 54e4fb44..d53b9e6a 100644 --- a/internal/provider/catalog/service_test.go +++ b/internal/provider/catalog/service_test.go @@ -59,6 +59,57 @@ func TestListProviderModelsCustomProviderDoesNotFallbackWithoutDiscovery(t *test } } +func TestListProviderModelsBuiltinDiscoversAndMergesWithStaticModels(t *testing.T) { + t.Setenv(testAPIKeyEnv, "test-key") + + registry := newRegistry(t, openaicompat.DriverName, func(ctx context.Context, cfg provider.RuntimeConfig) ([]providertypes.ModelDescriptor, error) { + return []providertypes.ModelDescriptor{ + {ID: "gpt-5.4", Name: "GPT-5.4 (Live)"}, + {ID: "gpt-new-model", Name: "GPT New Model"}, + }, nil + }) + + service := NewService("", registry, newMemoryStore()) + input := openAIProviderSource() + models, err := service.ListProviderModels(context.Background(), input) + if err != nil { + t.Fatalf("ListProviderModels() error = %v", err) + } + if len(models) != 7 { + t.Fatalf("expected 7 models (6 static + 1 new discovered), got %d: %+v", len(models), models) + } + + // Discovered model not in static list should appear + if !containsModelDescriptorID(models, "gpt-new-model") { + t.Fatalf("expected discovered new model to be present, got %+v", models) + } + // Static model should still be present + if !containsModelDescriptorID(models, "gpt-5.4") { + t.Fatalf("expected static model to be present, got %+v", models) + } +} + +func TestListProviderModelsBuiltinFallbackWhenDiscoveryFails(t *testing.T) { + t.Setenv(testAPIKeyEnv, "test-key") + + registry := newRegistry(t, openaicompat.DriverName, func(ctx context.Context, cfg provider.RuntimeConfig) ([]providertypes.ModelDescriptor, error) { + return nil, errors.New("network unreachable") + }) + + service := NewService("", registry, newMemoryStore()) + input := openAIProviderSource() + models, err := service.ListProviderModels(context.Background(), input) + if err != nil { + t.Fatalf("ListProviderModels() error = %v", err) + } + if len(models) != 6 { + t.Fatalf("expected 6 fallback static models when discovery fails, got %d: %+v", len(models), models) + } + if !containsModelDescriptorID(models, config.OpenAIDefaultModel) { + t.Fatalf("expected fallback default model to be present, got %+v", models) + } +} + func TestListProviderModelsMergesConfiguredMetadataAfterDiscovery(t *testing.T) { t.Setenv(testAPIKeyEnv, "test-key") @@ -574,20 +625,6 @@ func openAIProviderSource() provider.CatalogInput { providerCfg := config.OpenAIProvider() providerCfg.APIKeyEnv = testAPIKeyEnv input := mustCatalogInput(nil, providerCfg) - if len(input.ConfiguredModels) == 0 { - input.ConfiguredModels = providertypes.DescriptorsFromIDs([]string{ - config.OpenAIDefaultModel, - "gpt-5.4-mini", - "gpt-5.3-codex", - "gpt-4.1", - "gpt-4o", - "gpt-4o-mini", - }) - } - if len(input.DefaultModels) == 0 { - input.DefaultModels = providertypes.CloneModelDescriptors(input.ConfiguredModels) - } - input.DisableDiscovery = true return input } @@ -611,8 +648,7 @@ func mustCatalogInput(t *testing.T, cfg config.ProviderConfig) provider.CatalogI input := provider.CatalogInput{ Identity: identity, ConfiguredModels: providertypes.CloneModelDescriptors(cloned.Models), - DisableDiscovery: cloned.Source == config.ProviderSourceBuiltin || - (cloned.Source == config.ProviderSourceCustom && config.NormalizeModelSource(cloned.ModelSource) == config.ModelSourceManual), + DisableDiscovery: config.NormalizeModelSource(cloned.ModelSource) == config.ModelSourceManual, ResolveDiscoveryConfig: func() (provider.RuntimeConfig, error) { resolved, err := cloned.Resolve() if err != nil { diff --git a/internal/ptyproxy/proxy_unix_test.go b/internal/ptyproxy/proxy_unix_test.go index 7e743bd8..a942cc02 100644 --- a/internal/ptyproxy/proxy_unix_test.go +++ b/internal/ptyproxy/proxy_unix_test.go @@ -338,6 +338,8 @@ func TestSendDiagIPCCommandToPathResponseReadAndDecodeErrors(t *testing.T) { serverDone <- acceptErr return } + // Drain the client request so the write phase succeeds before we close. + _, _ = bufio.NewReader(conn).ReadBytes('\n') _ = conn.Close() serverDone <- nil }() diff --git a/internal/rules/loader_test.go b/internal/rules/loader_test.go index 61f9986b..10ff6375 100644 --- a/internal/rules/loader_test.go +++ b/internal/rules/loader_test.go @@ -158,6 +158,7 @@ func TestLoaderLoadHonorsCanceledContext(t *testing.T) { func TestLoaderLoadUsesHomeFallbackWhenBaseDirEmpty(t *testing.T) { homeDir := t.TempDir() t.Setenv("HOME", homeDir) + t.Setenv("USERPROFILE", homeDir) baseDir := filepath.Join(homeDir, defaultRulesDir) if err := os.MkdirAll(baseDir, 0o755); err != nil { t.Fatalf("mkdir baseDir: %v", err) diff --git a/internal/rules/store_test.go b/internal/rules/store_test.go index 499fbf1f..56be8e24 100644 --- a/internal/rules/store_test.go +++ b/internal/rules/store_test.go @@ -46,15 +46,12 @@ func TestProjectRulePathUsesFileParentDirectory(t *testing.T) { } func TestProjectRulePathNormalizesRelativeRootToAbsolute(t *testing.T) { - tempRoot := t.TempDir() workdir, err := os.Getwd() if err != nil { t.Fatalf("os.Getwd() error = %v", err) } - relativeRoot, err := filepath.Rel(workdir, tempRoot) - if err != nil { - t.Fatalf("filepath.Rel() error = %v", err) - } + relativeRoot := filepath.Join("relative-root-for-test", "nested") + tempRoot := filepath.Join(workdir, relativeRoot) got := ProjectRulePath(relativeRoot) want := filepath.Join(tempRoot, agentsFileName) diff --git a/internal/runtime/events.go b/internal/runtime/events.go index 47d127d1..619d1956 100644 --- a/internal/runtime/events.go +++ b/internal/runtime/events.go @@ -158,35 +158,35 @@ func newLedgerReconciledPayload( // PermissionRequestPayload 描述一次权限请求。 type PermissionRequestPayload struct { - RequestID string - ToolCallID string - ToolName string - ToolCategory string - ActionType string - Operation string - TargetType string - Target string - Decision string - Reason string - RuleID string - RememberScope string + RequestID string `json:"request_id"` + ToolCallID string `json:"tool_call_id"` + ToolName string `json:"tool_name"` + ToolCategory string `json:"tool_category"` + ActionType string `json:"action_type"` + Operation string `json:"operation"` + TargetType string `json:"target_type"` + Target string `json:"target"` + Decision string `json:"decision"` + Reason string `json:"reason"` + RuleID string `json:"rule_id"` + RememberScope string `json:"remember_scope,omitempty"` } // PermissionResolvedPayload 描述权限请求被处理后的状态。 type PermissionResolvedPayload struct { - RequestID string - ToolCallID string - ToolName string - ToolCategory string - ActionType string - Operation string - TargetType string - Target string - Decision string - Reason string - RuleID string - RememberScope string - ResolvedAs string + RequestID string `json:"request_id"` + ToolCallID string `json:"tool_call_id"` + ToolName string `json:"tool_name"` + ToolCategory string `json:"tool_category"` + ActionType string `json:"action_type"` + Operation string `json:"operation"` + TargetType string `json:"target_type"` + Target string `json:"target"` + Decision string `json:"decision"` + Reason string `json:"reason"` + RuleID string `json:"rule_id"` + RememberScope string `json:"remember_scope,omitempty"` + ResolvedAs string `json:"resolved_as,omitempty"` } // SessionSkillEventPayload 描述会话级 skill 变更事件。 @@ -502,3 +502,6 @@ type BashSideEffectPayload struct { PreemptivelyCapturedPaths []string `json:"preemptively_captured_paths,omitempty"` UncoveredPaths []string `json:"uncovered_paths,omitempty"` } + + + diff --git a/internal/runtime/final_acceptance_test.go b/internal/runtime/final_acceptance_test.go index 2ff4b624..e08b1096 100644 --- a/internal/runtime/final_acceptance_test.go +++ b/internal/runtime/final_acceptance_test.go @@ -127,7 +127,7 @@ func TestFinalAcceptanceHelpers(t *testing.T) { state.pendingFinalProgress = true applyAcceptanceResultProgress(&state, acceptance.AcceptanceDecision{Status: acceptance.AcceptanceContinue}) if state.finalInterceptStreak != 0 || state.pendingFinalProgress { - t.Fatalf("unexpected state after progress reset: %+v", state) + t.Fatalf("unexpected state after progress reset: streak=%d, pending=%v", state.finalInterceptStreak, state.pendingFinalProgress) } applyAcceptanceResultProgress(&state, acceptance.AcceptanceDecision{Status: acceptance.AcceptanceContinue}) diff --git a/internal/runtime/input_prepare.go b/internal/runtime/input_prepare.go index 24dbe345..df820509 100644 --- a/internal/runtime/input_prepare.go +++ b/internal/runtime/input_prepare.go @@ -101,6 +101,10 @@ func (s *Service) emitPrepareFailure(ctx context.Context, input PrepareInput, er Message: strings.TrimSpace(saveErr.Error()), }) } + // 会话不存在的错误由 gateway bridge 的 retry 透明处理,不需要暴露给用户 + if errors.Is(err, agentsession.ErrSessionNotFound) { + return nil + } return s.emitPrepareEvent(ctx, EventError, runID, sessionID, strings.TrimSpace(err.Error())) } diff --git a/internal/runtime/repo_hooks.go b/internal/runtime/repo_hooks.go index 90a65f90..ec6f1487 100644 --- a/internal/runtime/repo_hooks.go +++ b/internal/runtime/repo_hooks.go @@ -404,6 +404,14 @@ func evaluateWorkspaceTrust(workspace string) trustDecision { InvalidReason: fmt.Sprintf("read trust store failed: %v", err), } } + + if permErr := validateTrustStorePermissions(storePath); permErr != nil { + return trustDecision{ + Trusted: false, + TrustStorePath: storePath, + InvalidReason: fmt.Sprintf("trust store permissions unsafe: %v", permErr), + } + } if len(bytes.TrimSpace(raw)) == 0 { return trustDecision{ Trusted: false, diff --git a/internal/runtime/repo_hooks_trust_unix.go b/internal/runtime/repo_hooks_trust_unix.go new file mode 100644 index 00000000..5f072095 --- /dev/null +++ b/internal/runtime/repo_hooks_trust_unix.go @@ -0,0 +1,24 @@ +//go:build !windows + +package runtime + +import ( + "fmt" + "os" +) + +// validateTrustStorePermissions 检查 trust store 文件权限是否安全(Unix)。 +// 要求文件不能被 other 用户写入。 +func validateTrustStorePermissions(path string) error { + info, err := os.Stat(path) + if err != nil { + return fmt.Errorf("stat trust store: %w", err) + } + if !info.Mode().IsRegular() { + return fmt.Errorf("trust store is not a regular file") + } + if info.Mode().Perm()&0002 != 0 { + return fmt.Errorf("trust store is world-writable (mode %o)", info.Mode().Perm()) + } + return nil +} diff --git a/internal/runtime/repo_hooks_trust_windows.go b/internal/runtime/repo_hooks_trust_windows.go new file mode 100644 index 00000000..21a9127e --- /dev/null +++ b/internal/runtime/repo_hooks_trust_windows.go @@ -0,0 +1,20 @@ +//go:build windows + +package runtime + +import ( + "fmt" + "os" +) + +// validateTrustStorePermissions 检查 trust store 文件是否为常规文件(Windows)。 +func validateTrustStorePermissions(path string) error { + info, err := os.Stat(path) + if err != nil { + return fmt.Errorf("stat trust store: %w", err) + } + if info.IsDir() { + return fmt.Errorf("trust store path is a directory") + } + return nil +} diff --git a/internal/runtime/toolexec.go b/internal/runtime/toolexec.go index af366d46..1523cb7a 100644 --- a/internal/runtime/toolexec.go +++ b/internal/runtime/toolexec.go @@ -535,16 +535,16 @@ func resolveWorkdirPaths(workdir string, raw ...string) []string { if p == "" { continue } - if filepath.IsAbs(p) { - out = append(out, filepath.Clean(p)) + if isAbsolutePath(p) { + out = append(out, toSlash(filepath.Clean(p))) continue } wd := strings.TrimSpace(workdir) if wd == "" { - out = append(out, filepath.Clean(p)) + out = append(out, toSlash(filepath.Clean(p))) continue } - out = append(out, filepath.Clean(filepath.Join(wd, p))) + out = append(out, toSlash(filepath.Clean(filepath.Join(wd, p)))) } if len(out) == 0 { return nil @@ -552,6 +552,16 @@ func resolveWorkdirPaths(workdir string, raw ...string) []string { return out } +// isAbsolutePath 判断路径是否为绝对路径,兼容 POSIX 风格(以 / 开头)和 Windows 风格。 +func isAbsolutePath(p string) bool { + return filepath.IsAbs(p) || strings.HasPrefix(p, "/") +} + +// toSlash 统一路径分隔符为正斜杠,确保跨平台比较一致性。 +func toSlash(p string) string { + return strings.ReplaceAll(p, `\`, "/") +} + // bashCommandFromCall 从 bash 工具调用参数解析 command 字段,兼容 cmd 别名。 func bashCommandFromCall(call providertypes.ToolCall) string { args := strings.TrimSpace(call.Arguments) @@ -586,13 +596,13 @@ func collectUncoveredBashPaths(workdir string, fpDiff checkpoint.FingerprintDiff return } var abs string - if filepath.IsAbs(rel) { - abs = filepath.Clean(rel) - } else if wd != "" { - abs = filepath.Clean(filepath.Join(wd, rel)) - } else { - abs = filepath.Clean(rel) - } +if isAbsolutePath(rel) { +abs = toSlash(filepath.Clean(rel)) +} else if wd != "" { +abs = toSlash(filepath.Clean(filepath.Join(wd, rel))) +} else { +abs = toSlash(filepath.Clean(rel)) +} if _, ok := covered[abs]; ok { return } diff --git a/internal/runtime/user_hooks_test.go b/internal/runtime/user_hooks_test.go index 7eff52e5..8f0d963c 100644 --- a/internal/runtime/user_hooks_test.go +++ b/internal/runtime/user_hooks_test.go @@ -396,6 +396,10 @@ func TestResolveHookPathWithinWorkdirAndSymlinkBranches(t *testing.T) { } link := filepath.Join(workdir, "link.txt") if err := os.Symlink(target, link); err != nil { + if gruntime.GOOS == "windows" && + (errors.Is(err, os.ErrPermission) || strings.Contains(strings.ToLower(err.Error()), "privilege")) { + t.Skipf("skip symlink branch without Windows symlink privilege: %v", err) + } t.Fatalf("create symlink: %v", err) } got, err := resolveHookPathWithinWorkdir(workdir, "link.txt") @@ -936,7 +940,9 @@ func TestUserHookHandlersAndPathChecks(t *testing.T) { if !contains { t.Fatalf("expected symlink path detection to be true") } - } else if !errors.Is(err, os.ErrPermission) && !strings.Contains(strings.ToLower(err.Error()), "operation not permitted") { + } else if !errors.Is(err, os.ErrPermission) && + !strings.Contains(strings.ToLower(err.Error()), "operation not permitted") && + !strings.Contains(strings.ToLower(err.Error()), "privilege") { t.Fatalf("symlink creation error: %v", err) } diff --git a/internal/session/workspace_index.go b/internal/session/workspace_index.go new file mode 100644 index 00000000..72e619f3 --- /dev/null +++ b/internal/session/workspace_index.go @@ -0,0 +1,189 @@ +package session + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "sort" + "sync" + "time" +) + +const workspaceIndexFileName = "workspaces.json" + +// WorkspaceRecord 描述一个工作区在索引中的登记信息。 +type WorkspaceRecord struct { + Hash string `json:"hash"` + Path string `json:"path"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// WorkspaceIndex 管理工作区索引的读写与生命周期。 +type WorkspaceIndex struct { + baseDir string + mu sync.RWMutex + records []WorkspaceRecord +} + +// NewWorkspaceIndex 创建一个新的工作区索引管理器。 +func NewWorkspaceIndex(baseDir string) *WorkspaceIndex { + return &WorkspaceIndex{ + baseDir: baseDir, + records: make([]WorkspaceRecord, 0), + } +} + +// Load 从磁盘加载工作区索引。若文件不存在则返回空索引(无错误)。 +func (idx *WorkspaceIndex) Load() error { + idx.mu.Lock() + defer idx.mu.Unlock() + + path := idx.filePath() + data, err := os.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + idx.records = make([]WorkspaceRecord, 0) + return nil + } + return fmt.Errorf("workspace index load: %w", err) + } + + var loaded []WorkspaceRecord + if err := json.Unmarshal(data, &loaded); err != nil { + return fmt.Errorf("workspace index parse: %w", err) + } + idx.records = loaded + return nil +} + +// Save 将当前索引持久化到磁盘。 +func (idx *WorkspaceIndex) Save() error { + idx.mu.RLock() + data, err := json.MarshalIndent(idx.records, "", " ") + idx.mu.RUnlock() + if err != nil { + return fmt.Errorf("workspace index marshal: %w", err) + } + + path := idx.filePath() + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + return fmt.Errorf("workspace index mkdir: %w", err) + } + if err := os.WriteFile(path, data, 0644); err != nil { + return fmt.Errorf("workspace index write: %w", err) + } + return nil +} + +// Register 将一个工作区注册到索引。若已存在则更新 name 和 updated_at。 +func (idx *WorkspaceIndex) Register(path string, name string) (WorkspaceRecord, error) { + trimmedPath := NormalizeWorkspaceRoot(path) + if trimmedPath == "" { + return WorkspaceRecord{}, fmt.Errorf("workspace path is empty") + } + + hash := HashWorkspaceRoot(trimmedPath) + if name == "" { + name = filepath.Base(trimmedPath) + } + + idx.mu.Lock() + defer idx.mu.Unlock() + + now := time.Now() + for i, r := range idx.records { + if r.Hash == hash { + idx.records[i].Name = name + idx.records[i].UpdatedAt = now + return idx.records[i], nil + } + } + + record := WorkspaceRecord{ + Hash: hash, + Path: trimmedPath, + Name: name, + CreatedAt: now, + UpdatedAt: now, + } + idx.records = append(idx.records, record) + return record, nil +} + +// List 返回所有工作区记录,按 UpdatedAt 降序排列(最近活跃在前)。 +func (idx *WorkspaceIndex) List() []WorkspaceRecord { + idx.mu.RLock() + defer idx.mu.RUnlock() + + result := make([]WorkspaceRecord, len(idx.records)) + copy(result, idx.records) + sort.Slice(result, func(i, j int) bool { + return result[i].UpdatedAt.After(result[j].UpdatedAt) + }) + return result +} + +// Get 根据哈希查找工作区记录。 +func (idx *WorkspaceIndex) Get(hash string) (WorkspaceRecord, bool) { + idx.mu.RLock() + defer idx.mu.RUnlock() + + for _, r := range idx.records { + if r.Hash == hash { + return r, true + } + } + return WorkspaceRecord{}, false +} + +// Rename 修改指定工作区的显示名称。 +func (idx *WorkspaceIndex) Rename(hash string, name string) (WorkspaceRecord, error) { + if name == "" { + return WorkspaceRecord{}, fmt.Errorf("workspace name is empty") + } + + idx.mu.Lock() + defer idx.mu.Unlock() + + for i, r := range idx.records { + if r.Hash == hash { + idx.records[i].Name = name + idx.records[i].UpdatedAt = time.Now() + return idx.records[i], nil + } + } + return WorkspaceRecord{}, fmt.Errorf("workspace %s not found", hash) +} + +// Delete 从索引中移除指定工作区,并可选删除其数据目录。 +func (idx *WorkspaceIndex) Delete(hash string, removeData bool) (WorkspaceRecord, error) { + idx.mu.Lock() + defer idx.mu.Unlock() + + var target WorkspaceRecord + var found bool + for i, r := range idx.records { + if r.Hash == hash { + target = r + found = true + idx.records = append(idx.records[:i], idx.records[i+1:]...) + break + } + } + if !found { + return WorkspaceRecord{}, fmt.Errorf("workspace %s not found", hash) + } + + if removeData { + _ = os.RemoveAll(filepath.Join(idx.baseDir, projectsDirName, hash)) + } + return target, nil +} + +// filePath 返回索引文件的绝对路径。 +func (idx *WorkspaceIndex) filePath() string { + return filepath.Join(idx.baseDir, workspaceIndexFileName) +} diff --git a/internal/session/workspace_index_test.go b/internal/session/workspace_index_test.go new file mode 100644 index 00000000..caae6de3 --- /dev/null +++ b/internal/session/workspace_index_test.go @@ -0,0 +1,370 @@ +package session + +import ( + "os" + "path/filepath" + "strings" + "testing" + "time" +) + +func TestWorkspaceIndex_LoadEmpty(t *testing.T) { + base := t.TempDir() + idx := NewWorkspaceIndex(base) + if err := idx.Load(); err != nil { + t.Fatalf("Load on missing file should succeed, got %v", err) + } + if got := idx.List(); len(got) != 0 { + t.Fatalf("expected empty list, got %d records", len(got)) + } +} + +func TestWorkspaceIndex_RegisterAndGet(t *testing.T) { + base := t.TempDir() + idx := NewWorkspaceIndex(base) + + wsRoot := filepath.Join(base, "alpha") + if err := os.MkdirAll(wsRoot, 0o755); err != nil { + t.Fatalf("mkdir: %v", err) + } + + rec, err := idx.Register(wsRoot, "Alpha") + if err != nil { + t.Fatalf("Register: %v", err) + } + if rec.Hash != HashWorkspaceRoot(wsRoot) { + t.Fatalf("hash mismatch: got %q", rec.Hash) + } + if rec.Name != "Alpha" { + t.Fatalf("name = %q, want Alpha", rec.Name) + } + if rec.Path != NormalizeWorkspaceRoot(wsRoot) { + t.Fatalf("path mismatch: got %q", rec.Path) + } + if rec.CreatedAt.IsZero() || rec.UpdatedAt.IsZero() { + t.Fatalf("timestamps should be set") + } + + got, ok := idx.Get(rec.Hash) + if !ok { + t.Fatalf("Get returned ok=false for just-registered hash") + } + if got.Hash != rec.Hash || got.Name != "Alpha" { + t.Fatalf("Get returned unexpected record: %+v", got) + } +} + +func TestWorkspaceIndex_RegisterDefaultsName(t *testing.T) { + base := t.TempDir() + idx := NewWorkspaceIndex(base) + + wsRoot := filepath.Join(base, "my-project") + if err := os.MkdirAll(wsRoot, 0o755); err != nil { + t.Fatalf("mkdir: %v", err) + } + + rec, err := idx.Register(wsRoot, "") + if err != nil { + t.Fatalf("Register: %v", err) + } + if rec.Name != "my-project" { + t.Fatalf("name = %q, want my-project (filepath.Base)", rec.Name) + } +} + +func TestWorkspaceIndex_RegisterRejectsEmptyPath(t *testing.T) { + idx := NewWorkspaceIndex(t.TempDir()) + if _, err := idx.Register("", "name"); err == nil { + t.Fatalf("expected error for empty path") + } + if _, err := idx.Register(" ", "name"); err == nil { + t.Fatalf("expected error for whitespace path") + } +} + +func TestWorkspaceIndex_RegisterUpdatesExisting(t *testing.T) { + base := t.TempDir() + idx := NewWorkspaceIndex(base) + + wsRoot := filepath.Join(base, "x") + if err := os.MkdirAll(wsRoot, 0o755); err != nil { + t.Fatalf("mkdir: %v", err) + } + + first, err := idx.Register(wsRoot, "First") + if err != nil { + t.Fatalf("Register first: %v", err) + } + originalCreated := first.CreatedAt + time.Sleep(2 * time.Millisecond) + + second, err := idx.Register(wsRoot, "Second") + if err != nil { + t.Fatalf("Register second: %v", err) + } + if second.Hash != first.Hash { + t.Fatalf("hash should be stable across re-registrations: %q vs %q", first.Hash, second.Hash) + } + if second.Name != "Second" { + t.Fatalf("re-register should update name, got %q", second.Name) + } + if !second.CreatedAt.Equal(originalCreated) { + t.Fatalf("CreatedAt should be preserved on update: %v vs %v", second.CreatedAt, originalCreated) + } + if !second.UpdatedAt.After(originalCreated) { + t.Fatalf("UpdatedAt should advance: %v not after %v", second.UpdatedAt, originalCreated) + } + + if list := idx.List(); len(list) != 1 { + t.Fatalf("re-register must not duplicate; got %d records", len(list)) + } +} + +func TestWorkspaceIndex_ListSortedByUpdatedAtDesc(t *testing.T) { + base := t.TempDir() + idx := NewWorkspaceIndex(base) + + for _, name := range []string{"a", "b", "c"} { + ws := filepath.Join(base, name) + if err := os.MkdirAll(ws, 0o755); err != nil { + t.Fatalf("mkdir %s: %v", name, err) + } + if _, err := idx.Register(ws, name); err != nil { + t.Fatalf("register %s: %v", name, err) + } + time.Sleep(2 * time.Millisecond) + } + + list := idx.List() + if len(list) != 3 { + t.Fatalf("expected 3 records, got %d", len(list)) + } + for i := 1; i < len(list); i++ { + if list[i-1].UpdatedAt.Before(list[i].UpdatedAt) { + t.Fatalf("List not sorted desc: %v before %v", list[i-1].UpdatedAt, list[i].UpdatedAt) + } + } + // The last-registered "c" should be first. + if list[0].Name != "c" { + t.Fatalf("expected c first, got %q", list[0].Name) + } +} + +func TestWorkspaceIndex_GetMissing(t *testing.T) { + idx := NewWorkspaceIndex(t.TempDir()) + if _, ok := idx.Get("not-a-hash"); ok { + t.Fatalf("Get should return ok=false for missing hash") + } +} + +func TestWorkspaceIndex_RenameUpdatesNameAndTimestamp(t *testing.T) { + base := t.TempDir() + idx := NewWorkspaceIndex(base) + + ws := filepath.Join(base, "ws") + if err := os.MkdirAll(ws, 0o755); err != nil { + t.Fatalf("mkdir: %v", err) + } + rec, err := idx.Register(ws, "Original") + if err != nil { + t.Fatalf("Register: %v", err) + } + originalUpdated := rec.UpdatedAt + time.Sleep(2 * time.Millisecond) + + renamed, err := idx.Rename(rec.Hash, "Renamed") + if err != nil { + t.Fatalf("Rename: %v", err) + } + if renamed.Name != "Renamed" { + t.Fatalf("name = %q, want Renamed", renamed.Name) + } + if !renamed.UpdatedAt.After(originalUpdated) { + t.Fatalf("UpdatedAt should advance on rename") + } + + got, ok := idx.Get(rec.Hash) + if !ok || got.Name != "Renamed" { + t.Fatalf("Get after rename returned %+v ok=%v", got, ok) + } +} + +func TestWorkspaceIndex_RenameRejectsEmptyName(t *testing.T) { + idx := NewWorkspaceIndex(t.TempDir()) + if _, err := idx.Rename("any", ""); err == nil { + t.Fatalf("expected error for empty rename name") + } +} + +func TestWorkspaceIndex_RenameMissing(t *testing.T) { + idx := NewWorkspaceIndex(t.TempDir()) + _, err := idx.Rename("missing", "X") + if err == nil || !strings.Contains(err.Error(), "not found") { + t.Fatalf("expected not-found error, got %v", err) + } +} + +func TestWorkspaceIndex_DeleteRemovesRecord(t *testing.T) { + base := t.TempDir() + idx := NewWorkspaceIndex(base) + + ws := filepath.Join(base, "ws") + if err := os.MkdirAll(ws, 0o755); err != nil { + t.Fatalf("mkdir: %v", err) + } + rec, err := idx.Register(ws, "Doomed") + if err != nil { + t.Fatalf("Register: %v", err) + } + + deleted, err := idx.Delete(rec.Hash, false) + if err != nil { + t.Fatalf("Delete: %v", err) + } + if deleted.Hash != rec.Hash { + t.Fatalf("Delete returned wrong record: %+v", deleted) + } + if _, ok := idx.Get(rec.Hash); ok { + t.Fatalf("record should be gone after Delete") + } +} + +func TestWorkspaceIndex_DeleteMissing(t *testing.T) { + idx := NewWorkspaceIndex(t.TempDir()) + _, err := idx.Delete("missing", false) + if err == nil || !strings.Contains(err.Error(), "not found") { + t.Fatalf("expected not-found error, got %v", err) + } +} + +func TestWorkspaceIndex_DeleteRemovesDataDir(t *testing.T) { + base := t.TempDir() + idx := NewWorkspaceIndex(base) + + ws := filepath.Join(base, "ws") + if err := os.MkdirAll(ws, 0o755); err != nil { + t.Fatalf("mkdir: %v", err) + } + rec, err := idx.Register(ws, "Doomed") + if err != nil { + t.Fatalf("Register: %v", err) + } + + dataDir := filepath.Join(base, projectsDirName, rec.Hash) + if err := os.MkdirAll(dataDir, 0o755); err != nil { + t.Fatalf("mkdir data dir: %v", err) + } + stamp := filepath.Join(dataDir, "session.db") + if err := os.WriteFile(stamp, []byte("x"), 0o644); err != nil { + t.Fatalf("write stamp: %v", err) + } + + if _, err := idx.Delete(rec.Hash, true); err != nil { + t.Fatalf("Delete: %v", err) + } + if _, err := os.Stat(dataDir); !os.IsNotExist(err) { + t.Fatalf("data dir should be gone, stat err = %v", err) + } +} + +func TestWorkspaceIndex_DeleteKeepsDataDir(t *testing.T) { + base := t.TempDir() + idx := NewWorkspaceIndex(base) + + ws := filepath.Join(base, "ws") + if err := os.MkdirAll(ws, 0o755); err != nil { + t.Fatalf("mkdir: %v", err) + } + rec, err := idx.Register(ws, "Keep") + if err != nil { + t.Fatalf("Register: %v", err) + } + + dataDir := filepath.Join(base, projectsDirName, rec.Hash) + if err := os.MkdirAll(dataDir, 0o755); err != nil { + t.Fatalf("mkdir data dir: %v", err) + } + stamp := filepath.Join(dataDir, "session.db") + if err := os.WriteFile(stamp, []byte("x"), 0o644); err != nil { + t.Fatalf("write stamp: %v", err) + } + + if _, err := idx.Delete(rec.Hash, false); err != nil { + t.Fatalf("Delete: %v", err) + } + if _, err := os.Stat(stamp); err != nil { + t.Fatalf("data file should be preserved, stat err = %v", err) + } +} + +func TestWorkspaceIndex_SaveLoadRoundtrip(t *testing.T) { + base := t.TempDir() + idx := NewWorkspaceIndex(base) + + roots := []string{filepath.Join(base, "a"), filepath.Join(base, "b")} + for i, r := range roots { + if err := os.MkdirAll(r, 0o755); err != nil { + t.Fatalf("mkdir %s: %v", r, err) + } + if _, err := idx.Register(r, []string{"alpha", "beta"}[i]); err != nil { + t.Fatalf("register: %v", err) + } + } + + if err := idx.Save(); err != nil { + t.Fatalf("Save: %v", err) + } + if _, err := os.Stat(filepath.Join(base, workspaceIndexFileName)); err != nil { + t.Fatalf("index file should exist, stat err = %v", err) + } + + reloaded := NewWorkspaceIndex(base) + if err := reloaded.Load(); err != nil { + t.Fatalf("Load: %v", err) + } + original := idx.List() + got := reloaded.List() + if len(got) != len(original) { + t.Fatalf("got %d records, want %d", len(got), len(original)) + } + for i := range got { + if got[i].Hash != original[i].Hash || got[i].Name != original[i].Name || got[i].Path != original[i].Path { + t.Fatalf("record[%d] mismatch after roundtrip: got %+v want %+v", i, got[i], original[i]) + } + } +} + +func TestWorkspaceIndex_LoadParseError(t *testing.T) { + base := t.TempDir() + if err := os.WriteFile(filepath.Join(base, workspaceIndexFileName), []byte("not-json"), 0o644); err != nil { + t.Fatalf("write bad index: %v", err) + } + idx := NewWorkspaceIndex(base) + if err := idx.Load(); err == nil { + t.Fatalf("expected parse error") + } +} + +func TestWorkspaceIndex_ListIsCopy(t *testing.T) { + base := t.TempDir() + idx := NewWorkspaceIndex(base) + + ws := filepath.Join(base, "ws") + if err := os.MkdirAll(ws, 0o755); err != nil { + t.Fatalf("mkdir: %v", err) + } + if _, err := idx.Register(ws, "X"); err != nil { + t.Fatalf("register: %v", err) + } + + first := idx.List() + first[0].Name = "Mutated" + + got, ok := idx.Get(first[0].Hash) + if !ok { + t.Fatalf("get failed") + } + if got.Name == "Mutated" { + t.Fatalf("List should return a defensive copy; internal state mutated") + } +} diff --git a/internal/tools/registry.go b/internal/tools/registry.go index 24abbb8e..16aee916 100644 --- a/internal/tools/registry.go +++ b/internal/tools/registry.go @@ -17,6 +17,7 @@ type Registry struct { microCompactPolicies map[string]MicroCompactPolicy microCompactSummarizers map[string]ContentSummarizer microCompactSummaryMu sync.RWMutex + mcpMu sync.RWMutex mcpRegistry *mcp.Registry mcpFactory *mcp.AdapterFactory mcpExposureFilter mcp.ExposureFilter @@ -36,6 +37,8 @@ func (r *Registry) SetMCPRegistry(registry *mcp.Registry) { if r == nil || registry == nil { return } + r.mcpMu.Lock() + defer r.mcpMu.Unlock() r.mcpRegistry = registry r.mcpFactory = mcp.NewAdapterFactory(registry) if r.mcpExposureFilter == nil { @@ -43,11 +46,36 @@ func (r *Registry) SetMCPRegistry(registry *mcp.Registry) { } } +// ReplaceMCPRegistry 安全替换当前 MCP registry,先绑定新实例再在锁外关闭旧实例,避免并发读路径命中已关闭的 client;filter 非 nil 时同步更新暴露过滤器。 +func (r *Registry) ReplaceMCPRegistry(registry *mcp.Registry, filter mcp.ExposureFilter) { + if r == nil { + return + } + r.mcpMu.Lock() + oldRegistry := r.mcpRegistry + r.mcpRegistry = registry + if registry != nil { + r.mcpFactory = mcp.NewAdapterFactory(registry) + } else { + r.mcpFactory = nil + } + if filter != nil { + r.mcpExposureFilter = filter + } + r.mcpMu.Unlock() + + if oldRegistry != nil { + _ = oldRegistry.Close() + } +} + // SetMCPExposureFilter 绑定 MCP 暴露过滤器,仅影响模型可见 specs,不影响工具执行。 func (r *Registry) SetMCPExposureFilter(filter mcp.ExposureFilter) { if r == nil { return } + r.mcpMu.Lock() + defer r.mcpMu.Unlock() r.mcpExposureFilter = filter } @@ -252,14 +280,19 @@ func (r *Registry) RememberSessionDecision(sessionID string, action security.Act // supportsMCPTool 判断指定工具名是否可由当前 MCP 快照解析。 func (r *Registry) supportsMCPTool(name string) bool { - if r == nil || r.mcpFactory == nil { + if r == nil { + return false + } + r.mcpMu.RLock() + defer r.mcpMu.RUnlock() + if r.mcpFactory == nil { return false } lowerName := strings.ToLower(strings.TrimSpace(name)) if !strings.HasPrefix(lowerName, "mcp.") { return false } - for _, snapshot := range r.mcpFactoryBuildSnapshot() { + for _, snapshot := range r.mcpRegistry.Snapshot() { for _, tool := range snapshot.Tools { if strings.EqualFold(mcpToolFullName(snapshot.ServerID, tool.Name), lowerName) { return true @@ -271,7 +304,15 @@ func (r *Registry) supportsMCPTool(name string) bool { // listMCPAdapters 返回 MCP 快照对应的 adapter 列表。 func (r *Registry) listMCPAdapters(ctx context.Context, input SpecListInput) ([]*mcp.Adapter, error) { - if r == nil || r.mcpFactory == nil || r.mcpRegistry == nil { + if err := ctx.Err(); err != nil { + return nil, err + } + if r == nil { + return nil, nil + } + r.mcpMu.RLock() + defer r.mcpMu.RUnlock() + if r.mcpFactory == nil || r.mcpRegistry == nil { return nil, nil } snapshots := r.mcpRegistry.Snapshot() @@ -292,7 +333,12 @@ func (r *Registry) resolveMCPAdapter(ctx context.Context, fullName string) (*mcp if err := ctx.Err(); err != nil { return nil, err } - if r == nil || r.mcpRegistry == nil { + if r == nil { + return nil, errors.New("tool: not found") + } + r.mcpMu.RLock() + defer r.mcpMu.RUnlock() + if r.mcpRegistry == nil { return nil, errors.New("tool: not found") } @@ -394,7 +440,12 @@ func buildMCPFilterErrorAudit(snapshots []mcp.ServerSnapshot) []mcp.ExposureDeci // mcpFactoryBuildSnapshot 读取 MCP registry 快照,用于无上下文快速检查。 func (r *Registry) mcpFactoryBuildSnapshot() []mcp.ServerSnapshot { - if r == nil || r.mcpRegistry == nil { + if r == nil { + return nil + } + r.mcpMu.RLock() + defer r.mcpMu.RUnlock() + if r.mcpRegistry == nil { return nil } return r.mcpRegistry.Snapshot() diff --git a/internal/tui/core/app/input_features_test.go b/internal/tui/core/app/input_features_test.go index 1e9914c0..5011a492 100644 --- a/internal/tui/core/app/input_features_test.go +++ b/internal/tui/core/app/input_features_test.go @@ -368,6 +368,11 @@ func TestLoadImageAttachmentDataInvalidIndex(t *testing.T) { func TestAddImageFromClipboardUnsupported(t *testing.T) { app, _ := newTestApp(t) + originalRead := readClipboardImage + defer func() { readClipboardImage = originalRead }() + readClipboardImage = func() ([]byte, error) { + return nil, errors.New("clipboard image unavailable") + } if err := app.addImageFromClipboard(); err == nil { t.Fatalf("expected unsupported clipboard image error") } diff --git a/internal/tui/infra/markdown_cached_renderer.go b/internal/tui/infra/markdown_cached_renderer.go index fe290afb..c3535711 100644 --- a/internal/tui/infra/markdown_cached_renderer.go +++ b/internal/tui/infra/markdown_cached_renderer.go @@ -2,6 +2,7 @@ package infra import ( "fmt" + "hash/fnv" "regexp" "strings" @@ -47,7 +48,7 @@ func (r *CachedMarkdownRenderer) Render(content string, width int) (string, erro content = normalizeMarkdownForTerminal(content) renderWidth := max(16, width) - cacheKey := fmt.Sprintf("%d:%s", renderWidth, content) + cacheKey := hashMarkdownCacheKey(renderWidth, content) if cached, ok := r.cache[cacheKey]; ok { return cached, nil } @@ -201,3 +202,11 @@ func (r *CachedMarkdownRenderer) cacheResult(key string, value string) { } // maxInt 返回两个整数中的较大值。 + +// hashMarkdownCacheKey 生成固定长度的缓存键,避免长内容撑大 map key。 +func hashMarkdownCacheKey(width int, content string) string { + h := fnv.New64a() + fmt.Fprintf(h, "%d:", width) + h.Write([]byte(content)) + return fmt.Sprintf("%016x", h.Sum64()) +} diff --git a/internal/tui/services/gateway_stream_client_additional_test.go b/internal/tui/services/gateway_stream_client_additional_test.go index 3bf08a6f..49caa6d7 100644 --- a/internal/tui/services/gateway_stream_client_additional_test.go +++ b/internal/tui/services/gateway_stream_client_additional_test.go @@ -136,7 +136,7 @@ func TestRestoreRuntimePayloadCoversSpecializedTypes(t *testing.T) { { name: "permission request", eventType: EventPermissionRequested, - payload: map[string]any{"RequestID": "req-1"}, + payload: map[string]any{"request_id": "req-1"}, assertFn: func(t *testing.T, got any) { t.Helper() if v, ok := got.(PermissionRequestPayload); !ok || v.RequestID != "req-1" { diff --git a/internal/tui/services/runtime_contract.go b/internal/tui/services/runtime_contract.go index a8a74598..cbb67651 100644 --- a/internal/tui/services/runtime_contract.go +++ b/internal/tui/services/runtime_contract.go @@ -118,35 +118,35 @@ const ( // PermissionRequestPayload 描述权限请求事件载荷。 type PermissionRequestPayload struct { - RequestID string - ToolCallID string - ToolName string - ToolCategory string - ActionType string - Operation string - TargetType string - Target string - Decision string - Reason string - RuleID string - RememberScope string + RequestID string `json:"request_id"` + ToolCallID string `json:"tool_call_id"` + ToolName string `json:"tool_name"` + ToolCategory string `json:"tool_category"` + ActionType string `json:"action_type"` + Operation string `json:"operation"` + TargetType string `json:"target_type"` + Target string `json:"target"` + Decision string `json:"decision"` + Reason string `json:"reason"` + RuleID string `json:"rule_id"` + RememberScope string `json:"remember_scope,omitempty"` } // PermissionResolvedPayload 描述权限请求处理结果。 type PermissionResolvedPayload struct { - RequestID string - ToolCallID string - ToolName string - ToolCategory string - ActionType string - Operation string - TargetType string - Target string - Decision string - Reason string - RuleID string - RememberScope string - ResolvedAs string + RequestID string `json:"request_id"` + ToolCallID string `json:"tool_call_id"` + ToolName string `json:"tool_name"` + ToolCategory string `json:"tool_category"` + ActionType string `json:"action_type"` + Operation string `json:"operation"` + TargetType string `json:"target_type"` + Target string `json:"target"` + Decision string `json:"decision"` + Reason string `json:"reason"` + RuleID string `json:"rule_id"` + RememberScope string `json:"remember_scope,omitempty"` + ResolvedAs string `json:"resolved_as,omitempty"` } // SessionSkillState 描述会话技能状态。 diff --git a/web/components.json b/web/components.json new file mode 100644 index 00000000..25c8b97a --- /dev/null +++ b/web/components.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "src/index.css", + "baseColor": "zinc", + "cssVariables": true + }, + "aliases": { + "components": "@/components", + "utils": "@/utils/cn" + } +} diff --git a/web/electron-builder.config.cjs b/web/electron-builder.config.cjs new file mode 100644 index 00000000..b10dcc98 --- /dev/null +++ b/web/electron-builder.config.cjs @@ -0,0 +1,57 @@ +/** + * electron-builder 配置 + */ +const config = { + appId: 'com.neocode.app', + productName: 'NeoCode', + directories: { + output: 'release', + buildResources: 'build', + }, + files: [ + 'dist/**/*', + 'dist-electron/**/*', + ], + extraResources: [ + { + from: 'build', + to: '.', + filter: ['neocode-gateway', 'neocode-gateway.exe'], + }, + ], + win: { + target: [ + { + target: 'nsis', + arch: ['x64'], + }, + ], + artifactName: '${productName}-${version}-Setup.${ext}', + }, + nsis: { + oneClick: false, + perMachine: false, + allowToChangeInstallationDirectory: true, + deleteAppDataOnUninstall: false, + }, + mac: { + target: [ + { + target: 'dmg', + arch: ['x64', 'arm64'], + }, + ], + artifactName: '${productName}-${version}-${arch}.${ext}', + }, + linux: { + target: [ + { + target: 'AppImage', + arch: ['x64'], + }, + ], + artifactName: '${productName}-${version}.${ext}', + }, +} + +module.exports = config diff --git a/web/electron/main.ts b/web/electron/main.ts new file mode 100644 index 00000000..215ca117 --- /dev/null +++ b/web/electron/main.ts @@ -0,0 +1,370 @@ +import { app, BrowserWindow, ipcMain, shell, dialog } from 'electron' +import { spawn, type ChildProcess } from 'child_process' +import { join, dirname } from 'path' +import { fileURLToPath } from 'url' +import { existsSync, readFileSync } from 'fs' +import { homedir } from 'os' +import { electronApp, optimizer, is } from '@electron-toolkit/utils' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +const DEFAULT_BASE_PORT = 8080 +const MAX_PORT_ATTEMPTS = 10 + +let mainWindow: BrowserWindow | null = null +let gatewayProcess: ChildProcess | null = null +let gatewayReady = false +let gatewayAddress = '' +let gatewayToken = '' +let currentWorkdir = process.env['NEOCODE_WORKDIR'] ?? '' +let isQuitting = false + +/** 安全发送 Gateway 状态,避免窗口销毁后访问 webContents 触发主进程异常 */ +function sendGatewayStatus(data: { ready: boolean; error?: string }): void { + if (isQuitting) return + if (!mainWindow || mainWindow.isDestroyed() || mainWindow.webContents.isDestroyed()) return + mainWindow.webContents.send('gateway:status', data) +} + +/** 创建主窗口 */ +function createWindow(): void { + mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + minWidth: 800, + minHeight: 600, + show: false, + title: 'NeoCode', + titleBarStyle: 'hiddenInset', + webPreferences: { + preload: join(__dirname, 'preload.cjs'), + sandbox: false, + contextIsolation: true, + nodeIntegration: false, + }, + }) + + mainWindow.on('ready-to-show', () => { + mainWindow?.show() + }) + + mainWindow.on('closed', () => { + mainWindow = null + }) + + mainWindow.webContents.setWindowOpenHandler((details) => { + shell.openExternal(details.url) + return { action: 'deny' } + }) + + // Block unexpected in-page navigations that could lead to blank pages + mainWindow.webContents.on('will-navigate', (event, url) => { + const devUrl = process.env['ELECTRON_RENDERER_URL'] ?? '' + const isDevServer = is.dev && devUrl !== '' && url.startsWith(devUrl) + const isFileProtocol = url.startsWith('file://') + + if (isDevServer || isFileProtocol) { + return // allow + } + + event.preventDefault() + console.warn(`[Electron] Blocked navigation to: ${url}`) + }) + + // Intercept browser back/forward to prevent navigating to invalid URLs + mainWindow.on('app-command', (e, cmd) => { + if (cmd === 'browser-backward' || cmd === 'browser-forward') { + e.preventDefault() + } + }) + + // 开发模式加载 Vite dev server,生产模式加载打包文件 + if (is.dev && process.env['ELECTRON_RENDERER_URL']) { + mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) + } else { + mainWindow.loadFile(join(__dirname, '../dist/index.html')) + } +} + +// ---- Gateway 进程管理 ---- + +/** 查找 Gateway 可执行文件路径 */ +function findGatewayBinary(): string | null { + const explicit = process.env['NEOCODE_GATEWAY_BIN'] + if (explicit) return explicit + const candidates = [ + // 开发模式:build-gateway.js 的输出目录 + ...(is.dev ? [ + join(__dirname, '..', 'build', 'neocode-gateway'), + join(__dirname, '..', 'build', 'neocode-gateway.exe'), + ] : []), + // 打包模式:resources 目录 + join(process.resourcesPath, 'neocode-gateway'), + join(process.resourcesPath, 'neocode-gateway.exe'), + // 打包模式:可执行文件同目录 + join(app.getPath('exe'), '..', 'neocode-gateway'), + join(app.getPath('exe'), '..', 'neocode-gateway.exe'), + ] + for (const p of candidates) { + if (existsSync(p)) return p + } + return null +} + +/** 从 ~/.neocode/auth.json 读取认证 token */ +function loadGatewayToken(): string { + try { + const authPath = join(homedir(), '.neocode', 'auth.json') + const raw = readFileSync(authPath, 'utf-8') + const auth = JSON.parse(raw) as { token?: string } + const token = auth.token ?? '' + console.log(`[Electron] Loaded gateway token from ${authPath}: ${token ? `${token.slice(0, 8)}...` : '(empty)'}`) + return token + } catch (err) { + console.warn(`[Electron] Failed to load gateway token:`, err) + return '' + } +} + +/** 检测 Gateway 是否健康 */ +async function checkHealthz(address: string): Promise { + const url = /^https?:\/\//i.test(address) ? address : `http://${address}` + try { + const res = await fetch(`${url}/healthz`, { signal: AbortSignal.timeout(2000) }) + return res.ok + } catch { + return false + } +} + +/** 等待 Gateway 健康检查通过 */ +async function waitForHealthz(address: string, timeoutMs: number, intervalMs: number): Promise { + const url = /^https?:\/\//i.test(address) ? address : `http://${address}` + const deadline = Date.now() + timeoutMs + while (Date.now() < deadline) { + try { + const res = await fetch(`${url}/healthz`, { signal: AbortSignal.timeout(2000) }) + if (res.ok) return true + } catch { + // continue polling + } + await new Promise((r) => setTimeout(r, intervalMs)) + } + return false +} + +/** 从环境变量解析显式指定的端口 */ +function findExplicitPort(): number | null { + const envPort = process.env['NEOCODE_GATEWAY_PORT'] + if (envPort) { + const p = parseInt(envPort, 10) + if (p > 0 && p < 65536) return p + } + const envAddr = process.env['NEOCODE_GATEWAY'] + if (envAddr) { + const m = envAddr.match(/:(\d+)$/) + if (m) { + const p = parseInt(m[1], 10) + if (p > 0 && p < 65536) return p + } + } + return null +} + +/** 尝试在指定地址启动 Gateway */ +async function tryStartGateway(binary: string, httpAddress: string): Promise { + console.log(`[Electron] Starting Gateway: ${binary} on ${httpAddress}`) + const args = ['--http-listen', httpAddress] + if (currentWorkdir) { + args.push('--workdir', currentWorkdir) + } + const proc = spawn(binary, args, { + detached: false, + stdio: 'pipe', + }) + gatewayProcess = proc + + proc.stdout?.on('data', (data: Buffer) => { + console.log(`[Gateway stdout] ${data.toString().trim()}`) + }) + proc.stderr?.on('data', (data: Buffer) => { + console.error(`[Gateway stderr] ${data.toString().trim()}`) + }) + proc.on('exit', (code) => { + console.warn(`[Electron] Gateway exited with code ${code}`) + if (gatewayProcess !== proc) return + gatewayProcess = null + gatewayReady = false + sendGatewayStatus({ + ready: false, + error: code === 0 ? 'Gateway process exited' : `Gateway process crashed (exit code ${code})`, + }) + }) + + const ready = await waitForHealthz(httpAddress, 15000, 500) + if (ready) { + gatewayAddress = httpAddress + gatewayToken = loadGatewayToken() + gatewayReady = true + console.log(`[Electron] Gateway is ready at ${httpAddress}`) + } + return ready +} + +/** 启动 Gateway 子进程并等待就绪(自动端口轮询) */ +async function startGateway(): Promise { + const binary = findGatewayBinary() + if (!binary) { + console.warn('[Electron] Gateway binary not found, checking for external gateway') + gatewayAddress = process.env['NEOCODE_GATEWAY'] ?? '127.0.0.1:8080' + gatewayToken = process.env['NEOCODE_TOKEN'] ?? '' + gatewayReady = await checkHealthz(gatewayAddress) + if (!gatewayReady) { + sendGatewayStatus({ ready: false, error: 'Gateway binary not found and no external gateway detected' }) + } + return + } + + const explicitPort = findExplicitPort() + if (explicitPort !== null) { + console.log(`[Electron] Using specified port ${explicitPort}`) + const addr = `127.0.0.1:${explicitPort}` + if (await checkHealthz(addr)) { + console.log(`[Electron] Gateway already running at ${addr}`) + gatewayAddress = addr + gatewayToken = loadGatewayToken() + gatewayReady = true + return + } + if (await tryStartGateway(binary, addr)) return + sendGatewayStatus({ ready: false, error: `Gateway failed to start on port ${explicitPort}` }) + return + } + + for (let port = DEFAULT_BASE_PORT; port < DEFAULT_BASE_PORT + MAX_PORT_ATTEMPTS; port++) { + const addr = `127.0.0.1:${port}` + if (await checkHealthz(addr)) { + console.log(`[Electron] Gateway already running at ${addr}`) + gatewayAddress = addr + gatewayToken = loadGatewayToken() + gatewayReady = true + return + } + console.log(`[Electron] Trying port ${port}...`) + if (await tryStartGateway(binary, addr)) return + if (gatewayProcess) { + gatewayProcess.kill() + gatewayProcess = null + } + } + + console.error(`[Electron] All ports ${DEFAULT_BASE_PORT}-${DEFAULT_BASE_PORT + MAX_PORT_ATTEMPTS - 1} are unavailable`) + gatewayReady = false + sendGatewayStatus({ ready: false, error: `All ports ${DEFAULT_BASE_PORT}-${DEFAULT_BASE_PORT + MAX_PORT_ATTEMPTS - 1} are unavailable` }) +} + +/** 停止 Gateway 子进程 */ +function stopGateway(): void { + if (gatewayProcess) { + console.log('[Electron] Stopping Gateway') + gatewayProcess.kill() + gatewayProcess = null + } +} + +// ---- IPC 处理 ---- + +/** 获取认证 Token */ +ipcMain.handle('gateway:getToken', () => { + const token = gatewayToken || process.env['NEOCODE_TOKEN'] || '' + console.log(`[Electron] IPC getToken → ${token ? `${token.slice(0, 8)}...` : '(empty)'}`) + return token +}) + +/** 获取 Gateway 地址 */ +ipcMain.handle('gateway:getAddress', () => { + const addr = gatewayAddress || process.env['NEOCODE_GATEWAY'] || '127.0.0.1:8080' + console.log(`[Electron] IPC getAddress → ${addr} (gatewayAddress=${gatewayAddress}, ready=${gatewayReady})`) + return addr +}) + +/** 获取当前工作区目录 */ +ipcMain.handle('gateway:getWorkdir', () => currentWorkdir) + +/** 选择新工作区目录并重启 Gateway */ +ipcMain.handle('gateway:selectWorkdir', async () => { + if (!mainWindow) return { canceled: true, workdir: currentWorkdir } + const result = await dialog.showOpenDialog(mainWindow, { + properties: ['openDirectory'], + defaultPath: currentWorkdir || app.getPath('home'), + }) + if (result.canceled || result.filePaths.length === 0) { + return { canceled: true, workdir: currentWorkdir } + } + const newWorkdir = result.filePaths[0] + if (newWorkdir === currentWorkdir) { + return { canceled: false, workdir: currentWorkdir } + } + currentWorkdir = newWorkdir + console.log(`[Electron] Workdir changed to: ${currentWorkdir}`) + // 重启 Gateway 以应用新工作区 + stopGateway() + await startGateway() + return { canceled: false, workdir: currentWorkdir } +}) + +/** 纯目录选择器,不修改 Gateway 工作目录 */ +ipcMain.handle('dialog:pickDirectory', async () => { + if (!mainWindow) return { canceled: true, filePaths: [] as string[] } + const result = await dialog.showOpenDialog(mainWindow, { + properties: ['openDirectory'], + defaultPath: currentWorkdir || app.getPath('home'), + }) + return { canceled: result.canceled, filePaths: result.filePaths } +}) + +/** 窗口控制 */ +ipcMain.handle('window:minimize', () => mainWindow?.minimize()) +ipcMain.handle('window:maximize', () => { + if (mainWindow?.isMaximized()) { + mainWindow.unmaximize() + } else { + mainWindow?.maximize() + } +}) +ipcMain.handle('window:close', () => mainWindow?.close()) + +// ---- App 生命周期 ---- + +app.whenReady().then(async () => { + electronApp.setAppUserModelId('com.neocode.app') + + app.on('browser-window-created', (_, window) => { + optimizer.watchWindowShortcuts(window) + }) + + await startGateway() + createWindow() + + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) createWindow() + }) +}) + +app.on('before-quit', (event) => { + if (gatewayProcess) { + isQuitting = true + event.preventDefault() + gatewayProcess.on('exit', () => app.quit()) + gatewayProcess.kill() + } else { + isQuitting = true + stopGateway() + } +}) + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit() + } +}) diff --git a/web/electron/preload.ts b/web/electron/preload.ts new file mode 100644 index 00000000..5acbac1d --- /dev/null +++ b/web/electron/preload.ts @@ -0,0 +1,31 @@ +import { contextBridge, ipcRenderer } from 'electron' + +/** 暴露安全 API 到渲染进程 */ +contextBridge.exposeInMainWorld('electronAPI', { + /** 获取认证 Token */ + getToken: () => ipcRenderer.invoke('gateway:getToken'), + + /** 获取 Gateway 地址 */ + getAddress: () => ipcRenderer.invoke('gateway:getAddress'), + + /** 获取当前工作区目录 */ + getWorkdir: () => ipcRenderer.invoke('gateway:getWorkdir'), + + /** 选择新工作区目录并重启 Gateway */ + selectWorkdir: () => ipcRenderer.invoke('gateway:selectWorkdir') as Promise<{ canceled: boolean; workdir: string }>, + + /** 纯目录选择器(不重启 Gateway) */ + pickDirectory: () => ipcRenderer.invoke('dialog:pickDirectory') as Promise<{ canceled: boolean; filePaths: string[] }>, + + /** 窗口控制 */ + minimize: () => ipcRenderer.invoke('window:minimize'), + maximize: () => ipcRenderer.invoke('window:maximize'), + close: () => ipcRenderer.invoke('window:close'), + + /** 监听主进程 Gateway 状态变更 */ + onGatewayStatus: (callback: (data: { ready: boolean; error?: string }) => void) => { + const handler = (_event: Electron.IpcRendererEvent, data: unknown) => callback(data as { ready: boolean; error?: string }) + ipcRenderer.on('gateway:status', handler) + return () => { ipcRenderer.removeListener('gateway:status', handler) } + }, +}) diff --git a/web/index.html b/web/index.html new file mode 100644 index 00000000..e1ff4806 --- /dev/null +++ b/web/index.html @@ -0,0 +1,13 @@ + + + + + + + NeoCode — AI Coding Assistant + + +
+ + + diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 00000000..6b20ec15 --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,9881 @@ +{ + "name": "neocode-web", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "neocode-web", + "version": "0.1.0", + "dependencies": { + "lucide-react": "^0.468.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-markdown": "^10.1.0", + "react-router-dom": "^6.28.0", + "remark-gfm": "^4.0.1", + "zustand": "^5.0.2" + }, + "devDependencies": { + "@electron-toolkit/utils": "^4.0.0", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^22.10.0", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react": "^4.3.4", + "electron": "^33.2.0", + "electron-builder": "^25.1.8", + "happy-dom": "^20.9.0", + "jsdom": "^29.1.0", + "typescript": "^5.7.3", + "vite": "^6.0.7", + "vite-plugin-electron": "^0.28.7", + "vite-plugin-electron-renderer": "^0.14.6", + "vitest": "^4.1.5" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.0.tgz", + "integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.0.tgz", + "integrity": "sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.2.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz", + "integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@develar/schema-utils": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.0", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@electron-toolkit/utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@electron-toolkit/utils/-/utils-4.0.0.tgz", + "integrity": "sha512-qXSntwEzluSzKl4z5yFNBknmPGjPa3zFhE4mp9+h0cgokY5ornAeP+CJQDBhKsL1S58aOQfcwkD3NwLZCl+64g==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "electron": ">=13.0.0" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/get": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", + "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/notarize/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/notarize/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.1.tgz", + "integrity": "sha512-BAfviURMHpmb1Yb50YbCxnOY0wfwaLXH5KJ4+80zS0gUkzDX3ec23naTlEqKsN+PwYn+a1cCzM7BJ4Wcd3sGzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/osx-sign/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/osx-sign/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/rebuild": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-3.6.1.tgz", + "integrity": "sha512-f6596ZHpEq/YskUd8emYvOUne89ij8mQgjYFA5ru25QwbrRO+t1SImofdDv7kKOuWCmVOuU5tvfkbgGxIl3E/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "chalk": "^4.0.0", + "debug": "^4.1.1", + "detect-libc": "^2.0.1", + "fs-extra": "^10.0.0", + "got": "^11.7.0", + "node-abi": "^3.45.0", + "node-api-version": "^0.2.0", + "node-gyp": "^9.0.0", + "ora": "^5.1.0", + "read-binary-file-arch": "^1.0.6", + "semver": "^7.3.5", + "tar": "^6.0.5", + "yargs": "^17.0.1" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/@electron/rebuild/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@electron/rebuild/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/rebuild/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/rebuild/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/universal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.1.tgz", + "integrity": "sha512-fKpv9kg4SPmt+hY7SVBnIYULE9QJl8L3sCfcBsnqbJwwBwAeTLokJ9TRt9y7bK0JAzIW2y78TVVjvnQEms/yyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.2.7", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/universal/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/fs/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/plist": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", + "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*", + "xmlbuilder": ">=11.0.1" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/verror": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", + "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", + "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", + "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.5", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", + "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", + "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.5", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", + "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "@vitest/utils": "4.1.5", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", + "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", + "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.9.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz", + "integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.6" + } + }, + "node_modules/7zip-bin": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", + "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/app-builder-bin": { + "version": "5.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-5.0.0-alpha.10.tgz", + "integrity": "sha512-Ev4jj3D7Bo+O0GPD2NMvJl+PGiBAfS7pUGawntBNpCbxtpncfUixqFj9z9Jme7V7s3LBGqsWZZP54fxBX3JKJw==", + "dev": true, + "license": "MIT" + }, + "node_modules/app-builder-lib": { + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-25.1.8.tgz", + "integrity": "sha512-pCqe7dfsQFBABC1jeKZXQWhGcCPF3rPCXDdfqVKjIeWBcXzyC1iOWZdfFhGl+S9MyE/k//DFmC6FzuGAUudNDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@develar/schema-utils": "~2.6.5", + "@electron/notarize": "2.5.0", + "@electron/osx-sign": "1.3.1", + "@electron/rebuild": "3.6.1", + "@electron/universal": "2.0.1", + "@malept/flatpak-bundler": "^0.4.0", + "@types/fs-extra": "9.0.13", + "async-exit-hook": "^2.0.1", + "bluebird-lst": "^1.0.9", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "chromium-pickle-js": "^0.2.0", + "config-file-ts": "0.2.8-rc1", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", + "ejs": "^3.1.8", + "electron-publish": "25.1.7", + "form-data": "^4.0.0", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "is-ci": "^3.0.0", + "isbinaryfile": "^5.0.0", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "lazy-val": "^1.0.5", + "minimatch": "^10.0.0", + "resedit": "^1.7.0", + "sanitize-filename": "^1.6.3", + "semver": "^7.3.8", + "tar": "^6.1.12", + "temp-file": "^3.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "25.1.8", + "electron-builder-squirrel-windows": "25.1.8" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/app-builder-lib/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "dev": true, + "license": "ISC" + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.23", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.23.tgz", + "integrity": "sha512-xwVXGqevyKPsiuQdLj+dZMVjidjJV508TBqexND5HrF89cGdCYCJFB3qhcxRHSeMctdCfbR1jrxBajhDy7o29g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/bluebird-lst": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz", + "integrity": "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "^3.5.5" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "25.1.7", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-25.1.7.tgz", + "integrity": "sha512-7jPjzBwEGRbwNcep0gGNpLXG9P94VA3CPAZQCzxkFXiV2GMQKlziMbY//rXPI7WKfhsvGgFXjTcXdBEwgXw9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "7zip-bin": "~5.2.0", + "app-builder-bin": "5.0.0-alpha.10", + "bluebird-lst": "^1.0.9", + "builder-util-runtime": "9.2.10", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "is-ci": "^3.0.0", + "js-yaml": "^4.1.0", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.2.10", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.10.tgz", + "integrity": "sha512-6p/gfG1RJSQeIbz8TK5aPNkoztgY1q5TgmGFMAXcY8itsGW6Y2ld1ALsZ5UJn8rog7hKF3zHx5iQbNQ8uLcRlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/builder-util/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/builder-util/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/builder-util/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/cacache/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/config-file-ts": { + "version": "0.2.8-rc1", + "resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.8-rc1.tgz", + "integrity": "sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.3.12", + "typescript": "^5.4.3" + } + }, + "node_modules/config-file-ts/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/config-file-ts/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/config-file-ts/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/config-file-ts/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/config-file-ts/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.1.0" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-urls/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dir-compare": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " + } + }, + "node_modules/dir-compare/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-25.1.8.tgz", + "integrity": "sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "25.1.8", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "fs-extra": "^10.1.0", + "iconv-lite": "^0.6.2", + "js-yaml": "^4.1.0" + }, + "optionalDependencies": { + "dmg-license": "^1.0.11" + } + }, + "node_modules/dmg-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dmg-builder/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/dmg-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/dmg-license": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", + "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "@types/plist": "^3.0.1", + "@types/verror": "^1.10.3", + "ajv": "^6.10.0", + "crc": "^3.8.0", + "iconv-corefoundation": "^1.1.7", + "plist": "^3.0.4", + "smart-buffer": "^4.0.2", + "verror": "^1.10.0" + }, + "bin": { + "dmg-license": "bin/dmg-license.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "33.4.11", + "resolved": "https://registry.npmjs.org/electron/-/electron-33.4.11.tgz", + "integrity": "sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^20.9.0", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-builder": { + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-25.1.8.tgz", + "integrity": "sha512-poRgAtUHHOnlzZnc9PK4nzG53xh74wj2Jy7jkTrqZ0MWPoHGh1M2+C//hGeYdA+4K8w4yiVCNYoLXF7ySj2Wig==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "25.1.8", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "chalk": "^4.1.2", + "dmg-builder": "25.1.8", + "fs-extra": "^10.1.0", + "is-ci": "^3.0.0", + "lazy-val": "^1.0.5", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-25.1.8.tgz", + "integrity": "sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "25.1.8", + "archiver": "^5.3.1", + "builder-util": "25.1.7", + "fs-extra": "^10.1.0" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-publish": { + "version": "25.1.7", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-25.1.7.tgz", + "integrity": "sha512-+jbTkR9m39eDBMP4gfbqglDd6UvBC7RLh5Y0MhFSsc6UkGHj9Vj9TWobxevHYMMqmoujL11ZLjfPpMX+Pt6YEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "chalk": "^4.1.2", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-publish/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-publish/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-publish/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.344", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", + "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", + "dev": true, + "license": "ISC" + }, + "node_modules/electron/node_modules/@types/node": { + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/happy-dom": { + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.9.0.tgz", + "integrity": "sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": ">=20.0.0", + "@types/whatwg-mimetype": "^3.0.2", + "@types/ws": "^8.18.1", + "entities": "^7.0.1", + "whatwg-mimetype": "^3.0.0", + "ws": "^8.18.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-corefoundation": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", + "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "cli-truncate": "^2.1.0", + "node-addon-api": "^1.6.3" + }, + "engines": { + "node": "^8.11.2 || >=10" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true, + "license": "ISC" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "29.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.0.tgz", + "integrity": "sha512-YNUc7fB9QuvSSQWfrH0xF+TyABkxUwx8sswgIDaCrw4Hol8BghdZDkITtZheRJeMtzWlnTfsM3bBBusRvpO1wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/jsdom/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.468.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz", + "integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "license": "ISC", + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.89.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", + "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-api-version/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", + "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.13 || ^14.13 || >=16" + } + }, + "node_modules/node-gyp/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pe-library": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", + "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/plist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.1.tgz", + "integrity": "sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.9.10", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/postcss": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resedit": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", + "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pe-library": "^0.4.1" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/temp-file/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/temp-file/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/temp-file/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.29", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.29.tgz", + "integrity": "sha512-JIXCerhudr/N6OWLwLF1HVsTTUo7ry6qHa5eWZEkiMuxsIiAACL55tGLfqfHfoH7QaMQUW8fngD7u7TxWexYQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.29" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.29", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.29.tgz", + "integrity": "sha512-W99NuU7b1DcG3uJ3v9k9VztCH3WialNbBkBft5wCs8V8mexu0XQqaZEYb9l9RNNzK8+3EJ9PKWB0/RUtTQ/o+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-electron": { + "version": "0.28.8", + "resolved": "https://registry.npmjs.org/vite-plugin-electron/-/vite-plugin-electron-0.28.8.tgz", + "integrity": "sha512-ir+B21oSGK9j23OEvt4EXyco9xDCaF6OGFe0V/8Zc0yL2+HMyQ6mmNQEIhXsEsZCSfIowBpwQBeHH4wVsfraeg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "vite-plugin-electron-renderer": "*" + }, + "peerDependenciesMeta": { + "vite-plugin-electron-renderer": { + "optional": true + } + } + }, + "node_modules/vite-plugin-electron-renderer": { + "version": "0.14.6", + "resolved": "https://registry.npmjs.org/vite-plugin-electron-renderer/-/vite-plugin-electron-renderer-0.14.6.tgz", + "integrity": "sha512-oqkWFa7kQIkvHXG7+Mnl1RTroA4sP0yesKatmAy0gjZC4VwUqlvF9IvOpHd1fpLWsqYX/eZlVxlhULNtaQ78Jw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", + "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.5", + "@vitest/mocker": "4.1.5", + "@vitest/pretty-format": "4.1.5", + "@vitest/runner": "4.1.5", + "@vitest/snapshot": "4.1.5", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.5", + "@vitest/browser-preview": "4.1.5", + "@vitest/browser-webdriverio": "4.1.5", + "@vitest/coverage-istanbul": "4.1.5", + "@vitest/coverage-v8": "4.1.5", + "@vitest/ui": "4.1.5", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zustand": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz", + "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 00000000..b2bf9e52 --- /dev/null +++ b/web/package.json @@ -0,0 +1,46 @@ +{ + "name": "neocode-web", + "private": true, + "version": "0.1.0", + "type": "module", + "main": "dist-electron/main.js", + "scripts": { + "dev": "vite", + "dev:electron": "node scripts/clean-electron.js && node scripts/build-gateway.js && vite --mode electron", + "build": "tsc -b && vite build", + "build:electron": "node scripts/clean-electron.js && node scripts/build-gateway.js && vite build --mode electron && node scripts/verify-electron-preload.js && electron-builder", + "preview": "vite preview", + "test": "vitest run", + "test:ui": "vitest --ui", + "coverage": "vitest run --coverage", + "verify:electron-preload": "node scripts/verify-electron-preload.js" + }, + "dependencies": { + "lucide-react": "^0.468.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-markdown": "^10.1.0", + "react-router-dom": "^6.28.0", + "remark-gfm": "^4.0.1", + "zustand": "^5.0.2" + }, + "devDependencies": { + "@electron-toolkit/utils": "^4.0.0", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^22.10.0", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react": "^4.3.4", + "electron": "^33.2.0", + "electron-builder": "^25.1.8", + "happy-dom": "^20.9.0", + "jsdom": "^29.1.0", + "typescript": "^5.7.3", + "vite": "^6.0.7", + "vite-plugin-electron": "^0.28.7", + "vite-plugin-electron-renderer": "^0.14.6", + "vitest": "^4.1.5" + } +} diff --git a/web/scripts/build-gateway.js b/web/scripts/build-gateway.js new file mode 100644 index 00000000..c7acc177 --- /dev/null +++ b/web/scripts/build-gateway.js @@ -0,0 +1,50 @@ +import { spawnSync } from 'child_process' +import { existsSync, mkdirSync } from 'fs' +import { join, resolve, dirname } from 'path' +import { fileURLToPath } from 'url' +import { platform, arch } from 'os' + +const targetMap = { + win32: { x64: 'windows/amd64', ia32: 'windows/386', arm64: 'windows/arm64' }, + darwin: { x64: 'darwin/amd64', arm64: 'darwin/arm64' }, + linux: { x64: 'linux/amd64', arm64: 'linux/arm64' }, +} + +const currentPlatform = platform() +const currentArch = arch() +const goosGoarch = targetMap[currentPlatform]?.[currentArch] + +if (!goosGoarch) { + console.error(`Unsupported platform/arch: ${currentPlatform}/${currentArch}`) + process.exit(1) +} + +const [goos, goarch] = goosGoarch.split('/') +const __dirname = dirname(fileURLToPath(import.meta.url)) +const projectRoot = resolve(__dirname, '..', '..') +const outputDir = resolve(__dirname, '..', 'build') +const binaryName = goos === 'windows' ? 'neocode-gateway.exe' : 'neocode-gateway' +const outputPath = join(outputDir, binaryName) + +if (!existsSync(outputDir)) { + mkdirSync(outputDir, { recursive: true }) +} + +console.log(`Building neocode-gateway for ${goos}/${goarch}...`) + +const result = spawnSync('go', ['build', '-a', '-o', outputPath, './cmd/neocode-gateway'], { + cwd: projectRoot, + env: { ...process.env, GOOS: goos, GOARCH: goarch, CGO_ENABLED: '0' }, + stdio: 'inherit', +}) + +if (result.error) { + console.error('Go build failed:', result.error.message) + process.exit(1) +} +if (result.status !== 0) { + console.error('Go build failed') + process.exit(result.status ?? 1) +} + +console.log(`Built: ${outputPath}`) diff --git a/web/scripts/clean-electron.js b/web/scripts/clean-electron.js new file mode 100644 index 00000000..bb6d7d40 --- /dev/null +++ b/web/scripts/clean-electron.js @@ -0,0 +1,18 @@ +import { existsSync, rmSync } from 'fs' +import { resolve, dirname } from 'path' +import { fileURLToPath } from 'url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const outputDir = resolve(__dirname, '..', 'dist-electron') + +// 清理 Electron 构建产物,避免损坏或残留文件被开发模式直接加载。 +function cleanElectronOutput() { + if (!existsSync(outputDir)) { + console.log(`Electron output already clean: ${outputDir}`) + return + } + rmSync(outputDir, { recursive: true, force: true }) + console.log(`Cleaned Electron output: ${outputDir}`) +} + +cleanElectronOutput() diff --git a/web/scripts/verify-electron-preload.js b/web/scripts/verify-electron-preload.js new file mode 100644 index 00000000..2e4d621e --- /dev/null +++ b/web/scripts/verify-electron-preload.js @@ -0,0 +1,28 @@ +import { spawnSync } from 'child_process' +import { existsSync } from 'fs' +import { resolve, dirname } from 'path' +import { fileURLToPath } from 'url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const preloadPath = resolve(__dirname, '..', 'dist-electron', 'preload.cjs') + +// 校验 preload 产物语法,避免损坏脚本进入 Electron 运行阶段。 +function verifyElectronPreload() { + if (!existsSync(preloadPath)) { + console.error(`Electron preload not found: ${preloadPath}`) + process.exit(1) + } + + const result = spawnSync(process.execPath, ['--check', preloadPath], { + stdio: 'inherit', + }) + if (result.error) { + console.error('Electron preload syntax check failed:', result.error.message) + process.exit(1) + } + if (result.status !== 0) { + process.exit(result.status ?? 1) + } +} + +verifyElectronPreload() diff --git a/web/src/App.tsx b/web/src/App.tsx new file mode 100644 index 00000000..0844806f --- /dev/null +++ b/web/src/App.tsx @@ -0,0 +1,113 @@ +import { HashRouter, Routes, Route, Navigate } from 'react-router-dom' +import ChatPage from './pages/ChatPage' +import ConnectPage from './pages/ConnectPage' +import { useRuntime } from './context/RuntimeProvider' +import { ErrorBoundary } from './components/ErrorBoundary' + +/** 加载/错误状态全屏遮罩 */ +function LoadingScreen({ message }: { message?: string }) { + return ( +
+ {message || '正在连接 Gateway...'} +
+ ) +} + +/** Electron 错误状态恢复界面 */ +function ElectronErrorScreen({ error, onRetry }: { error: string; onRetry: () => void }) { + return ( +
+
Gateway 连接失败
+
+ {error || '无法连接到 Gateway 服务'} +
+ +
+ ) +} + +function AppRoutes() { + const { status, mode, error, retry, loadingMessage } = useRuntime() + + if (status === 'loading') { + return + } + + if (status === 'needs_config' && mode === 'browser') { + return + } + + if (status === 'connecting') { + return + } + + if (status === 'error' && mode === 'browser') { + return + } + + if (status === 'error' && mode === 'electron') { + return + } + + if (status !== 'connected') { + return + } + + return ( + + } /> + } /> + + ) +} + +function App() { + return ( + + + + + + ) +} + +export default App diff --git a/web/src/api/gateway.ts b/web/src/api/gateway.ts new file mode 100644 index 00000000..0e80843d --- /dev/null +++ b/web/src/api/gateway.ts @@ -0,0 +1,239 @@ +import { type WSClient } from './wsClient' +import { + Method, + type RPCResult, + type AuthenticateParams, + type BindStreamParams, + type RunParams, + type CancelParams, + type LoadSessionParams, + type ResolvePermissionParams, + type Session, + type RunAckResult, + type ListSessionsResult, + type CancelResult, + type DeleteSessionParams, + type DeleteSessionResult, + type RenameSessionParams, + type RenameSessionResult, + type ListFilesParams, + type ListFilesResult, + type ListModelsResult, + type SetSessionModelParams, + type SetSessionModelResult, + type GetSessionModelParams, + type GetSessionModelResult, + type ListProvidersResult, + type CreateProviderParams, + type CreateProviderResult, + type DeleteProviderParams, + type DeleteProviderResult, + type SelectProviderModelParams, + type SelectProviderModelResult, + type ListMCPServersResult, + type UpsertMCPServerParams, + type UpsertMCPServerResult, + type SetMCPServerEnabledParams, + type SetMCPServerEnabledResult, + type DeleteMCPServerParams, + type DeleteMCPServerResult, + type ActivateSessionSkillParams, + type ActivateSessionSkillResult, + type DeactivateSessionSkillParams, + type DeactivateSessionSkillResult, + type ListSessionSkillsParams, + type ListSessionSkillsResult, + type ListAvailableSkillsParams, + type ListAvailableSkillsResult, + type ListWorkspacesResult, + type CreateWorkspaceParams, + type CreateWorkspaceResult, + type SwitchWorkspaceParams, + type SwitchWorkspaceResult, + type RenameWorkspaceParams, + type RenameWorkspaceResult, + type DeleteWorkspaceParams, + type DeleteWorkspaceResult, +} from './protocol' + +/** Gateway 业务 API 客户端,基于 WebSocket 全双工通道 */ +export class GatewayAPI { + private ws: WSClient + + constructor(ws: WSClient) { + this.ws = ws + } + + /** 认证,返回 ack 结果 */ + async authenticate(token: string) { + return this.ws.call(Method.Authenticate, { token } satisfies AuthenticateParams) + } + + /** 绑定事件流到指定会话 */ + async bindStream(params: BindStreamParams) { + return this.ws.call(Method.BindStream, params) + } + + /** 发起一次 run,返回 ack 含 session_id 和 run_id */ + async run(params: RunParams) { + return this.ws.call(Method.Run, params) + } + + /** 取消运行,返回取消结果 */ + async cancel(params: CancelParams) { + return this.ws.call(Method.Cancel, params) + } + + /** 压缩上下文 */ + async compact(sessionId: string, runId: string) { + return this.ws.call>(Method.Compact, { session_id: sessionId, run_id: runId }) + } + + /** 列出所有会话 */ + async listSessions() { + return this.ws.call(Method.ListSessions) + } + + /** 加载会话详情 */ + async loadSession(sessionId: string) { + return this.ws.call>(Method.LoadSession, { session_id: sessionId } satisfies LoadSessionParams) + } + + /** 解析权限请求 */ + async resolvePermission(params: ResolvePermissionParams) { + return this.ws.call(Method.ResolvePermission, params) + } + + /** 执行系统工具 */ + async executeSystemTool(sessionId: string, runId: string, toolName: string, args: any, workdir?: string) { + return this.ws.call(Method.ExecuteSystemTool, { + session_id: sessionId, + run_id: runId, + tool_name: toolName, + arguments: args, + workdir, + }) + } + + /** Ping 网关 */ + async ping() { + return this.ws.call(Method.Ping) + } + + /** 删除/归档会话 */ + async deleteSession(sessionId: string) { + return this.ws.call(Method.DeleteSession, { session_id: sessionId } satisfies DeleteSessionParams) + } + + /** 重命名会话 */ + async renameSession(sessionId: string, title: string) { + return this.ws.call(Method.RenameSession, { session_id: sessionId, title } satisfies RenameSessionParams) + } + + /** 列出工作目录文件树 */ + async listFiles(params: ListFilesParams = {}) { + return this.ws.call(Method.ListFiles, params) + } + + /** 列出可用模型 */ + async listModels(sessionId?: string) { + return this.ws.call(Method.ListModels, sessionId ? { session_id: sessionId } : undefined) + } + + /** 设置会话模型 */ + async setSessionModel(sessionId: string, modelId: string, providerId?: string) { + const params: SetSessionModelParams = { session_id: sessionId, model_id: modelId } + if (providerId) params.provider_id = providerId + return this.ws.call(Method.SetSessionModel, params) + } + + /** 获取当前会话模型 */ + async getSessionModel(sessionId: string) { + return this.ws.call(Method.GetSessionModel, { session_id: sessionId } satisfies GetSessionModelParams) + } + + /** 列出可管理 provider */ + async listProviders() { + return this.ws.call(Method.ListProviders) + } + + /** 创建自定义 provider */ + async createCustomProvider(params: CreateProviderParams) { + return this.ws.call(Method.CreateCustomProvider, params) + } + + /** 删除自定义 provider */ + async deleteCustomProvider(providerId: string) { + return this.ws.call(Method.DeleteCustomProvider, { provider_id: providerId } satisfies DeleteProviderParams) + } + + /** 全局选择 provider/model */ + async selectProviderModel(params: SelectProviderModelParams) { + return this.ws.call(Method.SelectProviderModel, params) + } + + /** 列出 MCP server 配置 */ + async listMCPServers() { + return this.ws.call(Method.ListMCPServers) + } + + /** 新增或更新 MCP server */ + async upsertMCPServer(params: UpsertMCPServerParams) { + return this.ws.call(Method.UpsertMCPServer, params) + } + + /** 启停 MCP server */ + async setMCPServerEnabled(id: string, enabled: boolean) { + return this.ws.call(Method.SetMCPServerEnabled, { id, enabled } satisfies SetMCPServerEnabledParams) + } + + /** 删除 MCP server */ + async deleteMCPServer(id: string) { + return this.ws.call(Method.DeleteMCPServer, { id } satisfies DeleteMCPServerParams) + } + + /** 查询当前可用技能列表 */ + async listAvailableSkills(sessionId?: string) { + return this.ws.call(Method.ListAvailableSkills, sessionId ? { session_id: sessionId } satisfies ListAvailableSkillsParams : undefined) + } + + /** 查询指定会话的激活技能列表 */ + async listSessionSkills(sessionId: string) { + return this.ws.call(Method.ListSessionSkills, { session_id: sessionId } satisfies ListSessionSkillsParams) + } + + /** 在指定会话中激活一个技能 */ + async activateSessionSkill(sessionId: string, skillId: string) { + return this.ws.call(Method.ActivateSessionSkill, { session_id: sessionId, skill_id: skillId } satisfies ActivateSessionSkillParams) + } + + /** 在指定会话中停用一个技能 */ + async deactivateSessionSkill(sessionId: string, skillId: string) { + return this.ws.call(Method.DeactivateSessionSkill, { session_id: sessionId, skill_id: skillId } satisfies DeactivateSessionSkillParams) + } + + /** 列出所有工作区 */ + async listWorkspaces() { + return this.ws.call(Method.ListWorkspaces) + } + + /** 创建工作区 */ + async createWorkspace(path: string, name?: string) { + return this.ws.call(Method.CreateWorkspace, { path, name } satisfies CreateWorkspaceParams) + } + + /** 切换工作区 */ + async switchWorkspace(workspaceHash: string) { + return this.ws.call(Method.SwitchWorkspace, { workspace_hash: workspaceHash } satisfies SwitchWorkspaceParams) + } + + /** 重命名工作区 */ + async renameWorkspace(workspaceHash: string, name: string) { + return this.ws.call(Method.RenameWorkspace, { workspace_hash: workspaceHash, name } satisfies RenameWorkspaceParams) + } + + /** 删除工作区 */ + async deleteWorkspace(workspaceHash: string, removeData?: boolean) { + return this.ws.call(Method.DeleteWorkspace, { workspace_hash: workspaceHash, remove_data: removeData } satisfies DeleteWorkspaceParams) + } +} diff --git a/web/src/api/protocol.ts b/web/src/api/protocol.ts new file mode 100644 index 00000000..34ad98d7 --- /dev/null +++ b/web/src/api/protocol.ts @@ -0,0 +1,586 @@ +/** + * Gateway JSON-RPC 协议常量,从 Go internal/gateway/protocol/jsonrpc.go 对齐。 + */ + +// JSON-RPC 版本 +export const JSONRPC_VERSION = '2.0' + +// RPC 方法名 +export const Method = { + Authenticate: 'gateway.authenticate', + Ping: 'gateway.ping', + BindStream: 'gateway.bindStream', + Run: 'gateway.run', + Cancel: 'gateway.cancel', + Compact: 'gateway.compact', + ListSessions: 'gateway.listSessions', + LoadSession: 'gateway.loadSession', + ResolvePermission: 'gateway.resolvePermission', + ExecuteSystemTool: 'gateway.executeSystemTool', + ActivateSessionSkill: 'gateway.activateSessionSkill', + DeactivateSessionSkill: 'gateway.deactivateSessionSkill', + ListSessionSkills: 'gateway.listSessionSkills', + ListAvailableSkills: 'gateway.listAvailableSkills', + DeleteSession: 'gateway.deleteSession', + RenameSession: 'gateway.renameSession', + ListFiles: 'gateway.listFiles', + ListModels: 'gateway.listModels', + SetSessionModel: 'gateway.setSessionModel', + GetSessionModel: 'gateway.getSessionModel', + ListProviders: 'gateway.listProviders', + CreateCustomProvider: 'gateway.createCustomProvider', + DeleteCustomProvider: 'gateway.deleteCustomProvider', + SelectProviderModel: 'gateway.selectProviderModel', + ListMCPServers: 'gateway.listMCPServers', + UpsertMCPServer: 'gateway.upsertMCPServer', + SetMCPServerEnabled: 'gateway.setMCPServerEnabled', + DeleteMCPServer: 'gateway.deleteMCPServer', + Event: 'gateway.event', + ListWorkspaces: 'gateway.listWorkspaces', + CreateWorkspace: 'gateway.createWorkspace', + SwitchWorkspace: 'gateway.switchWorkspace', + RenameWorkspace: 'gateway.renameWorkspace', + DeleteWorkspace: 'gateway.deleteWorkspace', +} as const + +// 帧类型 +export const FrameType = { + Ack: 'ack', + Error: 'error', + Event: 'event', +} as const + +// 帧动作 +export const FrameAction = { + Run: 'run', + ListProviders: 'list_providers', + CreateCustomProvider: 'create_custom_provider', + DeleteCustomProvider: 'delete_custom_provider', + SelectProviderModel: 'select_provider_model', + ListMCPServers: 'list_mcp_servers', + UpsertMCPServer: 'upsert_mcp_server', + SetMCPServerEnabled: 'set_mcp_server_enabled', + DeleteMCPServer: 'delete_mcp_server', +} as const + +// 运行时事件类型(从 Go internal/tui/services/runtime_contract.go 对齐) +export const EventType = { + UserMessage: 'user_message', + AgentChunk: 'agent_chunk', + AgentDone: 'agent_done', + ToolStart: 'tool_start', + ToolResult: 'tool_result', + ToolChunk: 'tool_chunk', + ToolCallThinking: 'tool_call_thinking', + RunCanceled: 'run_canceled', + Error: 'error', + PermissionRequested: 'permission_requested', + PermissionResolved: 'permission_resolved', + CompactStart: 'compact_start', + CompactApplied: 'compact_applied', + CompactError: 'compact_error', + TokenUsage: 'token_usage', + PhaseChanged: 'phase_changed', + StopReasonDecided: 'stop_reason_decided', + InputNormalized: 'input_normalized', + SkillActivated: 'skill_activated', + SkillDeactivated: 'skill_deactivated', + SkillMissing: 'skill_missing', + TodoUpdated: 'todo_updated', + TodoConflict: 'todo_conflict', + TodoSummaryInjected: 'todo_summary_injected', + AssetSaved: 'asset_saved', + AssetSaveFailed: 'asset_save_failed', + ProgressEvaluated: 'progress_evaluated', + VerificationStarted: 'verification_started', + VerificationStageFinished: 'verification_stage_finished', + VerificationFinished: 'verification_finished', + VerificationCompleted: 'verification_completed', + VerificationFailed: 'verification_failed', + AcceptanceDecided: 'acceptance_decided', +} as const + +// 权限审批决策 +export const PermissionDecision = { + AllowOnce: 'allow_once', + AllowSession: 'allow_session', + Reject: 'reject', +} as const + +// 停止原因 +export const StopReason = { + UserInterrupt: 'user_interrupt', + FatalError: 'fatal_error', + BudgetExceeded: 'budget_exceeded', + MaxTurnExceeded: 'max_turn_exceeded', + Accepted: 'accepted', + RetryExhausted: 'retry_exhausted', +} as const + +// --- 类型定义 --- + +/** JSON-RPC 响应 */ +export interface JSONRPCResponse { + jsonrpc: typeof JSONRPC_VERSION + id: string | number + result?: unknown + error?: JSONRPCError | null +} + +/** JSON-RPC 错误 */ +export interface JSONRPCError { + code: number + message: string + data?: { gateway_code?: string } +} + +/** JSON-RPC 通知(服务端推送) */ +export interface JSONRPCNotification { + jsonrpc: typeof JSONRPC_VERSION + method: string + params?: unknown +} + +/** 网关消息帧 */ +export interface MessageFrame { + type: string + action?: string + session_id?: string + run_id?: string + payload?: unknown +} + +/** 通用 RPC 响应包装(MessageFrame 格式) */ +export interface RPCResult { + type: string + action: string + session_id?: string + run_id?: string + payload: T +} + +/** 运行时事件包裹 */ +export interface RuntimeEventEnvelope { + runtime_event_type: string + turn?: number + phase?: string + timestamp?: string + payload_version?: number + payload?: unknown +} + +/** gateway.authenticate 参数 */ +export interface AuthenticateParams { + token: string +} + +/** gateway.bindStream 参数 */ +export interface BindStreamParams { + session_id: string + run_id?: string + channel?: string +} + +/** gateway.run 参数 */ +export interface RunParams { + session_id?: string + new_session?: boolean + run_id?: string + input_text?: string + input_parts?: RunInputPart[] + workdir?: string + mode?: string +} + +/** gateway.run 输入分片 */ +export interface RunInputPart { + type: string + text?: string + media?: { uri: string; mime_type: string; file_name?: string } +} + +/** gateway.cancel 参数 */ +export interface CancelParams { + session_id?: string + run_id: string +} + +/** gateway.loadSession 参数 */ +export interface LoadSessionParams { + session_id: string +} + +/** gateway.resolvePermission 参数 */ +export interface ResolvePermissionParams { + request_id: string + decision: string +} + +/** 会话摘要 */ +export interface SessionSummary { + id: string + title: string + created_at: string + updated_at: string +} + +/** 会话消息 */ +export interface SessionMessage { + role: string + content: string + tool_calls?: ToolCall[] + tool_call_id?: string + is_error?: boolean +} + +/** 工具调用 */ +export interface ToolCall { + id: string + name: string + arguments: string +} + +/** 会话详情 */ +export interface Session { + id: string + title: string + created_at: string + updated_at: string + workdir?: string + provider?: string + model?: string + agent_mode?: string + messages?: SessionMessage[] +} + +/** Token 用量 */ +export interface TokenUsage { + input_tokens: number + output_tokens: number + input_source?: string + output_source?: string + has_unknown_usage?: boolean + session_input_tokens: number + session_output_tokens: number +} + +/** gateway.run ack 响应 */ +export type RunAckResult = RPCResult<{ message: string }> + +/** gateway.listSessions 响应 */ +export type ListSessionsResult = RPCResult<{ sessions: SessionSummary[] }> + +/** gateway.cancel 响应 */ +export type CancelResult = RPCResult<{ canceled: boolean; run_id: string }> + +/** gateway.deleteSession 参数 */ +export interface DeleteSessionParams { + session_id: string +} + +/** gateway.deleteSession 响应 */ +export type DeleteSessionResult = RPCResult<{ deleted: boolean; session_id: string }> + +/** gateway.renameSession 参数 */ +export interface RenameSessionParams { + session_id: string + title: string +} + +/** gateway.renameSession 响应 */ +export type RenameSessionResult = RPCResult<{ session_id: string; title: string }> + +/** gateway.listFiles 参数 */ +export interface ListFilesParams { + session_id?: string + workdir?: string + path?: string +} + +/** 文件条目 */ +export interface FileEntry { + name: string + path: string + is_dir: boolean + size?: number + mod_time?: string +} + +/** gateway.listFiles 响应 */ +export type ListFilesResult = RPCResult<{ files: FileEntry[] }> + +/** 模型条目 */ +export interface ModelEntry { + id: string + name: string + provider: string +} + +/** gateway.listModels 响应 */ +export type ListModelsResult = RPCResult<{ models: ModelEntry[] }> + +/** gateway.setSessionModel 参数 */ +export interface SetSessionModelParams { + session_id: string + provider_id?: string + model_id: string +} + +/** gateway.setSessionModel 响应 */ +export type SetSessionModelResult = RPCResult<{ session_id: string; model_id: string }> + +/** gateway.getSessionModel 参数 */ +export interface GetSessionModelParams { + session_id: string +} + +/** gateway.getSessionModel 响应 */ +export type GetSessionModelResult = RPCResult<{ model_id: string; model_name?: string; provider?: string }> + +/** 模型能力提示 */ +export interface ProviderModelCapabilityHints { + tool_calling?: string + image_input?: string +} + +/** Provider 模型描述 */ +export interface ProviderModelDescriptor { + id: string + name: string + description?: string + context_window?: number + max_output_tokens?: number + capability_hints?: ProviderModelCapabilityHints +} + +/** Provider 选项 */ +export interface ProviderOption { + id: string + name: string + driver: string + base_url?: string + api_key_env: string + source: string + selected: boolean + models?: ProviderModelDescriptor[] +} + +/** gateway.listProviders 响应 */ +export type ListProvidersResult = RPCResult<{ providers: ProviderOption[] }> + +/** gateway.createCustomProvider 参数 */ +export interface CreateProviderParams { + name: string + driver: string + base_url?: string + chat_api_mode?: string + chat_endpoint_path?: string + api_key_env: string + api_key?: string + model_source?: string + discovery_endpoint_path?: string + models?: ProviderModelDescriptor[] +} + +/** gateway.createCustomProvider 响应 */ +export type CreateProviderResult = RPCResult<{ provider_id: string; model_id: string }> + +/** gateway.deleteCustomProvider 参数 */ +export interface DeleteProviderParams { + provider_id: string +} + +/** gateway.deleteCustomProvider 响应 */ +export type DeleteProviderResult = RPCResult<{ deleted: boolean; provider_id: string }> + +/** gateway.selectProviderModel 参数 */ +export interface SelectProviderModelParams { + provider_id: string + model_id?: string +} + +/** gateway.selectProviderModel 响应 */ +export type SelectProviderModelResult = RPCResult<{ provider_id: string; model_id: string }> + +/** MCP server stdio 参数 */ +export interface MCPStdioParams { + command?: string + args?: string[] + workdir?: string + start_timeout_sec?: number + call_timeout_sec?: number + restart_backoff_sec?: number +} + +/** MCP server 环境变量 */ +export interface MCPEnvVarParams { + name: string + value?: string + value_env?: string +} + +/** MCP server 配置 */ +export interface MCPServerParams { + id: string + enabled?: boolean + source?: string + version?: string + stdio?: MCPStdioParams + env?: MCPEnvVarParams[] +} + +/** gateway.listMCPServers 响应 */ +export type ListMCPServersResult = RPCResult<{ servers: MCPServerParams[] }> + +/** gateway.upsertMCPServer 参数 */ +export interface UpsertMCPServerParams { + server: MCPServerParams +} + +/** gateway.upsertMCPServer 响应 */ +export type UpsertMCPServerResult = RPCResult<{ server: MCPServerParams }> + +/** gateway.setMCPServerEnabled 参数 */ +export interface SetMCPServerEnabledParams { + id: string + enabled: boolean +} + +/** gateway.setMCPServerEnabled 响应 */ +export type SetMCPServerEnabledResult = RPCResult<{ id: string; enabled: boolean }> + +/** gateway.deleteMCPServer 参数 */ +export interface DeleteMCPServerParams { + id: string +} + +/** gateway.deleteMCPServer 响应 */ +export type DeleteMCPServerResult = RPCResult<{ deleted: boolean; id: string }> + +/** 技能来源元信息 */ +export interface SkillSource { + kind: string + layer?: string + root_dir?: string + skill_dir?: string + file_path?: string +} + +/** 技能描述元信息 */ +export interface SkillDescriptor { + id: string + name?: string + description?: string + version?: string + source?: SkillSource + scope?: string +} + +/** 会话技能状态 */ +export interface SessionSkillState { + skill_id: string + missing?: boolean + descriptor?: SkillDescriptor +} + +/** 可用技能状态 */ +export interface AvailableSkillState { + descriptor: SkillDescriptor + active: boolean +} + +/** gateway.activateSessionSkill 参数 */ +export interface ActivateSessionSkillParams { + session_id: string + skill_id: string +} + +/** gateway.activateSessionSkill 响应 */ +export type ActivateSessionSkillResult = RPCResult<{ session_id: string; skill_id: string; message: string }> + +/** gateway.deactivateSessionSkill 参数 */ +export interface DeactivateSessionSkillParams { + session_id: string + skill_id: string +} + +/** gateway.deactivateSessionSkill 响应 */ +export type DeactivateSessionSkillResult = RPCResult<{ session_id: string; skill_id: string; message: string }> + +/** gateway.listSessionSkills 参数 */ +export interface ListSessionSkillsParams { + session_id: string +} + +/** gateway.listSessionSkills 响应 */ +export type ListSessionSkillsResult = RPCResult<{ skills: SessionSkillState[] }> + +/** gateway.listAvailableSkills 参数 */ +export interface ListAvailableSkillsParams { + session_id?: string +} + +/** gateway.listAvailableSkills 响应 */ +export type ListAvailableSkillsResult = RPCResult<{ skills: AvailableSkillState[] }> + +/** 权限请求载荷 */ +export interface PermissionRequestPayload { + request_id: string + tool_call_id: string + tool_name: string + tool_category: string + action_type: string + operation: string + target_type: string + target: string + decision: string + reason: string + rule_id?: string +} + +/** 工作区记录 */ +export interface Workspace { + hash: string + path: string + name: string + created_at: string + updated_at: string +} + +/** gateway.listWorkspaces 响应 */ +export type ListWorkspacesResult = RPCResult<{ workspaces: Workspace[] }> + +/** gateway.createWorkspace 参数 */ +export interface CreateWorkspaceParams { + path: string + name?: string +} + +/** gateway.createWorkspace 响应 */ +export type CreateWorkspaceResult = RPCResult<{ workspace: Workspace }> + +/** gateway.switchWorkspace 参数 */ +export interface SwitchWorkspaceParams { + workspace_hash: string +} + +/** gateway.switchWorkspace 响应 */ +export type SwitchWorkspaceResult = RPCResult<{ workspace_hash: string }> + +/** gateway.renameWorkspace 参数 */ +export interface RenameWorkspaceParams { + workspace_hash: string + name: string +} + +/** gateway.renameWorkspace 响应 */ +export type RenameWorkspaceResult = RPCResult<{ hash: string; name: string }> + +/** gateway.deleteWorkspace 参数 */ +export interface DeleteWorkspaceParams { + workspace_hash: string + remove_data?: boolean +} + +/** gateway.deleteWorkspace 响应 */ +export type DeleteWorkspaceResult = RPCResult<{ hash: string }> diff --git a/web/src/api/wsClient.ts b/web/src/api/wsClient.ts new file mode 100644 index 00000000..ae85783d --- /dev/null +++ b/web/src/api/wsClient.ts @@ -0,0 +1,449 @@ +import { + JSONRPC_VERSION, + Method, + type JSONRPCNotification, + type JSONRPCResponse, + type MessageFrame, +} from './protocol' + +/** WS 连接状态 */ +export type WSConnectionState = 'disconnected' | 'connecting' | 'connected' | 'error' | 'permanent_error' + +/** WS 事件回调 */ +export type WSEventHandler = (frame: MessageFrame) => void + +/** WS 连接状态变更回调 */ +export type WSStateHandler = (state: WSConnectionState) => void + +/** WS 重连成功回调 */ +export type WSReconnectHandler = () => void + +/** WS 客户端配置 */ +export interface WSClientConfig { + /** Gateway 基础地址,例如 http://127.0.0.1:8080 */ + baseURL?: string + /** WS 端点路径,默认 /ws */ + endpoint?: string + /** 认证 Token */ + token?: string + /** 重连基础间隔(毫秒),默认 1000 */ + reconnectBaseInterval?: number + /** 重连最大间隔(毫秒),默认 30000 */ + reconnectMaxInterval?: number + /** 最大重连次数,默认 30 */ + maxReconnectAttempts?: number + /** 心跳超时(毫秒),默认 15000(5 个心跳周期) */ + heartbeatTimeout?: number + /** 心跳检测间隔(毫秒),默认 5000 */ + heartbeatCheckInterval?: number + /** 单次 RPC 请求超时(毫秒),默认 30000 */ + rpcTimeout?: number + /** 认证超时(毫秒),默认 5000(需小于服务端 3s+Wiggle room) */ + authTimeout?: number +} + +interface PendingRPC { + resolve: (value: unknown) => void + reject: (reason: unknown) => void + timer: ReturnType +} + +/** + * 管理 Gateway WebSocket 连接。 + * 全双工单通道:RPC 请求/响应 + 事件推送共用同一连接。 + */ +export function createWSClient(config: WSClientConfig = {}) { + const baseURL = normalizeBaseURL(config.baseURL) + const endpoint = config.endpoint ?? '/ws' + const reconnectBaseInterval = config.reconnectBaseInterval ?? 1000 + const reconnectMaxInterval = config.reconnectMaxInterval ?? 30000 + const maxReconnectAttempts = config.maxReconnectAttempts ?? 30 + const heartbeatTimeout = config.heartbeatTimeout ?? 15000 + const heartbeatCheckInterval = config.heartbeatCheckInterval ?? 5000 + const rpcTimeout = config.rpcTimeout ?? 30000 + const authTimeout = config.authTimeout ?? 5000 + + let ws: WebSocket | null = null + let nextId = 1 + let reconnectAttempts = 0 + let reconnectTimer: ReturnType | null = null + let state: WSConnectionState = 'disconnected' + let token = config.token?.trim() ?? '' + let lastMessageAt = 0 + let heartbeatCheckTimer: ReturnType | null = null + + // Promise that resolves when WS opens, rejects on close/error before open + let openResolve: (() => void) | null = null + let openReject: ((reason: unknown) => void) | null = null + let openPromise: Promise | null = null + + const pendingRPCs = new Map() + const eventHandlers: WSEventHandler[] = [] + const stateHandlers: WSStateHandler[] = [] + const reconnectHandlers: WSReconnectHandler[] = [] + + function setState(s: WSConnectionState) { + state = s + stateHandlers.forEach((h) => h(s)) + } + + function getState(): WSConnectionState { + return state + } + + function setToken(t: string) { + token = t + } + + function onEvent(handler: WSEventHandler) { + eventHandlers.push(handler) + return () => { + const idx = eventHandlers.indexOf(handler) + if (idx >= 0) eventHandlers.splice(idx, 1) + } + } + + function onStateChange(handler: WSStateHandler) { + stateHandlers.push(handler) + return () => { + const idx = stateHandlers.indexOf(handler) + if (idx >= 0) stateHandlers.splice(idx, 1) + } + } + + function onReconnect(handler: WSReconnectHandler) { + reconnectHandlers.push(handler) + return () => { + const idx = reconnectHandlers.indexOf(handler) + if (idx >= 0) reconnectHandlers.splice(idx, 1) + } + } + + /** 构建 WebSocket URL */ + function buildWSURL(): string { + const wsBase = baseURL + .replace(/^http:\/\//, 'ws://') + .replace(/^https:\/\//, 'wss://') + const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}` + let url = `${wsBase}${normalizedEndpoint}` + if (token) { + const separator = url.includes('?') ? '&' : '?' + url = `${url}${separator}token=${encodeURIComponent(token)}` + } + return url + } + + /** 建立连接 */ + function connect() { + if (ws) { + ws.close() + ws = null + } + + setState('connecting') + lastMessageAt = Date.now() + + // Create open promise so call() can wait for the socket to open + openPromise = new Promise((resolve, reject) => { + openResolve = resolve + openReject = reject + }) + + const url = buildWSURL() + ws = new WebSocket(url) + + ws.onopen = () => { + lastMessageAt = Date.now() + const wasReconnect = reconnectAttempts > 0 + reconnectAttempts = 0 + setState('connected') + startHeartbeatCheck() + + // Resolve the open promise so any pending call() can proceed + openResolve?.() + openResolve = null + openReject = null + + if (wasReconnect) { + reconnectHandlers.forEach((h) => h()) + } + } + + ws.onmessage = (event: MessageEvent) => { + lastMessageAt = Date.now() + handleMessage(event.data) + } + + ws.onclose = () => { + stopHeartbeatCheck() + + // Reject all pending RPCs immediately (same as disconnect) + for (const [, pending] of pendingRPCs) { + clearTimeout(pending.timer) + pending.reject(new Error('WebSocket connection closed')) + } + pendingRPCs.clear() + + // Reject the open promise if it hasn't resolved yet + openReject?.(new Error('WebSocket connection closed before opening')) + openResolve = null + openReject = null + openPromise = null + + if (state !== 'disconnected') { + setState('error') + scheduleReconnect() + } + } + + ws.onerror = () => { + // onclose will fire after onerror, reconnect logic is in onclose + } + } + + /** 处理收到的消息 */ + function handleMessage(data: string) { + let parsed: unknown + try { + parsed = JSON.parse(data) + } catch { + console.warn('[WSClient] Failed to parse message:', data) + return + } + + // JSON-RPC response (has id) + if (isJSONRPCResponse(parsed)) { + const resp = parsed as JSONRPCResponse + const pending = pendingRPCs.get(resp.id) + if (pending) { + clearTimeout(pending.timer) + pendingRPCs.delete(resp.id) + if (resp.error) { + const gatewayCode = resp.error.data?.gateway_code ?? '' + pending.reject(new Error(`RPC ${resp.error.code}: ${resp.error.message}${gatewayCode ? ` (${gatewayCode})` : ''}`)) + } else { + pending.resolve(resp.result) + } + } + return + } + + // JSON-RPC notification (no id, has method) + if (isJSONRPCNotification(parsed)) { + const notification = parsed as JSONRPCNotification + if (notification.method === Method.Event && notification.params) { + const frame = notification.params as MessageFrame + eventHandlers.forEach((h) => h(frame)) + } + // heartbeat notifications are handled implicitly by lastMessageAt update + return + } + + // Heartbeat payload (simple object with type: "heartbeat") + if (isHeartbeatPayload(parsed)) { + return + } + } + + /** RPC 调用,自动等待 WS 打开后发送 */ + async function call(method: string, params?: unknown): Promise { + // Wait for the socket to open (with a short timeout) + if (openPromise && ws?.readyState !== WebSocket.OPEN) { + const waitTimeout = new Promise((_, reject) => + setTimeout(() => reject(new Error('WebSocket open timeout')), authTimeout) + ) + await Promise.race([openPromise, waitTimeout]) + } + + if (!ws || ws.readyState !== WebSocket.OPEN) { + throw new Error('WebSocket is not connected') + } + + const socket = ws + + return new Promise((resolve, reject) => { + const id = nextId++ + const request = { + jsonrpc: JSONRPC_VERSION, + id, + method, + ...(params !== undefined ? { params } : {}), + } + + const timer = setTimeout(() => { + pendingRPCs.delete(id) + reject(new Error(`RPC timeout: ${method}`)) + }, rpcTimeout) + + pendingRPCs.set(id, { resolve: resolve as (v: unknown) => void, reject, timer }) + + try { + socket.send(JSON.stringify(request)) + } catch (err) { + clearTimeout(timer) + pendingRPCs.delete(id) + reject(err) + } + }) + } + + /** 安排重连(指数退避 + 抖动) */ + function scheduleReconnect() { + if (reconnectAttempts >= maxReconnectAttempts) { + setState('permanent_error') + return + } + + const delay = Math.min(reconnectMaxInterval, reconnectBaseInterval * Math.pow(2, reconnectAttempts)) + const jitter = delay * 0.2 * Math.random() + const finalDelay = delay + jitter + + reconnectAttempts++ + reconnectTimer = setTimeout(() => { + connect() + }, finalDelay) + } + + /** 主动重连(用于 visibilitychange/online 事件) */ + function reconnect() { + if (reconnectTimer) { + clearTimeout(reconnectTimer) + reconnectTimer = null + } + if (ws) { + ws.close() + ws = null + } + reconnectAttempts = 0 + connect() + } + + /** 断开连接 */ + function disconnect() { + if (reconnectTimer) { + clearTimeout(reconnectTimer) + reconnectTimer = null + } + stopHeartbeatCheck() + + // Reject all pending RPCs + for (const [, pending] of pendingRPCs) { + clearTimeout(pending.timer) + pending.reject(new Error('WebSocket disconnected')) + } + pendingRPCs.clear() + + if (ws) { + ws.onclose = null + ws.onerror = null + ws.onmessage = null + ws.onopen = null + ws.close() + ws = null + } + + reconnectAttempts = 0 + setState('disconnected') + } + + /** 启动心跳检测 */ + function startHeartbeatCheck() { + stopHeartbeatCheck() + heartbeatCheckTimer = setInterval(() => { + if (state === 'connected' && Date.now() - lastMessageAt > heartbeatTimeout) { + // No message received for too long, connection is likely dead + if (ws) { + ws.close() + } + // onclose handler will trigger reconnect + } + }, heartbeatCheckInterval) + } + + /** 停止心跳检测 */ + function stopHeartbeatCheck() { + if (heartbeatCheckTimer) { + clearInterval(heartbeatCheckTimer) + heartbeatCheckTimer = null + } + } + + /** 注册 visibilitychange / online / offline 事件 */ + function setupVisibilityAndNetworkHandlers() { + document.addEventListener('visibilitychange', handleVisibilityChange) + window.addEventListener('online', handleOnline) + } + + function teardownVisibilityAndNetworkHandlers() { + document.removeEventListener('visibilitychange', handleVisibilityChange) + window.removeEventListener('online', handleOnline) + } + + function handleVisibilityChange() { + if (document.visibilityState === 'visible' && state === 'error') { + reconnect() + } + } + + function handleOnline() { + if (state === 'error') { + reconnect() + } + } + + let visibilityHandlersInstalled = false + + function connectWithHandlers() { + if (!visibilityHandlersInstalled) { + setupVisibilityAndNetworkHandlers() + visibilityHandlersInstalled = true + } + connect() + } + + function disconnectWithHandlers() { + if (visibilityHandlersInstalled) { + teardownVisibilityAndNetworkHandlers() + visibilityHandlersInstalled = false + } + disconnect() + } + + return { + connect: connectWithHandlers, + disconnect: disconnectWithHandlers, + reconnect, + call, + onEvent, + onStateChange, + onReconnect, + getState, + setToken, + } +} + +export type WSClient = ReturnType + +// --- Helpers --- + +function normalizeBaseURL(baseURL?: string) { + return (baseURL ?? '').trim().replace(/\/+$/, '') +} + +function isJSONRPCResponse(data: unknown): data is JSONRPCResponse { + if (typeof data !== 'object' || data === null) return false + const obj = data as Record + return 'id' in obj && ('result' in obj || 'error' in obj) +} + +function isJSONRPCNotification(data: unknown): data is JSONRPCNotification { + if (typeof data !== 'object' || data === null) return false + const obj = data as Record + return 'method' in obj && !('id' in obj) +} + +function isHeartbeatPayload(data: unknown): boolean { + if (typeof data !== 'object' || data === null) return false + const obj = data as Record + return obj.type === 'heartbeat' +} diff --git a/web/src/components/ErrorBoundary.tsx b/web/src/components/ErrorBoundary.tsx new file mode 100644 index 00000000..80fdd4e4 --- /dev/null +++ b/web/src/components/ErrorBoundary.tsx @@ -0,0 +1,103 @@ +import { Component, type ReactNode } from 'react' + +interface ErrorBoundaryProps { + children: ReactNode + fallback?: (error: Error, retry: () => void) => ReactNode +} + +interface ErrorBoundaryState { + hasError: boolean + error: Error | null +} + +/** + * React ErrorBoundary 防止组件异常导致白屏。 + * 捕获渲染阶段错误并显示恢复界面。 + */ +export class ErrorBoundary extends Component { + constructor(props: ErrorBoundaryProps) { + super(props) + this.state = { hasError: false, error: null } + } + + static getDerivedStateFromError(error: Error): ErrorBoundaryState { + return { hasError: true, error } + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.error('[ErrorBoundary] Caught error:', error, errorInfo) + } + + handleRetry = () => { + this.setState({ hasError: false, error: null }) + } + + handleReload = () => { + window.location.reload() + } + + render() { + if (this.state.hasError) { + if (this.props.fallback && this.state.error) { + return this.props.fallback(this.state.error, this.handleRetry) + } + + return ( +
+
应用遇到了一个错误
+
+ {this.state.error?.message || '未知错误'} +
+
+ + +
+
+ ) + } + + return this.props.children + } +} diff --git a/web/src/components/chat/ChatInput.tsx b/web/src/components/chat/ChatInput.tsx new file mode 100644 index 00000000..f77f9bd6 --- /dev/null +++ b/web/src/components/chat/ChatInput.tsx @@ -0,0 +1,525 @@ +import { useState, useRef, useEffect, useCallback } from 'react' +import { useChatStore, createUserMessage } from '@/stores/useChatStore' +import { useGatewayStore } from '@/stores/useGatewayStore' +import { useSessionStore, isValidSessionId } from '@/stores/useSessionStore' +import { useUIStore } from '@/stores/useUIStore' +import { useGatewayAPI } from '@/context/RuntimeProvider' +import { + builtinSlashCommands, + matchSlashCommands, + parseSlashCommand, + isSlashCommand, + type AnySlashCommand, + type SkillSlashCommand, + isSkillCommand, +} from '@/utils/slashCommands' +import SlashCommandMenu from './SlashCommandMenu' +import SkillPicker from './SkillPicker' +import { Send, Square, Paperclip, AtSign } from 'lucide-react' + +/** 聊天输入框 */ +export default function ChatInput() { + const gatewayAPI = useGatewayAPI() + const [text, setText] = useState('') + const [rows, setRows] = useState(1) + const textareaRef = useRef(null) + const containerRef = useRef(null) + const runCancelledRef = useRef(false) + const isGenerating = useChatStore((s) => s.isGenerating) + const addMessage = useChatStore((s) => s.addMessage) + const addSystemMessage = useChatStore((s) => s.addSystemMessage) + const setGenerating = useChatStore((s) => s.setGenerating) + const sessionId = useSessionStore((s) => s.currentSessionId) + const agentMode = useChatStore((s) => s.agentMode) + const setAgentMode = useChatStore((s) => s.setAgentMode) + + // Slash command 菜单状态 + const [showSlashMenu, setShowSlashMenu] = useState(false) + const [selectedIndex, setSelectedIndex] = useState(0) + const [matchedCommands, setMatchedCommands] = useState([]) + const [availableSkillCommands, setAvailableSkillCommands] = useState([]) + + // 技能选择弹层 + const [showSkillPicker, setShowSkillPicker] = useState(false) + + // 动态加载技能列表用于 slash 菜单 + useEffect(() => { + if (!showSlashMenu || !gatewayAPI) return + + gatewayAPI.listAvailableSkills(sessionId || undefined).then((result) => { + const skills = result.payload?.skills || [] + const skillCommands: SkillSlashCommand[] = skills.map((s) => ({ + id: `skill-${s.descriptor.id}`, + usage: `/${s.descriptor.id}`, + description: s.descriptor.description || '技能', + hasArgument: false, + isSkill: true, + skillId: s.descriptor.id, + active: s.active, + })) + setAvailableSkillCommands(skillCommands) + }).catch(() => { + setAvailableSkillCommands([]) + }) + }, [showSlashMenu, gatewayAPI, sessionId]) + + // 合并内置命令和技能命令,并做匹配 + useEffect(() => { + if (!isSlashCommand(text)) { + setShowSlashMenu(false) + return + } + const allCommands: AnySlashCommand[] = [...builtinSlashCommands, ...availableSkillCommands] + const matched = matchSlashCommands(text, allCommands) + if (matched.length > 0) { + setMatchedCommands(matched) + setShowSlashMenu(true) + setSelectedIndex(0) + } else { + setShowSlashMenu(false) + } + }, [text, availableSkillCommands]) + + // 行数自适应 + useEffect(() => { + const lines = text.split('\n').length + setRows(Math.min(Math.max(lines, 1), 8)) + }, [text]) + + /** 执行 slash command */ + const executeSlashCommand = useCallback(async (input: string): Promise => { + const parsed = parseSlashCommand(input) + if (!parsed) return false + + const { command, argument } = parsed + const currentSessionId = sessionId + const api = gatewayAPI + + if (!api) { + useUIStore.getState().showToast('Gateway 未连接', 'error') + return true + } + + switch (command) { + case '/help': { + const allUsages = [...builtinSlashCommands.map((c) => c.usage), '/'] + const maxLen = Math.max(...allUsages.map((u) => u.length)) + const helpLines = [ + '可用命令:', + ...builtinSlashCommands.map((cmd) => { + const pad = ' '.repeat(maxLen - cmd.usage.length) + return ` ${cmd.usage}${pad} — ${cmd.description}` + }), + ` /${''.padEnd(maxLen - 1)} — 激活/停用技能`, + ] + addSystemMessage(helpLines.join('\n')) + return true + } + + case '/compact': { + if (!isValidSessionId(currentSessionId)) { + useUIStore.getState().showToast('请先发送消息建立会话', 'error') + return true + } + try { + await api.compact(currentSessionId, '') + } catch (err) { + console.error('Compact failed:', err) + useUIStore.getState().showToast('压缩失败', 'error') + } + return true + } + + case '/memo': { + if (!isValidSessionId(currentSessionId)) { + useUIStore.getState().showToast('请先发送消息建立会话', 'error') + return true + } + try { + const result = await api.executeSystemTool(currentSessionId, '', 'memo_list', {}) + const content = (result as any)?.payload?.content || '备忘录查询完成' + addSystemMessage(content) + } catch (err) { + console.error('Memo list failed:', err) + useUIStore.getState().showToast('查询备忘录失败', 'error') + } + return true + } + + case '/remember': { + if (!argument) { + useUIStore.getState().showToast('用法: /remember <内容>', 'error') + return true + } + if (!isValidSessionId(currentSessionId)) { + useUIStore.getState().showToast('请先发送消息建立会话', 'error') + return true + } + try { + const result = await api.executeSystemTool(currentSessionId, '', 'memo_remember', { + type: 'user', + title: argument, + content: argument, + }) + const content = (result as any)?.payload?.content || '备忘录已保存' + addSystemMessage(content) + } catch (err) { + console.error('Remember failed:', err) + useUIStore.getState().showToast('保存备忘录失败', 'error') + } + return true + } + + case '/forget': { + if (!argument) { + useUIStore.getState().showToast('用法: /forget <关键词>', 'error') + return true + } + if (!isValidSessionId(currentSessionId)) { + useUIStore.getState().showToast('请先发送消息建立会话', 'error') + return true + } + try { + const result = await api.executeSystemTool(currentSessionId, '', 'memo_remove', { + keyword: argument, + scope: 'all', + }) + const content = (result as any)?.payload?.content || '备忘录已删除' + addSystemMessage(content) + } catch (err) { + console.error('Forget failed:', err) + useUIStore.getState().showToast('删除备忘录失败', 'error') + } + return true + } + + case '/skills': { + setShowSkillPicker(true) + return true + } + + default: { + if (isGenerating) { + useUIStore.getState().showToast('生成中无法切换技能,请等待当前对话完成', 'info') + return true + } + // 检查是否是技能快捷命令 + const skillCmd = availableSkillCommands.find((s) => s.usage === command) + if (skillCmd && isValidSessionId(currentSessionId)) { + try { + if (skillCmd.active) { + await api.deactivateSessionSkill(currentSessionId, skillCmd.skillId) + } else { + await api.activateSessionSkill(currentSessionId, skillCmd.skillId) + } + // 刷新技能列表状态 + setAvailableSkillCommands((prev) => + prev.map((item) => + item.skillId === skillCmd.skillId ? { ...item, active: !item.active } : item + ) + ) + } catch (err) { + console.error('Skill toggle failed:', err) + useUIStore.getState().showToast('技能操作失败', 'error') + } + return true + } + return false + } + } + }, [gatewayAPI, sessionId, addSystemMessage, availableSkillCommands, isGenerating]) + + async function handleSubmit() { + const input = text.trim() + if (!input) return + if (isGenerating) { + if (isSlashCommand(input)) { + useUIStore.getState().showToast('生成中无法执行命令,请等待当前对话完成', 'info') + } + return + } + + // Slash command 拦截 + if (isSlashCommand(input)) { + setText('') + setShowSlashMenu(false) + const handled = await executeSlashCommand(input) + if (handled) return + // 未知命令回退为普通消息 + } + + setText('') + const userMsg = createUserMessage(input) + addMessage(userMsg) + setGenerating(true) + runCancelledRef.current = false + + try { + if (!gatewayAPI) return + const isNewSession = !isValidSessionId(sessionId) + const ack = await gatewayAPI.run({ + session_id: isNewSession ? undefined : sessionId, + new_session: isNewSession ? true : undefined, + input_text: input, + mode: agentMode, + }) + + // 仅在未被取消时回写 session_id 和 run_id + if (!runCancelledRef.current) { + const gwStore = useGatewayStore.getState() + const sessStore = useSessionStore.getState() + if (ack.run_id) { + gwStore.setCurrentRunId(ack.run_id) + } + if (ack.session_id) { + sessStore.setCurrentSessionId(ack.session_id) + // 将 stream 绑定到新会话,确保后续事件的 frameSessionId 正确 + if (gatewayAPI) { + gatewayAPI.bindStream({ session_id: ack.session_id, channel: 'all' }).catch(() => {}) + } + } + } + } catch (err) { + if (!runCancelledRef.current) { + setGenerating(false) + useChatStore.getState().removeMessage(userMsg.id) + console.error('Run failed:', err) + useUIStore.getState().showToast('消息发送失败,请重试', 'error') + } + } + } + + function handleKeyDown(e: React.KeyboardEvent) { + if (!showSlashMenu) { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault() + handleSubmit() + } + return + } + + // 菜单打开时的键盘导航 + switch (e.key) { + case 'ArrowDown': { + e.preventDefault() + setSelectedIndex((prev) => (prev + 1) % matchedCommands.length) + return + } + case 'ArrowUp': { + e.preventDefault() + setSelectedIndex((prev) => (prev - 1 + matchedCommands.length) % matchedCommands.length) + return + } + case 'Enter': { + e.preventDefault() + const cmd = matchedCommands[selectedIndex] + if (cmd) { + handleSelectCommand(cmd) + } + return + } + case 'Escape': { + e.preventDefault() + setShowSlashMenu(false) + return + } + case 'Tab': { + e.preventDefault() + const cmd = matchedCommands[selectedIndex] + if (cmd) { + setText(cmd.usage + ' ') + textareaRef.current?.focus() + } + return + } + } + } + + function handleSelectCommand(cmd: AnySlashCommand) { + if (isSkillCommand(cmd)) { + // 技能命令:直接切换激活状态 + setText(cmd.usage) + setShowSlashMenu(false) + executeSlashCommand(cmd.usage) + return + } + + if (cmd.hasArgument) { + // 需要参数的命令:填充 usage 到输入框,保留菜单关闭让用户继续输入 + setText(cmd.usage + ' ') + setShowSlashMenu(false) + textareaRef.current?.focus() + } else { + // 无参数命令:直接执行 + setText('') + setShowSlashMenu(false) + executeSlashCommand(cmd.usage) + } + } + + async function handleCancel() { + runCancelledRef.current = true + const runId = useGatewayStore.getState().currentRunId + if (runId && gatewayAPI) { + try { + await gatewayAPI.cancel({ run_id: runId }) + } catch (err) { + console.error('Cancel failed:', err) + } + } + useChatStore.getState().resetGeneratingState() + } + + return ( + <> + {showSkillPicker && gatewayAPI && ( + setShowSkillPicker(false)} + /> + )} +
+
+ {showSlashMenu && matchedCommands.length > 0 && ( + + )} +
+