Unified Agent + reusable Go agent core.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Feat/workspace dir (#37)

* docs: add workspace_dir feature note

* docs: broaden workspace attachment design

* docs: simplify workspace attachment design

* feat: implement phase 1 workspace attachments

* feat: add console workspace backend

* fix: wait for terminal output capture before exit

* docs: update workspace console backend design note

* feat: add console workspace browser

* feat: improve console workspace support

* fix: lock workspace attachment writes across processes

authored by

Lyric Wai and committed by
GitHub
ec4b72af 70f024a0

+9889 -407
+5 -3
cmd/mistermorph/chatcmd/agents.go
··· 12 12 "time" 13 13 14 14 "github.com/quailyquaily/mistermorph/agent" 15 + "github.com/quailyquaily/mistermorph/internal/pathroots" 15 16 "github.com/quailyquaily/mistermorph/llm" 16 17 ) 17 18 ··· 33 34 func handleAgentsGenerate( 34 35 writer io.Writer, 35 36 input string, 36 - chatFileCacheDir string, 37 + projectDir string, 37 38 timeout time.Duration, 38 39 engine *agent.Engine, 39 40 model string, 40 41 history []llm.Message, 41 42 ) ([]llm.Message, bool) { 42 - agentsPath := filepath.Join(chatFileCacheDir, "AGENTS.md") 43 + agentsPath := filepath.Join(projectDir, "AGENTS.md") 43 44 isUpdate := strings.ToLower(input) == "/update" 44 45 if isUpdate { 45 46 _, _ = fmt.Fprintln(writer, "\033[33m⚙️ Regenerating AGENTS.md...\033[0m") ··· 73 74 74 75 Use bash and read_file tools to explore the project structure, README, go.mod, package.json, Makefile, etc. to gather accurate information. 75 76 76 - IMPORTANT: Do NOT use the write_file tool. Instead, write the final AGENTS.md content directly as your response text. Use markdown format. Be concise but thorough.`, chatFileCacheDir) 77 + IMPORTANT: Do NOT use the write_file tool. Instead, write the final AGENTS.md content directly as your response text. Use markdown format. Be concise but thorough.`, projectDir) 78 + initCtx = pathroots.WithWorkspaceDir(initCtx, projectDir) 77 79 final, _, err := engine.Run(initCtx, initPrompt, agent.RunOptions{ 78 80 Model: strings.TrimSpace(model), 79 81 Scene: "chat.init",
+2 -10
cmd/mistermorph/chatcmd/chat.go
··· 3 3 import ( 4 4 "fmt" 5 5 "log/slog" 6 - "os" 7 - "path/filepath" 8 6 "time" 9 7 10 8 "github.com/quailyquaily/mistermorph/guard" ··· 47 45 cmd.Flags().Duration("timeout", 30*time.Minute, "Overall timeout.") 48 46 cmd.Flags().Bool("compact-mode", false, "Compact display mode: omit user/assistant name prefixes in prompts and output.") 49 47 cmd.Flags().Bool("verbose", false, "Show info-level logs (default: only errors).") 48 + cmd.Flags().String("workspace", "", "Attach a workspace directory for this chat session.") 49 + cmd.Flags().Bool("no-workspace", false, "Start chat without a workspace attachment.") 50 50 51 51 return cmd 52 52 } 53 - 54 - func resolveChatFileCacheDir() (string, error) { 55 - wd, err := os.Getwd() 56 - if err != nil { 57 - return "", fmt.Errorf("resolve working directory for chat file_cache_dir: %w", err) 58 - } 59 - return filepath.Clean(wd), nil 60 - }
+64 -6
cmd/mistermorph/chatcmd/commands.go
··· 3 3 import ( 4 4 "context" 5 5 "fmt" 6 - "github.com/quailyquaily/mistermorph/internal/chatcommands" 7 - "github.com/quailyquaily/mistermorph/llm" 8 6 "io" 9 7 "path/filepath" 8 + 9 + "github.com/quailyquaily/mistermorph/internal/chatcommands" 10 + "github.com/quailyquaily/mistermorph/internal/workspace" 11 + "github.com/quailyquaily/mistermorph/llm" 10 12 ) 11 13 12 14 // registerChatCommands binds all slash commands into the given registry. ··· 37 39 38 40 reg.Register("/help", chatcommands.HelpHandler(reg, "Available commands:")) 39 41 42 + reg.Register("/workspace", func(ctx context.Context, args string) (*chatcommands.Result, error) { 43 + cmd, err := workspace.ParseCommandArgs(args) 44 + if err != nil { 45 + return &chatcommands.Result{Reply: err.Error()}, nil 46 + } 47 + switch cmd.Action { 48 + case workspace.CommandStatus: 49 + return &chatcommands.Result{Reply: workspace.StatusText(sess.workspaceDir)}, nil 50 + case workspace.CommandAttach: 51 + dir, err := workspace.ValidateDir(cmd.Dir, nil) 52 + if err != nil { 53 + return &chatcommands.Result{Reply: "error: " + err.Error()}, nil 54 + } 55 + oldDir := sess.workspaceDir 56 + sess.workspaceDir = dir 57 + sess.refreshProjectScope() 58 + if err := sess.rebuildRuntimeState(); err != nil { 59 + sess.workspaceDir = oldDir 60 + sess.refreshProjectScope() 61 + _ = sess.rebuildRuntimeState() 62 + return nil, err 63 + } 64 + return &chatcommands.Result{Reply: workspace.AttachText(oldDir, dir, oldDir != "")}, nil 65 + case workspace.CommandDetach: 66 + oldDir := sess.workspaceDir 67 + sess.workspaceDir = "" 68 + sess.refreshProjectScope() 69 + if err := sess.rebuildRuntimeState(); err != nil { 70 + sess.workspaceDir = oldDir 71 + sess.refreshProjectScope() 72 + _ = sess.rebuildRuntimeState() 73 + return nil, err 74 + } 75 + return &chatcommands.Result{Reply: workspace.DetachText(oldDir, oldDir != "")}, nil 76 + default: 77 + return &chatcommands.Result{Reply: "error: unsupported workspace command"}, nil 78 + } 79 + }) 80 + 40 81 reg.Register("/remember", func(ctx context.Context, args string) (*chatcommands.Result, error) { 41 82 if args == "" { 42 83 return &chatcommands.Result{Reply: "Usage: /remember <content>"}, nil ··· 71 112 }) 72 113 73 114 reg.Register("/init", func(ctx context.Context, args string) (*chatcommands.Result, error) { 74 - agentsPath := filepath.Join(sess.chatFileCacheDir, "AGENTS.md") 115 + projectDir := sess.projectDir() 116 + agentsPath := filepath.Join(projectDir, "AGENTS.md") 75 117 if handleInitRead(writer, agentsPath) { 76 118 return &chatcommands.Result{}, nil 77 119 } 78 - newHistory, ok := handleAgentsGenerate(writer, "/init", sess.chatFileCacheDir, sess.timeout, sess.engine, sess.mainCfg.Model, *history) 120 + newHistory, ok := handleAgentsGenerate(writer, "/init", projectDir, sess.timeout, sess.engine, sess.mainCfg.Model, *history) 79 121 if ok { 80 122 *history = newHistory 81 123 } ··· 83 125 }) 84 126 85 127 reg.Register("/update", func(ctx context.Context, args string) (*chatcommands.Result, error) { 86 - newHistory, ok := handleAgentsGenerate(writer, "/update", sess.chatFileCacheDir, sess.timeout, sess.engine, sess.mainCfg.Model, *history) 128 + newHistory, ok := handleAgentsGenerate(writer, "/update", sess.projectDir(), sess.timeout, sess.engine, sess.mainCfg.Model, *history) 87 129 if ok { 88 130 *history = newHistory 89 131 } ··· 98 140 99 141 // handleHelp prints the help text. 100 142 func handleHelp(writer io.Writer) { 101 - _, _ = fmt.Fprintln(writer, "Commands: /exit, /quit, /reset, /memory, /remember <content>, /model, /init, /update, /help") 143 + _, _ = fmt.Fprintln(writer, "Commands: /exit, /quit, /reset, /memory, /remember <content>, /model, /workspace, /init, /update, /help") 144 + } 145 + 146 + func chatBuiltinCommandsBlock() string { 147 + return "## Built-in Chat Commands\n\n" + 148 + "The user can type these special commands at any time:\n" + 149 + "- `/exit` or `/quit` — exit the chat session\n" + 150 + "- `/reset` — reset the current conversation (clear history, keep memory)\n" + 151 + "- `/memory` — display the current project memory\n" + 152 + "- `/remember <content>` — add a long-term memory item for the current project\n" + 153 + "- `/model` — inspect or change the current model selection for this session\n" + 154 + "- `/workspace` — show the current workspace attachment\n" + 155 + "- `/workspace attach <dir>` — attach or replace the current workspace directory\n" + 156 + "- `/workspace detach` — detach the current workspace directory\n" + 157 + "- `/init` — generate an AGENTS.md file for the current project\n" + 158 + "- `/update` — regenerate AGENTS.md, overwriting the existing file\n" + 159 + "If the user asks about any of these commands, explain what they do." 102 160 }
+6 -1
cmd/mistermorph/chatcmd/repl.go
··· 14 14 "github.com/chzyer/readline" 15 15 "github.com/quailyquaily/mistermorph/agent" 16 16 "github.com/quailyquaily/mistermorph/internal/chatcommands" 17 + "github.com/quailyquaily/mistermorph/internal/pathroots" 17 18 "github.com/quailyquaily/mistermorph/internal/llmstats" 18 19 "github.com/quailyquaily/mistermorph/internal/outputfmt" 19 20 "github.com/quailyquaily/mistermorph/llm" ··· 31 32 readline.PcItem("/init"), 32 33 readline.PcItem("/update"), 33 34 readline.PcItem("/model"), 35 + readline.PcItem("/workspace"), 36 + readline.PcItem("/workspace attach "), 37 + readline.PcItem("/workspace detach"), 34 38 readline.PcItem("/help"), 35 39 ) 36 40 ··· 49 53 sess.setWriter(rl.Stdout()) 50 54 writer := sess.currentWriter() 51 55 52 - printChatSessionHeader(writer, strings.TrimSpace(sess.mainCfg.Model), sess.chatFileCacheDir) 56 + printChatSessionHeader(writer, strings.TrimSpace(sess.mainCfg.Model), sess.workspaceDir, sess.fileCacheDir) 53 57 54 58 reg := chatcommands.NewRegistry() 55 59 history := make([]llm.Message, 0, 32) ··· 98 102 99 103 // Not a command — run an agent turn 100 104 turnCtx, turnCancel := context.WithCancel(context.Background()) 105 + turnCtx = pathroots.WithWorkspaceDir(turnCtx, sess.workspaceDir) 101 106 go func() { 102 107 <-time.After(sess.timeout) 103 108 turnCancel()
+73 -27
cmd/mistermorph/chatcmd/session.go
··· 5 5 "fmt" 6 6 "io" 7 7 "log/slog" 8 + "os" 8 9 "strings" 9 10 "sync" 10 11 "time" ··· 18 19 "github.com/quailyquaily/mistermorph/internal/llmutil" 19 20 "github.com/quailyquaily/mistermorph/internal/logutil" 20 21 "github.com/quailyquaily/mistermorph/internal/memoryruntime" 22 + "github.com/quailyquaily/mistermorph/internal/pathroots" 21 23 "github.com/quailyquaily/mistermorph/internal/personautil" 22 24 "github.com/quailyquaily/mistermorph/internal/promptprofile" 23 25 "github.com/quailyquaily/mistermorph/internal/skillsutil" 24 26 "github.com/quailyquaily/mistermorph/internal/statepaths" 25 27 "github.com/quailyquaily/mistermorph/internal/toolsutil" 28 + "github.com/quailyquaily/mistermorph/internal/workspace" 26 29 "github.com/quailyquaily/mistermorph/llm" 27 30 "github.com/quailyquaily/mistermorph/memory" 28 31 "github.com/quailyquaily/mistermorph/tools" ··· 48 51 compactMode bool 49 52 userName string 50 53 agentName string 51 - chatFileCacheDir string 54 + launchDir string 55 + fileCacheDir string 56 + workspaceDir string 52 57 sessionStore *llmselect.Store 53 58 llmValues llmutil.RuntimeValues 54 59 buildClient func(llmutil.ResolvedRoute, *llmconfig.ClientConfig) (llm.Client, error) 55 60 makeEngine func(*tools.Registry, llm.Client, string) *agent.Engine 61 + basePromptSpec agent.PromptSpec 56 62 promptSpec agent.PromptSpec 57 63 timeout time.Duration 58 64 writer io.Writer ··· 79 85 return cloneToolRegistry(deps.RegistryFromViper()) 80 86 } 81 87 88 + func (s *chatSession) projectDir() string { 89 + if s == nil { 90 + return "" 91 + } 92 + if dir := strings.TrimSpace(s.workspaceDir); dir != "" { 93 + return dir 94 + } 95 + return strings.TrimSpace(s.launchDir) 96 + } 97 + 98 + func (s *chatSession) refreshProjectScope() { 99 + if s == nil { 100 + return 101 + } 102 + s.subjectID = cliMemorySubjectID(s.projectDir()) 103 + } 104 + 105 + func (s *chatSession) rebuildPromptSpec() { 106 + if s == nil { 107 + return 108 + } 109 + spec := agent.PromptSpec{ 110 + Identity: s.basePromptSpec.Identity, 111 + Rules: append([]string(nil), s.basePromptSpec.Rules...), 112 + Skills: append([]agent.PromptSkill(nil), s.basePromptSpec.Skills...), 113 + Blocks: append([]agent.PromptBlock(nil), s.basePromptSpec.Blocks...), 114 + } 115 + blocks := make([]agent.PromptBlock, 0, len(spec.Blocks)+2) 116 + if workspaceDir := strings.TrimSpace(s.workspaceDir); workspaceDir != "" { 117 + if block := workspace.PromptBlock(workspaceDir); strings.TrimSpace(block.Content) != "" { 118 + blocks = append(blocks, block) 119 + } 120 + } 121 + blocks = append(blocks, agent.PromptBlock{Content: chatBuiltinCommandsBlock()}) 122 + blocks = append(blocks, spec.Blocks...) 123 + spec.Blocks = blocks 124 + s.promptSpec = spec 125 + } 126 + 82 127 func (s *chatSession) rebuildRuntimeState() error { 83 128 currentRoute, err := llmselect.ResolveMainRoute(s.llmValues, s.sessionStore.Get()) 84 129 if err != nil { ··· 106 151 PlanCreateModel: strings.TrimSpace(planRoute.ClientConfig.Model), 107 152 }) 108 153 154 + s.rebuildPromptSpec() 109 155 s.toolRegistry = reg 110 156 s.engine = s.makeEngine(reg, s.client, s.mainCfg.Model) 111 157 return nil ··· 190 236 slog.SetDefault(logger) 191 237 logOpts := logutil.LogOptionsFromViper() 192 238 193 - chatFileCacheDir, err := resolveChatFileCacheDir() 239 + launchDir, err := os.Getwd() 194 240 if err != nil { 195 241 return nil, err 196 242 } 197 - viper.Set("file_cache_dir", chatFileCacheDir) 243 + launchDir = pathroots.New(launchDir, "", "").WorkspaceDir 244 + fileCacheDir := strings.TrimSpace(viper.GetString("file_cache_dir")) 245 + rawWorkspace, _ := cmd.Flags().GetString("workspace") 246 + noWorkspace, _ := cmd.Flags().GetBool("no-workspace") 247 + workspaceDir, err := workspace.ResolveInitialWorkspace(launchDir, rawWorkspace, noWorkspace, nil) 248 + if err != nil { 249 + return nil, err 250 + } 198 251 199 252 llmValues := llmutil.RuntimeValuesFromViper() 200 253 mainRoute, err := llmutil.ResolveRoute(llmValues, llmutil.RoutePurposeMainLoop) ··· 306 359 promptprofile.AppendTodoWorkflowBlock(&promptSpec, reg) 307 360 promptprofile.AppendGPT5PromptPatch(&promptSpec, strings.TrimSpace(mainCfg.Model), logger) 308 361 309 - // Inject chat working directory context into system prompt 310 - promptSpec.Blocks = append([]agent.PromptBlock{{ 311 - Content: fmt.Sprintf("## Chat Session Context\n\n"+ 312 - "You are running in an interactive chat session. The user's current working directory is:\n\n"+ 313 - " %s\n\n"+ 314 - "CRITICAL: All file operations (read_file, write_file, bash) MUST use paths relative to THIS directory by default. "+ 315 - "When the user asks you to create or modify files WITHOUT specifying a path, write them to this directory (NOT to file_state_dir or ~/.morph/). "+ 316 - "Only use file_state_dir or ~/.morph/ when the user explicitly asks for configuration files, memory, or state storage. "+ 317 - "You may use `bash` with `ls`, `find`, etc. to explore the directory structure when needed.\n\n"+ 318 - "## Built-in Chat Commands\n\n"+ 319 - "The user can type these special commands at any time:\n"+ 320 - "- `/exit` or `/quit` — exit the chat session\n"+ 321 - "- `/reset` — reset the current conversation (clear history, keep memory)\n"+ 322 - "- `/memory` — display the current project memory\n"+ 323 - "- `/remember <content>` — add a long-term memory item for the current project\n"+ 324 - "- `/init` — generate an AGENTS.md file for the current project (analyzes the codebase and creates a guide for AI assistants)\n"+ 325 - "- `/update` — regenerate AGENTS.md, overwriting the existing file (useful after major project changes)\n"+ 326 - "If the user asks about any of these commands, explain what they do.", chatFileCacheDir), 327 - }}, promptSpec.Blocks...) 328 - 329 362 // Initialize memory runtime 330 - subjectID := cliMemorySubjectID(chatFileCacheDir) 331 - memManager, memOrchestrator, memWorker, memCleanup, err := initChatMemoryRuntime(chatFileCacheDir, logger) 363 + projectDir := strings.TrimSpace(workspaceDir) 364 + if projectDir == "" { 365 + projectDir = launchDir 366 + } 367 + subjectID := cliMemorySubjectID(projectDir) 368 + memManager, memOrchestrator, memWorker, memCleanup, err := initChatMemoryRuntime(projectDir, logger) 332 369 if err != nil { 333 370 logger.Warn("chat_memory_init_failed", "error", err.Error()) 334 371 } ··· 396 433 })) 397 434 398 435 makeEngine := func(engReg *tools.Registry, engClient llm.Client, defaultModel string) *agent.Engine { 436 + currentPromptSpec := promptSpec 437 + if sess != nil { 438 + currentPromptSpec = sess.promptSpec 439 + } 399 440 return agent.New( 400 441 engClient, 401 442 engReg, ··· 406 447 ToolRepeatLimit: configutil.FlagOrViperInt(cmd, "tool-repeat-limit", "tool_repeat_limit"), 407 448 DefaultModel: strings.TrimSpace(defaultModel), 408 449 }, 409 - promptSpec, 450 + currentPromptSpec, 410 451 append(opts, 411 452 agent.WithEngineToolsConfig(agent.EngineToolsConfig{ 412 453 SpawnEnabled: viper.GetBool("tools.spawn.enabled"), ··· 441 482 compactMode: compactMode, 442 483 userName: userName, 443 484 agentName: agentName, 444 - chatFileCacheDir: chatFileCacheDir, 445 485 sessionStore: sessionStore, 446 486 llmValues: llmValues, 447 487 buildClient: buildClient, 448 488 makeEngine: makeEngine, 489 + launchDir: launchDir, 490 + fileCacheDir: fileCacheDir, 491 + workspaceDir: workspaceDir, 492 + basePromptSpec: promptSpec, 449 493 promptSpec: promptSpec, 450 494 timeout: timeout, 451 495 } 496 + sess.rebuildPromptSpec() 497 + sess.engine = sess.makeEngine(sess.toolRegistry, sess.client, sess.mainCfg.Model) 452 498 453 499 return sess, nil 454 500 }
+4 -1
cmd/mistermorph/chatcmd/ui.go
··· 133 133 return stop, setMessage 134 134 } 135 135 136 - func printChatSessionHeader(writer io.Writer, model string, fileCacheDir string) { 136 + func printChatSessionHeader(writer io.Writer, model string, workspaceDir string, fileCacheDir string) { 137 137 _, _ = fmt.Fprint(writer, chatBanner) 138 138 if model != "" { 139 139 _, _ = fmt.Fprintf(writer, "model=%s\n", model) 140 + } 141 + if workspaceDir != "" { 142 + _, _ = fmt.Fprintf(writer, "workspace_dir=%s\n", workspaceDir) 140 143 } 141 144 if fileCacheDir != "" { 142 145 _, _ = fmt.Fprintf(writer, "file_cache_dir=%s\n", fileCacheDir)
+194 -11
cmd/mistermorph/consolecmd/local_runtime.go
··· 24 24 "github.com/quailyquaily/mistermorph/internal/channelruntime/depsutil" 25 25 heartbeatloop "github.com/quailyquaily/mistermorph/internal/channelruntime/heartbeat" 26 26 "github.com/quailyquaily/mistermorph/internal/channelruntime/taskruntime" 27 + "github.com/quailyquaily/mistermorph/internal/chatcommands" 27 28 "github.com/quailyquaily/mistermorph/internal/chathistory" 28 29 "github.com/quailyquaily/mistermorph/internal/daemonruntime" 29 30 "github.com/quailyquaily/mistermorph/internal/heartbeatutil" ··· 35 36 "github.com/quailyquaily/mistermorph/internal/mcphost" 36 37 "github.com/quailyquaily/mistermorph/internal/memoryruntime" 37 38 "github.com/quailyquaily/mistermorph/internal/outputfmt" 39 + "github.com/quailyquaily/mistermorph/internal/pathroots" 38 40 "github.com/quailyquaily/mistermorph/internal/pathutil" 39 41 "github.com/quailyquaily/mistermorph/internal/personautil" 40 42 "github.com/quailyquaily/mistermorph/internal/promptprofile" ··· 42 44 "github.com/quailyquaily/mistermorph/internal/statepaths" 43 45 "github.com/quailyquaily/mistermorph/internal/streaming" 44 46 "github.com/quailyquaily/mistermorph/internal/toolsutil" 47 + "github.com/quailyquaily/mistermorph/internal/workspace" 45 48 "github.com/quailyquaily/mistermorph/llm" 46 49 "github.com/quailyquaily/mistermorph/memory" 47 50 "github.com/quailyquaily/mistermorph/tools" ··· 62 65 TaskID string 63 66 ConversationKey string 64 67 TopicID string 68 + WorkspaceDir string 65 69 Task string 66 70 Model string 67 71 Timeout time.Duration ··· 118 122 heartbeatState *heartbeatutil.State 119 123 heartbeatPokeRequests chan heartbeatloop.PokeRequest 120 124 heartbeatCancel context.CancelFunc 125 + workspaceStore *workspace.Store 121 126 handlerMu sync.RWMutex 122 127 handler http.Handler 123 128 authToken string ··· 125 130 seq atomic.Uint64 126 131 } 127 132 133 + type topicDeleterFunc func(id string) bool 134 + 135 + func (fn topicDeleterFunc) DeleteTopic(id string) bool { 136 + if fn == nil { 137 + return false 138 + } 139 + return fn(id) 140 + } 141 + 128 142 func newConsoleLocalRuntime(cfg serveConfig, reader *viper.Viper) (*consoleLocalRuntime, error) { 129 143 inspectors, err := newConsoleInspectors(cfg.inspectPrompt, cfg.inspectRequest, "console", "console", "20060102_150405") 130 144 if err != nil { ··· 174 188 out.store = store 175 189 out.bus = inprocBus 176 190 out.streamHub = newConsoleStreamHub() 191 + out.workspaceStore = workspace.NewStore(consoleWorkspaceAttachmentsPathFromReader(gen.reader)) 177 192 out.runner = runtimecore.NewConversationRunner[string, consoleLocalTaskJob]( 178 193 workersCtx, 179 194 make(chan struct{}, 1), ··· 326 341 return pathutil.ResolveStateDir(r.GetString("file_state_dir")) 327 342 } 328 343 344 + func consoleWorkspaceAttachmentsPathFromReader(r interface { 345 + GetString(string) string 346 + }) string { 347 + return pathutil.ResolveStateFile(consoleStateDirFromReader(r), "workspace_attachments.json") 348 + } 349 + 329 350 func consoleHeartbeatChecklistPathFromReader(r interface { 330 351 GetString(string) string 331 352 }) string { ··· 499 520 return r.handler 500 521 } 501 522 523 + func (r *consoleLocalRuntime) currentWorkspaceStore() *workspace.Store { 524 + if r == nil { 525 + return nil 526 + } 527 + r.generationMu.RLock() 528 + store := r.workspaceStore 529 + r.generationMu.RUnlock() 530 + return store 531 + } 532 + 502 533 func (r *consoleLocalRuntime) currentConfigReader() *viper.Viper { 503 534 generation := r.currentGeneration() 504 535 if generation == nil { ··· 535 566 r.generationMu.Lock() 536 567 prevGeneration := r.generation 537 568 r.generation = generation 569 + r.workspaceStore = workspace.NewStore(consoleWorkspaceAttachmentsPathFromReader(reader)) 538 570 r.generationMu.Unlock() 539 571 r.handlerMu.Lock() 540 572 r.authToken = authToken ··· 719 751 return heartbeatloop.Trigger(ctx, r.heartbeatPokeRequests, input) 720 752 } 721 753 754 + func (r *consoleLocalRuntime) workspaceDirForTopic(_ context.Context, topicID string) (string, error) { 755 + topicID = strings.TrimSpace(topicID) 756 + if topicID == "" { 757 + return "", daemonruntime.BadRequest("topic_id is required") 758 + } 759 + store := r.currentWorkspaceStore() 760 + if store == nil { 761 + return "", fmt.Errorf("workspace store is not configured") 762 + } 763 + return workspace.LookupWorkspaceDir(store, buildConsoleConversationKey(topicID)) 764 + } 765 + 766 + func (r *consoleLocalRuntime) setWorkspaceDirForTopic(_ context.Context, topicID string, workspaceDir string) (string, error) { 767 + topicID = strings.TrimSpace(topicID) 768 + if topicID == "" { 769 + return "", daemonruntime.BadRequest("topic_id is required") 770 + } 771 + store := r.currentWorkspaceStore() 772 + if store == nil { 773 + return "", fmt.Errorf("workspace store is not configured") 774 + } 775 + dir, err := workspace.ValidateDir(workspaceDir, nil) 776 + if err != nil { 777 + return "", daemonruntime.BadRequest(strings.TrimSpace(err.Error())) 778 + } 779 + if _, _, err := store.Set(buildConsoleConversationKey(topicID), workspace.Attachment{WorkspaceDir: dir}); err != nil { 780 + return "", err 781 + } 782 + return dir, nil 783 + } 784 + 785 + func (r *consoleLocalRuntime) deleteWorkspaceDirForTopic(_ context.Context, topicID string) error { 786 + topicID = strings.TrimSpace(topicID) 787 + if topicID == "" { 788 + return daemonruntime.BadRequest("topic_id is required") 789 + } 790 + store := r.currentWorkspaceStore() 791 + if store == nil { 792 + return fmt.Errorf("workspace store is not configured") 793 + } 794 + _, _, err := store.Delete(buildConsoleConversationKey(topicID)) 795 + return err 796 + } 797 + 798 + func daemonruntimeTreeListing(listing workspace.TreeListing) daemonruntime.WorkspaceTreeListing { 799 + items := make([]daemonruntime.WorkspaceTreeEntry, 0, len(listing.Items)) 800 + for _, item := range listing.Items { 801 + items = append(items, daemonruntime.WorkspaceTreeEntry{ 802 + Name: item.Name, 803 + Path: item.Path, 804 + IsDir: item.IsDir, 805 + HasChildren: item.HasChildren, 806 + SizeBytes: item.SizeBytes, 807 + }) 808 + } 809 + return daemonruntime.WorkspaceTreeListing{ 810 + RootPath: listing.RootPath, 811 + Path: listing.Path, 812 + Items: items, 813 + } 814 + } 815 + 816 + func (r *consoleLocalRuntime) workspaceTreeForTopic(ctx context.Context, topicID string, treePath string) (daemonruntime.WorkspaceTreeListing, error) { 817 + workspaceDir, err := r.workspaceDirForTopic(ctx, topicID) 818 + if err != nil { 819 + return daemonruntime.WorkspaceTreeListing{}, err 820 + } 821 + if strings.TrimSpace(workspaceDir) == "" { 822 + return daemonruntime.WorkspaceTreeListing{}, daemonruntime.BadRequest("workspace is not attached") 823 + } 824 + listing, err := workspace.ListAttachedTree(workspaceDir, treePath) 825 + if err != nil { 826 + return daemonruntime.WorkspaceTreeListing{}, daemonruntime.BadRequest(strings.TrimSpace(err.Error())) 827 + } 828 + return daemonruntimeTreeListing(listing), nil 829 + } 830 + 831 + func (r *consoleLocalRuntime) browseWorkspaceTree(_ context.Context, treePath string) (daemonruntime.WorkspaceTreeListing, error) { 832 + listing, err := workspace.ListSystemTree(treePath) 833 + if err != nil { 834 + return daemonruntime.WorkspaceTreeListing{}, daemonruntime.BadRequest(strings.TrimSpace(err.Error())) 835 + } 836 + return daemonruntimeTreeListing(listing), nil 837 + } 838 + 839 + func (r *consoleLocalRuntime) openWorkspacePathForTopic(ctx context.Context, topicID string, treePath string) error { 840 + workspaceDir, err := r.workspaceDirForTopic(ctx, topicID) 841 + if err != nil { 842 + return err 843 + } 844 + if strings.TrimSpace(workspaceDir) == "" { 845 + return daemonruntime.BadRequest("workspace is not attached") 846 + } 847 + targetPath, err := workspace.ResolveAttachedItemPath(workspaceDir, treePath) 848 + if err != nil { 849 + return daemonruntime.BadRequest(strings.TrimSpace(err.Error())) 850 + } 851 + return workspace.OpenPath(targetPath) 852 + } 853 + 854 + func (r *consoleLocalRuntime) deleteTopic(id string) bool { 855 + if r == nil || r.store == nil { 856 + return false 857 + } 858 + if !r.store.DeleteTopic(id) { 859 + return false 860 + } 861 + store := r.currentWorkspaceStore() 862 + if store != nil { 863 + _, _, _ = store.Delete(buildConsoleConversationKey(id)) 864 + } 865 + return true 866 + } 867 + 722 868 func (r *consoleLocalRuntime) routesOptions(authToken string) daemonruntime.RoutesOptions { 723 869 var poke daemonruntime.PokeFunc 724 870 if r.canPokeHeartbeat() { ··· 733 879 } 734 880 return personautil.LoadAgentName(consoleStateDirFromReader(generation.reader)) 735 881 }, 736 - AuthToken: strings.TrimSpace(authToken), 737 - TaskReader: r.store, 738 - TopicReader: r.store, 739 - TopicDeleter: r.store, 740 - Submit: r.submitTask, 741 - HealthEnabled: true, 882 + AuthToken: strings.TrimSpace(authToken), 883 + TaskReader: r.store, 884 + TopicReader: r.store, 885 + TopicDeleter: topicDeleterFunc(r.deleteTopic), 886 + Submit: r.submitTask, 887 + WorkspaceGet: r.workspaceDirForTopic, 888 + WorkspacePut: r.setWorkspaceDirForTopic, 889 + WorkspaceDelete: r.deleteWorkspaceDirForTopic, 890 + WorkspaceOpen: r.openWorkspacePathForTopic, 891 + WorkspaceTree: r.workspaceTreeForTopic, 892 + WorkspaceBrowse: r.browseWorkspaceTree, 893 + HealthEnabled: true, 742 894 Overview: func(ctx context.Context) (map[string]any, error) { 743 895 generation, err := r.captureGeneration() 744 896 if err != nil { ··· 823 975 Ref: "web/console", 824 976 }) 825 977 task := strings.TrimSpace(req.Task) 978 + if resp, handled, err := r.handleConsoleWorkspaceCommand(generation, req, timeout, trigger); handled { 979 + if err == nil { 980 + releaseGeneration = false 981 + } 982 + return resp, err 983 + } 826 984 if output, handled := r.handleConsoleModelCommand(generation.reader, task); handled { 827 985 resp, err := r.submitSyntheticTask(generation, task, output, timeout, strings.TrimSpace(req.TopicID), strings.TrimSpace(req.TopicTitle), trigger) 828 986 if err == nil { ··· 847 1005 return resp, err 848 1006 } 849 1007 1008 + func (r *consoleLocalRuntime) handleConsoleWorkspaceCommand(generation *consoleLocalRuntimeGeneration, req daemonruntime.SubmitTaskRequest, timeout time.Duration, trigger daemonruntime.TaskTrigger) (daemonruntime.SubmitTaskResponse, bool, error) { 1009 + task := strings.TrimSpace(req.Task) 1010 + cmdWord, cmdArgs := chatcommands.ParseCommand(task) 1011 + if chatcommands.NormalizeCommand(cmdWord) != "/workspace" { 1012 + return daemonruntime.SubmitTaskResponse{}, false, nil 1013 + } 1014 + topicID := strings.TrimSpace(req.TopicID) 1015 + if topicID == "" { 1016 + return daemonruntime.SubmitTaskResponse{}, true, daemonruntime.BadRequest("topic_id is required for /workspace") 1017 + } 1018 + store := r.currentWorkspaceStore() 1019 + if store == nil { 1020 + return daemonruntime.SubmitTaskResponse{}, true, fmt.Errorf("workspace store is not configured") 1021 + } 1022 + result, cmdErr := workspace.ExecuteStoreCommand(store, buildConsoleConversationKey(topicID), cmdArgs, nil) 1023 + output := strings.TrimSpace(result.Reply) 1024 + if cmdErr != nil { 1025 + output = "error: " + strings.TrimSpace(cmdErr.Error()) 1026 + } 1027 + resp, err := r.submitSyntheticTask(generation, task, output, timeout, topicID, strings.TrimSpace(req.TopicTitle), trigger) 1028 + return resp, true, err 1029 + } 1030 + 850 1031 func (r *consoleLocalRuntime) handleConsoleModelCommand(reader *viper.Viper, task string) (string, bool) { 851 1032 output, handled, err := llmselect.ExecuteCommandText( 852 1033 llmutil.RuntimeValuesFromReader(reader), ··· 1007 1188 } 1008 1189 generation := job.Generation 1009 1190 ctx = llmstats.WithRunID(ctx, job.TaskID) 1191 + ctx = pathroots.WithWorkspaceDir(ctx, job.WorkspaceDir) 1010 1192 task := strings.TrimSpace(job.Task) 1011 1193 if task == "" { 1012 1194 return nil, nil, fmt.Errorf("empty console task") ··· 1053 1235 if pokeMeta := job.WakeSignal.MetaValue(); pokeMeta != nil { 1054 1236 meta["poke"] = pokeMeta 1055 1237 } 1056 - var promptAugment taskruntime.PromptAugmentFunc 1057 - if !job.WakeSignal.IsZero() { 1058 - wakeSignal := job.WakeSignal.Normalize() 1059 - promptAugment = func(spec *agent.PromptSpec, _ *tools.Registry) { 1060 - promptprofile.AppendWakeSignalBlock(spec, wakeSignal) 1238 + promptAugment := func(spec *agent.PromptSpec, _ *tools.Registry) { 1239 + if block := workspace.PromptBlock(job.WorkspaceDir); strings.TrimSpace(block.Content) != "" { 1240 + spec.Blocks = append([]agent.PromptBlock{block}, spec.Blocks...) 1241 + } 1242 + if !job.WakeSignal.IsZero() { 1243 + promptprofile.AppendWakeSignalBlock(spec, job.WakeSignal.Normalize()) 1061 1244 } 1062 1245 } 1063 1246 bundle := generation.bundle
+21
cmd/mistermorph/consolecmd/local_runtime_bus.go
··· 12 12 runtimecore "github.com/quailyquaily/mistermorph/internal/channelruntime/core" 13 13 "github.com/quailyquaily/mistermorph/internal/daemonruntime" 14 14 "github.com/quailyquaily/mistermorph/internal/idempotency" 15 + "github.com/quailyquaily/mistermorph/internal/workspace" 15 16 ) 16 17 17 18 const ( ··· 66 67 } 67 68 } 68 69 conversationKey := buildConsoleConversationKey(topicID) 70 + workspaceDir := "" 71 + if store := r.currentWorkspaceStore(); store != nil { 72 + dir, err := workspace.LookupWorkspaceDir(store, conversationKey) 73 + if err != nil { 74 + return consoleLocalTaskJob{}, daemonruntime.SubmitTaskResponse{}, err 75 + } 76 + workspaceDir = dir 77 + } 69 78 if err := r.store.UpsertWithTrigger(daemonruntime.TaskInfo{ 70 79 ID: taskID, 71 80 Status: daemonruntime.TaskQueued, ··· 81 90 TaskID: taskID, 82 91 ConversationKey: conversationKey, 83 92 TopicID: topicID, 93 + WorkspaceDir: workspaceDir, 84 94 Task: strings.TrimSpace(task), 85 95 Model: model, 86 96 Timeout: timeout, ··· 222 232 TaskID: stored.ID, 223 233 ConversationKey: buildConsoleConversationKey(stored.TopicID), 224 234 TopicID: stored.TopicID, 235 + WorkspaceDir: "", 225 236 Task: stored.Task, 226 237 Model: stored.Model, 227 238 Timeout: parseConsoleTaskTimeout(stored.Timeout, consoleDefaultTimeoutFromReader(generation.reader)), ··· 229 240 Trigger: trigger, 230 241 AutoRenameTopic: autoRename, 231 242 Generation: generation, 243 + } 244 + if store := r.currentWorkspaceStore(); store != nil { 245 + dir, err := workspace.LookupWorkspaceDir(store, job.ConversationKey) 246 + if err != nil { 247 + if generation != nil { 248 + generation.release() 249 + } 250 + return err 251 + } 252 + job.WorkspaceDir = dir 232 253 } 233 254 } 234 255 if err := r.runner.Enqueue(ctx, job.ConversationKey, func(version uint64) consoleLocalTaskJob {
+136
cmd/mistermorph/consolecmd/local_runtime_test.go
··· 5 5 "encoding/json" 6 6 "errors" 7 7 "fmt" 8 + "path/filepath" 8 9 "strings" 9 10 "testing" 10 11 "time" ··· 16 17 "github.com/quailyquaily/mistermorph/internal/chathistory" 17 18 "github.com/quailyquaily/mistermorph/internal/daemonruntime" 18 19 "github.com/quailyquaily/mistermorph/internal/heartbeatutil" 20 + "github.com/quailyquaily/mistermorph/internal/workspace" 19 21 "github.com/spf13/viper" 20 22 ) 21 23 ··· 454 456 t.Fatalf("pendingJobs[%q] still exists, want removed after enqueue", job.TaskID) 455 457 } 456 458 } 459 + 460 + func TestConsoleLocalRuntimeAcceptTaskLoadsWorkspaceAttachment(t *testing.T) { 461 + store, err := daemonruntime.NewConsoleFileStore(daemonruntime.ConsoleFileStoreOptions{ 462 + HeartbeatTopicID: "_heartbeat", 463 + Persist: false, 464 + }) 465 + if err != nil { 466 + t.Fatalf("NewConsoleFileStore() error = %v", err) 467 + } 468 + topic, err := store.CreateTopic("Topic A") 469 + if err != nil { 470 + t.Fatalf("CreateTopic() error = %v", err) 471 + } 472 + 473 + workspaceRoot := t.TempDir() 474 + attachmentsPath := filepath.Join(workspaceRoot, "workspace_attachments.json") 475 + workspaceStore := workspace.NewStore(attachmentsPath) 476 + if _, _, err := workspaceStore.Set(buildConsoleConversationKey(topic.ID), workspace.Attachment{WorkspaceDir: workspaceRoot}); err != nil { 477 + t.Fatalf("workspaceStore.Set() error = %v", err) 478 + } 479 + 480 + generation := &consoleLocalRuntimeGeneration{reader: viper.New()} 481 + rt := &consoleLocalRuntime{ 482 + store: store, 483 + workspaceStore: workspaceStore, 484 + } 485 + 486 + job, _, err := rt.acceptTask( 487 + generation, 488 + "hello", 489 + "", 490 + time.Minute, 491 + topic.ID, 492 + "", 493 + daemonruntime.TaskTrigger{Source: "ui", Event: "chat_submit", Ref: "web/console"}, 494 + ) 495 + if err != nil { 496 + t.Fatalf("acceptTask() error = %v", err) 497 + } 498 + if job.WorkspaceDir != workspaceRoot { 499 + t.Fatalf("job.WorkspaceDir = %q, want %q", job.WorkspaceDir, workspaceRoot) 500 + } 501 + } 502 + 503 + func TestConsoleLocalRuntimeDeleteTopicRemovesWorkspaceAttachment(t *testing.T) { 504 + store, err := daemonruntime.NewConsoleFileStore(daemonruntime.ConsoleFileStoreOptions{ 505 + HeartbeatTopicID: "_heartbeat", 506 + Persist: false, 507 + }) 508 + if err != nil { 509 + t.Fatalf("NewConsoleFileStore() error = %v", err) 510 + } 511 + topic, err := store.CreateTopic("Topic A") 512 + if err != nil { 513 + t.Fatalf("CreateTopic() error = %v", err) 514 + } 515 + 516 + workspaceRoot := t.TempDir() 517 + attachmentsPath := filepath.Join(workspaceRoot, "workspace_attachments.json") 518 + workspaceStore := workspace.NewStore(attachmentsPath) 519 + if _, _, err := workspaceStore.Set(buildConsoleConversationKey(topic.ID), workspace.Attachment{WorkspaceDir: workspaceRoot}); err != nil { 520 + t.Fatalf("workspaceStore.Set() error = %v", err) 521 + } 522 + 523 + rt := &consoleLocalRuntime{ 524 + store: store, 525 + workspaceStore: workspaceStore, 526 + } 527 + if !rt.deleteTopic(topic.ID) { 528 + t.Fatalf("deleteTopic(%q) = false, want true", topic.ID) 529 + } 530 + currentDir, err := workspace.LookupWorkspaceDir(workspaceStore, buildConsoleConversationKey(topic.ID)) 531 + if err != nil { 532 + t.Fatalf("LookupWorkspaceDir() error = %v", err) 533 + } 534 + if currentDir != "" { 535 + t.Fatalf("currentDir = %q, want empty after topic delete", currentDir) 536 + } 537 + } 538 + 539 + func TestConsoleLocalRuntimeSubmitTaskHandlesWorkspaceCommand(t *testing.T) { 540 + store, err := daemonruntime.NewConsoleFileStore(daemonruntime.ConsoleFileStoreOptions{ 541 + HeartbeatTopicID: "_heartbeat", 542 + Persist: false, 543 + }) 544 + if err != nil { 545 + t.Fatalf("NewConsoleFileStore() error = %v", err) 546 + } 547 + topic, err := store.CreateTopic("Topic A") 548 + if err != nil { 549 + t.Fatalf("CreateTopic() error = %v", err) 550 + } 551 + 552 + workspaceRoot := t.TempDir() 553 + attachmentsPath := filepath.Join(workspaceRoot, "workspace_attachments.json") 554 + workspaceStore := workspace.NewStore(attachmentsPath) 555 + reader := viper.New() 556 + generation := &consoleLocalRuntimeGeneration{reader: reader} 557 + rt := &consoleLocalRuntime{ 558 + store: store, 559 + workspaceStore: workspaceStore, 560 + generation: generation, 561 + } 562 + 563 + resp, err := rt.submitTask(context.Background(), daemonruntime.SubmitTaskRequest{ 564 + Task: "/workspace attach " + workspaceRoot, 565 + TopicID: topic.ID, 566 + }) 567 + if err != nil { 568 + t.Fatalf("submitTask() error = %v", err) 569 + } 570 + if resp.Status != daemonruntime.TaskDone { 571 + t.Fatalf("resp.Status = %q, want %q", resp.Status, daemonruntime.TaskDone) 572 + } 573 + if resp.TopicID != topic.ID { 574 + t.Fatalf("resp.TopicID = %q, want %q", resp.TopicID, topic.ID) 575 + } 576 + currentDir, err := workspace.LookupWorkspaceDir(workspaceStore, buildConsoleConversationKey(topic.ID)) 577 + if err != nil { 578 + t.Fatalf("LookupWorkspaceDir() error = %v", err) 579 + } 580 + if currentDir != workspaceRoot { 581 + t.Fatalf("currentDir = %q, want %q", currentDir, workspaceRoot) 582 + } 583 + task, ok := store.Get(resp.ID) 584 + if !ok || task == nil { 585 + t.Fatalf("store.Get(%q) missing", resp.ID) 586 + } 587 + result, _ := task.Result.(map[string]any) 588 + final, _ := result["final"].(map[string]any) 589 + if got := strings.TrimSpace(fmt.Sprint(final["output"])); got != "workspace attached: "+workspaceRoot { 590 + t.Fatalf("final.output = %q, want %q", got, "workspace attached: "+workspaceRoot) 591 + } 592 + }
+2 -2
cmd/mistermorph/consolecmd/runtime_support.go
··· 12 12 "github.com/quailyquaily/mistermorph/agent" 13 13 "github.com/quailyquaily/mistermorph/guard" 14 14 "github.com/quailyquaily/mistermorph/internal/mcphost" 15 + "github.com/quailyquaily/mistermorph/internal/pathroots" 15 16 "github.com/quailyquaily/mistermorph/internal/pathutil" 16 17 "github.com/quailyquaily/mistermorph/internal/toolsutil" 17 18 "github.com/quailyquaily/mistermorph/secrets" ··· 159 160 toolsutil.RegisterStaticTools(reg, toolsutil.StaticRegistryConfig{ 160 161 Common: toolsutil.StaticCommonConfig{ 161 162 UserAgent: cfg.UserAgent, 162 - FileCacheDir: cfg.FileCacheDir, 163 - FileStateDir: cfg.FileStateDir, 163 + PathRoots: pathroots.New("", cfg.FileCacheDir, cfg.FileStateDir), 164 164 AuthenticatedHTTPConfigured: authenticatedHTTPConfigured, 165 165 }, 166 166 ReadFile: toolsutil.StaticReadFileConfig{
+2 -2
cmd/mistermorph/registry.go
··· 7 7 "time" 8 8 9 9 "github.com/quailyquaily/mistermorph/internal/configdefaults" 10 + "github.com/quailyquaily/mistermorph/internal/pathroots" 10 11 "github.com/quailyquaily/mistermorph/internal/pathutil" 11 12 "github.com/quailyquaily/mistermorph/internal/toolsutil" 12 13 "github.com/quailyquaily/mistermorph/secrets" ··· 149 150 toolsutil.RegisterStaticTools(r, toolsutil.StaticRegistryConfig{ 150 151 Common: toolsutil.StaticCommonConfig{ 151 152 UserAgent: userAgent, 152 - FileCacheDir: strings.TrimSpace(cfg.FileCacheDir), 153 - FileStateDir: strings.TrimSpace(cfg.FileStateDir), 153 + PathRoots: pathroots.New("", strings.TrimSpace(cfg.FileCacheDir), strings.TrimSpace(cfg.FileStateDir)), 154 154 AuthenticatedHTTPConfigured: authenticatedHTTPConfigured, 155 155 }, 156 156 ReadFile: toolsutil.StaticReadFileConfig{
+19
cmd/mistermorph/runcmd/run.go
··· 23 23 "github.com/quailyquaily/mistermorph/internal/llmutil" 24 24 "github.com/quailyquaily/mistermorph/internal/logutil" 25 25 "github.com/quailyquaily/mistermorph/internal/outputfmt" 26 + "github.com/quailyquaily/mistermorph/internal/pathroots" 26 27 "github.com/quailyquaily/mistermorph/internal/promptprofile" 27 28 "github.com/quailyquaily/mistermorph/internal/skillsutil" 28 29 "github.com/quailyquaily/mistermorph/internal/statepaths" 29 30 "github.com/quailyquaily/mistermorph/internal/toolsutil" 31 + "github.com/quailyquaily/mistermorph/internal/workspace" 30 32 "github.com/quailyquaily/mistermorph/llm" 31 33 "github.com/quailyquaily/mistermorph/tools" 32 34 "github.com/spf13/cobra" ··· 74 76 } 75 77 } 76 78 79 + launchDir, err := os.Getwd() 80 + if err != nil { 81 + return err 82 + } 83 + rawWorkspace, _ := cmd.Flags().GetString("workspace") 84 + noWorkspace, _ := cmd.Flags().GetBool("no-workspace") 85 + workspaceDir, err := workspace.ResolveInitialWorkspace(launchDir, rawWorkspace, noWorkspace, nil) 86 + if err != nil { 87 + return err 88 + } 89 + 77 90 llmValues := llmutil.RuntimeValuesFromViper() 78 91 mainRoute, err := llmutil.ResolveRoute(llmValues, llmutil.RoutePurposeMainLoop) 79 92 if err != nil { ··· 202 215 promptprofile.AppendPlanCreateGuidanceBlock(&promptSpec, reg) 203 216 promptprofile.AppendTodoWorkflowBlock(&promptSpec, reg) 204 217 promptprofile.AppendGPT5PromptPatch(&promptSpec, strings.TrimSpace(mainCfg.Model), logger) 218 + if block := workspace.PromptBlock(workspaceDir); strings.TrimSpace(block.Content) != "" { 219 + promptSpec.Blocks = append([]agent.PromptBlock{block}, promptSpec.Blocks...) 220 + } 205 221 206 222 var hook agent.Hook 207 223 if configutil.FlagOrViperBool(cmd, "interactive", "interactive") { ··· 297 313 298 314 runID := llmstats.NewSyntheticRunID("cli") 299 315 ctx = llmstats.WithRunID(ctx, runID) 316 + ctx = pathroots.WithWorkspaceDir(ctx, workspaceDir) 300 317 final, runCtx, err := engine.Run(ctx, task, agent.RunOptions{ 301 318 Model: strings.TrimSpace(mainCfg.Model), 302 319 Scene: "cli.loop", ··· 328 345 329 346 cmd.Flags().String("task", "", "Task to run (if empty, reads from stdin).") 330 347 cmd.Flags().Bool("heartbeat", false, "Run a single heartbeat check (ignores --task and stdin).") 348 + cmd.Flags().String("workspace", "", "Attach a workspace directory for this run.") 349 + cmd.Flags().Bool("no-workspace", false, "Run without a workspace attachment.") 331 350 cmd.Flags().String("provider", "openai", "Provider: openai|openai_resp|openai_custom|deepseek|xai|gemini|azure|anthropic|bedrock|susanoo|cloudflare.") 332 351 cmd.Flags().String("endpoint", "https://api.openai.com", "Base URL for provider.") 333 352 cmd.Flags().String("model", "gpt-5.2", "Model name.")
+614
docs/feat/feat_20260420_workspace_dir.md
··· 1 + --- 2 + date: 2026-04-20 3 + title: Workspace Attachment Across Sessions 4 + status: in_progress 5 + --- 6 + 7 + # Workspace Attachment Across Sessions 8 + 9 + ## 1) 目标 10 + 11 + 这次要解决的是一个统一运行时能力,不是 CLI 小功能。 12 + 13 + 目标只有四件事: 14 + 15 + - 允许把一个本地目录 attach 到当前 scope 16 + - 让这个目录成为默认工作区 17 + - 让 agent 和本地文件工具围绕这个目录形成一致语义 18 + - 同时保留 `file_cache_dir` 和 `file_state_dir` 的原职责 19 + 20 + 这里真正新增的是第三类目录语义: 21 + 22 + - `workspace_dir` 23 + - `file_cache_dir` 24 + - `file_state_dir` 25 + 26 + ## 2) 非目标 27 + 28 + 这版方案不解决下面这些事: 29 + 30 + - 不做新的 sandbox 31 + - 不做多 workspace 并存 32 + - 这次只做 Console Local runtime 后端与 runtime API,不展开新的 UI 交互设计 33 + - 不规定 attachment store 的具体文件格式 34 + - 不处理 Slack thread-scoped workspace 35 + 36 + ## 3) 核心不变量 37 + 38 + ### 3.1 绑定对象不是进程,而是 scope 39 + 40 + 这次能力的最小抽象是: 41 + 42 + - 一个稳定 scope key 最多绑定一个 workspace 43 + - 这个 workspace 决定默认工作区语义 44 + 45 + 不是: 46 + 47 + - 进程全局 `cwd` 48 + - 进程全局 `workspace_dir` 49 + - 某个 runtime 私有的临时魔法变量 50 + 51 + ### 3.2 这是工作区语义,不是权限边界 52 + 53 + `workspace_dir` 负责: 54 + 55 + - 默认读写路径 56 + - 默认 shell 工作目录 57 + - 项目文件的默认输出位置 58 + - prompt 里的项目上下文 59 + 60 + 它不是新的安全边界。 61 + 62 + 现有安全边界仍然来自: 63 + 64 + - guard 65 + - deny-path / allowlist 66 + - runtime 自己的权限约束 67 + 68 + ### 3.3 三类目录必须分开 69 + 70 + 三类目录的职责如下: 71 + 72 + - `workspace_dir`:项目工作区 73 + - `file_cache_dir`:下载、转换、中间产物 74 + - `file_state_dir`:memory、TODO、contacts、skills、guard 等状态数据 75 + 76 + 不能再把: 77 + 78 + - workspace 假装成 cache 79 + - 状态目录假装成 workspace 80 + 81 + ### 3.4 当前 chat 的实现是错的 82 + 83 + 当前 CLI `chat` 把当前目录借道塞进 `file_cache_dir`。 84 + 这个实现本身就是错的。 85 + 86 + 做 `workspace_dir` 时,必须一起拆掉这层耦合: 87 + 88 + - 当前目录应该进入 `workspace_dir` 89 + - `file_cache_dir` 只做 cache 90 + 91 + ## 4) ScopeKey 规则 92 + 93 + attachment store 的主键必须是 canonical conversation key。 94 + 95 + 不要同时接受多套主键。 96 + 97 + 第一阶段按现有 canonical key 走: 98 + 99 + - Console: `console:<topic_id>` 100 + - Telegram: `tg:<chat_id>` 101 + - Slack: `slack:<team_id>:<channel_id>` 102 + - LINE: `line:<chat_id>` 或 `line:<group_id>` 103 + - Lark: `lark:<chat_id>` 104 + 105 + 这意味着: 106 + 107 + - Console 不同 topic 可以绑定不同 workspace 108 + - Console store 只认 `console:<topic_id>` 109 + - 不使用 bus envelope 的 `session_id` 做 attachment key 110 + 111 + ## 5) 生命周期与持久化 112 + 113 + 第一阶段只需要下面这组规则: 114 + 115 + - CLI `chat`:进程内临时状态,不进 attachment store 116 + - CLI `run`:一次性运行参数,不进 attachment store 117 + - Telegram / Slack / LINE / Lark:按 canonical conversation key 落 attachment store 118 + - Console Local runtime:按 canonical conversation key 落 attachment store 119 + - Console topic 删除时,同步删除 `console:<topic_id>` attachment 120 + 121 + attachment store 只保存绑定关系。 122 + 123 + 最小数据模型够用即可: 124 + 125 + ```go 126 + type WorkspaceAttachment struct { 127 + ScopeKey string 128 + WorkspaceDir string 129 + } 130 + ``` 131 + 132 + 是否加时间戳、来源字段、JSON 还是 JSONL,都不是这版必须先定死的事情。 133 + 134 + ## 6) 命令协议 135 + 136 + 统一消息文本协议固定为: 137 + 138 + - `/workspace` 139 + - `/workspace attach <dir>` 140 + - `/workspace detach` 141 + 142 + 不要再保留 `/attach` / `/detach` 这种平行命令。 143 + 144 + 这套协议适用于: 145 + 146 + - CLI `chat` 147 + - Telegram 148 + - Slack 149 + - LINE 150 + - Lark 151 + 152 + Console Local runtime 后端已经接入这套协议;结构化 Web API 也沿用同一组语义。 153 + 154 + 行为规则只有三条: 155 + 156 + - `/workspace`:查看当前绑定状态 157 + - `/workspace attach <dir>`:绑定目录;如果已有绑定,直接替换旧值,并明确回显替换结果 158 + - `/workspace detach`:解绑 159 + 160 + 失败规则也固定下来: 161 + 162 + - 路径不存在:失败 163 + - 路径不可读:失败 164 + - 当 runtime 配置了 allow roots 时,路径不在允许范围内:失败 165 + - 不自动创建目录 166 + 167 + ## 7) 工具与路径语义 168 + 169 + 如果工具层不支持 `workspace_dir`,这个能力就只是贴皮。 170 + 171 + 所以第一阶段必须做到: 172 + 173 + - `write_file` 支持 `workspace_dir/<path>` alias 174 + - `read_file` 支持 `workspace_dir/<path>` alias 175 + - 有 workspace 时,相对路径默认按 workspace 解析 176 + - `bash` / `powershell` 默认 `cwd = workspace_dir` 177 + 178 + 同时保留两个边界: 179 + 180 + - `url_fetch` 继续写 `file_cache_dir` 181 + - TODO / memory / guard / contacts / skills 继续写 `file_state_dir` 182 + 183 + 这里要注意一点: 184 + 185 + - `write_file` 的允许根会从两类目录变成三类目录 186 + 187 + 也就是: 188 + 189 + - `workspace_dir` 190 + - `file_cache_dir` 191 + - `file_state_dir` 192 + 193 + 因此 guard、deny-path 和相关校验要一起更新。 194 + 195 + ## 8) 实现约束 196 + 197 + 实现上只需要定死下面三件事: 198 + 199 + ### 8.1 运行时要先解 scope,再解 roots 200 + 201 + 处理当前消息或任务前,runtime 需要先得到: 202 + 203 + - 当前 canonical conversation key 204 + - 当前 attached workspace 205 + 206 + 然后再构造这一轮运行使用的 path roots。 207 + 208 + ### 8.2 roots 要显式建模 209 + 210 + 不要再靠位置数组猜目录意义。 211 + 212 + 建议显式结构: 213 + 214 + ```go 215 + type PathRoots struct { 216 + WorkspaceDir string 217 + FileCacheDir string 218 + FileStateDir string 219 + } 220 + ``` 221 + 222 + ### 8.3 命令解析要复用公用设施 223 + 224 + `/workspace ...` 是共享文本协议。 225 + 226 + 不要让每个 runtime 自己拆字符串。 227 + 应复用现有公用命令解析设施,统一产出: 228 + 229 + - status 230 + - attach 231 + - detach 232 + 233 + ## 9) 最终结论 234 + 235 + 这次需求的正确描述是: 236 + 237 + > 为系统增加一个全局的 workspace attachment 能力,使不同 runtime 都能把本地目录附加到各自的 canonical conversation scope,并让 agent 与本地文件工具围绕这个 workspace 形成一致语义。 238 + 239 + 第一阶段明确四条规则: 240 + 241 + - 一个 canonical conversation key 最多绑定一个 workspace 242 + - CLI `chat` 临时保存,CLI `run` 只吃一次性参数,其他有稳定 conversation key 的 runtime 落 attachment store 243 + - 统一命令协议是 `/workspace`、`/workspace attach <dir>`、`/workspace detach` 244 + - 项目文件写 `workspace_dir`,临时文件写 `file_cache_dir`,系统状态写 `file_state_dir` 245 + 246 + 这一期已经覆盖: 247 + 248 + - CLI 249 + - 消息通道 250 + - Console Local runtime 后端 251 + 252 + 这一期仍然不包含新的 Console workspace 专用 UI 控件。 253 + 254 + ## 10) Console Web API 与后端 255 + 256 + ### 10.1 当前边界 257 + 258 + Console web 需要的是一个 UI 可直接调用的结构化 API。 259 + 260 + 但这不意味着要重新定义一套 workspace 语义。 261 + 262 + 当前实现仍然遵守前面已经定下来的规则: 263 + 264 + - 绑定对象仍然是 scope,不是进程 265 + - Console scope key 仍然是 `console:<topic_id>` 266 + - attachment store 仍然只保存绑定关系 267 + - 文本协议仍然是 `/workspace`、`/workspace attach <dir>`、`/workspace detach` 268 + 269 + 所以 Console web 真正新增的,不是新的业务语义,而是: 270 + 271 + - 给浏览器一个结构化 HTTP 面 272 + - 让 Console runtime 把 topic-scoped workspace 真正接进执行链 273 + 274 + ### 10.2 不要把 workspace 塞进 `/tasks` 或 `topic.json` 275 + 276 + 这件事要先说清楚,否则实现很容易跑偏。 277 + 278 + 不要做下面两种设计: 279 + 280 + - 不要把 `workspace_dir` 塞进 `POST /tasks` 281 + - 不要把 `workspace_dir` 持久化进 `tasks/console/topic.json` 282 + 283 + 原因很直接: 284 + 285 + - task 是一次提交,不是长期绑定关系 286 + - topic projection 是 Console 自己的查询视图,不是 attachment store 287 + - workspace attachment 本来就应该独立存放,并以 canonical conversation key 为主键 288 + 289 + 如果把 workspace 写进 task 或 topic projection,等于又把三类目录语义和存储边界搅回去了。 290 + 291 + ### 10.3 HTTP 资源形态 292 + 293 + 这里已经采用简化后的单资源设计,不把 workspace 做成复杂的 topic 子资源。 294 + 295 + 最小 API 直接收成一个单资源: 296 + 297 + - `GET /workspace?topic_id=<id>` 298 + - `PUT /workspace` 299 + - `DELETE /workspace?topic_id=<id>` 300 + 301 + 这里只保留前端真正需要的两个字段: 302 + 303 + - `topic_id` 304 + - `workspace_dir` 305 + 306 + 后端收到后,统一映射成 canonical key: 307 + 308 + - `scope_key = console:<topic_id>` 309 + 310 + 浏览器不需要自己拼 `console:<topic_id>`。 311 + store 也不需要暴露多套主键给前端。 312 + 313 + ### 10.4 返回体 314 + 315 + `GET /workspace?topic_id=t_abc123` 316 + 317 + ```json 318 + { 319 + "topic_id": "t_abc123", 320 + "workspace_dir": "/path/to/project" 321 + } 322 + ``` 323 + 324 + 未绑定时返回: 325 + 326 + ```json 327 + { 328 + "topic_id": "t_abc123", 329 + "workspace_dir": "" 330 + } 331 + ``` 332 + 333 + `PUT /workspace` 334 + 335 + 请求体: 336 + 337 + ```json 338 + { 339 + "topic_id": "t_abc123", 340 + "workspace_dir": "/path/to/project" 341 + } 342 + ``` 343 + 344 + 响应体: 345 + 346 + ```json 347 + { 348 + "topic_id": "t_abc123", 349 + "workspace_dir": "/path/to/project" 350 + } 351 + ``` 352 + 353 + `DELETE /workspace?topic_id=t_abc123` 354 + 355 + 响应体: 356 + 357 + ```json 358 + { 359 + "topic_id": "t_abc123", 360 + "workspace_dir": "" 361 + } 362 + ``` 363 + 364 + 这已经够表达三种状态: 365 + 366 + - 有值:已绑定 367 + - 空串:未绑定 368 + - `PUT` 覆盖旧值:替换绑定 369 + 370 + `workspace attached/replaced/detached` 这种文本回显,继续留给 `/workspace ...` 文本协议即可,不必塞进结构化 API。 371 + 372 + ### 10.5 错误语义 373 + 374 + 当前 Console Local backend 的错误语义如下: 375 + 376 + - `400 Bad Request` 377 + - `topic_id` 非法 378 + - 请求体缺 `workspace_dir` 379 + - 路径不存在 380 + - 路径不是目录 381 + - 路径不可读 382 + - `503 Service Unavailable` 383 + - 当前 runtime 不支持 topic-scoped workspace 384 + - `500 Internal Server Error` 385 + - store 读写失败 386 + - runtime 内部错误 387 + 388 + 仍然保持: 389 + 390 + - 不自动创建目录 391 + - 不接受前端直接提交 canonical key 392 + - 不接受“顺手帮我新建 topic + attach workspace”这种复合魔法动作 393 + 394 + 当前实现也没有强制做 `404 topic not found`。 395 + 396 + 原因不是不能做,而是没必要: 397 + 398 + - attachment 的本体是 `scope_key -> workspace_dir` 399 + - 当前通用 runtime 抽象里也没有统一的 `GetTopic()` 能力 400 + - 为了一个 `404` 去扩 topic 接口,收益很低 401 + 402 + 更简单的做法是: 403 + 404 + - `/workspace` 只关心 attachment store 405 + - topic 删除成功时,顺手删掉 `console:<topic_id>` 的 attachment 406 + 407 + 这样职责更清楚,也更省实现成本。 408 + 409 + 补一条实现层面的事实: 410 + 411 + - 当前 Console Local runtime 在 `PUT /workspace` 时调用的是 `workspace.ValidateDir(..., nil)` 412 + - 也就是会检查存在、可读、目录类型,但还没有额外加 Console 自己的 allow-roots 约束 413 + - 如果以后 Console 引入显式 allow-roots,再把“路径不在允许范围内”并入同一个 `400` 即可 414 + 415 + ### 10.6 文本协议仍然保留 416 + 417 + Console web 做了结构化 API,并不意味着 `/workspace ...` 文本协议可以删掉。 418 + 419 + 当前后端已经同时保留两条入口: 420 + 421 + - Chat 输入框里直接输入 `/workspace` 422 + - UI 上通过结构化 API 做 attach / detach / status 423 + 424 + 两条入口共用的是同一份 store、同一套 scope key 规则和同一组 workspace 语义,但代码路径不完全相同: 425 + 426 + - 文本协议路径: 427 + - `chatcommands.ParseCommand(...)` 428 + - `workspace.ExecuteStoreCommand(...)` 429 + - HTTP 路径: 430 + - `workspaceDirForTopic(...)` 431 + - `setWorkspaceDirForTopic(...)` 432 + - `deleteWorkspaceDirForTopic(...)` 433 + 434 + 也就是说: 435 + 436 + - 结构化 API 是给 UI 控件用的 437 + - 文本协议是给 chat 入口和跨 runtime 一致性用的 438 + 439 + ### 10.7 当前后端接线 440 + 441 + 现在已经落下来的接线有四段。 442 + 443 + 第一段是 submit path: 444 + 445 + - `consoleLocalRuntime.submitTask()` 会先判断输入是不是 `/workspace ...` 446 + - 命中后不走 LLM 任务 447 + - 直接执行 workspace 命令并返回 synthetic task result 448 + 449 + 第二段是 API path: 450 + 451 + - `routesOptions(...)` 已经挂上 `WorkspaceGet`、`WorkspacePut`、`WorkspaceDelete` 452 + - `/workspace` runtime API 已经可以直接读写 topic-scoped attachment 453 + 454 + 第三段是 accept/run path: 455 + 456 + - `acceptTask(...)` 会按 `console:<topic_id>` 读取当前 attachment 457 + - bus fallback 重建 job 时也会补回 `WorkspaceDir` 458 + - 执行任务时会设置 `pathroots.WithWorkspaceDir(ctx, job.WorkspaceDir)` 459 + - prompt augment 会 prepend `workspace.PromptBlock(job.WorkspaceDir)` 460 + 461 + 第四段是清理 path: 462 + 463 + - topic 删除成功时,会同步删除 `console:<topic_id>` attachment 464 + 465 + 如果不做这段接线,Web UI 即使能 attach 成功,也不会真正影响: 466 + 467 + - `read_file` 468 + - `write_file` 469 + - `bash` 470 + - `powershell` 471 + - prompt 里的项目上下文 472 + 473 + 那这个 API 只是摆设。 474 + 475 + ### 10.8 与现有前端调用方式的关系 476 + 477 + 当前 Console web 的 runtime 数据面已经统一走: 478 + 479 + - `GET /api/proxy?endpoint=<ref>&uri=<runtime-path>` 480 + 481 + 所以不需要再造一个 Console 专用“workspace 总控 API”。 482 + 483 + 正确做法是: 484 + 485 + - 把 `/workspace` 做成 runtime API 486 + - 浏览器仍然通过现有 `/api/proxy` 转发 487 + 488 + 这样: 489 + 490 + - `Console Local` 可直接支持 491 + - 未来如果出现远端 console-like runtime,也能复用同一条路 492 + - Console backend 不需要额外复制一遍 attachment 业务逻辑 493 + 494 + ### 10.9 UI 约束 495 + 496 + 当前后端实现仍然按“workspace 只绑定到已存在 topic”处理。 497 + 498 + 这意味着: 499 + 500 + - 当前 topic 未创建时,不提供 attach 操作 501 + - 前端在 `creatingTopic=true` 且没有真实 `topic_id` 时,应禁用 workspace 控件 502 + - 用户先发第一条消息创建 topic,再 attach workspace 503 + 504 + 这是当前最小方案。 505 + 506 + 如果以后明确需要“先 attach,再开始第一条消息”,那是下一步单独讨论的事。 507 + 到那时再决定是否补: 508 + 509 + - `POST /topics` 510 + - 或 `POST /tasks` 支持显式创建空 topic 511 + 512 + 但这不是这次 Console workspace API 的最小范围。 513 + 514 + ## 11) Checklist 515 + 516 + ### 11.1 路径模型 517 + 518 + - [x] 引入显式 `PathRoots` 519 + - [x] 在运行时构造 `PathRoots{WorkspaceDir, FileCacheDir, FileStateDir}` 520 + - [x] 去掉当前 CLI `chat` 对 `file_cache_dir = cwd` 的错误耦合 521 + 522 + ### 11.2 attachment store 523 + 524 + - [x] 新增 workspace attachment store 525 + - [x] store 主键固定为 canonical conversation key 526 + - [x] store value 最小只保存 `WorkspaceDir` 527 + - [x] `attach` 时覆盖旧值,不保留双绑定 528 + - [x] `detach` 时删除当前绑定 529 + 530 + ### 11.3 scope key 接线 531 + 532 + - [x] Telegram 统一使用 `tg:<chat_id>` 533 + - [x] Slack 统一使用 `slack:<team_id>:<channel_id>` 534 + - [x] LINE 统一使用 `line:<chat_id>` 或 `line:<group_id>` 535 + - [x] Lark 统一使用 `lark:<chat_id>` 536 + 537 + ### 11.4 命令解析 538 + 539 + - [x] 复用现有公用命令解析设施 540 + - [x] 支持 `/workspace` 541 + - [x] 支持 `/workspace attach <dir>` 542 + - [x] 支持 `/workspace detach` 543 + - [x] 非法语法返回稳定错误 544 + 545 + ### 11.5 CLI 546 + 547 + - [x] `chat` 支持 `--workspace <dir>` 548 + - [x] `chat` 支持 `--no-workspace` 549 + - [x] `chat` 默认当前目录作为 `workspace_dir` 550 + - [x] `chat` 进程内保存当前 workspace 551 + - [x] `run` 支持 `--workspace <dir>` 552 + - [x] `run` 支持 `--no-workspace` 553 + - [x] `run` 默认当前目录作为 `workspace_dir` 554 + - [x] `run` 不写 attachment store 555 + 556 + ### 11.6 Console Local backend 557 + 558 + - [x] Console 聊天输入可透传 `/workspace` 文本协议 559 + - [x] runtime API 新增 `GET /workspace?topic_id=<id>` 560 + - [x] runtime API 新增 `PUT /workspace` 561 + - [x] runtime API 新增 `DELETE /workspace?topic_id=<id>` 562 + - [x] Console 统一使用 `console:<topic_id>` 563 + - [x] 一个 topic 可绑定一个 workspace 564 + - [x] 不同 topic 可绑定不同 workspace 565 + - [x] 切 topic 时切换当前 workspace 566 + - [x] 刷新后能从 attachment store 恢复绑定 567 + - [x] topic 删除成功时同步删除 `console:<topic_id>` attachment 568 + - [x] Console runtime 在 run path 接入 workspace lookup + `pathroots.WithWorkspaceDir` 569 + - [x] Console runtime 在 prompt augment 接入 `workspace.PromptBlock` 570 + - [ ] Console workspace 专用 UI 控件 571 + 572 + ### 11.7 Channel runtimes 573 + 574 + - [x] Telegram 接入 `/workspace` 文本协议 575 + - [x] Slack 接入 `/workspace` 文本协议 576 + - [x] LINE 接入 `/workspace` 文本协议 577 + - [x] Lark 接入 `/workspace` 文本协议 578 + - [x] 这些 runtime 重启后能从 attachment store 恢复绑定 579 + 580 + ### 11.8 工具层 581 + 582 + - [x] `write_file` 支持 `workspace_dir/<path>` alias 583 + - [x] `read_file` 支持 `workspace_dir/<path>` alias 584 + - [x] 有 workspace 时,相对路径默认按 workspace 解析 585 + - [x] `bash` 默认 `cwd = workspace_dir` 586 + - [x] `powershell` 默认 `cwd = workspace_dir` 587 + - [x] `url_fetch` 继续写 `file_cache_dir` 588 + - [x] TODO / memory / guard / contacts / skills 继续写 `file_state_dir` 589 + - [x] guard 与 deny-path 校验覆盖三类目录 590 + 591 + ### 11.9 返回语义 592 + 593 + - [x] `/workspace` 返回当前绑定状态 594 + - [x] 首次 attach 明确返回绑定成功 595 + - [x] 替换 attach 明确返回旧目录到新目录的切换结果 596 + - [x] `detach` 明确返回解绑成功 597 + - [x] 路径不存在时返回失败 598 + - [x] 路径不可读时返回失败 599 + - [x] 当 runtime 配置了 allow roots 时,路径不在允许范围内返回失败 600 + - [x] 不自动创建目录 601 + 602 + ### 11.10 最小测试(当前) 603 + 604 + - [x] attachment store 按 canonical key 读写正常 605 + - [x] 同一 key 重复 attach 只保留最新值 606 + - [x] detach 后绑定消失 607 + - [x] `/workspace` 三种语法解析正确 608 + - [x] Console `/workspace` 文本协议返回 synthetic task result 609 + - [x] Console `acceptTask` 能从 attachment store 恢复 workspace 610 + - [x] Console topic 删除会清理 workspace attachment 611 + - [x] Console `/workspace` runtime API 的 `GET` / `PUT` / `DELETE` 已覆盖测试 612 + - [ ] CLI `chat` 不再把 cwd 塞进 `file_cache_dir` 613 + - [ ] Telegram / Slack / LINE / Lark 重启后能恢复绑定 614 + - [x] `write_file` / `read_file` / shell 工具按 workspace 生效
+1 -2
integration/registry.go
··· 59 59 toolsutil.RegisterStaticTools(r, toolsutil.StaticRegistryConfig{ 60 60 Common: toolsutil.StaticCommonConfig{ 61 61 UserAgent: userAgent, 62 - FileCacheDir: strings.TrimSpace(cfg.FileCacheDir), 63 - FileStateDir: strings.TrimSpace(cfg.FileStateDir), 62 + PathRoots: cfg.PathRoots, 64 63 AuthenticatedHTTPConfigured: authenticatedHTTPConfigured, 65 64 }, 66 65 ReadFile: toolsutil.StaticReadFileConfig{
+42 -42
integration/runtime_snapshot.go
··· 10 10 "github.com/quailyquaily/mistermorph/internal/channelopts" 11 11 "github.com/quailyquaily/mistermorph/internal/llmutil" 12 12 "github.com/quailyquaily/mistermorph/internal/mcphost" 13 + "github.com/quailyquaily/mistermorph/internal/pathroots" 13 14 "github.com/quailyquaily/mistermorph/internal/skillsutil" 14 15 "github.com/quailyquaily/mistermorph/secrets" 15 16 ) ··· 31 32 } 32 33 33 34 type registrySnapshot struct { 34 - UserAgent string 35 - SecretsAllowProfiles []string 36 - AuthProfiles map[string]secrets.AuthProfile 37 - FileCacheDir string 38 - FileStateDir string 39 - ToolsReadFileMaxBytes int64 40 - ToolsReadFileDenyPaths []string 41 - ToolsWriteFileEnabled bool 42 - ToolsWriteFileMaxBytes int 43 - ToolsSpawnEnabled bool 44 - ToolsACPSpawnEnabled bool 45 - ToolsBashEnabled bool 46 - ToolsBashTimeout time.Duration 47 - ToolsBashMaxOutputBytes int 48 - ToolsBashDenyPaths []string 49 - ToolsBashInjectedEnvVars []string 50 - ToolsPowerShellEnabled bool 51 - ToolsPowerShellTimeout time.Duration 52 - ToolsPowerShellMaxOutputBytes int 53 - ToolsPowerShellDenyPaths []string 35 + UserAgent string 36 + SecretsAllowProfiles []string 37 + AuthProfiles map[string]secrets.AuthProfile 38 + PathRoots pathroots.PathRoots 39 + ToolsReadFileMaxBytes int64 40 + ToolsReadFileDenyPaths []string 41 + ToolsWriteFileEnabled bool 42 + ToolsWriteFileMaxBytes int 43 + ToolsSpawnEnabled bool 44 + ToolsACPSpawnEnabled bool 45 + ToolsBashEnabled bool 46 + ToolsBashTimeout time.Duration 47 + ToolsBashMaxOutputBytes int 48 + ToolsBashDenyPaths []string 49 + ToolsBashInjectedEnvVars []string 50 + ToolsPowerShellEnabled bool 51 + ToolsPowerShellTimeout time.Duration 52 + ToolsPowerShellMaxOutputBytes int 53 + ToolsPowerShellDenyPaths []string 54 54 ToolsPowerShellInjectedEnvVars []string 55 - ToolsURLFetchEnabled bool 56 - ToolsURLFetchTimeout time.Duration 57 - ToolsURLFetchMaxBytes int64 58 - ToolsURLFetchMaxBytesDownload int64 59 - ToolsWebSearchEnabled bool 60 - ToolsWebSearchTimeout time.Duration 61 - ToolsWebSearchMaxResults int 62 - ToolsWebSearchBaseURL string 63 - ToolsContactsSendEnabled bool 64 - ToolsPlanCreateEnabled bool 65 - ToolsPlanCreateMaxSteps int 66 - ToolsTodoUpdateEnabled bool 67 - TODOPathWIP string 68 - TODOPathDone string 69 - ContactsDir string 70 - TelegramBotToken string 71 - TelegramBaseURL string 72 - SlackBotToken string 73 - SlackBaseURL string 74 - LineChannelAccessToken string 75 - LineBaseURL string 76 - ContactsFailureCooldown time.Duration 55 + ToolsURLFetchEnabled bool 56 + ToolsURLFetchTimeout time.Duration 57 + ToolsURLFetchMaxBytes int64 58 + ToolsURLFetchMaxBytesDownload int64 59 + ToolsWebSearchEnabled bool 60 + ToolsWebSearchTimeout time.Duration 61 + ToolsWebSearchMaxResults int 62 + ToolsWebSearchBaseURL string 63 + ToolsContactsSendEnabled bool 64 + ToolsPlanCreateEnabled bool 65 + ToolsPlanCreateMaxSteps int 66 + ToolsTodoUpdateEnabled bool 67 + TODOPathWIP string 68 + TODOPathDone string 69 + ContactsDir string 70 + TelegramBotToken string 71 + TelegramBaseURL string 72 + SlackBotToken string 73 + SlackBaseURL string 74 + LineChannelAccessToken string 75 + LineBaseURL string 76 + ContactsFailureCooldown time.Duration 77 77 } 78 78 79 79 type guardSnapshot struct {
+42 -42
integration/runtime_snapshot_loader.go
··· 12 12 "github.com/quailyquaily/mistermorph/internal/llmutil" 13 13 "github.com/quailyquaily/mistermorph/internal/logutil" 14 14 "github.com/quailyquaily/mistermorph/internal/mcphost" 15 + "github.com/quailyquaily/mistermorph/internal/pathroots" 15 16 "github.com/quailyquaily/mistermorph/internal/pathutil" 16 17 "github.com/quailyquaily/mistermorph/internal/skillsutil" 17 18 "github.com/quailyquaily/mistermorph/internal/statepaths" ··· 71 72 }, 72 73 SkillsConfig: cloneSkillsConfig(skillsutil.SkillsConfigFromReader(v)), 73 74 Registry: registrySnapshot{ 74 - UserAgent: strings.TrimSpace(v.GetString("user_agent")), 75 - SecretsAllowProfiles: append([]string(nil), v.GetStringSlice("secrets.allow_profiles")...), 76 - AuthProfiles: copyAuthProfilesMap(authProfiles), 77 - FileCacheDir: strings.TrimSpace(v.GetString("file_cache_dir")), 78 - FileStateDir: fileStateDir, 79 - ToolsReadFileMaxBytes: int64(v.GetInt("tools.read_file.max_bytes")), 80 - ToolsReadFileDenyPaths: append([]string(nil), v.GetStringSlice("tools.read_file.deny_paths")...), 81 - ToolsWriteFileEnabled: v.GetBool("tools.write_file.enabled"), 82 - ToolsWriteFileMaxBytes: v.GetInt("tools.write_file.max_bytes"), 83 - ToolsSpawnEnabled: v.GetBool("tools.spawn.enabled"), 84 - ToolsACPSpawnEnabled: v.GetBool("tools.acp_spawn.enabled"), 85 - ToolsBashEnabled: v.GetBool("tools.bash.enabled"), 86 - ToolsBashTimeout: v.GetDuration("tools.bash.timeout"), 87 - ToolsBashMaxOutputBytes: v.GetInt("tools.bash.max_output_bytes"), 88 - ToolsBashDenyPaths: append([]string(nil), v.GetStringSlice("tools.bash.deny_paths")...), 89 - ToolsBashInjectedEnvVars: append([]string(nil), v.GetStringSlice("tools.bash.injected_env_vars")...), 90 - ToolsPowerShellEnabled: v.GetBool("tools.powershell.enabled"), 91 - ToolsPowerShellTimeout: v.GetDuration("tools.powershell.timeout"), 92 - ToolsPowerShellMaxOutputBytes: v.GetInt("tools.powershell.max_output_bytes"), 93 - ToolsPowerShellDenyPaths: append([]string(nil), v.GetStringSlice("tools.powershell.deny_paths")...), 75 + UserAgent: strings.TrimSpace(v.GetString("user_agent")), 76 + SecretsAllowProfiles: append([]string(nil), v.GetStringSlice("secrets.allow_profiles")...), 77 + AuthProfiles: copyAuthProfilesMap(authProfiles), 78 + PathRoots: pathroots.New("", strings.TrimSpace(v.GetString("file_cache_dir")), fileStateDir), 79 + ToolsReadFileMaxBytes: int64(v.GetInt("tools.read_file.max_bytes")), 80 + ToolsReadFileDenyPaths: append([]string(nil), v.GetStringSlice("tools.read_file.deny_paths")...), 81 + ToolsWriteFileEnabled: v.GetBool("tools.write_file.enabled"), 82 + ToolsWriteFileMaxBytes: v.GetInt("tools.write_file.max_bytes"), 83 + ToolsSpawnEnabled: v.GetBool("tools.spawn.enabled"), 84 + ToolsACPSpawnEnabled: v.GetBool("tools.acp_spawn.enabled"), 85 + ToolsBashEnabled: v.GetBool("tools.bash.enabled"), 86 + ToolsBashTimeout: v.GetDuration("tools.bash.timeout"), 87 + ToolsBashMaxOutputBytes: v.GetInt("tools.bash.max_output_bytes"), 88 + ToolsBashDenyPaths: append([]string(nil), v.GetStringSlice("tools.bash.deny_paths")...), 89 + ToolsBashInjectedEnvVars: append([]string(nil), v.GetStringSlice("tools.bash.injected_env_vars")...), 90 + ToolsPowerShellEnabled: v.GetBool("tools.powershell.enabled"), 91 + ToolsPowerShellTimeout: v.GetDuration("tools.powershell.timeout"), 92 + ToolsPowerShellMaxOutputBytes: v.GetInt("tools.powershell.max_output_bytes"), 93 + ToolsPowerShellDenyPaths: append([]string(nil), v.GetStringSlice("tools.powershell.deny_paths")...), 94 94 ToolsPowerShellInjectedEnvVars: append([]string(nil), v.GetStringSlice("tools.powershell.injected_env_vars")...), 95 - ToolsURLFetchEnabled: v.GetBool("tools.url_fetch.enabled"), 96 - ToolsURLFetchTimeout: v.GetDuration("tools.url_fetch.timeout"), 97 - ToolsURLFetchMaxBytes: v.GetInt64("tools.url_fetch.max_bytes"), 98 - ToolsURLFetchMaxBytesDownload: v.GetInt64("tools.url_fetch.max_bytes_download"), 99 - ToolsWebSearchEnabled: v.GetBool("tools.web_search.enabled"), 100 - ToolsWebSearchTimeout: v.GetDuration("tools.web_search.timeout"), 101 - ToolsWebSearchMaxResults: v.GetInt("tools.web_search.max_results"), 102 - ToolsWebSearchBaseURL: v.GetString("tools.web_search.base_url"), 103 - ToolsContactsSendEnabled: v.GetBool("tools.contacts_send.enabled"), 104 - ToolsPlanCreateEnabled: v.GetBool("tools.plan_create.enabled"), 105 - ToolsPlanCreateMaxSteps: v.GetInt("tools.plan_create.max_steps"), 106 - ToolsTodoUpdateEnabled: v.GetBool("tools.todo_update.enabled"), 107 - TODOPathWIP: pathutil.ResolveStateFile(fileStateDir, statepaths.TODOWIPFilename), 108 - TODOPathDone: pathutil.ResolveStateFile(fileStateDir, statepaths.TODODONEFilename), 109 - ContactsDir: pathutil.ResolveStateChildDir(fileStateDir, strings.TrimSpace(v.GetString("contacts.dir_name")), "contacts"), 110 - TelegramBotToken: strings.TrimSpace(v.GetString("telegram.bot_token")), 111 - TelegramBaseURL: "https://api.telegram.org", 112 - SlackBotToken: strings.TrimSpace(v.GetString("slack.bot_token")), 113 - SlackBaseURL: strings.TrimSpace(v.GetString("slack.base_url")), 114 - LineChannelAccessToken: strings.TrimSpace(v.GetString("line.channel_access_token")), 115 - LineBaseURL: strings.TrimSpace(v.GetString("line.base_url")), 116 - ContactsFailureCooldown: contactsFailureCooldownFromReader(v), 95 + ToolsURLFetchEnabled: v.GetBool("tools.url_fetch.enabled"), 96 + ToolsURLFetchTimeout: v.GetDuration("tools.url_fetch.timeout"), 97 + ToolsURLFetchMaxBytes: v.GetInt64("tools.url_fetch.max_bytes"), 98 + ToolsURLFetchMaxBytesDownload: v.GetInt64("tools.url_fetch.max_bytes_download"), 99 + ToolsWebSearchEnabled: v.GetBool("tools.web_search.enabled"), 100 + ToolsWebSearchTimeout: v.GetDuration("tools.web_search.timeout"), 101 + ToolsWebSearchMaxResults: v.GetInt("tools.web_search.max_results"), 102 + ToolsWebSearchBaseURL: v.GetString("tools.web_search.base_url"), 103 + ToolsContactsSendEnabled: v.GetBool("tools.contacts_send.enabled"), 104 + ToolsPlanCreateEnabled: v.GetBool("tools.plan_create.enabled"), 105 + ToolsPlanCreateMaxSteps: v.GetInt("tools.plan_create.max_steps"), 106 + ToolsTodoUpdateEnabled: v.GetBool("tools.todo_update.enabled"), 107 + TODOPathWIP: pathutil.ResolveStateFile(fileStateDir, statepaths.TODOWIPFilename), 108 + TODOPathDone: pathutil.ResolveStateFile(fileStateDir, statepaths.TODODONEFilename), 109 + ContactsDir: pathutil.ResolveStateChildDir(fileStateDir, strings.TrimSpace(v.GetString("contacts.dir_name")), "contacts"), 110 + TelegramBotToken: strings.TrimSpace(v.GetString("telegram.bot_token")), 111 + TelegramBaseURL: "https://api.telegram.org", 112 + SlackBotToken: strings.TrimSpace(v.GetString("slack.bot_token")), 113 + SlackBaseURL: strings.TrimSpace(v.GetString("slack.base_url")), 114 + LineChannelAccessToken: strings.TrimSpace(v.GetString("line.channel_access_token")), 115 + LineBaseURL: strings.TrimSpace(v.GetString("line.base_url")), 116 + ContactsFailureCooldown: contactsFailureCooldownFromReader(v), 117 117 }, 118 118 Guard: guardSnapshot{ 119 119 Enabled: v.GetBool("guard.enabled"),
+27 -10
internal/acpclient/terminal.go
··· 33 33 } 34 34 35 35 type managedTerminal struct { 36 - id string 37 - sessionID string 38 - cmd *exec.Cmd 39 - output *terminalOutputBuffer 40 - done chan struct{} 36 + id string 37 + sessionID string 38 + cmd *exec.Cmd 39 + output *terminalOutputBuffer 40 + done chan struct{} 41 + captureDone chan struct{} 42 + captureWG sync.WaitGroup 41 43 42 44 mu sync.Mutex 43 45 exited bool ··· 130 132 131 133 terminalID := m.nextTerminalID() 132 134 term := &managedTerminal{ 133 - id: terminalID, 134 - sessionID: strings.TrimSpace(req.SessionID), 135 - cmd: cmd, 136 - output: &terminalOutputBuffer{limit: outputLimit}, 137 - done: make(chan struct{}), 135 + id: terminalID, 136 + sessionID: strings.TrimSpace(req.SessionID), 137 + cmd: cmd, 138 + output: &terminalOutputBuffer{limit: outputLimit}, 139 + done: make(chan struct{}), 140 + captureDone: make(chan struct{}), 138 141 } 139 142 140 143 if err := cmd.Start(); err != nil { ··· 145 148 m.terminals[terminalID] = term 146 149 m.mu.Unlock() 147 150 151 + term.captureWG.Add(2) 148 152 go term.capture(stdout) 149 153 go term.capture(stderr) 150 154 go term.wait() ··· 253 257 } 254 258 255 259 func (t *managedTerminal) capture(r io.ReadCloser) { 260 + defer t.captureWG.Done() 256 261 defer func() { _ = r.Close() }() 257 262 _, _ = io.Copy(t.output, r) 258 263 } ··· 284 289 slog.Default().Debug("acp_terminal_exit", "terminal_id", t.id) 285 290 } 286 291 close(t.done) 292 + t.captureWG.Wait() 293 + if t.captureDone != nil { 294 + close(t.captureDone) 295 + } 287 296 } 288 297 289 298 func (t *managedTerminal) waitContext(ctx context.Context) error { ··· 294 303 case <-ctx.Done(): 295 304 return ctx.Err() 296 305 case <-t.done: 306 + } 307 + if t.captureDone == nil { 308 + return nil 309 + } 310 + select { 311 + case <-ctx.Done(): 312 + return ctx.Err() 313 + case <-t.captureDone: 297 314 return nil 298 315 } 299 316 }
+54
internal/acpclient/terminal_test.go
··· 1 1 package acpclient 2 2 3 3 import ( 4 + "context" 5 + "errors" 4 6 "os" 5 7 "path/filepath" 6 8 "runtime" 7 9 "testing" 10 + "time" 8 11 ) 9 12 10 13 func TestClampTerminalOutputLimit(t *testing.T) { ··· 53 56 t.Fatal("resolveTerminalCWD() error = nil, want outside allowed roots") 54 57 } 55 58 } 59 + 60 + func TestManagedTerminalWaitContext_WaitsForCapturedOutput(t *testing.T) { 61 + t.Parallel() 62 + 63 + term := &managedTerminal{ 64 + done: make(chan struct{}), 65 + captureDone: make(chan struct{}), 66 + } 67 + 68 + errCh := make(chan error, 1) 69 + go func() { 70 + errCh <- term.waitContext(context.Background()) 71 + }() 72 + 73 + close(term.done) 74 + 75 + select { 76 + case err := <-errCh: 77 + t.Fatalf("waitContext() returned early: %v", err) 78 + case <-time.After(20 * time.Millisecond): 79 + } 80 + 81 + close(term.captureDone) 82 + 83 + select { 84 + case err := <-errCh: 85 + if err != nil { 86 + t.Fatalf("waitContext() error = %v", err) 87 + } 88 + case <-time.After(time.Second): 89 + t.Fatal("waitContext() did not return after capture completed") 90 + } 91 + } 92 + 93 + func TestManagedTerminalWaitContext_RespectsContextWhileWaitingForCapture(t *testing.T) { 94 + t.Parallel() 95 + 96 + term := &managedTerminal{ 97 + done: make(chan struct{}), 98 + captureDone: make(chan struct{}), 99 + } 100 + close(term.done) 101 + 102 + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) 103 + defer cancel() 104 + 105 + err := term.waitContext(ctx) 106 + if !errors.Is(err, context.DeadlineExceeded) { 107 + t.Fatalf("waitContext() error = %v, want %v", err, context.DeadlineExceeded) 108 + } 109 + }
+19
internal/channelruntime/lark/runtime.go
··· 15 15 runtimecore "github.com/quailyquaily/mistermorph/internal/channelruntime/core" 16 16 "github.com/quailyquaily/mistermorph/internal/channelruntime/depsutil" 17 17 "github.com/quailyquaily/mistermorph/internal/channelruntime/taskruntime" 18 + "github.com/quailyquaily/mistermorph/internal/chatcommands" 18 19 "github.com/quailyquaily/mistermorph/internal/chathistory" 19 20 "github.com/quailyquaily/mistermorph/internal/daemonruntime" 20 21 "github.com/quailyquaily/mistermorph/internal/llminspect" ··· 22 23 "github.com/quailyquaily/mistermorph/internal/llmutil" 23 24 "github.com/quailyquaily/mistermorph/internal/personautil" 24 25 "github.com/quailyquaily/mistermorph/internal/statepaths" 26 + "github.com/quailyquaily/mistermorph/internal/workspace" 25 27 "github.com/quailyquaily/mistermorph/llm" 26 28 ) 27 29 ··· 64 66 if err := contactsStore.Ensure(context.Background()); err != nil { 65 67 return err 66 68 } 69 + workspaceStore := workspace.NewStore(statepaths.WorkspaceAttachmentsPath()) 67 70 contactsSvc := contacts.NewService(contactsStore) 68 71 larkInboundAdapter, err := larkbus.NewInboundAdapter(larkbus.InboundAdapterOptions{ 69 72 Bus: inprocBus, ··· 322 325 if text == "" { 323 326 return fmt.Errorf("lark inbound text is required") 324 327 } 328 + cmdWord, cmdArgs := chatcommands.ParseCommand(text) 329 + if chatcommands.NormalizeCommand(cmdWord) == "/workspace" { 330 + result, cmdErr := workspace.ExecuteStoreCommand(workspaceStore, msg.ConversationKey, cmdArgs, nil) 331 + reply := result.Reply 332 + if cmdErr != nil { 333 + reply = "error: " + strings.TrimSpace(cmdErr.Error()) 334 + } 335 + correlationID := fmt.Sprintf("lark:workspace:%s:%s", inbound.ChatID, inbound.MessageID) 336 + _, publishErr := publishLarkBusOutbound(ctx, inprocBus, inbound.ChatID, reply, inbound.MessageID, correlationID) 337 + return publishErr 338 + } 325 339 if strings.EqualFold(strings.TrimSpace(inbound.ChatType), "group") { 326 340 mu.Lock() 327 341 historySnapshot := append([]chathistory.ChatHistoryItem(nil), history[msg.ConversationKey]...) ··· 377 391 ) 378 392 } 379 393 394 + workspaceDir, err := workspace.LookupWorkspaceDir(workspaceStore, msg.ConversationKey) 395 + if err != nil { 396 + return err 397 + } 380 398 jobTaskID := larkTaskID(inbound.ChatID, inbound.MessageID) 381 399 if err := runner.Enqueue(ctx, msg.ConversationKey, func(version uint64) larkJob { 382 400 return larkJob{ ··· 388 406 FromUserID: inbound.FromUserID, 389 407 DisplayName: inbound.DisplayName, 390 408 Text: text, 409 + WorkspaceDir: workspaceDir, 391 410 SentAt: inbound.SentAt, 392 411 Version: version, 393 412 MentionUsers: append([]string(nil), inbound.MentionUsers...),
+7
internal/channelruntime/lark/runtime_task.go
··· 15 15 "github.com/quailyquaily/mistermorph/internal/idempotency" 16 16 "github.com/quailyquaily/mistermorph/internal/llmstats" 17 17 "github.com/quailyquaily/mistermorph/internal/memoryruntime" 18 + "github.com/quailyquaily/mistermorph/internal/pathroots" 18 19 "github.com/quailyquaily/mistermorph/internal/promptprofile" 19 20 "github.com/quailyquaily/mistermorph/internal/todo" 20 21 "github.com/quailyquaily/mistermorph/internal/toolsutil" 22 + "github.com/quailyquaily/mistermorph/internal/workspace" 21 23 "github.com/quailyquaily/mistermorph/llm" 22 24 "github.com/quailyquaily/mistermorph/tools" 23 25 "github.com/quailyquaily/mistermorph/tools/builtin" ··· 40 42 FromUserID string 41 43 DisplayName string 42 44 Text string 45 + WorkspaceDir string 43 46 SentAt time.Time 44 47 Version uint64 45 48 MentionUsers []string ··· 60 63 return nil, nil, nil, fmt.Errorf("lark task runtime is nil") 61 64 } 62 65 ctx = llmstats.WithMetadata(ctx, job.TaskID, job.EventID) 66 + ctx = pathroots.WithWorkspaceDir(ctx, job.WorkspaceDir) 63 67 ctx = builtin.WithContactsSendRuntimeContext(ctx, contactsSendRuntimeContextForLark(job)) 64 68 task := strings.TrimSpace(job.Text) 65 69 if task == "" { ··· 129 133 StickySkills: stickySkills, 130 134 Registry: buildLarkRegistry(rt.BaseRegistry, job.ChatType), 131 135 PromptAugment: func(spec *agent.PromptSpec, reg *tools.Registry) { 136 + if block := workspace.PromptBlock(job.WorkspaceDir); strings.TrimSpace(block.Content) != "" { 137 + spec.Blocks = append([]agent.PromptBlock{block}, spec.Blocks...) 138 + } 132 139 toolsutil.SetTodoUpdateToolAddContext(reg, todoResolveContextForLark(job)) 133 140 promptprofile.AppendLarkRuntimeBlocks(spec, isLarkGroupChat(job.ChatType)) 134 141 },
+20
internal/channelruntime/line/runtime.go
··· 17 17 runtimecore "github.com/quailyquaily/mistermorph/internal/channelruntime/core" 18 18 "github.com/quailyquaily/mistermorph/internal/channelruntime/depsutil" 19 19 "github.com/quailyquaily/mistermorph/internal/channelruntime/taskruntime" 20 + "github.com/quailyquaily/mistermorph/internal/chatcommands" 20 21 "github.com/quailyquaily/mistermorph/internal/chathistory" 21 22 "github.com/quailyquaily/mistermorph/internal/daemonruntime" 22 23 "github.com/quailyquaily/mistermorph/internal/llminspect" ··· 26 27 "github.com/quailyquaily/mistermorph/internal/personautil" 27 28 "github.com/quailyquaily/mistermorph/internal/statepaths" 28 29 "github.com/quailyquaily/mistermorph/internal/telegramutil" 30 + "github.com/quailyquaily/mistermorph/internal/workspace" 29 31 "github.com/quailyquaily/mistermorph/llm" 30 32 ) 31 33 ··· 43 45 DisplayName string 44 46 Text string 45 47 ImagePaths []string 48 + WorkspaceDir string 46 49 SentAt time.Time 47 50 Version uint64 48 51 MentionUsers []string ··· 87 90 if err := contactsStore.Ensure(context.Background()); err != nil { 88 91 return err 89 92 } 93 + workspaceStore := workspace.NewStore(statepaths.WorkspaceAttachmentsPath()) 90 94 contactsSvc := contacts.NewService(contactsStore) 91 95 lineInboundAdapter, err := linebus.NewInboundAdapter(linebus.InboundAdapterOptions{ 92 96 Bus: inprocBus, ··· 368 372 if text == "" { 369 373 return fmt.Errorf("line inbound text is required") 370 374 } 375 + cmdWord, cmdArgs := chatcommands.ParseCommand(text) 376 + if chatcommands.NormalizeCommand(cmdWord) == "/workspace" { 377 + result, cmdErr := workspace.ExecuteStoreCommand(workspaceStore, msg.ConversationKey, cmdArgs, nil) 378 + reply := result.Reply 379 + if cmdErr != nil { 380 + reply = "error: " + strings.TrimSpace(cmdErr.Error()) 381 + } 382 + correlationID := fmt.Sprintf("line:workspace:%s:%s", inbound.ChatID, inbound.MessageID) 383 + _, publishErr := publishLineBusOutbound(ctx, inprocBus, inbound.ChatID, reply, inbound.ReplyToken, correlationID) 384 + return publishErr 385 + } 371 386 if strings.EqualFold(strings.TrimSpace(inbound.ChatType), "group") { 372 387 mu.Lock() 373 388 historySnapshot := append([]chathistory.ChatHistoryItem(nil), history[msg.ConversationKey]...) ··· 460 475 inbound.ImagePaths = []string{path} 461 476 inbound.ImagePending = false 462 477 } 478 + workspaceDir, err := workspace.LookupWorkspaceDir(workspaceStore, msg.ConversationKey) 479 + if err != nil { 480 + return err 481 + } 463 482 jobTaskID := lineTaskID(inbound.ChatID, inbound.MessageID) 464 483 if err := runner.Enqueue(ctx, msg.ConversationKey, func(version uint64) lineJob { 465 484 return lineJob{ ··· 474 493 DisplayName: inbound.DisplayName, 475 494 Text: text, 476 495 ImagePaths: append([]string(nil), inbound.ImagePaths...), 496 + WorkspaceDir: workspaceDir, 477 497 SentAt: inbound.SentAt, 478 498 Version: version, 479 499 MentionUsers: append([]string(nil), inbound.MentionUsers...),
+6
internal/channelruntime/line/runtime_task.go
··· 16 16 "github.com/quailyquaily/mistermorph/internal/idempotency" 17 17 "github.com/quailyquaily/mistermorph/internal/llmstats" 18 18 "github.com/quailyquaily/mistermorph/internal/memoryruntime" 19 + "github.com/quailyquaily/mistermorph/internal/pathroots" 19 20 "github.com/quailyquaily/mistermorph/internal/promptprofile" 20 21 "github.com/quailyquaily/mistermorph/internal/todo" 21 22 "github.com/quailyquaily/mistermorph/internal/toolsutil" 23 + "github.com/quailyquaily/mistermorph/internal/workspace" 22 24 "github.com/quailyquaily/mistermorph/llm" 23 25 "github.com/quailyquaily/mistermorph/tools" 24 26 "github.com/quailyquaily/mistermorph/tools/builtin" ··· 47 49 return nil, nil, nil, fmt.Errorf("line task runtime is nil") 48 50 } 49 51 ctx = llmstats.WithMetadata(ctx, job.TaskID, job.EventID) 52 + ctx = pathroots.WithWorkspaceDir(ctx, job.WorkspaceDir) 50 53 ctx = builtin.WithContactsSendRuntimeContext(ctx, contactsSendRuntimeContextForLine(job)) 51 54 logger := rt.Logger 52 55 task := strings.TrimSpace(job.Text) ··· 122 125 StickySkills: stickySkills, 123 126 Registry: buildLineRegistry(rt.BaseRegistry, job.ChatType), 124 127 PromptAugment: func(spec *agent.PromptSpec, reg *tools.Registry) { 128 + if block := workspace.PromptBlock(job.WorkspaceDir); strings.TrimSpace(block.Content) != "" { 129 + spec.Blocks = append([]agent.PromptBlock{block}, spec.Blocks...) 130 + } 125 131 toolsutil.SetTodoUpdateToolAddContext(reg, todoResolveContextForLine(job)) 126 132 promptprofile.AppendLineRuntimeBlocks(spec, isLineGroupChat(job.ChatType)) 127 133 },
+31
internal/channelruntime/slack/model_command.go
··· 6 6 "strings" 7 7 8 8 busruntime "github.com/quailyquaily/mistermorph/internal/bus" 9 + "github.com/quailyquaily/mistermorph/internal/chatcommands" 10 + "github.com/quailyquaily/mistermorph/internal/workspace" 9 11 ) 10 12 11 13 func maybeHandleSlackProfileCommand(ctx context.Context, d Dependencies, inprocBus *busruntime.Inproc, event slackInboundEvent, botUserID string) (bool, error) { ··· 33 35 output, 34 36 event.ThreadTS, 35 37 fmt.Sprintf("slack:model:%s:%s", event.ChannelID, event.MessageTS), 38 + ) 39 + return true, publishErr 40 + } 41 + 42 + func maybeHandleSlackWorkspaceCommand(ctx context.Context, inprocBus *busruntime.Inproc, store *workspace.Store, conversationKey string, event slackInboundEvent, botUserID string) (bool, error) { 43 + if isSlackGroupChat(event.ChatType) && !slackCommandExplicitlyAddressed(event.Text, botUserID) { 44 + return false, nil 45 + } 46 + text := normalizeSlackCommandText(event.Text, botUserID) 47 + cmdWord, cmdArgs := chatcommands.ParseCommand(text) 48 + if chatcommands.NormalizeCommand(cmdWord) != "/workspace" { 49 + return false, nil 50 + } 51 + result, err := workspace.ExecuteStoreCommand(store, conversationKey, cmdArgs, nil) 52 + output := result.Reply 53 + if err != nil { 54 + output = "error: " + strings.TrimSpace(err.Error()) 55 + } 56 + if ctx == nil { 57 + ctx = context.Background() 58 + } 59 + _, publishErr := publishSlackBusOutbound( 60 + ctx, 61 + inprocBus, 62 + event.TeamID, 63 + event.ChannelID, 64 + output, 65 + event.ThreadTS, 66 + fmt.Sprintf("slack:workspace:%s:%s", event.ChannelID, event.MessageTS), 36 67 ) 37 68 return true, publishErr 38 69 }
+31 -1
internal/channelruntime/slack/runtime.go
··· 24 24 "github.com/quailyquaily/mistermorph/internal/llmutil" 25 25 "github.com/quailyquaily/mistermorph/internal/personautil" 26 26 "github.com/quailyquaily/mistermorph/internal/statepaths" 27 + "github.com/quailyquaily/mistermorph/internal/workspace" 27 28 "github.com/quailyquaily/mistermorph/llm" 28 29 slacktools "github.com/quailyquaily/mistermorph/tools/slack" 29 30 ) ··· 74 75 Username string 75 76 DisplayName string 76 77 Text string 78 + WorkspaceDir string 77 79 SentAt time.Time 78 80 Version uint64 79 81 MentionUsers []string ··· 171 173 if err := contactsStore.Ensure(context.Background()); err != nil { 172 174 return err 173 175 } 176 + workspaceStore := workspace.NewStore(statepaths.WorkspaceAttachmentsPath()) 174 177 contactsSvc := contacts.NewService(contactsStore) 175 178 slackInboundAdapter, err := slackbus.NewInboundAdapter(slackbus.InboundAdapterOptions{ 176 179 Bus: inprocBus, ··· 567 570 if text == "" { 568 571 return fmt.Errorf("slack inbound text is required") 569 572 } 573 + workspaceDir, err := workspace.LookupWorkspaceDir(workspaceStore, msg.ConversationKey) 574 + if err != nil { 575 + return err 576 + } 570 577 jobTaskID := slackTaskID(inbound.TeamID, inbound.ChannelID, inbound.MessageTS) 571 578 if err := runner.Enqueue(ctx, msg.ConversationKey, func(version uint64) slackJob { 572 579 return slackJob{ ··· 581 588 Username: inbound.Username, 582 589 DisplayName: inbound.DisplayName, 583 590 Text: text, 591 + WorkspaceDir: workspaceDir, 584 592 SentAt: inbound.SentAt, 585 593 Version: version, 586 594 MentionUsers: append([]string(nil), inbound.MentionUsers...), ··· 797 805 } 798 806 event.Username = username 799 807 event.DisplayName = displayName 800 - handledCommand, cmdErr := maybeHandleSlackProfileCommand(context.Background(), d, inprocBus, event, botUserID) 808 + handledCommand, cmdErr := maybeHandleSlackWorkspaceCommand(context.Background(), inprocBus, workspaceStore, conversationKey, event, botUserID) 809 + if cmdErr != nil { 810 + logger.Warn("slack_workspace_command_error", 811 + "conversation_key", conversationKey, 812 + "team_id", event.TeamID, 813 + "channel_id", event.ChannelID, 814 + "message_ts", event.MessageTS, 815 + "error", cmdErr.Error(), 816 + ) 817 + callErrorHook(context.Background(), logger, hooks, ErrorEvent{ 818 + Stage: ErrorStagePublishOutbound, 819 + ConversationKey: conversationKey, 820 + TeamID: event.TeamID, 821 + ChannelID: event.ChannelID, 822 + MessageTS: event.MessageTS, 823 + Err: cmdErr, 824 + }) 825 + return nil 826 + } 827 + if handledCommand { 828 + return nil 829 + } 830 + handledCommand, cmdErr = maybeHandleSlackProfileCommand(context.Background(), d, inprocBus, event, botUserID) 801 831 if cmdErr != nil { 802 832 logger.Warn("slack_profile_command_error", 803 833 "conversation_key", conversationKey,
+6
internal/channelruntime/slack/runtime_task.go
··· 14 14 "github.com/quailyquaily/mistermorph/internal/idempotency" 15 15 "github.com/quailyquaily/mistermorph/internal/llmstats" 16 16 "github.com/quailyquaily/mistermorph/internal/memoryruntime" 17 + "github.com/quailyquaily/mistermorph/internal/pathroots" 17 18 "github.com/quailyquaily/mistermorph/internal/promptprofile" 18 19 "github.com/quailyquaily/mistermorph/internal/todo" 19 20 "github.com/quailyquaily/mistermorph/internal/toolsutil" 21 + "github.com/quailyquaily/mistermorph/internal/workspace" 20 22 "github.com/quailyquaily/mistermorph/llm" 21 23 "github.com/quailyquaily/mistermorph/tools" 22 24 "github.com/quailyquaily/mistermorph/tools/builtin" ··· 49 51 return nil, nil, nil, nil, fmt.Errorf("slack task runtime is nil") 50 52 } 51 53 ctx = llmstats.WithRunID(ctx, job.TaskID) 54 + ctx = pathroots.WithWorkspaceDir(ctx, job.WorkspaceDir) 52 55 ctx = builtin.WithContactsSendRuntimeContext(ctx, contactsSendRuntimeContextForSlack(job)) 53 56 logger := rt.Logger 54 57 task := strings.TrimSpace(job.Text) ··· 150 153 StickySkills: stickySkills, 151 154 Registry: reg, 152 155 PromptAugment: func(spec *agent.PromptSpec, reg *tools.Registry) { 156 + if block := workspace.PromptBlock(job.WorkspaceDir); strings.TrimSpace(block.Content) != "" { 157 + spec.Blocks = append([]agent.PromptBlock{block}, spec.Blocks...) 158 + } 153 159 toolsutil.SetTodoUpdateToolAddContext(reg, todoResolveContextForSlack(job)) 154 160 promptprofile.AppendSlackRuntimeBlocks(spec, isSlackGroupChat(job.ChatType), job.MentionUsers, strings.Join(availableEmojiNames, ",")) 155 161 },
+32 -3
internal/channelruntime/telegram/runtime.go
··· 22 22 runtimecore "github.com/quailyquaily/mistermorph/internal/channelruntime/core" 23 23 "github.com/quailyquaily/mistermorph/internal/channelruntime/depsutil" 24 24 "github.com/quailyquaily/mistermorph/internal/channelruntime/taskruntime" 25 + "github.com/quailyquaily/mistermorph/internal/chatcommands" 25 26 "github.com/quailyquaily/mistermorph/internal/chathistory" 26 27 "github.com/quailyquaily/mistermorph/internal/daemonruntime" 27 28 "github.com/quailyquaily/mistermorph/internal/llminspect" ··· 30 31 "github.com/quailyquaily/mistermorph/internal/personautil" 31 32 "github.com/quailyquaily/mistermorph/internal/statepaths" 32 33 "github.com/quailyquaily/mistermorph/internal/telegramutil" 34 + "github.com/quailyquaily/mistermorph/internal/workspace" 33 35 "github.com/quailyquaily/mistermorph/llm" 34 36 telegramtools "github.com/quailyquaily/mistermorph/tools/telegram" 35 37 ) 36 38 37 39 type telegramJob struct { 38 40 TaskID string 41 + ConversationKey string 39 42 ChatID int64 40 43 MessageID int64 41 44 ReplyToMessageID int64 ··· 48 51 FromDisplayName string 49 52 Text string 50 53 ImagePaths []string 54 + WorkspaceDir string 51 55 Version uint64 52 56 Meta map[string]any 53 57 MentionUsers []string ··· 133 137 defer inprocBus.Close() 134 138 135 139 contactsStore := contacts.NewFileStore(statepaths.ContactsDir()) 140 + workspaceStore := workspace.NewStore(statepaths.WorkspaceAttachmentsPath()) 136 141 contactsSvc := contacts.NewService(contactsStore) 137 142 138 143 var telegramInboundAdapter *telegrambus.InboundAdapter ··· 735 740 "text_len", len(text), 736 741 "image_count", len(inbound.ImagePaths), 737 742 ) 743 + workspaceDir, err := workspace.LookupWorkspaceDir(workspaceStore, msg.ConversationKey) 744 + if err != nil { 745 + return err 746 + } 738 747 jobTaskID := telegramTaskID(inbound.ChatID, inbound.MessageID) 739 748 if err := runner.Enqueue(ctx, inbound.ChatID, func(version uint64) telegramJob { 740 749 return telegramJob{ 741 750 TaskID: jobTaskID, 751 + ConversationKey: msg.ConversationKey, 742 752 ChatID: inbound.ChatID, 743 753 MessageID: inbound.MessageID, 744 754 ReplyToMessageID: inbound.ReplyToMessageID, ··· 751 761 FromDisplayName: inbound.FromDisplayName, 752 762 Text: text, 753 763 ImagePaths: append([]string(nil), inbound.ImagePaths...), 764 + WorkspaceDir: workspaceDir, 754 765 Version: version, 755 766 MentionUsers: append([]string(nil), inbound.MentionUsers...), 756 767 } ··· 891 902 mu.Unlock() 892 903 } 893 904 894 - cmdWord, cmdArgs := splitCommand(text) 895 - normalizedCmd := normalizeSlashCommand(cmdWord) 905 + cmdWord, cmdArgs := chatcommands.ParseCommand(text) 906 + normalizedCmd := chatcommands.NormalizeCommand(cmdWord) 896 907 messageRunID := "" 897 908 if msg != nil && msg.MessageID > 0 { 898 909 messageRunID = telegramTaskID(chatID, msg.MessageID) ··· 1026 1037 switch normalizedCmd { 1027 1038 case "/start", "/help": 1028 1039 help := "Send a message and I will run it as an agent task.\n" + 1029 - "Commands: /echo <msg>, /humanize, /model, /reset, /id\n\n" + 1040 + "Commands: /echo <msg>, /humanize, /model, /workspace, /reset, /id\n\n" + 1030 1041 "Group chats: reply to me, or mention @" + botUser + ".\n" + 1031 1042 "You can also send a file (document/photo). It will be downloaded under file_cache_dir/telegram/ and the agent can process it.\n" + 1032 1043 "Note: if Bot Privacy Mode is enabled, I may not receive normal group messages." ··· 1045 1056 continue 1046 1057 case "/id": 1047 1058 _ = api.sendMessageHTML(context.Background(), chatID, fmt.Sprintf("chat_id=%d type=%s", chatID, chatType), true) 1059 + continue 1060 + case "/workspace": 1061 + if len(allowed) > 0 && !allowed[chatID] { 1062 + logger.Warn("telegram_unauthorized_chat", "chat_id", chatID) 1063 + sendTelegramUnauthorizedMessage(api, chatID, chatType) 1064 + continue 1065 + } 1066 + conversationKey, convErr := busruntime.BuildTelegramChatConversationKey(strconv.FormatInt(chatID, 10)) 1067 + if convErr != nil { 1068 + _ = api.sendMessageHTML(context.Background(), chatID, "error: "+htmlstd.EscapeString(convErr.Error()), true) 1069 + continue 1070 + } 1071 + result, cmdErr := workspace.ExecuteStoreCommand(workspaceStore, conversationKey, cmdArgs, nil) 1072 + reply := result.Reply 1073 + if cmdErr != nil { 1074 + reply = "error: " + strings.TrimSpace(cmdErr.Error()) 1075 + } 1076 + _ = api.sendMessageHTML(context.Background(), chatID, htmlstd.EscapeString(reply), true) 1048 1077 continue 1049 1078 case "/humanize": 1050 1079 if len(allowed) > 0 && !allowed[chatID] {
-24
internal/channelruntime/telegram/runtime_helpers.go
··· 19 19 "github.com/quailyquaily/mistermorph/internal/idempotency" 20 20 ) 21 21 22 - func splitCommand(text string) (cmd string, rest string) { 23 - text = strings.TrimSpace(text) 24 - if text == "" { 25 - return "", "" 26 - } 27 - i := strings.IndexAny(text, " \n\t") 28 - if i == -1 { 29 - return text, "" 30 - } 31 - return text[:i], strings.TrimSpace(text[i:]) 32 - } 33 - 34 - func normalizeSlashCommand(cmd string) string { 35 - cmd = strings.TrimSpace(cmd) 36 - if cmd == "" || !strings.HasPrefix(cmd, "/") { 37 - return "" 38 - } 39 - // Allow "/cmd@BotName" variants by stripping "@...". 40 - if at := strings.IndexByte(cmd, '@'); at >= 0 { 41 - cmd = cmd[:at] 42 - } 43 - return strings.ToLower(cmd) 44 - } 45 - 46 22 const mentionUserSnapshotLimit = 12 47 23 48 24 func collectMentionCandidates(msg *telegramMessage, botUser string) []string {
+6
internal/channelruntime/telegram/runtime_task.go
··· 23 23 "github.com/quailyquaily/mistermorph/internal/chathistory" 24 24 "github.com/quailyquaily/mistermorph/internal/llmstats" 25 25 "github.com/quailyquaily/mistermorph/internal/memoryruntime" 26 + "github.com/quailyquaily/mistermorph/internal/pathroots" 26 27 "github.com/quailyquaily/mistermorph/internal/promptprofile" 27 28 "github.com/quailyquaily/mistermorph/internal/todo" 28 29 "github.com/quailyquaily/mistermorph/internal/toolsutil" 30 + "github.com/quailyquaily/mistermorph/internal/workspace" 29 31 "github.com/quailyquaily/mistermorph/llm" 30 32 "github.com/quailyquaily/mistermorph/tools" 31 33 "github.com/quailyquaily/mistermorph/tools/builtin" ··· 53 55 return nil, nil, nil, nil, fmt.Errorf("telegram task runtime is nil") 54 56 } 55 57 ctx = llmstats.WithRunID(ctx, job.TaskID) 58 + ctx = pathroots.WithWorkspaceDir(ctx, job.WorkspaceDir) 56 59 ctx = builtin.WithContactsSendRuntimeContext(ctx, contactsSendRuntimeContextForTelegram(job)) 57 60 if sendTelegramText == nil { 58 61 return nil, nil, nil, nil, fmt.Errorf("send telegram text callback is required") ··· 154 157 StickySkills: stickySkills, 155 158 Registry: reg, 156 159 PromptAugment: func(spec *agent.PromptSpec, reg *tools.Registry) { 160 + if block := workspace.PromptBlock(job.WorkspaceDir); strings.TrimSpace(block.Content) != "" { 161 + spec.Blocks = append([]agent.PromptBlock{block}, spec.Blocks...) 162 + } 157 163 toolsutil.SetTodoUpdateToolAddContext(reg, todo.AddResolveContext{ 158 164 Channel: "telegram", 159 165 ChatType: job.ChatType,
+246 -11
internal/daemonruntime/server.go
··· 37 37 type SubmitFunc func(ctx context.Context, req SubmitTaskRequest) (SubmitTaskResponse, error) 38 38 type OverviewFunc func(ctx context.Context) (map[string]any, error) 39 39 type PokeFunc func(ctx context.Context, input PokeInput) error 40 + type WorkspaceGetFunc func(ctx context.Context, topicID string) (string, error) 41 + type WorkspacePutFunc func(ctx context.Context, topicID string, workspaceDir string) (string, error) 42 + type WorkspaceDeleteFunc func(ctx context.Context, topicID string) error 43 + type WorkspaceOpenFunc func(ctx context.Context, topicID string, targetPath string) error 44 + 45 + type WorkspaceTreeEntry struct { 46 + Name string `json:"name"` 47 + Path string `json:"path"` 48 + IsDir bool `json:"is_dir"` 49 + HasChildren bool `json:"has_children"` 50 + SizeBytes int64 `json:"size_bytes"` 51 + } 52 + 53 + type WorkspaceTreeListing struct { 54 + RootPath string `json:"root_path,omitempty"` 55 + Path string `json:"path"` 56 + Items []WorkspaceTreeEntry `json:"items"` 57 + } 58 + 59 + type WorkspaceTreeFunc func(ctx context.Context, topicID string, treePath string) (WorkspaceTreeListing, error) 60 + type WorkspaceBrowseFunc func(ctx context.Context, treePath string) (WorkspaceTreeListing, error) 40 61 41 62 var ErrPokeBusy = errors.New("poke already running") 42 63 ··· 61 82 } 62 83 63 84 type RoutesOptions struct { 64 - Mode string 65 - AgentName string 66 - AgentNameFunc func() string 67 - AuthToken string 68 - TaskReader TaskReader 69 - TopicReader TopicReader 70 - TopicDeleter TopicDeleter 71 - Submit SubmitFunc 72 - Overview OverviewFunc 73 - Poke PokeFunc 74 - HealthEnabled bool 85 + Mode string 86 + AgentName string 87 + AgentNameFunc func() string 88 + AuthToken string 89 + TaskReader TaskReader 90 + TopicReader TopicReader 91 + TopicDeleter TopicDeleter 92 + Submit SubmitFunc 93 + Overview OverviewFunc 94 + Poke PokeFunc 95 + WorkspaceGet WorkspaceGetFunc 96 + WorkspacePut WorkspacePutFunc 97 + WorkspaceDelete WorkspaceDeleteFunc 98 + WorkspaceOpen WorkspaceOpenFunc 99 + WorkspaceTree WorkspaceTreeFunc 100 + WorkspaceBrowse WorkspaceBrowseFunc 101 + HealthEnabled bool 75 102 } 76 103 77 104 const ( ··· 126 153 instanceID := buildRuntimeInstanceID() 127 154 overview := opts.Overview 128 155 poke := opts.Poke 156 + workspaceGet := opts.WorkspaceGet 157 + workspacePut := opts.WorkspacePut 158 + workspaceDelete := opts.WorkspaceDelete 159 + workspaceOpen := opts.WorkspaceOpen 160 + workspaceTree := opts.WorkspaceTree 161 + workspaceBrowse := opts.WorkspaceBrowse 129 162 var pokeMu sync.RWMutex 130 163 lastPokeAt := "" 131 164 if overview == nil { ··· 784 817 http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 785 818 return 786 819 } 820 + }) 821 + 822 + mux.HandleFunc("/workspace", func(w http.ResponseWriter, r *http.Request) { 823 + if !checkAuth(r, authToken) { 824 + http.Error(w, "unauthorized", http.StatusUnauthorized) 825 + return 826 + } 827 + 828 + writeWorkspaceResponse := func(topicID string, workspaceDir string) { 829 + w.Header().Set("Content-Type", "application/json") 830 + _ = json.NewEncoder(w).Encode(map[string]any{ 831 + "topic_id": strings.TrimSpace(topicID), 832 + "workspace_dir": strings.TrimSpace(workspaceDir), 833 + }) 834 + } 835 + handleWorkspaceError := func(err error) { 836 + if err == nil { 837 + return 838 + } 839 + if msg, ok := badRequestMessage(err); ok { 840 + http.Error(w, msg, http.StatusBadRequest) 841 + return 842 + } 843 + http.Error(w, strings.TrimSpace(err.Error()), http.StatusServiceUnavailable) 844 + } 845 + 846 + switch r.Method { 847 + case http.MethodGet: 848 + if workspaceGet == nil { 849 + http.Error(w, "workspace is unavailable", http.StatusServiceUnavailable) 850 + return 851 + } 852 + topicID := strings.TrimSpace(r.URL.Query().Get("topic_id")) 853 + if topicID == "" { 854 + http.Error(w, "topic_id is required", http.StatusBadRequest) 855 + return 856 + } 857 + workspaceDir, err := workspaceGet(r.Context(), topicID) 858 + if err != nil { 859 + handleWorkspaceError(err) 860 + return 861 + } 862 + writeWorkspaceResponse(topicID, workspaceDir) 863 + return 864 + 865 + case http.MethodPut: 866 + if workspacePut == nil { 867 + http.Error(w, "workspace is unavailable", http.StatusServiceUnavailable) 868 + return 869 + } 870 + var req struct { 871 + TopicID string `json:"topic_id"` 872 + WorkspaceDir string `json:"workspace_dir"` 873 + } 874 + if err := json.NewDecoder(io.LimitReader(r.Body, 1<<20)).Decode(&req); err != nil { 875 + http.Error(w, "invalid json", http.StatusBadRequest) 876 + return 877 + } 878 + req.TopicID = strings.TrimSpace(req.TopicID) 879 + if req.TopicID == "" { 880 + http.Error(w, "topic_id is required", http.StatusBadRequest) 881 + return 882 + } 883 + if strings.TrimSpace(req.WorkspaceDir) == "" { 884 + http.Error(w, "workspace_dir is required", http.StatusBadRequest) 885 + return 886 + } 887 + workspaceDir, err := workspacePut(r.Context(), req.TopicID, req.WorkspaceDir) 888 + if err != nil { 889 + handleWorkspaceError(err) 890 + return 891 + } 892 + writeWorkspaceResponse(req.TopicID, workspaceDir) 893 + return 894 + 895 + case http.MethodDelete: 896 + if workspaceDelete == nil { 897 + http.Error(w, "workspace is unavailable", http.StatusServiceUnavailable) 898 + return 899 + } 900 + topicID := strings.TrimSpace(r.URL.Query().Get("topic_id")) 901 + if topicID == "" { 902 + http.Error(w, "topic_id is required", http.StatusBadRequest) 903 + return 904 + } 905 + if err := workspaceDelete(r.Context(), topicID); err != nil { 906 + handleWorkspaceError(err) 907 + return 908 + } 909 + writeWorkspaceResponse(topicID, "") 910 + return 911 + 912 + default: 913 + w.Header().Set("Allow", "GET, PUT, DELETE") 914 + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 915 + return 916 + } 917 + }) 918 + 919 + mux.HandleFunc("/workspace/tree", func(w http.ResponseWriter, r *http.Request) { 920 + if r.Method != http.MethodGet { 921 + w.Header().Set("Allow", "GET") 922 + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 923 + return 924 + } 925 + if !checkAuth(r, authToken) { 926 + http.Error(w, "unauthorized", http.StatusUnauthorized) 927 + return 928 + } 929 + if workspaceTree == nil { 930 + http.Error(w, "workspace tree is unavailable", http.StatusServiceUnavailable) 931 + return 932 + } 933 + topicID := strings.TrimSpace(r.URL.Query().Get("topic_id")) 934 + if topicID == "" { 935 + http.Error(w, "topic_id is required", http.StatusBadRequest) 936 + return 937 + } 938 + treePath := strings.TrimSpace(r.URL.Query().Get("path")) 939 + payload, err := workspaceTree(r.Context(), topicID, treePath) 940 + if err != nil { 941 + if msg, ok := badRequestMessage(err); ok { 942 + http.Error(w, msg, http.StatusBadRequest) 943 + return 944 + } 945 + http.Error(w, strings.TrimSpace(err.Error()), http.StatusServiceUnavailable) 946 + return 947 + } 948 + w.Header().Set("Content-Type", "application/json") 949 + _ = json.NewEncoder(w).Encode(payload) 950 + }) 951 + 952 + mux.HandleFunc("/workspace/open", func(w http.ResponseWriter, r *http.Request) { 953 + if r.Method != http.MethodPost { 954 + w.Header().Set("Allow", "POST") 955 + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 956 + return 957 + } 958 + if !checkAuth(r, authToken) { 959 + http.Error(w, "unauthorized", http.StatusUnauthorized) 960 + return 961 + } 962 + if workspaceOpen == nil { 963 + http.Error(w, "workspace open is unavailable", http.StatusServiceUnavailable) 964 + return 965 + } 966 + var req struct { 967 + TopicID string `json:"topic_id"` 968 + Path string `json:"path"` 969 + } 970 + if err := json.NewDecoder(io.LimitReader(r.Body, 1<<20)).Decode(&req); err != nil { 971 + http.Error(w, "invalid json", http.StatusBadRequest) 972 + return 973 + } 974 + req.TopicID = strings.TrimSpace(req.TopicID) 975 + req.Path = strings.TrimSpace(req.Path) 976 + if req.TopicID == "" { 977 + http.Error(w, "topic_id is required", http.StatusBadRequest) 978 + return 979 + } 980 + if req.Path == "" { 981 + http.Error(w, "path is required", http.StatusBadRequest) 982 + return 983 + } 984 + if err := workspaceOpen(r.Context(), req.TopicID, req.Path); err != nil { 985 + if msg, ok := badRequestMessage(err); ok { 986 + http.Error(w, msg, http.StatusBadRequest) 987 + return 988 + } 989 + http.Error(w, strings.TrimSpace(err.Error()), http.StatusServiceUnavailable) 990 + return 991 + } 992 + w.Header().Set("Content-Type", "application/json") 993 + _ = json.NewEncoder(w).Encode(map[string]any{"ok": true}) 994 + }) 995 + 996 + mux.HandleFunc("/workspace/browse", func(w http.ResponseWriter, r *http.Request) { 997 + if r.Method != http.MethodGet { 998 + w.Header().Set("Allow", "GET") 999 + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 1000 + return 1001 + } 1002 + if !checkAuth(r, authToken) { 1003 + http.Error(w, "unauthorized", http.StatusUnauthorized) 1004 + return 1005 + } 1006 + if workspaceBrowse == nil { 1007 + http.Error(w, "workspace browser is unavailable", http.StatusServiceUnavailable) 1008 + return 1009 + } 1010 + treePath := strings.TrimSpace(r.URL.Query().Get("path")) 1011 + payload, err := workspaceBrowse(r.Context(), treePath) 1012 + if err != nil { 1013 + if msg, ok := badRequestMessage(err); ok { 1014 + http.Error(w, msg, http.StatusBadRequest) 1015 + return 1016 + } 1017 + http.Error(w, strings.TrimSpace(err.Error()), http.StatusServiceUnavailable) 1018 + return 1019 + } 1020 + w.Header().Set("Content-Type", "application/json") 1021 + _ = json.NewEncoder(w).Encode(payload) 787 1022 }) 788 1023 789 1024 mux.HandleFunc("/topics", func(w http.ResponseWriter, r *http.Request) {
+274
internal/daemonruntime/server_workspace_test.go
··· 1 + package daemonruntime 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + "net/http" 7 + "net/http/httptest" 8 + "strings" 9 + "testing" 10 + ) 11 + 12 + func TestWorkspaceRouteGet(t *testing.T) { 13 + mux := http.NewServeMux() 14 + RegisterRoutes(mux, RoutesOptions{ 15 + Mode: "console", 16 + AuthToken: "token", 17 + WorkspaceGet: func(_ context.Context, topicID string) (string, error) { 18 + if topicID != "topic_a" { 19 + t.Fatalf("topicID = %q, want %q", topicID, "topic_a") 20 + } 21 + return "/repo/project", nil 22 + }, 23 + }) 24 + 25 + req := httptest.NewRequest(http.MethodGet, "/workspace?topic_id=topic_a", nil) 26 + req.Header.Set("Authorization", "Bearer token") 27 + rec := httptest.NewRecorder() 28 + mux.ServeHTTP(rec, req) 29 + 30 + if rec.Code != http.StatusOK { 31 + t.Fatalf("status = %d, want %d (%s)", rec.Code, http.StatusOK, rec.Body.String()) 32 + } 33 + 34 + var payload map[string]any 35 + if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil { 36 + t.Fatalf("json.Unmarshal() error = %v", err) 37 + } 38 + if payload["topic_id"] != "topic_a" { 39 + t.Fatalf("payload.topic_id = %#v, want %q", payload["topic_id"], "topic_a") 40 + } 41 + if payload["workspace_dir"] != "/repo/project" { 42 + t.Fatalf("payload.workspace_dir = %#v, want %q", payload["workspace_dir"], "/repo/project") 43 + } 44 + } 45 + 46 + func TestWorkspaceRoutePut(t *testing.T) { 47 + mux := http.NewServeMux() 48 + RegisterRoutes(mux, RoutesOptions{ 49 + Mode: "console", 50 + AuthToken: "token", 51 + WorkspacePut: func(_ context.Context, topicID string, workspaceDir string) (string, error) { 52 + if topicID != "topic_a" { 53 + t.Fatalf("topicID = %q, want %q", topicID, "topic_a") 54 + } 55 + if workspaceDir != "./repo" { 56 + t.Fatalf("workspaceDir = %q, want %q", workspaceDir, "./repo") 57 + } 58 + return "/repo/project", nil 59 + }, 60 + }) 61 + 62 + req := httptest.NewRequest(http.MethodPut, "/workspace", strings.NewReader(`{"topic_id":"topic_a","workspace_dir":"./repo"}`)) 63 + req.Header.Set("Authorization", "Bearer token") 64 + rec := httptest.NewRecorder() 65 + mux.ServeHTTP(rec, req) 66 + 67 + if rec.Code != http.StatusOK { 68 + t.Fatalf("status = %d, want %d (%s)", rec.Code, http.StatusOK, rec.Body.String()) 69 + } 70 + 71 + var payload map[string]any 72 + if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil { 73 + t.Fatalf("json.Unmarshal() error = %v", err) 74 + } 75 + if payload["workspace_dir"] != "/repo/project" { 76 + t.Fatalf("payload.workspace_dir = %#v, want %q", payload["workspace_dir"], "/repo/project") 77 + } 78 + } 79 + 80 + func TestWorkspaceRouteDelete(t *testing.T) { 81 + mux := http.NewServeMux() 82 + RegisterRoutes(mux, RoutesOptions{ 83 + Mode: "console", 84 + AuthToken: "token", 85 + WorkspaceDelete: func(_ context.Context, topicID string) error { 86 + if topicID != "topic_a" { 87 + t.Fatalf("topicID = %q, want %q", topicID, "topic_a") 88 + } 89 + return nil 90 + }, 91 + }) 92 + 93 + req := httptest.NewRequest(http.MethodDelete, "/workspace?topic_id=topic_a", nil) 94 + req.Header.Set("Authorization", "Bearer token") 95 + rec := httptest.NewRecorder() 96 + mux.ServeHTTP(rec, req) 97 + 98 + if rec.Code != http.StatusOK { 99 + t.Fatalf("status = %d, want %d (%s)", rec.Code, http.StatusOK, rec.Body.String()) 100 + } 101 + 102 + var payload map[string]any 103 + if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil { 104 + t.Fatalf("json.Unmarshal() error = %v", err) 105 + } 106 + if payload["workspace_dir"] != "" { 107 + t.Fatalf("payload.workspace_dir = %#v, want empty", payload["workspace_dir"]) 108 + } 109 + } 110 + 111 + func TestWorkspaceTreeRouteGet(t *testing.T) { 112 + mux := http.NewServeMux() 113 + RegisterRoutes(mux, RoutesOptions{ 114 + Mode: "console", 115 + AuthToken: "token", 116 + WorkspaceTree: func(_ context.Context, topicID string, treePath string) (WorkspaceTreeListing, error) { 117 + if topicID != "topic_a" { 118 + t.Fatalf("topicID = %q, want %q", topicID, "topic_a") 119 + } 120 + if treePath != "src" { 121 + t.Fatalf("treePath = %q, want %q", treePath, "src") 122 + } 123 + return WorkspaceTreeListing{ 124 + RootPath: "/repo/project", 125 + Path: "src", 126 + Items: []WorkspaceTreeEntry{ 127 + {Name: "main.go", Path: "src/main.go", IsDir: false, SizeBytes: 42}, 128 + }, 129 + }, nil 130 + }, 131 + }) 132 + 133 + req := httptest.NewRequest(http.MethodGet, "/workspace/tree?topic_id=topic_a&path=src", nil) 134 + req.Header.Set("Authorization", "Bearer token") 135 + rec := httptest.NewRecorder() 136 + mux.ServeHTTP(rec, req) 137 + 138 + if rec.Code != http.StatusOK { 139 + t.Fatalf("status = %d, want %d (%s)", rec.Code, http.StatusOK, rec.Body.String()) 140 + } 141 + 142 + var payload map[string]any 143 + if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil { 144 + t.Fatalf("json.Unmarshal() error = %v", err) 145 + } 146 + if payload["path"] != "src" { 147 + t.Fatalf("payload.path = %#v, want %q", payload["path"], "src") 148 + } 149 + items, ok := payload["items"].([]any) 150 + if !ok || len(items) != 1 { 151 + t.Fatalf("payload.items = %#v, want one item", payload["items"]) 152 + } 153 + first, ok := items[0].(map[string]any) 154 + if !ok { 155 + t.Fatalf("payload.items[0] = %#v, want object", items[0]) 156 + } 157 + if first["size_bytes"] != float64(42) { 158 + t.Fatalf("payload.items[0].size_bytes = %#v, want %v", first["size_bytes"], float64(42)) 159 + } 160 + } 161 + 162 + func TestWorkspaceBrowseRouteGet(t *testing.T) { 163 + mux := http.NewServeMux() 164 + RegisterRoutes(mux, RoutesOptions{ 165 + Mode: "console", 166 + AuthToken: "token", 167 + WorkspaceBrowse: func(_ context.Context, treePath string) (WorkspaceTreeListing, error) { 168 + if treePath != "" { 169 + t.Fatalf("treePath = %q, want empty", treePath) 170 + } 171 + return WorkspaceTreeListing{ 172 + Path: "", 173 + Items: []WorkspaceTreeEntry{ 174 + {Name: "tmp", Path: "/tmp", IsDir: true, HasChildren: true}, 175 + }, 176 + }, nil 177 + }, 178 + }) 179 + 180 + req := httptest.NewRequest(http.MethodGet, "/workspace/browse", nil) 181 + req.Header.Set("Authorization", "Bearer token") 182 + rec := httptest.NewRecorder() 183 + mux.ServeHTTP(rec, req) 184 + 185 + if rec.Code != http.StatusOK { 186 + t.Fatalf("status = %d, want %d (%s)", rec.Code, http.StatusOK, rec.Body.String()) 187 + } 188 + 189 + var payload map[string]any 190 + if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil { 191 + t.Fatalf("json.Unmarshal() error = %v", err) 192 + } 193 + if payload["path"] != "" { 194 + t.Fatalf("payload.path = %#v, want empty", payload["path"]) 195 + } 196 + items, ok := payload["items"].([]any) 197 + if !ok || len(items) != 1 { 198 + t.Fatalf("payload.items = %#v, want one item", payload["items"]) 199 + } 200 + } 201 + 202 + func TestWorkspaceOpenRoutePost(t *testing.T) { 203 + mux := http.NewServeMux() 204 + RegisterRoutes(mux, RoutesOptions{ 205 + Mode: "console", 206 + AuthToken: "token", 207 + WorkspaceOpen: func(_ context.Context, topicID string, targetPath string) error { 208 + if topicID != "topic_a" { 209 + t.Fatalf("topicID = %q, want %q", topicID, "topic_a") 210 + } 211 + if targetPath != "src/main.go" { 212 + t.Fatalf("targetPath = %q, want %q", targetPath, "src/main.go") 213 + } 214 + return nil 215 + }, 216 + }) 217 + 218 + req := httptest.NewRequest(http.MethodPost, "/workspace/open", strings.NewReader(`{"topic_id":"topic_a","path":"src/main.go"}`)) 219 + req.Header.Set("Authorization", "Bearer token") 220 + rec := httptest.NewRecorder() 221 + mux.ServeHTTP(rec, req) 222 + 223 + if rec.Code != http.StatusOK { 224 + t.Fatalf("status = %d, want %d (%s)", rec.Code, http.StatusOK, rec.Body.String()) 225 + } 226 + 227 + var payload map[string]any 228 + if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil { 229 + t.Fatalf("json.Unmarshal() error = %v", err) 230 + } 231 + if payload["ok"] != true { 232 + t.Fatalf("payload.ok = %#v, want true", payload["ok"]) 233 + } 234 + } 235 + 236 + func TestWorkspaceRouteUnavailable(t *testing.T) { 237 + mux := http.NewServeMux() 238 + RegisterRoutes(mux, RoutesOptions{ 239 + Mode: "console", 240 + AuthToken: "token", 241 + }) 242 + 243 + req := httptest.NewRequest(http.MethodGet, "/workspace?topic_id=topic_a", nil) 244 + req.Header.Set("Authorization", "Bearer token") 245 + rec := httptest.NewRecorder() 246 + mux.ServeHTTP(rec, req) 247 + 248 + if rec.Code != http.StatusServiceUnavailable { 249 + t.Fatalf("status = %d, want %d (%s)", rec.Code, http.StatusServiceUnavailable, rec.Body.String()) 250 + } 251 + } 252 + 253 + func TestWorkspaceRouteBadRequestErrors(t *testing.T) { 254 + mux := http.NewServeMux() 255 + RegisterRoutes(mux, RoutesOptions{ 256 + Mode: "console", 257 + AuthToken: "token", 258 + WorkspacePut: func(_ context.Context, topicID string, workspaceDir string) (string, error) { 259 + return "", BadRequest("workspace dir does not exist") 260 + }, 261 + }) 262 + 263 + req := httptest.NewRequest(http.MethodPut, "/workspace", strings.NewReader(`{"topic_id":"topic_a","workspace_dir":"./missing"}`)) 264 + req.Header.Set("Authorization", "Bearer token") 265 + rec := httptest.NewRecorder() 266 + mux.ServeHTTP(rec, req) 267 + 268 + if rec.Code != http.StatusBadRequest { 269 + t.Fatalf("status = %d, want %d (%s)", rec.Code, http.StatusBadRequest, rec.Body.String()) 270 + } 271 + if got := strings.TrimSpace(rec.Body.String()); got != "workspace dir does not exist" { 272 + t.Fatalf("body = %q, want %q", got, "workspace dir does not exist") 273 + } 274 + }
+99
internal/pathroots/pathroots.go
··· 1 + package pathroots 2 + 3 + import ( 4 + "context" 5 + "strings" 6 + 7 + "github.com/quailyquaily/mistermorph/internal/pathutil" 8 + ) 9 + 10 + type PathRoots struct { 11 + WorkspaceDir string 12 + FileCacheDir string 13 + FileStateDir string 14 + } 15 + 16 + func New(workspaceDir string, fileCacheDir string, fileStateDir string) PathRoots { 17 + return PathRoots{ 18 + WorkspaceDir: normalizeRoot(workspaceDir), 19 + FileCacheDir: normalizeRoot(fileCacheDir), 20 + FileStateDir: normalizeRoot(fileStateDir), 21 + } 22 + } 23 + 24 + func (r PathRoots) WithWorkspaceDir(dir string) PathRoots { 25 + r.WorkspaceDir = normalizeRoot(dir) 26 + return r 27 + } 28 + 29 + func (r PathRoots) BaseDir(alias string) string { 30 + switch strings.ToLower(strings.TrimSpace(alias)) { 31 + case "workspace_dir": 32 + return strings.TrimSpace(r.WorkspaceDir) 33 + case "file_cache_dir": 34 + return strings.TrimSpace(r.FileCacheDir) 35 + case "file_state_dir": 36 + return strings.TrimSpace(r.FileStateDir) 37 + default: 38 + return "" 39 + } 40 + } 41 + 42 + func (r PathRoots) DefaultFileDir() string { 43 + if dir := strings.TrimSpace(r.WorkspaceDir); dir != "" { 44 + return dir 45 + } 46 + return strings.TrimSpace(r.FileCacheDir) 47 + } 48 + 49 + func (r PathRoots) AllowedBaseDirs() []string { 50 + out := make([]string, 0, 3) 51 + for _, dir := range []string{r.WorkspaceDir, r.FileCacheDir, r.FileStateDir} { 52 + dir = strings.TrimSpace(dir) 53 + if dir == "" { 54 + continue 55 + } 56 + out = append(out, dir) 57 + } 58 + return out 59 + } 60 + 61 + type workspaceDirContextKey struct{} 62 + 63 + func WithWorkspaceDir(ctx context.Context, dir string) context.Context { 64 + if ctx == nil { 65 + ctx = context.Background() 66 + } 67 + return context.WithValue(ctx, workspaceDirContextKey{}, normalizeRoot(dir)) 68 + } 69 + 70 + func WorkspaceDirFromContext(ctx context.Context) (string, bool) { 71 + if ctx == nil { 72 + return "", false 73 + } 74 + dir, ok := ctx.Value(workspaceDirContextKey{}).(string) 75 + if !ok { 76 + return "", false 77 + } 78 + dir = strings.TrimSpace(dir) 79 + if dir == "" { 80 + return "", false 81 + } 82 + return dir, true 83 + } 84 + 85 + func Resolve(ctx context.Context, fallback PathRoots) PathRoots { 86 + fallback = New(fallback.WorkspaceDir, fallback.FileCacheDir, fallback.FileStateDir) 87 + if dir, ok := WorkspaceDirFromContext(ctx); ok { 88 + return fallback.WithWorkspaceDir(dir) 89 + } 90 + return fallback 91 + } 92 + 93 + func normalizeRoot(dir string) string { 94 + dir = strings.TrimSpace(dir) 95 + if dir == "" { 96 + return "" 97 + } 98 + return pathutil.ExpandHomePath(dir) 99 + }
+13
internal/pathutil/pathutil.go
··· 66 66 } 67 67 return filepath.Clean(filepath.Join(base, filename)) 68 68 } 69 + 70 + func IsWithinDir(baseAbs string, candAbs string) bool { 71 + baseAbs = filepath.Clean(baseAbs) 72 + candAbs = filepath.Clean(candAbs) 73 + rel, err := filepath.Rel(baseAbs, candAbs) 74 + if err != nil { 75 + return false 76 + } 77 + if rel == "." || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { 78 + return false 79 + } 80 + return true 81 + }
+4
internal/statepaths/statepaths.go
··· 87 87 return pathutil.ResolveStateFile(viper.GetString("file_state_dir"), TODODONEFilename) 88 88 } 89 89 90 + func WorkspaceAttachmentsPath() string { 91 + return pathutil.ResolveStateFile(viper.GetString("file_state_dir"), "workspace_attachments.json") 92 + } 93 + 90 94 func DefaultSkillsRoots() []string { 91 95 return dedupeNonEmptyStrings([]string{SkillsDir()}) 92 96 }
+7 -11
internal/toolsutil/static_register.go
··· 4 4 "strings" 5 5 "time" 6 6 7 + "github.com/quailyquaily/mistermorph/internal/pathroots" 7 8 "github.com/quailyquaily/mistermorph/tools" 8 9 "github.com/quailyquaily/mistermorph/tools/builtin" 9 10 ) ··· 49 50 50 51 type StaticCommonConfig struct { 51 52 UserAgent string 52 - FileCacheDir string 53 - FileStateDir string 53 + PathRoots pathroots.PathRoots 54 54 AuthenticatedHTTPConfigured bool 55 55 } 56 56 ··· 131 131 reg.Register(builtin.NewReadFileToolWithDenyPaths( 132 132 cfg.ReadFile.MaxBytes, 133 133 append([]string(nil), cfg.ReadFile.DenyPaths...), 134 - strings.TrimSpace(cfg.Common.FileCacheDir), 135 - strings.TrimSpace(cfg.Common.FileStateDir), 134 + cfg.Common.PathRoots, 136 135 )) 137 136 } 138 137 ··· 140 139 reg.Register(builtin.NewWriteFileTool( 141 140 true, 142 141 cfg.WriteFile.MaxBytes, 143 - strings.TrimSpace(cfg.Common.FileCacheDir), 144 - strings.TrimSpace(cfg.Common.FileStateDir), 142 + cfg.Common.PathRoots, 145 143 )) 146 144 } 147 145 ··· 150 148 true, 151 149 cfg.Bash.Timeout, 152 150 cfg.Bash.MaxOutputBytes, 153 - strings.TrimSpace(cfg.Common.FileCacheDir), 154 - strings.TrimSpace(cfg.Common.FileStateDir), 151 + cfg.Common.PathRoots, 155 152 ) 156 153 bt.DenyPaths = append([]string(nil), cfg.Bash.DenyPaths...) 157 154 bt.InjectedEnvVars = append([]string(nil), cfg.Bash.InjectedEnvVars...) ··· 167 164 true, 168 165 cfg.PowerShell.Timeout, 169 166 cfg.PowerShell.MaxOutputBytes, 170 - strings.TrimSpace(cfg.Common.FileCacheDir), 171 - strings.TrimSpace(cfg.Common.FileStateDir), 167 + cfg.Common.PathRoots, 172 168 ) 173 169 pt.DenyPaths = append([]string(nil), cfg.PowerShell.DenyPaths...) 174 170 pt.InjectedEnvVars = append([]string(nil), cfg.PowerShell.InjectedEnvVars...) ··· 185 181 cfg.URLFetch.MaxBytes, 186 182 cfg.URLFetch.MaxBytesDownload, 187 183 strings.TrimSpace(cfg.Common.UserAgent), 188 - strings.TrimSpace(cfg.Common.FileCacheDir), 184 + strings.TrimSpace(cfg.Common.PathRoots.FileCacheDir), 189 185 cfg.URLFetch.Auth, 190 186 )) 191 187 }
+375
internal/workspace/attachment.go
··· 1 + package workspace 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "os" 7 + "path/filepath" 8 + "strings" 9 + "sync" 10 + 11 + "github.com/quailyquaily/mistermorph/internal/fsstore" 12 + "github.com/quailyquaily/mistermorph/internal/pathutil" 13 + ) 14 + 15 + const attachmentStoreVersion = 1 16 + 17 + type Attachment struct { 18 + WorkspaceDir string `json:"workspace_dir"` 19 + } 20 + 21 + type Store struct { 22 + path string 23 + lockPath string 24 + mu sync.Mutex 25 + } 26 + 27 + type attachmentFile struct { 28 + Version int `json:"version"` 29 + Attachments map[string]Attachment `json:"attachments"` 30 + } 31 + 32 + func NewStore(path string) *Store { 33 + path = strings.TrimSpace(path) 34 + lockPath := "" 35 + if path != "" { 36 + lockPath = path + ".lck" 37 + } 38 + return &Store{ 39 + path: path, 40 + lockPath: lockPath, 41 + } 42 + } 43 + 44 + func (s *Store) Get(scopeKey string) (Attachment, bool, error) { 45 + scopeKey = normalizeScopeKey(scopeKey) 46 + if s == nil || scopeKey == "" { 47 + return Attachment{}, false, nil 48 + } 49 + s.mu.Lock() 50 + defer s.mu.Unlock() 51 + data, err := s.readLocked() 52 + if err != nil { 53 + return Attachment{}, false, err 54 + } 55 + att, ok := data.Attachments[scopeKey] 56 + if !ok { 57 + return Attachment{}, false, nil 58 + } 59 + if strings.TrimSpace(att.WorkspaceDir) == "" { 60 + return Attachment{}, false, nil 61 + } 62 + return att, true, nil 63 + } 64 + 65 + func (s *Store) Set(scopeKey string, attachment Attachment) (Attachment, bool, error) { 66 + scopeKey = normalizeScopeKey(scopeKey) 67 + attachment.WorkspaceDir = strings.TrimSpace(attachment.WorkspaceDir) 68 + if s == nil || scopeKey == "" { 69 + return Attachment{}, false, nil 70 + } 71 + if attachment.WorkspaceDir == "" { 72 + return Attachment{}, false, fmt.Errorf("workspace dir is required") 73 + } 74 + var prev Attachment 75 + var hadPrev bool 76 + err := s.withMutationLock(func() error { 77 + data, err := s.readLocked() 78 + if err != nil { 79 + return err 80 + } 81 + prev, hadPrev = data.Attachments[scopeKey] 82 + data.Attachments[scopeKey] = attachment 83 + return s.writeLocked(data) 84 + }) 85 + if err != nil { 86 + return Attachment{}, false, err 87 + } 88 + return prev, hadPrev, nil 89 + } 90 + 91 + func (s *Store) Delete(scopeKey string) (Attachment, bool, error) { 92 + scopeKey = normalizeScopeKey(scopeKey) 93 + if s == nil || scopeKey == "" { 94 + return Attachment{}, false, nil 95 + } 96 + var prev Attachment 97 + var hadPrev bool 98 + err := s.withMutationLock(func() error { 99 + data, err := s.readLocked() 100 + if err != nil { 101 + return err 102 + } 103 + prev, hadPrev = data.Attachments[scopeKey] 104 + if !hadPrev { 105 + return nil 106 + } 107 + delete(data.Attachments, scopeKey) 108 + return s.writeLocked(data) 109 + }) 110 + if err != nil { 111 + return Attachment{}, false, err 112 + } 113 + return prev, true, nil 114 + } 115 + 116 + func (s *Store) withMutationLock(fn func() error) error { 117 + if s == nil || fn == nil { 118 + return nil 119 + } 120 + s.mu.Lock() 121 + defer s.mu.Unlock() 122 + if strings.TrimSpace(s.lockPath) == "" { 123 + return fn() 124 + } 125 + return fsstore.WithLock(context.Background(), s.lockPath, fn) 126 + } 127 + 128 + func (s *Store) readLocked() (attachmentFile, error) { 129 + data := attachmentFile{ 130 + Version: attachmentStoreVersion, 131 + Attachments: map[string]Attachment{}, 132 + } 133 + if s == nil || strings.TrimSpace(s.path) == "" { 134 + return data, nil 135 + } 136 + var persisted attachmentFile 137 + found, err := fsstore.ReadJSON(strings.TrimSpace(s.path), &persisted) 138 + if err != nil { 139 + return data, err 140 + } 141 + if !found { 142 + return data, nil 143 + } 144 + if persisted.Version <= 0 { 145 + persisted.Version = attachmentStoreVersion 146 + } 147 + if persisted.Attachments == nil { 148 + persisted.Attachments = map[string]Attachment{} 149 + } 150 + for scopeKey, att := range persisted.Attachments { 151 + scopeKey = normalizeScopeKey(scopeKey) 152 + att.WorkspaceDir = strings.TrimSpace(att.WorkspaceDir) 153 + if scopeKey == "" || att.WorkspaceDir == "" { 154 + continue 155 + } 156 + data.Attachments[scopeKey] = att 157 + } 158 + return data, nil 159 + } 160 + 161 + func (s *Store) writeLocked(data attachmentFile) error { 162 + if s == nil || strings.TrimSpace(s.path) == "" { 163 + return nil 164 + } 165 + if data.Attachments == nil { 166 + data.Attachments = map[string]Attachment{} 167 + } 168 + data.Version = attachmentStoreVersion 169 + return fsstore.WriteJSONAtomic(strings.TrimSpace(s.path), data, fsstore.FileOptions{}) 170 + } 171 + 172 + type CommandAction string 173 + 174 + const ( 175 + CommandStatus CommandAction = "status" 176 + CommandAttach CommandAction = "attach" 177 + CommandDetach CommandAction = "detach" 178 + ) 179 + 180 + type Command struct { 181 + Action CommandAction 182 + Dir string 183 + } 184 + 185 + type CommandResult struct { 186 + Reply string 187 + WorkspaceDir string 188 + } 189 + 190 + func ParseCommandArgs(args string) (Command, error) { 191 + args = strings.TrimSpace(args) 192 + if args == "" { 193 + return Command{Action: CommandStatus}, nil 194 + } 195 + parts := strings.Fields(args) 196 + if len(parts) == 0 { 197 + return Command{Action: CommandStatus}, nil 198 + } 199 + switch strings.ToLower(strings.TrimSpace(parts[0])) { 200 + case "attach": 201 + dir := strings.TrimSpace(args[len(parts[0]):]) 202 + if dir == "" { 203 + return Command{}, fmt.Errorf("usage: /workspace | /workspace attach <dir> | /workspace detach") 204 + } 205 + return Command{Action: CommandAttach, Dir: dir}, nil 206 + case "detach": 207 + if len(parts) != 1 { 208 + return Command{}, fmt.Errorf("usage: /workspace | /workspace attach <dir> | /workspace detach") 209 + } 210 + return Command{Action: CommandDetach}, nil 211 + default: 212 + return Command{}, fmt.Errorf("usage: /workspace | /workspace attach <dir> | /workspace detach") 213 + } 214 + } 215 + 216 + func LookupWorkspaceDir(store *Store, scopeKey string) (string, error) { 217 + if store == nil { 218 + return "", nil 219 + } 220 + att, ok, err := store.Get(scopeKey) 221 + if err != nil { 222 + return "", err 223 + } 224 + if !ok { 225 + return "", nil 226 + } 227 + return strings.TrimSpace(att.WorkspaceDir), nil 228 + } 229 + 230 + func ExecuteStoreCommand(store *Store, scopeKey string, args string, allowRoots []string) (CommandResult, error) { 231 + if store == nil { 232 + return CommandResult{}, fmt.Errorf("workspace store is not configured") 233 + } 234 + cmd, err := ParseCommandArgs(args) 235 + if err != nil { 236 + return CommandResult{}, err 237 + } 238 + currentDir, err := LookupWorkspaceDir(store, scopeKey) 239 + if err != nil { 240 + return CommandResult{}, err 241 + } 242 + switch cmd.Action { 243 + case CommandStatus: 244 + return CommandResult{ 245 + Reply: StatusText(currentDir), 246 + WorkspaceDir: currentDir, 247 + }, nil 248 + case CommandAttach: 249 + dir, err := ValidateDir(cmd.Dir, allowRoots) 250 + if err != nil { 251 + return CommandResult{}, err 252 + } 253 + prev, hadPrev, err := store.Set(scopeKey, Attachment{WorkspaceDir: dir}) 254 + if err != nil { 255 + return CommandResult{}, err 256 + } 257 + return CommandResult{ 258 + Reply: AttachText(prev.WorkspaceDir, dir, hadPrev), 259 + WorkspaceDir: dir, 260 + }, nil 261 + case CommandDetach: 262 + prev, hadPrev, err := store.Delete(scopeKey) 263 + if err != nil { 264 + return CommandResult{}, err 265 + } 266 + return CommandResult{ 267 + Reply: DetachText(prev.WorkspaceDir, hadPrev), 268 + WorkspaceDir: "", 269 + }, nil 270 + default: 271 + return CommandResult{}, fmt.Errorf("unsupported workspace command") 272 + } 273 + } 274 + 275 + func ResolveInitialWorkspace(cwd string, raw string, disabled bool, allowRoots []string) (string, error) { 276 + if disabled { 277 + return "", nil 278 + } 279 + target := strings.TrimSpace(raw) 280 + if target == "" { 281 + target = strings.TrimSpace(cwd) 282 + } 283 + if target == "" { 284 + return "", nil 285 + } 286 + return ValidateDir(target, allowRoots) 287 + } 288 + 289 + func ValidateDir(raw string, allowRoots []string) (string, error) { 290 + raw = strings.TrimSpace(raw) 291 + if raw == "" { 292 + return "", fmt.Errorf("workspace dir is required") 293 + } 294 + resolved, err := filepath.Abs(pathutil.ExpandHomePath(raw)) 295 + if err != nil { 296 + return "", err 297 + } 298 + info, err := os.Stat(resolved) 299 + if err != nil { 300 + if os.IsNotExist(err) { 301 + return "", fmt.Errorf("workspace dir does not exist: %s", resolved) 302 + } 303 + return "", err 304 + } 305 + if !info.IsDir() { 306 + return "", fmt.Errorf("workspace dir is not a directory: %s", resolved) 307 + } 308 + if _, err := os.ReadDir(resolved); err != nil { 309 + return "", fmt.Errorf("workspace dir is not readable: %s", resolved) 310 + } 311 + allowed := normalizedAllowRoots(allowRoots) 312 + if len(allowed) > 0 { 313 + for _, root := range allowed { 314 + if pathutil.IsWithinDir(root, resolved) || filepath.Clean(root) == filepath.Clean(resolved) { 315 + return resolved, nil 316 + } 317 + } 318 + return "", fmt.Errorf("workspace dir is outside allowed roots: %s", resolved) 319 + } 320 + return resolved, nil 321 + } 322 + 323 + func StatusText(current string) string { 324 + current = strings.TrimSpace(current) 325 + if current == "" { 326 + return "workspace: (none)" 327 + } 328 + return "workspace: " + current 329 + } 330 + 331 + func AttachText(oldDir string, newDir string, replaced bool) string { 332 + oldDir = strings.TrimSpace(oldDir) 333 + newDir = strings.TrimSpace(newDir) 334 + if replaced && oldDir != "" && oldDir != newDir { 335 + return fmt.Sprintf("workspace replaced: %s -> %s", oldDir, newDir) 336 + } 337 + if oldDir == newDir && newDir != "" { 338 + return "workspace unchanged: " + newDir 339 + } 340 + return "workspace attached: " + newDir 341 + } 342 + 343 + func DetachText(oldDir string, detached bool) string { 344 + oldDir = strings.TrimSpace(oldDir) 345 + if !detached || oldDir == "" { 346 + return "workspace: already detached" 347 + } 348 + return "workspace detached: " + oldDir 349 + } 350 + 351 + func normalizeScopeKey(scopeKey string) string { 352 + return strings.TrimSpace(scopeKey) 353 + } 354 + 355 + func normalizedAllowRoots(roots []string) []string { 356 + out := make([]string, 0, len(roots)) 357 + seen := map[string]bool{} 358 + for _, root := range roots { 359 + root = strings.TrimSpace(root) 360 + if root == "" { 361 + continue 362 + } 363 + absRoot, err := filepath.Abs(pathutil.ExpandHomePath(root)) 364 + if err != nil { 365 + continue 366 + } 367 + absRoot = filepath.Clean(absRoot) 368 + if seen[absRoot] { 369 + continue 370 + } 371 + seen[absRoot] = true 372 + out = append(out, absRoot) 373 + } 374 + return out 375 + }
+182
internal/workspace/attachment_test.go
··· 1 + package workspace 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "path/filepath" 7 + "strings" 8 + "testing" 9 + "time" 10 + 11 + "github.com/quailyquaily/mistermorph/internal/fsstore" 12 + ) 13 + 14 + func TestParseCommandArgs(t *testing.T) { 15 + cases := []struct { 16 + name string 17 + args string 18 + want Command 19 + wantErr string 20 + }{ 21 + { 22 + name: "status", 23 + args: "", 24 + want: Command{Action: CommandStatus}, 25 + }, 26 + { 27 + name: "attach", 28 + args: "attach ./repo", 29 + want: Command{Action: CommandAttach, Dir: "./repo"}, 30 + }, 31 + { 32 + name: "detach", 33 + args: "detach", 34 + want: Command{Action: CommandDetach}, 35 + }, 36 + { 37 + name: "detach extra args", 38 + args: "detach now", 39 + wantErr: "usage: /workspace | /workspace attach <dir> | /workspace detach", 40 + }, 41 + { 42 + name: "unknown", 43 + args: "list", 44 + wantErr: "usage: /workspace | /workspace attach <dir> | /workspace detach", 45 + }, 46 + } 47 + 48 + for _, tc := range cases { 49 + t.Run(tc.name, func(t *testing.T) { 50 + got, err := ParseCommandArgs(tc.args) 51 + if tc.wantErr != "" { 52 + if err == nil { 53 + t.Fatalf("expected error, got nil") 54 + } 55 + if err.Error() != tc.wantErr { 56 + t.Fatalf("error = %q, want %q", err.Error(), tc.wantErr) 57 + } 58 + return 59 + } 60 + if err != nil { 61 + t.Fatalf("unexpected error: %v", err) 62 + } 63 + if got != tc.want { 64 + t.Fatalf("got %#v, want %#v", got, tc.want) 65 + } 66 + }) 67 + } 68 + } 69 + 70 + func TestExecuteStoreCommandLifecycle(t *testing.T) { 71 + store := NewStore(filepath.Join(t.TempDir(), "workspace_attachments.json")) 72 + scopeKey := "line:Cgroup123" 73 + dirA := t.TempDir() 74 + dirB := t.TempDir() 75 + 76 + result, err := ExecuteStoreCommand(store, scopeKey, "", nil) 77 + if err != nil { 78 + t.Fatalf("status error: %v", err) 79 + } 80 + if result.Reply != "workspace: (none)" { 81 + t.Fatalf("status reply = %q", result.Reply) 82 + } 83 + 84 + result, err = ExecuteStoreCommand(store, scopeKey, "attach "+dirA, nil) 85 + if err != nil { 86 + t.Fatalf("attach error: %v", err) 87 + } 88 + if result.Reply != "workspace attached: "+dirA { 89 + t.Fatalf("attach reply = %q", result.Reply) 90 + } 91 + if result.WorkspaceDir != dirA { 92 + t.Fatalf("workspace dir = %q, want %q", result.WorkspaceDir, dirA) 93 + } 94 + 95 + result, err = ExecuteStoreCommand(store, scopeKey, "attach "+dirB, nil) 96 + if err != nil { 97 + t.Fatalf("replace error: %v", err) 98 + } 99 + if result.Reply != "workspace replaced: "+dirA+" -> "+dirB { 100 + t.Fatalf("replace reply = %q", result.Reply) 101 + } 102 + 103 + currentDir, err := LookupWorkspaceDir(store, scopeKey) 104 + if err != nil { 105 + t.Fatalf("lookup error: %v", err) 106 + } 107 + if currentDir != dirB { 108 + t.Fatalf("lookup = %q, want %q", currentDir, dirB) 109 + } 110 + 111 + result, err = ExecuteStoreCommand(store, scopeKey, "detach", nil) 112 + if err != nil { 113 + t.Fatalf("detach error: %v", err) 114 + } 115 + if result.Reply != "workspace detached: "+dirB { 116 + t.Fatalf("detach reply = %q", result.Reply) 117 + } 118 + 119 + currentDir, err = LookupWorkspaceDir(store, scopeKey) 120 + if err != nil { 121 + t.Fatalf("lookup after detach error: %v", err) 122 + } 123 + if currentDir != "" { 124 + t.Fatalf("lookup after detach = %q, want empty", currentDir) 125 + } 126 + } 127 + 128 + func TestValidateDir_RejectsOutsideAllowedRoots(t *testing.T) { 129 + allowedRoot := t.TempDir() 130 + outsideRoot := t.TempDir() 131 + 132 + _, err := ValidateDir(outsideRoot, []string{allowedRoot}) 133 + if err == nil { 134 + t.Fatalf("expected error, got nil") 135 + } 136 + if !strings.Contains(err.Error(), "outside allowed roots") { 137 + t.Fatalf("unexpected error: %v", err) 138 + } 139 + } 140 + 141 + func TestStoreSetWaitsForCrossProcessLock(t *testing.T) { 142 + store := NewStore(filepath.Join(t.TempDir(), "workspace_attachments.json")) 143 + scopeKey := "line:Cgroup123" 144 + dir := t.TempDir() 145 + done := make(chan error, 1) 146 + 147 + err := fsstore.WithLock(context.Background(), store.lockPath, func() error { 148 + go func() { 149 + _, _, err := store.Set(scopeKey, Attachment{WorkspaceDir: dir}) 150 + done <- err 151 + }() 152 + select { 153 + case err := <-done: 154 + return fmt.Errorf("set finished while lock was held: %v", err) 155 + case <-time.After(120 * time.Millisecond): 156 + return nil 157 + } 158 + }) 159 + if err != nil { 160 + t.Fatalf("holding external lock: %v", err) 161 + } 162 + 163 + select { 164 + case err := <-done: 165 + if err != nil { 166 + t.Fatalf("store.Set() error = %v", err) 167 + } 168 + case <-time.After(1 * time.Second): 169 + t.Fatalf("store.Set() did not finish after lock release") 170 + } 171 + 172 + got, ok, err := store.Get(scopeKey) 173 + if err != nil { 174 + t.Fatalf("store.Get() error = %v", err) 175 + } 176 + if !ok { 177 + t.Fatalf("store.Get() found = false, want true") 178 + } 179 + if got.WorkspaceDir != dir { 180 + t.Fatalf("workspace dir = %q, want %q", got.WorkspaceDir, dir) 181 + } 182 + }
+93
internal/workspace/open.go
··· 1 + package workspace 2 + 3 + import ( 4 + "fmt" 5 + "os" 6 + "os/exec" 7 + "path/filepath" 8 + "runtime" 9 + "strings" 10 + 11 + "github.com/quailyquaily/mistermorph/internal/pathutil" 12 + ) 13 + 14 + var openPathRunner = openPathWithSystemDefault 15 + 16 + func ResolveAttachedItemPath(workspaceDir string, relPath string) (string, error) { 17 + rootDir, err := ValidateDir(workspaceDir, nil) 18 + if err != nil { 19 + return "", err 20 + } 21 + return resolveAttachedItemPath(rootDir, relPath) 22 + } 23 + 24 + func OpenPath(rawPath string) error { 25 + target := strings.TrimSpace(rawPath) 26 + if target == "" { 27 + return fmt.Errorf("path is required") 28 + } 29 + absPath, err := filepath.Abs(pathutil.ExpandHomePath(target)) 30 + if err != nil { 31 + return err 32 + } 33 + if _, err := os.Stat(absPath); err != nil { 34 + if os.IsNotExist(err) { 35 + return fmt.Errorf("path does not exist: %s", absPath) 36 + } 37 + return err 38 + } 39 + return openPathRunner(absPath) 40 + } 41 + 42 + func resolveAttachedItemPath(rootDir string, relPath string) (string, error) { 43 + relPath = strings.TrimSpace(relPath) 44 + if relPath == "" || relPath == "." { 45 + return "", fmt.Errorf("workspace item path is required") 46 + } 47 + if filepath.IsAbs(relPath) { 48 + return "", fmt.Errorf("workspace item path must be relative") 49 + } 50 + cleanPath := filepath.Clean(relPath) 51 + if cleanPath == "." || cleanPath == ".." || strings.HasPrefix(cleanPath, ".."+string(filepath.Separator)) { 52 + return "", fmt.Errorf("workspace item path is outside the attached directory") 53 + } 54 + targetPath, err := filepath.Abs(filepath.Join(rootDir, cleanPath)) 55 + if err != nil { 56 + return "", err 57 + } 58 + if filepath.Clean(targetPath) != filepath.Clean(rootDir) && !pathutil.IsWithinDir(rootDir, targetPath) { 59 + return "", fmt.Errorf("workspace item path is outside the attached directory") 60 + } 61 + if _, err := os.Stat(targetPath); err != nil { 62 + if os.IsNotExist(err) { 63 + return "", fmt.Errorf("workspace item does not exist: %s", cleanPath) 64 + } 65 + return "", err 66 + } 67 + return targetPath, nil 68 + } 69 + 70 + func openPathWithSystemDefault(absPath string) error { 71 + cmd, err := openPathCommand(absPath) 72 + if err != nil { 73 + return err 74 + } 75 + if err := cmd.Start(); err != nil { 76 + return err 77 + } 78 + go func() { 79 + _ = cmd.Wait() 80 + }() 81 + return nil 82 + } 83 + 84 + func openPathCommand(absPath string) (*exec.Cmd, error) { 85 + switch runtime.GOOS { 86 + case "darwin": 87 + return exec.Command("open", absPath), nil 88 + case "windows": 89 + return exec.Command("cmd", "/c", "start", "", absPath), nil 90 + default: 91 + return exec.Command("xdg-open", absPath), nil 92 + } 93 + }
+55
internal/workspace/open_test.go
··· 1 + package workspace 2 + 3 + import ( 4 + "os" 5 + "path/filepath" 6 + "testing" 7 + ) 8 + 9 + func TestResolveAttachedItemPath(t *testing.T) { 10 + root := t.TempDir() 11 + targetPath := filepath.Join(root, "notes.md") 12 + if err := os.WriteFile(targetPath, []byte("ok"), 0o644); err != nil { 13 + t.Fatalf("os.WriteFile(notes.md) error = %v", err) 14 + } 15 + 16 + resolved, err := ResolveAttachedItemPath(root, "notes.md") 17 + if err != nil { 18 + t.Fatalf("ResolveAttachedItemPath() error = %v", err) 19 + } 20 + if resolved != filepath.Clean(targetPath) { 21 + t.Fatalf("resolved = %q, want %q", resolved, filepath.Clean(targetPath)) 22 + } 23 + } 24 + 25 + func TestResolveAttachedItemPathRejectsEscape(t *testing.T) { 26 + root := t.TempDir() 27 + if _, err := ResolveAttachedItemPath(root, "../outside"); err == nil { 28 + t.Fatal("ResolveAttachedItemPath() error = nil, want escape rejection") 29 + } 30 + } 31 + 32 + func TestOpenPathUsesSystemRunner(t *testing.T) { 33 + targetPath := filepath.Join(t.TempDir(), "report.txt") 34 + if err := os.WriteFile(targetPath, []byte("ok"), 0o644); err != nil { 35 + t.Fatalf("os.WriteFile(report.txt) error = %v", err) 36 + } 37 + 38 + previous := openPathRunner 39 + defer func() { 40 + openPathRunner = previous 41 + }() 42 + 43 + var opened string 44 + openPathRunner = func(path string) error { 45 + opened = path 46 + return nil 47 + } 48 + 49 + if err := OpenPath(targetPath); err != nil { 50 + t.Fatalf("OpenPath() error = %v", err) 51 + } 52 + if opened != filepath.Clean(targetPath) { 53 + t.Fatalf("opened = %q, want %q", opened, filepath.Clean(targetPath)) 54 + } 55 + }
+24
internal/workspace/prompt.go
··· 1 + package workspace 2 + 3 + import ( 4 + "fmt" 5 + "strings" 6 + 7 + "github.com/quailyquaily/mistermorph/agent" 8 + ) 9 + 10 + func PromptBlock(workspaceDir string) agent.PromptBlock { 11 + workspaceDir = strings.TrimSpace(workspaceDir) 12 + if workspaceDir == "" { 13 + return agent.PromptBlock{} 14 + } 15 + return agent.PromptBlock{ 16 + Content: fmt.Sprintf("## Workspace Context\n\n"+ 17 + "A local workspace is attached for this run.\n\n"+ 18 + "workspace_dir: %s\n\n"+ 19 + "Use this as the default working directory for project files.\n"+ 20 + "Relative paths for read_file, write_file, bash, and powershell resolve under workspace_dir unless the user explicitly uses file_cache_dir/ or file_state_dir/.\n"+ 21 + "Use file_cache_dir for downloads and temporary artifacts.\n"+ 22 + "Use file_state_dir for memory, TODO, skills, contacts, and guard state.\n", workspaceDir), 23 + } 24 + }
+210
internal/workspace/tree.go
··· 1 + package workspace 2 + 3 + import ( 4 + "fmt" 5 + "os" 6 + "path/filepath" 7 + "runtime" 8 + "sort" 9 + "strings" 10 + 11 + "github.com/quailyquaily/mistermorph/internal/pathutil" 12 + ) 13 + 14 + type TreeEntry struct { 15 + Name string `json:"name"` 16 + Path string `json:"path"` 17 + IsDir bool `json:"is_dir"` 18 + HasChildren bool `json:"has_children"` 19 + SizeBytes int64 `json:"size_bytes"` 20 + } 21 + 22 + type TreeListing struct { 23 + RootPath string `json:"root_path,omitempty"` 24 + Path string `json:"path"` 25 + Items []TreeEntry `json:"items"` 26 + } 27 + 28 + func ListAttachedTree(workspaceDir string, relPath string) (TreeListing, error) { 29 + rootDir, err := ValidateDir(workspaceDir, nil) 30 + if err != nil { 31 + return TreeListing{}, err 32 + } 33 + cleanPath, targetDir, err := resolveAttachedTreePath(rootDir, relPath) 34 + if err != nil { 35 + return TreeListing{}, err 36 + } 37 + items, err := listTreeEntries(targetDir, func(absPath string) string { 38 + rel, relErr := filepath.Rel(rootDir, absPath) 39 + if relErr != nil { 40 + return "" 41 + } 42 + if rel == "." { 43 + return "" 44 + } 45 + return filepath.Clean(rel) 46 + }) 47 + if err != nil { 48 + return TreeListing{}, err 49 + } 50 + return TreeListing{ 51 + RootPath: rootDir, 52 + Path: cleanPath, 53 + Items: items, 54 + }, nil 55 + } 56 + 57 + func ListSystemTree(rawPath string) (TreeListing, error) { 58 + requestPath := strings.TrimSpace(rawPath) 59 + if requestPath == "" { 60 + items, err := listSystemRootEntries() 61 + if err != nil { 62 + return TreeListing{}, err 63 + } 64 + return TreeListing{ 65 + RootPath: systemRootLabel(), 66 + Path: "", 67 + Items: items, 68 + }, nil 69 + } 70 + targetDir, err := filepath.Abs(pathutil.ExpandHomePath(requestPath)) 71 + if err != nil { 72 + return TreeListing{}, err 73 + } 74 + items, err := listTreeEntries(targetDir, func(absPath string) string { 75 + return filepath.Clean(absPath) 76 + }) 77 + if err != nil { 78 + return TreeListing{}, err 79 + } 80 + return TreeListing{ 81 + RootPath: targetDir, 82 + Path: targetDir, 83 + Items: items, 84 + }, nil 85 + } 86 + 87 + func resolveAttachedTreePath(rootDir string, relPath string) (string, string, error) { 88 + relPath = strings.TrimSpace(relPath) 89 + if relPath == "" || relPath == "." { 90 + return "", rootDir, nil 91 + } 92 + if filepath.IsAbs(relPath) { 93 + return "", "", fmt.Errorf("workspace tree path must be relative") 94 + } 95 + cleanPath := filepath.Clean(relPath) 96 + if cleanPath == "." { 97 + return "", rootDir, nil 98 + } 99 + if cleanPath == ".." || strings.HasPrefix(cleanPath, ".."+string(filepath.Separator)) { 100 + return "", "", fmt.Errorf("workspace tree path is outside the attached directory") 101 + } 102 + targetDir, err := filepath.Abs(filepath.Join(rootDir, cleanPath)) 103 + if err != nil { 104 + return "", "", err 105 + } 106 + if filepath.Clean(targetDir) != filepath.Clean(rootDir) && !pathutil.IsWithinDir(rootDir, targetDir) { 107 + return "", "", fmt.Errorf("workspace tree path is outside the attached directory") 108 + } 109 + return cleanPath, targetDir, nil 110 + } 111 + 112 + func listSystemRootEntries() ([]TreeEntry, error) { 113 + if runtime.GOOS != "windows" { 114 + root := string(filepath.Separator) 115 + return listTreeEntries(root, func(absPath string) string { 116 + return filepath.Clean(absPath) 117 + }) 118 + } 119 + items := make([]TreeEntry, 0, 8) 120 + for drive := 'A'; drive <= 'Z'; drive += 1 { 121 + root := fmt.Sprintf("%c:\\", drive) 122 + info, err := os.Stat(root) 123 + if err != nil || !info.IsDir() { 124 + continue 125 + } 126 + items = append(items, TreeEntry{ 127 + Name: root, 128 + Path: root, 129 + IsDir: true, 130 + HasChildren: dirHasChildren(root), 131 + SizeBytes: info.Size(), 132 + }) 133 + } 134 + sortTreeEntries(items) 135 + return items, nil 136 + } 137 + 138 + func systemRootLabel() string { 139 + if runtime.GOOS == "windows" { 140 + return "" 141 + } 142 + return string(filepath.Separator) 143 + } 144 + 145 + func listTreeEntries(dir string, pathBuilder func(absPath string) string) ([]TreeEntry, error) { 146 + dir = strings.TrimSpace(dir) 147 + if dir == "" { 148 + return nil, fmt.Errorf("directory path is required") 149 + } 150 + info, err := os.Stat(dir) 151 + if err != nil { 152 + if os.IsNotExist(err) { 153 + return nil, fmt.Errorf("directory does not exist: %s", dir) 154 + } 155 + return nil, err 156 + } 157 + if !info.IsDir() { 158 + return nil, fmt.Errorf("path is not a directory: %s", dir) 159 + } 160 + entries, err := os.ReadDir(dir) 161 + if err != nil { 162 + return nil, fmt.Errorf("directory is not readable: %s", dir) 163 + } 164 + items := make([]TreeEntry, 0, len(entries)) 165 + for _, entry := range entries { 166 + name := strings.TrimSpace(entry.Name()) 167 + if name == "" { 168 + continue 169 + } 170 + absPath := filepath.Join(dir, name) 171 + sizeBytes := int64(-1) 172 + info, infoErr := entry.Info() 173 + if infoErr == nil { 174 + sizeBytes = info.Size() 175 + } 176 + items = append(items, TreeEntry{ 177 + Name: name, 178 + Path: pathBuilder(absPath), 179 + IsDir: entry.IsDir(), 180 + HasChildren: entry.IsDir() && dirHasChildren(absPath), 181 + SizeBytes: sizeBytes, 182 + }) 183 + } 184 + sortTreeEntries(items) 185 + return items, nil 186 + } 187 + 188 + func dirHasChildren(dir string) bool { 189 + file, err := os.Open(dir) 190 + if err != nil { 191 + return false 192 + } 193 + defer file.Close() 194 + names, err := file.Readdirnames(1) 195 + return err == nil && len(names) > 0 196 + } 197 + 198 + func sortTreeEntries(items []TreeEntry) { 199 + sort.Slice(items, func(i, j int) bool { 200 + if items[i].IsDir != items[j].IsDir { 201 + return items[i].IsDir 202 + } 203 + leftName := strings.ToLower(items[i].Name) 204 + rightName := strings.ToLower(items[j].Name) 205 + if leftName == rightName { 206 + return items[i].Name < items[j].Name 207 + } 208 + return leftName < rightName 209 + }) 210 + }
+97
internal/workspace/tree_test.go
··· 1 + package workspace 2 + 3 + import ( 4 + "os" 5 + "path/filepath" 6 + "testing" 7 + ) 8 + 9 + func TestListAttachedTree(t *testing.T) { 10 + root := t.TempDir() 11 + alphaDir := filepath.Join(root, "alpha") 12 + betaDir := filepath.Join(root, "beta") 13 + gammaFile := filepath.Join(root, "gamma.txt") 14 + if err := os.Mkdir(alphaDir, 0o755); err != nil { 15 + t.Fatalf("os.Mkdir(alpha) error = %v", err) 16 + } 17 + if err := os.Mkdir(betaDir, 0o755); err != nil { 18 + t.Fatalf("os.Mkdir(beta) error = %v", err) 19 + } 20 + if err := os.WriteFile(filepath.Join(alphaDir, "nested.txt"), []byte("ok"), 0o644); err != nil { 21 + t.Fatalf("os.WriteFile(alpha/nested.txt) error = %v", err) 22 + } 23 + if err := os.WriteFile(gammaFile, []byte("ok"), 0o644); err != nil { 24 + t.Fatalf("os.WriteFile(gamma.txt) error = %v", err) 25 + } 26 + 27 + listing, err := ListAttachedTree(root, "") 28 + if err != nil { 29 + t.Fatalf("ListAttachedTree() error = %v", err) 30 + } 31 + if listing.RootPath != filepath.Clean(root) { 32 + t.Fatalf("listing.RootPath = %q, want %q", listing.RootPath, filepath.Clean(root)) 33 + } 34 + if listing.Path != "" { 35 + t.Fatalf("listing.Path = %q, want empty", listing.Path) 36 + } 37 + if len(listing.Items) != 3 { 38 + t.Fatalf("len(listing.Items) = %d, want 3", len(listing.Items)) 39 + } 40 + if listing.Items[0].Name != "alpha" || !listing.Items[0].IsDir || listing.Items[0].Path != "alpha" || !listing.Items[0].HasChildren { 41 + t.Fatalf("listing.Items[0] = %#v, want alpha dir with children", listing.Items[0]) 42 + } 43 + if listing.Items[1].Name != "beta" || !listing.Items[1].IsDir || listing.Items[1].Path != "beta" || listing.Items[1].HasChildren { 44 + t.Fatalf("listing.Items[1] = %#v, want beta empty dir", listing.Items[1]) 45 + } 46 + if listing.Items[1].SizeBytes < 0 { 47 + t.Fatalf("listing.Items[1].SizeBytes = %d, want non-negative", listing.Items[1].SizeBytes) 48 + } 49 + if listing.Items[2].Name != "gamma.txt" || listing.Items[2].IsDir || listing.Items[2].Path != "gamma.txt" { 50 + t.Fatalf("listing.Items[2] = %#v, want gamma.txt file", listing.Items[2]) 51 + } 52 + if listing.Items[2].SizeBytes != 2 { 53 + t.Fatalf("listing.Items[2].SizeBytes = %d, want %d", listing.Items[2].SizeBytes, 2) 54 + } 55 + } 56 + 57 + func TestListAttachedTreeRejectsEscape(t *testing.T) { 58 + root := t.TempDir() 59 + if _, err := ListAttachedTree(root, "../outside"); err == nil { 60 + t.Fatal("ListAttachedTree() error = nil, want escape rejection") 61 + } 62 + } 63 + 64 + func TestListSystemTree(t *testing.T) { 65 + root := t.TempDir() 66 + dirPath := filepath.Join(root, "docs") 67 + filePath := filepath.Join(root, "README.md") 68 + if err := os.Mkdir(dirPath, 0o755); err != nil { 69 + t.Fatalf("os.Mkdir(docs) error = %v", err) 70 + } 71 + if err := os.WriteFile(filePath, []byte("ok"), 0o644); err != nil { 72 + t.Fatalf("os.WriteFile(README.md) error = %v", err) 73 + } 74 + 75 + listing, err := ListSystemTree(root) 76 + if err != nil { 77 + t.Fatalf("ListSystemTree() error = %v", err) 78 + } 79 + if listing.Path != filepath.Clean(root) { 80 + t.Fatalf("listing.Path = %q, want %q", listing.Path, filepath.Clean(root)) 81 + } 82 + if len(listing.Items) != 2 { 83 + t.Fatalf("len(listing.Items) = %d, want 2", len(listing.Items)) 84 + } 85 + if listing.Items[0].Path != filepath.Join(root, "docs") || !listing.Items[0].IsDir { 86 + t.Fatalf("listing.Items[0] = %#v, want docs dir", listing.Items[0]) 87 + } 88 + if listing.Items[0].SizeBytes < 0 { 89 + t.Fatalf("listing.Items[0].SizeBytes = %d, want non-negative", listing.Items[0].SizeBytes) 90 + } 91 + if listing.Items[1].Path != filepath.Join(root, "README.md") || listing.Items[1].IsDir { 92 + t.Fatalf("listing.Items[1] = %#v, want README.md file", listing.Items[1]) 93 + } 94 + if listing.Items[1].SizeBytes != 2 { 95 + t.Fatalf("listing.Items[1].SizeBytes = %d, want %d", listing.Items[1].SizeBytes, 2) 96 + } 97 + }
+7 -6
tools/builtin/bash.go
··· 14 14 "time" 15 15 16 16 "github.com/quailyquaily/mistermorph/agent" 17 + "github.com/quailyquaily/mistermorph/internal/pathroots" 17 18 "github.com/quailyquaily/mistermorph/tools" 18 19 ) 19 20 ··· 21 22 Enabled bool 22 23 DefaultTimeout time.Duration 23 24 MaxOutputBytes int 24 - BaseDirs []string 25 + Roots pathroots.PathRoots 25 26 DenyPaths []string 26 27 DenyTokens []string 27 28 InjectedEnvVars []string ··· 29 30 30 31 type bashExecutionPayload = shellExecutionPayload 31 32 32 - func NewBashTool(enabled bool, defaultTimeout time.Duration, maxOutputBytes int, baseDirs ...string) *BashTool { 33 + func NewBashTool(enabled bool, defaultTimeout time.Duration, maxOutputBytes int, roots pathroots.PathRoots) *BashTool { 33 34 if defaultTimeout <= 0 { 34 35 defaultTimeout = 30 * time.Second 35 36 } ··· 40 41 Enabled: enabled, 41 42 DefaultTimeout: defaultTimeout, 42 43 MaxOutputBytes: maxOutputBytes, 43 - BaseDirs: normalizeBaseDirs(baseDirs), 44 + Roots: pathroots.New(roots.WorkspaceDir, roots.FileCacheDir, roots.FileStateDir), 44 45 } 45 46 } 46 47 ··· 48 49 49 50 func (t *BashTool) Description() string { 50 51 return "Runs a bash command and returns stdout/stderr." + 51 - "For the `cmd` and `cwd`, supports path aliases file_cache_dir and file_state_dir." 52 + "For the `cmd` and `cwd`, supports path aliases workspace_dir, file_cache_dir, and file_state_dir. When workspace_dir is attached, bash defaults to cwd=workspace_dir." 52 53 } 53 54 54 55 func (t *BashTool) ParameterSchema() string { ··· 83 84 return "", fmt.Errorf("bash tool is disabled (enable via config: tools.bash.enabled=true)") 84 85 } 85 86 86 - inv, err := prepareShellInvocation(params, t.commonConfig(), t.runnerSpec()) 87 + inv, err := prepareShellInvocation(ctx, params, t.commonConfig(), t.runnerSpec()) 87 88 if err != nil { 88 89 return "", err 89 90 } ··· 105 106 ToolName: t.Name(), 106 107 DefaultTimeout: t.DefaultTimeout, 107 108 MaxOutputBytes: t.MaxOutputBytes, 108 - BaseDirs: append([]string(nil), t.BaseDirs...), 109 + Roots: t.Roots, 109 110 DenyPaths: append([]string(nil), t.DenyPaths...), 110 111 DenyTokens: append([]string(nil), t.DenyTokens...), 111 112 InjectedEnvVars: append([]string(nil), t.InjectedEnvVars...),
+28 -10
tools/builtin/bash_test.go
··· 14 14 "time" 15 15 16 16 "github.com/quailyquaily/mistermorph/agent" 17 + "github.com/quailyquaily/mistermorph/internal/pathroots" 17 18 ) 18 19 19 20 type stubBashSubtaskRunner struct { ··· 144 145 t.Fatal(err) 145 146 } 146 147 147 - tool := NewBashTool(true, 5*time.Second, 4096, cache, state) 148 + tool := NewBashTool(true, 5*time.Second, 4096, pathroots.New("", cache, state)) 148 149 out, err := tool.Execute(context.Background(), map[string]any{ 149 150 "cmd": "pwd", 150 151 "cwd": "file_state_dir/scripts", ··· 159 160 160 161 func TestBashTool_Execute_PathAliasMissingBaseDir(t *testing.T) { 161 162 cache := t.TempDir() 162 - tool := NewBashTool(true, 5*time.Second, 4096, cache) 163 + tool := NewBashTool(true, 5*time.Second, 4096, pathroots.New("", cache, "")) 163 164 _, err := tool.Execute(context.Background(), map[string]any{ 164 165 "cmd": "cat file_state_dir/note.txt", 165 166 }) ··· 171 172 } 172 173 } 173 174 175 + func TestBashTool_Execute_DefaultCWDUsesWorkspaceDirFromContext(t *testing.T) { 176 + workspaceDir := t.TempDir() 177 + cacheDir := t.TempDir() 178 + tool := NewBashTool(true, 5*time.Second, 4096, pathroots.New("", cacheDir, "")) 179 + 180 + ctx := pathroots.WithWorkspaceDir(context.Background(), workspaceDir) 181 + out, err := tool.Execute(ctx, map[string]any{ 182 + "cmd": "pwd", 183 + }) 184 + if err != nil { 185 + t.Fatalf("expected nil error, got %v (out=%q)", err, out) 186 + } 187 + if !strings.Contains(out, filepath.Clean(workspaceDir)) { 188 + t.Fatalf("expected pwd output to contain %q, got %q", workspaceDir, out) 189 + } 190 + } 191 + 174 192 func TestBashTool_Execute_UsesWhitelistedEnvOnly(t *testing.T) { 175 193 t.Setenv("HOME", "/tmp/mm-home") 176 194 t.Setenv("LANG", "C.UTF-8") 177 195 t.Setenv("MISTER_MORPH_API_KEY", "secret_value_should_not_leak") 178 196 179 - tool := NewBashTool(true, 5*time.Second, 4096) 197 + tool := NewBashTool(true, 5*time.Second, 4096, pathroots.PathRoots{}) 180 198 out, err := tool.Execute(context.Background(), map[string]any{ 181 199 "cmd": "env | sort", 182 200 }) ··· 198 216 t.Setenv("CUSTOM_API_BASE", "https://example.com") 199 217 t.Setenv("CUSTOM_HTTP_TIMEOUT", "15s") 200 218 201 - tool := NewBashTool(true, 5*time.Second, 4096) 219 + tool := NewBashTool(true, 5*time.Second, 4096, pathroots.PathRoots{}) 202 220 tool.InjectedEnvVars = []string{"CUSTOM_API_BASE"} 203 221 204 222 out, err := tool.Execute(context.Background(), map[string]any{ ··· 235 253 } 236 254 237 255 func TestBashTool_Execute_EmitsStreamEvents(t *testing.T) { 238 - tool := NewBashTool(true, 5*time.Second, 4096) 256 + tool := NewBashTool(true, 5*time.Second, 4096, pathroots.PathRoots{}) 239 257 sink := &recordingEventSink{} 240 258 ctx := agent.WithEventSinkContext(context.Background(), sink) 241 259 ··· 273 291 } 274 292 275 293 func TestBashTool_CaptureCommandStream_IgnoresClosedPipeRead(t *testing.T) { 276 - tool := NewBashTool(true, 5*time.Second, 4096) 294 + tool := NewBashTool(true, 5*time.Second, 4096, pathroots.PathRoots{}) 277 295 sink := &recordingEventSink{} 278 296 ctx := agent.WithEventSinkContext(context.Background(), sink) 279 297 dst := &limitedBuffer{Limit: 4096} ··· 320 338 } 321 339 322 340 func TestBashTool_Execute_RunInSubtask(t *testing.T) { 323 - tool := NewBashTool(true, 5*time.Second, 4096) 341 + tool := NewBashTool(true, 5*time.Second, 4096, pathroots.PathRoots{}) 324 342 runner := &stubBashSubtaskRunner{ 325 343 result: &agent.SubtaskResult{ 326 344 TaskID: "sub_bash", ··· 363 381 } 364 382 365 383 func TestBashTool_Execute_RunInSubtaskDoesNotRecurse(t *testing.T) { 366 - tool := NewBashTool(true, 5*time.Second, 4096) 384 + tool := NewBashTool(true, 5*time.Second, 4096, pathroots.PathRoots{}) 367 385 runner := &stubBashSubtaskRunner{ 368 386 result: &agent.SubtaskResult{TaskID: "should_not_be_used"}, 369 387 } ··· 386 404 } 387 405 388 406 func TestBashTool_Execute_RunInSubtaskWithoutRunnerFallsBackToDirectSubtask(t *testing.T) { 389 - tool := NewBashTool(true, 5*time.Second, 4096) 407 + tool := NewBashTool(true, 5*time.Second, 4096, pathroots.PathRoots{}) 390 408 out, err := tool.Execute(context.Background(), map[string]any{ 391 409 "cmd": "printf fallback", 392 410 "run_in_subtask": true, ··· 408 426 } 409 427 410 428 func TestBashTool_Execute_RunInSubtaskFailureReturnsErrorAndEnvelope(t *testing.T) { 411 - tool := NewBashTool(true, 5*time.Second, 4096) 429 + tool := NewBashTool(true, 5*time.Second, 4096, pathroots.PathRoots{}) 412 430 runner := &stubBashSubtaskRunner{ 413 431 result: &agent.SubtaskResult{ 414 432 TaskID: "sub_fail",
+90
tools/builtin/local_path_roots.go
··· 1 + package builtin 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "path/filepath" 7 + "strings" 8 + 9 + "github.com/quailyquaily/mistermorph/internal/pathroots" 10 + "github.com/quailyquaily/mistermorph/internal/pathutil" 11 + ) 12 + 13 + func resolveLocalPathRoots(ctx context.Context, fallback pathroots.PathRoots) pathroots.PathRoots { 14 + return pathroots.Resolve(ctx, fallback) 15 + } 16 + 17 + func detectPathAlias(userPath string) (string, string) { 18 + trimmed := strings.TrimLeft(userPath, "/\\") 19 + lower := strings.ToLower(trimmed) 20 + prefixes := []struct { 21 + Alias string 22 + Prefix string 23 + }{ 24 + {Alias: "workspace_dir", Prefix: "workspace_dir/"}, 25 + {Alias: "workspace_dir", Prefix: "workspace_dir\\"}, 26 + {Alias: "file_cache_dir", Prefix: "file_cache_dir/"}, 27 + {Alias: "file_cache_dir", Prefix: "file_cache_dir\\"}, 28 + {Alias: "file_state_dir", Prefix: "file_state_dir/"}, 29 + {Alias: "file_state_dir", Prefix: "file_state_dir\\"}, 30 + } 31 + switch lower { 32 + case "workspace_dir": 33 + return "workspace_dir", "" 34 + case "file_cache_dir": 35 + return "file_cache_dir", "" 36 + case "file_state_dir": 37 + return "file_state_dir", "" 38 + } 39 + for _, item := range prefixes { 40 + if !strings.HasPrefix(lower, item.Prefix) { 41 + continue 42 + } 43 + return item.Alias, strings.TrimLeft(trimmed[len(item.Prefix):], "/\\") 44 + } 45 + return "", userPath 46 + } 47 + 48 + func resolveAliasedPath(roots pathroots.PathRoots, alias string, rest string, requireLeaf bool) (string, error) { 49 + base := strings.TrimSpace(roots.BaseDir(alias)) 50 + if base == "" { 51 + return "", fmt.Errorf("base dir %s is not configured", alias) 52 + } 53 + baseAbs, err := filepath.Abs(pathutil.ExpandHomePath(base)) 54 + if err != nil { 55 + return "", err 56 + } 57 + rest = strings.TrimLeft(strings.TrimSpace(rest), "/\\") 58 + if rest == "" { 59 + if requireLeaf { 60 + return "", fmt.Errorf("invalid path: alias requires a relative file path (for example: %s/notes/todo.md)", alias) 61 + } 62 + return baseAbs, nil 63 + } 64 + candidate := filepath.Join(baseAbs, rest) 65 + candAbs, err := filepath.Abs(candidate) 66 + if err != nil { 67 + return "", err 68 + } 69 + if !pathutil.IsWithinDir(baseAbs, candAbs) { 70 + return "", fmt.Errorf("refusing to access outside allowed base dir %s", alias) 71 + } 72 + return candAbs, nil 73 + } 74 + 75 + func formatBaseDirHint(roots pathroots.PathRoots) string { 76 + parts := make([]string, 0, 3) 77 + if dir := strings.TrimSpace(roots.WorkspaceDir); dir != "" { 78 + parts = append(parts, fmt.Sprintf("workspace_dir=%s", dir)) 79 + } 80 + if dir := strings.TrimSpace(roots.FileCacheDir); dir != "" { 81 + parts = append(parts, fmt.Sprintf("file_cache_dir=%s", dir)) 82 + } 83 + if dir := strings.TrimSpace(roots.FileStateDir); dir != "" { 84 + parts = append(parts, fmt.Sprintf("file_state_dir=%s", dir)) 85 + } 86 + if len(parts) == 0 { 87 + return "base_dirs=[]" 88 + } 89 + return strings.Join(parts, " ") 90 + }
+7 -5
tools/builtin/powershell.go
··· 8 8 "path" 9 9 "strings" 10 10 "time" 11 + 12 + "github.com/quailyquaily/mistermorph/internal/pathroots" 11 13 ) 12 14 13 15 type PowerShellTool struct { 14 16 Enabled bool 15 17 DefaultTimeout time.Duration 16 18 MaxOutputBytes int 17 - BaseDirs []string 19 + Roots pathroots.PathRoots 18 20 DenyPaths []string 19 21 DenyTokens []string 20 22 InjectedEnvVars []string 21 23 } 22 24 23 - func NewPowerShellTool(enabled bool, defaultTimeout time.Duration, maxOutputBytes int, baseDirs ...string) *PowerShellTool { 25 + func NewPowerShellTool(enabled bool, defaultTimeout time.Duration, maxOutputBytes int, roots pathroots.PathRoots) *PowerShellTool { 24 26 if defaultTimeout <= 0 { 25 27 defaultTimeout = 30 * time.Second 26 28 } ··· 31 33 Enabled: enabled, 32 34 DefaultTimeout: defaultTimeout, 33 35 MaxOutputBytes: maxOutputBytes, 34 - BaseDirs: normalizeBaseDirs(baseDirs), 36 + Roots: pathroots.New(roots.WorkspaceDir, roots.FileCacheDir, roots.FileStateDir), 35 37 } 36 38 } 37 39 ··· 39 41 40 42 func (t *PowerShellTool) Description() string { 41 43 return "Runs a PowerShell command and returns stdout/stderr. " + 42 - "For the `cmd` and `cwd`, supports path aliases file_cache_dir and file_state_dir." 44 + "For the `cmd` and `cwd`, supports path aliases workspace_dir, file_cache_dir, and file_state_dir. When workspace_dir is attached, powershell defaults to cwd=workspace_dir." 43 45 } 44 46 45 47 func (t *PowerShellTool) ParameterSchema() string { ··· 77 79 ToolName: t.Name(), 78 80 DefaultTimeout: t.DefaultTimeout, 79 81 MaxOutputBytes: t.MaxOutputBytes, 80 - BaseDirs: append([]string(nil), t.BaseDirs...), 82 + Roots: t.Roots, 81 83 DenyPaths: append([]string(nil), t.DenyPaths...), 82 84 DenyTokens: append([]string(nil), t.DenyTokens...), 83 85 InjectedEnvVars: append([]string(nil), t.InjectedEnvVars...),
+5 -2
tools/builtin/powershell_test.go
··· 1 1 package builtin 2 2 3 3 import ( 4 + "context" 4 5 "path/filepath" 5 6 "strings" 6 7 "testing" 7 8 "time" 9 + 10 + "github.com/quailyquaily/mistermorph/internal/pathroots" 8 11 ) 9 12 10 13 func TestPowerShellToolEnv_UsesAllowlistedEnvOnly(t *testing.T) { ··· 59 62 func TestPrepareShellInvocation_PowerShellAliasSupportsBackslashes(t *testing.T) { 60 63 cache := t.TempDir() 61 64 62 - inv, err := prepareShellInvocation(map[string]any{ 65 + inv, err := prepareShellInvocation(context.Background(), map[string]any{ 63 66 "cmd": `Get-Content file_cache_dir\notes.txt`, 64 67 }, shellToolCommon{ 65 68 ToolName: "powershell", 66 69 DefaultTimeout: 5 * time.Second, 67 - BaseDirs: []string{cache}, 70 + Roots: pathroots.New("", cache, ""), 68 71 }, shellRunnerSpec{ 69 72 TokenBoundary: isPowerShellBoundaryByte, 70 73 })
+24 -19
tools/builtin/read_file.go
··· 8 8 "path/filepath" 9 9 "strings" 10 10 11 + "github.com/quailyquaily/mistermorph/internal/pathroots" 11 12 "github.com/quailyquaily/mistermorph/internal/pathutil" 12 13 ) 13 14 14 15 type ReadFileTool struct { 15 16 MaxBytes int64 16 17 DenyPaths []string 17 - BaseDirs []string 18 + Roots pathroots.PathRoots 18 19 } 19 20 20 21 func NewReadFileTool(maxBytes int64) *ReadFileTool { 21 22 return &ReadFileTool{MaxBytes: maxBytes} 22 23 } 23 24 24 - func NewReadFileToolWithDenyPaths(maxBytes int64, denyPaths []string, baseDirs ...string) *ReadFileTool { 25 + func NewReadFileToolWithDenyPaths(maxBytes int64, denyPaths []string, roots pathroots.PathRoots) *ReadFileTool { 25 26 tool := &ReadFileTool{MaxBytes: maxBytes, DenyPaths: denyPaths} 26 - tool.BaseDirs = normalizeBaseDirs(baseDirs) 27 + tool.Roots = pathroots.New(roots.WorkspaceDir, roots.FileCacheDir, roots.FileStateDir) 27 28 return tool 28 29 } 29 30 ··· 39 40 "properties": map[string]any{ 40 41 "path": map[string]any{ 41 42 "type": "string", 42 - "description": "File path to read. Supports aliases `file_cache_dir/<path>` and `file_state_dir/<path>`.", 43 + "description": "File path to read. Supports aliases `workspace_dir/<path>`, `file_cache_dir/<path>`, and `file_state_dir/<path>`. Relative paths resolve under workspace_dir when attached, otherwise under file_cache_dir.", 43 44 }, 44 45 }, 45 46 "required": []string{"path"}, ··· 48 49 return string(b) 49 50 } 50 51 51 - func (t *ReadFileTool) Execute(_ context.Context, params map[string]any) (string, error) { 52 + func (t *ReadFileTool) Execute(ctx context.Context, params map[string]any) (string, error) { 52 53 path, _ := params["path"].(string) 53 54 path = strings.TrimSpace(path) 54 55 if path == "" { 55 56 return "", fmt.Errorf("missing required param: path") 56 57 } 57 58 var err error 58 - path, err = t.resolvePath(path) 59 + path, err = t.resolvePath(ctx, path) 59 60 if err != nil { 60 61 return "", err 61 62 } ··· 74 75 return string(data), nil 75 76 } 76 77 77 - func (t *ReadFileTool) resolvePath(rawPath string) (string, error) { 78 + func (t *ReadFileTool) resolvePath(ctx context.Context, rawPath string) (string, error) { 79 + roots := resolveLocalPathRoots(ctx, t.Roots) 78 80 rawPath = strings.TrimSpace(rawPath) 79 - alias, rest := detectWritePathAlias(rawPath) 80 - if alias == "" { 81 - return pathutil.ExpandHomePath(rawPath), nil 81 + rawPath = pathutil.ExpandHomePath(rawPath) 82 + alias, rest := detectPathAlias(rawPath) 83 + if alias != "" { 84 + return resolveAliasedPath(roots, alias, rest, true) 82 85 } 83 - rest = strings.TrimLeft(strings.TrimSpace(rest), "/\\") 84 - if rest == "" { 85 - return "", fmt.Errorf("invalid path: alias requires a relative file path (for example: %s/notes/todo.md)", alias) 86 + if filepath.IsAbs(rawPath) { 87 + return filepath.Abs(filepath.Clean(rawPath)) 86 88 } 87 - 88 - base := selectBaseForAlias(t.BaseDirs, alias) 89 + base := strings.TrimSpace(roots.DefaultFileDir()) 89 90 if strings.TrimSpace(base) == "" { 90 - return "", fmt.Errorf("base dir %s is not configured", alias) 91 + return filepath.Abs(filepath.Clean(rawPath)) 92 + } 93 + defaultAlias := "file_cache_dir" 94 + if strings.TrimSpace(roots.WorkspaceDir) != "" { 95 + defaultAlias = "workspace_dir" 91 96 } 92 97 baseAbs, err := filepath.Abs(pathutil.ExpandHomePath(base)) 93 98 if err != nil { 94 99 return "", err 95 100 } 96 - candidate := filepath.Join(baseAbs, rest) 101 + candidate := filepath.Join(baseAbs, rawPath) 97 102 candAbs, err := filepath.Abs(candidate) 98 103 if err != nil { 99 104 return "", err 100 105 } 101 - if !isWithinDir(baseAbs, candAbs) { 102 - return "", fmt.Errorf("refusing to read outside allowed base dir %s", alias) 106 + if !pathutil.IsWithinDir(baseAbs, candAbs) { 107 + return "", fmt.Errorf("refusing to read outside allowed base dir %s", defaultAlias) 103 108 } 104 109 return candAbs, nil 105 110 }
+27 -3
tools/builtin/read_file_test.go
··· 6 6 "path/filepath" 7 7 "strings" 8 8 "testing" 9 + 10 + "github.com/quailyquaily/mistermorph/internal/pathroots" 9 11 ) 10 12 11 13 func TestDenyPath(t *testing.T) { ··· 53 55 home := t.TempDir() 54 56 t.Setenv("HOME", home) 55 57 56 - tool := NewWriteFileTool(true, 1024, "~/.morph-cache") 58 + tool := NewWriteFileTool(true, 1024, pathroots.New("", "~/.morph-cache", "")) 57 59 _, err := tool.Execute(context.Background(), map[string]any{ 58 60 "path": "out.txt", 59 61 "content": "ok", ··· 77 79 if err := os.WriteFile(filepath.Join(state, "note.txt"), []byte("hi"), 0o644); err != nil { 78 80 t.Fatal(err) 79 81 } 80 - tool := NewReadFileToolWithDenyPaths(1024, nil, cache, state) 82 + tool := NewReadFileToolWithDenyPaths(1024, nil, pathroots.New("", cache, state)) 81 83 out, err := tool.Execute(context.Background(), map[string]any{"path": "file_state_dir/note.txt"}) 82 84 if err != nil { 83 85 t.Fatalf("Execute returned error: %v", err) ··· 90 92 func TestReadFileTool_BareAliasRejected(t *testing.T) { 91 93 cache := t.TempDir() 92 94 state := t.TempDir() 93 - tool := NewReadFileToolWithDenyPaths(1024, nil, cache, state) 95 + tool := NewReadFileToolWithDenyPaths(1024, nil, pathroots.New("", cache, state)) 94 96 _, err := tool.Execute(context.Background(), map[string]any{"path": "file_state_dir"}) 95 97 if err == nil { 96 98 t.Fatalf("expected error, got nil") ··· 99 101 t.Fatalf("unexpected error: %v", err) 100 102 } 101 103 } 104 + 105 + func TestReadFileTool_RelativePathUsesWorkspaceDirFromContext(t *testing.T) { 106 + workspaceDir := t.TempDir() 107 + cacheDir := t.TempDir() 108 + stateDir := t.TempDir() 109 + if err := os.WriteFile(filepath.Join(workspaceDir, "note.txt"), []byte("from-workspace"), 0o644); err != nil { 110 + t.Fatal(err) 111 + } 112 + if err := os.WriteFile(filepath.Join(cacheDir, "note.txt"), []byte("from-cache"), 0o644); err != nil { 113 + t.Fatal(err) 114 + } 115 + 116 + tool := NewReadFileToolWithDenyPaths(1024, nil, pathroots.New("", cacheDir, stateDir)) 117 + ctx := pathroots.WithWorkspaceDir(context.Background(), workspaceDir) 118 + out, err := tool.Execute(ctx, map[string]any{"path": "note.txt"}) 119 + if err != nil { 120 + t.Fatalf("Execute returned error: %v", err) 121 + } 122 + if out != "from-workspace" { 123 + t.Fatalf("got %q, want %q", out, "from-workspace") 124 + } 125 + }
+27 -19
tools/builtin/shell_runner.go
··· 11 11 "sync" 12 12 "time" 13 13 14 + "github.com/quailyquaily/mistermorph/internal/pathroots" 14 15 "github.com/quailyquaily/mistermorph/internal/pathutil" 15 16 ) 16 17 ··· 26 27 ToolName string 27 28 DefaultTimeout time.Duration 28 29 MaxOutputBytes int 29 - BaseDirs []string 30 + Roots pathroots.PathRoots 30 31 DenyPaths []string 31 32 DenyTokens []string 32 33 InjectedEnvVars []string ··· 61 62 shellFailureExec 62 63 ) 63 64 64 - func prepareShellInvocation(params map[string]any, common shellToolCommon, spec shellRunnerSpec) (shellInvocation, error) { 65 + func prepareShellInvocation(ctx context.Context, params map[string]any, common shellToolCommon, spec shellRunnerSpec) (shellInvocation, error) { 65 66 cmdStr, _ := params["cmd"].(string) 66 67 cmdStr = strings.TrimSpace(cmdStr) 67 68 if cmdStr == "" { ··· 69 70 } 70 71 71 72 var err error 72 - cmdStr, err = expandShellPathAliases(common.BaseDirs, cmdStr, spec.TokenBoundary) 73 + cmdStr, err = expandShellPathAliases(resolveLocalPathRoots(ctx, common.Roots), cmdStr, spec.TokenBoundary) 73 74 if err != nil { 74 75 return shellInvocation{}, err 75 76 } ··· 84 85 } 85 86 86 87 cwd, _ := params["cwd"].(string) 87 - cwd, err = resolveShellCWD(common.BaseDirs, strings.TrimSpace(cwd)) 88 + cwd, err = resolveShellCWD(ctx, common.Roots, strings.TrimSpace(cwd)) 88 89 if err != nil { 89 90 return shellInvocation{}, err 90 91 } ··· 104 105 } 105 106 106 107 func executeShellCommand(ctx context.Context, params map[string]any, common shellToolCommon, spec shellRunnerSpec) (string, error) { 107 - inv, err := prepareShellInvocation(params, common, spec) 108 + inv, err := prepareShellInvocation(ctx, params, common, spec) 108 109 if err != nil { 109 110 return "", err 110 111 } ··· 259 260 } 260 261 } 261 262 262 - func resolveShellCWD(baseDirs []string, raw string) (string, error) { 263 + func resolveShellCWD(ctx context.Context, roots pathroots.PathRoots, raw string) (string, error) { 264 + roots = resolveLocalPathRoots(ctx, roots) 263 265 raw = strings.TrimSpace(raw) 264 266 if raw == "" { 267 + if workspaceDir := strings.TrimSpace(roots.WorkspaceDir); workspaceDir != "" { 268 + return workspaceDir, nil 269 + } 265 270 return "", nil 266 271 } 267 - alias, rest := detectWritePathAlias(raw) 268 - if alias == "" { 269 - return pathutil.ExpandHomePath(raw), nil 272 + raw = pathutil.ExpandHomePath(raw) 273 + alias, rest := detectPathAlias(raw) 274 + if alias != "" { 275 + return resolveAliasedPath(roots, alias, rest, false) 270 276 } 271 - base := selectBaseForAlias(baseDirs, alias) 272 - if strings.TrimSpace(base) == "" { 273 - return "", fmt.Errorf("base dir %s is not configured", alias) 277 + if filepath.IsAbs(raw) { 278 + return filepath.Abs(filepath.Clean(raw)) 274 279 } 275 - rest = strings.TrimLeft(strings.TrimSpace(rest), "/\\") 276 - if rest == "" { 277 - return filepath.Clean(base), nil 280 + if workspaceDir := strings.TrimSpace(roots.WorkspaceDir); workspaceDir != "" { 281 + return filepath.Abs(filepath.Join(workspaceDir, raw)) 278 282 } 279 - return filepath.Clean(filepath.Join(base, rest)), nil 283 + return pathutil.ExpandHomePath(raw), nil 280 284 } 281 285 282 - func expandShellPathAliases(baseDirs []string, cmd string, isBoundary func(byte) bool) (string, error) { 286 + func expandShellPathAliases(roots pathroots.PathRoots, cmd string, isBoundary func(byte) bool) (string, error) { 283 287 var err error 284 - cmd, err = replaceAliasTokenInCommand(cmd, "file_cache_dir", selectBaseForAlias(baseDirs, "file_cache_dir"), isBoundary) 288 + cmd, err = replaceAliasTokenInCommand(cmd, "workspace_dir", strings.TrimSpace(roots.WorkspaceDir), isBoundary) 285 289 if err != nil { 286 290 return "", err 287 291 } 288 - cmd, err = replaceAliasTokenInCommand(cmd, "file_state_dir", selectBaseForAlias(baseDirs, "file_state_dir"), isBoundary) 292 + cmd, err = replaceAliasTokenInCommand(cmd, "file_cache_dir", strings.TrimSpace(roots.FileCacheDir), isBoundary) 293 + if err != nil { 294 + return "", err 295 + } 296 + cmd, err = replaceAliasTokenInCommand(cmd, "file_state_dir", strings.TrimSpace(roots.FileStateDir), isBoundary) 289 297 if err != nil { 290 298 return "", err 291 299 }
+2 -1
tools/builtin/url_fetch.go
··· 18 18 "time" 19 19 20 20 "github.com/quailyquaily/mistermorph/guard" 21 + "github.com/quailyquaily/mistermorph/internal/pathroots" 21 22 "github.com/quailyquaily/mistermorph/secrets" 22 23 "golang.org/x/net/html" 23 24 ) ··· 469 470 if truncated { 470 471 return "", fmt.Errorf("download truncated (max_bytes=%d); increase tools.url_fetch.max_bytes_download or pass a larger max_bytes", maxBytes) 471 472 } 472 - _, resolvedPath, err := resolveWritePath([]string{t.FileCacheDir}, downloadPath) 473 + _, resolvedPath, err := resolveWritePath(pathroots.New("", t.FileCacheDir, ""), downloadPath) 473 474 if err != nil { 474 475 return "", err 475 476 }
+34 -113
tools/builtin/write_file.go
··· 8 8 "path/filepath" 9 9 "strings" 10 10 11 + "github.com/quailyquaily/mistermorph/internal/pathroots" 11 12 "github.com/quailyquaily/mistermorph/internal/pathutil" 12 13 ) 13 14 14 15 type WriteFileTool struct { 15 16 Enabled bool 16 17 MaxBytes int 17 - BaseDirs []string 18 + Roots pathroots.PathRoots 18 19 } 19 20 20 - func NewWriteFileTool(enabled bool, maxBytes int, baseDirs ...string) *WriteFileTool { 21 + func NewWriteFileTool(enabled bool, maxBytes int, roots pathroots.PathRoots) *WriteFileTool { 21 22 if maxBytes <= 0 { 22 23 maxBytes = 512 * 1024 23 24 } 24 - cleaned := make([]string, 0, len(baseDirs)) 25 - for _, dir := range baseDirs { 26 - dir = strings.TrimSpace(dir) 27 - if dir == "" { 28 - continue 29 - } 30 - cleaned = append(cleaned, dir) 31 - } 32 - if len(cleaned) == 0 { 33 - cleaned = []string{"~/.cache/morph"} 34 - } 35 25 return &WriteFileTool{ 36 26 Enabled: enabled, 37 27 MaxBytes: maxBytes, 38 - BaseDirs: cleaned, 28 + Roots: pathroots.New(roots.WorkspaceDir, roots.FileCacheDir, roots.FileStateDir), 39 29 } 40 30 } 41 31 42 32 func (t *WriteFileTool) Name() string { return "write_file" } 43 33 44 34 func (t *WriteFileTool) Description() string { 45 - return "Writes text content to a local file (overwrite or append). Writes are restricted to file_cache_dir or file_state_dir." 35 + return "Writes text content to a local file (overwrite or append). Writes are restricted to workspace_dir, file_cache_dir, or file_state_dir." 46 36 } 47 37 48 38 func (t *WriteFileTool) ParameterSchema() string { ··· 51 41 "properties": map[string]any{ 52 42 "path": map[string]any{ 53 43 "type": "string", 54 - "description": "File path to write. Relative paths are resolved under file_cache_dir. Absolute paths are allowed only if they resolve within file_cache_dir or file_state_dir. Prefix with file_state_dir/ to force state dir.", 44 + "description": "File path to write. Relative paths are resolved under workspace_dir when attached, otherwise under file_cache_dir. Absolute paths are allowed only if they resolve within workspace_dir, file_cache_dir, or file_state_dir. Prefix with workspace_dir/ or file_state_dir/ to force a base dir.", 55 45 }, 56 46 "content": map[string]any{ 57 47 "type": "string", ··· 68 58 return string(b) 69 59 } 70 60 71 - func (t *WriteFileTool) Execute(_ context.Context, params map[string]any) (string, error) { 61 + func (t *WriteFileTool) Execute(ctx context.Context, params map[string]any) (string, error) { 72 62 if !t.Enabled { 73 63 return "", fmt.Errorf("write_file tool is disabled (enable via config: tools.write_file.enabled=true)") 74 64 } ··· 78 68 if path == "" { 79 69 return "", fmt.Errorf("missing required param: path") 80 70 } 81 - baseDir, resolvedPath, err := resolveWritePath(t.BaseDirs, path) 71 + roots := resolveLocalPathRoots(ctx, t.Roots) 72 + baseDir, resolvedPath, err := resolveWritePath(roots, path) 82 73 if err != nil { 83 74 return "", err 84 75 } ··· 131 122 return string(out), nil 132 123 } 133 124 134 - func resolveWritePath(baseDirs []string, userPath string) (string, string, error) { 135 - bases := normalizeBaseDirs(baseDirs) 136 - if len(bases) == 0 { 137 - return "", "", fmt.Errorf("file_cache_dir/file_state_dir is not configured") 125 + func resolveWritePath(roots pathroots.PathRoots, userPath string) (string, string, error) { 126 + roots = pathroots.New(roots.WorkspaceDir, roots.FileCacheDir, roots.FileStateDir) 127 + if strings.TrimSpace(roots.FileCacheDir) == "" && strings.TrimSpace(roots.FileStateDir) == "" && strings.TrimSpace(roots.WorkspaceDir) == "" { 128 + return "", "", fmt.Errorf("workspace_dir/file_cache_dir/file_state_dir is not configured") 138 129 } 139 130 140 131 userPath = pathutil.ExpandHomePath(userPath) ··· 143 134 return "", "", fmt.Errorf("missing required param: path") 144 135 } 145 136 146 - if alias, rest := detectWritePathAlias(userPath); alias != "" { 147 - base := selectBaseForAlias(bases, alias) 148 - if strings.TrimSpace(base) == "" { 149 - return "", "", fmt.Errorf("base dir %s is not configured", alias) 137 + if alias, rest := detectPathAlias(userPath); alias != "" { 138 + resolved, err := resolveAliasedPath(roots, alias, rest, true) 139 + if err != nil { 140 + return "", "", err 141 + } 142 + base := strings.TrimSpace(roots.BaseDir(alias)) 143 + baseAbs, err := ensureWriteBaseDir(base) 144 + if err != nil { 145 + return "", "", err 150 146 } 151 - return resolveWritePathWithBase(base, rest, formatBaseDirHint(bases)) 147 + return baseAbs, resolved, nil 152 148 } 153 149 154 150 if filepath.IsAbs(userPath) { ··· 156 152 if err != nil { 157 153 return "", "", err 158 154 } 159 - for _, base := range bases { 155 + for _, base := range roots.AllowedBaseDirs() { 160 156 baseAbs, err := filepath.Abs(base) 161 157 if err != nil { 162 158 continue 163 159 } 164 - if !isWithinDir(baseAbs, candAbs) { 160 + if !pathutil.IsWithinDir(baseAbs, candAbs) && filepath.Clean(baseAbs) != filepath.Clean(candAbs) { 165 161 continue 166 162 } 167 163 baseAbs, err = ensureWriteBaseDir(baseAbs) ··· 170 166 } 171 167 return baseAbs, candAbs, nil 172 168 } 173 - return "", "", fmt.Errorf("refusing to write outside allowed base dirs (%s path=%s)", formatBaseDirHint(bases), candAbs) 169 + return "", "", fmt.Errorf("refusing to write outside allowed base dirs (%s path=%s)", formatBaseDirHint(roots), candAbs) 174 170 } 175 171 176 - return resolveWritePathWithBase(bases[0], userPath, formatBaseDirHint(bases)) 177 - } 178 - 179 - func normalizeBaseDirs(baseDirs []string) []string { 180 - out := make([]string, 0, len(baseDirs)) 181 - for _, dir := range baseDirs { 182 - dir = strings.TrimSpace(dir) 183 - if dir == "" { 184 - continue 185 - } 186 - out = append(out, pathutil.ExpandHomePath(dir)) 172 + defaultBase := strings.TrimSpace(roots.DefaultFileDir()) 173 + defaultAlias := "file_cache_dir" 174 + if strings.TrimSpace(roots.WorkspaceDir) != "" { 175 + defaultAlias = "workspace_dir" 187 176 } 188 - return out 177 + return resolveWritePathWithBase(defaultBase, defaultAlias, userPath, formatBaseDirHint(roots)) 189 178 } 190 179 191 - func detectWritePathAlias(userPath string) (string, string) { 192 - trimmed := strings.TrimLeft(userPath, "/\\") 193 - lower := strings.ToLower(trimmed) 194 - cachePrefix := "file_cache_dir/" 195 - cachePrefixAlt := "file_cache_dir\\" 196 - statePrefix := "file_state_dir/" 197 - statePrefixAlt := "file_state_dir\\" 198 - switch { 199 - case lower == "file_cache_dir": 200 - return "file_cache_dir", "" 201 - case lower == "file_state_dir": 202 - return "file_state_dir", "" 203 - case strings.HasPrefix(lower, cachePrefix): 204 - return "file_cache_dir", strings.TrimLeft(trimmed[len(cachePrefix):], "/\\") 205 - case strings.HasPrefix(lower, cachePrefixAlt): 206 - return "file_cache_dir", strings.TrimLeft(trimmed[len(cachePrefixAlt):], "/\\") 207 - case strings.HasPrefix(lower, statePrefix): 208 - return "file_state_dir", strings.TrimLeft(trimmed[len(statePrefix):], "/\\") 209 - case strings.HasPrefix(lower, statePrefixAlt): 210 - return "file_state_dir", strings.TrimLeft(trimmed[len(statePrefixAlt):], "/\\") 211 - default: 212 - return "", userPath 213 - } 214 - } 215 - 216 - func selectBaseForAlias(bases []string, alias string) string { 217 - if len(bases) == 0 { 218 - return "" 219 - } 220 - switch alias { 221 - case "file_cache_dir": 222 - return bases[0] 223 - case "file_state_dir": 224 - if len(bases) > 1 { 225 - return bases[1] 226 - } 227 - } 228 - return "" 229 - } 230 - 231 - func resolveWritePathWithBase(baseDir string, userPath string, hint string) (string, string, error) { 180 + func resolveWritePathWithBase(baseDir string, alias string, userPath string, hint string) (string, string, error) { 232 181 baseAbs, err := ensureWriteBaseDir(baseDir) 233 182 if err != nil { 234 183 return "", "", err 235 184 } 236 185 userPath = strings.TrimLeft(strings.TrimSpace(userPath), "/\\") 237 186 if userPath == "" { 238 - return "", "", fmt.Errorf("invalid path: alias requires a relative file path (for example: file_state_dir/notes/todo.md)") 187 + return "", "", fmt.Errorf("invalid path: alias requires a relative file path (for example: %s/notes/todo.md)", alias) 239 188 } 240 189 candidate := filepath.Join(baseAbs, userPath) 241 190 candAbs, err := filepath.Abs(candidate) 242 191 if err != nil { 243 192 return "", "", err 244 193 } 245 - if !isWithinDir(baseAbs, candAbs) { 194 + if !pathutil.IsWithinDir(baseAbs, candAbs) { 246 195 return "", "", fmt.Errorf("refusing to write outside allowed base dirs (%s path=%s)", hint, candAbs) 247 196 } 248 197 return baseAbs, candAbs, nil ··· 275 224 } 276 225 return baseAbs, nil 277 226 } 278 - 279 - func formatBaseDirHint(bases []string) string { 280 - if len(bases) == 0 { 281 - return "base_dirs=[]" 282 - } 283 - parts := make([]string, 0, len(bases)) 284 - parts = append(parts, fmt.Sprintf("file_cache_dir=%s", bases[0])) 285 - if len(bases) > 1 { 286 - parts = append(parts, fmt.Sprintf("file_state_dir=%s", bases[1])) 287 - } 288 - for i := 2; i < len(bases); i++ { 289 - parts = append(parts, fmt.Sprintf("base_dir_%d=%s", i+1, bases[i])) 290 - } 291 - return strings.Join(parts, " ") 292 - } 293 - 294 - func isWithinDir(baseAbs string, candAbs string) bool { 295 - baseAbs = filepath.Clean(baseAbs) 296 - candAbs = filepath.Clean(candAbs) 297 - rel, err := filepath.Rel(baseAbs, candAbs) 298 - if err != nil { 299 - return false 300 - } 301 - if rel == "." || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { 302 - return false 303 - } 304 - return true 305 - }
+30 -4
tools/builtin/write_file_test.go
··· 6 6 "path/filepath" 7 7 "strings" 8 8 "testing" 9 + 10 + "github.com/quailyquaily/mistermorph/internal/pathroots" 9 11 ) 10 12 11 13 func TestWriteFileTool_RestrictedToBaseDir(t *testing.T) { 12 14 base := t.TempDir() 13 - tool := NewWriteFileTool(true, 1024, base) 15 + tool := NewWriteFileTool(true, 1024, pathroots.New("", base, "")) 14 16 15 17 out, err := tool.Execute(context.Background(), map[string]any{ 16 18 "path": "a.txt", ··· 42 44 43 45 func TestWriteFileTool_PathTraversalRejected(t *testing.T) { 44 46 base := t.TempDir() 45 - tool := NewWriteFileTool(true, 1024, base) 47 + tool := NewWriteFileTool(true, 1024, pathroots.New("", base, "")) 46 48 47 49 out, err := tool.Execute(context.Background(), map[string]any{ 48 50 "path": "../escape.txt", ··· 56 58 func TestWriteFileTool_AllowStateDirPrefix(t *testing.T) { 57 59 cache := t.TempDir() 58 60 state := t.TempDir() 59 - tool := NewWriteFileTool(true, 1024, cache, state) 61 + tool := NewWriteFileTool(true, 1024, pathroots.New("", cache, state)) 60 62 61 63 out, err := tool.Execute(context.Background(), map[string]any{ 62 64 "path": "file_state_dir/note.txt", ··· 77 79 func TestWriteFileTool_BareAliasRejected(t *testing.T) { 78 80 cache := t.TempDir() 79 81 state := t.TempDir() 80 - tool := NewWriteFileTool(true, 1024, cache, state) 82 + tool := NewWriteFileTool(true, 1024, pathroots.New("", cache, state)) 81 83 82 84 out, err := tool.Execute(context.Background(), map[string]any{ 83 85 "path": "file_state_dir", ··· 93 95 t.Fatalf("unexpected file created under cache dir") 94 96 } 95 97 } 98 + 99 + func TestWriteFileTool_RelativePathUsesWorkspaceDirFromContext(t *testing.T) { 100 + workspaceDir := t.TempDir() 101 + cacheDir := t.TempDir() 102 + stateDir := t.TempDir() 103 + tool := NewWriteFileTool(true, 1024, pathroots.New("", cacheDir, stateDir)) 104 + 105 + ctx := pathroots.WithWorkspaceDir(context.Background(), workspaceDir) 106 + out, err := tool.Execute(ctx, map[string]any{ 107 + "path": "note.txt", 108 + "content": "workspace", 109 + }) 110 + if err != nil { 111 + t.Fatalf("expected nil error, got %v (out=%q)", err, out) 112 + } 113 + 114 + got, err := os.ReadFile(filepath.Join(workspaceDir, "note.txt")) 115 + if err != nil { 116 + t.Fatal(err) 117 + } 118 + if string(got) != "workspace" { 119 + t.Fatalf("unexpected content: %q", string(got)) 120 + } 121 + }
+27
web/console/src/assets/workspace-icons/NOTICE.md
··· 1 + These SVG icons are vendored from `Henriquehnnm/vitesse-icon-theme`. 2 + 3 + Source: `https://github.com/Henriquehnnm/vitesse-icon-theme` 4 + Theme variant: `icons/vitesse-light/icons` 5 + License: MIT 6 + 7 + MIT License 8 + 9 + Copyright (c) 2025 henrique 10 + 11 + Permission is hereby granted, free of charge, to any person obtaining a copy 12 + of this software and associated documentation files (the "Software"), to deal 13 + in the Software without restriction, including without limitation the rights 14 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 + copies of the Software, and to permit persons to whom the Software is 16 + furnished to do so, subject to the following conditions: 17 + 18 + The above copyright notice and this permission notice shall be included in all 19 + copies or substantial portions of the Software. 20 + 21 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 + SOFTWARE.
+1
web/console/src/assets/workspace-icons/_3d.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="m3.5 13-2-1.25V9.5m0-3.17V4l2-1M4 5.75 1.5 4m4-2L8 .5 10.5 2m2 1 2 1v2.5M12 5.75 14.5 4m0 5.5V12l-2 1m-2 1L8 15.5 5.5 14M8 13v2.5M8 8l2.25-1.25M8 8v2.25M8 8 5.75 6.75"/></svg>
+1
web/console/src/assets/workspace-icons/adonis.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#5d5dff"><path d="M8 1.5c5.2 0 6.5 1.3 6.5 6.5s-1.3 6.5-6.5 6.5S1.5 13.2 1.5 8 2.8 1.5 8 1.5Z"/><path stroke-linejoin="round" d="M8 10.89c1.08 0 1.44.36 2.27.66.51.2 1.1-.07 1.28-.55a.87.87 0 0 0-.03-.71l-2.68-5.4a.96.96 0 0 0-1.68 0l-2.68 5.4c-.23.47-.01 1.02.5 1.24.23.1.5.12.75.02.83-.3 1.19-.66 2.27-.66Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/alex.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round"><rect width="13" height="13" x="1.5" y="1.5" rx="2"/><path d="M5.5 12.5h5M5.5 10.5h5M6.75 3.5H8c.79 0 1.5.71 1.5 1.5v3.5h-2c-.79 0-1-1-1-1.5s.21-1.5 1-1.5h2"/></g></svg>
+1
web/console/src/assets/workspace-icons/android.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round" d="M1.5 6.5v5m13-5v5m-11-6h9V12c0 .83-.67 1.5-1.5 1.5H5A1.5 1.5 0 0 1 3.5 12V5.5h0Zm9 0c0-2.49-2.01-4-4.5-4s-4.5 1.51-4.5 4m1-5 1 1.5m6-1.5-1 1.5m-5 11.5v2m5-2v2"/></svg>
+1
web/console/src/assets/workspace-icons/angular.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#e45649" d="m8 1 6.5 2-1 9.5L8 15l-5.5-2.5-1-9.5z"/><path stroke="#333333" d="m4.5 10.5 3.5-7 3.5 7m-5.8-2h4.64"/></g></svg>
+1
web/console/src/assets/workspace-icons/ansible.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M4.75 11.25 8 3.75l3.25 7.5L6.5 7.5m1.5 7a6.5 6.5 0 1 0 0-13 6.5 6.5 0 0 0 0 13Z"/></svg>
+1
web/console/src/assets/workspace-icons/antlr.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round"><path d="M1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0"/><path d="m8 9.5 3 1-3-6-3 6"/></g></svg>
+1
web/console/src/assets/workspace-icons/api_blueprint.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8"><circle cx="3.5" cy="12.5" r="2"/><circle cx="12.5" cy="12.5" r="2"/><circle cx="8" cy="3.5" r="2"/><path d="m4 11 3.5-6m1 0 3.5 6m-8.5 2v-1"/></g></svg>
+1
web/console/src/assets/workspace-icons/apollo.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round"><path d="M14.24 6.2a6.5 6.5 0 1 1-1.68-2.83"/><circle cx="12.56" cy="3.37" r="1"/><path d="M4.98 10.44 8 4.57l3.03 5.87M6 8.5h2"/></g></svg>
+1
web/console/src/assets/workspace-icons/apple.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M14.46 11.37c-1.43 2.76-2.57 4.13-3.62 4.13-.48 0-.96-.15-1.42-.45a2.18 2.18 0 0 0-2.25-.04c-.56.32-1.1.49-1.6.49-1.54 0-4.07-4.55-4.07-7.05 0-2.66 1.45-4.9 3.66-4.9 1.03 0 1.93.65 2.68.95.32.13.68.12 1-.03.6-.28 1.41-.92 2.41-.92 1.22 0 2.28.79 3.17 1.92.13.18.1.42-.08.55-.99.72-1.47 1.52-1.47 2.43 0 .91.48 1.72 1.47 2.43.15.11.2.32.12.49Z"/><path fill="#333333" d="M8.38 3A.38.38 0 0 1 8 2.63 2.63 2.63 0 0 1 10.63 0a.37.37 0 0 1 .37.38A2.63 2.63 0 0 1 8.38 3Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/apps_script.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#bd2c00" d="M2.5 11.5h9a1 1 0 0 1 0 2h-9a1 1 0 0 1 0-2Z"/><path stroke="#e0c240" d="m4.7 6.52 7.37 5.16a1 1 0 1 1-1.14 1.64L3.55 8.16A1 1 0 1 1 4.7 6.52Z"/><path stroke="#50a14f" d="m9.36 3.7 3.08 8.46a1 1 0 0 1-1.88.68L7.48 4.38a1 1 0 1 1 1.88-.68Z"/><path stroke="#3b8ad8" d="m14.8 4.07-2.33 8.69a1 1 0 1 1-1.94-.52l2.33-8.7a1 1 0 1 1 1.94.53Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/appveyor.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#007ACC" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6.5"/><circle cx="8" cy="8" r="3"/><path d="m5.39 6.24-3.64 2.9m8.03 1.46-2.73 3.87"/></g></svg>
+1
web/console/src/assets/workspace-icons/arduino.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#007ACC" stroke-linecap="round" stroke-linejoin="round" d="M8.72 6.65a3.53 3.53 0 0 1 6.71.66c.3 1.5-.42 3.04-1.79 3.77a3.55 3.55 0 0 1-4.15-.6C8.04 8.95 7.85 6.88 6.5 5.54c-1-1-2.52-1.3-3.84-.76a3.5 3.5 0 0 0 0 6.45 3.54 3.54 0 0 0 4.61-1.86M5 8H3m9.9 0h-1.88M12 9V7"/></svg>
+1
web/console/src/assets/workspace-icons/artistic_style.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#3b8ad8" d="m12.2 7.2.36-.36c1.55-1.62 2.49-3.16 2.12-3.52-.39-.4-2.13.72-3.89 2.47l-.35.37C8.89 7.78 7.95 9.32 8.32 9.68c.39.4 2.13-.72 3.89-2.47Z"/><path stroke="#333333" d="M3.5 4.5H2A1.5 1.5 0 1 1 3.5 3v10c0 .83.67 1.5 1.5 1.5h9m-1.5-3H14a1.5 1.5 0 1 1-1.5 1.5V9m0-6c0-.83-.67-1.5-1.5-1.5H2"/><path stroke="#e0c240" stroke-linejoin="round" d="M8.14 9.87c-.75-.75-2.07-.19-2.45.19a9.72 9.72 0 0 0-.18 2.44H6a7.77 7.77 0 0 0 1.94-.19c.37-.37.94-1.69.19-2.44Z"/><path stroke="#6c7280" d="M5 3.5h5m-5 3h2.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/asciidoc.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#008080" stroke-linecap="square" d="m5.13 11.5 1.74 3c-.74.04-1.48.05-2.23.04h-.69l-.45-.01-2-.03v-3h3.63Zm8.58 0a5.6 5.6 0 0 1-3.63 2.56L8.6 11.5ZM1.5 5.2l1.9 3.3H1.5V5.2Zm3.83-3.74c4.62.01 9.17.7 9.17 6.54l-.01.5H6.86L2.83 1.49l.68-.02h.45l.7-.01h.22Z"/></svg>
+1
web/console/src/assets/workspace-icons/assembly.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333"><path d="M5.32 1.5h5.36c1 0 1.82.81 1.82 1.82v9.36c0 1-.81 1.82-1.82 1.82H5.32c-1 0-1.82-.81-1.82-1.82V3.32c0-1 .81-1.82 1.82-1.82Z"/><path stroke-linecap="round" d="M3.5 3.5h-2M3.5 6.5h-2M3.5 9.5h-2M3.5 12.5h-2M14.5 3.5h-2M14.5 6.5h-2M14.5 9.5h-2M14.5 12.5h-2"/></g></svg>
+1
web/console/src/assets/workspace-icons/astro.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="m10.56 1.61 2.94 8.89c-1-.52-2.4-1.3-3.5-1.5L8 3.5 6 9c-1.1.2-2.5.98-3.5 1.5l3.16-8.9c.12-.39.19-.59.3-.74.11-.13.25-.23.4-.29.18-.07.38-.07.8-.07h1.9c.42 0 .63 0 .8.07.16.06.3.16.4.3.12.14.18.34.3.74Z"/><path stroke="#5d5dff" d="M10.41 11.27c-.43.36-1.3.6-2.28.6-1.22 0-2.24-.37-2.51-.87-.1.29-.12.62-.12.83 0 0-.06 1.04.67 1.76 0-.37.3-.68.68-.68.65 0 .65.56.65 1.02v.04c0 .69.43 1.28 1.03 1.53-.1-.19-.14-.4-.14-.61 0-.66.4-.9.85-1.19.36-.23.76-.48 1.03-.98a1.84 1.84 0 0 0 .14-1.45h0Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/astro_config.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="M8.6 8.6a6.2 6.2 0 0 0-1.36-.45L5.44 3.2l-1.8 4.95c-1 .17-2.24.87-3.14 1.34l2.84-8a2 2 0 0 1 .27-.66c.1-.12.22-.21.36-.27.16-.06.34-.06.72-.06h1.7c.38 0 .57 0 .73.06.14.06.26.15.35.27.11.13.17.3.28.67l1.98 6"/><path stroke="#5d5dff" d="M6.56 11.14c-.3.07-.64.1-1 .1-1.1 0-2.01-.33-2.26-.78-.08.26-.1.55-.1.74 0 0-.06.94.6 1.58 0-.33.27-.6.61-.6.59 0 .58.5.58.9v.04c0 .62.39 1.16.93 1.38a1.24 1.24 0 0 1-.13-.55c0-.59.36-.81.76-1.07"/><path stroke="#6c7280" d="M11.5 13.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm1.75-4 1.75 3-1.75 3h-3.5L8 12.5l1.75-3h3.5Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/audio.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e45649"><circle cx="8" cy="8" r="6.5"/><path stroke-linecap="round" stroke-linejoin="round" d="M8.5 9.97a1.5 1.5 0 1 0 0 .03V4.5L11 6"/></g></svg>
+1
web/console/src/assets/workspace-icons/aurelia.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#cc297b" stroke-linecap="square" d="m14.16 4.46-2.12-2.12-9.2 9.2 2.12 2.12 9.2-9.2ZM3.55 8 2.14 6.59.72 8l1.42 1.41L3.55 8Zm4.24 5.66.71-.71.7.7-.7.71-.7-.7Z"/><path stroke="#5d5dff" stroke-linecap="square" d="m12.04 9.41 2.82 2.83-2.12 2.12-2.83-2.82 2.13-2.13ZM9.2 2.34 7.79.93l-.7.7L8.5 3.06l.7-.7ZM14.86 8l-.7-.7-.71.7.7.7.71-.7Z"/><path stroke="#e45649" stroke-linecap="square" d="m4.26 1.64 2.82 2.82L4.96 6.6 2.13 3.76z"/><path stroke="#cc297b" stroke-linejoin="round" d="m.37 5.53.7-.71M6.03 15.42l.7-.7"/></g></svg>
+1
web/console/src/assets/workspace-icons/auto.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="m4.5 5.5 2.79 3.21a1 1 0 1 0 1.42-1.42L4.5 5.5ZM1.5 8a6.5 6.5 0 1 0 13 0"/><path stroke="#e45649" d="M14.19 6a6.5 6.5 0 0 0-2.04-3"/><path stroke="#e0c240" d="M10.5 2a6.48 6.48 0 0 0-5 0"/><path stroke="#cc297b" d="M3.85 3A6.52 6.52 0 0 0 1.8 6"/></g></svg>
+1
web/console/src/assets/workspace-icons/autohotkey.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#50a14f"><rect width="13" height="13" x="1.5" y="1.5" rx="2"/><path stroke-linecap="round" d="M5.5 4.5v7m0-3 5-2m0-2v7"/></g></svg>
+1
web/console/src/assets/workspace-icons/autoit.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333"><rect width="13" height="13" x="1.5" y="1.5" rx="6.5"/><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 10.5 8 4l3.5 6.5h-4"/></g></svg>
+1
web/console/src/assets/workspace-icons/azure.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#00BFFF" stroke-linecap="round" stroke-linejoin="round" d="m8 7-.67 2M6 13l-.5 1.5h-4l4-12h5l4 12h-5l-4-4H9m-2.5-8 4 12"/></svg>
+1
web/console/src/assets/workspace-icons/azure_pipelines.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#00BFFF" stroke-linecap="round" stroke-linejoin="round" d="M1.5 12v2.5H4m2.5-9 2-4h6v6l-4 3v4h-4L6 14l1.5-1.5L6 11l-1.5 1.5-1-1L5 10 3.5 8.5 2 10l-.5-.5v-4h5Zm4 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"/></svg>
+1
web/console/src/assets/workspace-icons/babel.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e0c240" stroke-linecap="round" stroke-linejoin="round" d="M4.45 4.02c1.1-.68 2.32-1.53 3.63-1.95 1.16-.38 2.43-.73 3.62-.5.6.13 1.27.41 1.57.93.3.49.3 1.18.05 1.68-.97 1.88-5.26 3.6-5.26 3.6m1.2-5.92L3.5 14.5s6.14-2.13 8.23-4.43c.44-.48.96-1.17.75-1.78-.18-.53-.9-.75-1.45-.85-1.56-.26-4.67.92-4.67.92"/></svg>
+1
web/console/src/assets/workspace-icons/bash.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#50a14f" d="M2.88 14.5c-.65 0-1.38-.73-1.38-1.37V4.88c0-.65.73-1.38 1.37-1.38h8.26c.64 0 1.37.73 1.37 1.38v8.25c0 .64-.73 1.37-1.37 1.37H2.88ZM2 3.9c1.5-2.25 2-2.4 2.87-2.4h8.26c.64 0 1.37.73 1.37 1.38v8.25c0 .87-.25 1.47-2.5 2.97M5.5 5v1m0 6v1M7 7.75c0-.69-.54-1.25-1.2-1.25h-.6c-.66 0-1.2.56-1.2 1.25S4.54 9 5.2 9h.6c.66 0 1.2.56 1.2 1.25s-.54 1.25-1.2 1.25h-.6c-.66 0-1.2-.56-1.2-1.25"/></svg>
+1
web/console/src/assets/workspace-icons/bat.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round" d="m3.5 8.5 4 2-4 2m-.62 2c-.65 0-1.38-.73-1.38-1.37V4.88c0-.65.73-1.38 1.37-1.38h8.26c.64 0 1.37.73 1.37 1.38v8.25c0 .64-.73 1.37-1.37 1.37H2.88ZM2 3.9c1.5-2.25 2-2.4 2.87-2.4h8.26c.64 0 1.37.73 1.37 1.38v8.25c0 .87-.25 1.47-2.5 2.97"/></svg>
+1
web/console/src/assets/workspace-icons/bazel.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round" d="M8 15.5 15.5 8V4L12 .5l-4 4-4-4L.5 4v4L8 15.5ZM.5 4 8 11.5 15.5 4M8 15.5v-4m3.5.5V8L8 4.5 4.5 8v4"/></svg>
+1
web/console/src/assets/workspace-icons/bazel_ignore.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#6c7280" stroke-linecap="round" stroke-linejoin="round" d="M8 15.5 15.5 8V4L12 .5l-4 4-4-4L.5 4v4L8 15.5ZM.5 4 8 11.5 15.5 4M8 15.5v-4m3.5.5V8L8 4.5 4.5 8v4"/></svg>
+1
web/console/src/assets/workspace-icons/bicep.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#00BFFF" d="M12 9.5H3.5m0 4 8.5 1m0 0a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Zm-8.5-1a2 2 0 1 0 0-4 2 2 0 0 0 0 4Zm-.5-2h1m8 1a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm-6.5-3 3-5m-1-1L2.25 10M8.5.75 7.5 2v1.5l1 1H10l1.25-1.75"/></svg>
+1
web/console/src/assets/workspace-icons/binary.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333" stroke-linejoin="round"><rect width="13" height="13" x="1.5" y="1.5" stroke-linecap="round" rx="2"/><path stroke-linecap="round" d="M10.5 9.5h1v3.05"/><path d="M6 9.5h1c.28 0 .5.22.5.5v2a.5.5 0 0 1-.5.5H6a.5.5 0 0 1-.5-.5v-2c0-.28.22-.5.5-.5ZM10 3.5h1c.28 0 .5.22.5.5v2a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5V4c0-.28.22-.5.5-.5Z"/><path stroke-linecap="round" d="M5.5 3.5h1v3.05"/></g></svg>
+1
web/console/src/assets/workspace-icons/biome.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#00BFFF" stroke-linecap="round" stroke-linejoin="round" d="M.5 14.5h15L8 1.5 4.83 7C7 6.25 9 6.5 10 7.75l-1.25 4c-1.5-.33-3 .17-4.5 1.5l-1.25-1C3.83 10.75 5.25 10 7.25 10l.5-1.5c-5.25-.25-7 3.25-7.25 6Z"/></svg>
+1
web/console/src/assets/workspace-icons/bitbucket.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linecap="square" stroke-linejoin="round" d="m13.5 14.5 2-13H.5l2 13h11Zm1-9h-9l.5 5h4l.47-4.75"/></svg>
+1
web/console/src/assets/workspace-icons/bithound.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linejoin="round"><path stroke-linecap="square" d="M4.5 2.5c2-.5 2.25-2 3.5-2s1.5 1 1.5 2.5.25 2.5-1.5 2.5c-1.17 0-1.33-.67-.5-2-1.33 0-2.33-.33-3-1Z"/><path stroke-linecap="square" d="M11 1.5c1.67 2 2.5 4.33 2.5 7s-1 5-3 7c-.17-3.33-1.17-5-3-5s-2.83 1.67-3 5a5.5 5.5 0 0 1-1.75-4c0-2.25 1.25-4 4.75-4C9 7.5 12 7 11 1.5Z"/><path stroke-linecap="round" d="M3 9c-1-.67-1.5-2.17-1.5-4.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/blitz.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#5d5dff" stroke-linejoin="round" d="M12.46 7.5H10.1c-.9 0-1.74-.41-2.28-1.12L5.5 3.5 7 .5l5.46 7Zm-8.96 1h2.36c.9 0 1.74.41 2.28 1.12l2.32 2.88-1.5 3-5.46-7Z"/></svg>
+1
web/console/src/assets/workspace-icons/bower.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#00BFFF" stroke-linecap="round" d="M10.8 3.5c.13-.28.27-.53.45-.75.67-.83 1.75-1.25 3.25-1.25-.5 2.17-1 3.5-1.5 4s-1.33.83-2.5 1c-.03-.45-.05-.79-.05-1"/><circle cx="5.5" cy="4.5" r="1" stroke="#e0c240"/><path stroke="#e0c240" stroke-linecap="round" stroke-linejoin="round" d="M8.5 10c.67.17 1.17.08 1.5-.25.67.17 1.17-.08 1.5-.75.83-.17 1.17-.67 1-1.5"/><path stroke="#ca7f00" stroke-linecap="square" stroke-linejoin="round" d="M14.75 10c0 .67-.58 1-1.75 1-.33.33-.67.5-1 .5-.33 0-.67-.17-1-.5-.33.33-.67.58-1 .75-.33.17-.75.08-1.25-.25l.75 2c.17.5 0 .83-.5 1-.43.14-.75-.25-1.25-.25s-.56.65-.75.75c-.67.33-1.17.17-1.5-.5L5 13c-.33.33-.75.5-1.25.5-.55 0-2.03-1.2-2.79-3.5A9.54 9.54 0 0 1 .5 7C.5 2.75 4 1.5 6 1.5c1.33 0 2.5.67 3.5 2l2.5 1-3.5 2 7 1.5c.17 1-.08 1.67-.75 2Z"/><path stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round" d="m8.5 6.5 7 1.5c.17 1-.08 1.67-.75 2 0 .67-.58 1-1.75 1-.33.33-.67.5-1 .5-.33 0-.67-.17-1-.5-.33.33-.67.58-1 .75-.33.17-.75.08-1.25-.25"/><path stroke="#e0c240" stroke-linecap="round" stroke-linejoin="round" d="m8.75 11.5.75 2c.17.5 0 .83-.5 1-.43.14-.75-.25-1.25-.25s-.56.65-.75.75c-.67.33-1.17.17-1.5-.5L5 13c-.33.33-.75.5-1.25.5-.55 0-2.03-1.2-2.79-3.5"/><path stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round" d="M.96 10A9.54 9.54 0 0 1 .5 7C.5 2.75 4 1.5 6 1.5c1.33 0 2.5.67 3.5 2l2.5 1-3.5 2M1 10c1.33.33 2.33.17 3-.5 2.33.33 3.83-.67 4.5-3"/></g></svg>
+1
web/console/src/assets/workspace-icons/browserslist.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e0c240" stroke-linecap="round" stroke-linejoin="round"><path d="M12.49 7.72a4.23 4.23 0 0 1-1.5-4.56 5.42 5.42 0 0 0-3.3.81A4.34 4.34 0 0 1 7.28 2a5.47 5.47 0 0 0-2.25 2.6 4.3 4.3 0 0 1-1.5-1.4c-.49.97-.58 2.2-.36 3.37-.62.07-1.24 0-2.01-.28a5.5 5.5 0 0 0 1.66 3c-.47.42-1.01.72-1.82.94.75.8 1.87 1.3 3.05 1.5-.15.6-.43 1.15-.96 1.78a5.45 5.45 0 0 0 3.37-.53c.22.59.31 1.2.24 2.02 1.53-.66 3.03-2.61 3.28-4.91 1.27-.21 2.06-.64 2.7-.98"/><circle cx="13.5" cy="8.5" r="1"/><path d="M10 9.84c0-.6-.36-1.27-.45-1.89-.06-.4-.15-.8-.09-1.52 0-.66.62-2.1 1.16-2.99"/></g></svg>
+1
web/console/src/assets/workspace-icons/buck.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#3b8ad8" stroke-linecap="square" d="m3.5 14.5 4-3.5L5 8.5h10.5v2l-4 2-2 2M11.75 8.25 8.5 5.5v-3"/><path stroke="#3b8ad8" stroke-linecap="square" d="M12.5 2.5V5l-1.96 2M8 8 4.5 4.5v-3M.5 1.5V4l2.46 2.53H6"/><path fill="#3b8ad8" d="M9 10h1v1H9z"/></g></svg>
+1
web/console/src/assets/workspace-icons/buildkite.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round" d="m10.5 13.5 5-3v-5l-5-3-5 3-5-3V8l5 2.5 5-2.5v5.5Zm-5-3v-5l5-3V8l5-2.5"/></svg>
+1
web/console/src/assets/workspace-icons/bun.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#e66861" stroke-linecap="square" d="M.5 8.5c0 5.25 5.5 6 7.5 6s7.5-.75 7.5-6c0-4-4.5-6-7.5-7-3 1-7.5 3-7.5 7Z"/><path stroke="#e45649" stroke-linecap="round" stroke-linejoin="round" d="M6.5 10.5h3c-.33.67-.83 1-1.5 1s-1.17-.33-1.5-1Z"/><path stroke="#333333" d="M5 8.5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm6 0a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/bun_lock.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#e66861" stroke-linecap="round" d="M14.5 7c0-3.7-4.2-5.58-7-6.5-2.8.92-7 2.8-7 6.5 0 4.85 5.13 5.5 7 5.5"/><path stroke="#e45649" stroke-linecap="round" stroke-linejoin="round" d="M5.5 8.5h4c-.67.67-1.33 1-2 1-.67 0-1.33-.33-2-1Z"/><path stroke="#333333" d="M5 6.5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm5 0a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Z"/><path stroke="#6c7280" d="M15 11.5c.27 0 .5.22.5.5v3a.5.5 0 0 1-.5.5h-5a.5.5 0 0 1-.5-.5v-3c0-.28.22-.5.5-.5h5Zm-4 0V10a1.5 1.5 0 0 1 3 0v1.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/c.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#5d5dff" d="M12.64 12.48a6.43 6.43 0 0 1-7.05 1.57 6.5 6.5 0 0 1-.1-12.06 6.43 6.43 0 0 1 7.08 1.46"/><path stroke="#333333" d="M11 10.75a4.1 4.1 0 0 1-4.43.97A4 4 0 0 1 4 8.03 4 4 0 0 1 6.5 4.3a4.1 4.1 0 0 1 4.45.9"/></g></svg>
+1
web/console/src/assets/workspace-icons/cabal.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#5d5dff" stroke-linecap="square" d="M8.5 9.5h7l-4 5h-7z"/><path stroke="#3b8ad8" d="m3.5 9.5-1.76.93.33-1.97L.65 7.07l1.97-.28L3.5 5l.88 1.79 1.97.28-1.42 1.39.33 1.97z"/><path stroke="#008080" stroke-linecap="square" d="M8.5 6.5h7l-4-5h-7z"/></g></svg>
+1
web/console/src/assets/workspace-icons/caddy.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#50a14f" d="M7.5 11.5h3v-4h-5v2M9.5 7.5V6a1.5 1.5 0 0 0-3 0v1.5"/><path stroke="#3b8ad8" d="M14.05 5.76c.9 2.5.22 5.3-1.75 7.07A6.45 6.45 0 0 1 5.5 14M2.72 11.8A6.52 6.52 0 0 1 4 2.85a6.45 6.45 0 0 1 8.64.64"/><circle cx="13.5" cy="4.5" r="1" stroke="#3b8ad8"/><circle cx="4" cy="13" r="1.5" stroke="#3b8ad8"/><path stroke="#3b8ad8" d="m5.5 11.5 2-2M12.5 5.5l-1 1"/></g></svg>
+1
web/console/src/assets/workspace-icons/cakephp.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="square" d="M7.5 13.5v-2c-2.67 0-4.67-.42-6-1.25V12c0 .75 3 1.5 6 1.5ZM13 13c.75 0 1.5-.5 1.5-1v-1.5c-.67.33-1.17.5-1.5.5L8.5 9.5v2.25L13 13ZM1.5 7c0 .5 2 2 6 2V7l6 1.5c.5-.25 1-.75 1-1.25V5c0-.25-2.5-1.5-6.5-1.5S1.5 4.5 1.5 5v2Z"/></svg>
+1
web/console/src/assets/workspace-icons/capacitor.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#00BFFF" stroke-linecap="round" stroke-linejoin="round" d="m14.36 9.2-1.52 1.5c-.08.09-.14.16-.27.03-1.5-1.52-6.4-6.4-7.32-7.31-.09-.1-.08-.15 0-.24l1.62-1.6c.1-.1.16-.09.25 0L9.64 4.1c.07.07.13.16.25.17l2.68-2.69c.1-.1.17-.11.27 0l1.56 1.54c.14.13.13.2 0 .33l-2.52 2.5c-.17.17-.15.25.01.4l2.47 2.47c.16.15.15.23 0 .37ZM3.44 5.28c-.11-.11-.18-.11-.29 0-.5.52-1.02 1.03-1.53 1.53-.15.14-.13.22 0 .35L4.1 9.64c.17.16.16.25 0 .41l-2.5 2.49c-.12.12-.15.2 0 .33.51.5 1.02 1.02 1.52 1.54.12.12.19.12.3 0 .58-.59 2.4-2.4 2.75-2.7l2.7 2.7c.1.1.17.1.26 0l1.6-1.6c.1-.09.1-.14 0-.23L3.45 5.29Z"/></svg>
+1
web/console/src/assets/workspace-icons/cargo.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linejoin="round"><path stroke-linecap="round" d="M1.5 7 8 10.5 14.5 7M4.5 4.5l3.5 2L14.5 3M4.5 5.5v7M8 6.5V14"/><path stroke-linecap="round" d="m8 3 3.5 1.5v8"/><path stroke-linecap="square" d="M1.5 6.98V11L8 14.5l6.5-3.5V3L11 1.5l-6.5 3v1z"/></g></svg>
+1
web/console/src/assets/workspace-icons/cargo_lock.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#6c7280" d="M15 11.5c.27 0 .5.22.5.5v3a.5.5 0 0 1-.5.5h-5a.5.5 0 0 1-.5-.5v-3c0-.28.22-.5.5-.5h5Zm-4 0V10a1.5 1.5 0 0 1 3 0v1.5"/><path stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round" d="m.5 6 6 3 6-3.5M3.5 3.5l3 2 6-3.5M3.5 4v7M6.5 5.5v7"/><path stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round" d="m6.5 2.5 3 1.5v3"/><path stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round" d="M12.5 5.5V2L9.54.5l-6.04 3v1L.5 6v3.54l6 2.96"/></g></svg>
+1
web/console/src/assets/workspace-icons/certificate.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#ca7f00" d="M8 9a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/><path stroke="#e45649" d="M9.86 2.5h.89c.97 0 1.75.78 1.75 1.75v.9m0 3.72v.88c0 .97-.78 1.75-1.75 1.75m-5.5 0c-.97 0-1.75-.78-1.75-1.75v-.88m0-3.73v-.89c0-.97.78-1.75 1.75-1.75h.89"/><path stroke="#e45649" d="m12.5 5.14.63.62c.68.69.68 1.8 0 2.48l-.64.63M10.5 11.5V15L8 13l-2.5 2v-3.5m-2-2.64-.63-.62a1.75 1.75 0 0 1 0-2.48l.63-.62M6.13 2.5l.63-.63c.69-.68 1.8-.68 2.48 0l.62.63"/></g></svg>
+1
web/console/src/assets/workspace-icons/changelog.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#007ACC" stroke-linecap="round" stroke-linejoin="round"><path d="M2.71 10.96a6.5 6.5 0 1 0-.69-3.53M2 8l1.5-1.5M2 8 .5 6.5M8.5 8.5v-4M8.5 8.5h3"/></g></svg>
+1
web/console/src/assets/workspace-icons/chart.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linejoin="round"><path stroke-linecap="round" d="M3.5 11.5C4.48 12.62 6.52 13 8 13s3.52-.38 4.5-1.5M8 13v2.5m-4-3L3 14m9-1.5 1 1.5M3.5 4.5C4.48 3.38 6.52 3 8 3s3.52.38 4.5 1.5M8 3V.5m-4 3L3 2m9 1.5L13 2"/><path d="M14.5 10V6.5L13 8l-1.5-1.5V10m-3-4v3.5H10m-3-3H5.5v3H7M5.5 8H7M1.5 6v4m0-2.5h2m0-1.5v4"/></g></svg>
+1
web/console/src/assets/workspace-icons/chart_lock.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round" d="M2.5 9c.98 1.12 3.02 1.5 4.5 1.5.46 0 .98-.04 1.5-.12M7 10.5v2M3 10l-.75 1.5M2.5 4C3.48 2.88 5.52 2.5 7 2.5s3.52.38 4.5 1.5M7 2.5v-2M3 3l-.75-1.5M11 3l.75-1.5"/><path stroke="#3b8ad8" stroke-linejoin="round" d="M13.5 7.5v-2L12 7l-1.5-1.5V8m-3-3v2.5H9m-3-2H4.5v2H6M.5 5v3m0-1.5h2m0-1.5v3"/><path stroke="#6c7280" d="M15 11.5c.27 0 .5.22.5.5v3a.5.5 0 0 1-.5.5h-5a.5.5 0 0 1-.5-.5v-3c0-.28.22-.5.5-.5h5Zm-4 0V10a1.5 1.5 0 0 1 3 0v1.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/chromium.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#00BFFF" stroke-linecap="round" d="M8 5.5h6.25M5.83 9.25 2.71 3.84m7.46 5.41-3.13 5.41M8 10.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z"/><circle cx="8" cy="8" r="7" stroke="#3b8ad8"/></g></svg>
+1
web/console/src/assets/workspace-icons/circle_ci.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" d="M1.67 9.5a6.5 6.5 0 1 0 0-3H4.3a4 4 0 1 1 0 3H1.67Z"/><circle cx="8" cy="8" r="1" fill="#333333"/></g></svg>
+1
web/console/src/assets/workspace-icons/clojure.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round" d="M14.17 10.03A6.5 6.5 0 0 1 1.81 6.02"/><path stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round" d="M1.87 5.85A6.5 6.5 0 0 1 14.22 9.9"/><path stroke="#50a14f" d="M6.36 4.9a3.5 3.5 0 1 0 3.41 6.12"/><path stroke="#3b8ad8" d="M9.77 11.02a3.5 3.5 0 0 0-3.4-6.11"/><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M7.86 7.77s-1.52 2.2-1.36 3.38"/><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M1.85 5.9c.47-1.07 1.86-1.47 3.04-1.5 3.3-.06 2.62 5.68 5.2 6.77 1.2.52 3.45-.06 4.1-1.17"/></g></svg>
+1
web/console/src/assets/workspace-icons/cmake.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#50a14f" d="M5.91 11.49 2.39 14.5h10.82z"/><path stroke="#e45649" d="m8.97 1.5 5.53 11.04-4.75-1.95z"/><path stroke="#3b8ad8" d="m1.5 12.36 5.93-5.07-.5-5.79L1.5 12.36z"/></g></svg>
+1
web/console/src/assets/workspace-icons/cmake_in.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"><path d="M5.91 11.49 2.39 14.5h10.82zM8.97 1.5l5.53 11.04-4.75-1.95zM1.5 12.36l5.93-5.07-.5-5.79L1.5 12.36z"/></g></svg>
+1
web/console/src/assets/workspace-icons/cobol.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round" d="M6.74 2.24c.32-1.32 2.2-1.32 2.52 0a1.3 1.3 0 0 0 1.93.8c1.15-.7 2.48.62 1.77 1.77a1.3 1.3 0 0 0 .8 1.93c1.32.32 1.32 2.2 0 2.52a1.3 1.3 0 0 0-.8 1.93c.7 1.15-.62 2.48-1.77 1.77a1.3 1.3 0 0 0-1.93.8c-.32 1.32-2.2 1.32-2.52 0a1.3 1.3 0 0 0-1.93-.8c-1.15.7-2.48-.62-1.77-1.77a1.3 1.3 0 0 0-.8-1.93c-1.32-.32-1.32-2.2 0-2.52a1.3 1.3 0 0 0 .8-1.93c-.7-1.15.62-2.48 1.77-1.77a1.3 1.3 0 0 0 1.93-.8ZM10 6.5a2.5 2.5 0 1 0 0 3"/></svg>
+1
web/console/src/assets/workspace-icons/cocoapods.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linejoin="round"><path d="M6 13.5a5.5 5.5 0 1 1 5.3-7H8a2.5 2.5 0 1 0 0 3h3.3a5.5 5.5 0 0 1-5.3 4Z"/><path stroke-linecap="round" d="m12.5 2.5 3 5.5-3 5.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/cocoapods_lock.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#e45649" stroke-linejoin="round" d="M5.5.5a5 5 0 1 0 4.9 6H8.33a3 3 0 1 1 0-2h2.07a5 5 0 0 0-4.9-4Z"/><path stroke="#e45649" stroke-linecap="round" stroke-linejoin="round" d="m11.5.5 3.03 5.13L13.68 7"/><path stroke="#6c7280" d="M15 11.5c.27 0 .5.22.5.5v3a.5.5 0 0 1-.5.5h-5a.5.5 0 0 1-.5-.5v-3c0-.28.22-.5.5-.5h5Zm-4 0V10a1.5 1.5 0 0 1 3 0v1.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/code_climate.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"><path d="M5 7 .55 11.56l1.98 1.94 2.49-2.45L7.5 13.5l2-1.94zM11.5 4.5l-3 3 2 2 1-1 2 2 2-1.86z"/></g></svg>
+1
web/console/src/assets/workspace-icons/code_of_conduct.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="round" stroke-linejoin="round"><path d="M13.42 8.93 8.01 14.5 2.59 8.93a3.85 3.85 0 0 1-.93-3.8A3.55 3.55 0 0 1 8 4.01a3.55 3.55 0 0 1 6.34 1.14c.4 1.34.05 2.8-.92 3.79"/><path d="M8 4 5.64 6.49a.77.77 0 0 0 0 1.05l.4.41c.49.52 1.3.52 1.8 0l.72-.75a2.25 2.25 0 0 1 3.25 0l1.62 1.69m-5.05 2.25 1.44 1.5m.36-3.38 1.45 1.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/codecov.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="round" stroke-linejoin="round" d="M6.34 9.18a4.23 4.23 0 0 0-2.38-.73c-.91 0-1.76.25-2.46.74A6.6 6.6 0 0 1 8 2.5c3.59 0 6.5 3 6.5 6.69a4.3 4.3 0 0 0-6.03 1.19A4.57 4.57 0 0 0 8 14.5"/></svg>
+1
web/console/src/assets/workspace-icons/codeowners.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#ca7f00"><path stroke-linecap="round" stroke-linejoin="round" d="m8.01 1.5 5.73 1.77C15.25 11.15 9.8 14.5 7.98 14.5c-1.82 0-7.28-3.36-5.7-11.23L8.01 1.5Z"/><path d="M8.5 8.5v2l-1-2h1Z"/><circle cx="8" cy="7" r="1.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/coffeescript.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 7c-.5 2.5-2 5.5-3 6.5s-2 1-3 1-2 0-3.02-1C4.45 12.5 3 9.5 2.5 7c3 2 9 2 12 0ZM2.5 5c3 2 9 2 12 0"/><path d="M2.5 8.4c-1.73 1.6-1.26 4.17 2 4.1M6.99 2.14c-.75-.36-1.68.03-1.73.6-.04.58 1.06.63 1.64.6.59-.03 1.06-.49 1.58-.77.63-.34 1.26-.5 1.9-.4.52.09 1.44.19 1.38.78-.09.76-1.87.82-2.25.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/command.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#333333" d="M12.5 14.5a2 2 0 1 0 0-4h-9a2 2 0 1 0 2 2v-9a2 2 0 1 0-2 2h9a2 2 0 1 0-2-2v9c0 1.1.9 2 2 2Z"/></svg>
+1
web/console/src/assets/workspace-icons/commitlint.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#007ACC" d="M4.5 9.23c-.74.49-2.04.3-2.59-.45a2.21 2.21 0 0 1 0-2.58c.56-.74 1.85-.92 2.59-.42M5.5 4h1v4.4s-.14 1.12.93 1.1H8.5"/><path stroke="#e45649" d="M10.5 9.51h4M10.5 13.5l2-2 2 2"/></g></svg>
+1
web/console/src/assets/workspace-icons/contributing.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="M8.13 14.5H3.32c-.39 0-.82-.43-.82-.82V2.32c0-.39.43-.82.82-.82h9.37c.38 0 .81.43.81.82v6.86M5.5 5.5h5M5.5 9.5h3"/><path stroke="#50a14f" d="m9.5 12.5 2 2 4-4"/></g></svg>
+1
web/console/src/assets/workspace-icons/cpp.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#3b8ad8" d="M12.64 12.48a6.43 6.43 0 0 1-7.05 1.57 6.5 6.5 0 0 1-.1-12.06 6.43 6.43 0 0 1 7.08 1.46"/><path stroke="#333333" d="M6.5 7v3M5 8.5h3M12.5 7v3M11 8.5h3"/></g></svg>
+1
web/console/src/assets/workspace-icons/craco.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linejoin="round" d="M9.5 1.5v2.25c.52.2 1.01.47 1.43.83l.22-.13 1.73-1 1.5 2.6-1.95 1.13a4.67 4.67 0 0 1 0 1.65l.22.12 1.73 1-1.5 2.6-1.95-1.13c-.42.35-.9.64-1.44.83l.01 2.25h-3v-2.25a4.51 4.51 0 0 1-1.43-.84v.01l-1.95 1.13-1.5-2.6 1.95-1.13a4.67 4.67 0 0 1 0-1.65l-.22-.12-1.73-1 1.5-2.6 1.95 1.13c.42-.35.9-.64 1.44-.83L6.5 1.5h3Zm-4 5L7 8 5.5 9.5m3-3L10 8 8.5 9.5"/></svg>
+1
web/console/src/assets/workspace-icons/crystal.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="m.5 6 7.25-1.75-1.5 7.5L.5 6Zm0 0 1.75 7.75L10 15.5l5.5-5.5-1.75-7.75L6 .5.5 6Z"/></svg>
+1
web/console/src/assets/workspace-icons/csharp.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#50a14f" d="M12.64 12.48a6.43 6.43 0 0 1-7.05 1.57 6.5 6.5 0 0 1-.1-12.06 6.43 6.43 0 0 1 7.08 1.46"/><path stroke="#333333" d="M7.5 5 6 11M10.5 5 9 11M6 6.5h5M5.25 9.5h5"/></g></svg>
+1
web/console/src/assets/workspace-icons/css.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#3b8ad8" d="M1.5 1.5h13L13 13l-5 2-5-2z"/><path stroke="#333333" d="M5 4.5h6l-.5 6-2.5 1-2.5-1-.08-1M6.5 7.5h4"/></g></svg>
+1
web/console/src/assets/workspace-icons/css_map.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round" d="M10 3.5h5.5l-1.27 9.8L10 15l-4.23-1.7L5.6 12"/><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M9.46 5.5h3.08L12 11l-2 1-2-1-.05-.5M8.5 8.5h3.62"/><g><path d="M0 0h8v11H0z"/><path stroke="#e45649" stroke-linecap="round" stroke-linejoin="round" d="M.5 4.06c0 .77.24 1.52.7 2.13l2.24 3.96.04.08h0c.13.17.32.27.52.27s.36-.09.48-.23h0l.03-.03.08-.15 2.2-3.88c.46-.61.71-1.37.71-2.15A3.63 3.63 0 0 0 3.88.5C1.95.5.5 2.1.5 4.06Z"/><circle cx="4" cy="4" r="1.5" stroke="#e45649" stroke-linecap="round" stroke-linejoin="round"/></g></g></svg>
+1
web/console/src/assets/workspace-icons/csv.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#50a14f"><path d="M1.5 3.5c0-.54.48-1 1.08-1H6.5l1.54 1h5.38c.6 0 1.08.44 1.08.98l-.09 9.04c0 .54-.48.98-1.08.98H2.58c-.6 0-1.08-.44-1.08-.98V3.5Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M3.5 7.5v4m3-4v4m3-4v4m3-4v4m-9 0h9m-9-2h9m-9-2h9"/></g></svg>
+1
web/console/src/assets/workspace-icons/cucumber.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round"><path d="M14 7.28a7.37 7.37 0 0 1-7.49 7.22v-1.63h0a5.76 5.76 0 0 1-4.32-7.04A5.8 5.8 0 0 1 4.94 2.3c1.37-.78 3-1 4.54-.62A5.86 5.86 0 0 1 14 7.3h0ZM6.89 5.16l-.38-.75M9.14 9.66l.37.75M5.76 8.53l-.75.38M7.26 9.66l-.37.75M8.76 5.16l.38-.75M11.01 8.53l-.75-.37M5.76 6.66l-.75-.38"/></g></svg>
+1
web/console/src/assets/workspace-icons/cuda.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round"><path d="M13.46 9C9.5 12 3.5 13 .5 8c3.53-4.5 7-4.5 11-.5-.62.65-4.52 3.9-8.5.5 0 0 2.5-3 5.5-.5 0 0-1.05.59-1.5.75"/><path d="M4.5 3.61V2.5h11v11h-11v-1.11"/></g></svg>
+1
web/console/src/assets/workspace-icons/cypress.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round"><path d="M7.5 9.4a2.1 2.1 0 0 1-2.38 1.06A2.23 2.23 0 0 1 3.5 8.35c-.03-1 .58-1.91 1.5-2.22.9-.3 1.9.06 2.43.9M10.5 10l-2-4M8 14.5c.36-.05.52-.07.73-.21.12-.1.24-.28.3-.43L12.5 6"/><path d="M7.96 14.5a6.5 6.5 0 1 1 3.84-1.23"/></g></svg>
+1
web/console/src/assets/workspace-icons/d.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#bd2c00" d="M15 3.5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm-8-1c1.84 0 3.47.9 4.47 2.29a2 2 0 1 1 1.01 3.71 5.5 5.5 0 0 1-5.48 5H1.5v-11Zm-3.5 2v7H7a3.5 3.5 0 0 0 0-7H3.5Z"/></svg>
+1
web/console/src/assets/workspace-icons/dart.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#007ACC" stroke-linecap="round" stroke-linejoin="round"><path d="M7 14.5h4.5v-3h3V7L9.17 1.64c-.28-.29-.8-.47-1.17-.29L3.5 3.5 1.35 8c-.18.37 0 .88.3 1.17L7 14.5Z"/><path d="M3.5 11V3.5H11m-7.5 0 8 8"/></g></svg>
+1
web/console/src/assets/workspace-icons/dart_generated.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#6c7280" stroke-linecap="round" stroke-linejoin="round"><path d="M7 14.5h4.5v-3h3V7L9.17 1.64c-.28-.29-.8-.47-1.17-.29L3.5 3.5 1.35 8c-.18.37 0 .88.3 1.17L7 14.5Z"/><path d="M3.5 11V3.5H11m-7.5 0 8 8"/></g></svg>
+1
web/console/src/assets/workspace-icons/database.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e0c240" d="M8 6.5c3.59 0 6.5-1.4 6.5-2.68 0-1.28-2.91-2.32-6.5-2.32S1.5 2.54 1.5 3.82C1.5 5.1 4.41 6.5 8 6.5ZM14.5 8c0 .83-1.24 1.79-3.25 2.2-2.01.41-4.49.41-6.5 0S1.5 8.83 1.5 8m13 4.18c0 .83-1.24 1.6-3.25 2-2.01.42-4.49.42-6.5 0-2.01-.4-3.25-1.17-3.25-2m0-8.3v8.3m13-8.3v8.3"/></svg>
+1
web/console/src/assets/workspace-icons/deno.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333"><path stroke-linecap="round" stroke-linejoin="round" d="M1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0m7.67 5.8L8.11 9.56C6.2 9.49 4.5 8.38 4.5 7.03c0-1.4 1.62-2.53 3.61-2.53 2 0 2.89.72 3.61 2.17.02.03.5 1.6 1.45 4.7"/><path d="M8 6.5h1"/></g></svg>
+1
web/console/src/assets/workspace-icons/deno_lock.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#6c7280" d="M15 11.5c.27 0 .5.22.5.5v3a.5.5 0 0 1-.5.5h-5a.5.5 0 0 1-.5-.5v-3c0-.28.22-.5.5-.5h5Zm-4 0V10a1.5 1.5 0 0 1 3 0v1.5"/><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M12.5 6.5a6 6 0 1 0-5 5.92m.17-.92-.84-3.33C5.07 8.1 3.5 7.08 3.5 5.83 3.5 4.54 5 3.5 6.83 3.5c1.84 0 2.67.67 3.34 2l.62 2"/><path stroke="#333333" d="M7 5.5h1"/></g></svg>
+1
web/console/src/assets/workspace-icons/dependabot.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8"><path stroke-linecap="round" stroke-linejoin="round" d="M5.5 7.5v2"/><path d="M15.5 9V7"/><path stroke-linecap="round" stroke-linejoin="round" d="M10.5 7.5v2"/><path d="M.5 9V7"/><path stroke-linecap="round" stroke-linejoin="round" d="M8.5 3.5v-2H6.52M2.5 3.5h11a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/detox.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round"><path stroke="#00BFFF" d="M5.5 8.5h5M8 2.5C1.5 2.5.5 7 .5 10c0 2.16 2 3.5 7.5 3.5s7.5-1.25 7.5-3.5c0-3-1-7.5-7.5-7.5Z"/><path stroke="#333333" d="M5.5 6.5h5a2 2 0 1 1 0 4h-5a2 2 0 1 1 0-4Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/diff.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#6c7280" d="M4 1.5h8A2.5 2.5 0 0 1 14.5 4v8a2.5 2.5 0 0 1-2.5 2.5H4A2.5 2.5 0 0 1 1.5 12V4A2.5 2.5 0 0 1 4 1.5Z"/><path stroke="#e45649" d="M8.5 11.5h3"/><path stroke="#50a14f" d="M5.5 3.5v4m-2-2h4"/><path stroke="#6c7280" d="m5.5 10.5 5-5"/></g></svg>
+1
web/console/src/assets/workspace-icons/disc.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="M1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0"/><path stroke="#6c7280" d="M7.5 8a.5.5 0 1 0 1 0 .5.5 0 0 0-1 0"/><path stroke="#00BFFF" d="M8 4.5A3.5 3.5 0 0 0 4.5 8"/><path stroke="#3b8ad8" d="M8 11.5A3.5 3.5 0 0 0 11.5 8"/></g></svg>
+1
web/console/src/assets/workspace-icons/django.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round"><path d="M12.5 6.5v4.37c0 1.64-.12 2.43-.5 3.1a2.8 2.8 0 0 1-1.5 1.53l-2-1c.94-.43 1.3-.7 1.6-1.28.3-.59.4-1.27.4-3.06V6.5h2ZM12.5 4.5h-2v-2h2zM8.5 12.21c-1.14.2-1.97.29-2.88.29-2.7 0-4.12-1.16-4.12-3.38 0-2.14 1.72-3.53 4.04-3.53.36 0 .63.03.96.11V2.5h2v9.71Z"/><path d="M3.5 9.08c0 1.12.84 1.47 1.98 1.47.24 0 .7-.01 1.02-.05v-3c-.26-.08-.73-.1-1-.1-1.13 0-2 .53-2 1.68Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/doc.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 3.13c0-.77.86-1.63 1.62-1.63h9.76c.76 0 1.62.86 1.62 1.63v9.75c0 .76-.86 1.62-1.62 1.62H4.13c-.77 0-1.63-.86-1.63-1.62"/><path d="m.5 5.5 1 5 1-5 1 5 .97-5M7.5 6.5h4M7.5 9.5h4"/></g></svg>
+1
web/console/src/assets/workspace-icons/docker.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8"><path stroke-linecap="round" stroke-linejoin="round" d="M.5 8.5H11l.75-.5a5.35 5.35 0 0 1 0-3.5c1 .6 1 1.88 1.74 2 .77-.09 1.23.01 2 .52 0 0-.97 1.77-2.5 1.98-1.93 3.65-4.5 5.5-6.98 5.5C0 14.5.5 8.5.5 8.5Z"/><path d="M1.5 9V6m-.5.5h9M3.5 9V4m-.5.5h5m-3-2h3M5.5 9V2m2 7V2m2 7V6"/></g></svg>
+1
web/console/src/assets/workspace-icons/docker_compose.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#008080"><path stroke-linecap="round" stroke-linejoin="round" d="M.5 8.5H11l.75-.5a5.35 5.35 0 0 1 0-3.5c1 .6 1 1.88 1.74 2 .77-.09 1.23.01 2 .52 0 0-.97 1.77-2.5 1.98-1.93 3.65-4.5 5.5-6.98 5.5C0 14.5.5 8.5.5 8.5Z"/><path d="M1.5 9V6m-.5.5h9M3.5 9V4m-.5.5h5m-3-2h3M5.5 9V2m2 7V2m2 7V6"/></g></svg>
+1
web/console/src/assets/workspace-icons/docker_ignore.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#6c7280"><path stroke-linecap="round" stroke-linejoin="round" d="M.5 8.5H11l.75-.5a5.35 5.35 0 0 1 0-3.5c1 .6 1 1.88 1.74 2 .77-.09 1.23.01 2 .52 0 0-.97 1.77-2.5 1.98-1.93 3.65-4.5 5.5-6.98 5.5C0 14.5.5 8.5.5 8.5Z"/><path d="M1.5 9V6m-.5.5h9M3.5 9V4m-.5.5h5m-3-2h3M5.5 9V2m2 7V2m2 7V6"/></g></svg>
+1
web/console/src/assets/workspace-icons/docusaurus.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round" d="M14.5 4.5v-1a1 1 0 0 0-1-1h-10a1 1 0 0 0-1 1v9c-1 0-1.67-.33-2-1 0 3 .67 3 2 3h12a1 1 0 0 0 0-2V7M5.25 2.25 4.5 1.5l-1 1-1-1v1h-1l1 1-1 1 1 1-1 1 1 1-1 1 .75.75M15 9.5a.5.5 0 1 0 0-1H9.5V9a.5.5 0 0 1-.5.5h-.5v3h1a1 1 0 0 1 0 2"/><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="m9.5 6.5 1-1 1 1 1-1 1 1 1-1m-8 0a1 1 0 1 0-2 0"/><path stroke="#e0c240" d="M10 10.5h3m0 2h-2"/></g></svg>
+1
web/console/src/assets/workspace-icons/dotjs.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#00BFFF" d="M4.25 14.5c2.63 1.51 6.44-.16 8.51-3.75 2.07-3.59 1.62-7.73-1.01-9.25C9.12 0 5.31 1.66 3.24 5.25c-2.07 3.59-1.62 7.73 1.01 9.25Zm10.25-2.75c1.51-2.63-.16-6.44-3.75-8.51C7.16 1.17 3.02 1.62 1.5 4.25c-1.51 2.63.16 6.44 3.75 8.51 3.59 2.07 7.73 1.62 9.25-1.01Z"/></svg>
+1
web/console/src/assets/workspace-icons/drawio.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#ca7f00" d="M2.25 10.5h2.5c.41 0 .75.34.75.75v2.5c0 .41-.34.75-.75.75h-2.5a.75.75 0 0 1-.75-.75v-2.5c0-.41.34-.75.75-.75Zm9 0h2.5c.41 0 .75.34.75.75v2.5c0 .41-.34.75-.75.75h-2.5a.75.75 0 0 1-.75-.75v-2.5c0-.41.34-.75.75-.75Zm-5-9h3.5c.41 0 .75.34.75.75v2.5c0 .41-.34.75-.75.75h-3.5a.75.75 0 0 1-.75-.75v-2.5c0-.41.34-.75.75-.75Zm-2.75 9 4-5m5 5-4-5"/></svg>
+1
web/console/src/assets/workspace-icons/editorconfig.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e66861" stroke-linecap="round" stroke-linejoin="round" d="M3.86 6.46c.23.08.46.15.66.28a2 2 0 0 1 .66.6c.1.15.17.35.15.53a.8.8 0 0 1-.29.53c-.2.16-.5.14-.74.15a3.9 3.9 0 0 1-1.5-.32 3.48 3.48 0 0 1-.64-.31c-.18-.12-.38-.25-.5-.45a.81.81 0 0 1-.11-.5.8.8 0 0 1 .26-.48c.17-.16.43-.2.66-.23a4.31 4.31 0 0 1 1.4.2h-.01Zm4.05 1.7c.23.05.47.1.7.17.24.08.5.15.72.3.17.13.38.27.45.48.05.15.02.65-.05.8a.97.97 0 0 1-.37.39 1.4 1.4 0 0 1-.6.2c-.18.03-.37-.03-.56-.05-.25-.04-.5-.07-.73-.13-.27-.07-.56-.13-.8-.28-.17-.1-.32-.24-.43-.4a.87.87 0 0 1-.11-.33 2.82 2.82 0 0 1 0-.74c.03-.1.1-.2.19-.27.1-.1.23-.18.37-.21.19-.06.39-.03.58-.02.22.02.43.06.64.1ZM3.59 6.04s.3-.61.52-.85a4.1 4.1 0 0 1 3.17-.97 4.25 4.25 0 0 1 2 .7c.24.18.47.4.67.64a5.37 5.37 0 0 1 1.04 2.02c.08.34.12.7.1 1.06a5.3 5.3 0 0 1-.56 1.86c-.11.21-.25.4-.39.6a7.26 7.26 0 0 1-2.29 2.17c-.5.3-1.04.53-1.58.73-.53.2-1.06.4-1.62.47-.4.05-.81.06-1.2-.02a2.6 2.6 0 0 1-.98-.43 2.68 2.68 0 0 1-.8-1.08 2.85 2.85 0 0 1-.17-1.04c0-.16 0-.32.02-.47.02-.2.03-.4.07-.6a9.13 9.13 0 0 1 .51-1.5c.13-.28.44-.84.44-.84m2.49-4c-.02-.19-.06-.37-.06-.56 0-.22.02-.45.07-.66.04-.13.1-.25.16-.37.08-.15.17-.3.28-.42.12-.15.27-.26.42-.38.12-.1.23-.2.37-.27a2.6 2.6 0 0 1 1.02-.32c.13 0 .26 0 .39.02a1.08 1.08 0 0 1 .62.41c.09.12.17.25.2.4.02.09.01.2 0 .3-.02.19-.06.38-.13.57-.04.1-.1.21-.16.31-.12.22-.27.42-.4.62m3.07 2.45s.33-.5.55-.67c.2-.17.43-.3.68-.37.36-.09.75-.1 1.1-.01a1.66 1.66 0 0 1 1.2 1.22 1.88 1.88 0 0 1-.11 1.42c-.19.33-.5.57-.79.8a3 3 0 0 1-1.6.72c-.23.04-.71.02-.71.02"/></svg>
+1
web/console/src/assets/workspace-icons/ejs.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linecap="square" d="M.5 8 4 2.5h2.5L3 8l3.5 5.5H4L.5 8Zm9 5.5 5-11m-4.5 3a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm4 8a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"/></svg>
+1
web/console/src/assets/workspace-icons/elixir.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round" d="M8.03 14.5C5 14.5 3.5 12.49 3.5 10.01c0-2.82 2.25-7.02 4.62-8.48a.24.24 0 0 1 .24 0c.08.04.12.12.11.2-.13 1.25.22 2.5.98 3.54.3.43.63.8 1.02 1.27.54.66.94 1.03 1.52 2.08l.01.02c.33.56.5 1.2.5 1.84 0 2.03-1.69 4.02-4.47 4.02Z"/></svg>
+1
web/console/src/assets/workspace-icons/elm.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round"><rect width="13" height="13" x="1.5" y="1.5" rx="1.5"/><path d="m2 2 12 12M8.5 1.5l6 6M11 11l3.5-3.5M4.5 4.5h6.25M2 14l9-9"/></g></svg>
+1
web/console/src/assets/workspace-icons/ember.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round" d="M1.5 9.25c6.11 1.18 8.03-.92 8.79-1.7 1.53-1.57 0-4.71-1.91-3.93-1.91.79-4.59 5.5-2.3 8.63 1.53 2.1 4.34 1.41 8.42-1.75"/></svg>
+1
web/console/src/assets/workspace-icons/env.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e0c240" d="M5.5 8.5V12m0-6.5V4m0 4.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm5 3.5v-1.5m0-3V4m0 6.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM4 1.5h8A2.5 2.5 0 0 1 14.5 4v8a2.5 2.5 0 0 1-2.5 2.5H4A2.5 2.5 0 0 1 1.5 12V4A2.5 2.5 0 0 1 4 1.5Z"/></svg>
+1
web/console/src/assets/workspace-icons/epub.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round" d="M14.54 8a3 3 0 0 1-.88 2.12l-3.54 3.54a3 3 0 0 1-4.24 0l-3.54-3.54a3 3 0 0 1 0-4.24l3.54-3.54a3 3 0 0 1 4.24 0l1.77 1.77L8 8"/></svg>
+1
web/console/src/assets/workspace-icons/erlang.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="round" stroke-linejoin="round"><path d="M6 5.5c0-1.25 1-2 2-2s2 .75 2 2H6Z"/><path d="M13.5 13c.47-.57 1.12-1.24 1.5-2l-2.25-1.25c-.74 1.36-1.76 2.75-3.25 2.75-2.1 0-3-2.3-3-5h8.07c.02-.3-.06-.35-.07-.5a6.78 6.78 0 0 0-1-4M3 13c-1.08-1.3-1.5-3-1.5-5S2.1 4.24 3 3"/></g></svg>
+1
web/console/src/assets/workspace-icons/esbuild.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e0c240" d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14Zm.5-10.5h2L13 8l-2.5 3.5h-2L11 8 8.5 4.5ZM4 4.5h2L8.5 8 6 11.5H4L6.5 8 4 4.5Z"/></svg>
+1
web/console/src/assets/workspace-icons/eslint.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#5d5dff" stroke-linejoin="round" d="M4.25 1.5h7.5L15.5 8l-3.75 6.5h-7.5L.5 8z"/><path stroke="#00BFFF" d="m8 4.07 3.5 1.97v3.92L8 11.93 4.5 9.96V6.04L8 4.07Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/eslint_ignore.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#6c7280"><path d="M11.71 1.5 15.42 8l-3.71 6.5H4.29L.58 8l3.71-6.5h7.42Z"/><path d="m8 4.07 3.5 1.97v3.92L8 11.93 4.5 9.96V6.04L8 4.07Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/exe.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><rect width="13" height="12" x="1.5" y="2.5" stroke="#333333" rx="1.5"/><path stroke="#e0c240" d="M3 4.5h1"/><path stroke="#50a14f" d="M5 4.5h1"/><path stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round" d="M7.44 6.94c.14-.59.98-.59 1.12 0a.57.57 0 0 0 .86.35c.51-.3 1.1.28.79.8a.57.57 0 0 0 .35.85c.59.14.59.98 0 1.12a.57.57 0 0 0-.35.86c.3.51-.28 1.1-.8.79a.57.57 0 0 0-.85.35c-.14.59-.98.59-1.12 0a.57.57 0 0 0-.86-.35c-.51.3-1.1-.28-.79-.8a.57.57 0 0 0-.35-.85c-.59-.14-.59-.98 0-1.12a.57.57 0 0 0 .35-.86c-.3-.51.28-1.1.8-.79.33.2.76.03.85-.35Z"/><path stroke="var(undefined)" d="M7.5 9.5h1"/></g></svg>
+1
web/console/src/assets/workspace-icons/fastlane.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#007ACC" stroke-linecap="round" stroke-linejoin="round" d="M4.5 13.5 3.05 8.42M2.99 4.56a2 2 0 1 0 .06 3.86L4.5 13.5"/><path stroke="#e45649" stroke-linecap="round" stroke-linejoin="round" d="m2.5 6.5 3.88-2.82A2 2 0 1 1 10 2.3"/><path stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round" d="M14.3 8.33a2 2 0 1 0-2.43-3L8.01 2.5"/><path stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round" d="M10.06 14.89a2 2 0 1 0 2-3.3l1.44-5.09"/><path stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round" d="M11.5 13.5h-5a2 2 0 1 1-3.73-1"/><circle cx="11.5" cy="13.5" r="1" fill="#5d5dff"/><circle cx="4.5" cy="13.5" r="1" fill="#007ACC"/><circle cx="2.5" cy="6.5" r="1" fill="#e45649"/><circle cx="8" cy="2.5" r="1" fill="#3b8ad8"/><circle cx="13.5" cy="6.5" r="1" fill="#50a14f"/></g></svg>
+1
web/console/src/assets/workspace-icons/favicon.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e0c240" stroke-linecap="round" stroke-linejoin="round" d="m3.67 14.57.83-4.82L1 6.34l4.84-.71L8 1.25l2.16 4.38 4.84.71-3.5 3.41.83 4.81L8 12.29z"/></svg>
+1
web/console/src/assets/workspace-icons/figma.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#50a14f" d="M7.5 11.5h-2a2 2 0 1 0 2 2v-2Z"/><path stroke="#5d5dff" d="M7.5 10.5v-4h-2a2 2 0 1 0 0 4h2Z"/><path stroke="#e45649" d="M7.5 5.5v-4h-2a2 2 0 1 0 0 4h2Z"/><path stroke="#ca7f00" d="M10.5 5.5a2 2 0 1 0 0-4h-2v4h2Z"/><circle cx="10.5" cy="8.5" r="2" stroke="#00BFFF"/></g></svg>
+1
web/console/src/assets/workspace-icons/file.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#333333" stroke-linejoin="round" d="M13.5 6.5v6a2 2 0 0 1-2 2h-7a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h4.01m-.01 0 5 5h-4a1 1 0 0 1-1-1v-4Z"/></svg>
+1
web/console/src/assets/workspace-icons/firebase.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e0c240" stroke-linecap="round" stroke-linejoin="round" d="m3.5 12.5 9-8 1 9-5 2-5-3Zm0 0 5-9 1.9 2.78M3.5 12.5l1-11 3.1 3.1"/></svg>
+14
web/console/src/assets/workspace-icons/fish.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 300 300"> 2 + <path 3 + d="m 223,125 c 3,1 3,1 3,1 z" 4 + fill="#50a14f" 5 + id="path10" /> 6 + <path 7 + d="m 230,126 c 2,1 2,1 2,1 z" 8 + fill="#50a14f" 9 + id="path12" /> 10 + <path 11 + style="fill:#50a14f;fill-opacity:1;stroke:#50a14f;stroke-width:0.239697;stroke-opacity:1" 12 + d="m 171.50336,225.792 c -8.20235,-0.50357 -15.58189,-1.66735 -23.49033,-3.70451 -11.61079,-2.99087 -21.6688,-6.84499 -33.79731,-12.95076 -17.415153,-8.76721 -32.473716,-19.07221 -54.769001,-37.48 -1.778755,-1.46861 -3.342779,-2.66949 -3.47561,-2.66862 -0.494991,0.003 -7.109223,5.98694 -11.633752,10.52475 -2.574398,2.58194 -5.963111,6.19103 -7.530474,8.02018 -3.245355,3.78741 -4.983771,5.19814 -7.287091,5.91347 -2.10395,0.65341 -5.066486,0.85376 -7.060605,0.4775 -3.230202,-0.6095 -6.643696,-2.88935 -8.26778,-5.522 -1.180228,-1.91316 -1.670614,-3.33882 -2.006212,-5.83251 -0.334629,-2.48649 -0.04742,-3.84343 1.486965,-7.02533 2.034499,-4.21899 3.84141,-6.2993 14.198326,-16.34661 3.978695,-3.85976 7.125307,-7.51471 7.125307,-8.27641 0,-0.62051 -1.253733,-2.80717 -2.766828,-4.8257 -0.691761,-0.92283 -3.063809,-3.56549 -5.271219,-5.87258 -12.810662,-13.38913 -14.089755,-15.02035 -14.749749,-18.81032 -0.819989,-4.70872 0.715481,-8.95697 4.609375,-12.75293 2.113582,-2.06043 3.592192,-2.56888 7.511895,-2.58314 4.571007,-0.0166 6.651747,0.84063 10.978448,4.52308 0.632974,0.53872 3.11384,3.13636 5.513035,5.77254 6.282442,6.90298 10.939737,11.79626 12.746517,13.39239 l 1.573318,1.38988 1.373146,-0.77301 c 2.338857,-1.31665 3.933484,-2.48765 6.414134,-4.71014 8.170904,-7.32056 25.190986,-19.93431 35.58769,-26.374377 7.389195,-4.577115 16.089805,-9.197823 23.321375,-12.385488 11.03362,-4.863601 24.58765,-8.952113 35.84716,-10.813135 6.94614,-1.148088 10.41324,-1.407454 18.73306,-1.401374 6.53262,0.0048 8.50309,0.09151 12.20661,0.537283 18.37151,2.211305 34.51152,8.04196 49.56844,17.906826 7.02011,4.599381 13.29685,9.790695 21.13336,17.478825 7.60308,7.45912 11.43689,11.90075 21.5176,24.92899 7.02234,9.07562 7.16493,9.34429 7.12459,13.42305 -0.0327,3.30674 -0.40037,4.58342 -2.59117,8.99817 -3.38201,6.81513 -8.62671,13.53338 -18.6171,23.84782 -2.55783,2.64078 -6.29516,6.53543 -8.30519,8.65479 -13.50241,14.23673 -29.42818,23.90233 -50.8765,30.87775 -7.29174,2.37142 -11.73981,3.32684 -18.93608,4.06737 -4.43834,0.45672 -12.81566,0.63967 -17.13835,0.37428 z m 12.8238,-26.23474 c 11.8987,-1.26891 20.32474,-3.66401 30.082,-8.55079 6.42167,-3.2162 11.01111,-6.1103 16.11956,-10.16501 3.54547,-2.81413 11.28038,-9.69935 14.56168,-12.96206 4.3749,-4.35011 12.64988,-13.90456 13.56782,-15.66568 1.31767,-2.52801 -0.42965,-5.42704 -8.24491,-13.67932 -2.44012,-2.57658 -5.51968,-5.9925 -6.84345,-7.59094 -7.71859,-9.32004 -16.13886,-15.42587 -32.69538,-23.7085 -5.09778,-2.55024 -7.05983,-3.40365 -9.32271,-4.05498 -3.97224,-1.14333 -7.01226,-1.73037 -10.51701,-2.03089 -1.64576,-0.14111 -4.55632,-0.44083 -6.46791,-0.66604 -4.82209,-0.568095 -15.87387,-0.552591 -20.25441,0.0284 -18.43522,2.44514 -35.93552,9.60857 -56.32885,23.05721 -8.877337,5.85428 -17.68726,12.51863 -29.403041,22.24224 l -3.755444,3.11687 0.589082,1.33202 c 2.567862,5.80638 10.502794,13.71577 20.464699,20.39882 8.241974,5.52921 19.631914,11.7207 31.680794,17.22146 13.57782,6.19878 27.79702,10.41074 38.91004,11.5258 1.45016,0.14551 2.96026,0.30173 3.35576,0.34716 2.09185,0.24028 11.61869,0.11166 14.50168,-0.19579 z" 13 + id="path1754" /> 14 + </svg>
+1
web/console/src/assets/workspace-icons/flow.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e0c240" stroke-linecap="square" stroke-linejoin="round" d="m6.5 14.5-4-4 2.5.06L2.5 6.5h2l-2-4h4v12Zm7-11v3l-7-5v2h7Zm0 9v-4h-2v-2h-2v3l4 3Zm-.5 2-2.5-2h-1v2H13Z"/></svg>
+1
web/console/src/assets/workspace-icons/folder.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v6c0 .83-.67 1.5-1.5 1.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/></svg>
+1
web/console/src/assets/workspace-icons/folder__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24l-1.75 7a1 1 0 0 1-.97.76H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/></svg>
+1
web/console/src/assets/workspace-icons/folder_api.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#e0c240" d="M12 13.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm-3-4a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm6 0a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm0 6a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm-6 0a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm4-4.5 1.5-1.5M13 13l1.5 1.5m-5 0L11 13M9.5 9.5 11 11"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_api__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#e0c240" d="M12 13.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm-3-4a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm6 0a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm0 6a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm-6 0a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm4-4.5 1.5-1.5M13 13l1.5 1.5m-5 0L11 13M9.5 9.5 11 11"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_app.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#6c7280" d="M11.5 7v1.5"/><path stroke="#6c7280" stroke-linecap="round" stroke-linejoin="round" d="m12 10.5 2.5 5m-3.5-5-2.5 5M7.8 11a4 4 0 0 0 5.49 2.08"/><circle cx="11.5" cy="9.5" r="1" stroke="#3b8ad8"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_app__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#6c7280" d="M11.5 7v1.5"/><path stroke="#6c7280" stroke-linecap="round" stroke-linejoin="round" d="m12 10.5 2.5 5m-3.5-5-2.5 5M7.8 11a4 4 0 0 0 5.49 2.08"/><circle cx="11.5" cy="9.5" r="1" stroke="#3b8ad8"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_client.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round"><path stroke="#333333" stroke-linejoin="round" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#3b8ad8" d="M15.5 15.5h-7m.8-7h5.4c.44 0 .8.36.8.8v3.4a.8.8 0 0 1-.8.8H9.3a.8.8 0 0 1-.8-.8V9.3c0-.44.36-.8.8-.8Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_client__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round"><path stroke="#333333" stroke-linejoin="round" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#3b8ad8" d="M15.5 15.5h-7m.8-7h5.4c.44 0 .8.36.8.8v3.4a.8.8 0 0 1-.8.8H9.3a.8.8 0 0 1-.8-.8V9.3c0-.44.36-.8.8-.8Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_command.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#6c7280" d="M14.75 13.5c.41 0 .75.34.75.75v.5c0 .41-.34.75-.75.75h-.5a.75.75 0 0 1-.75-.75v-5.5c0-.41.34-.75.75-.75h.5c.41 0 .75.34.75.75v.5c0 .41-.34.75-.75.75h-5.5a.75.75 0 0 1-.75-.75v-.5c0-.41.34-.75.75-.75h.5c.41 0 .75.34.75.75v5.5c0 .41-.34.75-.75.75h-.5a.75.75 0 0 1-.75-.75v-.5c0-.41.34-.75.75-.75h5.5Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_command__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#6c7280" d="M14.75 13.5c.41 0 .75.34.75.75v.5c0 .41-.34.75-.75.75h-.5a.75.75 0 0 1-.75-.75v-5.5c0-.41.34-.75.75-.75h.5c.41 0 .75.34.75.75v.5c0 .41-.34.75-.75.75h-5.5a.75.75 0 0 1-.75-.75v-.5c0-.41.34-.75.75-.75h.5c.41 0 .75.34.75.75v5.5c0 .41-.34.75-.75.75h-.5a.75.75 0 0 1-.75-.75v-.5c0-.41.34-.75.75-.75h5.5Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_components.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#e0c240" stroke-linecap="square" d="m9.5 9.5 4 4m-4 0 4-4m-1.65-1.89 3.54 3.54c.2.2.2.5 0 .7l-3.54 3.54a.5.5 0 0 1-.7 0L7.6 11.85a.5.5 0 0 1 0-.7l3.54-3.54c.2-.2.5-.2.7 0Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_components__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#e0c240" stroke-linecap="square" d="m9.5 9.5 4 4m-4 0 4-4m-1.65-1.89 3.54 3.54c.2.2.2.5 0 .7l-3.54 3.54a.5.5 0 0 1-.7 0L7.6 11.85a.5.5 0 0 1 0-.7l3.54-3.54c.2-.2.5-.2.7 0Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_config.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#6c7280" d="M11.5 13.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm1.75-4 1.75 3-1.75 3h-3.5L8 12.5l1.75-3h3.5Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_config__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#6c7280" d="M11.5 13.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm1.75-4 1.75 3-1.75 3h-3.5L8 12.5l1.75-3h3.5Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_coverage.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#50a14f" d="M12 7.5 8.5 8.67v2.62a4.3 4.3 0 0 0 3.5 4.38c2.36-.58 3.5-2.26 3.5-4.38V8.67L12 7.5Zm-1.5 4L12 13l1.5-2.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_coverage__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#50a14f" d="M12 7.5 8.5 8.67v2.62a4.3 4.3 0 0 0 3.5 4.38c2.36-.58 3.5-2.26 3.5-4.38V8.67L12 7.5Zm-1.5 4L12 13l1.5-2.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_dist.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#e45649" d="M9.5 10.5h5a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1Zm1-2h3v2h-3v-2Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_dist__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#e45649" d="M9.5 10.5h5a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1Zm1-2h3v2h-3v-2Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_docs.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><g stroke="#3b8ad8"><path d="M8.5 14.5v-5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v5m-6-1h6V15a.5.5 0 0 1-.5.5H9.5a1 1 0 0 1 0-2Z"/><path d="M12.5 9v1.5l.5-.5.5.5V9"/></g></g></svg>
+1
web/console/src/assets/workspace-icons/folder_docs__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><g stroke="#3b8ad8"><path d="M8.5 14.5v-5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v5m-6-1h6V15a.5.5 0 0 1-.5.5H9.5a1 1 0 0 1 0-2Z"/><path d="M12.5 9v1.5l.5-.5.5.5V9"/></g></g></svg>
+1
web/console/src/assets/workspace-icons/folder_examples.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><g stroke="#e0c240"><path d="M9.5 14.5h4m-3 1h2m-3-2h4"/><circle cx="11.5" cy="10.5" r="3"/></g></g></svg>
+1
web/console/src/assets/workspace-icons/folder_examples__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><g stroke="#e0c240"><path d="M9.5 14.5h4m-3 1h2m-3-2h4"/><circle cx="11.5" cy="10.5" r="3"/></g></g></svg>
+1
web/console/src/assets/workspace-icons/folder_images.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round" d="M14.5 15.5 11 12l-2.5 2.5"/><path stroke="#007ACC" stroke-linejoin="round" d="M9.5 8.5h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1Z"/><path stroke="#e0c240" d="M14 10.5h-1"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_images__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round" d="M14.5 15.5 11 12l-2.5 2.5"/><path stroke="#007ACC" stroke-linejoin="round" d="M9.5 8.5h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1Z"/><path stroke="#e0c240" d="M14 10.5h-1"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_library.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#6c7280" d="M8.7 8.5h1.6c.11 0 .2.09.2.2v6.6a.2.2 0 0 1-.2.2H8.7a.2.2 0 0 1-.2-.2V8.7c0-.11.09-.2.2-.2Zm2.3 2h1c.28 0 .5.22.5.5v4a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-4c0-.28.22-.5.5-.5Zm4.5 5.5-2-6"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_library__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#6c7280" d="M8.7 8.5h1.6c.11 0 .2.09.2.2v6.6a.2.2 0 0 1-.2.2H8.7a.2.2 0 0 1-.2-.2V8.7c0-.11.09-.2.2-.2Zm2.3 2h1c.28 0 .5.22.5.5v4a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-4c0-.28.22-.5.5-.5Zm4.5 5.5-2-6"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_node.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#50a14f" d="m12.5 8.58 3 1.71v3.42l-3 1.71-3-1.71v-3.42l3-1.71Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_node__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#50a14f" d="m12.5 8.58 3 1.71v3.42l-3 1.71-3-1.71v-3.42l3-1.71Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_packages.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#ca7f00" d="M12 15.5v-4L8.5 9.25M12 11.5l3.5-2.25M12 7.5l3.5 1.75v4.5L12 15.67l-3.5-1.92v-4.5L12 7.5Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_packages__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#ca7f00" d="M12 15.5v-4L8.5 9.25M12 11.5l3.5-2.25M12 7.5l3.5 1.75v4.5L12 15.67l-3.5-1.92v-4.5L12 7.5Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_public.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#007ACC" d="M12 15.5c-1.93 0-3.5-1.5-3.5-3.5s1.57-3.5 3.5-3.5c2 0 3.5 1.5 3.5 3.5S14 15.5 12 15.5ZM11.5 9c-1.5 2-1.5 4 .14 6.34m.9-6.34c1.5 2 1.5 4-.15 6.34"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_public__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#007ACC" d="M12 15.5c-1.93 0-3.5-1.5-3.5-3.5s1.57-3.5 3.5-3.5c2 0 3.5 1.5 3.5 3.5S14 15.5 12 15.5ZM11.5 9c-1.5 2-1.5 4 .14 6.34m.9-6.34c1.5 2 1.5 4-.15 6.34"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_resource.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#e0c240" d="M11.5 11a1.5 1.5 0 0 0-3 0v.5h3V11Z"/><path stroke="#50a14f" d="M11 12.5a1.5 1.5 0 0 0 0 3h.5v-3H11Z"/><path stroke="#3b8ad8" d="M12.5 13a1.5 1.5 0 0 0 3 0v-.5h-3v.5Z"/><path stroke="#e45649" d="M13 11.5a1.5 1.5 0 0 0 0-3h-.5v3h.5Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_resource__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#e0c240" d="M11.5 11a1.5 1.5 0 0 0-3 0v.5h3V11Z"/><path stroke="#50a14f" d="M11 12.5a1.5 1.5 0 0 0 0 3h.5v-3H11Z"/><path stroke="#3b8ad8" d="M12.5 13a1.5 1.5 0 0 0 3 0v-.5h-3v.5Z"/><path stroke="#e45649" d="M13 11.5a1.5 1.5 0 0 0 0-3h-.5v3h.5Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_routes.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#333333" stroke-linecap="round" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#50a14f" d="M11.5 11.5v4m0-7.5v1.5m-1.5 6h3m-4.5-6v2h5.75l1.25-1-1.25-1H8.5Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_routes__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#333333" stroke-linecap="round" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#50a14f" d="M11.5 11.5v4m0-7.5v1.5m-1.5 6h3m-4.5-6v2h5.75l1.25-1-1.25-1H8.5Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_scripts.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#5d5dff" d="M8.5 13.5V9.75C8.5 9 9 8.5 9.75 8.5h3.75v5.75c0 .75-.5 1.25-1.25 1.25H8.5a1 1 0 0 1 0-2h3a1 1 0 0 0 0 2m2-5h1a1 1 0 0 0 0-2h-1"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_scripts__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#5d5dff" d="M8.5 13.5V9.75C8.5 9 9 8.5 9.75 8.5h3.75v5.75c0 .75-.5 1.25-1.25 1.25H8.5a1 1 0 0 1 0-2h3a1 1 0 0 0 0 2m2-5h1a1 1 0 0 0 0-2h-1"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_server.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#333333" stroke-linecap="round" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#e0c240" d="M8.5 9.5h7v2h-7v-2Zm2 0v2m-2 2h7v2h-7v-2Zm2 0v2"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_server__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#333333" stroke-linecap="round" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#e0c240" d="M8.5 9.5h7v2h-7v-2Zm2 0v2m-2 2h7v2h-7v-2Zm2 0v2"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_src.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#50a14f" d="m10.5 8.5-3 3.5 3 3.5m2-7 3 3.5-3 3.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_src__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#50a14f" d="m10.5 8.5-3 3.5 3 3.5m2-7 3 3.5-3 3.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_styles.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#3b8ad8" d="M8.5 15.5v-1.56a1.56 1.56 0 1 1 1.56 1.56H8.5m7-7a6.22 6.22 0 0 0-4.98 3.97M15.5 8.5a6.22 6.22 0 0 1-3.97 4.98m-.07-2.65a3.5 3.5 0 0 1 1.7 1.71"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_styles__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#3b8ad8" d="M8.5 15.5v-1.56a1.56 1.56 0 1 1 1.56 1.56H8.5m7-7a6.22 6.22 0 0 0-4.98 3.97M15.5 8.5a6.22 6.22 0 0 1-3.97 4.98m-.07-2.65a3.5 3.5 0 0 1 1.7 1.71"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_temp.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#008080" d="M11.5 15.5a4 4 0 1 0 0-8 4 4 0 0 0 0 8Zm0-6.5v2.5h2"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_temp__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#008080" d="M11.5 15.5a4 4 0 1 0 0-8 4 4 0 0 0 0 8Zm0-6.5v2.5h2"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_tests.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#333333" stroke-linecap="round" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#008080" d="m13.16 9.16-4.27 4.25a1.2 1.2 0 0 0-.07 1.7c.4.4 1.08.64 1.74-.03 1.9-1.88 3.33-3.3 4.27-4.25M12 8l4 4m-3.98 1.5H8.96"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_tests__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#333333" stroke-linecap="round" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#008080" d="m13.16 9.16-4.27 4.25a1.2 1.2 0 0 0-.07 1.7c.4.4 1.08.64 1.74-.03 1.9-1.88 3.33-3.3 4.27-4.25M12 8l4 4m-3.98 1.5H8.96"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_views.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v.5m-7 7H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#ca7f00" d="M14.7 8.5H9.3a.3.3 0 0 0-.3.34l.47 5.24c.01.12.1.23.22.27L12 15.5l2.32-1.15a.3.3 0 0 0 .22-.27L15 8.84a.3.3 0 0 0-.3-.34Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/folder_views__open.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="m1.87 8 .7-2.74a1 1 0 0 1 .96-.76h10.94a1 1 0 0 1 .97 1.24L15.12 7M6.5 13.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1"/><path stroke="#ca7f00" d="M14.7 8.5H9.3a.3.3 0 0 0-.3.34l.47 5.24c.01.12.1.23.22.27L12 15.5l2.32-1.15a.3.3 0 0 0 .22-.27L15 8.84a.3.3 0 0 0-.3-.34Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/font.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"><path d="m7 5 4 8.5h2.5L8 2.5l-4.5 11M2.5 13.5h2M9.5 13.5h5M5.5 9.5H9"/></g></svg>
+1
web/console/src/assets/workspace-icons/forth.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#333333" stroke-linejoin="round" d="M2.5 2.5h4v4h-4Zm7 0h4v4h-4Zm-7 7h4v4h-4Zm8.25 6c.75-.5.75-1 .75-2h-2v-4h4v4c0 1-.5 2-2.75 2Z"/></svg>
+1
web/console/src/assets/workspace-icons/fortran.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#5d5dff" stroke-linecap="square" stroke-linejoin="round" d="M7.5 14.5v-1l-1-1v-3h2l1 2h1v-6h-1l-1 2h-2v-4h5l1 3h1v-5h-11v1l1 1v9l-1 1.25v.75z"/></svg>
+1
web/console/src/assets/workspace-icons/fsharp.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round"><path d="m1 8 5.5-6v2.93L3.57 8l2.93 2.8v2.93zM15 8 9.5 2v2.93L12.25 8 9.5 10.79v2.93z"/></g></svg>
+1
web/console/src/assets/workspace-icons/gatsby.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round"><path d="M11.32 4.79a4.64 4.64 0 0 0-7.75 1.86l5.82 5.82a4.65 4.65 0 0 0 3.23-3.97H9.39M3.36 8.97l3.71 3.71"/><path d="M8 14.5a6.5 6.5 0 1 1 0-13 6.5 6.5 0 0 1 0 13Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/gcp.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round"><path stroke="#e45649" d="M2.12 7.28A6 6 0 0 1 14 8.12"/><path stroke="#e0c240" stroke-linejoin="round" d="M7.62 8a4 4 0 1 0-3.12 6.5"/><path stroke="#50a14f" stroke-linejoin="round" d="M4.5 14.5H12"/><path stroke="#3b8ad8" stroke-linejoin="round" d="M12 14.5a3.5 3.5 0 0 0 1.99-6.38"/></g></svg>
+1
web/console/src/assets/workspace-icons/git.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><circle cx="7.5" cy="10.5" r="1" stroke="#333333"/><circle cx="7.5" cy="4.5" r="1" stroke="#333333"/><circle cx="10.5" cy="7.5" r="1" stroke="#333333"/><path stroke="#333333" stroke-linecap="square" d="M7.5 5.5v4m-1-6-1-1m4 4-1-1"/><path stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round" d="m9.06 1.06 5.88 5.88a1.5 1.5 0 0 1 0 2.12l-5.88 5.88a1.5 1.5 0 0 1-2.12 0L1.06 9.06a1.5 1.5 0 0 1 0-2.12l5.88-5.88a1.5 1.5 0 0 1 2.12 0Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/gitbook.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#00BFFF" stroke-linecap="square" stroke-linejoin="round" d="m7.5 12 6-3m-12-2.5 5 3 8-4-6-3-7.5 4c-.33.33-.5.75-.5 1.25 0 .75 0 1 .5 1.5L5.5 12m9-2.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm-8 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"/></svg>
+1
web/console/src/assets/workspace-icons/gitlab.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linecap="square" stroke-linejoin="round" d="M8 14.49 14.5 10 12 2l-1.5 4.5h-5L4 2l-2.5 8z"/></svg>
+1
web/console/src/assets/workspace-icons/gitpod.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round" d="M8.7 3.27 4.68 5.79a.38.38 0 0 0-.18.33v3.96c0 .14.07.26.18.33l3.14 2c.11.06.25.06.36 0l3.14-2c.11-.07.18-.2.18-.33V7.5L8.7 9.4c-.33.19-.72.23-1.08.13-.36-.1-.67-.36-.85-.7a1.52 1.52 0 0 1 .54-2.02l4.27-2.54c.61-.35 1.35-.35 1.96.02.6.37.96 1.04.96 1.77v4.32c0 1.01-.51 1.95-1.35 2.45l-3.82 2.3c-.82.5-1.84.5-2.66 0l-3.82-2.3a2.85 2.85 0 0 1-1.35-2.45V5.82c0-1.01.51-1.95 1.35-2.45L7.3.7c.33-.2.71-.25 1.07-.15.36.1.67.36.85.7.39.7.16 1.61-.52 2.02Z"/></svg>
+1
web/console/src/assets/workspace-icons/gleam.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#cc297b" stroke-linecap="round" d="m7.5 6.5 1-5m-3 6-5-4m6.75 6.25L1.5 14.5m4-5 2.5 5m.5-6 7-1"/></svg>
+1
web/console/src/assets/workspace-icons/go.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#007ACC" stroke-linecap="round" stroke-linejoin="round"><path d="m15.48 8.06-4.85.48m4.85-.48a4.98 4.98 0 0 1-4.54 5.42 5 5 0 1 1 2.95-8.66l-1.7 1.84a2.5 2.5 0 0 0-4.18 2.06c.05.57.3 1.1.69 1.51.25.27 1 .83 1.78.82.8-.02 1.58-.25 2.07-.81 0 0 .8-.96.68-1.88M2.5 8.5l-2 .01M2 10.51h1.5M1.5 6.52l2-.02"/></g></svg>
+1
web/console/src/assets/workspace-icons/go_mod.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#007ACC"><path stroke-linecap="round" stroke-linejoin="round" d="m2.5 8.51-2 .01M2 10.52h1.5M1.5 6.52h1"/><path d="M9.23 4.33a1.5 1.5 0 1 0-2.16.58"/><path stroke-linecap="round" stroke-linejoin="round" d="m9.23 4.33 2.78-.74.75 2.78M13.33 8.53l.75 2.79-7.73 2.07-2.07-7.73 2.78-.75"/><path d="M13.34 8.54a1.5 1.5 0 1 0-.58-2.17"/></g></svg>
+1
web/console/src/assets/workspace-icons/godot.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#00BFFF" stroke-linecap="round" d="m1.5 11 2.5.5.25 1h2l.5-1h2.5l.5 1h2l.25-1 2.5-.5m-13 1.25c0 2.25 2.74 3.25 6.5 3.25 3.75 0 6.5-1 6.5-3.25v-5L15.75 6 14.5 4 13 5.25 11.5 4l.5-1.5-2.5-1L8.75 3h-1.5L6.5 1.5 4 2.5 4.5 4 3 5.25 1.5 4 .25 6 1.5 7.25v5ZM8 7.5v2m-3.5 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm7 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"/></svg>
+1
web/console/src/assets/workspace-icons/godot_assets.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linecap="round" d="m1.5 11 2.5.5.25 1h2l.5-1h2.5l.5 1h2l.25-1 2.5-.5m-13 1.25c0 2.25 2.74 3.25 6.5 3.25 3.75 0 6.5-1 6.5-3.25v-5L15.75 6 14.5 4 13 5.25 11.5 4l.5-1.5-2.5-1L8.75 3h-1.5L6.5 1.5 4 2.5 4.5 4 3 5.25 1.5 4 .25 6 1.5 7.25v5ZM8 7.5v2m-3.5 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm7 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"/></svg>
+1
web/console/src/assets/workspace-icons/gradle.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#007ACC" stroke-linecap="round" stroke-linejoin="round"><path d="m7.5 7.5-1.97.78c-.2.15-.74.33-1.2-.2a7.26 7.26 0 0 1-1-1.63"/><path d="M11.43 2.98c.54-.48 4.07-1.39 4.07 2.1 0 2.34-1.6 3.53-2.76 4.5-.75.61-1.35 1.28-1.35 3.92H9.88c0-1.29-2.66-2.32-3.17 0H5.38c0-1.29-2.65-2.32-3.16 0H.7C.29 11.92.22 8.3 3.33 6.45c-.15-.25-.27-1.02.43-1.3.87-.34 3.77-1.34 6.51.12 2.74 1.46 5.07.93 5.2.55"/></g></svg>
+1
web/console/src/assets/workspace-icons/graphql.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e45649"><circle cx="8" cy="1.5" r="1"/><circle cx="2.5" cy="4.5" r="1"/><circle cx="2.5" cy="11.5" r="1"/><circle cx="13.5" cy="11.5" r="1"/><circle cx="8" cy="14.5" r="1"/><circle cx="13.5" cy="4.5" r="1"/><path stroke-linecap="square" d="M2.5 5.5v5M13.5 5.5v5M3.5 11.5h9M6.5 14l-3-1.5M9.5 14l3-1.5M3.5 10.5l4-8M12.5 10.5l-4-8M3.5 3.5l3-1.5M9.5 2l3 1.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/gridsome.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linejoin="round" d="M14.5 7.5V8A6.5 6.5 0 1 1 8 1.5h.5v2H8a4.5 4.5 0 1 0 4.47 5H11v-1h3.5ZM8 8.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z"/></svg>
+1
web/console/src/assets/workspace-icons/groovy.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#007ACC" stroke-linecap="round" stroke-linejoin="round"><path d="M11.68 5.38c.4.19.54.68 1.53 3.25 1 2.57-.92 4.07-.92 4.07s-6.73 2.47-6.73 1.63c-.18-.92-1.92-2.08-1.92-2.08s-.52-.63.06-.75c5.89-1.27 6.96-.61 7.3-2"/><path d="M7.38 10.63C2.62 10.88 2.48 8.08 2.5 8 3.6 4.6 9.24.91 10.8 1.58 14.07 3.04 9.2 8.96 7 8.5c-4.02-.83 1.5-4 1.5-4"/></g></svg>
+1
web/console/src/assets/workspace-icons/gulp.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="round" stroke-linejoin="round" d="m13 4-1 7.5-1 1-.5 3h-5l-.5-3-1-1-.93-7L3 4m5 1.5c2.76 0 5-.67 5-1.5s-2.24-1.5-5-1.5S3 3.17 3 4s2.24 1.5 5 1.5ZM4 11c1.78.33 3.11.5 4 .5.89 0 2.22-.17 4-.5M9.5 4l1-2.5 2-1"/></svg>
+1
web/console/src/assets/workspace-icons/h.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="M5.5 7.5h5M5.5 4.5v7M10.5 4.5v7"/><path stroke="#5d5dff" d="M1.5 11.5v-7L8 .5l6.5 4v7l-6.5 4z"/></g></svg>
+1
web/console/src/assets/workspace-icons/haml.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" d="M7.25 9.25s2.15 1.22 2.98 1.38c.83.17 1.55.8 2.57-.26 1.1-1.15 1.2-1.27.29-1.93-.91-.66-2.59-1.94-2.59-2.69"/><path stroke="#e66861" stroke-linejoin="round" stroke-width=".91" d="M6.84 5.25a.8.8 0 0 0 1.24.29l3.84-3.1 1.1 1.35-3.45 2.8a6.9 6.9 0 0 0-2.37 3.76l-1.05 4.42-1.7-.4L5.49 10a6.9 6.9 0 0 0-.47-4.51l-1.77-3.8L4.84.95l2 4.3Z"/><path stroke="#333333" stroke-linejoin="round" d="m12.17 4.48-1.1-1.36 1.1 1.36Zm-6.9-2.62-1.6.74 1.6-.74Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/handlebars.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round"><path d="M8 6.15a1.73 1.73 0 0 0-2.48-.22c-.97.82-1.78 2.5-3.31 2.5-.39-.05-.71-.3-.83-.66a.97.97 0 0 1 .24-1.02C.45 6.75.1 8.67 1.03 9.56a4.76 4.76 0 0 0 4.7.56C6.63 9.76 7.4 9.2 8 8.45v-2.3ZM8 6.15a1.73 1.73 0 0 1 2.48-.22c.96.82 1.78 2.5 3.3 2.5.4-.05.71-.3.84-.66a.97.97 0 0 0-.24-1.02c1.17 0 1.52 1.92.58 2.81a4.76 4.76 0 0 1-4.7.56A5.34 5.34 0 0 1 8 8.45v-2.3Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/haskell.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round" d="M12.5 4.5h3m-1.5 3h1.5m-10 6 2.5-5-2.5-5H8l5.6 10h-2.53l-1.52-2.92L8 13.5H5.5Zm-5 0 2.5-5-2.5-5H3l2.5 5-2.5 5H.5Z"/></svg>
+1
web/console/src/assets/workspace-icons/hcl.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#333333" stroke-linecap="square" d="M2.5 11.5v-7l4-3m-1 5v7m0-5h5m0 2v-8m-1 12 4-3v-7"/></svg>
+1
web/console/src/assets/workspace-icons/helm.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333" stroke-linejoin="round"><path stroke-linecap="round" d="M3.5 11.5C4.48 12.62 6.52 13 8 13s3.52-.38 4.5-1.5M8 13v2.5m-4-3L3 14m9-1.5 1 1.5M3.5 4.5C4.48 3.38 6.52 3 8 3s3.52.38 4.5 1.5M8 3V.5m-4 3L3 2m9 1.5L13 2"/><path d="M14.5 10V6.5L13 8l-1.5-1.5V10m-3-4v3.5H10m-3-3H5.5v3H7M5.5 8H7M1.5 6v4m0-2.5h2m0-1.5v4"/></g></svg>
+1
web/console/src/assets/workspace-icons/heroku.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round"><rect width="13" height="13" x="1.5" y="1.5" rx="2"/><path d="M5.53 3.58 5.5 8.5s2-2 5-1v5M5.5 10.5v2M10.5 3.5c0 1 0 1.49-1 2"/></g></svg>
+1
web/console/src/assets/workspace-icons/histoire.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linejoin="round"><path stroke-linecap="round" d="m5 4.5 2.5 3 3-2 1.5 6-2.5-3-3 2z"/><path d="m2.5 1.5 11-1 1 14-11 1z"/></g></svg>
+1
web/console/src/assets/workspace-icons/hjson.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#50a14f" d="m2.67 11.69.26.97c.13.48.74.83 1.22.7l3.38-.9c1.45-.4 1.76.74 1.97 1.54-.21-.8-.52-1.93.93-2.32l3.38-.9c.49-.14.84-.75.7-1.23l-.25-.97"/><path stroke="#333333" d="M8.5 10.5 7.34 6.15"/><path stroke="#333333" stroke-linejoin="round" d="M9 4.5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm-3.5 0-2-1.25 2 .25v1Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/hpp.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="M4.5 8.5h3M4.5 5.5v6M7.5 5.5v6M10.5 5.5v2m-1-1h2M10.5 9.5v2m-1-1h2"/><path stroke="#3b8ad8" d="M1.5 11.5v-7L8 .5l6.5 4v7l-6.5 4z"/></g></svg>
+1
web/console/src/assets/workspace-icons/htaccess.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round"><path stroke="#e0c240" d="M9.5 10.5H8m3.5-2h-2"/><path stroke="#e45649" stroke-linejoin="round" d="M6.5 14.5a19.43 19.43 0 0 1 1.19-3.35c.15-.33.32-.65.5-.97 2-3.55 4.82-5.24 6.31-5.68-.58 2.7-2.85 8.05-7.38 8"/><path stroke="#333333" stroke-linejoin="round" d="M13.5 10v2.5a2 2 0 0 1-2 2H8m-3 0h-.5a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h4.01m.99 5a1 1 0 0 1-1-1v-4l3 3"/></g></svg>
+1
web/console/src/assets/workspace-icons/html.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#ca7f00" d="M1.5 1.5h13L13 13l-5 2-5-2z"/><path stroke="#333333" d="M11 4.5H5l.25 3h5.5l-.25 3-2.5 1-2.5-1-.08-1"/></g></svg>
+1
web/console/src/assets/workspace-icons/http.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8"><circle cx="8" cy="8" r="6.5"/><path stroke-linecap="round" stroke-linejoin="round" d="M8 1.5c1.67 2 2.5 4.17 2.5 6.5s-.83 4.5-2.5 6.5M8 1.5A9.96 9.96 0 0 0 5.5 8c0 2.33.83 4.5 2.5 6.5"/><path stroke-linecap="square" d="M2.5 10.5h11M2.5 5.5h11"/></g></svg>
+1
web/console/src/assets/workspace-icons/husky.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333"><path d="m9.63 3.54.48-.84c.94-1.68-1.51-3.13-2.46-1.46l-.47.84c-.94 1.68 1.5 3.14 2.45 1.46ZM13.72 5.96l.47-.84c.95-1.68-1.5-3.13-2.45-1.45l-.47.84c-.95 1.67 1.5 3.13 2.45 1.45ZM14.05 11.19l.24-.42c.94-1.68-1.51-3.13-2.46-1.45l-.23.42c-.95 1.67 1.5 3.13 2.45 1.45ZM5.3 5.44C6.22 3.76 3.77 2.31 2.83 4l-.24.42c-.94 1.68 1.51 3.13 2.45 1.45l.24-.42ZM2.57 9.02l4.06-1.56c1.25-.48 2.6.33 2.83 1.68l.71 4.38c.23 1.41-1.25 2.46-2.45 1.75a1.2 1.2 0 0 1-.4-.38l-.2-.33a5 5 0 0 0-3.9-2.3l-.38-.02a1.16 1.16 0 0 1-.51-.16 1.71 1.71 0 0 1 .24-3.06Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/i18n.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round"><path stroke="#e0c240" stroke-linejoin="round" d="m15 15-3.5-7.5L8 15m1.5-2.5h4"/><path stroke="#3b8ad8" d="M7.5 11c-2-1.5-3-2.5-4-5M1 13.5c4.67-3.67 7-7 7-10m-7 0h9m-4.5 0V2"/></g></svg>
+1
web/console/src/assets/workspace-icons/icon.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e0c240"><rect width="13" height="13" x="1.5" y="1.5" rx="2"/><path stroke-linejoin="round" d="m8 10.5-2.35 1.24.45-2.62-1.9-1.86 2.62-.38L8 4.5l1.18 2.38 2.62.38-1.9 1.86.45 2.62z"/></g></svg>
+1
web/console/src/assets/workspace-icons/image.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><circle cx="10" cy="6" r="1.5" stroke="#e0c240"/><path stroke="#50a14f" d="M7.5 13.5 11 10c.5-.5 1.5-.5 2 0l1.5 1.5"/><path stroke="#50a14f" d="m1.5 9.5 2-2C4 7 5 7 5.5 7.5l4 4"/><path stroke="#007ACC" stroke-linejoin="round" d="M3 2.5h10c.83 0 1.5.67 1.5 1.5v8c0 .83-.67 1.5-1.5 1.5H3A1.5 1.5 0 0 1 1.5 12V4c0-.83.67-1.5 1.5-1.5Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/ionic.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round" d="M12.6 3.4A6.5 6.5 0 1 0 14 5.5"/><circle cx="8" cy="8" r="2.5" stroke="#3b8ad8"/><circle cx="12.1" cy="2.9" r="1.5" fill="#3b8ad8"/></g></svg>
+1
web/console/src/assets/workspace-icons/java.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="round" stroke-linejoin="round"><path d="M11.5 10c0 1.15-.35 2.42-1.16 3.23a4.4 4.4 0 0 1-3.09 1.27h-1.5a4.14 4.14 0 0 1-2.98-1.27A4.73 4.73 0 0 1 1.5 10V7.5h10V10ZM11.5 7.5h1c1 0 2 .5 2 1.5s-1 1.5-2 1.5h-1M3.5 5.5c.07-1.33.6-2 1.56-2 .96 0 1.44-.67 1.44-2M7.5 5.5c.07-1.33.6-2 1.56-2 .96 0 1.44-.67 1.44-2"/></g></svg>
+1
web/console/src/assets/workspace-icons/java_class.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round"><path d="M11.5 10c0 1.15-.35 2.42-1.16 3.23a4.4 4.4 0 0 1-3.09 1.27h-1.5a4.14 4.14 0 0 1-2.98-1.27A4.73 4.73 0 0 1 1.5 10V7.5h10V10ZM11.5 7.5h1c1 0 2 .5 2 1.5s-1 1.5-2 1.5h-1M3.5 5.5c.07-1.33.6-2 1.56-2 .96 0 1.44-.67 1.44-2M7.5 5.5c.07-1.33.6-2 1.56-2 .96 0 1.44-.67 1.44-2"/></g></svg>
+1
web/console/src/assets/workspace-icons/java_jar.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#6c7280" stroke-linecap="round" stroke-linejoin="round"><path d="M11.5 10c0 1.15-.35 2.42-1.16 3.23a4.4 4.4 0 0 1-3.09 1.27h-1.5a4.14 4.14 0 0 1-2.98-1.27A4.73 4.73 0 0 1 1.5 10V7.5h10V10ZM11.5 7.5h1c1 0 2 .5 2 1.5s-1 1.5-2 1.5h-1M3.5 5.5c.07-1.33.6-2 1.56-2 .96 0 1.44-.67 1.44-2M7.5 5.5c.07-1.33.6-2 1.56-2 .96 0 1.44-.67 1.44-2"/></g></svg>
+1
web/console/src/assets/workspace-icons/javascript.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e0c240"><path d="M4.5 11a1.5 1.5 0 0 0 3 0V7"/><path stroke-linecap="round" stroke-linejoin="round" d="M12.5 8.75c0-.69-.54-1.25-1.2-1.25h-.6c-.66 0-1.2.56-1.2 1.25S10.04 10 10.7 10h.6c.66 0 1.2.56 1.2 1.25s-.54 1.25-1.2 1.25h-.6c-.66 0-1.2-.56-1.2-1.25"/><rect width="13" height="13" x="1.5" y="1.5" rx="2.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/javascript_config.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#6c7280" stroke-linecap="round" d="M11.5 13.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm1.75-4 1.75 3-1.75 3h-3.5L8 12.5l1.75-3h3.5Z"/><path stroke="#e0c240" stroke-linecap="round" d="M6.5 11.5h-4a2 2 0 0 1-2-2v-7c0-1.1.9-2 2-2h7a2 2 0 0 1 2 2V7h0"/><g stroke="#e0c240"><path stroke-linecap="round" d="M9.5 5c-.33-.33-.83-.5-1.5-.5-1 0-1.5.5-1.5 1s.5 1 1.5 1 1.5.5 1.5 1-.5 1-1.5 1c-.67 0-1.17-.17-1.5-.5"/><path d="M4.5 4v3.5a1 1 0 1 1-2 0V7"/></g></g></svg>
+1
web/console/src/assets/workspace-icons/javascript_map.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path d="M0 0h8v11H0z"/><path stroke="#e45649" stroke-linecap="round" stroke-linejoin="round" d="M.5 4.06c0 .77.24 1.52.7 2.13l2.24 3.96.04.08h0c.13.17.32.27.52.27s.36-.09.48-.23h0l.03-.03.08-.15 2.2-3.88c.46-.61.71-1.37.71-2.15A3.63 3.63 0 0 0 3.88.5C1.95.5.5 2.1.5 4.06Z"/><circle cx="4" cy="4" r="1.5" stroke="#e45649" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#e0c240" stroke-linecap="round" stroke-linejoin="round" d="M10 4.5h3.5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2h-7a2 2 0 0 1-2-2V13"/><g stroke="#e0c240" stroke-linejoin="round"><path stroke-linecap="round" d="M13.5 9c-.33-.33-.83-.5-1.5-.5-1 0-1.5.5-1.5 1s.5 1 1.5 1 1.5.5 1.5 1-.5 1-1.5 1c-.67 0-1.17-.17-1.5-.5"/><path d="M8.5 8v3.5a1 1 0 1 1-2 0V11"/></g></g></svg>
+1
web/console/src/assets/workspace-icons/javascript_react.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#00BFFF" d="M8 11c4.5 0 7.5-1.35 7.5-3s-3-3-7.5-3S.5 6.35.5 8s3 3 7.5 3ZM5.4 9.5c2.25 3.9 4.92 5.82 6.35 5 1.43-.83 1.1-4.1-1.15-8-2.25-3.9-4.92-5.82-6.35-5-1.43.83-1.1 4.1 1.15 8Zm0-3c-2.25 3.9-2.58 7.17-1.15 8 1.43.82 4.1-1.1 6.35-5s2.58-7.17 1.15-8c-1.43-.82-4.1 1.1-6.35 5Zm2.6 2a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Z"/></svg>
+1
web/console/src/assets/workspace-icons/javascript_test.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e0c240" stroke-linejoin="round"><path stroke-linecap="round" d="M15.5 12c-.33-.33-.83-.5-1.5-.5-1 0-1.5.5-1.5 1s.5 1 1.5 1 1.5.5 1.5 1-.5 1-1.5 1c-.67 0-1.17-.17-1.5-.5"/><path d="M10.5 11v3.5a1 1 0 1 1-2 0V14M12 7.5H4.98M9 0l7 7"/><path stroke-linecap="round" d="m10.72 1.75-8.49 8.48a2.5 2.5 0 1 0 3.54 3.54L8.53 11M10 9.54l4.25-4.26"/></g></svg>
+1
web/console/src/assets/workspace-icons/jenkins.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#6c7280" stroke-linecap="round" stroke-linejoin="round" d="M12.5 11.5c1.33-1 2-2.83 2-5.5 0-2-1-5.5-5.5-5.5C5.5.5 4.25 1.75 3.5 3c-1.33.67-1.75 1.75-1.25 3.25a5.11 5.11 0 0 0 0 2.25C2.42 9.17 3 9.58 4 9.75L4.5 12M7.25.75C6.42.92 5.67 1.67 5 3a5.64 5.64 0 0 0-.5 4c-.5-1.33-1.17-1.67-2-1"/><path stroke="#333333" stroke-linecap="round" d="M11.5 9.5h-2c-.83 0-1.5-.33-2-1m4.5-2a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm-4 0a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Z"/><path stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round" d="M14 15.5h.5l.75-2-2.75-2m-7.5.25L1 14l.5 1.5H6"/><path stroke="#e45649" stroke-linejoin="round" d="m10 14 2.5-1.5v3l-5-3v3z"/></g></svg>
+1
web/console/src/assets/workspace-icons/jest.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="round" stroke-linejoin="round"><path d="M7.5 6 5.56 1.5h9.94l-2 4.5"/><path d="m12.5 6-2-2.5-2 2.5M9.5 7.5h2M13.5 9c1 1-.5 2.75-3 4s-4.5 1.5-6 1.5c-2 0-3-1-3-2.9 0-.9.5-1.6 1.5-2.1"/><path d="M7.5 9c0 1.5-2 2.5-3 2.5S3 11.25 3 9.5"/><circle cx="3.5" cy="8.5" r="1"/><circle cx="8" cy="7.5" r="1.5"/><circle cx="13" cy="7.5" r="1.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/jinja.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="round" stroke-linejoin="round"><path d="M1.5 1.5c3.78 1.03 8.02 1.54 13 0L13 5c-3.5.75-6.5.75-10 0L1.5 1.5ZM1.5 7.59C6 8.75 10 8.75 14.5 7.5M5.5 5.98v8.52M2.5 14.5h4M9.5 14.5h4M10.5 5.98v8.52M3.5 8v6.5M12.5 8v6.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/json.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e0c240" stroke-linecap="round" stroke-linejoin="round" d="M4.5 2.5H4c-.75 0-1.5.75-1.5 1.5v2c0 1.1-1 2-1.83 2 .83 0 1.83.9 1.83 2v2c0 .75.75 1.5 1.5 1.5h.5m7-11h.5c.75 0 1.5.75 1.5 1.5v2c0 1.1 1 2 1.83 2-.83 0-1.83.9-1.83 2v2c0 .74-.75 1.5-1.5 1.5h-.5m-6.5-3a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm3 0a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm3 0a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Z"/></svg>
+1
web/console/src/assets/workspace-icons/julia.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#50a14f" d="M10.5 5a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0Z"/><path stroke="#e45649" d="M6.5 11a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0Z"/><path stroke="#5d5dff" d="M14.5 11a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/jupyter.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><circle cx="2" cy="2" r="1" fill="#6c7280" fill-rule="nonzero"/><circle cx="14" cy="2" r="1" fill="#6c7280" fill-rule="nonzero"/><circle cx="2" cy="14" r="1" fill="#6c7280" fill-rule="nonzero"/><path stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round" d="M1.5 9.5c1.67 2 3.83 3 6.5 3s4.83-1 6.5-3M1.5 6.5c1.67-2 3.83-3 6.5-3s4.83 1 6.5 3"/></g></svg>
+1
web/console/src/assets/workspace-icons/karma.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#008080" stroke-linecap="round" stroke-linejoin="round" d="M3.5 1.5h3v5l3.5-5h4l-4.5 6 5 7h-4l-3-4-1 1v3H6l-2.5-9v-4Zm0 13-1-3-1 1-1-3"/></svg>
+1
web/console/src/assets/workspace-icons/key.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333"><path stroke-linecap="round" stroke-linejoin="round" d="M10 10.5a4.5 4.5 0 1 0-4.02-2.48L1.5 12.5v2h2v-2h2v-2h2l.48-.48c.6.3 1.3.48 2.02.48Z"/><circle cx="11" cy="5" r="1"/></g></svg>
+1
web/console/src/assets/workspace-icons/kivy.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M4.5 3.5 11 10l-3.5 3.5-3-3v-7Zm11 1.5-3 3.5-3-3 6-.5Zm-13 3.5v5L.5 11l2-2.5Zm-2-5 2 2.5-2 2.5v-5Z"/></svg>
+1
web/console/src/assets/workspace-icons/kotlin.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#5d5dff" d="M2.5 13.5h11L8 8"/><path stroke="#ca7f00" stroke-linecap="round" d="M8.03 2.5h5.47l-8 8"/><path stroke="#e45649" stroke-linecap="round" d="M2.5 13.5V8"/><path stroke="#007ACC" stroke-linecap="round" d="M8 2.5H2.5V8l3-2.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/kubernetes.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8"><path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm0 4a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11ZM8 6.5V0m.66 9.35 2.85 5.84M7.34 9.35 4.5 15.19m2.05-6.85L.2 9.8m9.25-1.46L15.8 9.8M9.17 7.06l5.05-4.1m-7.39 4.1-5.05-4.1"/></g></svg>
+1
web/console/src/assets/workspace-icons/laravel.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="round" stroke-linejoin="round"><path d="M12.51 5.49v3.29M9.64 3.89l2.86 1.6 2.74-1.53M6.5 12v3.5M3.5 3.5l3-1.5"/><path d="m3.5 10.5 6-3.5V3.5L12.51 2l2.99 1.5V7l-3.06 1.5L9.5 7"/><path d="m.5 2 3-1.5 3 1.5v6.5"/><path d="M.5 2v10.17l6 3.33 6.02-3.41V8.5L6.5 12.04l-3-1.54v-7z"/></g></svg>
+1
web/console/src/assets/workspace-icons/latex.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"><path d="M13 12.5h1.5m-.5-4-3 4m0-4 3 4m-3.5 0h1m2-4 1.1.01m-4.1-.01h1.42M7.5 12.5H9m-1.5-2v4m-.86 0h3.66l.2-.5m-3.75-3.5H10l.5.5M2 13.5h3m-1.5-5v5m-2-3.5.25-1.5h3.5L5.5 10M9.5 6.5l2-5 2 5m-3-2h2M9 6.5h1m3 0h1M4.5 1.5v5m-1-5h2m-2 5H7l.54-.99"/></g></svg>
+1
web/console/src/assets/workspace-icons/lerna.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round"><path d="m1 10.3.6.15.78.17c.01.01-.14.25-.32.52-.7 1-.83 1.5-.44 1.5.12 0 .38-.06.6-.14.24-.07.67-.11 1.15-.11.97 0 1.95.24 3.18.8 1.17.53 1.56.65 2.9.93.63.13 1.17.27 1.2.3.04.04-.05.16-.2.28-.15.12.74.17 1.08.13 1.05-.13 1.79-.6 1.8-1.16.03-.7-.09-1.8-.22-1.88-.58.6-1.12.59-1.75.63a3.72 3.72 0 0 1-2.05-.48c-.5-.29-1.21-.55-1.6-1.14-.3-.49-.62-1.28-.56-1.4.02-.02.13-.02.23.02.29.11.7.28 2 .11a5.73 5.73 0 0 1 2.46.12c.73.18.62.34 1 .56.64-1.45.61-1 1.35-1.49.45-.32.82-.61.81-.67 0-.04-.26-.35-.56-.68-.47-.52-.6-.6-1.03-.76a8.97 8.97 0 0 1-3.9-2.63C8.58 2.81 8.33 2.54 7.67 2.1a7.3 7.3 0 0 0-2.4-1.06c-.73-.14-.52.1-.21.6 1.11 1.3 1.08 1.34 1.08 2.2-.11.67-.72.77-1.4.92-.3.06-.71.05-.91.05-.3 0-.93-.36-1.27-.73-.19-.2-.52-.61-.74-.93-.23-.32-.45-.58-.52-.58"/><path d="M8 5.79c1.27.43 1.34.63 1.76 1.37.1.17.16.3.15.31l-.25.07c-.33.07-.85.07-1.15 0-.56-.12-.58-.22-.81-.42a1.02 1.02 0 0 1-.3-.42c-.08-.21.04-.47-.04-.68-.07-.18-.4-.25-.35-.44.3-.1.78.13 1 .2Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/less.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linecap="round"><path stroke-linejoin="round" d="M4 2.5c-.74 0-1.5.76-1.5 1.5v2c0 1.1-1.1 2-1.83 2 .74 0 1.83.9 1.83 2v2c0 .74.76 1.5 1.5 1.5"/><path d="M5.5 5.5v5a1 1 0 0 0 1 1H7"/><path stroke-linejoin="round" d="M11.5 7.5c0-.69-.59-1-1.25-1h-.5c-.66 0-1.25.56-1.25 1.25S9.09 9 9.75 9h.5c.66 0 1.25.56 1.25 1.25s-.59 1.25-1.25 1.25h-.5c-.66 0-1.25-.31-1.25-1M12 2.5c.74 0 1.5.76 1.5 1.5v2c0 1.1 1.1 2 1.83 2-.74 0-1.83.9-1.83 2v2c0 .74-.76 1.5-1.5 1.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/lib.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 1.5h2a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-11a1 1 0 0 1 1-1ZM6.5 4.5h1a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1v-8a1 1 0 0 1 1-1ZM9.98 4.9l1.93-.52a.5.5 0 0 1 .62.35l2.33 8.7a.5.5 0 0 1-.36.6l-1.93.53a.5.5 0 0 1-.61-.36L9.63 5.5a.5.5 0 0 1 .35-.6Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/license.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 13.5h7M8.01 1v12.06M1.5 3.5h3l1.5-1h4l1.5 1h3M.5 10 3 4.48 5.5 10C4 11 2 11 .5 10ZM10.5 10 13 4.48 15.5 10c-1.5 1-3.5 1-5 0Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/lighthouse.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#00BFFF" stroke-linejoin="round" d="M10.5 3.88a1.5 1.5 0 0 1 2.46.78 1 1 0 1 1 .54 1.84h-2"/><path stroke="#ca7f00" d="m5.14 9.5-.89 5h7.5l-1.25-7h1v-2h-1V2.75L8 1.5 5.5 2.75V5.5h-1V7M9 10.5l-.25-2-1.5 1-.25 2 2-1Zm-1.5-5v-1L8 4l.5.5v1h-1Z"/><path stroke="#00BFFF" stroke-linejoin="round" d="M3 6.5c.71 0 1.3.5 1.46 1.16A1 1 0 1 1 5 9.5H3a1.5 1.5 0 0 1 0-3Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/lintstaged.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#ca7f00" d="M4.28 8.2c-.01.02.13.2.38.36.14.08.3.14.45.16M5.56 5.8c-.03.02.56.77 1.42.45"/><path stroke="#ca7f00" d="M3.6 10.45a2.11 2.11 0 0 1-.05-.2C3.4 9.69 3.5 9 4.4 8.45c-.67-1.23.66-2.56 1.13-2.6.24 0-.17-2.8 2.84-2.35M8.72 5.01c-.16-.14-.97-.54-.35-1.4M10.93 8.33s.1-.33 0-.74c-.01-.11-.05-.22-.1-.32"/><path stroke="#ca7f00" d="M6.42 11.5h4.96c.6.07 2.03-1.64-.42-3.21-.56.63-1.12 1.2-2.09 1.05"/><circle cx="8" cy="8" r="6.5" stroke="#e45649"/><path stroke="#e45649" d="m3.4 12.65 9.25-9.25"/></g></svg>
+1
web/console/src/assets/workspace-icons/lisp.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="round" stroke-linejoin="round" d="M9.5 10.5c2-1.67 2.33-3.83 1-6.5.67 2.33 1.67 4.5 3 6.5m-7-5c-2 1.67-2.33 3.83-1 6.5a23.07 23.07 0 0 0-3-6.5m5.5 10a7.5 7.5 0 1 0 0-15 7.5 7.5 0 0 0 0 15Zm3.5-1C7.83 13.5 6.67 11.33 8 8s.17-5.5-3.5-6.5"/></svg>
+1
web/console/src/assets/workspace-icons/literate.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M12.5 4.5h3m-1.5 3h1.5m-10 6 2.5-5-2.5-5H8l5.6 10h-2.53l-1.52-2.92L8 13.5H5.5Zm-5 0 2.5-5-2.5-5H3l2.5 5-2.5 5H.5Z"/></svg>
+1
web/console/src/assets/workspace-icons/livescript.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#007ACC" stroke-linecap="square" stroke-linejoin="round" d="M5.5 1.5v9h9v2h-9v2h-2v-2h-2v-2h2v-9h2ZM6 10l7.5-7.5m0-1v1h1m-7 6v-7m0 7h7m-5-2v-5m0 5h5m-3-2v-3m0 3h3"/></svg>
+1
web/console/src/assets/workspace-icons/log.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333" stroke-linejoin="round"><path d="M4.5 3.5h9v11h-9z"/><path stroke-linecap="round" d="M11.5 3.45V1.5h-9v11h1.95M7.5 7.5h3M7.5 10.5h3"/></g></svg>
+1
web/console/src/assets/workspace-icons/lottie.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#008080"><rect width="13" height="13" x="1.5" y="1.5" rx="2.5"/><path d="M4 11.5h.5c2 0 3.17-1.17 3.5-3.5.33-2.33 1.5-3.5 3.5-3.5h.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/lua.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><circle cx="9" cy="7" r="1.5" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#3b8ad8" d="M7 2.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13M14 .5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3"/></g></svg>
+1
web/console/src/assets/workspace-icons/mail.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#3b8ad8" d="M3 2.5h10c.83 0 1.5.67 1.5 1.5v8c0 .83-.67 1.5-1.5 1.5H3A1.5 1.5 0 0 1 1.5 12V4c0-.83.67-1.5 1.5-1.5ZM1.75 4 8 8.5 14.25 4"/></svg>
+1
web/console/src/assets/workspace-icons/makefile.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#ca7f00" d="m9.24 8.47-5.28 5.58c-.56.6-1.47.6-2.04 0a1.57 1.57 0 0 1 0-2.14L7.2 6.32"/><path stroke="#333333" d="m13.74 8.03-.86-.93a2.46 2.46 0 0 1-.64-1.68v-.65l-1.89-2.04A3.71 3.71 0 0 0 7.63 1.5H5.5l.64.61A4.72 4.72 0 0 1 7.5 5.57v1.16l1.46 1.5h1.71l1.57 1.42m-.74.85 3-3"/></g></svg>
+1
web/console/src/assets/workspace-icons/markdown.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#007ACC" stroke-linejoin="round" d="m9.25 8.25 2.25 2.25 2.25-2.25M3.5 11V5.5l2.04 3 1.96-3V11m4-.5V5M1.65 2.5h12.7c.59 0 1.15.49 1.15 1v9c0 .51-.56 1-1.15 1H1.65c-.59 0-1.15-.49-1.15-1V3.58c0-.5.56-1.08 1.15-1.08Z"/></svg>
+1
web/console/src/assets/workspace-icons/markdown_mdx.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linejoin="round" d="m9.25 8.25 2.25 2.25 2.25-2.25M3.5 11V5.5l2.04 3 1.96-3V11m4-.5V5M1.65 2.5h12.7c.59 0 1.15.49 1.15 1v9c0 .51-.56 1-1.15 1H1.65c-.59 0-1.15-.49-1.15-1V3.58c0-.5.56-1.08 1.15-1.08Z"/></svg>
+1
web/console/src/assets/workspace-icons/marko.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#e45649" d="m12 13.5 3.5-5-3.5-6"/><path stroke="#3b8ad8" d="m4 2.5-3.47 6 3.47 5"/><path stroke="#008080" d="M6.43 8.5 4 2.5"/><path stroke="#50a14f" d="m9 2.5-2.57 6"/><path stroke="#e0c240" d="m9.01 13.5 3.5-5L9 2.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/mathematica.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="square" stroke-linejoin="round" d="m9.5 13 3.5 1.5-1-3.5 3.5-.5L13 8l2.5-2.5L12 5l1-3.5L9.5 3 8 .5 6.5 3 3 1.5 4 5l-3.5.5L3 8 .5 10.5 4 11l-1 3.5L6.5 13 8 15.5 9.5 13ZM4 11l1.5-2.25L3 8m3.5-2-1 2.75L8 10.5l2.5-1.75L9.5 6h-3ZM13 8l-2.5.75L12 11m-5.5 2L8 10.5 9.5 13M4 5l2.5 1V3m3 0v3L12 5"/></svg>
+1
web/console/src/assets/workspace-icons/matlab.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#007ACC" stroke-linecap="round" d="M4 11 .5 8.5 5 7c.52-1.18 1.15-1.81 1.89-1.89.74-.07 1.94-1.28 3.61-3.61M5 7l1.5 1.5"/><path stroke="#ca7f00" stroke-linecap="square" d="m15.5 12.5-5-11C8.5 6.83 6.33 10 4 11c1.67-.33 2.67.83 3 3.5 3.5-1.5 3.5-3.5 5-4s1.5 1.5 3.5 2Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/maven.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#e45649" d="M2.5 14.5a24.25 24.25 0 0 1 1.63-4.36c.21-.42.45-.84.7-1.26 2.75-4.61 6.63-6.8 8.67-7.38-.8 3.52-3.91 10.46-10.15 10.4"/><path stroke="#ca7f00" d="M6.14 6.96C8.7 3.64 11.76 1.99 13.5 1.5a18.45 18.45 0 0 1-2.27 5.46"/><path stroke="#e45649" d="M6.5 9.5h-2M5.75 7.5h2.71"/></g></svg>
+1
web/console/src/assets/workspace-icons/mercurial.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#333333" d="M14.49 8.47a6.11 6.11 0 0 1-.86 2.95c-2.33 4.05-5.32 3.44-5.39 1.93 0-.2 0-.42.07-.62.13-.69.66-1.51.86-2.27.2-.82-.13-1.5-1.73-1.99C4.85 7.72 4 5.45 4.8 3.74c.46-1.03 1.52-2 3.32-2.2 3.99-.48 6.58 3.36 6.38 6.93Zm-8.1 3.1c.06.14.06.24.1.33.06.72-.4 1.35-1.08 1.54-.8.24-1.64-.24-1.84-1-.05-.2-.1-.4-.05-.58a1.4 1.4 0 0 1 1.1-1.3c.79-.24 1.58.24 1.78 1ZM2.43 6.99c.52-.05.93.3 1.03.9.05.05.05.11.05.17v.24c0 .6-.36 1.06-.92 1.18-.52.06-1.03-.41-1.08-1.06v-.3c.05-.6.41-1.07.92-1.13Z"/></svg>
+1
web/console/src/assets/workspace-icons/mermaid.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#bd2c00" stroke-linecap="round" stroke-linejoin="round" d="M1.5 2.5c0 6 2.25 5.75 4 7 .83.67 1.17 2 1 4h3c-.17-2 .17-3.33 1-4 1.75-1.25 4-1 4-7C12 2.5 10 3 8 7 6 3 4 2.5 1.5 2.5Z"/></svg>
+1
web/console/src/assets/workspace-icons/meson.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round"><path d="M8 1.5C4 3.75 1.5 8 1.5 12.74 5 15 11 15 14.5 12.75 14.5 8 12 3.75 8 1.5Z"/><path d="M10.43 8.55c-.11.74-.8 1.95-1.67 2.74.12.18.18.39.18.6-.55.3-1.09-.03-1.74-.62a5.77 5.77 0 0 1-1.6-2.96.91.91 0 0 1-.6-.14c.05-.68.73-1.08 1.34-1.32.61-.23 2.18-.31 3.29.1.08-.2.23-.35.4-.45.58.4.5 1.38.4 2.05Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/metro.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linejoin="round"><path stroke-linecap="square" d="M3.5 5.5v5l4.5 2 4.5-2v-5L8 7.5z"/><path stroke-linecap="round" d="M10.5 4.5 8 3.47 5.5 4.5"/><path stroke-linecap="round" d="M1.5 11.5v-7l6.5-3 6.5 3v7l-6.5 3z"/></g></svg>
+1
web/console/src/assets/workspace-icons/mint.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#008080" d="M5.5 13.5c3-.25 8-3 9.25-8.25a7.87 7.87 0 0 1-3.48.25"/><path stroke="#50a14f" d="M1.5 10.25C3.06 8.77 3 7.75 7 6.5c-3.75 1.25-4 2.68-4.75 3.75-.75 1.07.22 2.92 2 3.2 1.8.26 2.8-.46 3.53-1.15 2.34-2.23 1.2-6.02 5.72-9.3C6 1.38 3.07 4.05 2.5 4.5c-3.05 2.43-1.95 6.67-1 5.75Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/mjml.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#ca7f00" d="M13.5 4.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm-11-2h7a1 1 0 0 1 0 2h-7a1 1 0 1 1 0-2Zm11 12a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm-11-2h7a1 1 0 0 1 0 2h-7a1 1 0 0 1 0-2Zm0-3a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm4-2h7a1 1 0 0 1 0 2h-7a1 1 0 1 1 0-2Z"/></svg>
+1
web/console/src/assets/workspace-icons/mocha.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e66861" stroke-linejoin="round"><path stroke-linecap="square" d="M3 5.5h10l-1 6-1.5 4h-5l-1.5-4z"/><path stroke-linecap="round" d="m10.5.5-1 1 1 1-1 1M7.5 1.5l-1 1h1l-1 1M4 10.5h5.5l3-2"/></g></svg>
+1
web/console/src/assets/workspace-icons/modernizr.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#cc297b" stroke-linecap="square" d="M1.5 14.5h12v-12h-4v4h-4v4h-4v4Zm8 0v-8m-4 8v-4"/></svg>
+1
web/console/src/assets/workspace-icons/moon.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#5d5dff"><circle cx="12.5" cy="3.5" r="2"/><path d="M7.5 2.5a6 6 0 0 1 1.75.26h.02l-.11.06a3 3 0 0 0-.64.43l-.14.13A3 3 0 0 0 10.5 8.5a3 3 0 0 0 2.68-1.66l.05-.1.01.01c.14.46.23.95.25 1.45l.01.3a5.98 5.98 0 0 1-6 6 5.98 5.98 0 0 1-6-6 5.98 5.98 0 0 1 6-6Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/mooonscript.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e0c240" stroke-linejoin="round" d="M9 1.5c1.88 0 3.57.8 4.75 2.07a5.5 5.5 0 1 0 0 8.87A6.49 6.49 0 0 1 2.5 8 6.5 6.5 0 0 1 9 1.5Z"/></svg>
+1
web/console/src/assets/workspace-icons/nativescript.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#00BFFF" stroke-linecap="round" stroke-linejoin="round" d="M4.5 10.75c0 .5.25.75.75.75H6.5V8l3 3.5h1.25c.5 0 .75-.25.75-.75v-1.5c0-.5.42-.92 1.25-1.25-.83-.33-1.25-.75-1.25-1.25v-1.5c0-.5-.25-.75-.75-.75H9.5V8l-3-3.5H5.25c-.5 0-.75.25-.75.75v1.5c0 .5-.42.92-1.25 1.25.83.33 1.25.75 1.25 1.25v1.5ZM4 1.5h8A2.5 2.5 0 0 1 14.5 4v8a2.5 2.5 0 0 1-2.5 2.5H4A2.5 2.5 0 0 1 1.5 12V4A2.5 2.5 0 0 1 4 1.5Z"/></svg>
+1
web/console/src/assets/workspace-icons/nest.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="square" stroke-linejoin="round" d="m11.5 15.5.5-3.47C10.5 16 8.34 15.28 7.52 15.5c-.23.06 1.67-.48 2.48-2-.9.33-1.56.5-1.98.5 1.42-1.23 1.91-2.4 1.5-3.5-.34 2.33-4.61 4.11-5.53 2.06-.6-1.37-.28-2.23.97-2.57 0 1.06.51 1.59 1.54 1.59V10.5l1.97.91C8.16 8.14 6 6.83 2 7.5 1 6.35.5 5.52.5 5c0-.78.25-1 1-1s1-.02 2.03-1.05C5.09 1.46 7.1 1.1 9.5 2.57c-.22-.63.13-1.32 1.05-2.07 1.48.73 2.13 1.73 1.94 3-.19 1.27-1.02 1.94-2.5 2 .49.37 1.15.37 2 0a2.41 2.41 0 0 0 1.48-2c1.35 1.67 2.02 3.33 2.02 5s-.35 3.02-1.04 4.06l-.5-2.06c-.98 3.5-1.56 4.3-2.47 5Z"/></svg>
+1
web/console/src/assets/workspace-icons/netlify.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#008080" stroke-linejoin="round" d="M3.25 3.25 5 5m-1.75 8.75L5 12M1 8.5h4m6 0h4M8.5 1v3m0 8v3"/><path stroke="#333333" stroke-linecap="square" d="M6.5 10.5v-4h2c.68 0 .97.57 1 1v3"/></g></svg>
+1
web/console/src/assets/workspace-icons/next.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"><path d="M12.33 12.85a6.5 6.5 0 1 1 1.55-2.08"/><path d="M12.33 12.85 5.5 4.5v7M10.5 4.5v3"/></g></svg>
+1
web/console/src/assets/workspace-icons/nextflow.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round" d="M14.5 2.36c-2.4.52-4.2 1.62-6.46 3.79C4.7 2.23 1.89 2 1.5 2v2.47c.16.02 2.2.33 4.82 3.48a14.5 14.5 0 0 1-4.82 3.02v2.58a16.38 16.38 0 0 0 6.33-3.62A11.38 11.38 0 0 0 14.5 14v-2.55a9.04 9.04 0 0 1-4.93-3.32c1.81-2.02 3.2-2.93 4.93-3.34V2.36Z"/></svg>
+1
web/console/src/assets/workspace-icons/nginx.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round"><path d="M5.5 11.5v-6l5 6v-6"/><path d="M1.5 11.5v-7L8 .5l6.5 4v7l-6.5 4z"/></g></svg>
+1
web/console/src/assets/workspace-icons/nim.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e0c240" stroke-linecap="round" stroke-linejoin="round" d="M1 7 .5 4.5l1.01.67c.28-.27.47-.48 1.18-.85l.56-1.82L4.5 3.84c.77-.18 1.53-.36 2.4-.33L8 1.5l1.1 2.01c.87-.03 1.63.15 2.4.33l1.25-1.34.56 1.82c.7.37.9.58 1.18.85l1.01-.67L15 7m-1.5 1C13 6.5 11 5.5 8 5.5S3 6.5 2.5 8m11.5.75L13.5 8l-1 1.5-1.5.5-3-1.5L5 10l-1.5-.5-1-1.5-.5.75L1 7l1.25 3.75C3 12.75 6 13.5 8 13.5s5-.75 5.75-2.75L15 7l-1 1.75Z"/></svg>
+1
web/console/src/assets/workspace-icons/ninja.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><circle cx="8" cy="8" r="6.5" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M2.68 4.5H13.4M1.75 9.5h12.49"/><circle cx="6" cy="7" r="1" fill="#333333"/><circle cx="10" cy="7" r="1" fill="#333333"/></g></svg>
+1
web/console/src/assets/workspace-icons/nix.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#007ACC" d="M.5 7.5H4m1.39-2L2.05 11"/><path stroke="#3b8ad8" d="M4 1.5 5.5 4m3.5.5H2.55"/><path stroke="#007ACC" d="m12 1.5-1.5 3m1.01 2.6L8.5 1.5"/><path stroke="#3b8ad8" d="M15.5 8.52 12 8.5m-1.38 2L14 5"/><path stroke="#007ACC" d="m12.5 14.5-2.5-3m-2.97.02 6.48-.02"/><path stroke="#3b8ad8" d="m4 14.5 1.5-3M4.53 9l2.97 5.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/nodemon.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round" d="M1.5 11.5v-7L8 .5l6.5 4v7l-6.5 4-6.5-4Zm4-5-1-2 2 1 1.5-1 1.5 1 2-1-1 2 1 1v4l-2-1v-2L8 7.5l-1.5 1v2l-2 1v-4l1-1Z"/></svg>
+1
web/console/src/assets/workspace-icons/npm.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#e45649" d="M2.45 1.5a.95.95 0 0 0-.95.95v11.1a.95.95 0 0 0 .95.95h11.1a.95.95 0 0 0 .95-.95V2.45a.95.95 0 0 0-.95-.95H2.45Z"/><path stroke="#333333" d="M4.5 4.5h7v7h-2v-5h-2v5h-3z"/></g></svg>
+1
web/console/src/assets/workspace-icons/npm_ignore.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#6c7280" stroke-linecap="round" stroke-linejoin="round"><path d="M2.45 1.5a.95.95 0 0 0-.95.95v11.1a.95.95 0 0 0 .95.95h11.1a.95.95 0 0 0 .95-.95V2.45a.95.95 0 0 0-.95-.95H2.45Z"/><path d="M4.5 4.5h7v7h-2v-5h-2v5h-3z"/></g></svg>
+1
web/console/src/assets/workspace-icons/npm_lock.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#6c7280" d="M15 11.5c.27 0 .5.22.5.5v3a.5.5 0 0 1-.5.5h-5a.5.5 0 0 1-.5-.5v-3c0-.28.22-.5.5-.5h5Zm-4 0V10a1.5 1.5 0 0 1 3 0v1.5"/><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M9.5 9V5.5h-2v6h-4v-8h8v3"/><path stroke="#e45649" stroke-linecap="round" stroke-linejoin="round" d="M7.54 13.5H3A1.5 1.5 0 0 1 1.5 12V3c0-.83.67-1.5 1.5-1.5h9c.83 0 1.5.67 1.5 1.5v3.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/nuget.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#3b8ad8" d="M5.5 3.5h7a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2h-7a2 2 0 0 1-2-2v-7c0-1.1.9-2 2-2ZM2 2.5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm4.5 5a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm4 5a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/></svg>
+1
web/console/src/assets/workspace-icons/nunjucks.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#50a14f"><path d="M14.5 14.5h-4c-.75 0-1-.25-1-1v-3l2-.5v2.5h2v-8h2v9c0 .75-.25 1-1 1Z"/><path stroke-linecap="square" d="M.5 14.5v-13H2l2.5 6v-6h2v13H5l-2.5-6v6z"/></g></svg>
+1
web/console/src/assets/workspace-icons/nuxt.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round" d="M9.5 12.5h6l-5-7L7.44 11c-.67.98-1.32 1.48-1.94 1.5h-5l5-9 3 5"/></svg>
+1
web/console/src/assets/workspace-icons/nuxt_ignore.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#6c7280" stroke-linecap="round" stroke-linejoin="round" d="M9.5 12.5h6l-5-7L7.44 11c-.67.98-1.32 1.48-1.94 1.5h-5l5-9 3 5"/></svg>
+1
web/console/src/assets/workspace-icons/nx.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#007ACC" stroke-linecap="round" stroke-linejoin="round"><path d="M.5 3.5v10h2V7.6l4 5.9h2L9.55 12l.95 1.5h2l-2-3.5s-.06-1.01.84-1.54c.9-.54 1.56 0 1.56 0s.4.32.5.61c.1.29.48.35.7.47.24.13.43.23.49.6.05.37.5.63.84.22.34-.4-.54-3.44-2.62-3.81-2.08-.37-3.49 1.31-3.49 1.31L8.5 6.32V3.5h-2v6l-4-6h-2ZM6.67 5.54 8.5 5.5"/><path d="m6.5 13.5 2-3.5-2-3.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/ocaml.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#ca7f00"><path d="M1.5 8V3c0-.83.67-1.5 1.5-1.5h10c.83 0 1.5.67 1.5 1.5v10c0 .83-.67 1.5-1.5 1.5H9"/><path stroke-linecap="round" stroke-linejoin="round" d="m1.5 8 1.14-2.3c.06-.14.18-.22.36-.24a.8.8 0 0 1 .44.13c.18.12.23.53.28.64.06.1.64 1.23.85 1.23.2 0 .71-1.47.71-1.47s.37-.49.72-.49.55.32.67.49c.12.16.24 1.76.46 2.01.22.25 1.32.87 1.67.73.34-.13.53-.4.63-.73.1-.34-.14-.75 0-1a1.1 1.1 0 0 1 1.02-.55c.56.03 2.05.56 2.05 1.05 0 .5-.5.75-1.5.75-.48 1.33.28 2.22-3 2.25l1 4"/><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 14.5 1.5-4 1 4zM2.5 14.5l1.5-4-1.5-.5-1 1.54V14l1 .49Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/package_json.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linejoin="round" d="M11.5 7v-.17c0-.73-.6-1.33-1.33-1.33H9a1.5 1.5 0 1 0 0 3h1a1.5 1.5 0 0 1 0 3H9A1.5 1.5 0 0 1 7.5 10m-2-5v5c0 .82-.27 1.63-1 2l-1.25.5m-1.75-1v-7L8 .5l6.5 4v7l-6.5 4-6.5-4Z"/></svg>
+1
web/console/src/assets/workspace-icons/panda.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e0c240" stroke-linecap="round" stroke-linejoin="round" d="M7.18 1.55c-.7.08-1.36.22-2 .47a5.27 5.27 0 0 0-3.05 2.84c-.45 1-.61 2.05-.63 3.14-.02 1.15.12 2.28.35 3.4.2 1.02.47 2.02.86 2.98.03.09.08.12.18.12h4.74a1.56 1.56 0 0 0 .14 0l-.02-.06a66.78 66.78 0 0 0-.3-.66c-.2-.41-.4-.83-.57-1.25A13.58 13.58 0 0 1 5.8 8.5a5.09 5.09 0 0 1 .12-1.84c.2-.69.6-1.17 1.29-1.38a3.1 3.1 0 0 1 1.89.02c.55.19.92.58 1.07 1.16.12.45.12.9.03 1.35-.07.35-.2.66-.46.92-.45.45-1 .56-1.61.52l-.33-.03-.15-.02a1 1 0 0 1 0 .05v.08c.04.1.06.21.08.32.06.25.12.5.2.76.15.48.32.96.52 1.43a8.74 8.74 0 0 0 4.05-1.3l.06-.04a3.87 3.87 0 0 0 1.39-1.48c.53-1.02.63-2.1.47-3.22a4.54 4.54 0 0 0-1.54-2.88 5.4 5.4 0 0 0-2.06-1.1c-1.2-.34-2.41-.39-3.65-.28Z"/></svg>
+1
web/console/src/assets/workspace-icons/parse.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#00BFFF" stroke-linecap="round" d="M3.5 10.5a2 2 0 1 0 2 2V6a4.5 4.5 0 1 1 4.5 4.5H7.5"/></svg>
+1
web/console/src/assets/workspace-icons/payload.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333" stroke-linejoin="round"><path stroke-linecap="square" d="M13.5 12.5v-8l-6-4-4.25 2.83L9.5 7.5v8z"/><path stroke-linecap="round" d="M6.5 9.5v6l-5-3z"/></g></svg>
+1
web/console/src/assets/workspace-icons/pdf.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="round" stroke-linejoin="round" d="M2.8 14.34c1.81-1.25 3.02-3.16 3.91-5.5.9-2.33 1.86-4.33 1.44-6.63-.06-.36-.57-.73-.83-.7-1.02.06-.95 1.21-.85 1.9.24 1.71 1.56 3.7 2.84 5.56 1.27 1.87 2.32 2.16 3.78 2.26.5.03 1.25-.14 1.37-.58.77-2.8-9.02-.54-12.28 2.08-.4.33-.86 1-.6 1.46.2.36.87.4 1.23.15h0Z"/></svg>
+1
web/console/src/assets/workspace-icons/pdm.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#5d5dff"><path d="M10.5 2v10.25m1.5.95L3 8m-1.5.87 9-5.2"/><path stroke-linejoin="round" d="m8 .5 6.5 3.75v7.5L8 15.5l-6.5-3.75v-7.5z"/></g></svg>
+1
web/console/src/assets/workspace-icons/pdm_lock.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#6c7280" d="M15 11.5c.27 0 .5.22.5.5v3a.5.5 0 0 1-.5.5h-5a.5.5 0 0 1-.5-.5v-3c0-.28.22-.5.5-.5h5Zm-4 0V10a1.5 1.5 0 0 1 3 0v1.5"/><path stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round" d="m8 13.62-1.5.88-6-3.5V4l6-3.5 6 3.5v3m-4-5.35v8.74m0 0L3 7.2m-4.43 2.56L8.5 4.04"/></g></svg>
+1
web/console/src/assets/workspace-icons/perl.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linejoin="round" d="M12.5 15v-3.84c-1-.66-1-1.35-1-2.66V8m-3 1.5.02 2.53L9.75 15M5.5 9.5V15m9 0V9.23s.17-1.73-1-1.73c0-1.5-.5-6-2.5-6S8.75 4.25 8.75 4.25A3.67 3.67 0 0 0 6.5 7.12v-3.5c0-.63-.85-1.32-1.5-1.32-.92 0-1.33.59-1.5 1.2H2.25c-.42.11-.75.59-.75 1 0 .5.28 1 .75 1h1.22l.02 3c.01.75.51 1 1.51 1h5"/></svg>
+1
web/console/src/assets/workspace-icons/php.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round" d="M.5 12.5v.74c0 .76.77 1.26 1.5 1.26.94 0 1.5-.5 1.5-1.26V6C3.5 4.3 5 2.52 7.15 2.5c2.34 0 3.85 1.56 3.85 3 .17 2.99-1.42 4.14-3.5 5v4h8V9c.04-.64-.56-1.84-1.37-2.5-.94-.7-2.07-1-3.13-1m.5 9v-3M6 6.5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Z"/></svg>
+1
web/console/src/assets/workspace-icons/php_cs_fixer.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" d="M12 5.5h1m-3 0h1"/><path stroke="#bd2c00" stroke-linecap="round" d="M9.5 15.5c-.33-.5-.42-1.5-.25-3 .17-1.5.58-2.83 1.25-4m-1 4c3.25-.25 4-3 3.75-4 1.25-1.25 1.5-3 1.25-5 .5-1.25 0-2-1-3-1 .5-1.75 1-2 2-2.75 1.25-3.25 3.5-3 5-1.75 1.25-1 5 1 5Zm-5 3c0-1.5-.33-2.83-1-4m1 2c1.5-.5 1.5-1.25 1-3-.33-1.17-1.33-2.17-3-3-1.02 1.6-1.35 2.94-1 4 .53 1.59 1.5 2.5 3 2Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/phpstan.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round" d="M5.5 3.25C6.17 2.75 7 2.5 8 2.5s1.83.25 2.5.75M10 3c.33-.33.83-.5 1.5-.5 1 0 3 1.5 3 4s-1.25 4-2 4c-.5 0-1.08-.42-1.75-1.25.67-.83.92-2.08.75-3.75M6 3c-.33-.33-.83-.5-1.5-.5-1 0-3 1.5-3 4 0 .76.12 1.44.3 2M3.75 10c-.17-.33-.42-.5-.75-.5-.5 0-.5.5-.5 1.25 0 .3.23.77.62 1.25M5 13.33c.31.1.65.17 1 .17 2 0 4-1.5 4.5-5m-4 2c-.17.67-.42 1-.75 1s-.5-.08-.5-.25"/><circle cx="6" cy="7" r="1.5" stroke="#6c7280"/><path stroke="#333333" stroke-linecap="round" d="M5.1 9.33 3.5 13.5m2.5-4a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/phpunit.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#5d5dff" d="M13.45 3.42a1.5 1.5 0 0 1 1 1.87l-.3.96a2.5 2.5 0 1 0-1.17 3.82l-.87 2.87a1.5 1.5 0 0 1-1.88 1L2.58 11.6a1.5 1.5 0 0 1-1-1.87l2.35-7.66a1.5 1.5 0 0 1 1.87-1l7.65 2.35ZM12 7.5h1"/></svg>
+1
web/console/src/assets/workspace-icons/pinejs.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linejoin="round" d="m8 2.5 6.5 12h-13L8 2.5ZM3.5 11 7 9l3 2 2.25-1M7 9.5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm3 2a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Z"/></svg>
+1
web/console/src/assets/workspace-icons/plastic.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round" d="M1.5 11.5v-7L8 .5l6.5 4v7l-6.5 4m-3.5-2.16v-7.2L8 4l3.5 2.13v3.74L8 12l-1.5-.92v3.49"/></svg>
+1
web/console/src/assets/workspace-icons/playwright.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#e45649" d="M5 9.5c-1.02.35-1.5 1-1.5 1"/><path stroke="#e45649" d="m5.97 12.36-.1.03c-2.98.8-4.55-2.64-5.03-4.43C.62 7.13.53 6.28.5 5.88c-.03-.46.29-.33.89-.23a6.5 6.5 0 0 0 2.7-.17c.52-.14 1.26-.4 1.74-.65"/><path stroke="#e45649" d="M2.5 7.5s.36.56 1.18.31C4.5 7.57 4.5 7 4.5 7"/><path stroke="#50a14f" d="M15.05 7.57c-.62 2.33-2.67 6.81-6.52 5.78C4.68 12.3 5.14 7.4 5.77 5.08c.28-1.07.58-1.84.81-2.31.27-.54.53-.18 1.14.32a8.4 8.4 0 0 0 3.14 1.56 8.4 8.4 0 0 0 3.49.22c.78-.13 1.19-.3 1.15.29-.04.52-.16 1.33-.45 2.4Z"/><path stroke="#50a14f" d="M9.5 7.5s0-.82-.72-1.08c-.72-.26-1.28.08-1.28.08M7.5 9.5S8 11 9.21 11.33c1.21.34 2.29-.83 2.29-.83M13 9s.18-.87-.58-1.23C11.66 7.4 11 8 11 8"/></g></svg>
+1
web/console/src/assets/workspace-icons/plop.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#008080" stroke-linecap="round" stroke-linejoin="round" d="M3.5 10.62c.2 2.6 1.94 4.88 4.54 4.88h.05c2.6 0 4.33-2.29 4.53-4.88l.01-.23h0c0-1.15-.61-2.74-1.8-4.62A391.14 391.14 0 0 0 8.31 1.9l-.17-.27-.05-.07-.03-.05-.04.05-.05.07-.17.27-2.5 3.88C4.1 7.66 3.5 9.25 3.5 10.4v.23h0Z"/></svg>
+1
web/console/src/assets/workspace-icons/pnpm.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="square"><path stroke="#e0c240" d="M6.5 2.5v4"/><path stroke="#333333" d="M10.5 11.5v3M6.5 7.5v7"/><path stroke="#e0c240" d="M10.5 2.5V10M14.5 10V2.5h-12v4h12"/><path stroke="#333333" d="M2.5 10.5v4h12v-4z"/></g></svg>
+1
web/console/src/assets/workspace-icons/pnpm_lock.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#6c7280" d="M15 11.5c.27 0 .5.22.5.5v3a.5.5 0 0 1-.5.5h-5a.5.5 0 0 1-.5-.5v-3c0-.28.22-.5.5-.5h5Zm-4 0V10a1.5 1.5 0 0 1 3 0v1.5"/><path stroke="#e0c240" stroke-linecap="square" d="M4.5 1.5v3"/><path stroke="#333333" stroke-linecap="square" d="M7.5 7.5v3M4.5 5.5v4"/><path stroke="#e0c240" stroke-linecap="square" d="M7.5 1.5v6"/><path stroke="#e0c240" stroke-linecap="round" d="M10.5 6.5v-5h-9v3h9"/><path stroke="#333333" stroke-linecap="round" d="M9.5 7.5h-8v3h6"/></g></svg>
+1
web/console/src/assets/workspace-icons/poetry.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#5d5dff" stroke-linecap="round" d="m10.3 8.87 2.28 2.16C11.21 13.2 8 14.5 5.25 14.5L2.5 12.12"/><path stroke="#00BFFF" stroke-linecap="round" d="M2.5 1.5h11v.43a9.67 9.67 0 0 1-3.09 7.05"/><path stroke="#3b8ad8" d="M2.96 11.9c2.9 0 5.54-1.12 7.45-2.92L2.5 1.5v10.4h.46Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/postcss.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e45649"><circle cx="8" cy="8" r="6.5"/><path stroke-linejoin="round" d="m8 1.5 6 9H2z"/><path stroke-linejoin="round" d="M5.5 5.5h5v5h-5z"/><circle cx="8" cy="8" r="1"/></g></svg>
+1
web/console/src/assets/workspace-icons/posthtml.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e0c240" d="M3.5 5.5a3 3 0 1 0 1.59 5.54c.46-.29 5.35-4.8 5.82-5.08a3 3 0 1 1 1.6 5.54m-9.01-6c2.5 0 2.5 1 4.5 1s2-1 4.5-1m-9 6c2.5 0 2.5-1 4.5-1s2 1 4.5 1"/></svg>
+1
web/console/src/assets/workspace-icons/powershell.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round" d="m3.5 8.5 4 2-4 2m-.62 2c-.65 0-1.38-.73-1.38-1.37V4.88c0-.65.73-1.38 1.37-1.38h8.26c.64 0 1.37.73 1.37 1.38v8.25c0 .64-.73 1.37-1.37 1.37H2.88ZM2 3.9c1.5-2.25 2-2.4 2.87-2.4h8.26c.64 0 1.37.73 1.37 1.38v8.25c0 .87-.25 1.47-2.5 2.97m-3.5-1.6h2"/></svg>
+1
web/console/src/assets/workspace-icons/premake.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#50a14f" d="m11.05 4.39-.72-.19-.06-.9-.8-.1-.17.96-.7.17-.45-.93-1.05.32.39.96-.37.47-.9-.38-.5.59 1.2 1.55m4.62-3.13s-1 1.74-1.77 2.27c-.76.53-1.42 1.14-3.9.84-2.5-.3-3.89.04-4.2.38-.3.34-.25 1.07.58 1.51.82.44 1.94.8 1.94.8"/><path stroke="#e0c240" d="m11.86 10.7.54-.52.78.42.51-.65-.71-.65.22-.7 1.01.1.29-1.08-1.01-.17-.19-.56.79-.59-.23-.74-1.92.22m.17 5.67s-.92-1.8-.96-2.73c-.03-.94-.2-1.83 1.36-3.84 1.55-2 2-3.4 1.89-3.85-.12-.44-.76-.78-1.56-.3-.8.5-1.55 1.45-1.55 1.45"/><path stroke="#3b8ad8" d="m6.12 8.41.18.74-.75.49.29.77.91-.31.48.55-.58.84.77.8.64-.82.58.12.1 1 .74.16.77-1.82M5.36 8.26s1.98.08 2.8.51c.8.44 1.65.74 2.57 3.12.93 2.37 1.89 3.47 2.32 3.59.44.12 1.04-.29 1.03-1.24 0-.95-.35-2.1-.35-2.1"/></g></svg>
+1
web/console/src/assets/workspace-icons/prettier.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#007ACC" d="M1 2.5h12m-12 6h6"/><path stroke="#e0c240" d="M1 4.5h6m2 4h6"/><path stroke="#5d5dff" d="M9 4.5h6m-14 2h6m-6 6h6"/><path stroke="#e45649" d="M9 6.5h6m-14 4h12m-12 4h6"/></g></svg>
+1
web/console/src/assets/workspace-icons/prettier_ignore.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#6c7280" stroke-linejoin="round" d="M1 2.5h12m-12 2h6m2 0h6m-14 2h6m2 0h6m-14 2h6m2 0h6m-14 2h12m-12 2h6m-6 2h6"/></svg>
+1
web/console/src/assets/workspace-icons/prisma.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#008080" stroke-linecap="round" stroke-linejoin="round" d="m8 .5 6.5 12.05-10 2.95-3-5L8 .5m-3.5 15L8 .5"/></svg>
+1
web/console/src/assets/workspace-icons/processing.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#3b8ad8" d="M7.5 1.5H8a6.5 6.5 0 1 1 0 13h-.5V12a4 4 0 1 0 0-8V1.5ZM3.9 8.55l1.73-3.01 1.74 1-4 6.92-1.74-1 1.11-1.92"/><path stroke="#00BFFF" d="m1.18 7.85 1.73-1 2 3.46-1.73 1z"/></g></svg>
+1
web/console/src/assets/workspace-icons/prolog.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#ca7f00"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 13.5c-.33.33-.5.67-.5 1 0 .33.17.67.5 1 .17-.67.5-1 1-1s.83.33 1 1c.33-.33.5-.83.5-1.5h2c0 .67.17 1.17.5 1.5.17-.67.5-1 1-1s.83.33 1 1c.33-.33.5-.67.5-1 0-.33-.17-.67-.5-1l1-1 1.25 1.25c0-2.75 1-2.75 1-5.75a7.1 7.1 0 0 0-2-5.25A3.64 3.64 0 0 1 13 .5c-1.17 0-2 .42-2.5 1.25A3.08 3.08 0 0 0 8 .5c-1 0-1.83.42-2.5 1.25C5 .92 4.17.5 3 .5c.5.83.58 1.58.25 2.25a7.1 7.1 0 0 0-2 5.25c0 3 1 3 1 5.75L3.5 12.5l1 1Zm6-5a2 2 0 1 0 0-4 2 2 0 0 0 0 4Zm-5 0a2 2 0 1 0 0-4 2 2 0 0 0 0 4ZM7 8l1 2.5L9 8"/><path d="M10 6.5h1m-6 0h1"/></g></svg>
+1
web/console/src/assets/workspace-icons/properties.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M8 1.5c-.87 0-1.17 1.32-2.03 1.63-.86.3-2.17-.68-2.84 0-.68.67.3 1.98 0 2.84C2.83 6.83 1.5 7.13 1.5 8c0 .87 1.32 1.17 1.63 2.03.3.86-.68 2.17 0 2.85.67.67 1.98-.3 2.84 0 .85.3 1.16 1.62 2.03 1.62.87 0 1.17-1.32 2.03-1.63.86-.3 2.17.68 2.85 0 .67-.67-.3-1.98 0-2.84.3-.85 1.62-1.16 1.62-2.03 0-.87-1.32-1.17-1.63-2.03-.3-.86.68-2.17 0-2.84-.67-.68-1.98.3-2.84 0C9.17 2.83 8.87 1.5 8 1.5Zm0 9a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z"/></svg>
+1
web/console/src/assets/workspace-icons/proto.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#e45649" stroke-linecap="round" d="m.5 8.5 3-6h3l-3 6"/><path stroke="#3b8ad8" stroke-linecap="square" d="M6.5 13.5h-3l-3-5 1.36-2.73z"/><path stroke="#e0c240" stroke-linecap="round" d="m15.5 7.5-3 6h-3l3-6"/><path stroke="#50a14f" stroke-linecap="square" d="M9.5 2.5h3l3 5-1.36 2.73z"/></g></svg>
+1
web/console/src/assets/workspace-icons/pug.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e66861"><path stroke-linecap="round" stroke-linejoin="round" d="M3.5 4.5c.67-.5 1.17-.83 1.5-1 .5-.25 5.5-.25 6 0 .33.17.83.5 1.5 1m-9 0c-1.83.33-2.83.67-3 1-.25.5 0 3 .5 3.5.33.33.83.33 1.5 0L4 5.5l-.5-1Zm9 0c1.83.33 2.83.67 3 1 .25.5 0 3-.5 3.5-.33.33-.83.33-1.5 0L12 5.5l.5-1ZM2.5 9c-.33 1.67-.33 2.67 0 3 .33.33 1.17.67 2.5 1m8.5-4c.33 1.67.33 2.67 0 3-.33.33-1.17.67-2.5 1M9 9.5c-.75-.5-1.25-.5-2 0-.5.33-1.17 1.5-2 3.5-.5.5-.58.92-.25 1.25C5.25 14.75 7 14 8 14s2.75.75 3.25.25c.33-.33.25-.75-.25-1.25-.83-2-1.5-3.17-2-3.5Zm-2.25.25C6.92 10.58 7.33 11 8 11s1.08-.42 1.25-1.25"/><path d="M10.5 8V7m-5 1V7"/></g></svg>
+1
web/console/src/assets/workspace-icons/puppet.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e0c240" d="M9.5 6.5h3v3h-3Zm-2-2v-3h-3v3h3Zm0 10v-3h-3v3h3Zm2-8-2-2m0 7 2-2"/></svg>
+1
web/console/src/assets/workspace-icons/puppeteer.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#008080" stroke-linecap="square" d="M3.5 2.5 8 6.18l4.5-3.68h2v2L10.22 8l4.28 3.5v2h-2L8 9.82 3.5 13.5h-2v-2L5.78 8 1.5 4.5v-2z"/></svg>
+1
web/console/src/assets/workspace-icons/python.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#e0c240" stroke-linecap="round" stroke-linejoin="round" d="M10.5 10.5h-3m-3 1V13c0 .8.7 1.5 1.5 1.5h3c.8 0 1.5-.7 1.5-1.5v-2.5H13c.8 0 1.5-.7 1.5-1.5V7c0-.8-.7-1.5-1.48-1.5H11.5c0 1.5 0 2-1 2h-2"/><path stroke="#e0c240" d="M8 12.5h1"/><path stroke="#3b8ad8" d="M7 3.5h1"/><path stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round" d="M8.5 5.5h-3m6-1V3c0-.8-.7-1.5-1.5-1.5H7c-.8 0-1.5.7-1.5 1.5v2.5H3c-.8 0-1.5.7-1.5 1.5v2c0 .8.7 1.5 1.48 1.5H4.5c0-1.5 0-2 1-2h2"/></g></svg>
+1
web/console/src/assets/workspace-icons/python_compiled.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M10.5 10.5h-3m-3 1V13c0 .8.7 1.5 1.5 1.5h3c.8 0 1.5-.7 1.5-1.5v-2.5H13c.8 0 1.5-.7 1.5-1.5V7c0-.8-.7-1.5-1.48-1.5H11.5c0 1.5 0 2-1 2h-2"/><path stroke="#333333" d="M8 12.5h1"/><path stroke="#6c7280" d="M7 3.5h1"/><path stroke="#6c7280" stroke-linecap="round" stroke-linejoin="round" d="M8.5 5.5h-3m6-1V3c0-.8-.7-1.5-1.5-1.5H7c-.8 0-1.5.7-1.5 1.5v2.5H3c-.8 0-1.5.7-1.5 1.5v2c0 .8.7 1.5 1.48 1.5H4.5c0-1.5 0-2 1-2h2"/></g></svg>
+1
web/console/src/assets/workspace-icons/python_misc.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><g stroke-linecap="round" stroke-linejoin="round"><path stroke="#3b8ad8" d="M12.5 10.5h-2m3-1V9c0-.25-.25-.5-.5-.5h-1.75c-.5 0-.75.25-.75.75v1.25H9.25c-.5 0-.75.25-.75.75v1.5c0 .5.25.75.75.75h1.25v-.25c0-.5.25-.75.75-.75h.25"/><path stroke="#e0c240" d="M13.5 13.5h-2m-1 1v.5c0 .25.25.5.5.5h1.75c.5 0 .75-.25.75-.75V13.5h1.25c.5 0 .75-.25.75-.75v-1.5c0-.5-.25-.75-.75-.75H13.5v.25c0 .5-.25.75-.75.75h-.25"/></g><path stroke="#333333" stroke-linecap="round" d="M13.5 6.5v1m-6 7h-3a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h4.01"/><path stroke="#333333" stroke-linejoin="round" d="m8.5 1.5 5 5h-4a1 1 0 0 1-1-1v-4Z"/><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M4.5 11.5h2m-2-3h4m-4-3h2"/></g></svg>
+1
web/console/src/assets/workspace-icons/quasar.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#00BFFF" d="m7 6 1.5-2.5h-1V5l-1.25.25L7 6Zm3.23 2.13 1.42 2.55.5-.86-1.3-.75.4-1.21-1.02.27ZM6.77 9.87l-2.92-.05.5.86 1.3-.75.84.96.28-1.02ZM8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14Z"/><path stroke="#333333" stroke-linecap="round" d="m9.23 6.13 2.92.05-.5-.86-1.3.75-.84-.96-.28 1.02ZM9 10l-1.5 2.5h1V11l1.25-.25L9 10ZM5.77 7.87 4.35 5.32l-.5.86 1.3.75-.4 1.21 1.02-.27Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/r.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#6c7280" stroke-linecap="round" stroke-linejoin="round" d="M13.5 9.5c.63-.7 1-1.54 1-2.43 0-2.52-2.91-4.57-6.5-4.57S1.5 4.55 1.5 7.07c0 1.9 1.65 3.53 4 4.22"/><path stroke="#3b8ad8" d="M10.5 9.5c.4 0 .86.34 1 .7l1.25 3.8M7.5 14V5.5h3.05c.95 0 1.95 1 1.95 2s-1 2-1.95 2H7.5V14Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/racket.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#3b8ad8" d="M13.11 12.01A6.5 6.5 0 0 0 5.77 1.9c2.65 1.38 6.14 5.9 7.34 10.12h0Z"/><path stroke="#e45649" d="M7.07 5.65A11.28 11.28 0 0 0 3.72 3.1a6.48 6.48 0 0 0-.61 9.17c.88-2.65 2.54-5.2 3.96-6.63ZM8.27 7.12a15.15 15.15 0 0 0-3.32 6.62 6.47 6.47 0 0 0 6.18-.04 17.94 17.94 0 0 0-2.86-6.58h0Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/razor.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#5d5dff" stroke-linecap="round" d="M10.5 8v1c0 .67.4 1.21.97 1.55.58.34 1.53.34 2.1 0 .58-.34.94-.95.93-1.62V8a6.5 6.5 0 1 0-2.79 5.33M10.5 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0h0Z"/></svg>
+1
web/console/src/assets/workspace-icons/reactnative_config.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#00BFFF" d="M11.5 7.88c1.23-.44 2-1.06 2-1.75 0-1.34-2.91-2.42-6.5-2.42S.5 4.79.5 6.13C.5 7.47 3.41 8.56 7 8.56c.7 0 1.37-.04 2-.12m1.37-.94c-.29-.82-.7-1.7-1.22-2.58C7.35 1.88 4.94-.04 3.75.63c-1.19.67-.7 3.68 1.1 6.72a13.98 13.98 0 0 0 2.48 3.15M4.85 4.92c-1.8 3.04-2.29 6.04-1.1 6.71 1.19.67 3.6-1.25 5.4-4.28 1.8-3.04 2.29-6.05 1.1-6.72-1.19-.67-3.6 1.25-5.4 4.3ZM7 6.5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Z"/><path stroke="#6c7280" d="M11.5 13.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm1.75-4 1.75 3-1.75 3h-3.5L8 12.5l1.75-3h3.5Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/readme.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#008080" d="M8 14.5a6.5 6.5 0 1 0 0-13 6.5 6.5 0 0 0 0 13Zm-.4-10h.8c.06 0 .1.04.1.1v.8a.1.1 0 0 1-.1.1h-.8a.1.1 0 0 1-.1-.1v-.8c0-.06.04-.1.1-.1Zm0 3h.8c.06 0 .1.04.1.1v3.8a.1.1 0 0 1-.1.1h-.8a.1.1 0 0 1-.1-.1V7.6c0-.06.04-.1.1-.1Z"/></svg>
+1
web/console/src/assets/workspace-icons/redwood.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round" d="M4.74 3.19v-.65L8 1l3.26 1.54v.65m-6.52 0L8 5.56M4.74 3.19 2.19 5.56m5.81 0L4.74 7.77M8 5.56l3.26-2.37M8 5.56l3.26 2.21m-6.52 0 3.26 3m-3.26-3L2.19 5.56m2.55 2.21L1.99 9.45M8 10.76 4.74 12.5M8 10.76l3.26-2.99m-3.26 3 3.26 1.72m-6.52 0L8 15l3.26-2.51m-6.52 0H3.5L2 9.45m.2-3.9L1.5 8.47l.5 1m9.26-6.27 2.55 2.37m-2.55 2.21 2.55-2.21m-2.55 2.21 2.75 1.68m-2.75 3.04h1.23L14 9.45m-.2-3.9.69 2.91-.5 1"/></svg>
+1
web/console/src/assets/workspace-icons/remix.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><rect width="6" height="1" x="2" y="14" fill="#333333" rx=".5"/><path stroke="#333333" stroke-linejoin="round" d="M10.5 15c0-1.67-.5-2.83-1.5-3.5H2.5v-3h6.63c.91-.17 1.37-.67 1.37-1.5s-.46-1.33-1.38-1.5H2.5v-3H11c1.67 1 2.5 2.33 2.5 4s-.67 3-2 4c1.33.67 2 2.17 2 4.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/renovate.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="M11.28 11.04c-.2-.3-.33-.53-.25-.85.05-.2.17-.38.31-.52C12.62 8.41 13.76 7.32 14.8 6c.09-.12.13-.27.17-.41.03-.12.03-.25.03-.38 0-.12-.01-.24-.05-.35a6.9 6.9 0 0 0-1.4-2.18c-.14-.1-.28-.2-.44-.27a1.14 1.14 0 0 0-.36-.09c-.16 0-.33.01-.48.05-.17.05-.29.13-.46.23"/><path stroke="#008080" d="m2.63 5.4 6.41-3.6c.91-.52 2.06-.2 2.59.69A1.83 1.83 0 0 1 10.95 5L4.54 8.62c-.9.51-2.06.2-2.59-.7a1.83 1.83 0 0 1 .68-2.52Z"/><path stroke="#333333" d="m12.12 14.75-1.34-2.26a.71.71 0 0 1-.08-.56.73.73 0 0 1 .35-.44l.77-.43a.77.77 0 0 1 1.02.27l1.34 2.26"/></g></svg>
+1
web/console/src/assets/workspace-icons/replit.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#ca7f00" d="M3.25 1.5h3.5c.41 0 .75.34.75.75v2.5c0 .41-.34.75-.75.75h-3.5a.75.75 0 0 1-.75-.75v-2.5c0-.41.34-.75.75-.75Zm0 10h3.5c.41 0 .75.34.75.75v2.5c0 .41-.34.75-.75.75h-3.5a.75.75 0 0 1-.75-.75v-2.5c0-.41.34-.75.75-.75Zm6-5h3.5c.41 0 .75.34.75.75v2.5c0 .41-.34.75-.75.75h-3.5a.75.75 0 0 1-.75-.75v-2.5c0-.41.34-.75.75-.75Z"/></svg>
+1
web/console/src/assets/workspace-icons/riot.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="round" stroke-linejoin="round" d="M1.5 1.5v11c0 1.33.67 2 2 2v-11H10c1.67 0 2.5 1 2.5 3h2c0-3.17-1.33-4.83-4-5h-9Zm5 6c0 1.33.83 2 2.5 2h3.5v5c1.17 0 1.83-.67 2-2V10c0-1.5-1-2.33-3-2.5h-5Z"/></svg>
+1
web/console/src/assets/workspace-icons/roblox.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#00BFFF" d="M8.8 10.8 2.04 9l-.78 2.89 10.63 2.85 1.3-4.83-3.87-1.04-.52 1.93ZM7.2 5.2 13.96 7l.78-2.89L4.1 1.26 2.81 6.1l3.87 1.04.52-1.93Z"/></svg>
+1
web/console/src/assets/workspace-icons/robots.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#5d5dff" d="M3.5 5.5h9a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1h-9a1 1 0 0 1-1-1v-7a1 1 0 0 1 1-1Z"/><circle cx="6" cy="9" r="1" fill="#5d5dff"/><circle cx="10" cy="9" r="1" fill="#5d5dff"/><path stroke="#5d5dff" stroke-linecap="square" d="M8 5.5v-2"/><path fill="#000" fill-rule="nonzero" stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round" d="M.5 8.5v3M15.5 8.5v3"/><circle cx="8" cy="2.5" r="1" stroke="#5d5dff"/><path stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round" d="M6.5 12.5h3"/></g></svg>
+1
web/console/src/assets/workspace-icons/rollup.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#e45649" d="M4.37 14.5h9.13l-2-5c1.36-1.08 2-2.13 2-4 0-2.4-2.25-3.97-4-4h-7v11.3"/><path stroke="#e0c240" d="M11.46 6.2c-3.25 3.32-4.37 4.25-7.2 8.3-1.03-.13-1.67-.61-1.74-1.55-.36-.35 4.87-8.22 5.42-9.16.6-1 2.55-2.22 3.78-1.4-1.18-.38-2.58.56-2.41 1.5l.47 1.89c.34.58.66.8 1.18.73.56-.16.67-.53.63-1l-.36-.99"/></g></svg>
+1
web/console/src/assets/workspace-icons/rome.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#e66861" d="M1.99 9.5H4M12 9.5h2"/><path stroke="#e0c240" stroke-linecap="square" d="M9.5 14.52V8a1.5 1.5 0 0 0-3 0v6.52"/><path stroke="#e0c240" stroke-linecap="square" d="M11.5 14.52V8a3.5 3.5 0 0 0-7 0v6.52"/><path stroke="#e66861" stroke-linecap="square" stroke-linejoin="round" d="M12.5 14.5h2v-6a6.5 6.5 0 0 0-3.99-6m-5.02 0a6.5 6.5 0 0 0-3.99 6v6l2.03.02"/><path stroke="#e0c240" stroke-linecap="round" stroke-linejoin="round" d="M5.5 1.5h5l-1.48 3H7z"/></g></svg>
+1
web/console/src/assets/workspace-icons/rspec.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round"><path stroke="#e45649" stroke-linejoin="round" d="M8 12.5 12.5 7l-2-2.5h-5L3.5 7z"/><path stroke="#00BFFF" d="M9 1.58a6.5 6.5 0 0 1 5.06 8.76m-1 1.74a6.49 6.49 0 0 1-10.12 0m-1-1.74A6.48 6.48 0 0 1 7 1.58"/></g></svg>
+1
web/console/src/assets/workspace-icons/rubocop.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#333333" stroke-linecap="square" d="M3.5 10.5v4l1 1h7l1-1v-4m-5.5 5 .5-1h1l.5 1m-3.75-1.75L6.5 12.5h3l1.25 1.25M3.5 6.5h9l1 1v2l-1 1h-9l-1-1v-2l1-1Zm9 0V6a4.5 4.5 0 1 0-9 0v.5"/><path stroke="#e45649" stroke-linecap="round" d="M4.5 8.5h7"/></g></svg>
+1
web/console/src/assets/workspace-icons/ruby.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e45649" d="M1.5 9.06v2.5c.02.86.36 1.61.9 2.15 1.76 1.76 5.71.65 8.84-2.47 3.12-3.13 4.23-7.08 2.47-8.84a3.11 3.11 0 0 0-2.15-.9h-2.5M14.5 4l-.25 10.25L4 14.5m4.39-6.11c2.34-2.35 3.29-5.2 2.12-6.37S6.49 1.8 4.14 4.14C1.8 6.5.85 9.34 2.02 10.51s4.02.22 6.37-2.12ZM5.5 14.5l.25-3.75L11 11l-.25-5.25 3.75-.25"/></svg>
+1
web/console/src/assets/workspace-icons/ruby_gem.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="round" stroke-linejoin="round"><path d="m8 12.5 4.5-5-2-2h-5l-2 2z"/><path d="M14.5 12 8 15.5 1.5 12V4L8 .5 14.5 4z"/></g></svg>
+1
web/console/src/assets/workspace-icons/ruby_gem_lock.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#e45649" stroke-linecap="round" stroke-linejoin="round" d="m6 10.5 3.5-4-1.5-2H4l-1.5 2z"/><path stroke="#e45649" stroke-linecap="round" stroke-linejoin="round" d="M7.5 12.68 6 13.5l-5.5-3v-7L6 .5l5.5 3v3"/><path stroke="#6c7280" d="M15 11.5c.27 0 .5.22.5.5v3a.5.5 0 0 1-.5.5h-5a.5.5 0 0 1-.5-.5v-3c0-.28.22-.5.5-.5h5Zm-4 0V10a1.5 1.5 0 0 1 3 0v1.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/rust.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#ca7f00"><path stroke-linecap="round" stroke-linejoin="round" d="M15.5 9.5c-5 2.67-10 2.67-15 0l1-1-1-2 2-.5V4.5h2l.5-2 1.5 1 1.5-2 1.5 2 1.5-1 .5 2h2V6l2 .5-1 2 1 1Z"/><circle cx="5.5" cy="7.5" r="1"/><circle cx="10.5" cy="7.5" r="1"/><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 11c-.67.37-2 1-2 2.25s1.22 1.22 2 1.25v-2M11.5 11c.67.37 2 1 2 2.25s-1.22 1.22-2 1.25v-2"/></g></svg>
+1
web/console/src/assets/workspace-icons/salesforce.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#00BFFF" stroke-linecap="round" stroke-linejoin="round" d="M8.63 2.85c.99 0 1.85.57 2.31 1.43.41-.19.86-.29 1.3-.29a3.33 3.33 0 0 1 3.24 3.42 3.33 3.33 0 0 1-3.23 3.42c-.22 0-.43-.03-.64-.07a2.35 2.35 0 0 1-2.06 1.27c-.36 0-.72-.08-1.04-.25a2.7 2.7 0 0 1-2.48 1.72 2.7 2.7 0 0 1-2.53-1.84c-.17.03-.34.05-.51.05-1.39 0-2.5-1.19-2.5-2.65 0-.98.5-1.83 1.25-2.3-.16-.38-.24-.8-.24-1.2 0-1.7 1.3-3.06 2.91-3.06.94 0 1.79.47 2.32 1.2a2.56 2.56 0 0 1 1.9-.85Z"/></svg>
+1
web/console/src/assets/workspace-icons/sass.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#cc297b" stroke-linecap="round" stroke-linejoin="round" d="M6.75 6.38c1.85 1.07 3.35.74 4.83-.2 1.5-.95 2.7-2.78 1.3-4.15-.7-.68-3.25-.8-5.62.19-2.36.99-4.59 3.02-4.74 4.11-.31 2.19 3.15 2.88 3.64 4.23.49 1.35.28 1.98-.2 2.83-.5.85-1.96 1.62-2.8.68-.83-.95 1.67-2.75 2.98-3.25 1.3-.5 3.1-.4 3.69.25.58.64-.07 1.79-.03 1.79"/></svg>
+1
web/console/src/assets/workspace-icons/scala.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="round" stroke-linejoin="round"><path d="m2.5 2.48 11-.98v3.04l-11 1V2.48ZM2.5 7.48l11-.98v3.04l-11 1V7.48ZM2.5 12.48l11-.98v3.04l-11 1v-3.06Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/scheme.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round" d="M2.48 14.5 8 7.5m5.56 7c-4.87 0-4.87-13-9.75-13"/></svg>
+1
web/console/src/assets/workspace-icons/search.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333"><circle cx="6.5" cy="6.5" r="6"/><path stroke-linecap="round" stroke-linejoin="round" d="M15.5 15.5 11 11"/></g></svg>
+1
web/console/src/assets/workspace-icons/semantic_release.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M1.5 11.5v-7L8 .5l6.5 4v7l-6.5 4-6.5-4Zm6.5-2a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM1.75 7C2.25 8 3 8.5 4 8.5m0 4.41c1.13.07 1.93-.33 2.43-1.2m3.83 2.2c.62-.93.67-1.83.17-2.7M14.25 9C13.75 8 13 7.5 12 7.5m0-4.41c-1.13-.07-1.93.33-2.43 1.2m-3.83-2.2c-.62.93-.67 1.83-.17 2.7"/></svg>
+1
web/console/src/assets/workspace-icons/semgrep.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#50a14f" d="M3 10.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Zm5 0a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Zm5 0a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z"/></svg>
+1
web/console/src/assets/workspace-icons/semgrep_ignore.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#6c7280" d="M3 10.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Zm5 0a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Zm5 0a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z"/></svg>
+1
web/console/src/assets/workspace-icons/sentry.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="round" stroke-linejoin="round" d="M1.5 12.12c0 .26.07.52.22.74.25.37.68.62 1.15.64H5.1c0-1.54-1.08-2.9-2.64-3.3l1.47-2.43a6.2 6.2 0 0 1 4.07 5.73H13.06c.48 0 .93-.24 1.2-.62.26-.38.31-.85.13-1.28L9.26 3.2c-.25-.43-.74-.7-1.26-.7-.53 0-1.01.27-1.27.7L5.4 5.39a8.91 8.91 0 0 1 5.5 8.12"/></svg>
+1
web/console/src/assets/workspace-icons/sequelize.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#3b8ad8" d="M1.5 11.5v-7L8 .5l6.5 4v7l-6.5 4z"/><path stroke="#00BFFF" d="M12.5 5.75 8 3 3.5 5.75v4.5L8 13l4.5-2.75v-4.5ZM3.5 6 8 8.5V13m0-4.5L12.5 6M6 4.75l4.25 2.5v4.25M3.5 8.25 8 10.5l4.5-2.25M5.75 11.5V7.25l4.5-2.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/serverless.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="round" d="m6.5 11.5-1 2h9v-2h-8Zm2-4-1 2h7v-2h-6Zm2-4-1 2h5v-2h-4Zm-8 10 1-2h-2v2h1Zm2-4 1-2h-4v2h3Zm2-4 1-2h-6v2h5Z"/></svg>
+1
web/console/src/assets/workspace-icons/shader.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round"><path d="M6.41 12c0 .9-.47 1.72-1.23 2.17-.76.44-1.7.44-2.45 0A2.5 2.5 0 0 1 1.5 12c0-1.38 1.1-2.5 2.46-2.5A2.48 2.48 0 0 1 6.4 12h0ZM10.19 4.77A2.85 2.85 0 0 0 4.6 3.6a2.9 2.9 0 0 0 1.77 3.5c2.19.88 2.7 1.75 2.43 4.13a2.85 2.85 0 0 0 5.58 1.17 2.9 2.9 0 0 0-1.77-3.5c-2.19-.88-2.7-1.76-2.43-4.13h0Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/sketch.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round"><path d="m.7 6.9 6.67 7.32c.34.37.91.37 1.24 0l6.67-7.32c.26-.29.3-.72.07-1.04l-2.94-4a.83.83 0 0 0-.69-.36H4.28a.83.83 0 0 0-.68.36l-2.95 4A.84.84 0 0 0 .72 6.9H.7ZM2.5 6.5h11M12 6.5 9 3M4 6.5 7 3M7 11.5l-3-5M12 6.5l-3 5M12 6.5l-.5-3M4 6.5l.5-3"/></g></svg>
+1
web/console/src/assets/workspace-icons/slide.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#ca7f00"><path stroke-linecap="round" stroke-linejoin="round" d="M2.5 3.13c0-.77.86-1.63 1.62-1.63h9.76c.76 0 1.62.86 1.62 1.63v9.75c0 .76-.86 1.62-1.62 1.62H4.13c-.77 0-1.63-.86-1.63-1.62"/><path d="M7.5 5.8 11.88 8 7.5 10.2V5.8Z"/><path stroke-linecap="square" d="M.5 5.5v5"/><path d="M.5 8.5H2a1.5 1.5 0 0 0 0-3H.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/slim.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" d="M11.5 3.81c.35.06 1.03.21 2.04.46.56.13.96.63.96 1.21v8.06c0 .57-.39 1.07-.94 1.21-1.98.5-3.83.75-5.56.75-1.73 0-3.58-.25-5.56-.75a1.25 1.25 0 0 1-.94-1.21V5.45c0-.56.38-1.06.93-1.2 1.03-.27 1.72-.44 2.07-.5"/><circle cx="8" cy="4" r="3.5" stroke="#ca7f00"/><path stroke="#ca7f00" d="M8 4v2"/><path stroke="#6c7280" stroke-linecap="round" d="m3.5 10.5 1 2m1-2 1 2m4-2-1 2m3-2-1 2"/></g></svg>
+1
web/console/src/assets/workspace-icons/smarty.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e0c240" stroke-linecap="round" stroke-linejoin="round" d="M5.5 12.5h5m-1 .5v-1.73a5 5 0 1 0-3 0v3.23h3V13Z"/></svg>
+1
web/console/src/assets/workspace-icons/snowpack.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#007ACC" stroke-linecap="round" stroke-linejoin="round"><path d="M1.5 13.5h13L8 2z"/><path d="m5 8 1.5 1.5 1.5-2h3"/></g></svg>
+1
web/console/src/assets/workspace-icons/solidity.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round"><path d="m3 11.5 2.5 4 2.5-4 2.5 4 2.5-4-2.5-4-2.5 4m2.5 4h-5m7.5-4H3M13 4.5l-2.5-4-2.5 4-2.5-4-2.5 4 2.5 4 2.5-4M5.5.5h5M3 4.5h10"/></g></svg>
+1
web/console/src/assets/workspace-icons/sonar_cloud.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round"><path d="M8 7.38a4 4 0 1 0 0 6.24"/><path d="M8 7.38a4 4 0 1 0 0 6.24"/><path d="M6.5 10.5a4 4 0 1 0 5.49-3.71"/><path d="M9.5 10.21A4 4 0 1 0 4 6.8"/></g></svg>
+1
web/console/src/assets/workspace-icons/spreadsheet.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#50a14f"><path stroke-linecap="round" stroke-linejoin="round" d="M2.5 3.13c0-.77.86-1.63 1.62-1.63h9.76c.76 0 1.62.86 1.62 1.63v9.75c0 .76-.86 1.62-1.62 1.62H4.13c-.77 0-1.63-.86-1.63-1.62M.5 5.5l4 5M4.5 5.5l-4 5"/><path stroke-linejoin="round" d="M7.5 5.5h5v5h-5z"/><path stroke-linecap="square" d="M9.5 5.5v5M7.5 7.5h5"/></g></svg>
+1
web/console/src/assets/workspace-icons/stackblitz.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round" d="m3.5 9.5 7.04-9-2.04 6h4l-7.04 9 2.04-6z"/></svg>
+1
web/console/src/assets/workspace-icons/stencil.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round" d="M14.5 6.5H4l-2.5 3H12l2.5-3Zm-8 5h5l-2.5 3H4l2.5-3ZM8 1.5h5l-2.5 3h-5l2.5-3Z"/></svg>
+1
web/console/src/assets/workspace-icons/stitches.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333"><path stroke-linecap="round" stroke-linejoin="round" d="m6.23 14.25 8.08-4.66M4.09 13.18 11 9.19 8.58 5"/><path stroke-linecap="round" stroke-linejoin="round" d="M7.54 11.2 5.1 6.96l6.96-4.01"/><circle cx="8" cy="8" r="6.5" stroke-linecap="round" stroke-linejoin="round"/><path stroke-linecap="round" stroke-linejoin="round" d="M1.65 6.64 10 1.82"/><path stroke-linecap="square" d="m6.85 6 2.4 4.15"/></g></svg>
+1
web/console/src/assets/workspace-icons/storybook.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="M6.5 10.1c.45 1.05 1.23 1.4 2.46 1.4h-.21c1.35 0 2.25-.68 2.25-1.7 0-.84-.62-1.26-1.61-1.64L7.91 7.6c-.86-.33-1.41-1-1.41-1.73 0-.68.78-1.26 1.67-1.33l.36-.03c1.14-.1 2.24.53 2.47 1.39"/><path stroke="#e45649" d="M3.5 2.5 4 14l9.5.5v-13zM11.5 1.75v1.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/storybook_svelte.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="M6.5 10.1c.45 1.05 1.23 1.4 2.46 1.4h-.21c1.35 0 2.25-.68 2.25-1.7 0-.84-.62-1.26-1.61-1.64L7.91 7.6c-.86-.33-1.41-1-1.41-1.73 0-.68.78-1.26 1.67-1.33l.36-.03c1.14-.1 2.24.53 2.47 1.39"/><path stroke="#ca7f00" d="M3.5 2.5 4 14l9.5.5v-13zM11.5 1.75v1.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/storybook_vue.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#333333" d="M6.5 10.1c.45 1.05 1.23 1.4 2.46 1.4h-.21c1.35 0 2.25-.68 2.25-1.7 0-.84-.62-1.26-1.61-1.64L7.91 7.6c-.86-.33-1.41-1-1.41-1.73 0-.68.78-1.26 1.67-1.33l.36-.03c1.14-.1 2.24.53 2.47 1.39"/><path stroke="#50a14f" d="M3.5 2.5 4 14l9.5.5v-13zM11.5 1.75v1.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/stylelint.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M11.5 3.48 12 1.5h1.5l2 2-1.5 1L15 6l-7 9.5 2.72-9.32M6.5 3.5l-2-1v4l2-1"/><path stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="m9.5 3.5 2-1v4l-2-1M4.5 3.48 4 1.5H2.5l-2 2 1.5 1L1 6l7 9.5-2.72-9.33"/><circle cx="8" cy="8" r="1" fill="#333333"/><path stroke="#333333" stroke-linejoin="round" d="M6.5 3.5h3v2h-3z"/></g></svg>
+1
web/console/src/assets/workspace-icons/stylelint_ignore.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#6c7280" stroke-linecap="round" stroke-linejoin="round" d="M11.5 3.48 12 1.5h1.5l2 2-1.5 1L15 6l-7 9.5 2.72-9.32M6.5 3.5l-2-1v4l2-1"/><path stroke="#6c7280" stroke-linecap="round" stroke-linejoin="round" d="m9.5 3.5 2-1v4l-2-1M4.5 3.48 4 1.5H2.5l-2 2 1.5 1L1 6l7 9.5-2.72-9.33"/><circle cx="8" cy="8" r="1" fill="#6c7280"/><path stroke="#6c7280" stroke-linejoin="round" d="M6.5 3.5h3v2h-3z"/></g></svg>
+1
web/console/src/assets/workspace-icons/sublime.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e0c240" stroke-linecap="round" stroke-linejoin="round" d="m13.5 11.5-11 3v-3l6.29-1.71M2.5 4.5l11-3v3L7.21 6.21m6.29 5.29v-3l-11-4v3l11 4Z"/></svg>
+1
web/console/src/assets/workspace-icons/svelte.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round"><path d="M12.86 6.72s1.39-1.98.08-3.87C11.63.95 9.44 1.6 9.44 1.6S6.15 3.35 4.33 4.59c-1.4 1-2.24 2.26-1.03 4.37 1.22 2.1 4.58 1.21 4.58 1.21"/><path d="M3.14 9.28s-1.39 1.98-.08 3.87c1.31 1.9 3.5 1.24 3.5 1.24s3.29-1.74 5.11-2.98c1.4-1 2.24-2.26 1.03-4.37-1.22-2.1-4.58-1.21-4.58-1.21M6.3 6.96l4.14-2.56M5.52 11.65 9.66 9.1"/></g></svg>
+1
web/console/src/assets/workspace-icons/svelte_config.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#ca7f00" d="M9.92 5.32s1.26-1.83.07-3.58C8.79 0 6.8.6 6.8.6s-3 1.6-4.65 2.75C.88 4.28.13 5.44 1.23 7.39 2.33 9.33 5.39 8.5 5.39 8.5m-4.3-.82S-.19 9.51 1 11.26C2.21 13 4.2 12.4 4.2 12.4s1.76-.94 3.32-1.9m2.79-3a3.36 3.36 0 0 0-.53-1.89C8.67 3.67 5.61 4.5 5.61 4.5M3.96 5.54l3.76-2.36M3.25 9.87 7 7.5"/><path stroke="#6c7280" d="M11.5 13.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm1.75-4 1.75 3-1.75 3h-3.5L8 12.5l1.75-3h3.5Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/svg.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linecap="round" d="m4.54 10 6.92-4m-6.92 4a1.5 1.5 0 1 0-2.6 1.5 1.5 1.5 0 0 0 2.6-1.5ZM8 4v8m0-8a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM4.54 6l6.92 4M4.54 6a1.5 1.5 0 1 0-2.6-1.5A1.5 1.5 0 0 0 4.54 6ZM8 12a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm3.46-2a1.5 1.5 0 1 0 2.6 1.5 1.5 1.5 0 0 0-2.6-1.5Zm0-4a1.5 1.5 0 1 0 2.6-1.5 1.5 1.5 0 0 0-2.6 1.5Z"/></svg>
+1
web/console/src/assets/workspace-icons/swagger.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#50a14f"><path d="M6 4.5h-.81c-.47 0-.7.48-.69.95v1.28C4.5 7.43 3.52 8 3 8c.52 0 1.5.57 1.5 1.27v1.28c0 .47.22.95.69.95H6m4-7h.81c.47 0 .7.48.69.95v1.28c0 .7.98 1.27 1.5 1.27-.52 0-1.5.57-1.5 1.27v1.28c0 .47-.22.95-.69.95H10"/><path d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14ZM6.5 8.5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm3 0a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/swift.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round" d="M14.34 10.2c.34-1.08 1.1-5.07-4.45-8.62a.48.48 0 0 0-.6.07.44.44 0 0 0-.02.6c.03.02 2.07 2.5 1.34 5.34-1.26-.86-6.24-4.81-6.24-4.81L7.25 7.5 1.9 4.05S5.68 8.7 8 10.45c-1.12.4-3.56.82-6.78-1.18a.48.48 0 0 0-.58.06.44.44 0 0 0-.08.56c.11.18 2.7 4.36 8.14 4.36 1.5 0 2.37-.42 3.08-.77.43-.2.77-.37 1.14-.37.93 0 1.54.92 1.54.93.1.14.27.22.44.21a.46.46 0 0 0 .4-.28c.67-1.55-.49-3.2-.96-3.78h0Z"/></svg>
+1
web/console/src/assets/workspace-icons/syncpack.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#00BFFF" d="M5.5 6.5 3 9 .5 6.5h1.17A6.5 6.5 0 0 1 14.2 6c.03.11-.36-.22-1.19-1l-.97 1a4.5 4.5 0 0 0-8.28.5H5.5Z"/><path stroke="#50a14f" d="M10.5 9.5 13 7l2.5 2.5h-1.17A6.5 6.5 0 0 1 1.8 10c-.03-.11.36.22 1.19 1l.97-1a4.5 4.5 0 0 0 8.28-.5H10.5Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/tailwind.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#007ACC" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2.5c-2 0-3.25 1.11-3.75 3.33.75-1.1 1.63-1.52 2.62-1.25.58.16.98.62 1.43 1.13.74.83 1.6 1.79 3.45 1.79 2 0 3.25-1.11 3.75-3.33-.75 1.1-1.63 1.52-2.63 1.25-.57-.16-.97-.62-1.42-1.13C10.7 3.46 9.85 2.5 8 2.5ZM4.25 8.5C2.25 8.5 1 9.61.5 11.83c.75-1.1 1.63-1.52 2.63-1.25.57.16.97.62 1.42 1.13.74.83 1.6 1.79 3.45 1.79 2 0 3.25-1.11 3.75-3.33-.75 1.1-1.63 1.52-2.62 1.25-.58-.16-.98-.62-1.43-1.13-.74-.83-1.6-1.79-3.45-1.79Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/task.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#007ACC" stroke-linecap="round" stroke-linejoin="round" d="M14.5 11.75 8 15.5l-6.5-3.75v-7.5L8 .5l6.5 3.75v7.5Zm-13-7.5L8 8m6.5-3.75L8 8m0 0v7.5"/></svg>
+1
web/console/src/assets/workspace-icons/tauri.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#e0c240" stroke-linecap="round" stroke-linejoin="round" d="M4.73 4.02a4.64 4.64 0 1 1 5.55 6.56"/><circle cx="7" cy="10" r="1" fill="#00BFFF"/><path stroke="#00BFFF" stroke-linecap="round" stroke-linejoin="round" d="M11.26 12a4.64 4.64 0 1 1-5.63-6.55"/><circle cx="9" cy="6" r="1" fill="#e0c240"/></g></svg>
+1
web/console/src/assets/workspace-icons/taurignore.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#6c7280" stroke-linecap="round" stroke-linejoin="round" d="M4.73 4.02a4.64 4.64 0 1 1 5.55 6.56"/><circle cx="7" cy="10" r="1" fill="#6c7280"/><path stroke="#6c7280" stroke-linecap="round" stroke-linejoin="round" d="M11.26 12a4.64 4.64 0 1 1-5.63-6.55"/><circle cx="9" cy="6" r="1" fill="#6c7280"/></g></svg>
+1
web/console/src/assets/workspace-icons/terraform.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round" d="m1.5 6 8 4.25 4-2.25m-12-2V1.5l8 4.25 4-2.25V8m-4-2.25v8.75M5.53 3.82 5.5 12.5l4 2"/></svg>
+1
web/console/src/assets/workspace-icons/textlint.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="square" stroke-linejoin="round"><path stroke="#cc297b" d="M6.5 11.5h-2v-7h-3v10h13v-3h-5"/><path stroke="#00BFFF" d="M1.5 1.5h13v3h-5v10h-3v-10h-5z"/></g></svg>
+1
web/console/src/assets/workspace-icons/tldraw.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#333333" stroke-linejoin="round" d="M4 1.5h8A2.5 2.5 0 0 1 14.5 4v8a2.5 2.5 0 0 1-2.5 2.5H4A2.5 2.5 0 0 1 1.5 12V4A2.5 2.5 0 0 1 4 1.5Zm4 5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm-.5 7c1.33-1.33 2-2.5 2-3.5A1.5 1.5 0 1 0 8 11.5h.5l-1 2Z"/></svg>
+1
web/console/src/assets/workspace-icons/todo.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#50a14f"><circle cx="8" cy="8" r="6.5"/><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 7.5 2.5 3 4.5-5"/></g></svg>
+1
web/console/src/assets/workspace-icons/toml.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#6c7280" d="M3.5 1.5h-2v13h2M12.5 1.5h2v13h-2"/><path stroke="#333333" d="M4.5 3.5h7v3h-2v6h-3v-6h-2z"/></g></svg>
+1
web/console/src/assets/workspace-icons/travis.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#333333" stroke-linecap="square" stroke-linejoin="round" d="m2 8.5.5 2 1.5 1 3-.5 1-1.5L9 11l3 .5 1.5-1 .5-2"/><path stroke="#e45649" stroke-linejoin="round" d="M6 2.5h4M8 2.5V6"/><path stroke="#333333" d="M5 9.5h1M10 9.5h1"/><path stroke="#333333" stroke-linecap="square" d="M1.5 8.5c1 4.67 3.17 7 6.5 7s5.5-2.33 6.5-7"/><path stroke="#e0c240" stroke-linecap="square" stroke-linejoin="round" d="M2 4.5c1.33-2.67 3.33-4 6-4s4.67 1.33 6 4l1.5 1V8c-2-.33-4.5-.5-7.5-.5S2.5 7.67.5 8V5.5l1.5-1Z"/><path stroke="#e45649" stroke-linecap="round" stroke-linejoin="round" d="M1.5 13.5 3 14l3.5-1.5h3L13 14l1.5-.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/tree.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#50a14f" stroke-linecap="square" d="M14.5 14.5v-3h-4v3zM14.5 9.5v-3h-4v3z"/><path stroke="#333333" d="M3.5 6v7.5H9m-5.5-5H9"/><path stroke="#50a14f" stroke-linecap="square" d="M1.5 1.5v3h4v-3z"/></g></svg>
+1
web/console/src/assets/workspace-icons/twig.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round"><path d="M.5 11.5c1 1 1.67 2 2 3 .33-1 .33-3 0-6 1.33 1.33 2.17 3.33 2.5 6 .33-1.67.83-3 1.5-4M2.5 14.5H5M8 8c1 1.67 1.67 3.83 2 6.5-.33-4.67.83-9 3.5-13-1 6.33-1.33 10.67-1 13 .67-2 1.33-3.33 2-4M10 14.5h2.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/txt.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333"><path d="M13.5 6.5v6a2 2 0 0 1-2 2h-7a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h4.01"/><path stroke-linejoin="round" d="m8.5 1.5 5 5h-4a1 1 0 0 1-1-1v-4Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M5.5 11.5h5M5.5 8.5h5M5.5 5.5h1"/></g></svg>
+1
web/console/src/assets/workspace-icons/typescript.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8"><rect width="13" height="13" x="1.5" y="1.5" rx="2.5"/><path stroke-linecap="round" stroke-linejoin="round" d="M12.5 8.75c0-.69-.54-1.25-1.2-1.25h-.6c-.66 0-1.2.56-1.2 1.25S10.04 10 10.7 10h.6c.66 0 1.2.56 1.2 1.25s-.54 1.25-1.2 1.25h-.6c-.66 0-1.2-.56-1.2-1.25"/><path stroke-linejoin="round" d="M6.5 7.5V13"/><path stroke-linecap="round" stroke-linejoin="round" d="M5 7.5h3"/></g></svg>
+1
web/console/src/assets/workspace-icons/typescript_config.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#6c7280" stroke-linecap="round" d="M11.5 13.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm1.75-4 1.75 3-1.75 3h-3.5L8 12.5l1.75-3h3.5Z"/><path stroke="#3b8ad8" stroke-linecap="round" d="M6.5 11.5h-4a2 2 0 0 1-2-2v-7c0-1.1.9-2 2-2h7a2 2 0 0 1 2 2V7h0"/><path stroke="#3b8ad8" stroke-linecap="round" d="M9.5 5c-.33-.33-.83-.5-1.5-.5-1 0-1.5.5-1.5 1s.5 1 1.5 1 1.5.5 1.5 1-.5 1-1.5 1c-.67 0-1.17-.17-1.5-.5"/><path stroke="#3b8ad8" d="M3.5 4.5V9M2 4.5h3"/></g></svg>
+1
web/console/src/assets/workspace-icons/typescript_def.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#3b8ad8" stroke-linecap="round" d="M12.5 4.5h1a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2h-7a2 2 0 0 1-2-2V10"/><path stroke="#3b8ad8" stroke-linecap="round" d="M13.5 9c-.33-.33-.83-.5-1.5-.5-1 0-1.5.5-1.5 1s.5 1 1.5 1 1.5.5 1.5 1-.5 1-1.5 1c-.67 0-1.17-.17-1.5-.5"/><path stroke="#3b8ad8" d="M7.5 8.5V13M6 8.5h3"/><path stroke="#007ACC" stroke-linecap="round" d="M2.04 7.88.5 3.02c-.05-.15.06-.31.23-.37L7.54.52c.1-.04.21-.02.3.04l2.63 1.84c.1.07.13.19.1.3L9.44 5.72a.34.34 0 0 1-.22.2l-6.8 2.13c-.17.06-.34-.02-.4-.17Zm5.13-3.23a1.21 1.21 0 1 0 .62-2.35 1.21 1.21 0 0 0-.62 2.35Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/typescript_react.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#3b8ad8" d="M8 11c4.5 0 7.5-1.35 7.5-3s-3-3-7.5-3S.5 6.35.5 8s3 3 7.5 3ZM5.4 9.5c2.25 3.9 4.92 5.82 6.35 5 1.43-.83 1.1-4.1-1.15-8-2.25-3.9-4.92-5.82-6.35-5-1.43.83-1.1 4.1 1.15 8Zm0-3c-2.25 3.9-2.58 7.17-1.15 8 1.43.82 4.1-1.1 6.35-5s2.58-7.17 1.15-8c-1.43-.82-4.1 1.1-6.35 5Zm2.6 2a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Z"/></svg>
+1
web/console/src/assets/workspace-icons/typescript_test.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8"><g stroke-linejoin="round"><path stroke-linecap="round" d="M15.5 12c-.33-.33-.83-.5-1.5-.5-1 0-1.5.5-1.5 1s.5 1 1.5 1 1.5.5 1.5 1-.5 1-1.5 1c-.67 0-1.17-.17-1.5-.5"/><path d="M9.5 11.5V16M8 11.5h3"/></g><path stroke-linecap="round" d="m10.72 1.75-8.49 8.48a2.5 2.5 0 1 0 3.54 3.54l.77-.77m3-3 4.71-4.72"/><path stroke-linejoin="round" d="m9 0 7 7m-4 .5H4.98"/></g></svg>
+1
web/console/src/assets/workspace-icons/uml.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#e0c240" d="M2.5 13V6.25l1 .5L5 12l1.5-3.75 1 .5V15"/><path stroke="#5d5dff" d="M10.5 8v6.5L15 12"/><path stroke="#e45649" d="m4 4.5 5 2c1.83.5 3 .25 3.5-.75S12.83 4 12 3.5l-5-2"/></g></svg>
+1
web/console/src/assets/workspace-icons/unity.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="m9.5 1 5 3v5.5m-1 3-5.5 3-5.5-3m-1-3V3.83L6.5 1m-5 3L8 8v7.5M14.5 4 8 8"/></svg>
+1
web/console/src/assets/workspace-icons/unocss.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#6c7280" d="M9.5 12.5a3 3 0 1 1 6 0 3 3 0 0 1-6 0Z"/><path stroke="#333333" stroke-linejoin="round" d="M6.5 12.5c0 1.75-1.25 3-3 3s-3-1.25-3-3v-3h6v3ZM9.5 3.5c0-1.75 1.25-3 3-3s3 1.25 3 3v3h-6v-3Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/url.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#00BFFF" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.25" d="m5.5 10.5 5-5M3.73 6.61 2.67 7.67a4 4 0 1 0 5.66 5.66l1.06-1.06m2.88-2.88 1.06-1.06a4 4 0 1 0-5.66-5.66L6.61 3.73"/></svg>
+1
web/console/src/assets/workspace-icons/vagrant.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linecap="square" stroke-linejoin="round" d="m8 14.5 2.5-1 4-10v-1l-1.5-1-2.5 1v1L8 8.5l-2.5-5v-1L3 1.5l-1.5 1v1l4 10 2.5 1Zm-2.5-11-1 .5v1.5l2 5 1.5 1 1.5-1 2-5V4l-1-.5"/></svg>
+1
web/console/src/assets/workspace-icons/velocity.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8"><circle cx="8" cy="8" r="4.5" stroke-linecap="round"/><path stroke-linecap="round" d="M8 3.5h6.5M3.5 8V1.5m4.5 11H1.5m11-4.5v6.5"/><path d="m14.5 3.5-1.41-1.41M14.5 3.5l-1.41 1.41M3.5 1.5 2.09 2.91M3.5 1.5l1.41 1.41M1.5 12.5l1.41 1.41M1.5 12.5l1.41-1.41m9.59 3.41 1.41-1.41M12.5 14.5l-1.41-1.41"/></g></svg>
+1
web/console/src/assets/workspace-icons/vercel.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#333333" stroke-linecap="round" stroke-linejoin="round" d="M1.5 13.5h13L8 2z"/></svg>
+1
web/console/src/assets/workspace-icons/vercel_ignore.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#6c7280" stroke-linecap="round" stroke-linejoin="round" d="M1.5 13.5h13L8 2z"/></svg>
+1
web/console/src/assets/workspace-icons/verilog.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#333333"><path d="M4.5 2.5h7a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2h-7a2 2 0 0 1-2-2v-7c0-1.1.9-2 2-2Z"/><path stroke-linejoin="round" d="M5.5 5.5h5v5h-5z"/><path d="M14 5.5h2M14 8h2m-2 2.5h2M0 5h2M0 7.5h2M0 10h2M5.5 2V0M8 2V0m2.5 2V0m-5 16v-2M8 16v-2m2.5 2v-2"/></g></svg>
+1
web/console/src/assets/workspace-icons/video.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8"><path stroke-linejoin="round" d="M3 2.5h10c.83 0 1.5.67 1.5 1.5v9c0 .83-.67 1.5-1.5 1.5H3A1.5 1.5 0 0 1 1.5 13V4c0-.83.67-1.5 1.5-1.5Z"/><path stroke-linecap="square" d="M2.5 5.5h11"/><path stroke-linecap="round" stroke-linejoin="round" d="m3.5 5.5 2-3M7 5.5l2-3M10.5 5.5l2-3M6.5 8v4l4-2z"/></g></svg>
+1
web/console/src/assets/workspace-icons/vim.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#6c7280" d="m13.5 5.5 1.44 1.44a1.5 1.5 0 0 1 0 2.12l-5.88 5.88a1.5 1.5 0 0 1-2.12 0l-.44-.44h0m-5-5-.44-.44a1.5 1.5 0 0 1 0-2.12l.44-.44h0m5-5 .44-.44a1.5 1.5 0 0 1 2.12 0l.44.44h0"/><path stroke="#50a14f" d="M1.5 2.5h4M2.5 2.5v11h3l8-11H11l-6.5 9v-9M14.5 2.5H9"/></g></svg>
+1
web/console/src/assets/workspace-icons/visual_studio.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round"><path d="M10.5 11 3 4.5h-.5l-1 1V6l9 8.5 4-2v-9l-4-2v13M10.5 1.5 5.3 6.41M3.53 8.08 1.5 10v.5l.98 1.1.52-.1 2.17-1.88m1.91-1.66L10.5 5"/></g></svg>
+1
web/console/src/assets/workspace-icons/vite.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#5d5dff" d="m11.5 5.5 3-1-6.5 11-6.5-11 3 1"/><path stroke="#e0c240" d="m6 1.5-.5 5 2-1-1 3L8 8v3l4-7.5-2 .5L11.5.5z"/></g></svg>
+3752
web/console/src/assets/workspace-icons/vitesse-light-theme.json
··· 1 + { 2 + "hidesExplorerArrows": true, 3 + "file": "file", 4 + "folder": "folder", 5 + "folderExpanded": "folder__open", 6 + "rootFolder": "folder_root", 7 + "rootFolderExpanded": "folder_root__open", 8 + "iconDefinitions": { 9 + "_3d": { "iconPath": "./icons/_3d.svg" }, 10 + "adonis": { "iconPath": "./icons/adonis.svg" }, 11 + "advpl": { "iconPath": "./icons/advpl.svg" }, 12 + "alex": { "iconPath": "./icons/alex.svg" }, 13 + "android": { "iconPath": "./icons/android.svg" }, 14 + "angular": { "iconPath": "./icons/angular.svg" }, 15 + "ansible": { "iconPath": "./icons/ansible.svg" }, 16 + "antlr": { "iconPath": "./icons/antlr.svg" }, 17 + "api_blueprint": { "iconPath": "./icons/api_blueprint.svg" }, 18 + "apl": { "iconPath": "./icons/apl.svg" }, 19 + "apollo": { "iconPath": "./icons/apollo.svg" }, 20 + "apple": { "iconPath": "./icons/apple.svg" }, 21 + "apps_script": { "iconPath": "./icons/apps_script.svg" }, 22 + "appveyor": { "iconPath": "./icons/appveyor.svg" }, 23 + "arduino": { "iconPath": "./icons/arduino.svg" }, 24 + "artistic_style": { "iconPath": "./icons/artistic_style.svg" }, 25 + "asciidoc": { "iconPath": "./icons/asciidoc.svg" }, 26 + "assembly": { "iconPath": "./icons/assembly.svg" }, 27 + "astro": { "iconPath": "./icons/astro.svg" }, 28 + "astro_config": { "iconPath": "./icons/astro_config.svg" }, 29 + "audio": { "iconPath": "./icons/audio.svg" }, 30 + "aurelia": { "iconPath": "./icons/aurelia.svg" }, 31 + "auto": { "iconPath": "./icons/auto.svg" }, 32 + "autohotkey": { "iconPath": "./icons/autohotkey.svg" }, 33 + "autoit": { "iconPath": "./icons/autoit.svg" }, 34 + "azure": { "iconPath": "./icons/azure.svg" }, 35 + "azure_pipelines": { "iconPath": "./icons/azure_pipelines.svg" }, 36 + "babel": { "iconPath": "./icons/babel.svg" }, 37 + "ballerina": { "iconPath": "./icons/ballerina.svg" }, 38 + "bash": { "iconPath": "./icons/bash.svg" }, 39 + "bat": { "iconPath": "./icons/bat.svg" }, 40 + "bazel": { "iconPath": "./icons/bazel.svg" }, 41 + "bazel_ignore": { "iconPath": "./icons/bazel_ignore.svg" }, 42 + "bicep": { "iconPath": "./icons/bicep.svg" }, 43 + "binary": { "iconPath": "./icons/binary.svg" }, 44 + "biome": { "iconPath": "./icons/biome.svg" }, 45 + "bitbucket": { "iconPath": "./icons/bitbucket.svg" }, 46 + "bithound": { "iconPath": "./icons/bithound.svg" }, 47 + "blitz": { "iconPath": "./icons/blitz.svg" }, 48 + "bower": { "iconPath": "./icons/bower.svg" }, 49 + "browserslist": { "iconPath": "./icons/browserslist.svg" }, 50 + "buck": { "iconPath": "./icons/buck.svg" }, 51 + "buildkite": { "iconPath": "./icons/buildkite.svg" }, 52 + "bun": { "iconPath": "./icons/bun.svg" }, 53 + "bun_lock": { "iconPath": "./icons/bun_lock.svg" }, 54 + "c": { "iconPath": "./icons/c.svg" }, 55 + "cabal": { "iconPath": "./icons/cabal.svg" }, 56 + "caddy": { "iconPath": "./icons/caddy.svg" }, 57 + "cadence": { "iconPath": "./icons/cadence.svg" }, 58 + "cakephp": { "iconPath": "./icons/cakephp.svg" }, 59 + "capacitor": { "iconPath": "./icons/capacitor.svg" }, 60 + "cargo": { "iconPath": "./icons/cargo.svg" }, 61 + "cargo_lock": { "iconPath": "./icons/cargo_lock.svg" }, 62 + "certificate": { "iconPath": "./icons/certificate.svg" }, 63 + "changelog": { "iconPath": "./icons/changelog.svg" }, 64 + "chart": { "iconPath": "./icons/chart.svg" }, 65 + "chart_lock": { "iconPath": "./icons/chart_lock.svg" }, 66 + "chromium": { "iconPath": "./icons/chromium.svg" }, 67 + "circle_ci": { "iconPath": "./icons/circle_ci.svg" }, 68 + "clojure": { "iconPath": "./icons/clojure.svg" }, 69 + "cmake": { "iconPath": "./icons/cmake.svg" }, 70 + "cmake_in": { "iconPath": "./icons/cmake_in.svg" }, 71 + "cobol": { "iconPath": "./icons/cobol.svg" }, 72 + "cocoapods": { "iconPath": "./icons/cocoapods.svg" }, 73 + "cocoapods_lock": { "iconPath": "./icons/cocoapods_lock.svg" }, 74 + "coconut": { "iconPath": "./icons/coconut.svg" }, 75 + "code_climate": { "iconPath": "./icons/code_climate.svg" }, 76 + "code_of_conduct": { "iconPath": "./icons/code_of_conduct.svg" }, 77 + "codecov": { "iconPath": "./icons/codecov.svg" }, 78 + "codeowners": { "iconPath": "./icons/codeowners.svg" }, 79 + "coffeescript": { "iconPath": "./icons/coffeescript.svg" }, 80 + "command": { "iconPath": "./icons/command.svg" }, 81 + "commitlint": { "iconPath": "./icons/commitlint.svg" }, 82 + "contributing": { "iconPath": "./icons/contributing.svg" }, 83 + "cpp": { "iconPath": "./icons/cpp.svg" }, 84 + "craco": { "iconPath": "./icons/craco.svg" }, 85 + "crystal": { "iconPath": "./icons/crystal.svg" }, 86 + "csharp": { "iconPath": "./icons/csharp.svg" }, 87 + "css": { "iconPath": "./icons/css.svg" }, 88 + "css_map": { "iconPath": "./icons/css_map.svg" }, 89 + "csv": { "iconPath": "./icons/csv.svg" }, 90 + "cucumber": { "iconPath": "./icons/cucumber.svg" }, 91 + "cuda": { "iconPath": "./icons/cuda.svg" }, 92 + "cypress": { "iconPath": "./icons/cypress.svg" }, 93 + "d": { "iconPath": "./icons/d.svg" }, 94 + "dart": { "iconPath": "./icons/dart.svg" }, 95 + "dart_generated": { "iconPath": "./icons/dart_generated.svg" }, 96 + "database": { "iconPath": "./icons/database.svg" }, 97 + "denizen_script": { "iconPath": "./icons/denizen_script.svg" }, 98 + "deno": { "iconPath": "./icons/deno.svg" }, 99 + "deno_lock": { "iconPath": "./icons/deno_lock.svg" }, 100 + "dependabot": { "iconPath": "./icons/dependabot.svg" }, 101 + "detox": { "iconPath": "./icons/detox.svg" }, 102 + "dhall": { "iconPath": "./icons/dhall.svg" }, 103 + "diff": { "iconPath": "./icons/diff.svg" }, 104 + "disc": { "iconPath": "./icons/disc.svg" }, 105 + "django": { "iconPath": "./icons/django.svg" }, 106 + "doc": { "iconPath": "./icons/doc.svg" }, 107 + "docker": { "iconPath": "./icons/docker.svg" }, 108 + "docker_compose": { "iconPath": "./icons/docker_compose.svg" }, 109 + "docker_ignore": { "iconPath": "./icons/docker_ignore.svg" }, 110 + "docusaurus": { "iconPath": "./icons/docusaurus.svg" }, 111 + "dotjs": { "iconPath": "./icons/dotjs.svg" }, 112 + "drawio": { "iconPath": "./icons/drawio.svg" }, 113 + "editorconfig": { "iconPath": "./icons/editorconfig.svg" }, 114 + "ejs": { "iconPath": "./icons/ejs.svg" }, 115 + "elixir": { "iconPath": "./icons/elixir.svg" }, 116 + "elm": { "iconPath": "./icons/elm.svg" }, 117 + "ember": { "iconPath": "./icons/ember.svg" }, 118 + "env": { "iconPath": "./icons/env.svg" }, 119 + "epub": { "iconPath": "./icons/epub.svg" }, 120 + "erlang": { "iconPath": "./icons/erlang.svg" }, 121 + "esbuild": { "iconPath": "./icons/esbuild.svg" }, 122 + "eslint": { "iconPath": "./icons/eslint.svg" }, 123 + "eslint_ignore": { "iconPath": "./icons/eslint_ignore.svg" }, 124 + "exe": { "iconPath": "./icons/exe.svg" }, 125 + "fastlane": { "iconPath": "./icons/fastlane.svg" }, 126 + "favicon": { "iconPath": "./icons/favicon.svg" }, 127 + "fish": { "iconPath": "./icons/fish.svg" }, 128 + "figma": { "iconPath": "./icons/figma.svg" }, 129 + "file": { "iconPath": "./icons/file.svg" }, 130 + "firebase": { "iconPath": "./icons/firebase.svg" }, 131 + "flow": { "iconPath": "./icons/flow.svg" }, 132 + "folder": { "iconPath": "./icons/folder.svg" }, 133 + "folder__open": { "iconPath": "./icons/folder__open.svg" }, 134 + "folder_admin": { "iconPath": "./icons/folder_admin.svg" }, 135 + "folder_admin__open": { "iconPath": "./icons/folder_admin__open.svg" }, 136 + "folder_android": { "iconPath": "./icons/folder_android.svg" }, 137 + "folder_android__open": { "iconPath": "./icons/folder_android__open.svg" }, 138 + "folder_angular": { "iconPath": "./icons/folder_angular.svg" }, 139 + "folder_angular__open": { "iconPath": "./icons/folder_angular__open.svg" }, 140 + "folder_animation": { "iconPath": "./icons/folder_animation.svg" }, 141 + "folder_animation__open": { 142 + "iconPath": "./icons/folder_animation__open.svg" 143 + }, 144 + "folder_api": { "iconPath": "./icons/folder_api.svg" }, 145 + "folder_api__open": { "iconPath": "./icons/folder_api__open.svg" }, 146 + "folder_app": { "iconPath": "./icons/folder_app.svg" }, 147 + "folder_app__open": { "iconPath": "./icons/folder_app__open.svg" }, 148 + "folder_archive": { "iconPath": "./icons/folder_archive.svg" }, 149 + "folder_archive__open": { "iconPath": "./icons/folder_archive__open.svg" }, 150 + "folder_audio": { "iconPath": "./icons/folder_audio.svg" }, 151 + "folder_audio__open": { "iconPath": "./icons/folder_audio__open.svg" }, 152 + "folder_aws": { "iconPath": "./icons/folder_aws.svg" }, 153 + "folder_aws__open": { "iconPath": "./icons/folder_aws__open.svg" }, 154 + "folder_azure_pipelines": { 155 + "iconPath": "./icons/folder_azure_pipelines.svg" 156 + }, 157 + "folder_azure_pipelines__open": { 158 + "iconPath": "./icons/folder_azure_pipelines__open.svg" 159 + }, 160 + "folder_benchmark": { "iconPath": "./icons/folder_benchmark.svg" }, 161 + "folder_benchmark__open": { 162 + "iconPath": "./icons/folder_benchmark__open.svg" 163 + }, 164 + "folder_cart": { "iconPath": "./icons/folder_cart.svg" }, 165 + "folder_cart__open": { "iconPath": "./icons/folder_cart__open.svg" }, 166 + "folder_circle_ci": { "iconPath": "./icons/folder_circle_ci.svg" }, 167 + "folder_circle_ci__open": { 168 + "iconPath": "./icons/folder_circle_ci__open.svg" 169 + }, 170 + "folder_client": { "iconPath": "./icons/folder_client.svg" }, 171 + "folder_client__open": { "iconPath": "./icons/folder_client__open.svg" }, 172 + "folder_cloud": { "iconPath": "./icons/folder_cloud.svg" }, 173 + "folder_cloud__open": { "iconPath": "./icons/folder_cloud__open.svg" }, 174 + "folder_cocoapods": { "iconPath": "./icons/folder_cocoapods.svg" }, 175 + "folder_cocoapods__open": { 176 + "iconPath": "./icons/folder_cocoapods__open.svg" 177 + }, 178 + "folder_command": { "iconPath": "./icons/folder_command.svg" }, 179 + "folder_command__open": { "iconPath": "./icons/folder_command__open.svg" }, 180 + "folder_components": { "iconPath": "./icons/folder_components.svg" }, 181 + "folder_components__open": { 182 + "iconPath": "./icons/folder_components__open.svg" 183 + }, 184 + "folder_composables": { "iconPath": "./icons/folder_composables.svg" }, 185 + "folder_composables__open": { 186 + "iconPath": "./icons/folder_composables__open.svg" 187 + }, 188 + "folder_config": { "iconPath": "./icons/folder_config.svg" }, 189 + "folder_config__open": { "iconPath": "./icons/folder_config__open.svg" }, 190 + "folder_connection": { "iconPath": "./icons/folder_connection.svg" }, 191 + "folder_connection__open": { 192 + "iconPath": "./icons/folder_connection__open.svg" 193 + }, 194 + "folder_constant": { "iconPath": "./icons/folder_constant.svg" }, 195 + "folder_constant__open": { 196 + "iconPath": "./icons/folder_constant__open.svg" 197 + }, 198 + "folder_controllers": { "iconPath": "./icons/folder_controllers.svg" }, 199 + "folder_controllers__open": { 200 + "iconPath": "./icons/folder_controllers__open.svg" 201 + }, 202 + "folder_core": { "iconPath": "./icons/folder_core.svg" }, 203 + "folder_core__open": { "iconPath": "./icons/folder_core__open.svg" }, 204 + "folder_coverage": { "iconPath": "./icons/folder_coverage.svg" }, 205 + "folder_coverage__open": { 206 + "iconPath": "./icons/folder_coverage__open.svg" 207 + }, 208 + "folder_crontab": { "iconPath": "./icons/folder_crontab.svg" }, 209 + "folder_crontab__open": { "iconPath": "./icons/folder_crontab__open.svg" }, 210 + "folder_cypress": { "iconPath": "./icons/folder_cypress.svg" }, 211 + "folder_cypress__open": { "iconPath": "./icons/folder_cypress__open.svg" }, 212 + "folder_database": { "iconPath": "./icons/folder_database.svg" }, 213 + "folder_database__open": { 214 + "iconPath": "./icons/folder_database__open.svg" 215 + }, 216 + "folder_debug": { "iconPath": "./icons/folder_debug.svg" }, 217 + "folder_debug__open": { "iconPath": "./icons/folder_debug__open.svg" }, 218 + "folder_decorators": { "iconPath": "./icons/folder_decorators.svg" }, 219 + "folder_decorators__open": { 220 + "iconPath": "./icons/folder_decorators__open.svg" 221 + }, 222 + "folder_diff": { "iconPath": "./icons/folder_diff.svg" }, 223 + "folder_diff__open": { "iconPath": "./icons/folder_diff__open.svg" }, 224 + "folder_dist": { "iconPath": "./icons/folder_dist.svg" }, 225 + "folder_dist__open": { "iconPath": "./icons/folder_dist__open.svg" }, 226 + "folder_docker": { "iconPath": "./icons/folder_docker.svg" }, 227 + "folder_docker__open": { "iconPath": "./icons/folder_docker__open.svg" }, 228 + "folder_docs": { "iconPath": "./icons/folder_docs.svg" }, 229 + "folder_docs__open": { "iconPath": "./icons/folder_docs__open.svg" }, 230 + "folder_download": { "iconPath": "./icons/folder_download.svg" }, 231 + "folder_download__open": { 232 + "iconPath": "./icons/folder_download__open.svg" 233 + }, 234 + "folder_environment": { "iconPath": "./icons/folder_environment.svg" }, 235 + "folder_environment__open": { 236 + "iconPath": "./icons/folder_environment__open.svg" 237 + }, 238 + "folder_error": { "iconPath": "./icons/folder_error.svg" }, 239 + "folder_error__open": { "iconPath": "./icons/folder_error__open.svg" }, 240 + "folder_event": { "iconPath": "./icons/folder_event.svg" }, 241 + "folder_event__open": { "iconPath": "./icons/folder_event__open.svg" }, 242 + "folder_examples": { "iconPath": "./icons/folder_examples.svg" }, 243 + "folder_examples__open": { 244 + "iconPath": "./icons/folder_examples__open.svg" 245 + }, 246 + "folder_expo": { "iconPath": "./icons/folder_expo.svg" }, 247 + "folder_expo__open": { "iconPath": "./icons/folder_expo__open.svg" }, 248 + "folder_fastlane": { "iconPath": "./icons/folder_fastlane.svg" }, 249 + "folder_fastlane__open": { 250 + "iconPath": "./icons/folder_fastlane__open.svg" 251 + }, 252 + "folder_favorite": { "iconPath": "./icons/folder_favorite.svg" }, 253 + "folder_favorite__open": { 254 + "iconPath": "./icons/folder_favorite__open.svg" 255 + }, 256 + "folder_firebase": { "iconPath": "./icons/folder_firebase.svg" }, 257 + "folder_firebase__open": { 258 + "iconPath": "./icons/folder_firebase__open.svg" 259 + }, 260 + "folder_flow": { "iconPath": "./icons/folder_flow.svg" }, 261 + "folder_flow__open": { "iconPath": "./icons/folder_flow__open.svg" }, 262 + "folder_fonts": { "iconPath": "./icons/folder_fonts.svg" }, 263 + "folder_fonts__open": { "iconPath": "./icons/folder_fonts__open.svg" }, 264 + "folder_functions": { "iconPath": "./icons/folder_functions.svg" }, 265 + "folder_functions__open": { 266 + "iconPath": "./icons/folder_functions__open.svg" 267 + }, 268 + "folder_gamemaker": { "iconPath": "./icons/folder_gamemaker.svg" }, 269 + "folder_gamemaker__open": { 270 + "iconPath": "./icons/folder_gamemaker__open.svg" 271 + }, 272 + "folder_git": { "iconPath": "./icons/folder_git.svg" }, 273 + "folder_git__open": { "iconPath": "./icons/folder_git__open.svg" }, 274 + "folder_github": { "iconPath": "./icons/folder_github.svg" }, 275 + "folder_github__open": { "iconPath": "./icons/folder_github__open.svg" }, 276 + "folder_gitlab": { "iconPath": "./icons/folder_gitlab.svg" }, 277 + "folder_gitlab__open": { "iconPath": "./icons/folder_gitlab__open.svg" }, 278 + "folder_godot": { "iconPath": "./icons/folder_godot.svg" }, 279 + "folder_godot__open": { "iconPath": "./icons/folder_godot__open.svg" }, 280 + "folder_gradle": { "iconPath": "./icons/folder_gradle.svg" }, 281 + "folder_gradle__open": { "iconPath": "./icons/folder_gradle__open.svg" }, 282 + "folder_graphql": { "iconPath": "./icons/folder_graphql.svg" }, 283 + "folder_graphql__open": { "iconPath": "./icons/folder_graphql__open.svg" }, 284 + "folder_guard": { "iconPath": "./icons/folder_guard.svg" }, 285 + "folder_guard__open": { "iconPath": "./icons/folder_guard__open.svg" }, 286 + "folder_gulp": { "iconPath": "./icons/folder_gulp.svg" }, 287 + "folder_gulp__open": { "iconPath": "./icons/folder_gulp__open.svg" }, 288 + "folder_home": { "iconPath": "./icons/folder_home.svg" }, 289 + "folder_home__open": { "iconPath": "./icons/folder_home__open.svg" }, 290 + "folder_husky": { "iconPath": "./icons/folder_husky.svg" }, 291 + "folder_husky__open": { "iconPath": "./icons/folder_husky__open.svg" }, 292 + "folder_images": { "iconPath": "./icons/folder_images.svg" }, 293 + "folder_images__open": { "iconPath": "./icons/folder_images__open.svg" }, 294 + "folder_ios": { "iconPath": "./icons/folder_ios.svg" }, 295 + "folder_ios__open": { "iconPath": "./icons/folder_ios__open.svg" }, 296 + "folder_java": { "iconPath": "./icons/folder_java.svg" }, 297 + "folder_java__open": { "iconPath": "./icons/folder_java__open.svg" }, 298 + "folder_javascript": { "iconPath": "./icons/folder_javascript.svg" }, 299 + "folder_javascript__open": { 300 + "iconPath": "./icons/folder_javascript__open.svg" 301 + }, 302 + "folder_json": { "iconPath": "./icons/folder_json.svg" }, 303 + "folder_json__open": { "iconPath": "./icons/folder_json__open.svg" }, 304 + "folder_key": { "iconPath": "./icons/folder_key.svg" }, 305 + "folder_key__open": { "iconPath": "./icons/folder_key__open.svg" }, 306 + "folder_kubernetes": { "iconPath": "./icons/folder_kubernetes.svg" }, 307 + "folder_kubernetes__open": { 308 + "iconPath": "./icons/folder_kubernetes__open.svg" 309 + }, 310 + "folder_layouts": { "iconPath": "./icons/folder_layouts.svg" }, 311 + "folder_layouts__open": { "iconPath": "./icons/folder_layouts__open.svg" }, 312 + "folder_library": { "iconPath": "./icons/folder_library.svg" }, 313 + "folder_library__open": { "iconPath": "./icons/folder_library__open.svg" }, 314 + "folder_link": { "iconPath": "./icons/folder_link.svg" }, 315 + "folder_link__open": { "iconPath": "./icons/folder_link__open.svg" }, 316 + "folder_linux": { "iconPath": "./icons/folder_linux.svg" }, 317 + "folder_linux__open": { "iconPath": "./icons/folder_linux__open.svg" }, 318 + "folder_locales": { "iconPath": "./icons/folder_locales.svg" }, 319 + "folder_locales__open": { "iconPath": "./icons/folder_locales__open.svg" }, 320 + "folder_log": { "iconPath": "./icons/folder_log.svg" }, 321 + "folder_log__open": { "iconPath": "./icons/folder_log__open.svg" }, 322 + "folder_lottie": { "iconPath": "./icons/folder_lottie.svg" }, 323 + "folder_lottie__open": { "iconPath": "./icons/folder_lottie__open.svg" }, 324 + "folder_macos": { "iconPath": "./icons/folder_macos.svg" }, 325 + "folder_macos__open": { "iconPath": "./icons/folder_macos__open.svg" }, 326 + "folder_mail": { "iconPath": "./icons/folder_mail.svg" }, 327 + "folder_mail__open": { "iconPath": "./icons/folder_mail__open.svg" }, 328 + "folder_markdown": { "iconPath": "./icons/folder_markdown.svg" }, 329 + "folder_markdown__open": { 330 + "iconPath": "./icons/folder_markdown__open.svg" 331 + }, 332 + "folder_message": { "iconPath": "./icons/folder_message.svg" }, 333 + "folder_message__open": { "iconPath": "./icons/folder_message__open.svg" }, 334 + "folder_middleware": { "iconPath": "./icons/folder_middleware.svg" }, 335 + "folder_middleware__open": { 336 + "iconPath": "./icons/folder_middleware__open.svg" 337 + }, 338 + "folder_mjml": { "iconPath": "./icons/folder_mjml.svg" }, 339 + "folder_mjml__open": { "iconPath": "./icons/folder_mjml__open.svg" }, 340 + "folder_mobile": { "iconPath": "./icons/folder_mobile.svg" }, 341 + "folder_mobile__open": { "iconPath": "./icons/folder_mobile__open.svg" }, 342 + "folder_mocks": { "iconPath": "./icons/folder_mocks.svg" }, 343 + "folder_mocks__open": { "iconPath": "./icons/folder_mocks__open.svg" }, 344 + "folder_mojo": { "iconPath": "./icons/folder_mojo.svg" }, 345 + "folder_mojo__open": { "iconPath": "./icons/folder_mojo__open.svg" }, 346 + "folder_netlify": { "iconPath": "./icons/folder_netlify.svg" }, 347 + "folder_netlify__open": { "iconPath": "./icons/folder_netlify__open.svg" }, 348 + "folder_next": { "iconPath": "./icons/folder_next.svg" }, 349 + "folder_next__open": { "iconPath": "./icons/folder_next__open.svg" }, 350 + "folder_node": { "iconPath": "./icons/folder_node.svg" }, 351 + "folder_node__open": { "iconPath": "./icons/folder_node__open.svg" }, 352 + "folder_nuxt": { "iconPath": "./icons/folder_nuxt.svg" }, 353 + "folder_nuxt__open": { "iconPath": "./icons/folder_nuxt__open.svg" }, 354 + "folder_packages": { "iconPath": "./icons/folder_packages.svg" }, 355 + "folder_packages__open": { 356 + "iconPath": "./icons/folder_packages__open.svg" 357 + }, 358 + "folder_pdf": { "iconPath": "./icons/folder_pdf.svg" }, 359 + "folder_pdf__open": { "iconPath": "./icons/folder_pdf__open.svg" }, 360 + "folder_pdm": { "iconPath": "./icons/folder_pdm.svg" }, 361 + "folder_pdm__open": { "iconPath": "./icons/folder_pdm__open.svg" }, 362 + "folder_php": { "iconPath": "./icons/folder_php.svg" }, 363 + "folder_php__open": { "iconPath": "./icons/folder_php__open.svg" }, 364 + "folder_playground": { "iconPath": "./icons/folder_playground.svg" }, 365 + "folder_playground__open": { 366 + "iconPath": "./icons/folder_playground__open.svg" 367 + }, 368 + "folder_plugins": { "iconPath": "./icons/folder_plugins.svg" }, 369 + "folder_plugins__open": { "iconPath": "./icons/folder_plugins__open.svg" }, 370 + "folder_prisma": { "iconPath": "./icons/folder_prisma.svg" }, 371 + "folder_prisma__open": { "iconPath": "./icons/folder_prisma__open.svg" }, 372 + "folder_private": { "iconPath": "./icons/folder_private.svg" }, 373 + "folder_private__open": { "iconPath": "./icons/folder_private__open.svg" }, 374 + "folder_project": { "iconPath": "./icons/folder_project.svg" }, 375 + "folder_project__open": { "iconPath": "./icons/folder_project__open.svg" }, 376 + "folder_proto": { "iconPath": "./icons/folder_proto.svg" }, 377 + "folder_proto__open": { "iconPath": "./icons/folder_proto__open.svg" }, 378 + "folder_public": { "iconPath": "./icons/folder_public.svg" }, 379 + "folder_public__open": { "iconPath": "./icons/folder_public__open.svg" }, 380 + "folder_python": { "iconPath": "./icons/folder_python.svg" }, 381 + "folder_python__open": { "iconPath": "./icons/folder_python__open.svg" }, 382 + "folder_queue": { "iconPath": "./icons/folder_queue.svg" }, 383 + "folder_queue__open": { "iconPath": "./icons/folder_queue__open.svg" }, 384 + "folder_redux": { "iconPath": "./icons/folder_redux.svg" }, 385 + "folder_redux__open": { "iconPath": "./icons/folder_redux__open.svg" }, 386 + "folder_resource": { "iconPath": "./icons/folder_resource.svg" }, 387 + "folder_resource__open": { 388 + "iconPath": "./icons/folder_resource__open.svg" 389 + }, 390 + "folder_review": { "iconPath": "./icons/folder_review.svg" }, 391 + "folder_review__open": { "iconPath": "./icons/folder_review__open.svg" }, 392 + "folder_robot": { "iconPath": "./icons/folder_robot.svg" }, 393 + "folder_robot__open": { "iconPath": "./icons/folder_robot__open.svg" }, 394 + "folder_root": { "iconPath": "./icons/folder_root.svg" }, 395 + "folder_root__open": { "iconPath": "./icons/folder_root__open.svg" }, 396 + "folder_routes": { "iconPath": "./icons/folder_routes.svg" }, 397 + "folder_routes__open": { "iconPath": "./icons/folder_routes__open.svg" }, 398 + "folder_rules": { "iconPath": "./icons/folder_rules.svg" }, 399 + "folder_rules__open": { "iconPath": "./icons/folder_rules__open.svg" }, 400 + "folder_sass": { "iconPath": "./icons/folder_sass.svg" }, 401 + "folder_sass__open": { "iconPath": "./icons/folder_sass__open.svg" }, 402 + "folder_scala": { "iconPath": "./icons/folder_scala.svg" }, 403 + "folder_scala__open": { "iconPath": "./icons/folder_scala__open.svg" }, 404 + "folder_scripts": { "iconPath": "./icons/folder_scripts.svg" }, 405 + "folder_scripts__open": { "iconPath": "./icons/folder_scripts__open.svg" }, 406 + "folder_secure": { "iconPath": "./icons/folder_secure.svg" }, 407 + "folder_secure__open": { "iconPath": "./icons/folder_secure__open.svg" }, 408 + "folder_serveless__open": { 409 + "iconPath": "./icons/folder_serveless__open.svg" 410 + }, 411 + "folder_server": { "iconPath": "./icons/folder_server.svg" }, 412 + "folder_server__open": { "iconPath": "./icons/folder_server__open.svg" }, 413 + "folder_serverless": { "iconPath": "./icons/folder_serverless.svg" }, 414 + "folder_shader": { "iconPath": "./icons/folder_shader.svg" }, 415 + "folder_shader__open": { "iconPath": "./icons/folder_shader__open.svg" }, 416 + "folder_share": { "iconPath": "./icons/folder_share.svg" }, 417 + "folder_share__open": { "iconPath": "./icons/folder_share__open.svg" }, 418 + "folder_src": { "iconPath": "./icons/folder_src.svg" }, 419 + "folder_src__open": { "iconPath": "./icons/folder_src__open.svg" }, 420 + "folder_stencil": { "iconPath": "./icons/folder_stencil.svg" }, 421 + "folder_stencil__open": { "iconPath": "./icons/folder_stencil__open.svg" }, 422 + "folder_storybook": { "iconPath": "./icons/folder_storybook.svg" }, 423 + "folder_storybook__open": { 424 + "iconPath": "./icons/folder_storybook__open.svg" 425 + }, 426 + "folder_styles": { "iconPath": "./icons/folder_styles.svg" }, 427 + "folder_styles__open": { "iconPath": "./icons/folder_styles__open.svg" }, 428 + "folder_supabase": { "iconPath": "./icons/folder_supabase.svg" }, 429 + "folder_supabase__open": { 430 + "iconPath": "./icons/folder_supabase__open.svg" 431 + }, 432 + "folder_svelte": { "iconPath": "./icons/folder_svelte.svg" }, 433 + "folder_svelte__open": { "iconPath": "./icons/folder_svelte__open.svg" }, 434 + "folder_svg": { "iconPath": "./icons/folder_svg.svg" }, 435 + "folder_svg__open": { "iconPath": "./icons/folder_svg__open.svg" }, 436 + "folder_task": { "iconPath": "./icons/folder_task.svg" }, 437 + "folder_task__open": { "iconPath": "./icons/folder_task__open.svg" }, 438 + "folder_tauri": { "iconPath": "./icons/folder_tauri.svg" }, 439 + "folder_tauri__open": { "iconPath": "./icons/folder_tauri__open.svg" }, 440 + "folder_temp": { "iconPath": "./icons/folder_temp.svg" }, 441 + "folder_temp__open": { "iconPath": "./icons/folder_temp__open.svg" }, 442 + "folder_templates": { "iconPath": "./icons/folder_templates.svg" }, 443 + "folder_templates__open": { 444 + "iconPath": "./icons/folder_templates__open.svg" 445 + }, 446 + "folder_terraform": { "iconPath": "./icons/folder_terraform.svg" }, 447 + "folder_terraform__open": { 448 + "iconPath": "./icons/folder_terraform__open.svg" 449 + }, 450 + "folder_tests": { "iconPath": "./icons/folder_tests.svg" }, 451 + "folder_tests__open": { "iconPath": "./icons/folder_tests__open.svg" }, 452 + "folder_types": { "iconPath": "./icons/folder_types.svg" }, 453 + "folder_types__open": { "iconPath": "./icons/folder_types__open.svg" }, 454 + "folder_typescript": { "iconPath": "./icons/folder_typescript.svg" }, 455 + "folder_typescript__open": { 456 + "iconPath": "./icons/folder_typescript__open.svg" 457 + }, 458 + "folder_unity": { "iconPath": "./icons/folder_unity.svg" }, 459 + "folder_unity__open": { "iconPath": "./icons/folder_unity__open.svg" }, 460 + "folder_upload": { "iconPath": "./icons/folder_upload.svg" }, 461 + "folder_upload__open": { "iconPath": "./icons/folder_upload__open.svg" }, 462 + "folder_utils": { "iconPath": "./icons/folder_utils.svg" }, 463 + "folder_utils__open": { "iconPath": "./icons/folder_utils__open.svg" }, 464 + "folder_vercel": { "iconPath": "./icons/folder_vercel.svg" }, 465 + "folder_vercel__open": { "iconPath": "./icons/folder_vercel__open.svg" }, 466 + "folder_video": { "iconPath": "./icons/folder_video.svg" }, 467 + "folder_video__open": { "iconPath": "./icons/folder_video__open.svg" }, 468 + "folder_views": { "iconPath": "./icons/folder_views.svg" }, 469 + "folder_views__open": { "iconPath": "./icons/folder_views__open.svg" }, 470 + "folder_vscode": { "iconPath": "./icons/folder_vscode.svg" }, 471 + "folder_vscode__open": { "iconPath": "./icons/folder_vscode__open.svg" }, 472 + "folder_vue": { "iconPath": "./icons/folder_vue.svg" }, 473 + "folder_vue__open": { "iconPath": "./icons/folder_vue__open.svg" }, 474 + "folder_webpack": { "iconPath": "./icons/folder_webpack.svg" }, 475 + "folder_webpack__open": { "iconPath": "./icons/folder_webpack__open.svg" }, 476 + "folder_windows": { "iconPath": "./icons/folder_windows.svg" }, 477 + "folder_windows__open": { "iconPath": "./icons/folder_windows__open.svg" }, 478 + "folder_wordpress": { "iconPath": "./icons/folder_wordpress.svg" }, 479 + "folder_wordpress__open": { 480 + "iconPath": "./icons/folder_wordpress__open.svg" 481 + }, 482 + "folder_workflows": { "iconPath": "./icons/folder_workflows.svg" }, 483 + "folder_workflows__open": { 484 + "iconPath": "./icons/folder_workflows__open.svg" 485 + }, 486 + "folder_yarn": { "iconPath": "./icons/folder_yarn.svg" }, 487 + "folder_yarn__open": { "iconPath": "./icons/folder_yarn__open.svg" }, 488 + "font": { "iconPath": "./icons/font.svg" }, 489 + "forth": { "iconPath": "./icons/forth.svg" }, 490 + "fortran": { "iconPath": "./icons/fortran.svg" }, 491 + "foxpro": { "iconPath": "./icons/foxpro.svg" }, 492 + "fsharp": { "iconPath": "./icons/fsharp.svg" }, 493 + "gamemaker": { "iconPath": "./icons/gamemaker.svg" }, 494 + "gatsby": { "iconPath": "./icons/gatsby.svg" }, 495 + "gcp": { "iconPath": "./icons/gcp.svg" }, 496 + "git": { "iconPath": "./icons/git.svg" }, 497 + "gitbook": { "iconPath": "./icons/gitbook.svg" }, 498 + "gitlab": { "iconPath": "./icons/gitlab.svg" }, 499 + "gitpod": { "iconPath": "./icons/gitpod.svg" }, 500 + "gleam": { "iconPath": "./icons/gleam.svg" }, 501 + "go": { "iconPath": "./icons/go.svg" }, 502 + "go_mod": { "iconPath": "./icons/go_mod.svg" }, 503 + "godot": { "iconPath": "./icons/godot.svg" }, 504 + "godot_assets": { "iconPath": "./icons/godot_assets.svg" }, 505 + "gradle": { "iconPath": "./icons/gradle.svg" }, 506 + "grain": { "iconPath": "./icons/grain.svg" }, 507 + "graphql": { "iconPath": "./icons/graphql.svg" }, 508 + "gridsome": { "iconPath": "./icons/gridsome.svg" }, 509 + "groovy": { "iconPath": "./icons/groovy.svg" }, 510 + "gulp": { "iconPath": "./icons/gulp.svg" }, 511 + "h": { "iconPath": "./icons/h.svg" }, 512 + "haml": { "iconPath": "./icons/haml.svg" }, 513 + "handlebars": { "iconPath": "./icons/handlebars.svg" }, 514 + "haskell": { "iconPath": "./icons/haskell.svg" }, 515 + "haxe": { "iconPath": "./icons/haxe.svg" }, 516 + "hcl": { "iconPath": "./icons/hcl.svg" }, 517 + "helm": { "iconPath": "./icons/helm.svg" }, 518 + "heroku": { "iconPath": "./icons/heroku.svg" }, 519 + "histoire": { "iconPath": "./icons/histoire.svg" }, 520 + "hjson": { "iconPath": "./icons/hjson.svg" }, 521 + "hpp": { "iconPath": "./icons/hpp.svg" }, 522 + "htaccess": { "iconPath": "./icons/htaccess.svg" }, 523 + "html": { "iconPath": "./icons/html.svg" }, 524 + "http": { "iconPath": "./icons/http.svg" }, 525 + "husky": { "iconPath": "./icons/husky.svg" }, 526 + "i18n": { "iconPath": "./icons/i18n.svg" }, 527 + "icon": { "iconPath": "./icons/icon.svg" }, 528 + "image": { "iconPath": "./icons/image.svg" }, 529 + "ionic": { "iconPath": "./icons/ionic.svg" }, 530 + "java": { "iconPath": "./icons/java.svg" }, 531 + "java_class": { "iconPath": "./icons/java_class.svg" }, 532 + "java_jar": { "iconPath": "./icons/java_jar.svg" }, 533 + "javascript": { "iconPath": "./icons/javascript.svg" }, 534 + "javascript_config": { "iconPath": "./icons/javascript_config.svg" }, 535 + "javascript_map": { "iconPath": "./icons/javascript_map.svg" }, 536 + "javascript_react": { "iconPath": "./icons/javascript_react.svg" }, 537 + "javascript_test": { "iconPath": "./icons/javascript_test.svg" }, 538 + "jenkins": { "iconPath": "./icons/jenkins.svg" }, 539 + "jest": { "iconPath": "./icons/jest.svg" }, 540 + "jinja": { "iconPath": "./icons/jinja.svg" }, 541 + "json": { "iconPath": "./icons/json.svg" }, 542 + "julia": { "iconPath": "./icons/julia.svg" }, 543 + "jupyter": { "iconPath": "./icons/jupyter.svg" }, 544 + "karma": { "iconPath": "./icons/karma.svg" }, 545 + "key": { "iconPath": "./icons/key.svg" }, 546 + "kivy": { "iconPath": "./icons/kivy.svg" }, 547 + "kotlin": { "iconPath": "./icons/kotlin.svg" }, 548 + "kubernetes": { "iconPath": "./icons/kubernetes.svg" }, 549 + "laravel": { "iconPath": "./icons/laravel.svg" }, 550 + "latex": { "iconPath": "./icons/latex.svg" }, 551 + "lerna": { "iconPath": "./icons/lerna.svg" }, 552 + "less": { "iconPath": "./icons/less.svg" }, 553 + "lib": { "iconPath": "./icons/lib.svg" }, 554 + "license": { "iconPath": "./icons/license.svg" }, 555 + "lighthouse": { "iconPath": "./icons/lighthouse.svg" }, 556 + "lintstaged": { "iconPath": "./icons/lintstaged.svg" }, 557 + "lisp": { "iconPath": "./icons/lisp.svg" }, 558 + "literate": { "iconPath": "./icons/literate.svg" }, 559 + "livescript": { "iconPath": "./icons/livescript.svg" }, 560 + "log": { "iconPath": "./icons/log.svg" }, 561 + "lottie": { "iconPath": "./icons/lottie.svg" }, 562 + "lua": { "iconPath": "./icons/lua.svg" }, 563 + "mail": { "iconPath": "./icons/mail.svg" }, 564 + "makefile": { "iconPath": "./icons/makefile.svg" }, 565 + "markdown": { "iconPath": "./icons/markdown.svg" }, 566 + "markdown_mdx": { "iconPath": "./icons/markdown_mdx.svg" }, 567 + "marko": { "iconPath": "./icons/marko.svg" }, 568 + "mathematica": { "iconPath": "./icons/mathematica.svg" }, 569 + "matlab": { "iconPath": "./icons/matlab.svg" }, 570 + "maven": { "iconPath": "./icons/maven.svg" }, 571 + "mercurial": { "iconPath": "./icons/mercurial.svg" }, 572 + "mermaid": { "iconPath": "./icons/mermaid.svg" }, 573 + "meson": { "iconPath": "./icons/meson.svg" }, 574 + "metro": { "iconPath": "./icons/metro.svg" }, 575 + "mint": { "iconPath": "./icons/mint.svg" }, 576 + "mjml": { "iconPath": "./icons/mjml.svg" }, 577 + "mocha": { "iconPath": "./icons/mocha.svg" }, 578 + "modernizr": { "iconPath": "./icons/modernizr.svg" }, 579 + "mojo": { "iconPath": "./icons/mojo.svg" }, 580 + "moon": { "iconPath": "./icons/moon.svg" }, 581 + "mooonscript": { "iconPath": "./icons/mooonscript.svg" }, 582 + "nativescript": { "iconPath": "./icons/nativescript.svg" }, 583 + "nest": { "iconPath": "./icons/nest.svg" }, 584 + "netlify": { "iconPath": "./icons/netlify.svg" }, 585 + "next": { "iconPath": "./icons/next.svg" }, 586 + "nextflow": { "iconPath": "./icons/nextflow.svg" }, 587 + "nginx": { "iconPath": "./icons/nginx.svg" }, 588 + "nim": { "iconPath": "./icons/nim.svg" }, 589 + "ninja": { "iconPath": "./icons/ninja.svg" }, 590 + "nix": { "iconPath": "./icons/nix.svg" }, 591 + "nodemon": { "iconPath": "./icons/nodemon.svg" }, 592 + "npm": { "iconPath": "./icons/npm.svg" }, 593 + "npm_ignore": { "iconPath": "./icons/npm_ignore.svg" }, 594 + "npm_lock": { "iconPath": "./icons/npm_lock.svg" }, 595 + "nuget": { "iconPath": "./icons/nuget.svg" }, 596 + "nunjucks": { "iconPath": "./icons/nunjucks.svg" }, 597 + "nuxt": { "iconPath": "./icons/nuxt.svg" }, 598 + "nuxt_ignore": { "iconPath": "./icons/nuxt_ignore.svg" }, 599 + "nx": { "iconPath": "./icons/nx.svg" }, 600 + "ocaml": { "iconPath": "./icons/ocaml.svg" }, 601 + "odin": { "iconPath": "./icons/odin.svg" }, 602 + "package_json": { "iconPath": "./icons/package_json.svg" }, 603 + "panda": { "iconPath": "./icons/panda.svg" }, 604 + "parse": { "iconPath": "./icons/parse.svg" }, 605 + "pawn": { "iconPath": "./icons/pawn.svg" }, 606 + "payload": { "iconPath": "./icons/payload.svg" }, 607 + "pdf": { "iconPath": "./icons/pdf.svg" }, 608 + "pdm": { "iconPath": "./icons/pdm.svg" }, 609 + "pdm_lock": { "iconPath": "./icons/pdm_lock.svg" }, 610 + "perl": { "iconPath": "./icons/perl.svg" }, 611 + "php": { "iconPath": "./icons/php.svg" }, 612 + "php_cs_fixer": { "iconPath": "./icons/php_cs_fixer.svg" }, 613 + "phpstan": { "iconPath": "./icons/phpstan.svg" }, 614 + "phpunit": { "iconPath": "./icons/phpunit.svg" }, 615 + "pinejs": { "iconPath": "./icons/pinejs.svg" }, 616 + "plastic": { "iconPath": "./icons/plastic.svg" }, 617 + "playwright": { "iconPath": "./icons/playwright.svg" }, 618 + "plop": { "iconPath": "./icons/plop.svg" }, 619 + "pnpm": { "iconPath": "./icons/pnpm.svg" }, 620 + "pnpm_lock": { "iconPath": "./icons/pnpm_lock.svg" }, 621 + "poetry": { "iconPath": "./icons/poetry.svg" }, 622 + "postcss": { "iconPath": "./icons/postcss.svg" }, 623 + "posthtml": { "iconPath": "./icons/posthtml.svg" }, 624 + "powershell": { "iconPath": "./icons/powershell.svg" }, 625 + "premake": { "iconPath": "./icons/premake.svg" }, 626 + "prettier": { "iconPath": "./icons/prettier.svg" }, 627 + "prettier_ignore": { "iconPath": "./icons/prettier_ignore.svg" }, 628 + "prisma": { "iconPath": "./icons/prisma.svg" }, 629 + "processing": { "iconPath": "./icons/processing.svg" }, 630 + "prolog": { "iconPath": "./icons/prolog.svg" }, 631 + "properties": { "iconPath": "./icons/properties.svg" }, 632 + "proto": { "iconPath": "./icons/proto.svg" }, 633 + "pug": { "iconPath": "./icons/pug.svg" }, 634 + "puppet": { "iconPath": "./icons/puppet.svg" }, 635 + "puppeteer": { "iconPath": "./icons/puppeteer.svg" }, 636 + "purescript": { "iconPath": "./icons/purescript.svg" }, 637 + "python": { "iconPath": "./icons/python.svg" }, 638 + "python_compiled": { "iconPath": "./icons/python_compiled.svg" }, 639 + "python_misc": { "iconPath": "./icons/python_misc.svg" }, 640 + "quasar": { "iconPath": "./icons/quasar.svg" }, 641 + "r": { "iconPath": "./icons/r.svg" }, 642 + "racket": { "iconPath": "./icons/racket.svg" }, 643 + "razor": { "iconPath": "./icons/razor.svg" }, 644 + "reactnative_config": { "iconPath": "./icons/reactnative_config.svg" }, 645 + "readme": { "iconPath": "./icons/readme.svg" }, 646 + "reason": { "iconPath": "./icons/reason.svg" }, 647 + "red": { "iconPath": "./icons/red.svg" }, 648 + "redwood": { "iconPath": "./icons/redwood.svg" }, 649 + "remix": { "iconPath": "./icons/remix.svg" }, 650 + "renovate": { "iconPath": "./icons/renovate.svg" }, 651 + "replit": { "iconPath": "./icons/replit.svg" }, 652 + "rescript": { "iconPath": "./icons/rescript.svg" }, 653 + "restql": { "iconPath": "./icons/restql.svg" }, 654 + "riot": { "iconPath": "./icons/riot.svg" }, 655 + "roblox": { "iconPath": "./icons/roblox.svg" }, 656 + "robots": { "iconPath": "./icons/robots.svg" }, 657 + "rollup": { "iconPath": "./icons/rollup.svg" }, 658 + "rome": { "iconPath": "./icons/rome.svg" }, 659 + "rspec": { "iconPath": "./icons/rspec.svg" }, 660 + "rubocop": { "iconPath": "./icons/rubocop.svg" }, 661 + "ruby": { "iconPath": "./icons/ruby.svg" }, 662 + "ruby_gem": { "iconPath": "./icons/ruby_gem.svg" }, 663 + "ruby_gem_lock": { "iconPath": "./icons/ruby_gem_lock.svg" }, 664 + "rust": { "iconPath": "./icons/rust.svg" }, 665 + "salesforce": { "iconPath": "./icons/salesforce.svg" }, 666 + "sass": { "iconPath": "./icons/sass.svg" }, 667 + "scala": { "iconPath": "./icons/scala.svg" }, 668 + "scheme": { "iconPath": "./icons/scheme.svg" }, 669 + "search": { "iconPath": "./icons/search.svg" }, 670 + "semantic_release": { "iconPath": "./icons/semantic_release.svg" }, 671 + "semgrep": { "iconPath": "./icons/semgrep.svg" }, 672 + "semgrep_ignore": { "iconPath": "./icons/semgrep_ignore.svg" }, 673 + "sentry": { "iconPath": "./icons/sentry.svg" }, 674 + "sequelize": { "iconPath": "./icons/sequelize.svg" }, 675 + "serverless": { "iconPath": "./icons/serverless.svg" }, 676 + "shader": { "iconPath": "./icons/shader.svg" }, 677 + "sketch": { "iconPath": "./icons/sketch.svg" }, 678 + "slide": { "iconPath": "./icons/slide.svg" }, 679 + "slim": { "iconPath": "./icons/slim.svg" }, 680 + "smarty": { "iconPath": "./icons/smarty.svg" }, 681 + "snowpack": { "iconPath": "./icons/snowpack.svg" }, 682 + "solidity": { "iconPath": "./icons/solidity.svg" }, 683 + "sonar_cloud": { "iconPath": "./icons/sonar_cloud.svg" }, 684 + "spreadsheet": { "iconPath": "./icons/spreadsheet.svg" }, 685 + "spwn": { "iconPath": "./icons/spwn.svg" }, 686 + "stackblitz": { "iconPath": "./icons/stackblitz.svg" }, 687 + "stan": { "iconPath": "./icons/stan.svg" }, 688 + "stencil": { "iconPath": "./icons/stencil.svg" }, 689 + "stitches": { "iconPath": "./icons/stitches.svg" }, 690 + "storybook": { "iconPath": "./icons/storybook.svg" }, 691 + "storybook_svelte": { "iconPath": "./icons/storybook_svelte.svg" }, 692 + "storybook_vue": { "iconPath": "./icons/storybook_vue.svg" }, 693 + "stylelint": { "iconPath": "./icons/stylelint.svg" }, 694 + "stylelint_ignore": { "iconPath": "./icons/stylelint_ignore.svg" }, 695 + "sublime": { "iconPath": "./icons/sublime.svg" }, 696 + "svelte": { "iconPath": "./icons/svelte.svg" }, 697 + "svelte_config": { "iconPath": "./icons/svelte_config.svg" }, 698 + "svg": { "iconPath": "./icons/svg.svg" }, 699 + "swagger": { "iconPath": "./icons/swagger.svg" }, 700 + "swift": { "iconPath": "./icons/swift.svg" }, 701 + "syncpack": { "iconPath": "./icons/syncpack.svg" }, 702 + "tailwind": { "iconPath": "./icons/tailwind.svg" }, 703 + "task": { "iconPath": "./icons/task.svg" }, 704 + "tauri": { "iconPath": "./icons/tauri.svg" }, 705 + "taurignore": { "iconPath": "./icons/taurignore.svg" }, 706 + "terraform": { "iconPath": "./icons/terraform.svg" }, 707 + "textlint": { "iconPath": "./icons/textlint.svg" }, 708 + "tldraw": { "iconPath": "./icons/tldraw.svg" }, 709 + "todo": { "iconPath": "./icons/todo.svg" }, 710 + "toml": { "iconPath": "./icons/toml.svg" }, 711 + "travis": { "iconPath": "./icons/travis.svg" }, 712 + "tree": { "iconPath": "./icons/tree.svg" }, 713 + "twig": { "iconPath": "./icons/twig.svg" }, 714 + "twine": { "iconPath": "./icons/twine.svg" }, 715 + "txt": { "iconPath": "./icons/txt.svg" }, 716 + "typescript": { "iconPath": "./icons/typescript.svg" }, 717 + "typescript_config": { "iconPath": "./icons/typescript_config.svg" }, 718 + "typescript_def": { "iconPath": "./icons/typescript_def.svg" }, 719 + "typescript_react": { "iconPath": "./icons/typescript_react.svg" }, 720 + "typescript_test": { "iconPath": "./icons/typescript_test.svg" }, 721 + "uml": { "iconPath": "./icons/uml.svg" }, 722 + "unity": { "iconPath": "./icons/unity.svg" }, 723 + "unocss": { "iconPath": "./icons/unocss.svg" }, 724 + "url": { "iconPath": "./icons/url.svg" }, 725 + "v": { "iconPath": "./icons/v.svg" }, 726 + "vagrant": { "iconPath": "./icons/vagrant.svg" }, 727 + "vala": { "iconPath": "./icons/vala.svg" }, 728 + "velocity": { "iconPath": "./icons/velocity.svg" }, 729 + "vercel": { "iconPath": "./icons/vercel.svg" }, 730 + "vercel_ignore": { "iconPath": "./icons/vercel_ignore.svg" }, 731 + "verilog": { "iconPath": "./icons/verilog.svg" }, 732 + "video": { "iconPath": "./icons/video.svg" }, 733 + "vim": { "iconPath": "./icons/vim.svg" }, 734 + "visual_studio": { "iconPath": "./icons/visual_studio.svg" }, 735 + "vite": { "iconPath": "./icons/vite.svg" }, 736 + "vitest": { "iconPath": "./icons/vitest.svg" }, 737 + "vs_code": { "iconPath": "./icons/vs_code.svg" }, 738 + "vs_code_ignore": { "iconPath": "./icons/vs_code_ignore.svg" }, 739 + "vs_codium": { "iconPath": "./icons/vs_codium.svg" }, 740 + "vue": { "iconPath": "./icons/vue.svg" }, 741 + "vue_config": { "iconPath": "./icons/vue_config.svg" }, 742 + "wallaby": { "iconPath": "./icons/wallaby.svg" }, 743 + "watchman": { "iconPath": "./icons/watchman.svg" }, 744 + "web_assembly": { "iconPath": "./icons/web_assembly.svg" }, 745 + "webpack": { "iconPath": "./icons/webpack.svg" }, 746 + "windi": { "iconPath": "./icons/windi.svg" }, 747 + "wolfram": { "iconPath": "./icons/wolfram.svg" }, 748 + "workflow": { "iconPath": "./icons/workflow.svg" }, 749 + "xaml": { "iconPath": "./icons/xaml.svg" }, 750 + "xcode_project": { "iconPath": "./icons/xcode_project.svg" }, 751 + "xd": { "iconPath": "./icons/xd.svg" }, 752 + "xib": { "iconPath": "./icons/xib.svg" }, 753 + "xmake": { "iconPath": "./icons/xmake.svg" }, 754 + "xml": { "iconPath": "./icons/xml.svg" }, 755 + "yaml": { "iconPath": "./icons/yaml.svg" }, 756 + "yarn": { "iconPath": "./icons/yarn.svg" }, 757 + "yarn_lock": { "iconPath": "./icons/yarn_lock.svg" }, 758 + "zig": { "iconPath": "./icons/zig.svg" }, 759 + "zip": { "iconPath": "./icons/zip.svg" } 760 + }, 761 + "languageIds": { 762 + "ng-template": "angular", 763 + "apiblueprint": "api_blueprint", 764 + "applescript": "apple", 765 + "awk": "bash", 766 + "shellscript": "bash", 767 + "bat": "bat", 768 + "code-text-binary": "binary", 769 + "c": "c", 770 + "objective-c": "c", 771 + "objective-cpp": "c", 772 + "cabal": "cabal", 773 + "cadence": "cadence", 774 + "clojure": "clojure", 775 + "cmake": "cmake", 776 + "cmake-cache": "cmake_in", 777 + "cobol": "cobol", 778 + "coffeescript": "coffeescript", 779 + "cpp": "cpp", 780 + "csharp": "csharp", 781 + "css": "css", 782 + "csv": "csv", 783 + "tsv": "csv", 784 + "psv": "csv", 785 + "cucumber": "cucumber", 786 + "cuda-cpp": "cuda", 787 + "d": "d", 788 + "dart": "dart", 789 + "sql": "database", 790 + "diff": "diff", 791 + "django-html": "django", 792 + "django-txt": "django", 793 + "dockerfile": "docker", 794 + "dockercompose": "docker", 795 + "editorconfig": "editorconfig", 796 + "elixir": "elixir", 797 + "elm": "elm", 798 + "dotenv": "env", 799 + "erlang": "erlang", 800 + "forth": "forth", 801 + "fortran": "fortran", 802 + "foxpro": "foxpro", 803 + "fsharp": "fsharp", 804 + "fish": "fish", 805 + "git": "git", 806 + "git-commit": "git", 807 + "git-rebase": "git", 808 + "ignore": "git", 809 + "go": "go", 810 + "grain": "grain", 811 + "graphql": "graphql", 812 + "groovy": "groovy", 813 + "haml": "haml", 814 + "handlebars": "handlebars", 815 + "haskell": "haskell", 816 + "haxe": "haxe", 817 + "hxml": "haxe", 818 + "hjson": "hjson", 819 + "html": "html", 820 + "java": "java", 821 + "javascript": "javascript", 822 + "javascriptreact": "javascript_react", 823 + "jinja": "jinja", 824 + "json": "json", 825 + "jsonc": "json", 826 + "json5": "json", 827 + "jsonl": "json", 828 + "julia": "julia", 829 + "juliamarkdown": "julia", 830 + "jupyter": "jupyter", 831 + "bibtex": "latex", 832 + "bibtex-style": "latex", 833 + "tex": "latex", 834 + "doctex": "latex", 835 + "latex": "latex", 836 + "latex-expl3": "latex", 837 + "less": "less", 838 + "livescript": "livescript", 839 + "log": "log", 840 + "lua": "lua", 841 + "makefile": "makefile", 842 + "markdown": "markdown", 843 + "nim": "nim", 844 + "nimble": "nim", 845 + "nix": "nix", 846 + "nunjucks": "nunjucks", 847 + "pawn": "pawn", 848 + "pdf": "pdf", 849 + "perl": "perl", 850 + "perl6": "perl", 851 + "php": "php", 852 + "postcss": "postcss", 853 + "powershell": "powershell", 854 + "processing": "processing", 855 + "prolog": "prolog", 856 + "properties": "properties", 857 + "ini": "properties", 858 + "spring-boot-properties": "properties", 859 + "jade": "pug", 860 + "pug": "pug", 861 + "puppet": "puppet", 862 + "purescript": "purescript", 863 + "python": "python", 864 + "r": "r", 865 + "rsweave": "r", 866 + "razor": "razor", 867 + "aspnetcorerazor": "razor", 868 + "reason": "reason", 869 + "reason_lisp": "reason", 870 + "rescript": "rescript", 871 + "riot": "riot", 872 + "ruby": "ruby", 873 + "rust": "rust", 874 + "apex": "salesforce", 875 + "sass": "sass", 876 + "scss": "sass", 877 + "scala": "scala", 878 + "scheme": "scheme", 879 + "search-result": "search", 880 + "hlsl": "shader", 881 + "glsl": "shader", 882 + "wgsl": "shader", 883 + "slim": "slim", 884 + "smarty": "smarty", 885 + "solidity": "solidity", 886 + "spwn": "spwn", 887 + "svelte": "svelte", 888 + "svg": "svg", 889 + "stan": "stan", 890 + "swift": "swift", 891 + "tailwindcss": "tailwind", 892 + "toml": "toml", 893 + "twig": "twig", 894 + "twee3": "twine", 895 + "twee3-harlowe-3": "twine", 896 + "twee3-chapbook-1": "twine", 897 + "twee3-sugarcube-2": "twine", 898 + "plaintext": "txt", 899 + "typescript": "typescript", 900 + "typescriptreact": "typescript_react", 901 + "shaderlab": "unity", 902 + "v": "v", 903 + "vala": "vala", 904 + "vb": "visual_studio", 905 + "testOutput": "visual_studio", 906 + "vue": "vue", 907 + "vue-postcss": "vue", 908 + "vue-html": "vue", 909 + "vue-directives": "vue", 910 + "vue-injection-markdown": "vue", 911 + "vue-interpolations": "vue", 912 + "vue-sfc-style-variable-injection": "vue", 913 + "windi": "windi", 914 + "wolframlanguage": "wolfram", 915 + "xml": "xml", 916 + "xquery": "xml", 917 + "xsl": "xml", 918 + "yaml": "yaml", 919 + "github-actions-workflow": "yaml", 920 + "spring-boot-properties-yaml": "yaml", 921 + "ansible": "yaml", 922 + "ansible-jinja": "yaml" 923 + }, 924 + "fileExtensions": { 925 + "ac": "_3d", 926 + "blend": "_3d", 927 + "dxf": "_3d", 928 + "fbx": "_3d", 929 + "mesh": "_3d", 930 + "mqo": "_3d", 931 + "mtl": "_3d", 932 + "obj": "_3d", 933 + "pmd": "_3d", 934 + "pmx": "_3d", 935 + "reality": "_3d", 936 + "skp": "_3d", 937 + "stl": "_3d", 938 + "stp": "_3d", 939 + "usdz": "_3d", 940 + "vac": "_3d", 941 + "vdp": "_3d", 942 + "vox": "_3d", 943 + "prw": "advpl", 944 + "prx": "advpl", 945 + "ptm": "advpl", 946 + "tlpp": "advpl", 947 + "ch": "advpl", 948 + "apk": "android", 949 + "smali": "android", 950 + "dex": "android", 951 + "ng-template": "angular", 952 + "g4": "antlr", 953 + "apib": "api_blueprint", 954 + "apiblueprint": "api_blueprint", 955 + "apl": "apl", 956 + "applescript": "apple", 957 + "ipa": "apple", 958 + "gs": "apps_script", 959 + "ino": "arduino", 960 + "ad": "asciidoc", 961 + "adoc": "asciidoc", 962 + "asciidoc": "asciidoc", 963 + "asm": "assembly", 964 + "a51": "assembly", 965 + "inc": "assembly", 966 + "nasm": "assembly", 967 + "s": "assembly", 968 + "ms": "assembly", 969 + "agc": "assembly", 970 + "ags": "assembly", 971 + "aea": "assembly", 972 + "argus": "assembly", 973 + "mitigus": "assembly", 974 + "binsource": "assembly", 975 + "astro": "astro", 976 + "mp3": "audio", 977 + "wma": "audio", 978 + "wav": "audio", 979 + "m4a": "audio", 980 + "flac": "audio", 981 + "aiff": "audio", 982 + "ahk": "autohotkey", 983 + "au3": "autoit", 984 + "a3x": "autoit", 985 + "azcli": "azure", 986 + "azure-pipelines.yml": "azure_pipelines", 987 + "azure-pipelines.yaml": "azure_pipelines", 988 + "azure-pipelines-main.yml": "azure_pipelines", 989 + "azure-pipelines-main.yaml": "azure_pipelines", 990 + "bal": "ballerina", 991 + "balx": "ballerina", 992 + "bash": "bash", 993 + "sh": "bash", 994 + "awk": "bash", 995 + "bat": "bat", 996 + "bzl": "bazel", 997 + "bazel": "bazel", 998 + "bicep": "bicep", 999 + "bin": "binary", 1000 + "c": "c", 1001 + "i": "c", 1002 + "mi": "c", 1003 + "cabal": "cabal", 1004 + "cdc": "cadence", 1005 + "cob": "cobol", 1006 + "cbl": "cobol", 1007 + "coco": "coconut", 1008 + "command": "command", 1009 + "css": "css", 1010 + "css.map": "css_map", 1011 + "cer": "certificate", 1012 + "cert": "certificate", 1013 + "crt": "certificate", 1014 + "pfx": "certificate", 1015 + "crx": "chromium", 1016 + "clj": "clojure", 1017 + "cljs": "clojure", 1018 + "cljc": "clojure", 1019 + "cmake": "cmake", 1020 + "cmake.in": "cmake_in", 1021 + "mk.in": "cmake_in", 1022 + "coffee": "coffeescript", 1023 + "cson": "coffeescript", 1024 + "iced": "coffeescript", 1025 + "cc": "cpp", 1026 + "cpp": "cpp", 1027 + "cxx": "cpp", 1028 + "c++": "cpp", 1029 + "cp": "cpp", 1030 + "mm": "cpp", 1031 + "mii": "cpp", 1032 + "ii": "cpp", 1033 + "cr": "crystal", 1034 + "ecr": "crystal", 1035 + "cs": "csharp", 1036 + "csx": "csharp", 1037 + "csharp": "csharp", 1038 + "csv": "csv", 1039 + "tsv": "csv", 1040 + "psv": "csv", 1041 + "feature": "cucumber", 1042 + "features": "cucumber", 1043 + "cu": "cuda", 1044 + "cuh": "cuda", 1045 + "d": "d", 1046 + "dart": "dart", 1047 + "pdb": "database", 1048 + "sql": "database", 1049 + "pks": "database", 1050 + "pkb": "database", 1051 + "accdb": "database", 1052 + "mdb": "database", 1053 + "sqlite": "database", 1054 + "sqlite3": "database", 1055 + "pgsql": "database", 1056 + "postgres": "database", 1057 + "plpgsql": "database", 1058 + "psql": "database", 1059 + "db": "database", 1060 + "db3": "database", 1061 + "dsc": "denizen_script", 1062 + "dhall": "dhall", 1063 + "dhallb": "dhall", 1064 + "iso": "disc", 1065 + "vcd": "disc", 1066 + "cue": "disc", 1067 + "dmg": "disc", 1068 + "hdd": "disc", 1069 + "vmdk": "disc", 1070 + "qcow": "disc", 1071 + "qcow2": "disc", 1072 + "qed": "disc", 1073 + "djt": "django", 1074 + "doc": "doc", 1075 + "docx": "doc", 1076 + "rtf": "doc", 1077 + "odt": "doc", 1078 + "pages": "doc", 1079 + "dockerfile": "docker", 1080 + "dockerignore": "docker_ignore", 1081 + "def": "dotjs", 1082 + "dot": "dotjs", 1083 + "jst": "dotjs", 1084 + "drawio": "drawio", 1085 + "dio": "drawio", 1086 + "ejs": "ejs", 1087 + "ex": "elixir", 1088 + "exs": "elixir", 1089 + "eex": "elixir", 1090 + "leex": "elixir", 1091 + "heex": "elixir", 1092 + "elm": "elm", 1093 + "env": "env", 1094 + "epub": "epub", 1095 + "epub3": "epub", 1096 + "erl": "erlang", 1097 + "fish": "fish", 1098 + "exe": "exe", 1099 + "msi": "exe", 1100 + "fig": "figma", 1101 + "bmap": "font", 1102 + "eot": "font", 1103 + "fnt": "font", 1104 + "font": "font", 1105 + "fonts": "font", 1106 + "mrf": "font", 1107 + "ntf": "font", 1108 + "odttf": "font", 1109 + "otf": "font", 1110 + "sui": "font", 1111 + "suit": "font", 1112 + "ttc": "font", 1113 + "ttf": "font", 1114 + "woff": "font", 1115 + "woff2": "font", 1116 + "4th": "forth", 1117 + "fth": "forth", 1118 + "frt": "forth", 1119 + "f": "fortran", 1120 + "for": "fortran", 1121 + "f77": "fortran", 1122 + "f90": "fortran", 1123 + "f95": "fortran", 1124 + "f03": "fortran", 1125 + "f08": "fortran", 1126 + "f18": "fortran", 1127 + "f23": "fortran", 1128 + "fypp": "fortran", 1129 + "fxp": "foxpro", 1130 + "prg": "foxpro", 1131 + "fs": "fsharp", 1132 + "fsx": "fsharp", 1133 + "fsi": "fsharp", 1134 + "fsproj": "fsharp", 1135 + "gml": "gamemaker", 1136 + "yy": "gamemaker", 1137 + "yyp": "gamemaker", 1138 + "yyz": "gamemaker", 1139 + "gleam": "gleam", 1140 + "go": "go", 1141 + "gd": "godot", 1142 + "godot": "godot_assets", 1143 + "tres": "godot_assets", 1144 + "tscn": "godot_assets", 1145 + "gdns": "godot_assets", 1146 + "gdnlib": "godot_assets", 1147 + "gdshader": "godot_assets", 1148 + "gdshaderinc": "godot_assets", 1149 + "gdextension": "godot_assets", 1150 + "gradle": "gradle", 1151 + "gr": "grain", 1152 + "graphql": "graphql", 1153 + "gql": "graphql", 1154 + "groovy": "groovy", 1155 + "h": "h", 1156 + "haml": "haml", 1157 + "hbs": "handlebars", 1158 + "mustache": "handlebars", 1159 + "hs": "haskell", 1160 + "hx": "haxe", 1161 + "hxml": "haxe", 1162 + "hcl": "hcl", 1163 + "hjson": "hjson", 1164 + "hh": "hpp", 1165 + "hpp": "hpp", 1166 + "hxx": "hpp", 1167 + "h++": "hpp", 1168 + "hp": "hpp", 1169 + "tcc": "hpp", 1170 + "inl": "hpp", 1171 + "htm": "html", 1172 + "xhtml": "html", 1173 + "html_vm": "html", 1174 + "asp": "html", 1175 + "http": "http", 1176 + "rest": "http", 1177 + "cname": "http", 1178 + "pot": "i18n", 1179 + "po": "i18n", 1180 + "mo": "i18n", 1181 + "lang": "i18n", 1182 + "strings": "i18n", 1183 + "ico": "icon", 1184 + "icns": "icon", 1185 + "png": "image", 1186 + "jpeg": "image", 1187 + "jpg": "image", 1188 + "gif": "image", 1189 + "tif": "image", 1190 + "tiff": "image", 1191 + "psd": "image", 1192 + "psb": "image", 1193 + "ami": "image", 1194 + "apx": "image", 1195 + "avif": "image", 1196 + "bmp": "image", 1197 + "bpg": "image", 1198 + "brk": "image", 1199 + "cur": "image", 1200 + "dds": "image", 1201 + "dng": "image", 1202 + "exr": "image", 1203 + "fpx": "image", 1204 + "gbr": "image", 1205 + "img": "image", 1206 + "jbig2": "image", 1207 + "jb2": "image", 1208 + "jng": "image", 1209 + "jxr": "image", 1210 + "pgf": "image", 1211 + "pic": "image", 1212 + "raw": "image", 1213 + "webp": "image", 1214 + "eps": "image", 1215 + "afphoto": "image", 1216 + "ase": "image", 1217 + "aseprite": "image", 1218 + "clip": "image", 1219 + "cpt": "image", 1220 + "heif": "image", 1221 + "heic": "image", 1222 + "kra": "image", 1223 + "mdp": "image", 1224 + "ora": "image", 1225 + "pdn": "image", 1226 + "reb": "image", 1227 + "sai": "image", 1228 + "tga": "image", 1229 + "xcf": "image", 1230 + "jfif": "image", 1231 + "ppm": "image", 1232 + "pbm": "image", 1233 + "pgm": "image", 1234 + "pnm": "image", 1235 + "java": "java", 1236 + "jsp": "java", 1237 + "class": "java_class", 1238 + "jar": "java_jar", 1239 + "js": "javascript", 1240 + "cjs": "javascript", 1241 + "esx": "javascript", 1242 + "mjs": "javascript", 1243 + "jsconfig.json": "javascript_config", 1244 + "js.map": "javascript_map", 1245 + "mjs.map": "javascript_map", 1246 + "cjs.map": "javascript_map", 1247 + "jsx": "javascript_react", 1248 + "spec.js": "javascript_test", 1249 + "spec.cjs": "javascript_test", 1250 + "spec.mjs": "javascript_test", 1251 + "e2e-spec.js": "javascript_test", 1252 + "e2e-spec.cjs": "javascript_test", 1253 + "e2e-spec.mjs": "javascript_test", 1254 + "test.js": "javascript_test", 1255 + "test.cjs": "javascript_test", 1256 + "test.mjs": "javascript_test", 1257 + "js.snap": "javascript_test", 1258 + "cy.js": "javascript_test", 1259 + "spec.jsx": "javascript_test", 1260 + "test.jsx": "javascript_test", 1261 + "jsx.snap": "javascript_test", 1262 + "cy.jsx": "javascript_test", 1263 + "jenkinsfile": "jenkins", 1264 + "jenkins": "jenkins", 1265 + "jinja": "jinja", 1266 + "jinja2": "jinja", 1267 + "j2": "jinja", 1268 + "jinja-html": "jinja", 1269 + "json": "json", 1270 + "jsonc": "json", 1271 + "tsbuildinfo": "json", 1272 + "json5": "json", 1273 + "jsonl": "json", 1274 + "ndjson": "json", 1275 + "jl": "julia", 1276 + "ipynb": "jupyter", 1277 + "pub": "key", 1278 + "key": "key", 1279 + "pem": "key", 1280 + "asc": "key", 1281 + "gpg": "key", 1282 + "passwd": "key", 1283 + "keystore": "key", 1284 + "kv": "kivy", 1285 + "kt": "kotlin", 1286 + "kts": "kotlin", 1287 + "blade.php": "laravel", 1288 + "inky.php": "laravel", 1289 + "tex": "latex", 1290 + "sty": "latex", 1291 + "dtx": "latex", 1292 + "ltx": "latex", 1293 + "less": "less", 1294 + "lib": "lib", 1295 + "bib": "lib", 1296 + "dll": "lib", 1297 + "dlc": "properties", 1298 + "lisp": "lisp", 1299 + "lsp": "lisp", 1300 + "cl": "lisp", 1301 + "fast": "lisp", 1302 + "lhs": "literate", 1303 + "ls": "livescript", 1304 + "log": "log", 1305 + "lottie": "lottie", 1306 + "lua": "lua", 1307 + ".ics": "mail", 1308 + "mk": "makefile", 1309 + "md": "markdown", 1310 + "markdown": "markdown", 1311 + "rst": "markdown", 1312 + "mdx": "markdown_mdx", 1313 + "marko": "marko", 1314 + "nb": "mathematica", 1315 + "mat": "matlab", 1316 + "mermaid": "mermaid", 1317 + "mmd": "mermaid", 1318 + "meson": "meson", 1319 + "wrap": "meson", 1320 + "mint": "mint", 1321 + "mjml": "mjml", 1322 + "mojo": "mojo", 1323 + "🔥": "mojo", 1324 + "moon": "moonscript", 1325 + "nf": "nextflow", 1326 + "nginx": "nginx", 1327 + "nginxconfig": "nginx", 1328 + "nim": "nim", 1329 + "ninja": "ninja", 1330 + "nix": "nix", 1331 + "nupkg": "nuget", 1332 + "njk": "nunjucks", 1333 + "nunjucks": "nunjucks", 1334 + "nx": "nx", 1335 + "ml": "ocaml", 1336 + "mli": "ocaml", 1337 + "cmx": "ocaml", 1338 + "odin": "odin", 1339 + "pwn": "pawn", 1340 + "amx": "pawn", 1341 + "pdf": "pdf", 1342 + "pm": "perl", 1343 + "raku": "perl", 1344 + "pl": "perl", 1345 + "php": "php", 1346 + "pine": "pinejs", 1347 + "pcss": "postcss", 1348 + "sss": "postcss", 1349 + "ps1": "powershell", 1350 + "psm1": "powershell", 1351 + "psd1": "powershell", 1352 + "ps1xml": "powershell", 1353 + "psc1": "powershell", 1354 + "pssc": "powershell", 1355 + "prisma": "prisma", 1356 + "pde": "processing", 1357 + "p": "prolog", 1358 + "pro": "prolog", 1359 + "ini": "properties", 1360 + "config": "properties", 1361 + "conf": "properties", 1362 + "properties": "properties", 1363 + "prop": "properties", 1364 + "settings": "properties", 1365 + "option": "properties", 1366 + "props": "properties", 1367 + "prefs": "properties", 1368 + "sln.dotsettings": "properties", 1369 + "sln.dotsettings.user": "properties", 1370 + "cfg": "properties", 1371 + "proto": "proto", 1372 + "jade": "pug", 1373 + "pug": "pug", 1374 + "pp": "puppet", 1375 + "purs": "purescript", 1376 + "pure": "purescript", 1377 + "py": "python", 1378 + "pyc": "python_compiled", 1379 + "pyo": "python_compiled", 1380 + "pyd": "python_compiled", 1381 + "whl": "python_misc", 1382 + "r": "r", 1383 + "rmd": "r", 1384 + "rkt": "racket", 1385 + "rkts": "racket", 1386 + "rktd": "racket", 1387 + "rktl": "racket", 1388 + "cshtml": "razor", 1389 + "vbhtml": "razor", 1390 + "re": "reason", 1391 + "rei": "reason", 1392 + "red": "red", 1393 + "reds": "red", 1394 + "res": "rescript", 1395 + "resi": "rescript", 1396 + "rql": "restql", 1397 + "restql": "restql", 1398 + "riot": "riot", 1399 + "rbxl": "roblox", 1400 + "rbxlx": "roblox", 1401 + "rbxm": "roblox", 1402 + "rbxmx": "roblox", 1403 + "rb": "ruby", 1404 + "erb": "ruby", 1405 + "rs": "rust", 1406 + "ron": "rust", 1407 + "scss": "sass", 1408 + "sass": "sass", 1409 + "scala": "scala", 1410 + "sc": "scala", 1411 + "ss": "scheme", 1412 + "scm": "scheme", 1413 + "code-search": "search", 1414 + "glsl": "shader", 1415 + "vert": "shader", 1416 + "tesc": "shader", 1417 + "tese": "shader", 1418 + "geom": "shader", 1419 + "frag": "shader", 1420 + "comp": "shader", 1421 + "vert.glsl": "shader", 1422 + "tesc.glsl": "shader", 1423 + "tese.glsl": "shader", 1424 + "geom.glsl": "shader", 1425 + "frag.glsl": "shader", 1426 + "comp.glsl": "shader", 1427 + "vertex.glsl": "shader", 1428 + "geometry.glsl": "shader", 1429 + "fragment.glsl": "shader", 1430 + "compute.glsl": "shader", 1431 + "ts.glsl": "shader", 1432 + "gs.glsl": "shader", 1433 + "vs.glsl": "shader", 1434 + "fs.glsl": "shader", 1435 + "shader": "shader", 1436 + "vertexshader": "shader", 1437 + "fragmentshader": "shader", 1438 + "geometryshader": "shader", 1439 + "computeshader": "shader", 1440 + "hlsl": "shader", 1441 + "pixel.hlsl": "shader", 1442 + "geometry.hlsl": "shader", 1443 + "compute.hlsl": "shader", 1444 + "tessellation.hlsl": "shader", 1445 + "px.hlsl": "shader", 1446 + "geom.hlsl": "shader", 1447 + "comp.hlsl": "shader", 1448 + "tess.hlsl": "shader", 1449 + "wgsl": "shader", 1450 + "sketch": "sketch", 1451 + "pptx": "slide", 1452 + "ppt": "slide", 1453 + "pptm": "slide", 1454 + "potx": "slide", 1455 + "potm": "slide", 1456 + "ppsx": "slide", 1457 + "ppsm": "slide", 1458 + "pps": "slide", 1459 + "ppam": "slide", 1460 + "ppa": "slide", 1461 + "odp": "slide", 1462 + "slim": "slim", 1463 + "tpl": "smarty", 1464 + "sol": "solidity", 1465 + "xlsx": "spreadsheet", 1466 + "xlsm": "spreadsheet", 1467 + "xls": "spreadsheet", 1468 + "ods": "spreadsheet", 1469 + "numbers": "spreadsheet", 1470 + "spwn": "spwn", 1471 + "stan": "stan", 1472 + "story.js": "storybook", 1473 + "story.ts": "storybook", 1474 + "stories.js": "storybook", 1475 + "stories.ts": "storybook", 1476 + "story.jsx": "storybook", 1477 + "stories.jsx": "storybook", 1478 + "story.tsx": "storybook", 1479 + "stories.tsx": "storybook", 1480 + "story.svelte": "storybook_svelte", 1481 + "stories.svelte": "storybook_svelte", 1482 + "story.vue": "storybook_vue", 1483 + "stories.vue": "storybook_vue", 1484 + "sublime-project": "sublime", 1485 + "sublime-workspace": "sublime", 1486 + "svelte": "svelte", 1487 + "svg": "svg", 1488 + "swagger.json": "swagger", 1489 + "swagger.yml": "swagger", 1490 + "swagger.yaml": "swagger", 1491 + "swift": "swift", 1492 + "tauri": "tauri", 1493 + "taskfile.yml": "task", 1494 + "taskfile.yaml": "task", 1495 + "tf": "terraform", 1496 + "tf.json": "terraform", 1497 + "tfvars": "terraform", 1498 + "tfstate": "terraform", 1499 + "tfbackend": "terraform", 1500 + "tldr": "tldraw", 1501 + "todo": "todo", 1502 + "toml": "toml", 1503 + "tree": "tree", 1504 + "twig": "twig", 1505 + "tw": "twine", 1506 + "twee": "twine", 1507 + "txt": "txt", 1508 + "ts": "typescript", 1509 + "tsconfig.json": "typescript_config", 1510 + "d.ts": "typescript_def", 1511 + "d.cts": "typescript_def", 1512 + "d.mts": "typescript_def", 1513 + "tsx": "typescript_react", 1514 + "spec-d.ts": "typescript_test", 1515 + "spec.ts": "typescript_test", 1516 + "spec.cts": "typescript_test", 1517 + "spec.mts": "typescript_test", 1518 + "cy.ts": "typescript_test", 1519 + "e2e-spec.ts": "typescript_test", 1520 + "e2e-spec.cts": "typescript_test", 1521 + "e2e-spec.mts": "typescript_test", 1522 + "test-d.ts": "typescript_test", 1523 + "test.ts": "typescript_test", 1524 + "test.cts": "typescript_test", 1525 + "test.mts": "typescript_test", 1526 + "ts.snap": "typescript_test", 1527 + "spec.tsx": "typescript_test", 1528 + "test.tsx": "typescript_test", 1529 + "tsx.snap": "typescript_test", 1530 + "cy.tsx": "typescript_test", 1531 + "iuml": "uml", 1532 + "pu": "uml", 1533 + "puml": "uml", 1534 + "plantuml": "uml", 1535 + "wsd": "uml", 1536 + "unity": "unity", 1537 + "url": "url", 1538 + "v": "v", 1539 + "avi": "video", 1540 + "flv": "video", 1541 + "gifv": "video", 1542 + "m2v": "video", 1543 + "m4v": "video", 1544 + "mkv": "video", 1545 + "mov": "video", 1546 + "mp2": "video", 1547 + "mp4": "video", 1548 + "mpe": "video", 1549 + "mpeg": "video", 1550 + "mpg": "video", 1551 + "mpv": "video", 1552 + "ogg": "video", 1553 + "ogv": "video", 1554 + "qt": "video", 1555 + "rm": "video", 1556 + "rmvb": "video", 1557 + "vob": "video", 1558 + "webm": "video", 1559 + "wmv": "video", 1560 + "yuv": "video", 1561 + "vm": "velocity", 1562 + "fhtml": "velocity", 1563 + "vtl": "velocity", 1564 + "sv": "verilog", 1565 + "svh": "verilog", 1566 + "vhd": "verilog", 1567 + "exrc": "vim", 1568 + "gvimrc": "vim", 1569 + "vim": "vim", 1570 + "vimrc": "vim", 1571 + "viminfo": "vim", 1572 + "csproj": "visual_studio", 1573 + "ruleset": "visual_studio", 1574 + "sln": "visual_studio", 1575 + "suo": "visual_studio", 1576 + "vb": "visual_studio", 1577 + "vbs": "visual_studio", 1578 + "vcxitems": "visual_studio", 1579 + "vcxitems.filters": "visual_studio", 1580 + "vcxproj": "visual_studio", 1581 + "vcxproj.filters": "visual_studio", 1582 + "vcxproj.user": "visual_studio", 1583 + "vsixmanifest": "vs_code", 1584 + "vsix": "vs_code", 1585 + "code-workplace": "vs_code", 1586 + "code-workspace": "vs_code", 1587 + "code-profile": "vs_code", 1588 + "code-snippets": "vs_code", 1589 + "vscodeignore": "vs_code_ignore", 1590 + "vue": "vue", 1591 + "wat": "web_assembly", 1592 + "wasm": "web_assembly", 1593 + "wl": "wolfram", 1594 + "workflows/yml": "workflow", 1595 + "workflows/yaml": "workflow", 1596 + "vagrantfile": "vagrant", 1597 + "vala": "vala", 1598 + "xaml": "xaml", 1599 + "pbxproj": "xcode_project", 1600 + "xd": "xd", 1601 + "xib": "xib", 1602 + "nib": "xib", 1603 + "storyboard": "xib", 1604 + "xml": "xml", 1605 + "plist": "xml", 1606 + "xsd": "xml", 1607 + "dtd": "xml", 1608 + "xsl": "xml", 1609 + "xslt": "xml", 1610 + "resx": "xml", 1611 + "iml": "xml", 1612 + "xquery": "xml", 1613 + "tmLanguage": "xml", 1614 + "manifest": "xml", 1615 + "project": "xml", 1616 + "xml.dist": "xml", 1617 + "xml.dist.sample": "xml", 1618 + "dmn": "xml", 1619 + "jrxml": "xml", 1620 + "yml": "yaml", 1621 + "yaml": "yaml", 1622 + "zig": "zig", 1623 + "7z": "zip", 1624 + "br": "zip", 1625 + "brotli": "zip", 1626 + "bz2": "zip", 1627 + "bzip2": "zip", 1628 + "gz": "zip", 1629 + "gzip": "zip", 1630 + "lz4": "zip", 1631 + "lzma": "zip", 1632 + "rar": "zip", 1633 + "tar": "zip", 1634 + "tgz": "zip", 1635 + "txz": "zip", 1636 + "tz": "zip", 1637 + "xz": "zip", 1638 + "zip": "zip", 1639 + "zst": "zip" 1640 + }, 1641 + "fileNames": { 1642 + ".adonisrc.json": "adonis", 1643 + "ace": "adonis", 1644 + ".alexrc": "alex", 1645 + ".alexrc.yml": "alex", 1646 + ".alexrc.yaml": "alex", 1647 + "alexrc.js": "alex", 1648 + "androidmanifest.xml": "android", 1649 + "angular-cli.json": "angular", 1650 + ".angular-cli.json": "angular", 1651 + "angular.json": "angular", 1652 + "ansible.cfg": "ansible", 1653 + ".ansible-lint": "ansible", 1654 + "ansible-lint.yml": "ansible", 1655 + "apollo.config.js": "apollo", 1656 + "apollo.config.ts": "apollo", 1657 + ".appveyor.yml": "appveyor", 1658 + "appveyor.yml": "appveyor", 1659 + ".astylerc": "artistic_style", 1660 + "astro.config.js": "astro_config", 1661 + "astro.config.mjs": "astro_config", 1662 + "astro.config.cjs": "astro_config", 1663 + "astro.config.ts": "astro_config", 1664 + "astro.config.cts": "astro_config", 1665 + "astro.config.mts": "astro_config", 1666 + "aurelia.json": "aurelia", 1667 + ".autorc": "auto", 1668 + "auto.config.js": "auto", 1669 + "auto.config.ts": "auto", 1670 + "auto-config.json": "auto", 1671 + "auto-config.yaml": "auto", 1672 + "auto-config.yml": "auto", 1673 + "auto-config.ts": "auto", 1674 + "auto-config.js": "auto", 1675 + "azure-pipelines.yml": "azure_pipelines", 1676 + "azure-pipelines.yaml": "azure_pipelines", 1677 + "azure-pipelines-main.yml": "azure_pipelines", 1678 + "azure-pipelines-main.yaml": "azure_pipelines", 1679 + ".babelrc": "babel", 1680 + ".babelrc.cjs": "babel", 1681 + ".babelrc.js": "babel", 1682 + ".babelrc.mjs": "babel", 1683 + ".babelrc.json": "babel", 1684 + "babel.config.cjs": "babel", 1685 + "babel.config.js": "babel", 1686 + "babel.config.mjs": "babel", 1687 + "babel.config.json": "babel", 1688 + "babel-transform.js": "babel", 1689 + ".babel-plugin-macrosrc": "babel", 1690 + ".babel-plugin-macrosrc.json": "babel", 1691 + ".babel-plugin-macrosrc.yaml": "babel", 1692 + ".babel-plugin-macrosrc.yml": "babel", 1693 + ".babel-plugin-macrosrc.js": "babel", 1694 + "babel-plugin-macros.config.js": "babel", 1695 + "commit-msg": "bash", 1696 + "pre-commit": "bash", 1697 + "pre-push": "bash", 1698 + "post-merge": "bash", 1699 + ".bazelignore": "bazel_ignore", 1700 + ".bazelrc": "bazel", 1701 + ".bazelversion": "bazel", 1702 + "biome.json": "biome", 1703 + "bitbucket-pipelines.yaml": "bitbucket", 1704 + "bitbucket-pipelines.yml": "bitbucket", 1705 + ".bithoundrc": "bithound", 1706 + "blitz.config.js": "blitz", 1707 + "blitz.config.ts": "blitz", 1708 + ".blitz.config.compiled.js": "blitz", 1709 + ".bowerrc": "bower", 1710 + "bower.json": "bower", 1711 + "browserslist": "browserslist", 1712 + ".browserslistrc": "browserslist", 1713 + "buildkite.yml": "buildkite", 1714 + "buildkite.yaml": "buildkite", 1715 + ".buckconfig": "buck", 1716 + "bunfig.toml": "bun", 1717 + "bun.lockb": "bun_lock", 1718 + "cabal.project": "cabal", 1719 + "cabal.project.freeze": "cabal", 1720 + "cabal.project.local": "cabal", 1721 + "Caddyfile": "caddy", 1722 + "bin/cake": "cakephp", 1723 + "capacitor.config.json": "capacitor", 1724 + "capacitor.config.ts": "capacitor", 1725 + "cargo.toml": "cargo", 1726 + "cargo.lock": "cargo_lock", 1727 + "changelog": "changelog", 1728 + "changelog.md": "changelog", 1729 + "changelog.rst": "changelog", 1730 + "changelog.txt": "changelog", 1731 + "changes": "changelog", 1732 + "changes.md": "changelog", 1733 + "changes.rst": "changelog", 1734 + "changes.txt": "changelog", 1735 + "chart.yaml": "chart", 1736 + "chart.lock": "chart_lock", 1737 + "circle.yml": "circle_ci", 1738 + "cmakelists.txt": "cmake", 1739 + "cmakecache.txt": "cmake", 1740 + "podfile": "cocoapods", 1741 + "podfile.lock": "cocoapods_lock", 1742 + "code_of_conduct.md": "code_of_conduct", 1743 + "code_of_conduct.txt": "code_of_conduct", 1744 + "code_of_conduct": "code_of_conduct", 1745 + ".codeclimate.yml": "code_climate", 1746 + ".codecov.yml": "codecov", 1747 + "codecov.yml": "codecov", 1748 + "codeowners": "codeowners", 1749 + "owners": "codeowners", 1750 + "command": "command", 1751 + ".commitlintrc": "commitlint", 1752 + ".commitlintrc.js": "commitlint", 1753 + ".commitlintrc.cjs": "commitlint", 1754 + ".commitlintrc.ts": "commitlint", 1755 + ".commitlintrc.cts": "commitlint", 1756 + ".commitlintrc.json": "commitlint", 1757 + ".commitlintrc.yaml": "commitlint", 1758 + ".commitlintrc.yml": "commitlint", 1759 + ".commitlint.yaml": "commitlint", 1760 + ".commitlint.yml": "commitlint", 1761 + "commitlint.config.js": "commitlint", 1762 + "commitlint.config.cjs": "commitlint", 1763 + "commitlint.config.ts": "commitlint", 1764 + "commitlint.config.cts": "commitlint", 1765 + "contributing": "contributing", 1766 + "contributing.md": "contributing", 1767 + "contributing.rst": "contributing", 1768 + "contributing.txt": "contributing", 1769 + "craco.config.ts": "craco", 1770 + "craco.config.js": "craco", 1771 + "craco.config.cjs": "craco", 1772 + ".cracorc.ts": "craco", 1773 + ".cracorc.js": "craco", 1774 + ".cracorc": "craco", 1775 + "cypress.json": "cypress", 1776 + "cypress.env.json": "cypress", 1777 + "cypress.config.ts": "cypress", 1778 + "cypress.config.js": "cypress", 1779 + "cypress.config.cjs": "cypress", 1780 + "cypress.config.mjs": "cypress", 1781 + ".pubignore": "dart", 1782 + "freezed.dart": "dart_generated", 1783 + "g.dart": "dart_generated", 1784 + "deno.json": "deno", 1785 + "deno.jsonc": "deno", 1786 + "deno.lock": "deno_lock", 1787 + "dependabot.yml": "dependabot", 1788 + "dependabot.yaml": "dependabot", 1789 + ".detoxrc": "detox", 1790 + ".detoxrc.js": "detox", 1791 + ".detoxrc.json": "detox", 1792 + "detox.config.js": "detox", 1793 + "detox.config.json": "detox", 1794 + "dockerfile": "docker", 1795 + "dockerfile.prod": "docker", 1796 + "dockerfile.production": "docker", 1797 + "dockerfile.alpha": "docker", 1798 + "dockerfile.beta": "docker", 1799 + "dockerfile.stage": "docker", 1800 + "dockerfile.staging": "docker", 1801 + "dockerfile.dev": "docker", 1802 + "dockerfile.development": "docker", 1803 + "dockerfile.local": "docker", 1804 + "dockerfile.test": "docker", 1805 + "dockerfile.testing": "docker", 1806 + "dockerfile.ci": "docker", 1807 + "dockerfile.web": "docker", 1808 + "dockerfile.worker": "docker", 1809 + "docker-compose.yml": "docker_compose", 1810 + "docker-compose.override.yml": "docker_compose", 1811 + "docker-compose.prod.yml": "docker_compose", 1812 + "docker-compose.production.yml": "docker_compose", 1813 + "docker-compose.alpha.yml": "docker_compose", 1814 + "docker-compose.beta.yml": "docker_compose", 1815 + "docker-compose.stage.yml": "docker_compose", 1816 + "docker-compose.staging.yml": "docker_compose", 1817 + "docker-compose.dev.yml": "docker_compose", 1818 + "docker-compose.development.yml": "docker_compose", 1819 + "docker-compose.local.yml": "docker_compose", 1820 + "docker-compose.test.yml": "docker_compose", 1821 + "docker-compose.testing.yml": "docker_compose", 1822 + "docker-compose.ci.yml": "docker_compose", 1823 + "docker-compose.web.yml": "docker_compose", 1824 + "docker-compose.worker.yml": "docker_compose", 1825 + "docker-compose.yaml": "docker_compose", 1826 + "docker-compose.override.yaml": "docker_compose", 1827 + "docker-compose.prod.yaml": "docker_compose", 1828 + "docker-compose.production.yaml": "docker_compose", 1829 + "docker-compose.alpha.yaml": "docker_compose", 1830 + "docker-compose.beta.yaml": "docker_compose", 1831 + "docker-compose.stage.yaml": "docker_compose", 1832 + "docker-compose.staging.yaml": "docker_compose", 1833 + "docker-compose.dev.yaml": "docker_compose", 1834 + "docker-compose.development.yaml": "docker_compose", 1835 + "docker-compose.local.yaml": "docker_compose", 1836 + "docker-compose.test.yaml": "docker_compose", 1837 + "docker-compose.testing.yaml": "docker_compose", 1838 + "docker-compose.ci.yaml": "docker_compose", 1839 + "docker-compose.web.yaml": "docker_compose", 1840 + "docker-compose.worker.yaml": "docker_compose", 1841 + "dockerignore": "docker_ignore", 1842 + ".dockerignore": "docker_ignore", 1843 + "docusaurus.config.js": "docusaurus", 1844 + ".editorconfig": "editorconfig", 1845 + ".ember-cli": "ember", 1846 + ".ember-cli.js": "ember", 1847 + "ember-cli-builds.js": "ember", 1848 + ".env": "env", 1849 + ".env.defaults": "env", 1850 + ".env.example": "env", 1851 + ".env.sample": "env", 1852 + ".env.template": "env", 1853 + ".env.schema": "env", 1854 + ".env.local": "env", 1855 + ".env.dev": "env", 1856 + ".env.development": "env", 1857 + ".env.alpha": "env", 1858 + ".env.e2e": "env", 1859 + ".env.qa": "env", 1860 + ".env.dist": "env", 1861 + ".env.prod": "env", 1862 + ".env.production": "env", 1863 + ".env.stage": "env", 1864 + ".env.staging": "env", 1865 + ".env.preview": "env", 1866 + ".env.test": "env", 1867 + ".env.testing": "env", 1868 + ".env.development.local": "env", 1869 + ".env.qa.local": "env", 1870 + ".env.production.local": "env", 1871 + ".env.staging.local": "env", 1872 + ".env.test.local": "env", 1873 + ".env.uat": "env", 1874 + ".env.cypress": "env", 1875 + "esbuild.js": "esbuild", 1876 + "esbuild.ts": "esbuild", 1877 + "esbuild.cjs": "esbuild", 1878 + "esbuild.mjs": "esbuild", 1879 + "esbuild.config.js": "esbuild", 1880 + "esbuild.config.ts": "esbuild", 1881 + "esbuild.config.cjs": "esbuild", 1882 + "esbuild.config.mjs": "esbuild", 1883 + ".eslintrc.js": "eslint", 1884 + ".eslintrc.cjs": "eslint", 1885 + ".eslintrc.yaml": "eslint", 1886 + ".eslintrc.yml": "eslint", 1887 + ".eslintrc.json": "eslint", 1888 + ".eslintrc-md.js": "eslint", 1889 + ".eslintrc-jsdoc.js": "eslint", 1890 + ".eslintrc": "eslint", 1891 + "eslint.config.js": "eslint", 1892 + ".eslintignore": "eslint_ignore", 1893 + ".eslintcache": "eslint_ignore", 1894 + "fastfile": "fastlane", 1895 + "fastlane/appfile": "fastlane", 1896 + "fastlane/pluginfile": "fastlane", 1897 + "favicon.ico": "favicon", 1898 + "firebase.json": "firebase", 1899 + ".firebaserc": "firebase", 1900 + "firestore.rules": "firebase", 1901 + "firestore.indexes.json": "firebase", 1902 + ".flowconfig": "flow", 1903 + "gatsby-config.js": "gatsby", 1904 + "gatsby-config.mjs": "gatsby", 1905 + "gatsby-config.ts": "gatsby", 1906 + "gatsby-node.js": "gatsby", 1907 + "gatsby-node.mjs": "gatsby", 1908 + "gatsby-node.ts": "gatsby", 1909 + "gatsby-browser.js": "gatsby", 1910 + "gatsby-browser.tsx": "gatsby", 1911 + "gatsby-ssr.js": "gatsby", 1912 + "gatsby-ssr.tsx": "gatsby", 1913 + ".gcloudignore": "gcp", 1914 + ".gitignore": "git", 1915 + ".gitconfig": "git", 1916 + ".gitattributes": "git", 1917 + ".gitmodules": "git", 1918 + ".gitkeep": "git", 1919 + ".git-include": "git", 1920 + ".gitbook.yaml": "gitbook", 1921 + ".gitlab-ci.yml": "gitlab", 1922 + ".gitpod.yml": "gitpod", 1923 + "gleam.toml": "gleam", 1924 + "go.mod": "go_mod", 1925 + "go.sum": "go_mod", 1926 + "go.work": "go_mod", 1927 + "go.work.sum": "go_mod", 1928 + "gradle.properties": "gradle", 1929 + "gradlew": "gradle", 1930 + "gradle-wrapper.properties": "gradle", 1931 + ".graphqlconfig": "graphql", 1932 + ".graphqlrc": "graphql", 1933 + ".graphqlrc.json": "graphql", 1934 + ".graphqlrc.js": "graphql", 1935 + ".graphqlrc.cjs": "graphql", 1936 + ".graphqlrc.ts": "graphql", 1937 + ".graphqlrc.toml": "graphql", 1938 + ".graphqlrc.yaml": "graphql", 1939 + ".graphqlrc.yml": "graphql", 1940 + "graphql.config.json": "graphql", 1941 + "graphql.config.js": "graphql", 1942 + "graphql.config.ts": "graphql", 1943 + "graphql.config.toml": "graphql", 1944 + "graphql.config.yaml": "graphql", 1945 + "graphql.config.yml": "graphql", 1946 + "gridsome.config.js": "gridsome", 1947 + "gridsome.server.js": "gridsome", 1948 + "gulpfile.js": "gulp", 1949 + "gulpfile.mjs": "gulp", 1950 + "gulpfile.ts": "gulp", 1951 + "gulpfile.cts": "gulp", 1952 + "gulpfile.mts": "gulp", 1953 + "gulpfile.babel.js": "gulp", 1954 + ".helmignore": "helm", 1955 + "procfile": "heroku", 1956 + "procfile.windows": "heroku", 1957 + ".htaccess": "htaccess", 1958 + "ionic.config.json": "ionic", 1959 + ".io-config.json": "ionic", 1960 + "jsconfig.json": "javascript_config", 1961 + "jenkinsfile": "jenkins", 1962 + "jest.config.js": "jest", 1963 + "jest.config.cjs": "jest", 1964 + "jest.config.mjs": "jest", 1965 + "jest.config.ts": "jest", 1966 + "jest.config.cts": "jest", 1967 + "jest.config.mts": "jest", 1968 + "jest.config.json": "jest", 1969 + "jest.e2e.config.js": "jest", 1970 + "jest.e2e.config.cjs": "jest", 1971 + "jest.e2e.config.mjs": "jest", 1972 + "jest.e2e.config.ts": "jest", 1973 + "jest.e2e.config.cts": "jest", 1974 + "jest.e2e.config.mts": "jest", 1975 + "jest.e2e.config.json": "jest", 1976 + "jest.e2e.json": "jest", 1977 + "jest-unit.config.js": "jest", 1978 + "jest-e2e.config.js": "jest", 1979 + "jest-e2e.config.cjs": "jest", 1980 + "jest-e2e.config.mjs": "jest", 1981 + "jest-e2e.config.ts": "jest", 1982 + "jest-e2e.config.cts": "jest", 1983 + "jest-e2e.config.mts": "jest", 1984 + "jest-e2e.config.json": "jest", 1985 + "jest-e2e.json": "jest", 1986 + "jest-github-actions-reporter.js": "jest", 1987 + "jest.setup.js": "jest", 1988 + "jest.setup.ts": "jest", 1989 + "jest.json": "jest", 1990 + ".jestrc": "jest", 1991 + ".jestrc.js": "jest", 1992 + ".jestrc.json": "jest", 1993 + "jest.teardown.js": "jest", 1994 + "jest-preset.json": "jest", 1995 + "jest-preset.js": "jest", 1996 + "jest-preset.cjs": "jest", 1997 + "jest-preset.mjs": "jest", 1998 + "jest.preset.js": "jest", 1999 + "jest.preset.mjs": "jest", 2000 + "jest.preset.cjs": "jest", 2001 + "jest.preset.json": "jest", 2002 + "histoire.config.ts": "histoire", 2003 + "histoire.config.js": "histoire", 2004 + ".histoire.js": "histoire", 2005 + ".histoire.ts": "histoire", 2006 + ".huskyrc": "husky", 2007 + "husky.config.js": "husky", 2008 + ".huskyrc.json": "husky", 2009 + ".huskyrc.js": "husky", 2010 + ".huskyrc.yaml": "husky", 2011 + ".huskyrc.yml": "husky", 2012 + ".jscsrc": "json", 2013 + ".jshintrc": "json", 2014 + "composer.lock": "json", 2015 + ".jsbeautifyrc": "json", 2016 + ".esformatter": "json", 2017 + "cdp.pid": "json", 2018 + ".lintstagedrc": "lintstaged", 2019 + ".whitesource": "json", 2020 + "karma.conf.js": "karma", 2021 + "karma.conf.ts": "karma", 2022 + "karma.conf.coffee": "karma", 2023 + "karma.config.js": "karma", 2024 + "karma.config.ts": "karma", 2025 + "karma-main.js": "karma", 2026 + "karma-main.ts": "karma", 2027 + "k8s.yml": "kubernetes", 2028 + "k8s.yaml": "kubernetes", 2029 + "kubernetes.yml": "kubernetes", 2030 + "kubernetes.yaml": "kubernetes", 2031 + ".k8s.yml": "kubernetes", 2032 + ".k8s.yaml": "kubernetes", 2033 + "artisan": "laravel", 2034 + "lerna.json": "lerna", 2035 + "copying": "license", 2036 + "copying.md": "license", 2037 + "copying.rst": "license", 2038 + "copying.txt": "license", 2039 + "copyright": "license", 2040 + "copyright.md": "license", 2041 + "copyright.rst": "license", 2042 + "copyright.txt": "license", 2043 + "license": "license", 2044 + "license-agpl": "license", 2045 + "license-apache": "license", 2046 + "license-bsd": "license", 2047 + "license-mit": "license", 2048 + "license-gpl": "license", 2049 + "license-lgpl": "license", 2050 + "license.md": "license", 2051 + "license.rst": "license", 2052 + "license.txt": "license", 2053 + "licence": "license", 2054 + "licence-agpl": "license", 2055 + "licence-apache": "license", 2056 + "licence-bsd": "license", 2057 + "licence-mit": "license", 2058 + "licence-gpl": "license", 2059 + "licence-lgpl": "license", 2060 + "licence.md": "license", 2061 + "licence.rst": "license", 2062 + "licence.txt": "license", 2063 + ".lighthouserc.js": "lighthouse", 2064 + "lighthouserc.js": "lighthouse", 2065 + ".lighthouserc.json": "lighthouse", 2066 + "lighthouserc.json": "lighthouse", 2067 + ".lighthouserc.yml": "lighthouse", 2068 + "lighthouserc.yml": "lighthouse", 2069 + ".lighthouserc.yaml": "lighthouse", 2070 + "lighthouserc.yaml": "lighthouse", 2071 + ".lintstagedrc.json": "lintstaged", 2072 + ".lintstagedrc.yaml": "lintstaged", 2073 + ".lintstagedrc.yml": "lintstaged", 2074 + ".lintstagedrc.mjs": "lintstaged", 2075 + ".lintstagedrc.cjs": "lintstaged", 2076 + ".lintstagedrc.js": "lintstaged", 2077 + "lint-staged.config.js": "lintstaged", 2078 + "lint-staged.config.mjs": "lintstaged", 2079 + "lint-staged.config.cjs": "lintstaged", 2080 + ".luacheckrc": "lua", 2081 + ".mailmap": "mail", 2082 + "makefile": "makefile", 2083 + "gnumakefile": "makefile", 2084 + "kbuild": "makefile", 2085 + "maven.config": "maven", 2086 + "jvm.config": "maven", 2087 + "pom.xml": "maven", 2088 + ".hg": "mercurial", 2089 + ".hgignore": "mercurial", 2090 + ".hgflow": "mercurial", 2091 + ".hgrc": "mercurial", 2092 + "hgrc": "mercurial", 2093 + "mercurial.ini": "mercurial", 2094 + "meson.build": "meson", 2095 + "meson_options.txt": "meson", 2096 + "metro.config.js": "metro", 2097 + "metro.config.ts": "metro", 2098 + ".mjmlconfig": "mjml", 2099 + "mocha.opts": "mocha", 2100 + ".mocharc.yml": "mocha", 2101 + ".mocharc.yaml": "mocha", 2102 + ".mocharc.js": "mocha", 2103 + ".mocharc.json": "mocha", 2104 + ".mocharc.jsonc": "mocha", 2105 + ".modernizrrc": "modernizr", 2106 + ".modernizrrc.js": "modernizr", 2107 + ".modernizrrc.json": "modernizr", 2108 + "moon.yml": "moon", 2109 + "nativescript.config.ts": "nativescript", 2110 + "netlify.json": "netlify", 2111 + "netlify.yml": "netlify", 2112 + "netlify.yaml": "netlify", 2113 + "netlify.toml": "netlify", 2114 + "nest-cli.json": "nest", 2115 + ".nest-cli.json": "nest", 2116 + "nestconfig.json": "nest", 2117 + ".nestconfig.json": "nest", 2118 + "next.config.js": "next", 2119 + "next.config.mjs": "next", 2120 + "next.config.ts": "next", 2121 + "next.config.mts": "next", 2122 + "nginx.conf": "nginx", 2123 + "nodemon.json": "nodemon", 2124 + "nodemon-debug.json": "nodemon", 2125 + ".npmrc": "npm", 2126 + ".npmignore": "npm_ignore", 2127 + "package-lock.json": "npm_lock", 2128 + "nuget.config": "nuget", 2129 + ".nuspec": "nuget", 2130 + "nuget.exe": "nuget", 2131 + "nuxt.config.js": "nuxt", 2132 + "nuxt.config.ts": "nuxt", 2133 + ".nuxtrc": "nuxt", 2134 + ".nuxtignore": "nuxt_ignore", 2135 + "nx.json": "nx", 2136 + "package.json": "package_json", 2137 + ".nvmrc": "package_json", 2138 + ".esmrc": "package_json", 2139 + ".node-version": "package_json", 2140 + "panda.config.ts": "panda", 2141 + "panda.config.mjs": "panda", 2142 + "panda.config.js": "panda", 2143 + ".parse.local": "parse", 2144 + ".parse.project": "parse", 2145 + "payload.config.js": "payload", 2146 + "payload.config.mjs": "payload", 2147 + "payload.config.ts": "payload", 2148 + "payload.config.mts": "payload", 2149 + "pdm.toml": "pdm", 2150 + ".pdm-python": "pdm", 2151 + "pdm.lock": "pdm_lock", 2152 + ".php_cs": "php_cs_fixer", 2153 + ".php_cs.dist": "php_cs_fixer", 2154 + ".php_cs.php": "php_cs_fixer", 2155 + ".php_cs.dist.php": "php_cs_fixer", 2156 + ".php-cs-fixer.php": "php_cs_fixer", 2157 + ".php-cs-fixer.dist.php": "php_cs_fixer", 2158 + "phpstan.neon": "phpstan", 2159 + "phpstan.neon.dist": "phpstan", 2160 + "phpstan.dist.neon": "phpstan", 2161 + ".phpunit.result.cache": "phpunit", 2162 + ".phpunit-watcher.yml": "phpunit", 2163 + "phpunit.xml": "phpunit", 2164 + "phpunit.xml.dist": "phpunit", 2165 + "phpunit-watcher.yml": "phpunit", 2166 + "phpunit-watcher.yml.dist": "phpunit", 2167 + "plastic.branchexplorer": "plastic", 2168 + "plastic.selector": "plastic", 2169 + "plastic.wktree": "plastic", 2170 + "plastic.workspace": "plastic", 2171 + "plastic.workspaces": "plastic", 2172 + "playwright.config.js": "playwright", 2173 + "playwright.config.mjs": "playwright", 2174 + "playwright.config.ts": "playwright", 2175 + "playwright-ct.config.js": "playwright", 2176 + "playwright-ct.config.mjs": "playwright", 2177 + "playwright-ct.config.ts": "playwright", 2178 + "plopfile.js": "plop", 2179 + "plopfile.cjs": "plop", 2180 + "plopfile.mjs": "plop", 2181 + "plopfile.ts": "plop", 2182 + "pnpm-workspace.yaml": "pnpm", 2183 + ".pnpmfile.cjs": "pnpm", 2184 + "pnpm-lock.yaml": "pnpm_lock", 2185 + "poetry.lock": "poetry", 2186 + "postcss.config.js": "postcss", 2187 + "postcss.config.cjs": "postcss", 2188 + "postcss.config.mjs": "postcss", 2189 + "postcss.config.ts": "postcss", 2190 + "postcss.config.cts": "postcss", 2191 + ".postcssrc.js": "postcss", 2192 + ".postcssrc.cjs": "postcss", 2193 + ".postcssrc.ts": "postcss", 2194 + ".postcssrc.cts": "postcss", 2195 + ".postcssrc": "postcss", 2196 + ".postcssrc.json": "postcss", 2197 + ".postcssrc.yaml": "postcss", 2198 + ".postcssrc.yml": "postcss", 2199 + "posthtml.config.js": "posthtml", 2200 + ".posthtmlrc.js": "posthtml", 2201 + ".posthtmlrc": "posthtml", 2202 + ".posthtmlrc.json": "posthtml", 2203 + ".posthtmlrc.yml": "posthtml", 2204 + "premake4.lua": "premake", 2205 + "premake5.lua": "premake", 2206 + "premake.lua": "premake", 2207 + ".prettierrc": "prettier", 2208 + "prettier.config.js": "prettier", 2209 + "prettier.config.cjs": "prettier", 2210 + ".prettierrc.js": "prettier", 2211 + ".prettierrc.cjs": "prettier", 2212 + ".prettierrc.json": "prettier", 2213 + ".prettierrc.json5": "prettier", 2214 + ".prettierrc.yaml": "prettier", 2215 + ".prettierrc.yml": "prettier", 2216 + ".prettierignore": "prettier_ignore", 2217 + ".prettierrc.toml": "prettier", 2218 + "prisma.yml": "prisma", 2219 + ".clang-format": "properties", 2220 + ".clang-tidy": "properties", 2221 + ".pug-lintrc": "pug", 2222 + ".pug-lintrc.js": "pug", 2223 + ".pug-lintrc.json": "pug", 2224 + ".puppeteerrc.cjs": "puppeteer", 2225 + ".puppeteerrc.js": "puppeteer", 2226 + ".puppeteerrc": "puppeteer", 2227 + ".puppeteerrc.json,": "puppeteer", 2228 + ".puppeteerrc.yaml": "puppeteer", 2229 + "puppeteer.config.js": "puppeteer", 2230 + "puppeteer.config.cjs": "puppeteer", 2231 + "requirements.txt": "python_misc", 2232 + "pipfile": "python_misc", 2233 + ".python-version": "python_misc", 2234 + "manifest.in": "python_misc", 2235 + "pylintrc": "python_misc", 2236 + ".pylintrc": "python_misc", 2237 + "pyproject.toml": "python_misc", 2238 + "quasar.conf.js": "quasar", 2239 + "quasar.config.js": "quasar", 2240 + ".Rhistory": "r", 2241 + "react-native.config.js": "reactnative_config", 2242 + "react-native.config.ts": "reactnative_config", 2243 + "readme.md": "readme", 2244 + "readme.rst": "readme", 2245 + "readme.txt": "readme", 2246 + "readme": "readme", 2247 + "redwood.toml": "redwood", 2248 + "remix.config.js": "remix", 2249 + "remix.config.ts": "remix", 2250 + ".renovaterc": "renovate", 2251 + ".renovaterc.json": "renovate", 2252 + "renovate-config.json": "renovate", 2253 + "renovate.json": "renovate", 2254 + "renovate.json5": "renovate", 2255 + ".replit": "replit", 2256 + "robots.txt": "robots", 2257 + "rollup.config.js": "rollup", 2258 + "rollup.config.mjs": "rollup", 2259 + "rollup.config.ts": "rollup", 2260 + "rollup-config.js": "rollup", 2261 + "rollup-config.mjs": "rollup", 2262 + "rollup-config.ts": "rollup", 2263 + "rollup.config.common.js": "rollup", 2264 + "rollup.config.common.mjs": "rollup", 2265 + "rollup.config.common.ts": "rollup", 2266 + "rollup.config.base.js": "rollup", 2267 + "rollup.config.base.mjs": "rollup", 2268 + "rollup.config.base.ts": "rollup", 2269 + "rollup.config.prod.js": "rollup", 2270 + "rollup.config.prod.mjs": "rollup", 2271 + "rollup.config.prod.ts": "rollup", 2272 + "rollup.config.dev.js": "rollup", 2273 + "rollup.config.dev.mjs": "rollup", 2274 + "rollup.config.dev.ts": "rollup", 2275 + "rollup.config.prod.vendor.js": "rollup", 2276 + "rollup.config.prod.vendor.mjs": "rollup", 2277 + "rollup.config.prod.vendor.ts": "rollup", 2278 + "rome.json": "rome", 2279 + ".rubocop.yml": "rubocop", 2280 + ".rubocop-todo.yml": "rubocop", 2281 + ".rubocop_todo.yml": "rubocop", 2282 + ".ruby-version": "ruby", 2283 + "gemfile": "ruby_gem", 2284 + "gemfile.lock": "ruby_gem_lock", 2285 + ".rspec": "rspec", 2286 + ".releaserc": "semantic_release", 2287 + ".releaserc.yaml": "semantic_release", 2288 + ".releaserc.yml": "semantic_release", 2289 + ".releaserc.json": "semantic_release", 2290 + ".releaserc.js": "semantic_release", 2291 + ".releaserc.cjs": "semantic_release", 2292 + "release.config.js": "semantic_release", 2293 + "release.config.cjs": "semantic_release", 2294 + "semgrep.yml": "semgrep", 2295 + ".semgrepignore": "semgrep_ignore", 2296 + ".sentryclirc": "sentry", 2297 + ".sequelizerc": "sequelize", 2298 + "serverless.yml": "serverless", 2299 + "serverless.yaml": "serverless", 2300 + "serverless.json": "serverless", 2301 + "serverless.js": "serverless", 2302 + "serverless.ts": "serverless", 2303 + "snowpack.config.js": "snowpack", 2304 + "snowpack.config.cjs": "snowpack", 2305 + "snowpack.config.mjs": "snowpack", 2306 + "snowpack.config.ts": "snowpack", 2307 + "snowpack.config.cts": "snowpack", 2308 + "snowpack.config.mts": "snowpack", 2309 + "snowpack.deps.json": "snowpack", 2310 + "snowpack.config.json": "snowpack", 2311 + "sonar-project.properties": "sonar_cloud", 2312 + ".sonarcloud.properties": "sonar_cloud", 2313 + "sonarcloud.yaml": "sonar_cloud", 2314 + "svelte.config.js": "svelte", 2315 + "svelte.config.cjs": "svelte", 2316 + "svelte.config.ts": "svelte", 2317 + ".stackblitzrc": "stackblitz", 2318 + "stencil.config.js": "stencil", 2319 + "stencil.config.ts": "stencil", 2320 + "stitches.config.js": "stitches", 2321 + "stitches.config.ts": "stitches", 2322 + ".stylelintrc": "stylelint", 2323 + "stylelint.config.js": "stylelint", 2324 + "stylelint.config.cjs": "stylelint", 2325 + "stylelint.config.mjs": "stylelint", 2326 + ".stylelintrc.json": "stylelint", 2327 + ".stylelintrc.yaml": "stylelint", 2328 + ".stylelintrc.yml": "stylelint", 2329 + ".stylelintrc.js": "stylelint", 2330 + ".stylelintrc.cjs": "stylelint", 2331 + ".stylelintrc.mjs": "stylelint", 2332 + ".stylelintignore": "stylelint_ignore", 2333 + ".stylelintcache": "stylelint_ignore", 2334 + "swagger.json": "swagger", 2335 + "swagger.yml": "swagger", 2336 + "swagger.yaml": "swagger", 2337 + ".syncpackrc": "syncpack", 2338 + "syncpackrc.json": "syncpack", 2339 + ".syncpackrc.yaml": "syncpack", 2340 + ".syncpackrc.yml": "syncpack", 2341 + ".syncpackrc.js": "syncpack", 2342 + ".syncpackrc.cjs": "syncpack", 2343 + "syncpack.config.js": "syncpack", 2344 + "syncpack.config.cjs": "syncpack", 2345 + "tailwind.js": "tailwind", 2346 + "tailwind.ts": "tailwind", 2347 + "tailwind.config.js": "tailwind", 2348 + "tailwind.config.mjs": "tailwind", 2349 + "tailwind.config.cjs": "tailwind", 2350 + "tailwind.config.ts": "tailwind", 2351 + "tailwind.config.cts": "tailwind", 2352 + "tailwind.config.mts": "tailwind", 2353 + "tailwind.css": "tailwind", 2354 + "taskfile.yml": "task", 2355 + "taskfile.yaml": "task", 2356 + "taskfile.dist.yml": "task", 2357 + "taskfile.dist.yaml": "task", 2358 + "tauri.conf.json": "tauri", 2359 + "tauri.config.json": "tauri", 2360 + "tauri.linux.conf.json": "tauri", 2361 + "tauri.windows.conf.json": "tauri", 2362 + "tauri.macos.conf.json": "tauri", 2363 + ".taurignore": "taurignore", 2364 + ".textlintrc": "textlint", 2365 + ".textlintrc.js": "textlint", 2366 + ".textlintrc.json": "textlint", 2367 + ".textlintrc.yml": "textlint", 2368 + ".textlintrc.yaml": "textlint", 2369 + "todo": "todo", 2370 + "todo.txt": "todo", 2371 + "todo.md": "todo", 2372 + ".travis.yml": "travis", 2373 + "tsconfig.json": "typescript_config", 2374 + "tsconfig.app.json": "typescript_config", 2375 + "tsconfig.editor.json": "typescript_config", 2376 + "tsconfig.spec.json": "typescript_config", 2377 + "tsconfig.base.json": "typescript_config", 2378 + "tsconfig.build.json": "typescript_config", 2379 + "tsconfig.eslint.json": "typescript_config", 2380 + "tsconfig.lib.json": "typescript_config", 2381 + "tsconfig.lib.prod.json": "typescript_config", 2382 + "tsconfig.node.json": "typescript_config", 2383 + "tsconfig.test.json": "typescript_config", 2384 + "tsconfig.e2e.json": "typescript_config", 2385 + "tsconfig.web.json": "typescript_config", 2386 + "tsconfig.webworker.json": "typescript_config", 2387 + "tsconfig.worker.json": "typescript_config", 2388 + "tsconfig.config.json": "typescript_config", 2389 + "tsconfig.vitest.json": "typescript_config", 2390 + "tsconfig.cjs.json": "typescript_config", 2391 + "tsconfig.esm.json": "typescript_config", 2392 + "tsconfig.mjs.json": "typescript_config", 2393 + "tsconfig.doc.json": "typescript_config", 2394 + "uno.config.js": "unocss", 2395 + "uno.config.ts": "unocss", 2396 + "unocss.config.js": "unocss", 2397 + "unocss.config.ts": "unocss", 2398 + "vpkg.json": "v", 2399 + "v.mod": "v", 2400 + "vercel.json": "vercel", 2401 + "now.json": "vercel", 2402 + ".vercelignore": "vercel_ignore", 2403 + ".nowignore": "vercel_ignore", 2404 + "vite.config.js": "vite", 2405 + "vite.config.mjs": "vite", 2406 + "vite.config.cjs": "vite", 2407 + "vite.config.ts": "vite", 2408 + "vite.config.cts": "vite", 2409 + "vite.config.mts": "vite", 2410 + "vitest.config.js": "vitest", 2411 + "vitest.config.mjs": "vitest", 2412 + "vitest.config.cjs": "vitest", 2413 + "vitest.config.ts": "vitest", 2414 + "vitest.config.cts": "vitest", 2415 + "vitest.config.mts": "vitest", 2416 + "vue.config.js": "vue_config", 2417 + "vue.config.ts": "vue_config", 2418 + "vetur.config.js": "vue_config", 2419 + "vetur.config.ts": "vue_config", 2420 + "volar.config.js": "vue_config", 2421 + "wallaby.js": "wallaby", 2422 + "wallaby.conf.js": "wallaby", 2423 + ".watchmanconfig": "watchman", 2424 + "webpack.js": "webpack", 2425 + "webpack.cjs": "webpack", 2426 + "webpack.mjs": "webpack", 2427 + "webpack.ts": "webpack", 2428 + "webpack.cts": "webpack", 2429 + "webpack.mts": "webpack", 2430 + "webpack.base.js": "webpack", 2431 + "webpack.base.cjs": "webpack", 2432 + "webpack.base.mjs": "webpack", 2433 + "webpack.base.ts": "webpack", 2434 + "webpack.base.cts": "webpack", 2435 + "webpack.base.mts": "webpack", 2436 + "webpack.config.js": "webpack", 2437 + "webpack.config.cjs": "webpack", 2438 + "webpack.config.mjs": "webpack", 2439 + "webpack.config.ts": "webpack", 2440 + "webpack.config.cts": "webpack", 2441 + "webpack.config.mts": "webpack", 2442 + "webpack.common.js": "webpack", 2443 + "webpack.common.cjs": "webpack", 2444 + "webpack.common.mjs": "webpack", 2445 + "webpack.common.ts": "webpack", 2446 + "webpack.common.cts": "webpack", 2447 + "webpack.common.mts": "webpack", 2448 + "webpack.config.common.js": "webpack", 2449 + "webpack.config.common.cjs": "webpack", 2450 + "webpack.config.common.mjs": "webpack", 2451 + "webpack.config.common.ts": "webpack", 2452 + "webpack.config.common.cts": "webpack", 2453 + "webpack.config.common.mts": "webpack", 2454 + "webpack.config.common.babel.js": "webpack", 2455 + "webpack.config.common.babel.ts": "webpack", 2456 + "webpack.dev.js": "webpack", 2457 + "webpack.dev.cjs": "webpack", 2458 + "webpack.dev.mjs": "webpack", 2459 + "webpack.dev.ts": "webpack", 2460 + "webpack.dev.cts": "webpack", 2461 + "webpack.dev.mts": "webpack", 2462 + "webpack.development.js": "webpack", 2463 + "webpack.development.cjs": "webpack", 2464 + "webpack.development.mjs": "webpack", 2465 + "webpack.development.ts": "webpack", 2466 + "webpack.development.cts": "webpack", 2467 + "webpack.development.mts": "webpack", 2468 + "webpack.config.dev.js": "webpack", 2469 + "webpack.config.dev.cjs": "webpack", 2470 + "webpack.config.dev.mjs": "webpack", 2471 + "webpack.config.dev.ts": "webpack", 2472 + "webpack.config.dev.cts": "webpack", 2473 + "webpack.config.dev.mts": "webpack", 2474 + "webpack.config.dev.babel.js": "webpack", 2475 + "webpack.config.dev.babel.ts": "webpack", 2476 + "webpack.mix.js": "webpack", 2477 + "webpack.mix.cjs": "webpack", 2478 + "webpack.mix.mjs": "webpack", 2479 + "webpack.mix.ts": "webpack", 2480 + "webpack.mix.cts": "webpack", 2481 + "webpack.mix.mts": "webpack", 2482 + "webpack.prod.js": "webpack", 2483 + "webpack.prod.cjs": "webpack", 2484 + "webpack.prod.mjs": "webpack", 2485 + "webpack.prod.ts": "webpack", 2486 + "webpack.prod.cts": "webpack", 2487 + "webpack.prod.mts": "webpack", 2488 + "webpack.prod.config.js": "webpack", 2489 + "webpack.prod.config.cjs": "webpack", 2490 + "webpack.prod.config.mjs": "webpack", 2491 + "webpack.prod.config.ts": "webpack", 2492 + "webpack.prod.config.cts": "webpack", 2493 + "webpack.prod.config.mts": "webpack", 2494 + "webpack.production.js": "webpack", 2495 + "webpack.production.cjs": "webpack", 2496 + "webpack.production.mjs": "webpack", 2497 + "webpack.production.ts": "webpack", 2498 + "webpack.production.cts": "webpack", 2499 + "webpack.production.mts": "webpack", 2500 + "webpack.server.js": "webpack", 2501 + "webpack.server.cjs": "webpack", 2502 + "webpack.server.mjs": "webpack", 2503 + "webpack.server.ts": "webpack", 2504 + "webpack.server.cts": "webpack", 2505 + "webpack.server.mts": "webpack", 2506 + "webpack.client.js": "webpack", 2507 + "webpack.client.cjs": "webpack", 2508 + "webpack.client.mjs": "webpack", 2509 + "webpack.client.ts": "webpack", 2510 + "webpack.client.cts": "webpack", 2511 + "webpack.client.mts": "webpack", 2512 + "webpack.config.server.js": "webpack", 2513 + "webpack.config.server.cjs": "webpack", 2514 + "webpack.config.server.mjs": "webpack", 2515 + "webpack.config.server.ts": "webpack", 2516 + "webpack.config.server.cts": "webpack", 2517 + "webpack.config.server.mts": "webpack", 2518 + "webpack.config.client.js": "webpack", 2519 + "webpack.config.client.cjs": "webpack", 2520 + "webpack.config.client.mjs": "webpack", 2521 + "webpack.config.client.ts": "webpack", 2522 + "webpack.config.client.cts": "webpack", 2523 + "webpack.config.client.mts": "webpack", 2524 + "webpack.config.production.babel.js": "webpack", 2525 + "webpack.config.production.babel.ts": "webpack", 2526 + "webpack.config.prod.babel.js": "webpack", 2527 + "webpack.config.prod.babel.cjs": "webpack", 2528 + "webpack.config.prod.babel.mjs": "webpack", 2529 + "webpack.config.prod.babel.ts": "webpack", 2530 + "webpack.config.prod.babel.cts": "webpack", 2531 + "webpack.config.prod.babel.mts": "webpack", 2532 + "webpack.config.prod.js": "webpack", 2533 + "webpack.config.prod.cjs": "webpack", 2534 + "webpack.config.prod.mjs": "webpack", 2535 + "webpack.config.prod.ts": "webpack", 2536 + "webpack.config.prod.cts": "webpack", 2537 + "webpack.config.prod.mts": "webpack", 2538 + "webpack.config.production.js": "webpack", 2539 + "webpack.config.production.cjs": "webpack", 2540 + "webpack.config.production.mjs": "webpack", 2541 + "webpack.config.production.ts": "webpack", 2542 + "webpack.config.production.cts": "webpack", 2543 + "webpack.config.production.mts": "webpack", 2544 + "webpack.config.staging.js": "webpack", 2545 + "webpack.config.staging.cjs": "webpack", 2546 + "webpack.config.staging.mjs": "webpack", 2547 + "webpack.config.staging.ts": "webpack", 2548 + "webpack.config.staging.cts": "webpack", 2549 + "webpack.config.staging.mts": "webpack", 2550 + "webpack.config.babel.js": "webpack", 2551 + "webpack.config.babel.ts": "webpack", 2552 + "webpack.config.base.babel.js": "webpack", 2553 + "webpack.config.base.babel.ts": "webpack", 2554 + "webpack.config.base.js": "webpack", 2555 + "webpack.config.base.cjs": "webpack", 2556 + "webpack.config.base.mjs": "webpack", 2557 + "webpack.config.base.ts": "webpack", 2558 + "webpack.config.base.cts": "webpack", 2559 + "webpack.config.base.mts": "webpack", 2560 + "webpack.config.staging.babel.js": "webpack", 2561 + "webpack.config.staging.babel.ts": "webpack", 2562 + "webpack.config.coffee": "webpack", 2563 + "webpack.config.test.js": "webpack", 2564 + "webpack.config.test.cjs": "webpack", 2565 + "webpack.config.test.mjs": "webpack", 2566 + "webpack.config.test.ts": "webpack", 2567 + "webpack.config.test.cts": "webpack", 2568 + "webpack.config.test.mts": "webpack", 2569 + "webpack.config.vendor.js": "webpack", 2570 + "webpack.config.vendor.cjs": "webpack", 2571 + "webpack.config.vendor.mjs": "webpack", 2572 + "webpack.config.vendor.ts": "webpack", 2573 + "webpack.config.vendor.cts": "webpack", 2574 + "webpack.config.vendor.mts": "webpack", 2575 + "webpack.config.vendor.production.js": "webpack", 2576 + "webpack.config.vendor.production.cjs": "webpack", 2577 + "webpack.config.vendor.production.mjs": "webpack", 2578 + "webpack.config.vendor.production.ts": "webpack", 2579 + "webpack.config.vendor.production.cts": "webpack", 2580 + "webpack.config.vendor.production.mts": "webpack", 2581 + "webpack.test.js": "webpack", 2582 + "webpack.test.cjs": "webpack", 2583 + "webpack.test.mjs": "webpack", 2584 + "webpack.test.ts": "webpack", 2585 + "webpack.test.cts": "webpack", 2586 + "webpack.test.mts": "webpack", 2587 + "webpack.dist.js": "webpack", 2588 + "webpack.dist.cjs": "webpack", 2589 + "webpack.dist.mjs": "webpack", 2590 + "webpack.dist.ts": "webpack", 2591 + "webpack.dist.cts": "webpack", 2592 + "webpack.dist.mts": "webpack", 2593 + "webpackfile.js": "webpack", 2594 + "webpackfile.cjs": "webpack", 2595 + "webpackfile.mjs": "webpack", 2596 + "webpackfile.ts": "webpack", 2597 + "webpackfile.cts": "webpack", 2598 + "webpackfile.mts": "webpack", 2599 + "windi.config.js": "windi", 2600 + "windi.config.cjs": "windi", 2601 + "windi.config.ts": "windi", 2602 + "windi.config.cts": "windi", 2603 + "windi.config.json": "windi", 2604 + "xmake.lua": "xmake", 2605 + ".yamllint": "yaml", 2606 + ".yamllint.yml": "yaml", 2607 + ".yamllint.yaml": "yaml", 2608 + ".yarnrc": "yarn", 2609 + ".yarnclean": "yarn", 2610 + ".yarn-integrity": "yarn", 2611 + "yarn-error.log": "yarn", 2612 + ".yarnrc.yml": "yarn", 2613 + ".yarnrc.yaml": "yarn", 2614 + "yarn.lock": "yarn_lock" 2615 + }, 2616 + "folderNames": { 2617 + "admin": "folder_admin", 2618 + "manager": "folder_admin", 2619 + "managers": "folder_admin", 2620 + "moderator": "folder_admin", 2621 + "moderators": "folder_admin", 2622 + "android": "folder_android", 2623 + "apk": "folder_android", 2624 + "angular": "folder_angular", 2625 + ".angular": "folder_angular", 2626 + "anim": "folder_animation", 2627 + "anims": "folder_animation", 2628 + "animation": "folder_animation", 2629 + "animations": "folder_animation", 2630 + "animated": "folder_animation", 2631 + "api": "folder_api", 2632 + "apis": "folder_api", 2633 + "restapi": "folder_api", 2634 + "app": "folder_app", 2635 + "apps": "folder_app", 2636 + "application": "folder_app", 2637 + "project": "folder_project", 2638 + "projects": "folder_project", 2639 + "arc": "folder_archive", 2640 + "arcs": "folder_archive", 2641 + "archive": "folder_archive", 2642 + "archives": "folder_archive", 2643 + "archival": "folder_archive", 2644 + "bkp": "folder_archive", 2645 + "bkps": "folder_archive", 2646 + "bak": "folder_archive", 2647 + "baks": "folder_archive", 2648 + "backup": "folder_archive", 2649 + "backups": "folder_archive", 2650 + "back-up": "folder_archive", 2651 + "back-ups": "folder_archive", 2652 + "history": "folder_archive", 2653 + "histories": "folder_archive", 2654 + "aud": "folder_audio", 2655 + "auds": "folder_audio", 2656 + "audio": "folder_audio", 2657 + "audios": "folder_audio", 2658 + "music": "folder_audio", 2659 + "sound": "folder_audio", 2660 + "sounds": "folder_audio", 2661 + "aws": "folder_aws", 2662 + ".aws": "folder_aws", 2663 + "amazon": "folder_aws", 2664 + "ec2": "folder_aws", 2665 + "benchmark": "folder_benchmark", 2666 + "benchmarks": "folder_benchmark", 2667 + "performance": "folder_benchmark", 2668 + "measure": "folder_benchmark", 2669 + "measures": "folder_benchmark", 2670 + "measurement": "folder_benchmark", 2671 + "benches": "folder_benchmark", 2672 + ".azure-pipelines": "folder_azure_pipelines", 2673 + ".azure-pipelines-ci": "folder_azure_pipelines", 2674 + "azure-pipelines": "folder_azure_pipelines", 2675 + "cart": "folder_cart", 2676 + "shopping-cart": "folder_cart", 2677 + "shopping": "folder_cart", 2678 + "shop": "folder_cart", 2679 + ".circleci": "folder_circle_ci", 2680 + "client": "folder_client", 2681 + "clients": "folder_client", 2682 + "frontend": "folder_client", 2683 + "frontends": "folder_client", 2684 + "pwa": "folder_client", 2685 + "azure": "folder_cloud", 2686 + "cloud": "folder_cloud", 2687 + "clouds": "folder_cloud", 2688 + "pods": "folder_cocoapods", 2689 + "cmd": "folder_command", 2690 + "command": "folder_command", 2691 + "commands": "folder_command", 2692 + "cli": "folder_command", 2693 + "clis": "folder_command", 2694 + "components": "folder_components", 2695 + "widget": "folder_components", 2696 + "widgets": "folder_components", 2697 + "fragments": "folder_components", 2698 + "hook": "folder_composables", 2699 + "hooks": "folder_composables", 2700 + "composable": "folder_composables", 2701 + "composables": "folder_composables", 2702 + "mixin": "folder_composables", 2703 + "mixins": "folder_composables", 2704 + "connection": "folder_connection", 2705 + "connections": "folder_connection", 2706 + "integration": "folder_connection", 2707 + "integrations": "folder_connection", 2708 + "url": "folder_connection", 2709 + "urls": "folder_connection", 2710 + "cfg": "folder_config", 2711 + "cfgs": "folder_config", 2712 + "conf": "folder_config", 2713 + "confs": "folder_config", 2714 + "config": "folder_config", 2715 + ".config": "folder_config", 2716 + "configs": "folder_config", 2717 + "configuration": "folder_config", 2718 + "configurations": "folder_config", 2719 + "setting": "folder_config", 2720 + ".setting": "folder_config", 2721 + "settings": "folder_config", 2722 + ".settings": "folder_config", 2723 + "META-INF": "folder_config", 2724 + "const": "folder_constant", 2725 + "constant": "folder_constant", 2726 + "constants": "folder_constant", 2727 + "controller": "folder_controllers", 2728 + "controllers": "folder_controllers", 2729 + "service": "folder_controllers", 2730 + "services": "folder_controllers", 2731 + "provider": "folder_controllers", 2732 + "providers": "folder_controllers", 2733 + "handler": "folder_controllers", 2734 + "handlers": "folder_controllers", 2735 + "core": "folder_core", 2736 + "coverage": "folder_coverage", 2737 + "coverages": "folder_coverage", 2738 + ".nyc-output": "folder_coverage", 2739 + ".nyc_output": "folder_coverage", 2740 + "cron": "folder_cron", 2741 + "crontab": "folder_cron", 2742 + "cronjob": "folder_cron", 2743 + "cronjobs": "folder_cron", 2744 + "schedule": "folder_cron", 2745 + "schedules": "folder_cron", 2746 + "cypress": "folder_cypress", 2747 + ".cypress": "folder_cypress", 2748 + "db": "folder_database", 2749 + "database": "folder_database", 2750 + "databases": "folder_database", 2751 + "sql": "folder_database", 2752 + "data": "folder_database", 2753 + "_data": "folder_database", 2754 + "decorator": "folder_decorators", 2755 + "decorators": "folder_decorators", 2756 + "debug": "folder_debug", 2757 + "diff": "folder_diff", 2758 + "diffs": "folder_diff", 2759 + "patch": "folder_diff", 2760 + "patches": "folder_diff", 2761 + ".output": "folder_dist", 2762 + "dist": "folder_dist", 2763 + "out": "folder_dist", 2764 + "output": "folder_dist", 2765 + "build": "folder_dist", 2766 + "release": "folder_dist", 2767 + "bin": "folder_dist", 2768 + "target": "folder_dist", 2769 + "docker": "folder_docker", 2770 + "dockerfiles": "folder_docker", 2771 + ".docker": "folder_docker", 2772 + "doc": "folder_docs", 2773 + "docs": "folder_docs", 2774 + "document": "folder_docs", 2775 + "documents": "folder_docs", 2776 + "documentation": "folder_docs", 2777 + "download": "folder_download", 2778 + "downloads": "folder_download", 2779 + ".env": "folder_environment", 2780 + ".environment": "folder_environment", 2781 + "env": "folder_environment", 2782 + "envs": "folder_environment", 2783 + "environment": "folder_environment", 2784 + "environments": "folder_environment", 2785 + ".venv": "folder_environment", 2786 + "error": "folder_error", 2787 + "errors": "folder_error", 2788 + "err": "folder_error", 2789 + "event": "folder_event", 2790 + "events": "folder_event", 2791 + "demo": "folder_examples", 2792 + "demos": "folder_examples", 2793 + "example": "folder_examples", 2794 + "examples": "folder_examples", 2795 + "sample": "folder_examples", 2796 + "samples": "folder_examples", 2797 + "sample-data": "folder_examples", 2798 + ".expo": "folder_expo", 2799 + ".expo-shared": "folder_expo", 2800 + "fastlane": "folder_fastlane", 2801 + "favorite": "folder_favorite", 2802 + "favorites": "folder_favorite", 2803 + "like": "folder_favorite", 2804 + "likes": "folder_favorite", 2805 + "heart": "folder_favorite", 2806 + "hearts": "folder_favorite", 2807 + "firebase": "folder_firebase", 2808 + ".firebase": "folder_firebase", 2809 + "flow-typed": "folder_flow", 2810 + "font": "folder_fonts", 2811 + "fonts": "folder_fonts", 2812 + "func": "folder_functions", 2813 + "funcs": "folder_functions", 2814 + "function": "folder_functions", 2815 + "functions": "folder_functions", 2816 + "lambda": "folder_functions", 2817 + "lambdas": "folder_functions", 2818 + "logic": "folder_functions", 2819 + "math": "folder_functions", 2820 + "maths": "folder_functions", 2821 + "calc": "folder_functions", 2822 + "calcs": "folder_functions", 2823 + "calculation": "folder_functions", 2824 + "calculations": "folder_functions", 2825 + "gamemaker": "folder_gamemaker", 2826 + "gamemaker2": "folder_gamemaker", 2827 + ".git": "folder_git", 2828 + "git": "folder_git", 2829 + "githooks": "folder_git", 2830 + ".githooks": "folder_git", 2831 + "submodules": "folder_git", 2832 + ".submodules": "folder_git", 2833 + ".github": "folder_github", 2834 + ".gitlab": "folder_gitlab", 2835 + ".godot": "folder_godot", 2836 + "godot": "folder_godot", 2837 + ".godot-cpp": "folder_godot", 2838 + "godot-cpp": "folder_godot", 2839 + "gradle": "folder_gradle", 2840 + ".gradle": "folder_gradle", 2841 + "graphql": "folder_graphql", 2842 + "gql": "folder_graphql", 2843 + "guard": "folder_guard", 2844 + "guards": "folder_guard", 2845 + "gulp": "folder_gulp", 2846 + "gulp-tasks": "folder_gulp", 2847 + "gulpfile.js": "folder_gulp", 2848 + "gulpfile.mjs": "folder_gulp", 2849 + "gulpfile.ts": "folder_gulp", 2850 + "gulpfile.babel.js": "folder_gulp", 2851 + "home": "folder_home", 2852 + ".home": "folder_home", 2853 + "start": "folder_home", 2854 + ".start": "folder_home", 2855 + "husky": "folder_husky", 2856 + ".husky": "folder_husky", 2857 + "asset": "folder_images", 2858 + "assets": "folder_images", 2859 + "images": "folder_images", 2860 + "image": "folder_images", 2861 + "imgs": "folder_images", 2862 + "img": "folder_images", 2863 + "icons": "folder_images", 2864 + "icon": "folder_images", 2865 + "icos": "folder_images", 2866 + "ico": "folder_images", 2867 + "figures": "folder_images", 2868 + "figure": "folder_images", 2869 + "figs": "folder_images", 2870 + "fig": "folder_images", 2871 + "screenshot": "folder_images", 2872 + "screenshots": "folder_images", 2873 + "screengrab": "folder_images", 2874 + "screengrabs": "folder_images", 2875 + "pic": "folder_images", 2876 + "pics": "folder_images", 2877 + "picture": "folder_images", 2878 + "pictures": "folder_images", 2879 + "ios": "folder_ios", 2880 + "java": "folder_java", 2881 + "jar": "folder_java", 2882 + "js": "folder_javascript", 2883 + "javascript": "folder_javascript", 2884 + "javascripts": "folder_javascript", 2885 + "json": "folder_json", 2886 + "jsons": "folder_json", 2887 + "key": "folder_key", 2888 + "keys": "folder_key", 2889 + "token": "folder_key", 2890 + "tokens": "folder_key", 2891 + "jwt": "folder_key", 2892 + "secret": "folder_key", 2893 + "secrets": "folder_key", 2894 + "kubernetes": "folder_kubernetes", 2895 + ".kubernetes": "folder_kubernetes", 2896 + "layout": "folder_layouts", 2897 + "layouts": "folder_layouts", 2898 + "_layouts": "folder_layouts", 2899 + "lib": "folder_library", 2900 + "libs": "folder_library", 2901 + "library": "folder_library", 2902 + "libraries": "folder_library", 2903 + "vendor": "folder_library", 2904 + "vendors": "folder_library", 2905 + "third-party": "folder_library", 2906 + "dll": "folder_library", 2907 + "linux": "folder_linux", 2908 + "unix": "folder_linux", 2909 + "link": "folder_link", 2910 + "links": "folder_link", 2911 + "shortcut": "folder_link", 2912 + "shortcuts": "folder_link", 2913 + "i18n": "folder_locales", 2914 + "internationalization": "folder_locales", 2915 + "lang": "folder_locales", 2916 + "langs": "folder_locales", 2917 + "language": "folder_locales", 2918 + "languages": "folder_locales", 2919 + "locale": "folder_locales", 2920 + "locales": "folder_locales", 2921 + "l10n": "folder_locales", 2922 + "localization": "folder_locales", 2923 + "translation": "folder_locales", 2924 + "translate": "folder_locales", 2925 + "translations": "folder_locales", 2926 + "log": "folder_log", 2927 + "logs": "folder_log", 2928 + "report": "folder_log", 2929 + "reports": "folder_log", 2930 + "lottie": "folder_lottie", 2931 + "lotties": "folder_lottie", 2932 + "lottiefiles": "folder_lottie", 2933 + "mac": "folder_macos", 2934 + "macos": "folder_macos", 2935 + "macosx": "folder_macos", 2936 + "osx": "folder_macos", 2937 + "darwin": "folder_macos", 2938 + "mail": "folder_mail", 2939 + "mails": "folder_mail", 2940 + "email": "folder_mail", 2941 + "emails": "folder_mail", 2942 + "smtp": "folder_mail", 2943 + "mailer": "folder_mail", 2944 + "mailers": "folder_mail", 2945 + "phpmailer": "folder_mail", 2946 + "md": "folder_markdown", 2947 + "markdown": "folder_markdown", 2948 + "markdowns": "folder_markdown", 2949 + "message": "folder_message", 2950 + "messages": "folder_message", 2951 + "forum": "folder_message", 2952 + "chat": "folder_message", 2953 + "chats": "folder_message", 2954 + "conversation": "folder_message", 2955 + "conversations": "folder_message", 2956 + "middleware": "folder_middleware", 2957 + "middlewares": "folder_middleware", 2958 + "mjml": "folder_mjml", 2959 + "mobile": "folder_mobile", 2960 + "mobiles": "folder_mobile", 2961 + "phone": "folder_mobile", 2962 + "phones": "folder_mobile", 2963 + "mock": "folder_mocks", 2964 + "mocks": "folder_mocks", 2965 + "fixture": "folder_mocks", 2966 + "fixtures": "folder_mocks", 2967 + "draft": "folder_mocks", 2968 + "drafts": "folder_mocks", 2969 + "concept": "folder_mocks", 2970 + "concepts": "folder_mocks", 2971 + "sketch": "folder_mocks", 2972 + "sketches": "folder_mocks", 2973 + "stub": "folder_mocks", 2974 + "stubs": "folder_mocks", 2975 + "mojo": "folder_mojo", 2976 + ".netlify": "folder_netlify", 2977 + ".next": "folder_next", 2978 + "node_modules": "folder_node", 2979 + "node": "folder_node", 2980 + "nodejs": "folder_node", 2981 + ".nuxt": "folder_nuxt", 2982 + "nuxt": "folder_nuxt", 2983 + "package": "folder_packages", 2984 + "packages": "folder_packages", 2985 + "pkg": "folder_packages", 2986 + "pkgs": "folder_packages", 2987 + "pdf": "folder_pdf", 2988 + "pdfs": "folder_pdf", 2989 + ".pdm-plugins": "folder_pdm", 2990 + ".pdm-build": "folder_pdm", 2991 + "php": "folder_php", 2992 + "playground": "folder_playground", 2993 + "playgrounds": "folder_playground", 2994 + "plugin": "folder_plugins", 2995 + "plugins": "folder_plugins", 2996 + "_plugins": "folder_plugins", 2997 + "extension": "folder_plugins", 2998 + "extensions": "folder_plugins", 2999 + "addon": "folder_plugins", 3000 + "addons": "folder_plugins", 3001 + "module": "folder_plugins", 3002 + "modules": "folder_plugins", 3003 + "prisma": "folder_prisma", 3004 + "private": "folder_private", 3005 + ".private": "folder_private", 3006 + ".project": "folder_project", 3007 + ".projects": "folder_project", 3008 + "protobuf": "folder_proto", 3009 + "protobufs": "folder_proto", 3010 + "proto": "folder_proto", 3011 + "protos": "folder_proto", 3012 + "public": "folder_public", 3013 + "www": "folder_public", 3014 + "wwwroot": "folder_public", 3015 + "web": "folder_public", 3016 + "website": "folder_public", 3017 + "site": "folder_public", 3018 + "http": "folder_public", 3019 + "webroot": "folder_public", 3020 + "python": "folder_python", 3021 + "python2": "folder_python", 3022 + "python3": "folder_python", 3023 + ".pytest_cache": "folder_python", 3024 + "__pycache__": "folder_python", 3025 + "queue": "folder_queue", 3026 + "queues": "folder_queue", 3027 + "bull": "folder_queue", 3028 + "mq": "folder_queue", 3029 + "redux": "folder_redux", 3030 + "review": "folder_review", 3031 + "reviews": "folder_review", 3032 + "revisal": "folder_review", 3033 + "revisals": "folder_review", 3034 + "reviewed": "folder_review", 3035 + "res": "folder_resource", 3036 + "resource": "folder_resource", 3037 + "resources": "folder_resource", 3038 + "static": "folder_resource", 3039 + ".bot": "folder_robot", 3040 + "bot": "folder_robot", 3041 + "bots": "folder_robot", 3042 + ".robot": "folder_robot", 3043 + "robot": "folder_robot", 3044 + "robots": "folder_robot", 3045 + "_bot": "folder_robot", 3046 + "_robot": "folder_robot", 3047 + "route": "folder_routes", 3048 + "routes": "folder_routes", 3049 + "router": "folder_routes", 3050 + "routers": "folder_routes", 3051 + "navigation": "folder_routes", 3052 + "navigator": "folder_routes", 3053 + "navigators": "folder_routes", 3054 + "rule": "folder_rules", 3055 + "rules": "folder_rules", 3056 + "validation": "folder_rules", 3057 + "validations": "folder_rules", 3058 + "validator": "folder_rules", 3059 + "validators": "folder_rules", 3060 + "sass": "folder_sass", 3061 + "_sass": "folder_sass", 3062 + "scss": "folder_sass", 3063 + "_scss": "folder_sass", 3064 + "scala": "folder_scala", 3065 + "scripts": "folder_scripts", 3066 + "script": "folder_scripts", 3067 + "server": "folder_server", 3068 + "servers": "folder_server", 3069 + "backend": "folder_server", 3070 + "serverless": "folder_serverless", 3071 + ".serverless": "folder_serverless", 3072 + "auth": "folder_secure", 3073 + "authentication": "folder_secure", 3074 + "secure": "folder_secure", 3075 + "security": "folder_secure", 3076 + "glsl": "folder_shader", 3077 + "hlsl": "folder_shader", 3078 + "shader": "folder_shader", 3079 + "shaders": "folder_shader", 3080 + "share": "folder_share", 3081 + "common": "folder_share", 3082 + "src": "folder_src", 3083 + "srcs": "folder_src", 3084 + "source": "folder_src", 3085 + "sources": "folder_src", 3086 + "code": "folder_src", 3087 + "stencil": "folder_stencil", 3088 + ".stencil": "folder_stencil", 3089 + ".storybook": "folder_storybook", 3090 + "storybook": "folder_storybook", 3091 + "stories": "folder_storybook", 3092 + "__stories__": "folder_storybook", 3093 + "css": "folder_styles", 3094 + "stylesheet": "folder_styles", 3095 + "stylesheets": "folder_styles", 3096 + "style": "folder_styles", 3097 + "styles": "folder_styles", 3098 + "theme": "folder_styles", 3099 + "themes": "folder_styles", 3100 + "supabase": "folder_supabase", 3101 + ".supabase": "folder_supabase", 3102 + "svelte": "folder_svelte", 3103 + ".svelte-kit": "folder_svelte", 3104 + "svg": "folder_svg", 3105 + "svgs": "folder_svg", 3106 + "task": "folder_task", 3107 + "tasks": "folder_task", 3108 + "src-tauri": "folder_tauri", 3109 + "tauri": "folder_tauri", 3110 + "temp": "folder_temp", 3111 + "tmp": "folder_temp", 3112 + "temporary": "folder_temp", 3113 + "template": "folder_templates", 3114 + "templates": "folder_templates", 3115 + "_template": "folder_templates", 3116 + "_templates": "folder_templates", 3117 + "terraform": "folder_terraform", 3118 + ".terraform": "folder_terraform", 3119 + "test": "folder_tests", 3120 + "tests": "folder_tests", 3121 + "testing": "folder_tests", 3122 + "__tests__": "folder_tests", 3123 + "__snapshots__": "folder_tests", 3124 + "__mocks__": "folder_tests", 3125 + "__fixtures__": "folder_tests", 3126 + "__test__": "folder_tests", 3127 + "spec": "folder_tests", 3128 + "specs": "folder_tests", 3129 + "typings": "folder_types", 3130 + "@types": "folder_types", 3131 + "types": "folder_types", 3132 + "typescript": "folder_typescript", 3133 + "ts": "folder_typescript", 3134 + "unity": "folder_unity", 3135 + "upload": "folder_upload", 3136 + "uploads": "folder_upload", 3137 + "toolbox": "folder_utils", 3138 + "toolboxes": "folder_utils", 3139 + "tooling": "folder_utils", 3140 + "toolkit": "folder_utils", 3141 + "toolkits": "folder_utils", 3142 + "tools": "folder_utils", 3143 + "util": "folder_utils", 3144 + "utilities": "folder_utils", 3145 + "utility": "folder_utils", 3146 + "utils": "folder_utils", 3147 + "vercel": "folder_vercel", 3148 + ".vercel": "folder_vercel", 3149 + "now": "folder_vercel", 3150 + ".now": "folder_vercel", 3151 + "vid": "folder_video", 3152 + "vids": "folder_video", 3153 + "video": "folder_video", 3154 + "videos": "folder_video", 3155 + "movie": "folder_video", 3156 + "movies": "folder_video", 3157 + "media": "folder_video", 3158 + "view": "folder_views", 3159 + "views": "folder_views", 3160 + "screen": "folder_views", 3161 + "screens": "folder_views", 3162 + "page": "folder_views", 3163 + "pages": "folder_views", 3164 + "html": "folder_views", 3165 + ".vscode": "folder_vscode", 3166 + ".vscode-test": "folder_vscode", 3167 + "vue": "folder_vue", 3168 + ".webpack": "folder_webpack", 3169 + "webpack": "folder_webpack", 3170 + "windows": "folder_windows", 3171 + "win": "folder_windows", 3172 + "win32": "folder_windows", 3173 + "win64": "folder_windows", 3174 + "wince": "folder_windows", 3175 + ".wordpress-org": "folder_wordpress", 3176 + "wp-content": "folder_wordpress", 3177 + "workflow": "folder_workflows", 3178 + "workflows": "folder_workflows", 3179 + "ci": "folder_workflows", 3180 + ".ci": "folder_workflows", 3181 + "yarn": "folder_yarn", 3182 + ".yarn": "folder_yarn" 3183 + }, 3184 + "folderNamesExpanded": { 3185 + "admin": "folder_admin__open", 3186 + "manager": "folder_admin__open", 3187 + "managers": "folder_admin__open", 3188 + "moderator": "folder_admin__open", 3189 + "moderators": "folder_admin__open", 3190 + "android": "folder_android__open", 3191 + "apk": "folder_android__open", 3192 + "angular": "folder_angular__open", 3193 + ".angular": "folder_angular__open", 3194 + "anim": "folder_animation__open", 3195 + "anims": "folder_animation__open", 3196 + "animation": "folder_animation__open", 3197 + "animations": "folder_animation__open", 3198 + "animated": "folder_animation__open", 3199 + "api": "folder_api__open", 3200 + "apis": "folder_api__open", 3201 + "restapi": "folder_api__open", 3202 + "app": "folder_app__open", 3203 + "apps": "folder_app__open", 3204 + "application": "folder_app__open", 3205 + "project": "folder_project__open", 3206 + "projects": "folder_project__open", 3207 + "arc": "folder_archive__open", 3208 + "arcs": "folder_archive__open", 3209 + "archive": "folder_archive__open", 3210 + "archives": "folder_archive__open", 3211 + "archival": "folder_archive__open", 3212 + "bkp": "folder_archive__open", 3213 + "bkps": "folder_archive__open", 3214 + "bak": "folder_archive__open", 3215 + "baks": "folder_archive__open", 3216 + "backup": "folder_archive__open", 3217 + "backups": "folder_archive__open", 3218 + "back-up": "folder_archive__open", 3219 + "back-ups": "folder_archive__open", 3220 + "history": "folder_archive__open", 3221 + "histories": "folder_archive__open", 3222 + "aud": "folder_audio__open", 3223 + "auds": "folder_audio__open", 3224 + "audio": "folder_audio__open", 3225 + "audios": "folder_audio__open", 3226 + "music": "folder_audio__open", 3227 + "sound": "folder_audio__open", 3228 + "sounds": "folder_audio__open", 3229 + "aws": "folder_aws__open", 3230 + ".aws": "folder_aws__open", 3231 + "amazon": "folder_aws__open", 3232 + "ec2": "folder_aws__open", 3233 + "benchmark": "folder_benchmark__open", 3234 + "benchmarks": "folder_benchmark__open", 3235 + "performance": "folder_benchmark__open", 3236 + "measure": "folder_benchmark__open", 3237 + "measures": "folder_benchmark__open", 3238 + "measurement": "folder_benchmark__open", 3239 + "benches": "folder_benchmark__open", 3240 + ".azure-pipelines": "folder_azure_pipelines__open", 3241 + ".azure-pipelines-ci": "folder_azure_pipelines__open", 3242 + "azure-pipelines": "folder_azure_pipelines__open", 3243 + "cart": "folder_cart__open", 3244 + "shopping-cart": "folder_cart__open", 3245 + "shopping": "folder_cart__open", 3246 + "shop": "folder_cart__open", 3247 + ".circleci": "folder_circle_ci__open", 3248 + "client": "folder_client__open", 3249 + "clients": "folder_client__open", 3250 + "frontend": "folder_client__open", 3251 + "frontends": "folder_client__open", 3252 + "pwa": "folder_client__open", 3253 + "azure": "folder_cloud__open", 3254 + "cloud": "folder_cloud__open", 3255 + "clouds": "folder_cloud__open", 3256 + "pods": "folder_cocoapods__open", 3257 + "cmd": "folder_command__open", 3258 + "command": "folder_command__open", 3259 + "commands": "folder_command__open", 3260 + "cli": "folder_command__open", 3261 + "clis": "folder_command__open", 3262 + "components": "folder_components__open", 3263 + "widget": "folder_components__open", 3264 + "widgets": "folder_components__open", 3265 + "fragments": "folder_components__open", 3266 + "hook": "folder_composables__open", 3267 + "hooks": "folder_composables__open", 3268 + "composable": "folder_composables__open", 3269 + "composables": "folder_composables__open", 3270 + "mixin": "folder_composables__open", 3271 + "mixins": "folder_composables__open", 3272 + "connection": "folder_connection__open", 3273 + "connections": "folder_connection__open", 3274 + "integration": "folder_connection__open", 3275 + "integrations": "folder_connection__open", 3276 + "url": "folder_connection__open", 3277 + "urls": "folder_connection__open", 3278 + "cfg": "folder_config__open", 3279 + "cfgs": "folder_config__open", 3280 + "conf": "folder_config__open", 3281 + "confs": "folder_config__open", 3282 + "config": "folder_config__open", 3283 + ".config": "folder_config__open", 3284 + "configs": "folder_config__open", 3285 + "configuration": "folder_config__open", 3286 + "configurations": "folder_config__open", 3287 + "setting": "folder_config__open", 3288 + ".setting": "folder_config__open", 3289 + "settings": "folder_config__open", 3290 + ".settings": "folder_config__open", 3291 + "META-INF": "folder_config__open", 3292 + "const": "folder_constant__open", 3293 + "constant": "folder_constant__open", 3294 + "constants": "folder_constant__open", 3295 + "controller": "folder_controllers__open", 3296 + "controllers": "folder_controllers__open", 3297 + "service": "folder_controllers__open", 3298 + "services": "folder_controllers__open", 3299 + "provider": "folder_controllers__open", 3300 + "providers": "folder_controllers__open", 3301 + "handler": "folder_controllers__open", 3302 + "handlers": "folder_controllers__open", 3303 + "core": "folder_core__open", 3304 + "coverage": "folder_coverage__open", 3305 + "coverages": "folder_coverage__open", 3306 + ".nyc-output": "folder_coverage__open", 3307 + ".nyc_output": "folder_coverage__open", 3308 + "cron": "folder_cron__open", 3309 + "crontab": "folder_cron__open", 3310 + "cronjob": "folder_cron__open", 3311 + "cronjobs": "folder_cron__open", 3312 + "schedule": "folder_cron__open", 3313 + "schedules": "folder_cron__open", 3314 + "cypress": "folder_cypress__open", 3315 + ".cypress": "folder_cypress__open", 3316 + "db": "folder_database__open", 3317 + "database": "folder_database__open", 3318 + "databases": "folder_database__open", 3319 + "sql": "folder_database__open", 3320 + "data": "folder_database__open", 3321 + "_data": "folder_database__open", 3322 + "decorator": "folder_decorators__open", 3323 + "decorators": "folder_decorators__open", 3324 + "debug": "folder_debug__open", 3325 + "diff": "folder_diff__open", 3326 + "diffs": "folder_diff__open", 3327 + "patch": "folder_diff__open", 3328 + "patches": "folder_diff__open", 3329 + ".output": "folder_dist__open", 3330 + "dist": "folder_dist__open", 3331 + "out": "folder_dist__open", 3332 + "output": "folder_dist__open", 3333 + "build": "folder_dist__open", 3334 + "release": "folder_dist__open", 3335 + "bin": "folder_dist__open", 3336 + "target": "folder_dist__open", 3337 + "docker": "folder_docker__open", 3338 + "dockerfiles": "folder_docker__open", 3339 + ".docker": "folder_docker__open", 3340 + "doc": "folder_docs__open", 3341 + "docs": "folder_docs__open", 3342 + "document": "folder_docs__open", 3343 + "documents": "folder_docs__open", 3344 + "documentation": "folder_docs__open", 3345 + "download": "folder_download__open", 3346 + "downloads": "folder_download__open", 3347 + ".env": "folder_environment__open", 3348 + ".environment": "folder_environment__open", 3349 + "env": "folder_environment__open", 3350 + "envs": "folder_environment__open", 3351 + "environment": "folder_environment__open", 3352 + "environments": "folder_environment__open", 3353 + ".venv": "folder_environment__open", 3354 + "error": "folder_error__open", 3355 + "errors": "folder_error__open", 3356 + "err": "folder_error__open", 3357 + "event": "folder_event__open", 3358 + "events": "folder_event__open", 3359 + "demo": "folder_examples__open", 3360 + "demos": "folder_examples__open", 3361 + "example": "folder_examples__open", 3362 + "examples": "folder_examples__open", 3363 + "sample": "folder_examples__open", 3364 + "samples": "folder_examples__open", 3365 + "sample-data": "folder_examples__open", 3366 + ".expo": "folder_expo__open", 3367 + ".expo-shared": "folder_expo__open", 3368 + "fastlane": "folder_fastlane__open", 3369 + "favorite": "folder_favorite__open", 3370 + "favorites": "folder_favorite__open", 3371 + "like": "folder_favorite__open", 3372 + "likes": "folder_favorite__open", 3373 + "heart": "folder_favorite__open", 3374 + "hearts": "folder_favorite__open", 3375 + "firebase": "folder_firebase__open", 3376 + ".firebase": "folder_firebase__open", 3377 + "flow-typed": "folder_flow__open", 3378 + "font": "folder_fonts__open", 3379 + "fonts": "folder_fonts__open", 3380 + "func": "folder_functions__open", 3381 + "funcs": "folder_functions__open", 3382 + "function": "folder_functions__open", 3383 + "functions": "folder_functions__open", 3384 + "lambda": "folder_functions__open", 3385 + "lambdas": "folder_functions__open", 3386 + "logic": "folder_functions__open", 3387 + "math": "folder_functions__open", 3388 + "maths": "folder_functions__open", 3389 + "calc": "folder_functions__open", 3390 + "calcs": "folder_functions__open", 3391 + "calculation": "folder_functions__open", 3392 + "calculations": "folder_functions__open", 3393 + "gamemaker": "folder_gamemaker__open", 3394 + "gamemaker2": "folder_gamemaker__open", 3395 + ".git": "folder_git__open", 3396 + "git": "folder_git__open", 3397 + "githooks": "folder_git__open", 3398 + ".githooks": "folder_git__open", 3399 + "submodules": "folder_git__open", 3400 + ".submodules": "folder_git__open", 3401 + ".github": "folder_github__open", 3402 + ".gitlab": "folder_gitlab__open", 3403 + ".godot": "folder_godot__open", 3404 + "godot": "folder_godot__open", 3405 + ".godot-cpp": "folder_godot__open", 3406 + "godot-cpp": "folder_godot__open", 3407 + "gradle": "folder_gradle__open", 3408 + ".gradle": "folder_gradle__open", 3409 + "graphql": "folder_graphql__open", 3410 + "gql": "folder_graphql__open", 3411 + "guard": "folder_guard__open", 3412 + "guards": "folder_guard__open", 3413 + "gulp": "folder_gulp__open", 3414 + "gulp-tasks": "folder_gulp__open", 3415 + "gulpfile.js": "folder_gulp__open", 3416 + "gulpfile.mjs": "folder_gulp__open", 3417 + "gulpfile.ts": "folder_gulp__open", 3418 + "gulpfile.babel.js": "folder_gulp__open", 3419 + "home": "folder_home__open", 3420 + ".home": "folder_home__open", 3421 + "start": "folder_home__open", 3422 + ".start": "folder_home__open", 3423 + "husky": "folder_husky__open", 3424 + ".husky": "folder_husky__open", 3425 + "asset": "folder_images__open", 3426 + "assets": "folder_images__open", 3427 + "images": "folder_images__open", 3428 + "image": "folder_images__open", 3429 + "imgs": "folder_images__open", 3430 + "img": "folder_images__open", 3431 + "icons": "folder_images__open", 3432 + "icon": "folder_images__open", 3433 + "icos": "folder_images__open", 3434 + "ico": "folder_images__open", 3435 + "figures": "folder_images__open", 3436 + "figure": "folder_images__open", 3437 + "figs": "folder_images__open", 3438 + "fig": "folder_images__open", 3439 + "screenshot": "folder_images__open", 3440 + "screenshots": "folder_images__open", 3441 + "screengrab": "folder_images__open", 3442 + "screengrabs": "folder_images__open", 3443 + "pic": "folder_images__open", 3444 + "pics": "folder_images__open", 3445 + "picture": "folder_images__open", 3446 + "pictures": "folder_images__open", 3447 + "ios": "folder_ios__open", 3448 + "java": "folder_java__open", 3449 + "jar": "folder_java__open", 3450 + "js": "folder_javascript__open", 3451 + "javascript": "folder_javascript__open", 3452 + "javascripts": "folder_javascript__open", 3453 + "json": "folder_json__open", 3454 + "jsons": "folder_json__open", 3455 + "key": "folder_key__open", 3456 + "keys": "folder_key__open", 3457 + "token": "folder_key__open", 3458 + "tokens": "folder_key__open", 3459 + "jwt": "folder_key__open", 3460 + "secret": "folder_key__open", 3461 + "secrets": "folder_key__open", 3462 + "kubernetes": "folder_kubernetes__open", 3463 + ".kubernetes": "folder_kubernetes__open", 3464 + "layout": "folder_layouts__open", 3465 + "layouts": "folder_layouts__open", 3466 + "_layouts": "folder_layouts__open", 3467 + "lib": "folder_library__open", 3468 + "libs": "folder_library__open", 3469 + "library": "folder_library__open", 3470 + "libraries": "folder_library__open", 3471 + "vendor": "folder_library__open", 3472 + "vendors": "folder_library__open", 3473 + "third-party": "folder_library__open", 3474 + "dll": "folder_library__open", 3475 + "linux": "folder_linux__open", 3476 + "unix": "folder_linux__open", 3477 + "link": "folder_link__open", 3478 + "links": "folder_link__open", 3479 + "shortcut": "folder_link__open", 3480 + "shortcuts": "folder_link__open", 3481 + "i18n": "folder_locales__open", 3482 + "internationalization": "folder_locales__open", 3483 + "lang": "folder_locales__open", 3484 + "langs": "folder_locales__open", 3485 + "language": "folder_locales__open", 3486 + "languages": "folder_locales__open", 3487 + "locale": "folder_locales__open", 3488 + "locales": "folder_locales__open", 3489 + "l10n": "folder_locales__open", 3490 + "localization": "folder_locales__open", 3491 + "translation": "folder_locales__open", 3492 + "translate": "folder_locales__open", 3493 + "translations": "folder_locales__open", 3494 + "log": "folder_log__open", 3495 + "logs": "folder_log__open", 3496 + "report": "folder_log__open", 3497 + "reports": "folder_log__open", 3498 + "lottie": "folder_lottie__open", 3499 + "lotties": "folder_lottie__open", 3500 + "lottiefiles": "folder_lottie__open", 3501 + "mac": "folder_macos__open", 3502 + "macos": "folder_macos__open", 3503 + "macosx": "folder_macos__open", 3504 + "osx": "folder_macos__open", 3505 + "darwin": "folder_macos__open", 3506 + "mail": "folder_mail__open", 3507 + "mails": "folder_mail__open", 3508 + "email": "folder_mail__open", 3509 + "emails": "folder_mail__open", 3510 + "smtp": "folder_mail__open", 3511 + "mailer": "folder_mail__open", 3512 + "mailers": "folder_mail__open", 3513 + "phpmailer": "folder_mail__open", 3514 + "md": "folder_markdown__open", 3515 + "markdown": "folder_markdown__open", 3516 + "markdowns": "folder_markdown__open", 3517 + "message": "folder_message__open", 3518 + "messages": "folder_message__open", 3519 + "forum": "folder_message__open", 3520 + "chat": "folder_message__open", 3521 + "chats": "folder_message__open", 3522 + "conversation": "folder_message__open", 3523 + "conversations": "folder_message__open", 3524 + "middleware": "folder_middleware__open", 3525 + "middlewares": "folder_middleware__open", 3526 + "mjml": "folder_mjml__open", 3527 + "mobile": "folder_mobile__open", 3528 + "mobiles": "folder_mobile__open", 3529 + "phone": "folder_mobile__open", 3530 + "phones": "folder_mobile__open", 3531 + "mock": "folder_mocks__open", 3532 + "mocks": "folder_mocks__open", 3533 + "fixture": "folder_mocks__open", 3534 + "fixtures": "folder_mocks__open", 3535 + "draft": "folder_mocks__open", 3536 + "drafts": "folder_mocks__open", 3537 + "concept": "folder_mocks__open", 3538 + "concepts": "folder_mocks__open", 3539 + "sketch": "folder_mocks__open", 3540 + "sketches": "folder_mocks__open", 3541 + "stub": "folder_mocks__open", 3542 + "stubs": "folder_mocks__open", 3543 + "mojo": "folder_mojo__open", 3544 + ".netlify": "folder_netlify__open", 3545 + ".next": "folder_next__open", 3546 + "node_modules": "folder_node__open", 3547 + "node": "folder_node__open", 3548 + "nodejs": "folder_node__open", 3549 + ".nuxt": "folder_nuxt__open", 3550 + "nuxt": "folder_nuxt__open", 3551 + "package": "folder_packages__open", 3552 + "packages": "folder_packages__open", 3553 + "pkg": "folder_packages__open", 3554 + "pkgs": "folder_packages__open", 3555 + "pdf": "folder_pdf__open", 3556 + "pdfs": "folder_pdf__open", 3557 + ".pdm-plugins": "folder_pdm__open", 3558 + ".pdm-build": "folder_pdm__open", 3559 + "php": "folder_php__open", 3560 + "playground": "folder_playground__open", 3561 + "playgrounds": "folder_playground__open", 3562 + "plugin": "folder_plugins__open", 3563 + "plugins": "folder_plugins__open", 3564 + "_plugins": "folder_plugins__open", 3565 + "extension": "folder_plugins__open", 3566 + "extensions": "folder_plugins__open", 3567 + "addon": "folder_plugins__open", 3568 + "addons": "folder_plugins__open", 3569 + "module": "folder_plugins__open", 3570 + "modules": "folder_plugins__open", 3571 + "prisma": "folder_prisma__open", 3572 + "private": "folder_private__open", 3573 + ".private": "folder_private__open", 3574 + ".project": "folder_project__open", 3575 + ".projects": "folder_project__open", 3576 + "protobuf": "folder_proto__open", 3577 + "protobufs": "folder_proto__open", 3578 + "proto": "folder_proto__open", 3579 + "protos": "folder_proto__open", 3580 + "public": "folder_public__open", 3581 + "www": "folder_public__open", 3582 + "wwwroot": "folder_public__open", 3583 + "web": "folder_public__open", 3584 + "website": "folder_public__open", 3585 + "site": "folder_public__open", 3586 + "http": "folder_public__open", 3587 + "webroot": "folder_public__open", 3588 + "python": "folder_python__open", 3589 + "python2": "folder_python__open", 3590 + "python3": "folder_python__open", 3591 + ".pytest_cache": "folder_python__open", 3592 + "__pycache__": "folder_python__open", 3593 + "queue": "folder_queue__open", 3594 + "queues": "folder_queue__open", 3595 + "bull": "folder_queue__open", 3596 + "mq": "folder_queue__open", 3597 + "redux": "folder_redux__open", 3598 + "review": "folder_review__open", 3599 + "reviews": "folder_review__open", 3600 + "revisal": "folder_review__open", 3601 + "revisals": "folder_review__open", 3602 + "reviewed": "folder_review__open", 3603 + "res": "folder_resource__open", 3604 + "resource": "folder_resource__open", 3605 + "resources": "folder_resource__open", 3606 + "static": "folder_resource__open", 3607 + ".bot": "folder_robot__open", 3608 + "bot": "folder_robot__open", 3609 + "bots": "folder_robot__open", 3610 + ".robot": "folder_robot__open", 3611 + "robot": "folder_robot__open", 3612 + "robots": "folder_robot__open", 3613 + "_bot": "folder_robot__open", 3614 + "_robot": "folder_robot__open", 3615 + "route": "folder_routes__open", 3616 + "routes": "folder_routes__open", 3617 + "router": "folder_routes__open", 3618 + "routers": "folder_routes__open", 3619 + "navigation": "folder_routes__open", 3620 + "navigator": "folder_routes__open", 3621 + "navigators": "folder_routes__open", 3622 + "rule": "folder_rules__open", 3623 + "rules": "folder_rules__open", 3624 + "validation": "folder_rules__open", 3625 + "validations": "folder_rules__open", 3626 + "validator": "folder_rules__open", 3627 + "validators": "folder_rules__open", 3628 + "sass": "folder_sass__open", 3629 + "_sass": "folder_sass__open", 3630 + "scss": "folder_sass__open", 3631 + "_scss": "folder_sass__open", 3632 + "scala": "folder_scala__open", 3633 + "scripts": "folder_scripts__open", 3634 + "script": "folder_scripts__open", 3635 + "server": "folder_server__open", 3636 + "servers": "folder_server__open", 3637 + "backend": "folder_server__open", 3638 + "serverless": "folder_serverless__open", 3639 + ".serverless": "folder_serverless__open", 3640 + "auth": "folder_secure__open", 3641 + "authentication": "folder_secure__open", 3642 + "secure": "folder_secure__open", 3643 + "security": "folder_secure__open", 3644 + "glsl": "folder_shader__open", 3645 + "hlsl": "folder_shader__open", 3646 + "shader": "folder_shader__open", 3647 + "shaders": "folder_shader__open", 3648 + "share": "folder_share__open", 3649 + "common": "folder_share__open", 3650 + "src": "folder_src__open", 3651 + "srcs": "folder_src__open", 3652 + "source": "folder_src__open", 3653 + "sources": "folder_src__open", 3654 + "code": "folder_src__open", 3655 + "stencil": "folder_stencil__open", 3656 + ".stencil": "folder_stencil__open", 3657 + ".storybook": "folder_storybook__open", 3658 + "storybook": "folder_storybook__open", 3659 + "stories": "folder_storybook__open", 3660 + "__stories__": "folder_storybook__open", 3661 + "css": "folder_styles__open", 3662 + "stylesheet": "folder_styles__open", 3663 + "stylesheets": "folder_styles__open", 3664 + "style": "folder_styles__open", 3665 + "styles": "folder_styles__open", 3666 + "theme": "folder_styles__open", 3667 + "themes": "folder_styles__open", 3668 + "supabase": "folder_supabase__open", 3669 + ".supabase": "folder_supabase__open", 3670 + "svelte": "folder_svelte__open", 3671 + ".svelte-kit": "folder_svelte__open", 3672 + "svg": "folder_svg__open", 3673 + "svgs": "folder_svg__open", 3674 + "task": "folder_task__open", 3675 + "tasks": "folder_task__open", 3676 + "src-tauri": "folder_tauri__open", 3677 + "tauri": "folder_tauri__open", 3678 + "temp": "folder_temp__open", 3679 + "tmp": "folder_temp__open", 3680 + "temporary": "folder_temp__open", 3681 + "template": "folder_templates__open", 3682 + "templates": "folder_templates__open", 3683 + "_template": "folder_templates__open", 3684 + "_templates": "folder_templates__open", 3685 + "terraform": "folder_terraform__open", 3686 + ".terraform": "folder_terraform__open", 3687 + "test": "folder_tests__open", 3688 + "tests": "folder_tests__open", 3689 + "testing": "folder_tests__open", 3690 + "__tests__": "folder_tests__open", 3691 + "__snapshots__": "folder_tests__open", 3692 + "__mocks__": "folder_tests__open", 3693 + "__fixtures__": "folder_tests__open", 3694 + "__test__": "folder_tests__open", 3695 + "spec": "folder_tests__open", 3696 + "specs": "folder_tests__open", 3697 + "typings": "folder_types__open", 3698 + "@types": "folder_types__open", 3699 + "types": "folder_types__open", 3700 + "typescript": "folder_typescript__open", 3701 + "ts": "folder_typescript__open", 3702 + "unity": "folder_unity__open", 3703 + "upload": "folder_upload__open", 3704 + "uploads": "folder_upload__open", 3705 + "toolbox": "folder_utils__open", 3706 + "toolboxes": "folder_utils__open", 3707 + "tooling": "folder_utils__open", 3708 + "toolkit": "folder_utils__open", 3709 + "toolkits": "folder_utils__open", 3710 + "tools": "folder_utils__open", 3711 + "util": "folder_utils__open", 3712 + "utilities": "folder_utils__open", 3713 + "utility": "folder_utils__open", 3714 + "utils": "folder_utils__open", 3715 + "vercel": "folder_vercel__open", 3716 + ".vercel": "folder_vercel__open", 3717 + "now": "folder_vercel__open", 3718 + ".now": "folder_vercel__open", 3719 + "vid": "folder_video__open", 3720 + "vids": "folder_video__open", 3721 + "video": "folder_video__open", 3722 + "videos": "folder_video__open", 3723 + "movie": "folder_video__open", 3724 + "movies": "folder_video__open", 3725 + "media": "folder_video__open", 3726 + "view": "folder_views__open", 3727 + "views": "folder_views__open", 3728 + "screen": "folder_views__open", 3729 + "screens": "folder_views__open", 3730 + "page": "folder_views__open", 3731 + "pages": "folder_views__open", 3732 + "html": "folder_views__open", 3733 + ".vscode": "folder_vscode__open", 3734 + ".vscode-test": "folder_vscode__open", 3735 + "vue": "folder_vue__open", 3736 + ".webpack": "folder_webpack__open", 3737 + "webpack": "folder_webpack__open", 3738 + "windows": "folder_windows__open", 3739 + "win": "folder_windows__open", 3740 + "win32": "folder_windows__open", 3741 + "win64": "folder_windows__open", 3742 + "wince": "folder_windows__open", 3743 + ".wordpress-org": "folder_wordpress__open", 3744 + "wp-content": "folder_wordpress__open", 3745 + "workflow": "folder_workflows__open", 3746 + "workflows": "folder_workflows__open", 3747 + "ci": "folder_workflows__open", 3748 + ".ci": "folder_workflows__open", 3749 + "yarn": "folder_yarn__open", 3750 + ".yarn": "folder_yarn__open" 3751 + } 3752 + }
+1
web/console/src/assets/workspace-icons/vitest.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#50a14f" d="m14.5 8.5-6.5 6-6.5-6"/><path stroke="#e0c240" d="M7.5 11.5 8 8 5 7l4.5-5.5L9 5l3 1z"/></g></svg>
+1
web/console/src/assets/workspace-icons/vs_code.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linecap="round" stroke-linejoin="round"><path d="M10.5 11 3 4.5h-.5l-1 1V6l9 8.5 4-2v-9l-4-2v13M10.5 1.5 5.3 6.41M3.53 8.08 1.5 10v.5l.98 1.1.52-.1 2.17-1.88m1.91-1.66L10.5 5"/></g></svg>
+1
web/console/src/assets/workspace-icons/vs_code_ignore.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#6c7280" stroke-linecap="round" stroke-linejoin="round"><path d="M10.5 11 3 4.5h-.5l-1 1V6l9 8.5 4-2v-9l-4-2v13M10.5 1.5 5.3 6.41M3.53 8.08 1.5 10v.5l.98 1.1.52-.1 2.17-1.88m1.91-1.66L10.5 5"/></g></svg>
+1
web/console/src/assets/workspace-icons/vs_codium.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linecap="round"><path stroke-linejoin="round" d="M1.63 7.61c-.08 2.45.7 3.91 2.37 4.39a5.53 5.53 0 0 1 3.5 2.5"/><path stroke-linejoin="round" d="M3.5 3.5c1.19.76 1.78 1.53 1.78 2.32 0 .79-1.08 2.7-.72 4.18.45 1.82 2.94 2 2.94 4.5"/><path stroke-linejoin="round" d="M7.5 7.5c-.47.08-.96.24-1.47.5-.76.39-1.47 1.54-1.47 2"/><path d="M7.5 1.5c1.4 2.02 2.1 3.86 2.1 5.53 0 1.17-.29 2.3-.88 3.27-.57.93-1.22 1.16-1.22 4.2M12.5 2.5c-1 0-1.51.5-2.01 1-.34.33-.67 1-.99 2"/><path stroke-linejoin="round" d="M13.5 6.5c.23 1.46-.1 2.63-1 3.5-.9.88-1.63.45-3 1.5-.48.37-1.15 1.37-2 3M12 10.5h2.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/vue.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linecap="round" stroke-linejoin="round"><path d="M1 1.5h5.44L8 4.56 9.56 1.5H15l-6.99 13z"/><path d="M12.05 1.73 8 9.28 3.95 1.73"/></g></svg>
+1
web/console/src/assets/workspace-icons/vue_config.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#6c7280" d="M11.5 13.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm1.75-4 1.75 3-1.75 3h-3.5L8 12.5l1.75-3h3.5Z"/><path stroke="#50a14f" d="M.5.5h4.67L6.5 3.09 7.83.5h4.67l-6 11-6-11Zm9.47.2L6.5 7.08 3.03.7"/></g></svg>
+1
web/console/src/assets/workspace-icons/wallaby.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#50a14f" stroke-linejoin="round" d="M14.5 1.5v13h-13v-7h6v-6z"/></svg>
+1
web/console/src/assets/workspace-icons/watchman.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linejoin="round"><path stroke-linecap="round" d="M.5 4.5c1.67 5.33 4.17 8 7.5 8s5.84-2.67 7.53-8"/><path stroke-linecap="round" d="M.5 11.5c1.67-5.33 4.17-8 7.5-8s5.84 2.67 7.53 8"/><path d="M8 5.5 10.5 7v2l-2.48 1.51L5.5 9V7z"/></g></svg>
+1
web/console/src/assets/workspace-icons/web_assembly.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#5d5dff" stroke-linejoin="round"><path d="m9 13 2-4.5 2 4.5"/><path stroke-linecap="round" d="M10 11.5h2M3.5 8.5l1 4 1.5-4 1.5 4 1-4"/><path stroke-linecap="round" d="M10.5 1.5h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h2"/><path d="M5.5 1a2.5 2.5 0 0 0 5 0"/></g></svg>
+1
web/console/src/assets/workspace-icons/webpack.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path stroke="#007ACC" d="m4.5 10.02-3 1.73M11.47 10l3.03 1.75M8 4V.5"/><path stroke="#3b8ad8" d="M11.5 10 8 12l-3.5-2V6L8 4l3.5 2z"/><path stroke="#007ACC" d="M14.5 11.75 8 15.5l-6.5-3.75v-7.5L8 .5l6.5 3.75v7.5Zm-13-7.5L8 8m6.5-3.75L8 8m0 0v7.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/windi.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8" stroke-linecap="round"><path d="M1.5 5.5H6a2 2 0 1 0-2-2"/><path stroke-linejoin="round" d="M1.5 8.5H12A2.5 2.5 0 1 0 9.5 6"/><path d="M7.5 13A1.5 1.5 0 1 0 9 11.5H5.5"/><path stroke-linejoin="round" d="M1.5 11.5h2"/></g></svg>
+1
web/console/src/assets/workspace-icons/workflow.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round"><rect width="6" height="6" x="1.5" y="1.5" rx="2"/><rect width="6" height="6" x="8.5" y="8.5" rx="2"/><path d="M4.5 7.5V10c0 1 .5 1.5 1.5 1.5h2.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/xaml.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#3b8ad8" d="m10.25 4.5 2.25 4-2.25 4h-4.5l-2.25-4 2.25-4z"/><path stroke="#333333" stroke-linecap="round" d="m2.5 12.5-2-4 2-4"/><path stroke="#3b8ad8" stroke-linecap="round" d="m6 12 2-3.5h4M8 8.5 6 5"/><path stroke="#333333" stroke-linecap="round" d="m13.5 4.5 2 4-2 4"/></g></svg>
+1
web/console/src/assets/workspace-icons/xcode_project.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#3b8ad8"><path stroke-linecap="round" stroke-linejoin="round" d="m6.69 15.48-1.55-.91 3.55-6.28.59-1.04s-.3-1.92-2.89-3.45l.45-.78 5.76 2-.44.79 1.04.6.44-.78 1.81 1.07-2.07 3.66-1.81-1.07.44-.78-1.03-.61-.74 1.3-3.55 6.28Z"/><path d="M14.5 11v1a2.5 2.5 0 0 1-2.5 2.5H9m-5 0A2.5 2.5 0 0 1 1.5 12V4A2.5 2.5 0 0 1 4 1.5h8A2.5 2.5 0 0 1 14.5 4"/></g></svg>
+1
web/console/src/assets/workspace-icons/xib.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e0c240" stroke-linejoin="round" d="M8.03 5.97c-.22-.23-.44-.47-.68-.7-2.73-2.73-5.42-4.48-6-3.89-.6.59 1.15 3.28 3.88 6.01l.7.68M8.1 9.91c1.55 1.17 2.78 1.74 3.15 1.37.38-.37-.2-1.6-1.37-3.15M12.5 1.5l2 2-11 11h-2v-2l11-11Zm-2 2 2 2M3 11l2 2m6-2c1-1 2.75-.25 3.25.25.17.5.25 1.58.25 3.25-1.67 0-2.75-.08-3.25-.25C10.75 13.75 10 12 11 11Z"/></svg>
+1
web/console/src/assets/workspace-icons/xmake.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke-linejoin="round"><path stroke="#007ACC" d="M14.04 10.42a6.45 6.45 0 0 0-.56-5.92 6.53 6.53 0 0 0-.73-.94L8.99 6.18l5.05 4.24Z"/><path stroke="#008080" d="M7.35 7.32 2.2 10.94A6.5 6.5 0 0 0 13 12.15L7.35 7.32Z"/><path stroke="#50a14f" d="M3.04 3.8a6.47 6.47 0 0 0-1.47 5.14L5.72 6 3.04 3.8Z"/></g></svg>
+1
web/console/src/assets/workspace-icons/xml.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 4.5 1 8l3.5 3.5m7-7L15 8l-3.5 3.5M9.5 2l-3 12"/></g></svg>
+1
web/console/src/assets/workspace-icons/yaml.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#e45649" stroke-linecap="round" stroke-linejoin="round" d="M2.5 1.5h3l3 4 3-4h3l-9 13h-3L7 8z"/></svg>
+1
web/console/src/assets/workspace-icons/yarn.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round" d="M7.83 2A5.3 5.3 0 0 1 8.93.5c.26.46.4.97.44 1.5l.89-.2s1.33 2.61-.9 6.2a4.14 4.14 0 0 1 1.56 4.5c2.3-.76 3.06-1.5 3.82-1.5.2 0 .4.07.54.21.14.14.22.33.22.53 0 0 .1.5-1.23 1.15l-2.65 1.3a8.42 8.42 0 0 1-4 1.3c-1.55 0-2.65.11-2.65-.65.03-.5.26-.96.64-1.3h-.67S3.81 15.5 2.28 15.5c-1.53 0 0-1.3 0-1.3-1.33 0-.86-2.53.67-3.27a2.97 2.97 0 0 1-.22-2.8 3.07 3.07 0 0 1 2.21-1.76C4.17 5.6 3.61 4 4.28 3.76c.6-.25 1.07-.72 1.33-1.3A2.8 2.8 0 0 1 7.83 2h0Z"/></svg>
+1
web/console/src/assets/workspace-icons/yarn_lock.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd"><path stroke="#6c7280" d="M15 11.5c.27 0 .5.22.5.5v3a.5.5 0 0 1-.5.5h-5a.5.5 0 0 1-.5-.5v-3c0-.28.22-.5.5-.5h5Zm-4 0V10a1.5 1.5 0 0 1 3 0v1.5"/><path stroke="#5d5dff" stroke-linecap="round" stroke-linejoin="round" d="M7.5 13.18c-.54.17-1.11.28-1.7.32-1.34 0-2.3.09-2.3-.57.03-.43.23-.84.56-1.13h-.58s-.98 1.7-2.3 1.7c-1.33 0 0-1.13 0-1.13-1.16 0-.75-2.19.57-2.83a2.58 2.58 0 0 1-.19-2.42 2.66 2.66 0 0 1 1.92-1.53c-.67-.66-1.15-2.05-.57-2.26.51-.22.93-.63 1.15-1.14.55-.4 1.25-.54 1.92-.39.24-.49.56-.93.96-1.3.22.4.35.84.38 1.3l.77-.17S9.24 3.89 7.32 7a3.61 3.61 0 0 1 1.46 2.5"/></g></svg>
+1
web/console/src/assets/workspace-icons/zig.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" fill-rule="evenodd" stroke="#ca7f00" stroke-linecap="round" stroke-linejoin="round" d="M10 3.5H6l-1.5 2h4l-7 9 4.5-2h4l1.5-2h-4l7-9z"/></svg>
+1
web/console/src/assets/workspace-icons/zip.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" fill-rule="evenodd" stroke="#e0c240"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 14.5h-1a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-1 0"/><path stroke-linecap="square" stroke-linejoin="round" d="M6.5 14.5v-2L8 11l1.5 1.5v2z"/><path d="M6 2.5h2m0 1h2m-4 1h2m0 1h2m-4 1h2m0 1h2m-4 1h2m0 1h2"/></g></svg>
+138
web/console/src/core/workspace-icons.js
··· 1 + import vitesseLightTheme from "../assets/workspace-icons/vitesse-light-theme.json"; 2 + 3 + const svgModules = import.meta.glob("../assets/workspace-icons/*.svg", { 4 + eager: true, 5 + import: "default", 6 + }); 7 + 8 + const iconURLs = Object.fromEntries( 9 + Object.entries(svgModules).map(([path, url]) => { 10 + const filename = path.split("/").pop() || ""; 11 + return [filename.replace(/\.svg$/u, ""), url]; 12 + }) 13 + ); 14 + 15 + function normalizeLookupMap(raw) { 16 + return Object.fromEntries( 17 + Object.entries(raw || {}).map(([key, value]) => [String(key).toLowerCase(), value]) 18 + ); 19 + } 20 + 21 + const iconDefinitions = vitesseLightTheme?.iconDefinitions || {}; 22 + const fileNames = normalizeLookupMap(vitesseLightTheme?.fileNames); 23 + const fileExtensions = normalizeLookupMap(vitesseLightTheme?.fileExtensions); 24 + const fallbackFileIcon = String(vitesseLightTheme?.file || "file"); 25 + const fallbackFolderIcon = String(vitesseLightTheme?.folder || "folder"); 26 + const fallbackFolderExpandedIcon = String(vitesseLightTheme?.folderExpanded || "folder__open"); 27 + const commonFolderIcons = { 28 + api: "folder_api", 29 + app: "folder_app", 30 + apps: "folder_app", 31 + assets: "folder_images", 32 + build: "folder_dist", 33 + client: "folder_client", 34 + cmd: "folder_command", 35 + component: "folder_components", 36 + components: "folder_components", 37 + config: "folder_config", 38 + configs: "folder_config", 39 + coverage: "folder_coverage", 40 + dist: "folder_dist", 41 + doc: "folder_docs", 42 + docs: "folder_docs", 43 + example: "folder_examples", 44 + examples: "folder_examples", 45 + icons: "folder_images", 46 + images: "folder_images", 47 + img: "folder_images", 48 + lib: "folder_library", 49 + libs: "folder_library", 50 + node_modules: "folder_node", 51 + package: "folder_packages", 52 + packages: "folder_packages", 53 + page: "folder_views", 54 + pages: "folder_views", 55 + public: "folder_public", 56 + route: "folder_routes", 57 + routes: "folder_routes", 58 + script: "folder_scripts", 59 + scripts: "folder_scripts", 60 + server: "folder_server", 61 + src: "folder_src", 62 + static: "folder_resource", 63 + style: "folder_styles", 64 + styles: "folder_styles", 65 + temp: "folder_temp", 66 + test: "folder_tests", 67 + tests: "folder_tests", 68 + tmp: "folder_temp", 69 + vendor: "folder_library", 70 + view: "folder_views", 71 + views: "folder_views", 72 + }; 73 + 74 + function iconURLByName(iconName) { 75 + const key = String(iconName || "").trim(); 76 + if (!key) { 77 + return iconURLs.file || ""; 78 + } 79 + const definition = iconDefinitions[key]; 80 + const filename = String(definition?.iconPath || "") 81 + .trim() 82 + .split("/") 83 + .pop() 84 + ?.replace(/\.svg$/u, ""); 85 + if (filename && iconURLs[filename]) { 86 + return iconURLs[filename]; 87 + } 88 + return iconURLs[key] || iconURLs.file || ""; 89 + } 90 + 91 + function basenameLower(name) { 92 + return String(name || "").trim().toLowerCase(); 93 + } 94 + 95 + function fileExtensionCandidates(name) { 96 + const lower = basenameLower(name); 97 + if (!lower) { 98 + return []; 99 + } 100 + const candidates = [lower]; 101 + let dotIndex = lower.indexOf("."); 102 + while (dotIndex >= 0 && dotIndex < lower.length - 1) { 103 + candidates.push(lower.slice(dotIndex + 1)); 104 + dotIndex = lower.indexOf(".", dotIndex + 1); 105 + } 106 + return [...new Set(candidates)]; 107 + } 108 + 109 + function folderIconName(name, expanded) { 110 + const matched = commonFolderIcons[name]; 111 + if (!matched) { 112 + return expanded ? fallbackFolderExpandedIcon : fallbackFolderIcon; 113 + } 114 + return expanded ? `${matched}__open` : matched; 115 + } 116 + 117 + export function workspaceTreeIcon(entry, expanded = false) { 118 + const name = basenameLower(entry?.name); 119 + if (!name) { 120 + return iconURLByName(entry?.is_dir ? fallbackFolderIcon : fallbackFileIcon); 121 + } 122 + 123 + if (entry?.is_dir) { 124 + return iconURLByName(folderIconName(name, expanded)); 125 + } 126 + 127 + if (fileNames[name]) { 128 + return iconURLByName(fileNames[name]); 129 + } 130 + 131 + for (const candidate of fileExtensionCandidates(name)) { 132 + if (fileExtensions[candidate]) { 133 + return iconURLByName(fileExtensions[candidate]); 134 + } 135 + } 136 + 137 + return iconURLByName(fallbackFileIcon); 138 + }
+102
web/console/src/i18n/index.js
··· 317 317 chat_topic_untitled: "Untitled Topic", 318 318 chat_topic_system_show: "Show System", 319 319 chat_topic_system_hide: "Hide System", 320 + chat_workspace_label: "Workspace", 321 + chat_workspace_empty: "(none)", 322 + chat_workspace_status_loading: "Loading workspace...", 323 + chat_workspace_hint_empty: "No workspace is attached to this topic yet.", 324 + chat_workspace_hint_needs_topic: "Send the first message to create a topic.", 325 + chat_workspace_hint_no_topic: "Choose a topic first.", 326 + chat_workspace_hint_system_topic: "System topics do not support workspaces.", 327 + chat_workspace_input_placeholder: "Enter a local directory path", 328 + chat_workspace_action_attach: "Attach", 329 + chat_workspace_action_edit: "Edit", 330 + chat_workspace_action_detach: "Detach", 331 + chat_workspace_action_insert: "Add to Chat", 332 + chat_workspace_action_open: "Open", 333 + chat_workspace_action_save: "Save", 334 + chat_workspace_action_cancel: "Cancel", 335 + chat_workspace_sidebar_open: "Open workspace sidebar", 336 + chat_workspace_sidebar_close: "Close workspace sidebar", 337 + chat_workspace_tree_loading: "Loading files...", 338 + chat_workspace_tree_empty: "This directory is empty.", 339 + chat_workspace_empty_title: "No workspace attached", 340 + chat_workspace_unavailable_title: "Workspace unavailable", 341 + chat_workspace_dialog_title: "Attach workspace", 342 + chat_workspace_dialog_hint: "Browse the filesystem on the running morph host and choose a directory.", 343 + chat_workspace_dialog_selected: "Selected directory", 344 + chat_workspace_dialog_selection_empty: "No directory selected", 345 + chat_workspace_dialog_recent: "Recent", 346 + chat_workspace_dialog_recent_empty: "No recent workspaces yet.", 347 + chat_workspace_dialog_places: "Places", 348 + chat_workspace_dialog_home: "Home", 349 + chat_workspace_dialog_system: "System", 350 + chat_workspace_dialog_loading: "Loading directories...", 351 + chat_workspace_dialog_empty: "Nothing to show here.", 352 + chat_workspace_kind_dir: "dir", 353 + chat_workspace_kind_file: "file", 320 354 chat_topic_delete_action: "Delete Topic", 321 355 chat_topic_delete_confirm: "Delete topic \"{title}\"? This hides its tasks from the default view.", 322 356 chat_submit_unsupported: ··· 911 945 chat_topic_untitled: "未命名话题", 912 946 chat_topic_system_show: "显示系统话题", 913 947 chat_topic_system_hide: "隐藏系统话题", 948 + chat_workspace_label: "Workspace", 949 + chat_workspace_empty: "(未绑定)", 950 + chat_workspace_status_loading: "正在读取 workspace...", 951 + chat_workspace_hint_empty: "这个 topic 还没有绑定 workspace。", 952 + chat_workspace_hint_needs_topic: "先发一条消息创建 topic。", 953 + chat_workspace_hint_no_topic: "先选一个 topic。", 954 + chat_workspace_hint_system_topic: "系统 topic 不支持 workspace。", 955 + chat_workspace_input_placeholder: "输入本地目录路径", 956 + chat_workspace_action_attach: "绑定", 957 + chat_workspace_action_edit: "修改", 958 + chat_workspace_action_detach: "解绑", 959 + chat_workspace_action_insert: "添加到对话框", 960 + chat_workspace_action_open: "打开", 961 + chat_workspace_action_save: "保存", 962 + chat_workspace_action_cancel: "取消", 963 + chat_workspace_sidebar_open: "打开 workspace 侧栏", 964 + chat_workspace_sidebar_close: "关闭 workspace 侧栏", 965 + chat_workspace_tree_loading: "正在读取目录...", 966 + chat_workspace_tree_empty: "这个目录是空的。", 967 + chat_workspace_empty_title: "还没有绑定 workspace", 968 + chat_workspace_unavailable_title: "当前不能绑定 workspace", 969 + chat_workspace_dialog_title: "绑定 workspace", 970 + chat_workspace_dialog_hint: "这里显示的是运行中 morph 进程所在机器的文件系统目录树,请选一个目录。", 971 + chat_workspace_dialog_selected: "已选目录", 972 + chat_workspace_dialog_selection_empty: "还没有选目录", 973 + chat_workspace_dialog_recent: "最近", 974 + chat_workspace_dialog_recent_empty: "还没有最近使用过的 workspace。", 975 + chat_workspace_dialog_places: "位置", 976 + chat_workspace_dialog_home: "Home", 977 + chat_workspace_dialog_system: "System", 978 + chat_workspace_dialog_loading: "正在读取目录树...", 979 + chat_workspace_dialog_empty: "这里没有可显示的内容。", 980 + chat_workspace_kind_dir: "dir", 981 + chat_workspace_kind_file: "file", 914 982 chat_topic_delete_action: "删除话题", 915 983 chat_topic_delete_confirm: "要删除话题“{title}”吗?删除后该话题任务会从默认视图中隐藏。", 916 984 chat_submit_unsupported: ··· 1505 1573 chat_topic_untitled: "無題のトピック", 1506 1574 chat_topic_system_show: "システムを表示", 1507 1575 chat_topic_system_hide: "システムを非表示", 1576 + chat_workspace_label: "Workspace", 1577 + chat_workspace_empty: "(未設定)", 1578 + chat_workspace_status_loading: "workspace を読み込み中...", 1579 + chat_workspace_hint_empty: "このトピックにはまだ workspace がありません。", 1580 + chat_workspace_hint_needs_topic: "最初のメッセージでトピックを作成してください。", 1581 + chat_workspace_hint_no_topic: "先にトピックを選んでください。", 1582 + chat_workspace_hint_system_topic: "システムトピックでは workspace を使えません。", 1583 + chat_workspace_input_placeholder: "ローカルディレクトリのパスを入力", 1584 + chat_workspace_action_attach: "Attach", 1585 + chat_workspace_action_edit: "Edit", 1586 + chat_workspace_action_detach: "Detach", 1587 + chat_workspace_action_insert: "チャットに追加", 1588 + chat_workspace_action_open: "開く", 1589 + chat_workspace_action_save: "Save", 1590 + chat_workspace_action_cancel: "Cancel", 1591 + chat_workspace_sidebar_open: "workspace サイドバーを開く", 1592 + chat_workspace_sidebar_close: "workspace サイドバーを閉じる", 1593 + chat_workspace_tree_loading: "ファイルを読み込み中...", 1594 + chat_workspace_tree_empty: "このディレクトリは空です。", 1595 + chat_workspace_empty_title: "workspace がありません", 1596 + chat_workspace_unavailable_title: "workspace を設定できません", 1597 + chat_workspace_dialog_title: "workspace を付ける", 1598 + chat_workspace_dialog_hint: "実行中の morph ホスト上のファイルシステムを表示します。ディレクトリを選んでください。", 1599 + chat_workspace_dialog_selected: "選択したディレクトリ", 1600 + chat_workspace_dialog_selection_empty: "ディレクトリが選ばれていません", 1601 + chat_workspace_dialog_recent: "最近", 1602 + chat_workspace_dialog_recent_empty: "最近使った workspace はまだありません。", 1603 + chat_workspace_dialog_places: "場所", 1604 + chat_workspace_dialog_home: "Home", 1605 + chat_workspace_dialog_system: "System", 1606 + chat_workspace_dialog_loading: "ディレクトリを読み込み中...", 1607 + chat_workspace_dialog_empty: "表示できる内容がありません。", 1608 + chat_workspace_kind_dir: "dir", 1609 + chat_workspace_kind_file: "file", 1508 1610 chat_topic_delete_action: "トピックを削除", 1509 1611 chat_topic_delete_confirm: "Delete topic \"{title}\"? Its tasks will be hidden from the default view.", 1510 1612 chat_submit_unsupported:
+646 -8
web/console/src/views/ChatView.css
··· 27 27 28 28 .chat-shell.has-sidebar { 29 29 grid-template-columns: minmax(230px, var(--workspace-sidebar-max-width, 310px)) minmax(0, 1fr); 30 - gap: clamp(18px, 2.4vw, 32px); 30 + gap: clamp(9px, 1.2vw, 16px); 31 + } 32 + 33 + .chat-shell.has-workspace-panel { 34 + grid-template-columns: minmax(0, 1fr) minmax(230px, var(--workspace-sidebar-max-width, 310px)); 35 + gap: clamp(9px, 1.1vw, 14px); 36 + } 37 + 38 + .chat-shell.has-sidebar.has-workspace-panel { 39 + grid-template-columns: 40 + minmax(230px, var(--workspace-sidebar-max-width, 310px)) 41 + minmax(0, 1fr) 42 + minmax(230px, var(--workspace-sidebar-max-width, 310px)); 31 43 } 32 44 33 45 .chat-page-topics .page-bar { ··· 230 242 } 231 243 232 244 .chat-desk-head { 233 - display: flex; 234 - align-items: flex-start; 235 - justify-content: space-between; 236 - gap: 12px; 245 + display: grid; 246 + gap: 14px; 237 247 min-height: 68px; 238 248 padding: clamp(12px, 1.4vw, 20px) var(--chat-history-gutter-x) 0; 239 249 background: ··· 247 257 -webkit-backdrop-filter: blur(6px); 248 258 } 249 259 260 + .chat-desk-head-main { 261 + min-width: 0; 262 + display: flex; 263 + align-items: flex-start; 264 + justify-content: space-between; 265 + gap: 14px; 266 + pointer-events: auto; 267 + } 268 + 250 269 .chat-desk-copy { 251 270 min-width: 0; 252 271 display: grid; ··· 268 287 color: var(--text-2); 269 288 } 270 289 290 + .chat-desk-tools { 291 + flex: 0 0 auto; 292 + display: flex; 293 + align-items: center; 294 + gap: 8px; 295 + } 296 + 297 + .chat-workspace-toggle { 298 + min-width: 34px; 299 + min-height: 34px; 300 + padding: 0 6px !important; 301 + } 302 + 303 + .chat-workspace-toggle.is-active { 304 + background: color-mix(in srgb, var(--accent-1) 10%, var(--q-bg-paper)); 305 + color: var(--q-c-dark); 306 + } 307 + 308 + .chat-workspace-sidebar { 309 + position: sticky; 310 + top: calc(var(--chrome-bar-height) + 14px); 311 + min-height: 0; 312 + justify-self: start; 313 + padding: 0.5rem; 314 + overflow: hidden; 315 + } 316 + 317 + @media (min-width: 921px) { 318 + .page-view-hide-desktop-bar .chat-workspace-sidebar { 319 + top: 0; 320 + } 321 + } 322 + 323 + .chat-workspace-sidebar-shell, 324 + .chat-workspace-sidebar-shell-mobile { 325 + --chat-workspace-track-border: color-mix(in srgb, var(--accent-1) 24%, var(--line)); 326 + --chat-workspace-track-inner-border: color-mix(in srgb, var(--accent-1) 12%, var(--line)); 327 + --chat-workspace-track-blur: 10px; 328 + position: relative; 329 + overflow: hidden; 330 + border: 1px dashed var(--chat-workspace-track-border); 331 + border-radius: 2px; 332 + background: transparent; 333 + box-shadow: none; 334 + isolation: isolate; 335 + backdrop-filter: blur(var(--chat-workspace-track-blur)); 336 + -webkit-backdrop-filter: blur(var(--chat-workspace-track-blur)); 337 + } 338 + 339 + .chat-workspace-sidebar-shell { 340 + min-height: 0; 341 + height: 100%; 342 + width: 100%; 343 + display: grid; 344 + grid-template-rows: auto minmax(0, 1fr); 345 + gap: 10px; 346 + padding: 14px 14px 16px; 347 + } 348 + 349 + .chat-workspace-sidebar-shell-mobile { 350 + padding: 14px 14px 18px; 351 + } 352 + 353 + .chat-workspace-sidebar-shell::before, 354 + .chat-workspace-sidebar-shell-mobile::before { 355 + content: ""; 356 + position: absolute; 357 + inset: 4px; 358 + border: 1px solid var(--chat-workspace-track-inner-border); 359 + pointer-events: none; 360 + z-index: 0; 361 + } 362 + 363 + .chat-workspace-sidebar-shell > *, 364 + .chat-workspace-sidebar-shell-mobile > * { 365 + position: relative; 366 + z-index: 1; 367 + } 368 + 369 + .chat-workspace-tabs { 370 + padding-inline: 2px; 371 + } 372 + 373 + .chat-workspace-tabs .q-tab-button.with-icon:not(.with-label) { 374 + min-width: 42px; 375 + } 376 + 377 + .chat-workspace-pane { 378 + min-height: 0; 379 + width: 100%; 380 + height: 100%; 381 + display: flex; 382 + flex-direction: column; 383 + gap: 12px; 384 + overflow: hidden; 385 + padding: 0; 386 + border: 0; 387 + border-radius: 0; 388 + background: transparent; 389 + box-shadow: none; 390 + } 391 + 392 + .chat-workspace-pane-copy { 393 + flex: 1 1 auto; 394 + min-width: 0; 395 + display: grid; 396 + gap: 4px; 397 + } 398 + 399 + .chat-workspace-pane-fence { 400 + margin: 0; 401 + } 402 + 403 + .chat-workspace-pane-label { 404 + margin: 0; 405 + } 406 + 407 + .chat-workspace-pane-path { 408 + display: flex; 409 + align-items: baseline; 410 + width: 100%; 411 + min-width: 0; 412 + font-family: var(--font-mono); 413 + font-size: 11px; 414 + line-height: 1.5; 415 + color: var(--text-1); 416 + } 417 + 418 + .chat-workspace-pane-path-prefix { 419 + flex: 1 1 auto; 420 + min-width: 0; 421 + overflow: hidden; 422 + text-overflow: ellipsis; 423 + white-space: nowrap; 424 + } 425 + 426 + .chat-workspace-pane-path-separator { 427 + flex: 0 0 auto; 428 + white-space: nowrap; 429 + } 430 + 431 + .chat-workspace-pane-path-tail { 432 + flex: 0 1 auto; 433 + min-width: 0; 434 + overflow: hidden; 435 + text-overflow: ellipsis; 436 + white-space: nowrap; 437 + } 438 + 439 + .chat-workspace-pane-note { 440 + margin: 0; 441 + max-width: 30ch; 442 + font-size: 12px; 443 + line-height: 1.45; 444 + color: var(--text-2); 445 + text-wrap: pretty; 446 + } 447 + 448 + .chat-workspace-tree-shell { 449 + flex: 1 1 auto; 450 + min-height: 0; 451 + display: flex; 452 + flex-direction: column; 453 + overflow: hidden; 454 + border: 0; 455 + border-radius: 0; 456 + background: transparent; 457 + box-shadow: none; 458 + } 459 + 460 + .chat-workspace-browser-shell { 461 + min-height: 0; 462 + display: block; 463 + overflow: hidden; 464 + border: 1px solid color-mix(in srgb, var(--line-soft) 84%, transparent); 465 + border-radius: 2px; 466 + background: color-mix(in srgb, var(--q-bg-paper) 98%, transparent); 467 + box-shadow: none; 468 + } 469 + 470 + .chat-workspace-tree-list { 471 + min-height: 0; 472 + max-height: 100%; 473 + display: grid; 474 + align-content: start; 475 + gap: 2px; 476 + padding: 6px; 477 + overflow-x: hidden; 478 + overflow-y: auto; 479 + scrollbar-width: thin; 480 + scrollbar-gutter: stable; 481 + } 482 + 483 + .chat-workspace-tree-shell .chat-workspace-tree-list { 484 + flex: 1 1 auto; 485 + max-height: none; 486 + padding: 0; 487 + } 488 + 489 + .chat-workspace-tree-list.is-browser { 490 + max-height: min(48vh, 480px); 491 + } 492 + 493 + .chat-workspace-tree-row { 494 + --tree-indent: calc(var(--tree-depth, 0) * 14px); 495 + padding-left: var(--tree-indent); 496 + } 497 + 498 + .chat-workspace-tree-entry { 499 + border: 1px solid transparent; 500 + background: transparent; 501 + } 502 + 503 + .chat-workspace-tree-entry { 504 + width: 100%; 505 + min-width: 0; 506 + display: grid; 507 + grid-template-columns: auto minmax(0, 1fr); 508 + align-items: center; 509 + gap: 8px; 510 + padding: 7px 10px 7px 8px; 511 + border-radius: 2px; 512 + color: var(--text-1); 513 + cursor: default; 514 + text-align: left; 515 + transition: 516 + border-color 180ms ease, 517 + background-color 180ms ease, 518 + color 180ms ease; 519 + } 520 + 521 + .chat-workspace-tree-entry.is-actionable { 522 + cursor: pointer; 523 + } 524 + 525 + .chat-workspace-tree-entry.is-actionable:hover, 526 + .chat-workspace-tree-entry.is-actionable:focus-visible { 527 + border-color: color-mix(in srgb, var(--line-soft) 74%, transparent); 528 + background: var(--q-button-plain-hover-bg); 529 + color: var(--q-button-plain-hover-text); 530 + outline: none; 531 + } 532 + 533 + .chat-workspace-tree-entry:disabled { 534 + opacity: 1; 535 + } 536 + 537 + .chat-workspace-tree-entry.is-selectable:disabled { 538 + cursor: default; 539 + opacity: 0.68; 540 + } 541 + 542 + .chat-workspace-tree-entry.is-selected { 543 + border-color: var(--line); 544 + background: color-mix(in srgb, var(--q-bg-paper) 90%, var(--q-button-primary-border) 10%); 545 + color: var(--q-c-dark); 546 + } 547 + 548 + .chat-workspace-tree-kind { 549 + flex: 0 0 auto; 550 + display: inline-flex; 551 + align-items: center; 552 + justify-content: center; 553 + width: 18px; 554 + min-width: 18px; 555 + height: 18px; 556 + padding: 0; 557 + } 558 + 559 + .chat-workspace-tree-icon { 560 + display: block; 561 + width: 16px; 562 + height: 16px; 563 + object-fit: contain; 564 + user-select: none; 565 + pointer-events: none; 566 + } 567 + 568 + .chat-workspace-tree-name { 569 + min-width: 0; 570 + overflow: hidden; 571 + text-overflow: ellipsis; 572 + white-space: nowrap; 573 + font-size: 12px; 574 + line-height: 1.45; 575 + } 576 + 577 + .chat-workspace-tree-status { 578 + margin: 0; 579 + padding: 14px; 580 + font-size: 12px; 581 + line-height: 1.45; 582 + color: var(--text-2); 583 + } 584 + 585 + .chat-workspace-toolbar { 586 + flex: 0 0 auto; 587 + display: flex; 588 + align-items: flex-start; 589 + justify-content: space-between; 590 + gap: 12px; 591 + min-width: 0; 592 + } 593 + 594 + .chat-workspace-toolbar-actions { 595 + flex: 0 0 auto; 596 + display: flex; 597 + align-items: flex-start; 598 + gap: 6px; 599 + } 600 + 601 + .chat-workspace-empty-state { 602 + flex: 1 1 auto; 603 + min-height: 0; 604 + display: grid; 605 + justify-items: center; 606 + align-content: center; 607 + gap: 12px; 608 + padding: 16px; 609 + border: 1px dashed color-mix(in srgb, var(--line-soft) 84%, transparent); 610 + border-radius: 2px; 611 + background: color-mix(in srgb, var(--q-bg-paper) 98%, transparent); 612 + } 613 + 614 + .chat-workspace-empty-state.is-disabled { 615 + border-color: color-mix(in srgb, var(--line) 68%, transparent); 616 + background: color-mix(in srgb, var(--q-bg-paper) 99%, transparent); 617 + } 618 + 619 + .chat-workspace-empty-lead { 620 + min-width: 0; 621 + display: grid; 622 + justify-items: center; 623 + gap: 5px; 624 + max-width: 28ch; 625 + text-align: center; 626 + } 627 + 628 + .chat-workspace-empty-title { 629 + margin: 0; 630 + font-family: var(--q-card-title-font-family); 631 + max-width: 20ch; 632 + font-size: clamp(0.98rem, 0.94rem + 0.18vw, 1.08rem); 633 + line-height: 1.08; 634 + letter-spacing: -0.02em; 635 + color: color-mix(in srgb, var(--text-0) 82%, var(--text-2)); 636 + } 637 + 638 + .chat-workspace-empty-copy { 639 + margin: 0; 640 + max-width: 28ch; 641 + font-size: 12px; 642 + line-height: 1.45; 643 + color: color-mix(in srgb, var(--text-2) 76%, var(--q-bg-paper)); 644 + text-wrap: pretty; 645 + } 646 + 647 + .chat-workspace-empty-actions { 648 + display: flex; 649 + align-items: center; 650 + justify-content: center; 651 + gap: 8px; 652 + padding-top: 0; 653 + } 654 + 655 + .chat-workspace-empty-actions .q-button.primary.sm { 656 + min-width: 112px; 657 + } 658 + 659 + .chat-workspace-status { 660 + flex: 0 0 auto; 661 + display: grid; 662 + gap: 10px; 663 + padding: 10px 12px 12px; 664 + border-top: 1px solid color-mix(in srgb, var(--line-soft) 82%, transparent); 665 + background: color-mix(in srgb, var(--q-bg-paper) 92%, transparent); 666 + } 667 + 668 + .chat-workspace-status-head { 669 + display: flex; 670 + align-items: baseline; 671 + justify-content: space-between; 672 + gap: 10px; 673 + min-width: 0; 674 + } 675 + 676 + .chat-workspace-status-title { 677 + min-width: 0; 678 + margin: 0; 679 + overflow: hidden; 680 + text-overflow: ellipsis; 681 + white-space: nowrap; 682 + font-size: 13px; 683 + line-height: 1.35; 684 + color: var(--text-0); 685 + } 686 + 687 + .chat-workspace-status-kind { 688 + flex: 0 0 auto; 689 + color: var(--text-2); 690 + } 691 + 692 + .chat-workspace-status-grid { 693 + margin: 0; 694 + display: grid; 695 + gap: 8px; 696 + } 697 + 698 + .chat-workspace-status-row { 699 + min-width: 0; 700 + display: flex; 701 + align-items: center; 702 + justify-content: space-between; 703 + gap: 12px; 704 + margin: 0; 705 + } 706 + 707 + .chat-workspace-status-term { 708 + margin: 0; 709 + font-size: 10px; 710 + line-height: 1.3; 711 + letter-spacing: 0.04em; 712 + text-transform: uppercase; 713 + color: var(--text-2); 714 + } 715 + 716 + .chat-workspace-status-value { 717 + min-width: 0; 718 + margin: 0; 719 + overflow: hidden; 720 + text-overflow: ellipsis; 721 + white-space: nowrap; 722 + text-align: right; 723 + font-size: 12px; 724 + line-height: 1.45; 725 + color: var(--text-1); 726 + } 727 + 728 + .chat-workspace-status-actions { 729 + flex: 0 0 auto; 730 + display: flex; 731 + align-items: center; 732 + justify-content: flex-end; 733 + gap: 6px; 734 + margin: 0; 735 + } 736 + 737 + .chat-workspace-dialog { 738 + min-height: 0; 739 + width: min(720px, 100%); 740 + height: min(68vh, 560px); 741 + display: flex; 742 + flex-direction: column; 743 + gap: 16px; 744 + padding: 12px 16px; 745 + overflow: hidden; 746 + } 747 + 748 + .chat-workspace-dialog-head { 749 + display: grid; 750 + align-content: center; 751 + gap: 4px; 752 + min-height: 44px; 753 + padding: 12px 16px; 754 + } 755 + 756 + .chat-workspace-dialog-title { 757 + margin: 0; 758 + font-size: 16px; 759 + line-height: 1.2; 760 + } 761 + 762 + .chat-workspace-dialog-shell { 763 + flex: 1 1 auto; 764 + min-height: 0; 765 + display: grid; 766 + grid-template-columns: minmax(196px, 220px) minmax(0, 1fr); 767 + grid-template-rows: minmax(0, 1fr) auto; 768 + gap: 14px; 769 + align-items: stretch; 770 + } 771 + 772 + .chat-workspace-dialog-sidebar.workspace-sidebar-section { 773 + grid-column: 1; 774 + grid-row: 1; 775 + width: 100%; 776 + max-width: none; 777 + min-width: 0; 778 + display: grid; 779 + gap: 10px; 780 + align-content: start; 781 + overflow: hidden; 782 + } 783 + 784 + .chat-workspace-dialog-sidebar-group { 785 + display: grid; 786 + gap: 8px; 787 + min-width: 0; 788 + } 789 + 790 + .chat-workspace-dialog-sidebar-title { 791 + margin: 0; 792 + } 793 + 794 + .chat-workspace-dialog-sidebar-list { 795 + min-width: 0; 796 + max-height: none; 797 + overflow: visible; 798 + } 799 + 800 + .chat-workspace-dialog-sidebar-item { 801 + grid-template-columns: minmax(0, 1fr); 802 + gap: 0; 803 + } 804 + 805 + .chat-workspace-dialog-sidebar-empty { 806 + margin: 0; 807 + font-size: 12px; 808 + line-height: 1.45; 809 + color: var(--text-2); 810 + } 811 + 812 + .chat-workspace-dialog-main { 813 + grid-column: 2; 814 + grid-row: 1; 815 + min-width: 0; 816 + min-height: 0; 817 + display: flex; 818 + flex-direction: column; 819 + } 820 + 821 + .chat-workspace-browser-shell { 822 + flex: 1 1 auto; 823 + min-height: 0; 824 + display: flex; 825 + flex-direction: column; 826 + } 827 + 828 + .chat-workspace-browser-shell .chat-workspace-tree-list.is-browser { 829 + flex: 1 1 auto; 830 + max-height: none; 831 + } 832 + 833 + .chat-workspace-dialog-actions { 834 + grid-column: 1 / -1; 835 + grid-row: 2; 836 + display: flex; 837 + justify-content: flex-end; 838 + gap: 8px; 839 + padding: 0; 840 + } 841 + 842 + .q-dialog .q-dialog-body:has(.chat-workspace-dialog) { 843 + min-height: 0; 844 + overflow: hidden; 845 + padding: 0; 846 + } 847 + 848 + .q-dialog .q-dialog-header:has(.chat-workspace-dialog-head) { 849 + padding: 0; 850 + } 851 + 271 852 .chat-placeholder { 272 853 min-height: 0; 273 854 display: grid; ··· 654 1235 655 1236 @media (min-width: 921px) { 656 1237 .chat-main:not(.is-readonly) { 657 - --chat-overlay-head-h: 82px; 1238 + --chat-overlay-head-h: 108px; 658 1239 --chat-overlay-compose-h: 86px; 659 1240 grid-template-rows: minmax(0, 1fr); 660 1241 } ··· 792 1373 width: 100%; 793 1374 } 794 1375 795 - .chat-shell.has-sidebar { 1376 + .chat-shell.has-sidebar, 1377 + .chat-shell.has-workspace-panel, 1378 + .chat-shell.has-sidebar.has-workspace-panel { 796 1379 grid-template-columns: minmax(0, 1fr); 797 1380 gap: 0; 798 1381 } ··· 811 1394 } 812 1395 813 1396 .chat-desk-head { 814 - display: none; 1397 + display: grid; 1398 + gap: 10px; 1399 + min-height: 0; 1400 + padding: 10px var(--chat-history-gutter-x) 0; 1401 + } 1402 + 1403 + .chat-desk-head-main { 1404 + gap: 10px; 1405 + } 1406 + 1407 + .chat-desk-tools { 1408 + align-self: flex-start; 1409 + } 1410 + 1411 + .chat-workspace-sidebar-shell-mobile { 1412 + height: 100%; 1413 + } 1414 + 1415 + .chat-workspace-tree-list.is-browser { 1416 + max-height: min(56vh, 480px); 1417 + } 1418 + 1419 + .chat-workspace-dialog-shell { 1420 + grid-template-columns: minmax(0, 1fr); 1421 + grid-template-rows: auto minmax(0, 1fr) auto; 1422 + } 1423 + 1424 + .chat-workspace-dialog-sidebar.workspace-sidebar-section { 1425 + grid-column: 1; 1426 + grid-row: 1; 1427 + gap: 12px; 1428 + } 1429 + 1430 + .chat-workspace-dialog-main { 1431 + grid-column: 1; 1432 + grid-row: 2; 1433 + } 1434 + 1435 + .chat-workspace-dialog-sidebar-list.workspace-sidebar-list { 1436 + display: flex; 1437 + flex-wrap: wrap; 1438 + gap: 8px; 1439 + max-height: none; 1440 + overflow: visible; 1441 + padding-bottom: 0; 1442 + } 1443 + 1444 + .chat-workspace-dialog-sidebar-item { 1445 + flex: 0 0 auto; 1446 + min-width: 148px; 1447 + max-width: min(70vw, 240px); 1448 + } 1449 + 1450 + .chat-workspace-dialog-actions { 1451 + grid-column: 1; 1452 + grid-row: 3; 815 1453 } 816 1454 817 1455 .chat-placeholder {
+1366 -8
web/console/src/views/ChatView.js
··· 7 7 import MarkdownContent from "../components/MarkdownContent"; 8 8 import RawJsonDialog from "../components/RawJsonDialog"; 9 9 import { endpointChannelLabel } from "../core/endpoints"; 10 + import { workspaceTreeIcon } from "../core/workspace-icons"; 10 11 import { 11 12 buildConsoleStreamURL, 12 13 createConsoleStreamTicket, 13 14 currentLocale, 14 15 endpointState, 16 + formatBytes, 15 17 runtimeApiFetchForEndpoint, 16 18 runtimeEndpointByRef, 17 19 safeJSON, ··· 22 24 const COMPOSER_MAX_ROWS = 5; 23 25 const CHAT_HISTORY_LIMIT = 100; 24 26 const HEARTBEAT_TOPIC_ID = "_heartbeat"; 27 + const RECENT_WORKSPACE_DIRS_STORAGE_KEY = "mistermorph_console_recent_workspaces_v1"; 28 + const WORKSPACE_SIDEBAR_OPEN_STORAGE_KEY = "mistermorph_console_workspace_sidebar_open_v1"; 29 + const RECENT_WORKSPACE_DIRS_LIMIT = 32; 30 + const WORKSPACE_BROWSER_SOURCE_RECENT = "recent"; 31 + const WORKSPACE_BROWSER_SOURCE_HOME = "home"; 32 + const WORKSPACE_BROWSER_SOURCE_SYSTEM = "system"; 25 33 const POLLING_ACTION_KEYS = [ 26 34 "chat_polling_action_ponder", 27 35 "chat_polling_action_think", ··· 54 62 return String(raw || "").trim(); 55 63 } 56 64 65 + function isWorkspaceCommandText(raw) { 66 + return String(raw || "").trim().toLowerCase().startsWith("/workspace"); 67 + } 68 + 57 69 function isTerminalStatus(status) { 58 70 return status === "done" || status === "failed" || status === "canceled"; 59 71 } 60 72 73 + function hasOwnTreePath(map, path) { 74 + return Boolean(map) && Object.prototype.hasOwnProperty.call(map, path); 75 + } 76 + 77 + function normalizeTreeItems(raw) { 78 + if (!Array.isArray(raw)) { 79 + return []; 80 + } 81 + return raw 82 + .map((item) => ({ 83 + name: String(item?.name || "").trim(), 84 + path: String(item?.path || "").trim(), 85 + is_dir: item?.is_dir === true, 86 + has_children: item?.has_children === true, 87 + size_bytes: Number.isFinite(Number(item?.size_bytes)) ? Math.trunc(Number(item.size_bytes)) : -1, 88 + })) 89 + .filter((item) => item.name && item.path); 90 + } 91 + 92 + function buildTreeRows(itemsByPath, expandedByPath, parentPath = "", depth = 0) { 93 + const items = Array.isArray(itemsByPath?.[parentPath]) ? itemsByPath[parentPath] : []; 94 + const rows = []; 95 + for (const entry of items) { 96 + const entryPath = String(entry?.path || "").trim(); 97 + const hasLoadedChildren = hasOwnTreePath(itemsByPath, entryPath); 98 + const hasVisibleChildren = hasLoadedChildren && Array.isArray(itemsByPath?.[entryPath]) && itemsByPath[entryPath].length > 0; 99 + const expandable = Boolean(entry?.is_dir) && (entry?.has_children || hasVisibleChildren); 100 + const expanded = expandable && expandedByPath?.[entryPath] === true; 101 + rows.push({ 102 + key: `${parentPath}:${entryPath}`, 103 + depth, 104 + entry, 105 + expandable, 106 + expanded, 107 + }); 108 + if (expandable && expanded && hasLoadedChildren) { 109 + rows.push(...buildTreeRows(itemsByPath, expandedByPath, entryPath, depth + 1)); 110 + } 111 + } 112 + return rows; 113 + } 114 + 115 + const WORKSPACE_TAB_ID = "workspace"; 116 + 117 + function normalizeRecentWorkspaceDirs(raw) { 118 + if (!Array.isArray(raw)) { 119 + return []; 120 + } 121 + const seen = new Set(); 122 + const items = []; 123 + for (const item of raw) { 124 + const path = String(item || "").trim(); 125 + if (!path || seen.has(path)) { 126 + continue; 127 + } 128 + seen.add(path); 129 + items.push(path); 130 + if (items.length >= RECENT_WORKSPACE_DIRS_LIMIT) { 131 + break; 132 + } 133 + } 134 + return items; 135 + } 136 + 137 + function loadRecentWorkspaceDirs() { 138 + if (typeof localStorage === "undefined") { 139 + return []; 140 + } 141 + try { 142 + const raw = localStorage.getItem(RECENT_WORKSPACE_DIRS_STORAGE_KEY); 143 + if (!raw) { 144 + return []; 145 + } 146 + return normalizeRecentWorkspaceDirs(JSON.parse(raw)); 147 + } catch { 148 + return []; 149 + } 150 + } 151 + 152 + function saveRecentWorkspaceDirs(items) { 153 + if (typeof localStorage === "undefined") { 154 + return; 155 + } 156 + localStorage.setItem( 157 + RECENT_WORKSPACE_DIRS_STORAGE_KEY, 158 + JSON.stringify(normalizeRecentWorkspaceDirs(items)) 159 + ); 160 + } 161 + 162 + function rememberRecentWorkspaceDir(items, dir) { 163 + const path = String(dir || "").trim(); 164 + if (!path) { 165 + return normalizeRecentWorkspaceDirs(items); 166 + } 167 + return normalizeRecentWorkspaceDirs([path, ...(Array.isArray(items) ? items : [])]); 168 + } 169 + 170 + function loadWorkspaceSidebarOpen() { 171 + if (typeof localStorage === "undefined") { 172 + return false; 173 + } 174 + try { 175 + return localStorage.getItem(WORKSPACE_SIDEBAR_OPEN_STORAGE_KEY) === "true"; 176 + } catch { 177 + return false; 178 + } 179 + } 180 + 181 + function saveWorkspaceSidebarOpen(open) { 182 + if (typeof localStorage === "undefined") { 183 + return; 184 + } 185 + localStorage.setItem( 186 + WORKSPACE_SIDEBAR_OPEN_STORAGE_KEY, 187 + open ? "true" : "false" 188 + ); 189 + } 190 + 191 + function workspaceBrowserSource(sourceID) { 192 + const value = String(sourceID || "").trim(); 193 + if (value === WORKSPACE_BROWSER_SOURCE_RECENT) { 194 + return { 195 + id: WORKSPACE_BROWSER_SOURCE_RECENT, 196 + kind: "recent", 197 + path: "", 198 + selection: "", 199 + }; 200 + } 201 + if (value === WORKSPACE_BROWSER_SOURCE_SYSTEM) { 202 + return { 203 + id: WORKSPACE_BROWSER_SOURCE_SYSTEM, 204 + kind: "system", 205 + path: "", 206 + selection: "", 207 + }; 208 + } 209 + return { 210 + id: WORKSPACE_BROWSER_SOURCE_HOME, 211 + kind: "home", 212 + path: "~", 213 + selection: "", 214 + }; 215 + } 216 + 217 + function browserPathLabel(path) { 218 + const value = String(path || "").trim(); 219 + if (!value) { 220 + return ""; 221 + } 222 + const normalized = value.replace(/[\\/]+$/u, ""); 223 + if (!normalized) { 224 + return value; 225 + } 226 + const parts = normalized.split(/[\\/]/u).filter(Boolean); 227 + return parts.length > 0 ? parts[parts.length - 1] : value; 228 + } 229 + 230 + function splitWorkspaceDisplayPath(path) { 231 + const value = String(path || "").trim(); 232 + if (!value) { 233 + return { 234 + prefix: "", 235 + separator: "", 236 + tail: "", 237 + }; 238 + } 239 + if (/^[\\/]+$/u.test(value) || /^[A-Za-z]:[\\/]?$/u.test(value)) { 240 + return { 241 + prefix: "", 242 + separator: "", 243 + tail: value, 244 + }; 245 + } 246 + const normalized = value.replace(/[\\/]+$/u, ""); 247 + if (!normalized) { 248 + return { 249 + prefix: "", 250 + separator: "", 251 + tail: value, 252 + }; 253 + } 254 + const slashIndex = normalized.lastIndexOf("/"); 255 + const backslashIndex = normalized.lastIndexOf("\\"); 256 + const separatorIndex = Math.max(slashIndex, backslashIndex); 257 + if (separatorIndex < 0) { 258 + return { 259 + prefix: "", 260 + separator: "", 261 + tail: normalized, 262 + }; 263 + } 264 + const separator = normalized.charAt(separatorIndex); 265 + const prefix = normalized.slice(0, separatorIndex); 266 + const tail = normalized.slice(separatorIndex + 1); 267 + if (!tail) { 268 + return { 269 + prefix: "", 270 + separator: "", 271 + tail: value, 272 + }; 273 + } 274 + return { 275 + prefix, 276 + separator, 277 + tail, 278 + }; 279 + } 280 + 61 281 function stringifyResult(result) { 62 282 if (typeof result === "string") { 63 283 return result.trim(); ··· 293 513 const taskInput = ref(""); 294 514 const sending = ref(false); 295 515 const err = ref(""); 516 + const workspaceDir = ref(""); 517 + const workspaceLoading = ref(false); 518 + const workspaceSaving = ref(false); 519 + const workspaceOpening = ref(false); 520 + const workspaceError = ref(""); 521 + const workspaceSidebarOpen = ref(loadWorkspaceSidebarOpen()); 522 + const workspaceSidebarTabID = ref(WORKSPACE_TAB_ID); 523 + const workspaceTreeItems = ref({}); 524 + const workspaceTreeExpanded = ref({ "": true }); 525 + const workspaceTreeLoading = ref(false); 526 + const workspaceTreeLoadingPath = ref(""); 527 + const workspaceTreeError = ref(""); 528 + const workspaceTreeSelectionPath = ref(""); 529 + const workspaceBrowserOpen = ref(false); 530 + const workspaceBrowserItems = ref({}); 531 + const workspaceBrowserExpanded = ref({ "": true }); 532 + const workspaceBrowserLoading = ref(false); 533 + const workspaceBrowserLoadingPath = ref(""); 534 + const workspaceBrowserError = ref(""); 535 + const workspaceBrowserSourceID = ref(WORKSPACE_BROWSER_SOURCE_HOME); 536 + const workspaceBrowserRecentDirs = ref(loadRecentWorkspaceDirs()); 537 + const workspaceBrowserSelection = ref(""); 296 538 const pollTimers = new Set(); 297 539 const streamSockets = new Map(); 298 540 const composerField = ref(null); ··· 477 719 } 478 720 return mobileTopicView.value === "chat"; 479 721 }); 722 + const desktopWorkspaceSidebarVisible = computed( 723 + () => workspaceSidebarAvailable.value && !mobileMode.value && showChatPane.value && workspaceSidebarOpen.value 724 + ); 480 725 const shellClass = computed(() => { 481 - if (!consoleTopicsEnabled.value || !hasVisibleTopics.value) { 482 - return "chat-shell"; 726 + const classes = ["chat-shell"]; 727 + if (consoleTopicsEnabled.value && hasVisibleTopics.value && !mobileTopicSplitEnabled.value) { 728 + classes.push("has-sidebar"); 483 729 } 484 - if (!mobileTopicSplitEnabled.value) { 485 - return "chat-shell has-sidebar"; 730 + if (desktopWorkspaceSidebarVisible.value) { 731 + classes.push("has-workspace-panel"); 486 732 } 487 - return mobileTopicView.value === "topics" ? "chat-shell is-mobile-topics" : "chat-shell is-mobile-chat"; 733 + if (mobileTopicSplitEnabled.value) { 734 + classes.push(mobileTopicView.value === "topics" ? "is-mobile-topics" : "is-mobile-chat"); 735 + } 736 + return classes.join(" "); 488 737 }); 489 738 const chatMainClass = computed(() => { 490 739 const classes = ["chat-main"]; ··· 520 769 } 521 770 return parts.join(" · "); 522 771 }); 772 + const workspaceTopicID = computed(() => { 773 + if (!consoleTopicsEnabled.value || creatingTopic.value) { 774 + return ""; 775 + } 776 + const topicID = normalizeTopicID(selectedTopicID.value); 777 + if (!topicID || topicID === HEARTBEAT_TOPIC_ID) { 778 + return ""; 779 + } 780 + return topicID; 781 + }); 782 + const workspaceSidebarAvailable = computed(() => Boolean(workspaceTopicID.value)); 783 + const workspaceReady = computed(() => Boolean(submitEndpointRef.value && workspaceTopicID.value)); 784 + const workspaceBusy = computed(() => workspaceLoading.value || workspaceSaving.value); 785 + const workspaceHintText = computed(() => { 786 + if (workspaceTopicID.value) { 787 + return String(workspaceDir.value || "").trim() ? "" : t("chat_workspace_hint_empty"); 788 + } 789 + if (creatingTopic.value) { 790 + return t("chat_workspace_hint_needs_topic"); 791 + } 792 + if (normalizeTopicID(selectedTopicID.value) === HEARTBEAT_TOPIC_ID) { 793 + return t("chat_workspace_hint_system_topic"); 794 + } 795 + return t("chat_workspace_hint_no_topic"); 796 + }); 797 + const workspaceAttachDisabled = computed(() => !workspaceReady.value || workspaceBusy.value); 798 + const workspaceDetachDisabled = computed( 799 + () => !workspaceReady.value || workspaceBusy.value || String(workspaceDir.value || "").trim() === "" 800 + ); 801 + const workspaceDirDisplay = computed(() => splitWorkspaceDisplayPath(workspaceDir.value)); 802 + const workspacePanelTabs = computed(() => [ 803 + { 804 + id: WORKSPACE_TAB_ID, 805 + title: "", 806 + icon: "QIconEcosystem", 807 + }, 808 + ]); 809 + const selectedWorkspacePanelTab = computed( 810 + () => workspacePanelTabs.value.find((item) => item.id === workspaceSidebarTabID.value) || workspacePanelTabs.value[0] 811 + ); 812 + const workspaceTreeRows = computed(() => 813 + buildTreeRows(workspaceTreeItems.value, workspaceTreeExpanded.value) 814 + ); 815 + const workspaceSelectedTreeEntry = computed(() => { 816 + const selectedPath = String(workspaceTreeSelectionPath.value || "").trim(); 817 + if (!selectedPath) { 818 + return null; 819 + } 820 + const row = workspaceTreeRows.value.find( 821 + (item) => String(item?.entry?.path || "").trim() === selectedPath 822 + ); 823 + return row?.entry || null; 824 + }); 825 + const workspaceBrowserRecentItems = computed(() => 826 + workspaceBrowserRecentDirs.value.map((path) => ({ 827 + path, 828 + title: browserPathLabel(path), 829 + meta: path, 830 + })) 831 + ); 832 + const workspaceBrowserCurrentSource = computed(() => 833 + workspaceBrowserSource(workspaceBrowserSourceID.value) 834 + ); 835 + const workspaceBrowserRows = computed(() => { 836 + if (workspaceBrowserCurrentSource.value.kind === "recent") { 837 + return workspaceBrowserRecentItems.value.map((item) => ({ 838 + key: `recent:${item.path}`, 839 + depth: 0, 840 + entry: { 841 + name: item.title, 842 + path: item.path, 843 + is_dir: true, 844 + has_children: false, 845 + }, 846 + expandable: false, 847 + expanded: false, 848 + })); 849 + } 850 + return buildTreeRows( 851 + workspaceBrowserItems.value, 852 + workspaceBrowserExpanded.value, 853 + workspaceBrowserCurrentSource.value.path 854 + ); 855 + }); 856 + const workspaceBrowserConfirmDisabled = computed( 857 + () => !workspaceReady.value || workspaceSaving.value || String(workspaceBrowserSelection.value || "").trim() === "" 858 + ); 859 + const workspaceSidebarToggleLabel = computed(() => 860 + workspaceSidebarOpen.value ? t("chat_workspace_sidebar_close") : t("chat_workspace_sidebar_open") 861 + ); 862 + const workspaceBrowserEmptyText = computed(() => 863 + workspaceBrowserCurrentSource.value.kind === "recent" 864 + ? t("chat_workspace_dialog_recent_empty") 865 + : t("chat_workspace_dialog_empty") 866 + ); 523 867 const chatPlaceholderHint = computed(() => { 524 868 if (visibleTopics.value.length > 0) { 525 869 return t("chat_placeholder_choose_topic"); 526 870 } 527 871 return chatPlaceholderText.value; 528 872 }); 873 + let workspaceRequestSeq = 0; 529 874 530 875 function syncMobileTopicView(options = {}) { 531 876 if (!mobileTopicSplitEnabled.value) { ··· 598 943 }); 599 944 } 600 945 946 + function insertComposerText(rawText) { 947 + const insertText = String(rawText || ""); 948 + if (!insertText) { 949 + return; 950 + } 951 + const current = String(taskInput.value || ""); 952 + const textarea = composerTextarea(); 953 + const active = typeof document !== "undefined" ? document.activeElement : null; 954 + let start = current.length; 955 + let end = current.length; 956 + if ( 957 + textarea && 958 + active === textarea && 959 + typeof textarea.selectionStart === "number" && 960 + typeof textarea.selectionEnd === "number" 961 + ) { 962 + start = textarea.selectionStart; 963 + end = textarea.selectionEnd; 964 + } 965 + taskInput.value = `${current.slice(0, start)}${insertText}${current.slice(end)}`; 966 + void nextTick(() => { 967 + const field = composerTextarea(); 968 + if (!field || field.disabled) { 969 + return; 970 + } 971 + const nextOffset = start + insertText.length; 972 + field.focus({ preventScroll: true }); 973 + field.setSelectionRange(nextOffset, nextOffset); 974 + }); 975 + } 976 + 977 + function setTreeItems(target, path, items) { 978 + target.value = { 979 + ...target.value, 980 + [path]: normalizeTreeItems(items), 981 + }; 982 + } 983 + 984 + function setTreeExpanded(target, path, expanded) { 985 + const nextValue = { ...target.value }; 986 + if (expanded) { 987 + nextValue[path] = true; 988 + } else { 989 + delete nextValue[path]; 990 + } 991 + target.value = nextValue; 992 + } 993 + 994 + function resetWorkspaceTreeState() { 995 + workspaceTreeItems.value = {}; 996 + workspaceTreeExpanded.value = { "": true }; 997 + workspaceTreeLoading.value = false; 998 + workspaceTreeLoadingPath.value = ""; 999 + workspaceTreeError.value = ""; 1000 + workspaceTreeSelectionPath.value = ""; 1001 + } 1002 + 1003 + function resetWorkspaceBrowserState() { 1004 + workspaceBrowserItems.value = {}; 1005 + workspaceBrowserExpanded.value = { "": true }; 1006 + workspaceBrowserLoading.value = false; 1007 + workspaceBrowserLoadingPath.value = ""; 1008 + workspaceBrowserError.value = ""; 1009 + workspaceBrowserSelection.value = ""; 1010 + } 1011 + 1012 + function saveWorkspaceBrowserRecentDirs(items) { 1013 + const nextItems = normalizeRecentWorkspaceDirs(items); 1014 + workspaceBrowserRecentDirs.value = nextItems; 1015 + saveRecentWorkspaceDirs(nextItems); 1016 + } 1017 + 1018 + function rememberWorkspaceBrowserRecentDir(dir) { 1019 + saveWorkspaceBrowserRecentDirs( 1020 + rememberRecentWorkspaceDir(workspaceBrowserRecentDirs.value, dir) 1021 + ); 1022 + } 1023 + 1024 + function resetWorkspaceState() { 1025 + workspaceRequestSeq += 1; 1026 + workspaceDir.value = ""; 1027 + workspaceLoading.value = false; 1028 + workspaceSaving.value = false; 1029 + workspaceOpening.value = false; 1030 + workspaceError.value = ""; 1031 + workspaceBrowserOpen.value = false; 1032 + workspaceSidebarTabID.value = WORKSPACE_TAB_ID; 1033 + resetWorkspaceTreeState(); 1034 + resetWorkspaceBrowserState(); 1035 + } 1036 + 1037 + function applyWorkspacePayload(data) { 1038 + const nextDir = String(data?.workspace_dir || "").trim(); 1039 + workspaceDir.value = nextDir; 1040 + workspaceError.value = ""; 1041 + resetWorkspaceTreeState(); 1042 + resetWorkspaceBrowserState(); 1043 + if (nextDir) { 1044 + workspaceBrowserSelection.value = nextDir; 1045 + } 1046 + } 1047 + 1048 + async function refreshWorkspaceState() { 1049 + const endpointRef = String(submitEndpointRef.value || "").trim(); 1050 + const topicID = String(workspaceTopicID.value || "").trim(); 1051 + const requestID = ++workspaceRequestSeq; 1052 + 1053 + if (!endpointRef || !topicID) { 1054 + resetWorkspaceState(); 1055 + return true; 1056 + } 1057 + 1058 + workspaceLoading.value = true; 1059 + workspaceError.value = ""; 1060 + try { 1061 + const data = await runtimeApiFetchForEndpoint( 1062 + endpointRef, 1063 + `/workspace?topic_id=${encodeURIComponent(topicID)}` 1064 + ); 1065 + if (requestID !== workspaceRequestSeq) { 1066 + return false; 1067 + } 1068 + applyWorkspacePayload(data); 1069 + if (workspaceSidebarOpen.value && String(workspaceDir.value || "").trim()) { 1070 + await loadWorkspaceTree("", { force: true }); 1071 + } 1072 + return true; 1073 + } catch (e) { 1074 + if (requestID !== workspaceRequestSeq) { 1075 + return false; 1076 + } 1077 + workspaceDir.value = ""; 1078 + resetWorkspaceTreeState(); 1079 + workspaceError.value = e?.message || t("msg_load_failed"); 1080 + return false; 1081 + } finally { 1082 + if (requestID === workspaceRequestSeq) { 1083 + workspaceLoading.value = false; 1084 + } 1085 + } 1086 + } 1087 + 1088 + function toggleWorkspaceSidebar() { 1089 + if (!workspaceSidebarAvailable.value) { 1090 + return; 1091 + } 1092 + workspaceSidebarOpen.value = !workspaceSidebarOpen.value; 1093 + if (workspaceSidebarOpen.value) { 1094 + workspaceSidebarTabID.value = WORKSPACE_TAB_ID; 1095 + if (String(workspaceDir.value || "").trim() && !hasOwnTreePath(workspaceTreeItems.value, "")) { 1096 + void loadWorkspaceTree("", { force: true }); 1097 + } 1098 + } 1099 + } 1100 + 1101 + function onWorkspaceTabChange(detail) { 1102 + const nextID = String(detail?.tab?.id || "").trim(); 1103 + workspaceSidebarTabID.value = nextID || WORKSPACE_TAB_ID; 1104 + } 1105 + 1106 + function workspaceBrowserSourceItemClass(sourceID) { 1107 + const classes = ["workspace-sidebar-item", "chat-workspace-dialog-sidebar-item"]; 1108 + if (String(sourceID || "").trim() === workspaceBrowserSourceID.value) { 1109 + classes.push("is-active"); 1110 + } 1111 + return classes.join(" "); 1112 + } 1113 + 1114 + async function loadWorkspaceTree(treePath = "", options = {}) { 1115 + const endpointRef = String(submitEndpointRef.value || "").trim(); 1116 + const topicID = String(workspaceTopicID.value || "").trim(); 1117 + const currentDir = String(workspaceDir.value || "").trim(); 1118 + const path = String(treePath || "").trim(); 1119 + if (!endpointRef || !topicID || !currentDir) { 1120 + resetWorkspaceTreeState(); 1121 + return false; 1122 + } 1123 + if (!path && options.force === true) { 1124 + resetWorkspaceTreeState(); 1125 + } 1126 + workspaceTreeLoading.value = true; 1127 + workspaceTreeLoadingPath.value = path; 1128 + try { 1129 + const query = new URLSearchParams(); 1130 + query.set("topic_id", topicID); 1131 + if (path) { 1132 + query.set("path", path); 1133 + } 1134 + const data = await runtimeApiFetchForEndpoint( 1135 + endpointRef, 1136 + `/workspace/tree?${query.toString()}` 1137 + ); 1138 + setTreeItems(workspaceTreeItems, path, data?.items); 1139 + if (path) { 1140 + setTreeExpanded(workspaceTreeExpanded, path, true); 1141 + } 1142 + workspaceTreeError.value = ""; 1143 + return true; 1144 + } catch (e) { 1145 + workspaceTreeError.value = e?.message || t("msg_load_failed"); 1146 + return false; 1147 + } finally { 1148 + if (workspaceTreeLoadingPath.value === path) { 1149 + workspaceTreeLoading.value = false; 1150 + workspaceTreeLoadingPath.value = ""; 1151 + } 1152 + } 1153 + } 1154 + 1155 + async function toggleWorkspaceTreeNode(entry) { 1156 + const path = String(entry?.path || "").trim(); 1157 + if (!entry?.is_dir || !path) { 1158 + return; 1159 + } 1160 + if (workspaceTreeExpanded.value[path]) { 1161 + setTreeExpanded(workspaceTreeExpanded, path, false); 1162 + return; 1163 + } 1164 + if (!hasOwnTreePath(workspaceTreeItems.value, path)) { 1165 + const ok = await loadWorkspaceTree(path); 1166 + if (!ok) { 1167 + return; 1168 + } 1169 + } 1170 + setTreeExpanded(workspaceTreeExpanded, path, true); 1171 + } 1172 + 1173 + function workspaceTreeEntryClass(row) { 1174 + const classes = ["chat-workspace-tree-entry", "is-actionable", "is-selectable"]; 1175 + if (row?.entry?.is_dir) { 1176 + classes.push("is-dir"); 1177 + } 1178 + if (String(row?.entry?.path || "").trim() === String(workspaceTreeSelectionPath.value || "").trim()) { 1179 + classes.push("is-selected"); 1180 + } 1181 + return classes.join(" "); 1182 + } 1183 + 1184 + async function selectWorkspaceTreeNode(row) { 1185 + const entry = row?.entry || row; 1186 + const path = String(entry?.path || "").trim(); 1187 + if (!path) { 1188 + return; 1189 + } 1190 + workspaceTreeSelectionPath.value = path; 1191 + if (row?.expandable) { 1192 + await toggleWorkspaceTreeNode(entry); 1193 + } 1194 + } 1195 + 1196 + function addWorkspaceSelectionToComposer() { 1197 + if (composerDisabled.value) { 1198 + return; 1199 + } 1200 + const path = String(workspaceSelectedTreeEntry.value?.path || "").trim(); 1201 + if (!path) { 1202 + return; 1203 + } 1204 + insertComposerText(path); 1205 + } 1206 + 1207 + async function openWorkspaceSelection() { 1208 + const endpointRef = String(submitEndpointRef.value || "").trim(); 1209 + const topicID = String(workspaceTopicID.value || "").trim(); 1210 + const path = String(workspaceSelectedTreeEntry.value?.path || "").trim(); 1211 + if (!endpointRef || !topicID || !path || workspaceOpening.value) { 1212 + return; 1213 + } 1214 + workspaceOpening.value = true; 1215 + workspaceError.value = ""; 1216 + try { 1217 + await runtimeApiFetchForEndpoint(endpointRef, "/workspace/open", { 1218 + method: "POST", 1219 + body: { 1220 + topic_id: topicID, 1221 + path, 1222 + }, 1223 + }); 1224 + } catch (e) { 1225 + workspaceError.value = e?.message || t("msg_load_failed"); 1226 + } finally { 1227 + workspaceOpening.value = false; 1228 + } 1229 + } 1230 + 1231 + async function openWorkspaceBrowser() { 1232 + if (workspaceAttachDisabled.value) { 1233 + return; 1234 + } 1235 + workspaceBrowserOpen.value = true; 1236 + workspaceBrowserError.value = ""; 1237 + await activateWorkspaceBrowserSource(WORKSPACE_BROWSER_SOURCE_HOME); 1238 + } 1239 + 1240 + function closeWorkspaceBrowser() { 1241 + workspaceBrowserOpen.value = false; 1242 + workspaceBrowserError.value = ""; 1243 + } 1244 + 1245 + async function activateWorkspaceBrowserSource(sourceID) { 1246 + const source = workspaceBrowserSource(sourceID); 1247 + workspaceBrowserSourceID.value = source.id; 1248 + resetWorkspaceBrowserState(); 1249 + if (source.kind === "recent") { 1250 + workspaceBrowserError.value = ""; 1251 + return true; 1252 + } 1253 + const ok = await loadWorkspaceBrowser(source.path); 1254 + if (ok) { 1255 + workspaceBrowserSelection.value = source.selection; 1256 + } 1257 + return ok; 1258 + } 1259 + 1260 + async function loadWorkspaceBrowser(treePath = "") { 1261 + const endpointRef = String(submitEndpointRef.value || "").trim(); 1262 + const path = String(treePath || "").trim(); 1263 + if (!endpointRef) { 1264 + resetWorkspaceBrowserState(); 1265 + return false; 1266 + } 1267 + workspaceBrowserLoading.value = true; 1268 + workspaceBrowserLoadingPath.value = path; 1269 + try { 1270 + const query = new URLSearchParams(); 1271 + if (path) { 1272 + query.set("path", path); 1273 + } 1274 + const data = await runtimeApiFetchForEndpoint( 1275 + endpointRef, 1276 + query.toString() ? `/workspace/browse?${query.toString()}` : "/workspace/browse" 1277 + ); 1278 + setTreeItems(workspaceBrowserItems, path, data?.items); 1279 + if (path) { 1280 + setTreeExpanded(workspaceBrowserExpanded, path, true); 1281 + } 1282 + workspaceBrowserError.value = ""; 1283 + return true; 1284 + } catch (e) { 1285 + workspaceBrowserError.value = e?.message || t("msg_load_failed"); 1286 + return false; 1287 + } finally { 1288 + if (workspaceBrowserLoadingPath.value === path) { 1289 + workspaceBrowserLoading.value = false; 1290 + workspaceBrowserLoadingPath.value = ""; 1291 + } 1292 + } 1293 + } 1294 + 1295 + async function toggleWorkspaceBrowserNode(entry) { 1296 + const path = String(entry?.path || "").trim(); 1297 + if (!entry?.is_dir || !path) { 1298 + return; 1299 + } 1300 + if (workspaceBrowserExpanded.value[path]) { 1301 + setTreeExpanded(workspaceBrowserExpanded, path, false); 1302 + return; 1303 + } 1304 + if (!hasOwnTreePath(workspaceBrowserItems.value, path)) { 1305 + const ok = await loadWorkspaceBrowser(path); 1306 + if (!ok) { 1307 + return; 1308 + } 1309 + } 1310 + setTreeExpanded(workspaceBrowserExpanded, path, true); 1311 + } 1312 + 1313 + async function selectWorkspaceBrowserNode(row) { 1314 + const entry = row?.entry || row; 1315 + if (!entry?.is_dir) { 1316 + return; 1317 + } 1318 + workspaceBrowserSelection.value = String(entry.path || "").trim(); 1319 + if (!row?.expandable || workspaceBrowserCurrentSource.value.kind === "recent") { 1320 + return; 1321 + } 1322 + await toggleWorkspaceBrowserNode(entry); 1323 + } 1324 + 1325 + async function attachWorkspace() { 1326 + const endpointRef = String(submitEndpointRef.value || "").trim(); 1327 + const topicID = String(workspaceTopicID.value || "").trim(); 1328 + const nextDir = String(workspaceBrowserSelection.value || "").trim(); 1329 + if (!endpointRef || !topicID || !nextDir || workspaceSaving.value) { 1330 + return; 1331 + } 1332 + workspaceSaving.value = true; 1333 + workspaceError.value = ""; 1334 + workspaceBrowserError.value = ""; 1335 + try { 1336 + const data = await runtimeApiFetchForEndpoint(endpointRef, "/workspace", { 1337 + method: "PUT", 1338 + body: { 1339 + topic_id: topicID, 1340 + workspace_dir: nextDir, 1341 + } 1342 + }); 1343 + rememberWorkspaceBrowserRecentDir(String(data?.workspace_dir || nextDir || "").trim()); 1344 + applyWorkspacePayload(data); 1345 + workspaceBrowserOpen.value = false; 1346 + if (workspaceSidebarOpen.value) { 1347 + await loadWorkspaceTree("", { force: true }); 1348 + } 1349 + } catch (e) { 1350 + const message = e?.message || t("msg_save_failed"); 1351 + workspaceError.value = message; 1352 + workspaceBrowserError.value = message; 1353 + } finally { 1354 + workspaceSaving.value = false; 1355 + } 1356 + } 1357 + 1358 + async function detachWorkspace() { 1359 + const endpointRef = String(submitEndpointRef.value || "").trim(); 1360 + const topicID = String(workspaceTopicID.value || "").trim(); 1361 + if (!endpointRef || !topicID || workspaceDetachDisabled.value) { 1362 + return; 1363 + } 1364 + workspaceSaving.value = true; 1365 + workspaceError.value = ""; 1366 + try { 1367 + const data = await runtimeApiFetchForEndpoint( 1368 + endpointRef, 1369 + `/workspace?topic_id=${encodeURIComponent(topicID)}`, 1370 + { 1371 + method: "DELETE", 1372 + } 1373 + ); 1374 + applyWorkspacePayload(data); 1375 + } catch (e) { 1376 + workspaceError.value = e?.message || t("msg_save_failed"); 1377 + } finally { 1378 + workspaceSaving.value = false; 1379 + } 1380 + } 1381 + 601 1382 function chatRoutePath(topicID = "") { 602 1383 const normalized = normalizeTopicID(topicID); 603 1384 return normalized ? `/chat/${encodeURIComponent(normalized)}` : "/chat"; ··· 1023 1804 }); 1024 1805 if (isTerminalStatus(status)) { 1025 1806 closeTaskStream(taskID); 1807 + if (consoleTopicsEnabled.value && isWorkspaceCommandText(detail?.task)) { 1808 + void refreshWorkspaceState(); 1809 + } 1026 1810 scrollHistoryToBottom(); 1027 1811 } 1028 1812 if (!isTerminalStatus(status)) { ··· 1045 1829 selectedTopicID.value = ""; 1046 1830 creatingTopic.value = false; 1047 1831 showSystemTopics.value = false; 1832 + resetWorkspaceState(); 1048 1833 syncMobileTopicView({ preferTopics: true }); 1049 1834 } 1050 1835 ··· 1441 2226 } 1442 2227 ); 1443 2228 watch( 2229 + () => [submitEndpointRef.value, workspaceTopicID.value, consoleTopicsEnabled.value], 2230 + () => { 2231 + void refreshWorkspaceState(); 2232 + } 2233 + ); 2234 + watch( 2235 + () => workspaceSidebarOpen.value, 2236 + (open) => { 2237 + saveWorkspaceSidebarOpen(open); 2238 + if (open && String(workspaceDir.value || "").trim() && !hasOwnTreePath(workspaceTreeItems.value, "")) { 2239 + void loadWorkspaceTree("", { force: true }); 2240 + } 2241 + } 2242 + ); 2243 + watch( 1444 2244 () => routeTopicID.value, 1445 2245 () => { 1446 2246 void syncTopicFromRoute().finally(() => { ··· 1472 2272 taskInput, 1473 2273 sending, 1474 2274 err, 2275 + workspaceDir, 2276 + workspaceDirDisplay, 2277 + workspaceLoading, 2278 + workspaceSaving, 2279 + workspaceOpening, 2280 + workspaceBusy, 2281 + workspaceSidebarOpen, 2282 + workspaceSidebarTabID, 2283 + workspacePanelTabs, 2284 + selectedWorkspacePanelTab, 2285 + workspaceError, 2286 + workspaceReady, 2287 + workspaceHintText, 2288 + workspaceAttachDisabled, 2289 + workspaceDetachDisabled, 2290 + workspaceSidebarToggleLabel, 2291 + workspaceTreeLoading, 2292 + workspaceTreeLoadingPath, 2293 + workspaceTreeError, 2294 + workspaceTreeRows, 2295 + workspaceSelectedTreeEntry, 2296 + workspaceBrowserOpen, 2297 + workspaceBrowserLoading, 2298 + workspaceBrowserLoadingPath, 2299 + workspaceBrowserError, 2300 + workspaceBrowserRows, 2301 + workspaceBrowserRecentItems, 2302 + workspaceBrowserSelection, 2303 + workspaceBrowserEmptyText, 2304 + workspaceBrowserConfirmDisabled, 2305 + formatBytes, 2306 + workspaceTreeIcon, 2307 + workspaceTreeEntryClass, 1475 2308 composerField, 1476 2309 submitBlockedMessage, 1477 2310 chatReadonly, ··· 1485 2318 composerDisabled, 1486 2319 sendDisabled, 1487 2320 composerPlaceholder, 2321 + displayAgentName, 1488 2322 consoleTopicsEnabled, 1489 2323 mobileTopicSplitEnabled, 1490 2324 mobileBarTitle, ··· 1497 2331 chatPlaceholderHint, 1498 2332 showTopicSidebar, 1499 2333 showChatPane, 2334 + workspaceSidebarAvailable, 2335 + desktopWorkspaceSidebarVisible, 1500 2336 submitTask, 2337 + toggleWorkspaceSidebar, 2338 + onWorkspaceTabChange, 2339 + selectWorkspaceTreeNode, 2340 + addWorkspaceSelectionToComposer, 2341 + openWorkspaceSelection, 2342 + toggleWorkspaceTreeNode, 2343 + openWorkspaceBrowser, 2344 + closeWorkspaceBrowser, 2345 + activateWorkspaceBrowserSource, 2346 + workspaceBrowserSourceItemClass, 2347 + toggleWorkspaceBrowserNode, 2348 + selectWorkspaceBrowserNode, 2349 + attachWorkspace, 2350 + detachWorkspace, 1501 2351 selectTopic, 1502 2352 startNewTopic, 1503 2353 showTopicsView, ··· 1599 2449 </aside> 1600 2450 <section v-if="showChatPane" :class="chatMainClass"> 1601 2451 <header v-if="consoleTopicsEnabled && !showChatPlaceholder" class="chat-desk-head"> 1602 - <div class="chat-desk-copy"> 1603 - <p v-if="deskMeta" class="chat-desk-meta">{{ deskMeta }}</p> 1604 - <h3 class="chat-desk-title workspace-document-title">{{ deskTitle }}</h3> 2452 + <div class="chat-desk-head-main"> 2453 + <div class="chat-desk-copy"> 2454 + <p v-if="deskMeta" class="chat-desk-meta">{{ deskMeta }}</p> 2455 + <h3 class="chat-desk-title workspace-document-title">{{ deskTitle }}</h3> 2456 + </div> 2457 + <div v-if="workspaceSidebarAvailable" class="chat-desk-tools"> 2458 + <QButton 2459 + :class="workspaceSidebarOpen ? 'plain sm icon chat-workspace-toggle is-active' : 'plain sm icon chat-workspace-toggle'" 2460 + :title="workspaceSidebarToggleLabel" 2461 + :aria-label="workspaceSidebarToggleLabel" 2462 + @click="toggleWorkspaceSidebar" 2463 + > 2464 + <QIconLayoutRight class="icon" /> 2465 + </QButton> 2466 + </div> 1605 2467 </div> 1606 2468 </header> 1607 2469 <section v-if="showChatPlaceholder" class="chat-placeholder"> ··· 1694 2556 </QTextarea> 1695 2557 </div> 1696 2558 </section> 2559 + <aside 2560 + v-if="desktopWorkspaceSidebarVisible" 2561 + class="chat-workspace-sidebar workspace-sidebar-section" 2562 + :aria-label="t('chat_workspace_label')" 2563 + > 2564 + <div class="chat-workspace-sidebar-shell"> 2565 + <QTabs 2566 + class="chat-workspace-tabs" 2567 + :tabs="workspacePanelTabs" 2568 + :modelValue="selectedWorkspacePanelTab" 2569 + variant="plain" 2570 + @change="onWorkspaceTabChange" 2571 + /> 2572 + 2573 + <div class="chat-workspace-pane ui-track-panel"> 2574 + <template v-if="workspaceReady"> 2575 + <template v-if="workspaceDir"> 2576 + <header class="chat-workspace-toolbar"> 2577 + <div class="chat-workspace-pane-copy"> 2578 + <p class="chat-workspace-pane-label ui-kicker">{{ t("chat_workspace_label") }}</p> 2579 + <code class="chat-workspace-pane-path" :title="workspaceDir"> 2580 + <span 2581 + v-if="workspaceDirDisplay.prefix" 2582 + class="chat-workspace-pane-path-prefix" 2583 + > 2584 + {{ workspaceDirDisplay.prefix }} 2585 + </span> 2586 + <span 2587 + v-if="workspaceDirDisplay.separator" 2588 + class="chat-workspace-pane-path-separator" 2589 + aria-hidden="true" 2590 + > 2591 + {{ workspaceDirDisplay.separator }} 2592 + </span> 2593 + <span class="chat-workspace-pane-path-tail">{{ workspaceDirDisplay.tail }}</span> 2594 + </code> 2595 + <p v-if="workspaceHintText" class="chat-workspace-pane-note">{{ workspaceHintText }}</p> 2596 + </div> 2597 + 2598 + <div class="chat-workspace-toolbar-actions"> 2599 + <QButton 2600 + class="plain xs icon" 2601 + :title="t('chat_workspace_action_attach')" 2602 + :aria-label="t('chat_workspace_action_attach')" 2603 + :disabled="workspaceAttachDisabled" 2604 + @click="openWorkspaceBrowser" 2605 + > 2606 + <QIconPlus class="icon" /> 2607 + </QButton> 2608 + <QButton 2609 + class="plain xs icon" 2610 + :title="t('chat_workspace_action_detach')" 2611 + :aria-label="t('chat_workspace_action_detach')" 2612 + :disabled="workspaceDetachDisabled" 2613 + :loading="workspaceSaving" 2614 + @click="detachWorkspace" 2615 + > 2616 + <QIconTrash class="icon" /> 2617 + </QButton> 2618 + </div> 2619 + </header> 2620 + 2621 + <QFence 2622 + v-if="workspaceError" 2623 + class="chat-workspace-pane-fence" 2624 + type="danger" 2625 + icon="QIconCloseCircle" 2626 + :text="workspaceError" 2627 + /> 2628 + 2629 + <QFence 2630 + v-if="workspaceTreeError" 2631 + class="chat-workspace-pane-fence" 2632 + type="danger" 2633 + icon="QIconCloseCircle" 2634 + :text="workspaceTreeError" 2635 + /> 2636 + 2637 + <div class="chat-workspace-tree-shell"> 2638 + <p 2639 + v-if="workspaceTreeLoading && workspaceTreeRows.length === 0" 2640 + class="chat-workspace-tree-status" 2641 + > 2642 + {{ t("chat_workspace_tree_loading") }} 2643 + </p> 2644 + <div v-else-if="workspaceTreeRows.length > 0" class="chat-workspace-tree-list"> 2645 + <div 2646 + v-for="row in workspaceTreeRows" 2647 + :key="'workspace:' + row.key" 2648 + class="chat-workspace-tree-row" 2649 + :style="{ '--tree-depth': row.depth }" 2650 + > 2651 + <button 2652 + type="button" 2653 + :class="workspaceTreeEntryClass(row)" 2654 + :title="row.entry.path" 2655 + @click="selectWorkspaceTreeNode(row)" 2656 + > 2657 + <span class="chat-workspace-tree-kind" aria-hidden="true"> 2658 + <img class="chat-workspace-tree-icon" :src="workspaceTreeIcon(row.entry, row.expanded)" alt="" /> 2659 + </span> 2660 + <span class="chat-workspace-tree-name">{{ row.entry.name }}</span> 2661 + </button> 2662 + </div> 2663 + </div> 2664 + <p v-else class="chat-workspace-tree-status">{{ t("chat_workspace_tree_empty") }}</p> 2665 + </div> 2666 + 2667 + <footer v-if="workspaceSelectedTreeEntry" class="chat-workspace-status"> 2668 + <div class="chat-workspace-status-head"> 2669 + <p class="chat-workspace-status-title">{{ workspaceSelectedTreeEntry.name }}</p> 2670 + <span class="chat-workspace-status-kind ui-kicker"> 2671 + {{ 2672 + workspaceSelectedTreeEntry.is_dir 2673 + ? t("chat_workspace_kind_dir") 2674 + : t("chat_workspace_kind_file") 2675 + }} 2676 + </span> 2677 + </div> 2678 + 2679 + <dl class="chat-workspace-status-grid"> 2680 + <div class="chat-workspace-status-row"> 2681 + <dt class="chat-workspace-status-term">{{ t("audit_size") }}</dt> 2682 + <dd class="chat-workspace-status-value"> 2683 + {{ formatBytes(workspaceSelectedTreeEntry.size_bytes) }} 2684 + </dd> 2685 + </div> 2686 + <div class="chat-workspace-status-row"> 2687 + <dt class="chat-workspace-status-term">{{ t("audit_action") }}</dt> 2688 + <dd class="chat-workspace-status-actions"> 2689 + <QButton 2690 + class="plain xs icon" 2691 + :title="t('chat_workspace_action_insert')" 2692 + :aria-label="t('chat_workspace_action_insert')" 2693 + :disabled="composerDisabled" 2694 + @click="addWorkspaceSelectionToComposer" 2695 + > 2696 + <QIconPlus class="icon" /> 2697 + </QButton> 2698 + <QButton 2699 + class="plain xs icon" 2700 + :title="t('chat_workspace_action_open')" 2701 + :aria-label="t('chat_workspace_action_open')" 2702 + :loading="workspaceOpening" 2703 + @click="openWorkspaceSelection" 2704 + > 2705 + <QIconLinkExternal class="icon" /> 2706 + </QButton> 2707 + </dd> 2708 + </div> 2709 + </dl> 2710 + </footer> 2711 + </template> 2712 + 2713 + <template v-else> 2714 + <QFence 2715 + v-if="workspaceError" 2716 + class="chat-workspace-pane-fence" 2717 + type="danger" 2718 + icon="QIconCloseCircle" 2719 + :text="workspaceError" 2720 + /> 2721 + 2722 + <div class="chat-workspace-empty-state"> 2723 + <div class="chat-workspace-empty-lead"> 2724 + <p class="chat-workspace-empty-title">{{ t("chat_workspace_empty_title") }}</p> 2725 + </div> 2726 + <div class="chat-workspace-empty-actions"> 2727 + <QButton 2728 + class="primary sm" 2729 + :disabled="workspaceAttachDisabled" 2730 + @click="openWorkspaceBrowser" 2731 + > 2732 + {{ t("chat_workspace_action_attach") }} 2733 + </QButton> 2734 + </div> 2735 + </div> 2736 + </template> 2737 + </template> 2738 + 2739 + <div v-else class="chat-workspace-empty-state is-disabled"> 2740 + <div class="chat-workspace-empty-lead"> 2741 + <p class="chat-workspace-empty-title">{{ t("chat_workspace_unavailable_title") }}</p> 2742 + <p v-if="workspaceHintText" class="chat-workspace-empty-copy">{{ workspaceHintText }}</p> 2743 + </div> 2744 + </div> 2745 + </div> 2746 + </div> 2747 + </aside> 1697 2748 </section> 2749 + <QDrawer 2750 + :modelValue="mobileMode && workspaceSidebarAvailable && workspaceSidebarOpen" 2751 + placement="right" 2752 + size="min(88vw, 360px)" 2753 + :closable="false" 2754 + :showMask="true" 2755 + :maskClosable="true" 2756 + :lockScroll="true" 2757 + @update:modelValue="!$event && toggleWorkspaceSidebar()" 2758 + @close="workspaceSidebarOpen = false" 2759 + > 2760 + <div class="chat-workspace-sidebar-shell chat-workspace-sidebar-shell-mobile"> 2761 + <QTabs 2762 + class="chat-workspace-tabs" 2763 + :tabs="workspacePanelTabs" 2764 + :modelValue="selectedWorkspacePanelTab" 2765 + variant="plain" 2766 + @change="onWorkspaceTabChange" 2767 + /> 2768 + 2769 + <div class="chat-workspace-pane ui-track-panel"> 2770 + <template v-if="workspaceReady"> 2771 + <template v-if="workspaceDir"> 2772 + <header class="chat-workspace-toolbar"> 2773 + <div class="chat-workspace-pane-copy"> 2774 + <p class="chat-workspace-pane-label ui-kicker">{{ t("chat_workspace_label") }}</p> 2775 + <code class="chat-workspace-pane-path" :title="workspaceDir"> 2776 + <span 2777 + v-if="workspaceDirDisplay.prefix" 2778 + class="chat-workspace-pane-path-prefix" 2779 + > 2780 + {{ workspaceDirDisplay.prefix }} 2781 + </span> 2782 + <span 2783 + v-if="workspaceDirDisplay.separator" 2784 + class="chat-workspace-pane-path-separator" 2785 + aria-hidden="true" 2786 + > 2787 + {{ workspaceDirDisplay.separator }} 2788 + </span> 2789 + <span class="chat-workspace-pane-path-tail">{{ workspaceDirDisplay.tail }}</span> 2790 + </code> 2791 + <p v-if="workspaceHintText" class="chat-workspace-pane-note">{{ workspaceHintText }}</p> 2792 + </div> 2793 + 2794 + <div class="chat-workspace-toolbar-actions"> 2795 + <QButton 2796 + class="plain xs icon" 2797 + :title="t('chat_workspace_action_attach')" 2798 + :aria-label="t('chat_workspace_action_attach')" 2799 + :disabled="workspaceAttachDisabled" 2800 + @click="openWorkspaceBrowser" 2801 + > 2802 + <QIconPlus class="icon" /> 2803 + </QButton> 2804 + <QButton 2805 + class="plain xs icon" 2806 + :title="t('chat_workspace_action_detach')" 2807 + :aria-label="t('chat_workspace_action_detach')" 2808 + :disabled="workspaceDetachDisabled" 2809 + :loading="workspaceSaving" 2810 + @click="detachWorkspace" 2811 + > 2812 + <QIconTrash class="icon" /> 2813 + </QButton> 2814 + </div> 2815 + </header> 2816 + 2817 + <QFence 2818 + v-if="workspaceError" 2819 + class="chat-workspace-pane-fence" 2820 + type="danger" 2821 + icon="QIconCloseCircle" 2822 + :text="workspaceError" 2823 + /> 2824 + 2825 + <QFence 2826 + v-if="workspaceTreeError" 2827 + class="chat-workspace-pane-fence" 2828 + type="danger" 2829 + icon="QIconCloseCircle" 2830 + :text="workspaceTreeError" 2831 + /> 2832 + 2833 + <div class="chat-workspace-tree-shell"> 2834 + <p 2835 + v-if="workspaceTreeLoading && workspaceTreeRows.length === 0" 2836 + class="chat-workspace-tree-status" 2837 + > 2838 + {{ t("chat_workspace_tree_loading") }} 2839 + </p> 2840 + <div v-else-if="workspaceTreeRows.length > 0" class="chat-workspace-tree-list"> 2841 + <div 2842 + v-for="row in workspaceTreeRows" 2843 + :key="'workspace-mobile:' + row.key" 2844 + class="chat-workspace-tree-row" 2845 + :style="{ '--tree-depth': row.depth }" 2846 + > 2847 + <button 2848 + type="button" 2849 + :class="workspaceTreeEntryClass(row)" 2850 + :title="row.entry.path" 2851 + @click="selectWorkspaceTreeNode(row)" 2852 + > 2853 + <span class="chat-workspace-tree-kind" aria-hidden="true"> 2854 + <img class="chat-workspace-tree-icon" :src="workspaceTreeIcon(row.entry, row.expanded)" alt="" /> 2855 + </span> 2856 + <span class="chat-workspace-tree-name">{{ row.entry.name }}</span> 2857 + </button> 2858 + </div> 2859 + </div> 2860 + <p v-else class="chat-workspace-tree-status">{{ t("chat_workspace_tree_empty") }}</p> 2861 + </div> 2862 + 2863 + <footer v-if="workspaceSelectedTreeEntry" class="chat-workspace-status"> 2864 + <div class="chat-workspace-status-head"> 2865 + <p class="chat-workspace-status-title">{{ workspaceSelectedTreeEntry.name }}</p> 2866 + <span class="chat-workspace-status-kind ui-kicker"> 2867 + {{ 2868 + workspaceSelectedTreeEntry.is_dir 2869 + ? t("chat_workspace_kind_dir") 2870 + : t("chat_workspace_kind_file") 2871 + }} 2872 + </span> 2873 + </div> 2874 + 2875 + <dl class="chat-workspace-status-grid"> 2876 + <div class="chat-workspace-status-row"> 2877 + <dt class="chat-workspace-status-term">{{ t("audit_size") }}</dt> 2878 + <dd class="chat-workspace-status-value"> 2879 + {{ formatBytes(workspaceSelectedTreeEntry.size_bytes) }} 2880 + </dd> 2881 + </div> 2882 + <div class="chat-workspace-status-row"> 2883 + <dt class="chat-workspace-status-term">{{ t("audit_action") }}</dt> 2884 + <dd class="chat-workspace-status-actions"> 2885 + <QButton 2886 + class="plain xs icon" 2887 + :title="t('chat_workspace_action_insert')" 2888 + :aria-label="t('chat_workspace_action_insert')" 2889 + :disabled="composerDisabled" 2890 + @click="addWorkspaceSelectionToComposer" 2891 + > 2892 + <QIconPlus class="icon" /> 2893 + </QButton> 2894 + <QButton 2895 + class="plain xs icon" 2896 + :title="t('chat_workspace_action_open')" 2897 + :aria-label="t('chat_workspace_action_open')" 2898 + :loading="workspaceOpening" 2899 + @click="openWorkspaceSelection" 2900 + > 2901 + <QIconLinkExternal class="icon" /> 2902 + </QButton> 2903 + </dd> 2904 + </div> 2905 + </dl> 2906 + </footer> 2907 + </template> 2908 + 2909 + <template v-else> 2910 + <QFence 2911 + v-if="workspaceError" 2912 + class="chat-workspace-pane-fence" 2913 + type="danger" 2914 + icon="QIconCloseCircle" 2915 + :text="workspaceError" 2916 + /> 2917 + 2918 + <div class="chat-workspace-empty-state"> 2919 + <div class="chat-workspace-empty-lead"> 2920 + <p class="chat-workspace-empty-title">{{ t("chat_workspace_empty_title") }}</p> 2921 + </div> 2922 + <div class="chat-workspace-empty-actions"> 2923 + <QButton 2924 + class="primary sm" 2925 + :disabled="workspaceAttachDisabled" 2926 + @click="openWorkspaceBrowser" 2927 + > 2928 + {{ t("chat_workspace_action_attach") }} 2929 + </QButton> 2930 + </div> 2931 + </div> 2932 + </template> 2933 + </template> 2934 + 2935 + <div v-else class="chat-workspace-empty-state is-disabled"> 2936 + <div class="chat-workspace-empty-lead"> 2937 + <p class="chat-workspace-empty-title">{{ t("chat_workspace_unavailable_title") }}</p> 2938 + <p v-if="workspaceHintText" class="chat-workspace-empty-copy">{{ workspaceHintText }}</p> 2939 + </div> 2940 + </div> 2941 + </div> 2942 + </div> 2943 + </QDrawer> 2944 + <QDialog 2945 + :modelValue="workspaceBrowserOpen" 2946 + width="720px" 2947 + @update:modelValue="!$event && closeWorkspaceBrowser()" 2948 + @close="closeWorkspaceBrowser" 2949 + > 2950 + <template #header> 2951 + <header class="chat-workspace-dialog-head"> 2952 + <h3 class="chat-workspace-dialog-title">{{ t("chat_workspace_dialog_title") }}</h3> 2953 + </header> 2954 + </template> 2955 + 2956 + <section class="chat-workspace-dialog"> 2957 + <QFence 2958 + v-if="workspaceBrowserError" 2959 + class="chat-workspace-pane-fence" 2960 + type="danger" 2961 + icon="QIconCloseCircle" 2962 + :text="workspaceBrowserError" 2963 + /> 2964 + 2965 + <div class="chat-workspace-dialog-shell"> 2966 + <aside class="chat-workspace-dialog-sidebar workspace-sidebar-section"> 2967 + <section class="chat-workspace-dialog-sidebar-group"> 2968 + <p class="chat-workspace-dialog-sidebar-title ui-kicker">{{ t("chat_workspace_dialog_places") }}</p> 2969 + <div class="chat-workspace-dialog-sidebar-list workspace-sidebar-list"> 2970 + <button 2971 + type="button" 2972 + :class="workspaceBrowserSourceItemClass('recent')" 2973 + @click="activateWorkspaceBrowserSource('recent')" 2974 + > 2975 + <span class="workspace-sidebar-item-copy"> 2976 + <span class="workspace-sidebar-item-title">{{ t("chat_workspace_dialog_recent") }}</span> 2977 + </span> 2978 + </button> 2979 + <button 2980 + type="button" 2981 + :class="workspaceBrowserSourceItemClass('home')" 2982 + @click="activateWorkspaceBrowserSource('home')" 2983 + > 2984 + <span class="workspace-sidebar-item-copy"> 2985 + <span class="workspace-sidebar-item-title">{{ t("chat_workspace_dialog_home") }}</span> 2986 + </span> 2987 + </button> 2988 + <button 2989 + type="button" 2990 + :class="workspaceBrowserSourceItemClass('system')" 2991 + @click="activateWorkspaceBrowserSource('system')" 2992 + > 2993 + <span class="workspace-sidebar-item-copy"> 2994 + <span class="workspace-sidebar-item-title">{{ t("chat_workspace_dialog_system") }}</span> 2995 + </span> 2996 + </button> 2997 + </div> 2998 + </section> 2999 + </aside> 3000 + 3001 + <div class="chat-workspace-dialog-main"> 3002 + <div class="chat-workspace-browser-shell"> 3003 + <p 3004 + v-if="workspaceBrowserLoading && workspaceBrowserRows.length === 0" 3005 + class="chat-workspace-tree-status" 3006 + > 3007 + {{ t("chat_workspace_dialog_loading") }} 3008 + </p> 3009 + <div v-else-if="workspaceBrowserRows.length > 0" class="chat-workspace-tree-list is-browser"> 3010 + <div 3011 + v-for="row in workspaceBrowserRows" 3012 + :key="'browser:' + row.key" 3013 + class="chat-workspace-tree-row" 3014 + :style="{ '--tree-depth': row.depth }" 3015 + > 3016 + <button 3017 + type="button" 3018 + :class="workspaceBrowserSelection === row.entry.path 3019 + ? 'chat-workspace-tree-entry is-selectable is-selected is-actionable' 3020 + : 'chat-workspace-tree-entry is-selectable is-actionable'" 3021 + :disabled="!row.entry.is_dir" 3022 + :title="row.entry.path" 3023 + @click="selectWorkspaceBrowserNode(row)" 3024 + > 3025 + <span class="chat-workspace-tree-kind" aria-hidden="true"> 3026 + <img class="chat-workspace-tree-icon" :src="workspaceTreeIcon(row.entry, row.expanded)" alt="" /> 3027 + </span> 3028 + <span class="chat-workspace-tree-name">{{ row.entry.name }}</span> 3029 + </button> 3030 + </div> 3031 + </div> 3032 + <p v-else class="chat-workspace-tree-status">{{ workspaceBrowserEmptyText }}</p> 3033 + </div> 3034 + </div> 3035 + 3036 + <div class="chat-workspace-dialog-actions"> 3037 + <QButton 3038 + class="plain sm" 3039 + :disabled="workspaceSaving" 3040 + @click="closeWorkspaceBrowser" 3041 + > 3042 + {{ t("action_cancel") }} 3043 + </QButton> 3044 + <QButton 3045 + class="primary sm" 3046 + :loading="workspaceSaving" 3047 + :disabled="workspaceBrowserConfirmDisabled" 3048 + @click="attachWorkspace" 3049 + > 3050 + {{ t("chat_workspace_action_attach") }} 3051 + </QButton> 3052 + </div> 3053 + </div> 3054 + </section> 3055 + </QDialog> 1698 3056 <RawJsonDialog 1699 3057 :open="rawDialogOpen" 1700 3058 :json="rawDialogJSON"