| 1 | from __future__ import annotations |
| 2 | |
| 3 | from helpers.api import ApiHandler, Request, Response |
| 4 | |
| 5 | from plugins._commands.helpers import commands as commands_helper |
| 6 | |
| 7 | |
| 8 | class Commands(ApiHandler): |
| 9 | async def process(self, input: dict, request: Request) -> dict | Response: |
| 10 | action = str(input.get("action", "") or "").strip() |
| 11 | |
| 12 | if action == "list_effective": |
| 13 | return self._list_effective(input) |
| 14 | if action == "list_scope": |
| 15 | return self._list_scope(input) |
| 16 | if action == "get": |
| 17 | return self._get(input) |
| 18 | if action == "save": |
| 19 | return self._save(input) |
| 20 | if action == "delete": |
| 21 | return self._delete(input) |
| 22 | if action == "duplicate": |
| 23 | return self._duplicate(input) |
| 24 | if action == "scope_info": |
| 25 | return self._scope_info(input) |
| 26 | if action == "resolve": |
| 27 | return await self._resolve(input) |
| 28 | |
| 29 | return Response(status=400, response=f"Unknown action: {action}") |
| 30 | |
| 31 | def _list_effective(self, input: dict) -> dict | Response: |
| 32 | context_scope = commands_helper.get_context_scope(str(input.get("context_id", "") or "")) |
| 33 | commands, scope = commands_helper.list_effective_commands( |
| 34 | project_name=context_scope["project_name"], |
| 35 | ) |
| 36 | commands = [ |
| 37 | command |
| 38 | for command in commands |
| 39 | if not command.get("frontmatter_extra", {}).get("webui_hidden") |
| 40 | ] |
| 41 | return { |
| 42 | "ok": True, |
| 43 | "commands": commands, |
| 44 | "scope": scope, |
| 45 | } |
| 46 | |
| 47 | def _list_scope(self, input: dict) -> dict | Response: |
| 48 | commands, scope = commands_helper.list_scope_commands( |
| 49 | project_name=str(input.get("project_name", "") or ""), |
| 50 | ) |
| 51 | return { |
| 52 | "ok": True, |
| 53 | "commands": commands, |
| 54 | "builtin_commands": commands_helper.list_builtin_commands(), |
| 55 | "scope": scope, |
| 56 | } |
| 57 | |
| 58 | def _get(self, input: dict) -> dict | Response: |
| 59 | path = str(input.get("path", "") or "") |
| 60 | if not path: |
| 61 | return Response(status=400, response="Missing path") |
| 62 | |
| 63 | try: |
| 64 | command = commands_helper.get_command( |
| 65 | path, |
| 66 | project_name=str(input.get("project_name", "") or ""), |
| 67 | ) |
| 68 | except FileNotFoundError: |
| 69 | return Response(status=404, response="Command not found") |
| 70 | except ValueError as error: |
| 71 | return Response(status=400, response=str(error)) |
| 72 | |
| 73 | return {"ok": True, "command": command} |
| 74 | |
| 75 | def _save(self, input: dict) -> dict | Response: |
| 76 | try: |
| 77 | command = commands_helper.save_command( |
| 78 | project_name=str(input.get("project_name", "") or ""), |
| 79 | existing_path=str(input.get("existing_path", "") or ""), |
| 80 | name=str(input.get("name", "") or ""), |
| 81 | description=str(input.get("description", "") or ""), |
| 82 | argument_hint=str(input.get("argument_hint", "") or ""), |
| 83 | command_type=str(input.get("command_type", "text") or "text"), |
| 84 | body=str(input.get("body", "") or ""), |
| 85 | include_history=bool(input.get("include_history", False)), |
| 86 | extra_frontmatter=input.get("extra_frontmatter", {}) or {}, |
| 87 | ) |
| 88 | except FileExistsError as error: |
| 89 | return Response(status=409, response=str(error)) |
| 90 | except ValueError as error: |
| 91 | return Response(status=400, response=str(error)) |
| 92 | |
| 93 | return {"ok": True, "command": command} |
| 94 | |
| 95 | def _delete(self, input: dict) -> dict | Response: |
| 96 | path = str(input.get("path", "") or "") |
| 97 | if not path: |
| 98 | return Response(status=400, response="Missing path") |
| 99 | |
| 100 | try: |
| 101 | commands_helper.delete_command( |
| 102 | path, |
| 103 | project_name=str(input.get("project_name", "") or ""), |
| 104 | ) |
| 105 | except FileNotFoundError: |
| 106 | return Response(status=404, response="Command not found") |
| 107 | except ValueError as error: |
| 108 | return Response(status=400, response=str(error)) |
| 109 | |
| 110 | return {"ok": True} |
| 111 | |
| 112 | def _duplicate(self, input: dict) -> dict | Response: |
| 113 | path = str(input.get("path", "") or "") |
| 114 | if not path: |
| 115 | return Response(status=400, response="Missing path") |
| 116 | |
| 117 | try: |
| 118 | command = commands_helper.duplicate_command( |
| 119 | path, |
| 120 | project_name=str(input.get("project_name", "") or ""), |
| 121 | ) |
| 122 | except FileNotFoundError: |
| 123 | return Response(status=404, response="Command not found") |
| 124 | except ValueError as error: |
| 125 | return Response(status=400, response=str(error)) |
| 126 | |
| 127 | return {"ok": True, "command": command} |
| 128 | |
| 129 | def _scope_info(self, input: dict) -> dict | Response: |
| 130 | explicit_project = str(input.get("project_name", "") or "") |
| 131 | context_scope = commands_helper.get_context_scope(str(input.get("context_id", "") or "")) |
| 132 | |
| 133 | project_name = explicit_project if "project_name" in input else context_scope["project_name"] |
| 134 | |
| 135 | scope = commands_helper.get_scope_payload( |
| 136 | project_name=project_name, |
| 137 | ensure_directory=bool(input.get("ensure_directory", False)), |
| 138 | ) |
| 139 | return { |
| 140 | "ok": True, |
| 141 | "scope": commands_helper.strip_private_scope(scope), |
| 142 | "context_scope": context_scope, |
| 143 | } |
| 144 | |
| 145 | async def _resolve(self, input: dict) -> dict | Response: |
| 146 | path = str(input.get("path", "") or "") |
| 147 | if not path: |
| 148 | return Response(status=400, response="Missing path") |
| 149 | |
| 150 | slash_text = str(input.get("slash_text", "") or "") |
| 151 | if not slash_text: |
| 152 | return Response(status=400, response="Missing slash_text") |
| 153 | |
| 154 | try: |
| 155 | resolution = await commands_helper.resolve_command_invocation( |
| 156 | path=path, |
| 157 | slash_text=slash_text, |
| 158 | project_name=str(input.get("project_name", "") or ""), |
| 159 | context_id=str(input.get("context_id", "") or ""), |
| 160 | ) |
| 161 | except FileNotFoundError: |
| 162 | return Response(status=404, response="Command not found") |
| 163 | except ValueError as error: |
| 164 | return Response(status=400, response=str(error)) |
| 165 | |
| 166 | return {"ok": True, "resolution": resolution} |