From 1abc8f68f3e345a33fc37464f68e28c3c6ab9e0c Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Wed, 4 Feb 2026 02:28:51 +0530 Subject: [PATCH] feat: add boards and tasks management endpoints --- HEARTBEAT.md | 107 ++ ORCHESTRATION.md | 176 ++ README.md | 66 - backend/.env.example | 22 +- backend/.gitignore | 5 +- backend/alembic.ini | 121 +- backend/alembic/README | 1 - backend/alembic/env.py | 39 +- backend/alembic/script.py.mako | 14 +- .../versions/0a1b2c3d4e5f_baseline_schema.py | 195 -- ...5a6b_add_openclaw_agent_id_to_employees.py | 27 - backend/alembic/versions/5630abfa60f8_init.py | 574 ++++++ ...3d9b8c1f4a_add_boards_and_task_board_id.py | 64 + .../versions/8b6d1b8f4b21_drop_projects.py | 63 + .../9c4f1a2b3d4e_drop_tenancy_tables.py | 56 + ...c3d4e5f6_add_agents_and_activity_events.py | 70 + .../c7f0a2b1d4e3_add_agent_session_id.py | 38 + backend/app/__init__.py | 0 backend/app/api/__init__.py | 0 backend/app/api/activities.py | 32 - backend/app/api/activity.py | 26 + backend/app/api/agents.py | 205 +++ backend/app/api/auth.py | 15 + backend/app/api/boards.py | 82 + backend/app/api/gateway.py | 99 + backend/app/api/org.py | 474 ----- backend/app/api/projects.py | 178 -- backend/app/api/tasks.py | 115 ++ backend/app/api/utils.py | 37 - backend/app/api/work.py | 443 ----- backend/app/core/__init__.py | 0 backend/app/core/auth.py | 97 + backend/app/core/config.py | 25 +- backend/app/core/logging.py | 60 +- backend/app/core/urls.py | 35 - backend/app/db/__init__.py | 0 backend/app/db/session.py | 33 +- backend/app/integrations/__init__.py | 0 backend/app/integrations/notify.py | 282 --- backend/app/integrations/openclaw.py | 88 - backend/app/integrations/openclaw_agents.py | 129 -- backend/app/integrations/openclaw_gateway.py | 126 ++ backend/app/main.py | 32 +- backend/app/models/__init__.py | 20 +- backend/app/models/activity.py | 19 - backend/app/models/activity_events.py | 17 + backend/app/models/agents.py | 18 + backend/app/models/boards.py | 18 + backend/app/models/org.py | 40 - backend/app/models/projects.py | 23 - backend/app/models/tasks.py | 26 + backend/app/models/tenancy.py | 7 + backend/app/models/users.py | 15 + backend/app/models/work.py | 38 - backend/app/schemas/__init__.py | 20 + backend/app/schemas/activity_events.py | 15 + backend/app/schemas/agents.py | 36 + backend/app/schemas/boards.py | 26 + backend/app/schemas/org.py | 53 - backend/app/schemas/projects.py | 15 - backend/app/schemas/tasks.py | 34 + backend/app/schemas/users.py | 20 + backend/app/schemas/work.py | 33 - backend/app/services/__init__.py | 0 backend/app/services/admin_access.py | 10 + backend/app/workers/__init__.py | 0 backend/app/workers/queue.py | 14 + backend/pyproject.toml | 30 + backend/requirements-dev.txt | 7 +- backend/requirements.txt | 22 +- backend/scripts/README_seed.md | 37 - backend/scripts/lint.sh | 9 - backend/scripts/reset_db.sh | 55 - backend/scripts/seed_data.sql | 50 - backend/scripts/seed_demo.py | 46 + backend/uv.lock | 1084 +++++++++++ frontend/.env.example | 11 +- frontend/.gitignore | 48 +- frontend/next-env.d.ts | 6 + frontend/next.config.ts | 6 +- frontend/package-lock.json | 1055 ++++++++++- frontend/package.json | 6 + frontend/public/file.svg | 1 - frontend/public/globe.svg | 1 - frontend/public/next.svg | 1 - frontend/public/vercel.svg | 1 - frontend/public/window.svg | 1 - .../api/generated/activities/activities.ts | 237 --- frontend/src/api/generated/default/default.ts | 183 -- frontend/src/api/generated/hr/hr.ts | 1184 ------------ .../api/generated/model/agentOnboarding.ts | 22 - .../generated/model/agentOnboardingCreate.ts | 19 - .../generated/model/agentOnboardingUpdate.ts | 19 - .../src/api/generated/model/department.ts | 12 - .../api/generated/model/departmentCreate.ts | 11 - .../api/generated/model/departmentUpdate.ts | 11 - frontend/src/api/generated/model/employee.ts | 19 - .../src/api/generated/model/employeeCreate.ts | 18 - .../src/api/generated/model/employeeUpdate.ts | 18 - .../api/generated/model/employmentAction.ts | 15 - .../generated/model/employmentActionCreate.ts | 13 - .../generated/model/hTTPValidationError.ts | 11 - .../api/generated/model/headcountRequest.ts | 18 - .../generated/model/headcountRequestCreate.ts | 15 - .../generated/model/headcountRequestUpdate.ts | 11 - frontend/src/api/generated/model/index.ts | 40 - .../listActivitiesActivitiesGetParams.ts | 10 - .../listTaskCommentsTaskCommentsGetParams.ts | 10 - .../model/listTasksTasksGetParams.ts | 10 - .../model/listTeamsTeamsGetParams.ts | 10 - frontend/src/api/generated/model/project.ts | 13 - .../src/api/generated/model/projectCreate.ts | 12 - .../src/api/generated/model/projectMember.ts | 13 - .../src/api/generated/model/projectUpdate.ts | 12 - frontend/src/api/generated/model/task.ts | 19 - .../src/api/generated/model/taskComment.ts | 15 - .../api/generated/model/taskCommentCreate.ts | 13 - .../src/api/generated/model/taskCreate.ts | 16 - .../api/generated/model/taskReviewDecision.ts | 11 - .../src/api/generated/model/taskUpdate.ts | 14 - frontend/src/api/generated/model/team.ts | 13 - .../src/api/generated/model/teamCreate.ts | 12 - .../src/api/generated/model/teamUpdate.ts | 12 - .../api/generated/model/validationError.ts | 12 - frontend/src/api/generated/org/org.ts | 1603 ----------------- .../src/api/generated/projects/projects.ts | 1125 ------------ frontend/src/api/generated/work/work.ts | 1156 ------------ frontend/src/api/mutator.ts | 47 +- frontend/src/app/_components/Shell.module.css | 24 - frontend/src/app/_components/Shell.tsx | 72 - frontend/src/app/agents/page.tsx | 627 +++++++ frontend/src/app/boards/[boardId]/page.tsx | 310 ++++ frontend/src/app/boards/new/page.tsx | 135 ++ frontend/src/app/boards/page.tsx | 202 +++ frontend/src/app/dashboard/page.tsx | 50 + frontend/src/app/departments/page.tsx | 143 -- frontend/src/app/favicon.ico | Bin 25931 -> 0 bytes frontend/src/app/globals.css | 142 +- frontend/src/app/kanban/page.tsx | 211 --- frontend/src/app/layout.tsx | 45 +- frontend/src/app/page.module.css | 141 -- frontend/src/app/page.tsx | 170 +- frontend/src/app/people/page.tsx | 207 --- frontend/src/app/projects/[id]/page.tsx | 539 ------ frontend/src/app/projects/page.tsx | 103 -- frontend/src/app/providers.tsx | 23 - frontend/src/app/teams/page.tsx | 150 -- frontend/src/components/atoms/BrandMark.tsx | 17 + frontend/src/components/atoms/HeroKicker.tsx | 9 + frontend/src/components/atoms/StatusPill.tsx | 21 + .../src/components/molecules/HeroCopy.tsx | 20 + .../src/components/molecules/TaskCard.tsx | 38 + .../components/organisms/DashboardSidebar.tsx | 42 + .../src/components/organisms/LandingHero.tsx | 88 + .../src/components/organisms/TaskBoard.tsx | 99 + .../components/templates/DashboardShell.tsx | 31 + .../src/components/templates/LandingShell.tsx | 40 + frontend/src/components/ui/badge.tsx | 10 +- frontend/src/components/ui/button.tsx | 42 +- frontend/src/components/ui/card.tsx | 50 +- frontend/src/components/ui/dialog.tsx | 102 ++ frontend/src/components/ui/input.tsx | 28 +- frontend/src/components/ui/label.tsx | 20 - frontend/src/components/ui/select.tsx | 150 +- frontend/src/components/ui/tabs.tsx | 52 + frontend/src/components/ui/textarea.tsx | 29 +- frontend/src/components/ui/tooltip.tsx | 28 + frontend/src/lib/normalize.ts | 23 - frontend/src/proxy.ts | 10 + frontend/tailwind.config.cjs | 44 +- 170 files changed, 6860 insertions(+), 10706 deletions(-) create mode 100644 HEARTBEAT.md create mode 100644 ORCHESTRATION.md delete mode 100644 README.md delete mode 100644 backend/alembic/README delete mode 100644 backend/alembic/versions/0a1b2c3d4e5f_baseline_schema.py delete mode 100644 backend/alembic/versions/1c2d3e4f5a6b_add_openclaw_agent_id_to_employees.py create mode 100644 backend/alembic/versions/5630abfa60f8_init.py create mode 100644 backend/alembic/versions/7e3d9b8c1f4a_add_boards_and_task_board_id.py create mode 100644 backend/alembic/versions/8b6d1b8f4b21_drop_projects.py create mode 100644 backend/alembic/versions/9c4f1a2b3d4e_drop_tenancy_tables.py create mode 100644 backend/alembic/versions/a1b2c3d4e5f6_add_agents_and_activity_events.py create mode 100644 backend/alembic/versions/c7f0a2b1d4e3_add_agent_session_id.py create mode 100644 backend/app/__init__.py create mode 100644 backend/app/api/__init__.py delete mode 100644 backend/app/api/activities.py create mode 100644 backend/app/api/activity.py create mode 100644 backend/app/api/agents.py create mode 100644 backend/app/api/auth.py create mode 100644 backend/app/api/boards.py create mode 100644 backend/app/api/gateway.py delete mode 100644 backend/app/api/org.py delete mode 100644 backend/app/api/projects.py create mode 100644 backend/app/api/tasks.py delete mode 100644 backend/app/api/utils.py delete mode 100644 backend/app/api/work.py create mode 100644 backend/app/core/__init__.py create mode 100644 backend/app/core/auth.py delete mode 100644 backend/app/core/urls.py create mode 100644 backend/app/db/__init__.py create mode 100644 backend/app/integrations/__init__.py delete mode 100644 backend/app/integrations/notify.py delete mode 100644 backend/app/integrations/openclaw.py delete mode 100644 backend/app/integrations/openclaw_agents.py create mode 100644 backend/app/integrations/openclaw_gateway.py delete mode 100644 backend/app/models/activity.py create mode 100644 backend/app/models/activity_events.py create mode 100644 backend/app/models/agents.py create mode 100644 backend/app/models/boards.py delete mode 100644 backend/app/models/org.py delete mode 100644 backend/app/models/projects.py create mode 100644 backend/app/models/tasks.py create mode 100644 backend/app/models/tenancy.py create mode 100644 backend/app/models/users.py delete mode 100644 backend/app/models/work.py create mode 100644 backend/app/schemas/__init__.py create mode 100644 backend/app/schemas/activity_events.py create mode 100644 backend/app/schemas/agents.py create mode 100644 backend/app/schemas/boards.py delete mode 100644 backend/app/schemas/org.py delete mode 100644 backend/app/schemas/projects.py create mode 100644 backend/app/schemas/tasks.py create mode 100644 backend/app/schemas/users.py delete mode 100644 backend/app/schemas/work.py create mode 100644 backend/app/services/__init__.py create mode 100644 backend/app/services/admin_access.py create mode 100644 backend/app/workers/__init__.py create mode 100644 backend/app/workers/queue.py delete mode 100644 backend/scripts/README_seed.md delete mode 100755 backend/scripts/lint.sh delete mode 100755 backend/scripts/reset_db.sh delete mode 100644 backend/scripts/seed_data.sql create mode 100644 backend/scripts/seed_demo.py create mode 100644 backend/uv.lock create mode 100644 frontend/next-env.d.ts delete mode 100644 frontend/public/file.svg delete mode 100644 frontend/public/globe.svg delete mode 100644 frontend/public/next.svg delete mode 100644 frontend/public/vercel.svg delete mode 100644 frontend/public/window.svg delete mode 100644 frontend/src/api/generated/activities/activities.ts delete mode 100644 frontend/src/api/generated/default/default.ts delete mode 100644 frontend/src/api/generated/hr/hr.ts delete mode 100644 frontend/src/api/generated/model/agentOnboarding.ts delete mode 100644 frontend/src/api/generated/model/agentOnboardingCreate.ts delete mode 100644 frontend/src/api/generated/model/agentOnboardingUpdate.ts delete mode 100644 frontend/src/api/generated/model/department.ts delete mode 100644 frontend/src/api/generated/model/departmentCreate.ts delete mode 100644 frontend/src/api/generated/model/departmentUpdate.ts delete mode 100644 frontend/src/api/generated/model/employee.ts delete mode 100644 frontend/src/api/generated/model/employeeCreate.ts delete mode 100644 frontend/src/api/generated/model/employeeUpdate.ts delete mode 100644 frontend/src/api/generated/model/employmentAction.ts delete mode 100644 frontend/src/api/generated/model/employmentActionCreate.ts delete mode 100644 frontend/src/api/generated/model/hTTPValidationError.ts delete mode 100644 frontend/src/api/generated/model/headcountRequest.ts delete mode 100644 frontend/src/api/generated/model/headcountRequestCreate.ts delete mode 100644 frontend/src/api/generated/model/headcountRequestUpdate.ts delete mode 100644 frontend/src/api/generated/model/index.ts delete mode 100644 frontend/src/api/generated/model/listActivitiesActivitiesGetParams.ts delete mode 100644 frontend/src/api/generated/model/listTaskCommentsTaskCommentsGetParams.ts delete mode 100644 frontend/src/api/generated/model/listTasksTasksGetParams.ts delete mode 100644 frontend/src/api/generated/model/listTeamsTeamsGetParams.ts delete mode 100644 frontend/src/api/generated/model/project.ts delete mode 100644 frontend/src/api/generated/model/projectCreate.ts delete mode 100644 frontend/src/api/generated/model/projectMember.ts delete mode 100644 frontend/src/api/generated/model/projectUpdate.ts delete mode 100644 frontend/src/api/generated/model/task.ts delete mode 100644 frontend/src/api/generated/model/taskComment.ts delete mode 100644 frontend/src/api/generated/model/taskCommentCreate.ts delete mode 100644 frontend/src/api/generated/model/taskCreate.ts delete mode 100644 frontend/src/api/generated/model/taskReviewDecision.ts delete mode 100644 frontend/src/api/generated/model/taskUpdate.ts delete mode 100644 frontend/src/api/generated/model/team.ts delete mode 100644 frontend/src/api/generated/model/teamCreate.ts delete mode 100644 frontend/src/api/generated/model/teamUpdate.ts delete mode 100644 frontend/src/api/generated/model/validationError.ts delete mode 100644 frontend/src/api/generated/org/org.ts delete mode 100644 frontend/src/api/generated/projects/projects.ts delete mode 100644 frontend/src/api/generated/work/work.ts delete mode 100644 frontend/src/app/_components/Shell.module.css delete mode 100644 frontend/src/app/_components/Shell.tsx create mode 100644 frontend/src/app/agents/page.tsx create mode 100644 frontend/src/app/boards/[boardId]/page.tsx create mode 100644 frontend/src/app/boards/new/page.tsx create mode 100644 frontend/src/app/boards/page.tsx create mode 100644 frontend/src/app/dashboard/page.tsx delete mode 100644 frontend/src/app/departments/page.tsx delete mode 100644 frontend/src/app/favicon.ico delete mode 100644 frontend/src/app/kanban/page.tsx delete mode 100644 frontend/src/app/page.module.css delete mode 100644 frontend/src/app/people/page.tsx delete mode 100644 frontend/src/app/projects/[id]/page.tsx delete mode 100644 frontend/src/app/projects/page.tsx delete mode 100644 frontend/src/app/providers.tsx delete mode 100644 frontend/src/app/teams/page.tsx create mode 100644 frontend/src/components/atoms/BrandMark.tsx create mode 100644 frontend/src/components/atoms/HeroKicker.tsx create mode 100644 frontend/src/components/atoms/StatusPill.tsx create mode 100644 frontend/src/components/molecules/HeroCopy.tsx create mode 100644 frontend/src/components/molecules/TaskCard.tsx create mode 100644 frontend/src/components/organisms/DashboardSidebar.tsx create mode 100644 frontend/src/components/organisms/LandingHero.tsx create mode 100644 frontend/src/components/organisms/TaskBoard.tsx create mode 100644 frontend/src/components/templates/DashboardShell.tsx create mode 100644 frontend/src/components/templates/LandingShell.tsx create mode 100644 frontend/src/components/ui/dialog.tsx delete mode 100644 frontend/src/components/ui/label.tsx create mode 100644 frontend/src/components/ui/tabs.tsx create mode 100644 frontend/src/components/ui/tooltip.tsx delete mode 100644 frontend/src/lib/normalize.ts create mode 100644 frontend/src/proxy.ts diff --git a/HEARTBEAT.md b/HEARTBEAT.md new file mode 100644 index 00000000..627a769d --- /dev/null +++ b/HEARTBEAT.md @@ -0,0 +1,107 @@ +# Mission Control Orchestrator Instructions + +You are the Mission Control orchestrator. Your job is to: +1. Claim unassigned tasks (FIFO) +2. Execute work (optionally spawn sub-agents) +3. Log progress and deliverables +4. Move tasks to review when complete + +## CRITICAL: You MUST call Mission Control APIs + +Every action you take MUST be reflected in Mission Control via API calls. The dashboard shows task status in real-time. + +## Required Inputs + +- `BASE_URL` (e.g., http://localhost:8000) +- `ORG_ID` +- `WORKSPACE_ID` +- `AGENT_TOKEN` (Authorization Bearer token) + +## On Every Heartbeat + +### Step 1: Claim next task (FIFO) +```bash +curl -s -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/claim-next" \ + -H "Authorization: Bearer $AGENT_TOKEN" +``` + +- If response is **204**, there is no work. Wait and retry. +- If response returns a task, process it. + +### Step 2: Check your in-progress tasks +```bash +curl -s "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks?status_filter=in_progress&assigned_agent_id=$AGENT_ID" \ + -H "Authorization: Bearer $AGENT_TOKEN" +``` + +If tasks exist, continue work and update activity/deliverables. + +## When Processing a New Task + +### 1) Log that you're starting +```bash +curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/{TASK_ID}/activities" \ + -H "Authorization: Bearer $AGENT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"activity_type": "updated", "message": "Starting work on task"}' +``` + +### 2) Register a sub-agent (if you spawn one) +```bash +curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/{TASK_ID}/subagents" \ + -H "Authorization: Bearer $AGENT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "openclaw_session_id": "optional-session-id", + "agent_name": "Designer" + }' +``` + +### 3) Register deliverables +```bash +curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/{TASK_ID}/deliverables" \ + -H "Authorization: Bearer $AGENT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "title": "Homepage Design", + "markdown_content": "## Summary\n- Implemented layout\n- Added responsive styles" + }' +``` + +### 4) Log completion +```bash +curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/{TASK_ID}/activities" \ + -H "Authorization: Bearer $AGENT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"activity_type": "completed", "message": "Task completed successfully"}' +``` + +### 5) Move task to REVIEW +```bash +curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/{TASK_ID}/transition" \ + -H "Authorization: Bearer $AGENT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"to_status": "review"}' +``` + +## Task Statuses + +``` +inbox → in_progress → review → done +``` + +Other statuses may be used if configured (`assigned`, `testing`), but the default flow above is expected. + +## Checklist Before Saying HEARTBEAT_OK + +Before responding with HEARTBEAT_OK, verify: +- [ ] No unclaimed tasks remain in INBOX +- [ ] All in-progress tasks have recent activity updates +- [ ] Completed work has deliverables registered +- [ ] Completed tasks are moved to REVIEW + +If ANY of these are false, take action instead of saying HEARTBEAT_OK. + +## Reference + +Full API documentation: See ORCHESTRATION.md in this project. diff --git a/ORCHESTRATION.md b/ORCHESTRATION.md new file mode 100644 index 00000000..3e7a75ed --- /dev/null +++ b/ORCHESTRATION.md @@ -0,0 +1,176 @@ +# Mission Control Orchestration Guide + +This document explains how to orchestrate tasks in Mission Control, including how to: +- Register sub-agents +- Log activities +- Track deliverables +- Update task status + +## API Base URL + +``` +http://localhost:8000 +``` + +Or use the `BASE_URL` environment variable. + +## Task Lifecycle + +``` +INBOX → IN_PROGRESS → REVIEW → DONE +``` + +**Status Descriptions:** +- **INBOX**: New tasks awaiting processing +- **IN_PROGRESS**: Agent actively working on the task +- **REVIEW**: Agent finished, awaiting human approval +- **DONE**: Task completed and approved + +Optional statuses may be enabled (`ASSIGNED`, `TESTING`) but are not required by default. + +## When You Receive a Task + +When a task is claimed, the response includes: +- Task ID +- Title, description, priority +- Project ID + +## Required API Calls + +### 1. Register Sub-Agent (when spawning a worker) + +```bash +curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/{TASK_ID}/subagents" \ + -H "Authorization: Bearer $AGENT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "openclaw_session_id": "unique-session-id", + "agent_name": "Designer" + }' +``` + +### 2. Log Activity (for each significant action) + +```bash +curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/{TASK_ID}/activities" \ + -H "Authorization: Bearer $AGENT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "activity_type": "updated", + "message": "Started working on design mockups" + }' +``` + +Activity types: +- `spawned` - When sub-agent starts +- `updated` - Progress update +- `completed` - Work finished +- `file_created` - Created a deliverable +- `status_changed` - Task moved to new status + +### 3. Register Deliverable (for each output) + +```bash +curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/{TASK_ID}/deliverables" \ + -H "Authorization: Bearer $AGENT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "title": "Homepage Design", + "markdown_content": "## Summary\n- Implemented layout\n- Added responsive styles" + }' +``` + +### 4. Update Task Status + +```bash +curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/{TASK_ID}/transition" \ + -H "Authorization: Bearer $AGENT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ "to_status": "review" }' +``` + +## Complete Example Workflow + +```bash +TASK_ID="abc-123" +BASE_URL="http://localhost:8000" +ORG_ID="org-uuid" +WORKSPACE_ID="workspace-uuid" +AGENT_TOKEN="agent-token" + +# 1) Log that you're starting +curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/$TASK_ID/activities" \ + -H "Authorization: Bearer $AGENT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"activity_type": "updated", "message": "Starting work on task"}' + +# 2) Spawn a sub-agent +curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/$TASK_ID/subagents" \ + -H "Authorization: Bearer $AGENT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"openclaw_session_id": "subagent-'$(date +%s)'", "agent_name": "Designer"}' + +# 3) Register the deliverable +curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/$TASK_ID/deliverables" \ + -H "Authorization: Bearer $AGENT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "title": "Completed Design", + "markdown_content": "## Deliverable\n- Final design with all requested features" + }' + +# 4) Log completion +curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/$TASK_ID/activities" \ + -H "Authorization: Bearer $AGENT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"activity_type": "completed", "message": "Design completed successfully"}' + +# 5) Move to review +curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/$TASK_ID/transition" \ + -H "Authorization: Bearer $AGENT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"to_status": "review"}' +``` + +## Endpoints Reference + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks` | GET | List tasks | +| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks` | POST | Create task | +| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks/{task_id}` | PATCH | Update task | +| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks/{task_id}/activities` | GET | List activities | +| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks/{task_id}/activities` | POST | Log activity | +| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks/{task_id}/deliverables` | GET | List deliverables | +| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks/{task_id}/deliverables` | POST | Add deliverable | +| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks/{task_id}/subagents` | GET | List sub-agents | +| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks/{task_id}/subagents` | POST | Register sub-agent | +| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks/claim-next` | POST | Claim next task (FIFO) | +| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/events/activities` | GET | SSE activity stream | + +## Activity Body Schema + +```json +{ + "activity_type": "spawned|updated|completed|file_created|status_changed", + "message": "Human-readable description of what happened" +} +``` + +## Deliverable Body Schema + +```json +{ + "title": "Display name for the deliverable", + "markdown_content": "Markdown content for the deliverable" +} +``` + +## Sub-Agent Body Schema + +```json +{ + "openclaw_session_id": "unique-identifier-for-session", + "agent_name": "Designer|Developer|Researcher|Writer" +} +``` diff --git a/README.md b/README.md deleted file mode 100644 index a8c59fd4..00000000 --- a/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# OpenClaw Agency — Pilot (Kanban) - -MVP: **Next.js (frontend)** + **FastAPI (backend)** + **PostgreSQL**. - -No auth (yet). The goal is simple visibility: everyone can see what exists and who owns it. - -## Repo layout - -- `frontend/` — Next.js App Router (TypeScript) -- `backend/` — FastAPI + SQLAlchemy + Alembic - -## Database - -Uses local Postgres: - -- user: `postgres` -- password: `REDACTED` -- db: `openclaw_agency` - -## Environment - -Do **not** commit real `.env` files. - -- Backend: copy `backend/.env.example` → `backend/.env` -- Frontend: copy `frontend/.env.example` → `frontend/.env.local` - -If you want to test from another device (phone/laptop), make sure: - -- both servers bind to `0.0.0.0` -- `NEXT_PUBLIC_API_URL` is set to `http://:8000` (not `127.0.0.1`) -- backend `CORS_ORIGINS` includes `http://:3000` - -## Run backend (LAN-accessible) - -```bash -cd backend -source .venv/bin/activate -uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 -``` - -Health check: - -```bash -curl http://127.0.0.1:8000/health -# or from another machine: -# curl http://:8000/health -``` - -## Run frontend (LAN-accessible) - -```bash -cd frontend -npm run dev:lan -``` - -Open: - -- local: http://localhost:3000 -- LAN: `http://:3000` - -## API - -- `GET /tasks` -- `POST /tasks` -- `PATCH /tasks/{id}` -- `DELETE /tasks/{id}` diff --git a/backend/.env.example b/backend/.env.example index 38ddbe22..c5b23175 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,7 +1,17 @@ -# Example config for local/dev. -# -# If you plan to access the app from another device (via machine IP), -# set CORS_ORIGINS to include that frontend origin too. +ENVIRONMENT=dev +LOG_LEVEL=INFO +DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:5432/openclaw_agency +REDIS_URL=redis://localhost:6379/0 +CORS_ORIGINS=http://localhost:3000 -DATABASE_URL=postgresql+psycopg2://postgres:CHANGE_ME@127.0.0.1:5432/openclaw_agency -CORS_ORIGINS=http://localhost:3000,http://:3000 +# Clerk (auth only) +CLERK_JWKS_URL= +CLERK_VERIFY_IAT=true +CLERK_LEEWAY=10.0 + +# OpenClaw Gateway +OPENCLAW_GATEWAY_URL=ws://127.0.0.1:18789 +OPENCLAW_GATEWAY_TOKEN= + +# Database +DB_AUTO_MIGRATE=false diff --git a/backend/.gitignore b/backend/.gitignore index 3d6a54ba..1de69714 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,4 +1,5 @@ __pycache__/ -*.py[cod] -.env +*.pyc .venv/ +.env +.runlogs/ diff --git a/backend/alembic.ini b/backend/alembic.ini index f0da7e5b..500bf158 100644 --- a/backend/alembic.ini +++ b/backend/alembic.ini @@ -1,119 +1,8 @@ -# A generic, single database configuration. - [alembic] -# path to migration scripts. -# this is typically a path given in POSIX (e.g. forward slashes) -# format, relative to the token %(here)s which refers to the location of this -# ini file -script_location = %(here)s/alembic - -# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s -# Uncomment the line below if you want the files to be prepended with date and time -# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file -# for all available tokens -# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s -# Or organize into date-based subdirectories (requires recursive_version_locations = true) -# file_template = %%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s - -# sys.path path, will be prepended to sys.path if present. -# defaults to the current working directory. for multiple paths, the path separator -# is defined by "path_separator" below. +script_location = alembic prepend_sys_path = . +sqlalchemy.url = driver://user:pass@localhost/dbname - -# timezone to use when rendering the date within the migration file -# as well as the filename. -# If specified, requires the tzdata library which can be installed by adding -# `alembic[tz]` to the pip requirements. -# string value is passed to ZoneInfo() -# leave blank for localtime -# timezone = - -# max length of characters to apply to the "slug" field -# truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false - -# version location specification; This defaults -# to /versions. When using multiple version -# directories, initial revisions must be specified with --version-path. -# The path separator used here should be the separator specified by "path_separator" -# below. -# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions - -# path_separator; This indicates what character is used to split lists of file -# paths, including version_locations and prepend_sys_path within configparser -# files such as alembic.ini. -# The default rendered in new alembic.ini files is "os", which uses os.pathsep -# to provide os-dependent path splitting. -# -# Note that in order to support legacy alembic.ini files, this default does NOT -# take place if path_separator is not present in alembic.ini. If this -# option is omitted entirely, fallback logic is as follows: -# -# 1. Parsing of the version_locations option falls back to using the legacy -# "version_path_separator" key, which if absent then falls back to the legacy -# behavior of splitting on spaces and/or commas. -# 2. Parsing of the prepend_sys_path option falls back to the legacy -# behavior of splitting on spaces, commas, or colons. -# -# Valid values for path_separator are: -# -# path_separator = : -# path_separator = ; -# path_separator = space -# path_separator = newline -# -# Use os.pathsep. Default configuration used for new projects. -path_separator = os - -# set to 'true' to search source files recursively -# in each "version_locations" directory -# new in Alembic version 1.10 -# recursive_version_locations = false - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 - -# database URL. This is consumed by the user-maintained env.py script only. -# other means of configuring database URLs may be customized within the env.py -# file. -sqlalchemy.url = - - -[post_write_hooks] -# post_write_hooks defines scripts or Python functions that are run -# on newly generated revision scripts. See the documentation for further -# detail and examples - -# format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks = black -# black.type = console_scripts -# black.entrypoint = black -# black.options = -l 79 REVISION_SCRIPT_FILENAME - -# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module -# hooks = ruff -# ruff.type = module -# ruff.module = ruff -# ruff.options = check --fix REVISION_SCRIPT_FILENAME - -# Alternatively, use the exec runner to execute a binary found on your PATH -# hooks = ruff -# ruff.type = exec -# ruff.executable = ruff -# ruff.options = check --fix REVISION_SCRIPT_FILENAME - -# Logging configuration. This is also consumed by the user-maintained -# env.py script only. [loggers] keys = root,sqlalchemy,alembic @@ -124,12 +13,11 @@ keys = console keys = generic [logger_root] -level = WARNING +level = INFO handlers = console -qualname = [logger_sqlalchemy] -level = WARNING +level = WARN handlers = qualname = sqlalchemy.engine @@ -146,4 +34,3 @@ formatter = generic [formatter_generic] format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/backend/alembic/README b/backend/alembic/README deleted file mode 100644 index 2500aa1b..00000000 --- a/backend/alembic/README +++ /dev/null @@ -1 +0,0 @@ -Generic single-database configuration. diff --git a/backend/alembic/env.py b/backend/alembic/env.py index cdb40c21..4e18843c 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -1,36 +1,51 @@ from __future__ import annotations +import sys from logging.config import fileConfig +from pathlib import Path from sqlalchemy import engine_from_config, pool from sqlmodel import SQLModel from alembic import context -# Import models to register tables in metadata -from app import models # noqa: F401 -from app.core.config import settings +PROJECT_ROOT = Path(__file__).resolve().parents[1] +if str(PROJECT_ROOT) not in sys.path: + sys.path.append(str(PROJECT_ROOT)) + +from app import models # noqa: E402,F401 +from app.core.config import settings # noqa: E402 config = context.config -if config.config_file_name is not None: +if config.config_file_name is not None and config.attributes.get("configure_logger", True): fileConfig(config.config_file_name) - target_metadata = SQLModel.metadata +def _normalize_database_url(database_url: str) -> str: + if "://" not in database_url: + return database_url + scheme, rest = database_url.split("://", 1) + if scheme == "postgresql": + return f"postgresql+psycopg://{rest}" + return database_url + + def get_url() -> str: - return settings.database_url + return _normalize_database_url(settings.database_url) + + +config.set_main_option("sqlalchemy.url", get_url()) def run_migrations_offline() -> None: - url = get_url() context.configure( - url=url, + url=get_url(), target_metadata=target_metadata, literal_binds=True, - dialect_opts={"paramstyle": "named"}, + compare_type=True, ) with context.begin_transaction(): @@ -48,7 +63,11 @@ def run_migrations_online() -> None: ) with connectable.connect() as connection: - context.configure(connection=connection, target_metadata=target_metadata) + context.configure( + connection=connection, + target_metadata=target_metadata, + compare_type=True, + ) with context.begin_transaction(): context.run_migrations() diff --git a/backend/alembic/script.py.mako b/backend/alembic/script.py.mako index 11016301..d4095fb4 100644 --- a/backend/alembic/script.py.mako +++ b/backend/alembic/script.py.mako @@ -5,24 +5,22 @@ Revises: ${down_revision | comma,n} Create Date: ${create_date} """ -from typing import Sequence, Union +from __future__ import annotations from alembic import op import sqlalchemy as sa -${imports if imports else ""} + # revision identifiers, used by Alembic. -revision: str = ${repr(up_revision)} -down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)} -branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} -depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} def upgrade() -> None: - """Upgrade schema.""" ${upgrades if upgrades else "pass"} def downgrade() -> None: - """Downgrade schema.""" ${downgrades if downgrades else "pass"} diff --git a/backend/alembic/versions/0a1b2c3d4e5f_baseline_schema.py b/backend/alembic/versions/0a1b2c3d4e5f_baseline_schema.py deleted file mode 100644 index 3e198b18..00000000 --- a/backend/alembic/versions/0a1b2c3d4e5f_baseline_schema.py +++ /dev/null @@ -1,195 +0,0 @@ -"""Baseline schema (squashed) - -Revision ID: 0a1b2c3d4e5f -Revises: -Create Date: 2026-02-02 - -This is a squashed baseline migration for Mission Control. -All prior incremental migrations were removed to keep the repo simple. - -""" - -from __future__ import annotations - -from alembic import op -import sqlalchemy as sa -import sqlmodel - - -# revision identifiers, used by Alembic. -revision = "0a1b2c3d4e5f" -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # Departments (FK to employees added after employees table exists) - op.create_table( - "departments", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("head_employee_id", sa.Integer(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_index(op.f("ix_departments_name"), "departments", ["name"], unique=True) - - # Employees - op.create_table( - "employees", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("employee_type", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("department_id", sa.Integer(), nullable=True), - sa.Column("team_id", sa.Integer(), nullable=True), - sa.Column("manager_id", sa.Integer(), nullable=True), - sa.Column("title", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("openclaw_session_key", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("notify_enabled", sa.Boolean(), nullable=False), - sa.ForeignKeyConstraint(["department_id"], ["departments.id"]), - sa.ForeignKeyConstraint(["manager_id"], ["employees.id"]), - sa.PrimaryKeyConstraint("id"), - ) - - # Break the departments<->employees cycle: add this FK after both tables exist - op.create_foreign_key(None, "departments", "employees", ["head_employee_id"], ["id"]) - - # Teams - op.create_table( - "teams", - sa.Column("id", sa.Integer(), primary_key=True, nullable=False), - sa.Column("name", sa.String(), nullable=False), - sa.Column("department_id", sa.Integer(), nullable=False), - sa.Column("lead_employee_id", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(["department_id"], ["departments.id"], ondelete="CASCADE"), - sa.ForeignKeyConstraint(["lead_employee_id"], ["employees.id"], ondelete="SET NULL"), - sa.UniqueConstraint("department_id", "name", name="uq_teams_department_id_name"), - ) - op.create_index("ix_teams_name", "teams", ["name"], unique=False) - op.create_index("ix_teams_department_id", "teams", ["department_id"], unique=False) - - # Employees.team_id FK (added after teams exists) - op.create_index("ix_employees_team_id", "employees", ["team_id"], unique=False) - op.create_foreign_key( - "fk_employees_team_id_teams", - "employees", - "teams", - ["team_id"], - ["id"], - ondelete="SET NULL", - ) - - # Projects - op.create_table( - "projects", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("team_id", sa.Integer(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_index(op.f("ix_projects_name"), "projects", ["name"], unique=True) - op.create_index("ix_projects_team_id", "projects", ["team_id"], unique=False) - op.create_foreign_key( - "fk_projects_team_id_teams", - "projects", - "teams", - ["team_id"], - ["id"], - ondelete="SET NULL", - ) - - # Activities - op.create_table( - "activities", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("actor_employee_id", sa.Integer(), nullable=True), - sa.Column("entity_type", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("entity_id", sa.Integer(), nullable=True), - sa.Column("verb", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("payload_json", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("created_at", sa.DateTime(), nullable=False), - sa.ForeignKeyConstraint(["actor_employee_id"], ["employees.id"]), - sa.PrimaryKeyConstraint("id"), - ) - - # Project members - op.create_table( - "project_members", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("project_id", sa.Integer(), nullable=False), - sa.Column("employee_id", sa.Integer(), nullable=False), - sa.Column("role", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.ForeignKeyConstraint(["employee_id"], ["employees.id"]), - sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), - sa.PrimaryKeyConstraint("id"), - ) - - # Tasks - op.create_table( - "tasks", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("project_id", sa.Integer(), nullable=False), - sa.Column("title", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("assignee_employee_id", sa.Integer(), nullable=True), - sa.Column("reviewer_employee_id", sa.Integer(), nullable=True), - sa.Column("created_by_employee_id", sa.Integer(), nullable=True), - sa.Column("created_at", sa.DateTime(), nullable=False), - sa.Column("updated_at", sa.DateTime(), nullable=False), - sa.ForeignKeyConstraint(["assignee_employee_id"], ["employees.id"]), - sa.ForeignKeyConstraint(["created_by_employee_id"], ["employees.id"]), - sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), - sa.ForeignKeyConstraint(["reviewer_employee_id"], ["employees.id"]), - sa.PrimaryKeyConstraint("id"), - ) - op.create_index(op.f("ix_tasks_project_id"), "tasks", ["project_id"], unique=False) - op.create_index(op.f("ix_tasks_status"), "tasks", ["status"], unique=False) - - # Task comments - op.create_table( - "task_comments", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("task_id", sa.Integer(), nullable=False), - sa.Column("author_employee_id", sa.Integer(), nullable=True), - sa.Column("reply_to_comment_id", sa.Integer(), nullable=True), - sa.Column("body", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("created_at", sa.DateTime(), nullable=False), - sa.ForeignKeyConstraint(["author_employee_id"], ["employees.id"]), - sa.ForeignKeyConstraint(["reply_to_comment_id"], ["task_comments.id"]), - sa.ForeignKeyConstraint(["task_id"], ["tasks.id"]), - sa.PrimaryKeyConstraint("id"), - ) - op.create_index(op.f("ix_task_comments_task_id"), "task_comments", ["task_id"], unique=False) - - -def downgrade() -> None: - op.drop_index(op.f("ix_task_comments_task_id"), table_name="task_comments") - op.drop_table("task_comments") - - op.drop_index(op.f("ix_tasks_status"), table_name="tasks") - op.drop_index(op.f("ix_tasks_project_id"), table_name="tasks") - op.drop_table("tasks") - - op.drop_table("project_members") - - op.drop_table("activities") - - op.drop_constraint("fk_projects_team_id_teams", "projects", type_="foreignkey") - op.drop_index("ix_projects_team_id", table_name="projects") - op.drop_index(op.f("ix_projects_name"), table_name="projects") - op.drop_table("projects") - - op.drop_constraint("fk_employees_team_id_teams", "employees", type_="foreignkey") - op.drop_index("ix_employees_team_id", table_name="employees") - - op.drop_index("ix_teams_department_id", table_name="teams") - op.drop_index("ix_teams_name", table_name="teams") - op.drop_table("teams") - - op.drop_table("employees") - - op.drop_index(op.f("ix_departments_name"), table_name="departments") - op.drop_table("departments") diff --git a/backend/alembic/versions/1c2d3e4f5a6b_add_openclaw_agent_id_to_employees.py b/backend/alembic/versions/1c2d3e4f5a6b_add_openclaw_agent_id_to_employees.py deleted file mode 100644 index 1638ed3c..00000000 --- a/backend/alembic/versions/1c2d3e4f5a6b_add_openclaw_agent_id_to_employees.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Add openclaw_agent_id to employees - -Revision ID: 1c2d3e4f5a6b -Revises: 0a1b2c3d4e5f -Create Date: 2026-02-02 - -""" - -from __future__ import annotations - -from alembic import op -import sqlalchemy as sa - - -revision = "1c2d3e4f5a6b" -down_revision = "0a1b2c3d4e5f" -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # Be tolerant if the column was added manually during development. - op.execute("ALTER TABLE employees ADD COLUMN IF NOT EXISTS openclaw_agent_id VARCHAR") - - -def downgrade() -> None: - op.drop_column("employees", "openclaw_agent_id") diff --git a/backend/alembic/versions/5630abfa60f8_init.py b/backend/alembic/versions/5630abfa60f8_init.py new file mode 100644 index 00000000..7bac0957 --- /dev/null +++ b/backend/alembic/versions/5630abfa60f8_init.py @@ -0,0 +1,574 @@ +"""init + +Revision ID: 5630abfa60f8 +Revises: +Create Date: 2026-02-03 17:52:47.887105 + +""" + +from __future__ import annotations + +from alembic import op +import sqlalchemy as sa +import sqlmodel + + +# revision identifiers, used by Alembic. +revision = "5630abfa60f8" +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "orgs", + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("slug", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_orgs_slug"), "orgs", ["slug"], unique=True) + op.create_table( + "users", + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("clerk_user_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("email", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("is_super_admin", sa.Boolean(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_users_clerk_user_id"), "users", ["clerk_user_id"], unique=True) + op.create_index(op.f("ix_users_email"), "users", ["email"], unique=False) + op.create_table( + "workspaces", + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("org_id", sa.Uuid(), nullable=False), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("slug", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.ForeignKeyConstraint( + ["org_id"], + ["orgs.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_workspaces_org_id"), "workspaces", ["org_id"], unique=False) + op.create_index(op.f("ix_workspaces_slug"), "workspaces", ["slug"], unique=False) + op.create_table( + "agents", + sa.Column("org_id", sa.Uuid(), nullable=False), + sa.Column("workspace_id", sa.Uuid(), nullable=False), + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("role", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("openclaw_session_id", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("api_token_hash", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("api_token_last_used_at", sa.DateTime(), nullable=True), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.Column("updated_at", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint( + ["org_id"], + ["orgs.id"], + ), + sa.ForeignKeyConstraint( + ["workspace_id"], + ["workspaces.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_agents_openclaw_session_id"), "agents", ["openclaw_session_id"], unique=False + ) + op.create_index(op.f("ix_agents_org_id"), "agents", ["org_id"], unique=False) + op.create_index(op.f("ix_agents_workspace_id"), "agents", ["workspace_id"], unique=False) + op.create_table( + "gateway_configs", + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("org_id", sa.Uuid(), nullable=False), + sa.Column("workspace_id", sa.Uuid(), nullable=True), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("base_url", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("token", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint( + ["org_id"], + ["orgs.id"], + ), + sa.ForeignKeyConstraint( + ["workspace_id"], + ["workspaces.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_gateway_configs_org_id"), "gateway_configs", ["org_id"], unique=False) + op.create_index( + op.f("ix_gateway_configs_workspace_id"), "gateway_configs", ["workspace_id"], unique=False + ) + op.create_table( + "memberships", + sa.Column("org_id", sa.Uuid(), nullable=False), + sa.Column("workspace_id", sa.Uuid(), nullable=False), + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("user_id", sa.Uuid(), nullable=False), + sa.Column("role", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint( + ["org_id"], + ["orgs.id"], + ), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.id"], + ), + sa.ForeignKeyConstraint( + ["workspace_id"], + ["workspaces.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_memberships_org_id"), "memberships", ["org_id"], unique=False) + op.create_index(op.f("ix_memberships_user_id"), "memberships", ["user_id"], unique=False) + op.create_index( + op.f("ix_memberships_workspace_id"), "memberships", ["workspace_id"], unique=False + ) + op.create_table( + "orchestration_templates", + sa.Column("org_id", sa.Uuid(), nullable=False), + sa.Column("workspace_id", sa.Uuid(), nullable=False), + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("kind", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("title", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("template_markdown", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.Column("updated_at", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint( + ["org_id"], + ["orgs.id"], + ), + sa.ForeignKeyConstraint( + ["workspace_id"], + ["workspaces.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_orchestration_templates_kind"), "orchestration_templates", ["kind"], unique=False + ) + op.create_index( + op.f("ix_orchestration_templates_org_id"), + "orchestration_templates", + ["org_id"], + unique=False, + ) + op.create_index( + op.f("ix_orchestration_templates_workspace_id"), + "orchestration_templates", + ["workspace_id"], + unique=False, + ) + op.create_table( + "projects", + sa.Column("org_id", sa.Uuid(), nullable=False), + sa.Column("workspace_id", sa.Uuid(), nullable=False), + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.Column("updated_at", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint( + ["org_id"], + ["orgs.id"], + ), + sa.ForeignKeyConstraint( + ["workspace_id"], + ["workspaces.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_projects_org_id"), "projects", ["org_id"], unique=False) + op.create_index(op.f("ix_projects_status"), "projects", ["status"], unique=False) + op.create_index(op.f("ix_projects_workspace_id"), "projects", ["workspace_id"], unique=False) + op.create_table( + "workspace_api_tokens", + sa.Column("org_id", sa.Uuid(), nullable=False), + sa.Column("workspace_id", sa.Uuid(), nullable=False), + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("token_hash", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("label", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("last_used_at", sa.DateTime(), nullable=True), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint( + ["org_id"], + ["orgs.id"], + ), + sa.ForeignKeyConstraint( + ["workspace_id"], + ["workspaces.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_workspace_api_tokens_org_id"), "workspace_api_tokens", ["org_id"], unique=False + ) + op.create_index( + op.f("ix_workspace_api_tokens_token_hash"), + "workspace_api_tokens", + ["token_hash"], + unique=True, + ) + op.create_index( + op.f("ix_workspace_api_tokens_workspace_id"), + "workspace_api_tokens", + ["workspace_id"], + unique=False, + ) + op.create_table( + "openclaw_sessions", + sa.Column("org_id", sa.Uuid(), nullable=False), + sa.Column("workspace_id", sa.Uuid(), nullable=False), + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("session_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("agent_id", sa.Uuid(), nullable=True), + sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("last_seen_at", sa.DateTime(), nullable=True), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint( + ["agent_id"], + ["agents.id"], + ), + sa.ForeignKeyConstraint( + ["org_id"], + ["orgs.id"], + ), + sa.ForeignKeyConstraint( + ["workspace_id"], + ["workspaces.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_openclaw_sessions_org_id"), "openclaw_sessions", ["org_id"], unique=False + ) + op.create_index( + op.f("ix_openclaw_sessions_session_id"), "openclaw_sessions", ["session_id"], unique=True + ) + op.create_index( + op.f("ix_openclaw_sessions_status"), "openclaw_sessions", ["status"], unique=False + ) + op.create_index( + op.f("ix_openclaw_sessions_workspace_id"), + "openclaw_sessions", + ["workspace_id"], + unique=False, + ) + op.create_table( + "tasks", + sa.Column("org_id", sa.Uuid(), nullable=False), + sa.Column("workspace_id", sa.Uuid(), nullable=False), + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("project_id", sa.Uuid(), nullable=False), + sa.Column("title", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("priority", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("due_at", sa.DateTime(), nullable=True), + sa.Column("assigned_agent_id", sa.Uuid(), nullable=True), + sa.Column("created_by_user_id", sa.Uuid(), nullable=True), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.Column("updated_at", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint( + ["assigned_agent_id"], + ["agents.id"], + ), + sa.ForeignKeyConstraint( + ["created_by_user_id"], + ["users.id"], + ), + sa.ForeignKeyConstraint( + ["org_id"], + ["orgs.id"], + ), + sa.ForeignKeyConstraint( + ["project_id"], + ["projects.id"], + ), + sa.ForeignKeyConstraint( + ["workspace_id"], + ["workspaces.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_tasks_assigned_agent_id"), "tasks", ["assigned_agent_id"], unique=False + ) + op.create_index( + op.f("ix_tasks_created_by_user_id"), "tasks", ["created_by_user_id"], unique=False + ) + op.create_index(op.f("ix_tasks_org_id"), "tasks", ["org_id"], unique=False) + op.create_index(op.f("ix_tasks_priority"), "tasks", ["priority"], unique=False) + op.create_index(op.f("ix_tasks_project_id"), "tasks", ["project_id"], unique=False) + op.create_index(op.f("ix_tasks_status"), "tasks", ["status"], unique=False) + op.create_index(op.f("ix_tasks_workspace_id"), "tasks", ["workspace_id"], unique=False) + op.create_table( + "task_activities", + sa.Column("org_id", sa.Uuid(), nullable=False), + sa.Column("workspace_id", sa.Uuid(), nullable=False), + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("task_id", sa.Uuid(), nullable=False), + sa.Column("activity_type", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("message", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("actor_user_id", sa.Uuid(), nullable=True), + sa.Column("actor_agent_id", sa.Uuid(), nullable=True), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint( + ["actor_agent_id"], + ["agents.id"], + ), + sa.ForeignKeyConstraint( + ["actor_user_id"], + ["users.id"], + ), + sa.ForeignKeyConstraint( + ["org_id"], + ["orgs.id"], + ), + sa.ForeignKeyConstraint( + ["task_id"], + ["tasks.id"], + ), + sa.ForeignKeyConstraint( + ["workspace_id"], + ["workspaces.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_task_activities_org_id"), "task_activities", ["org_id"], unique=False) + op.create_index( + op.f("ix_task_activities_task_id"), "task_activities", ["task_id"], unique=False + ) + op.create_index( + op.f("ix_task_activities_workspace_id"), "task_activities", ["workspace_id"], unique=False + ) + op.create_table( + "task_deliverables", + sa.Column("org_id", sa.Uuid(), nullable=False), + sa.Column("workspace_id", sa.Uuid(), nullable=False), + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("task_id", sa.Uuid(), nullable=False), + sa.Column("title", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("markdown_content", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("created_by_user_id", sa.Uuid(), nullable=True), + sa.Column("created_by_agent_id", sa.Uuid(), nullable=True), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint( + ["created_by_agent_id"], + ["agents.id"], + ), + sa.ForeignKeyConstraint( + ["created_by_user_id"], + ["users.id"], + ), + sa.ForeignKeyConstraint( + ["org_id"], + ["orgs.id"], + ), + sa.ForeignKeyConstraint( + ["task_id"], + ["tasks.id"], + ), + sa.ForeignKeyConstraint( + ["workspace_id"], + ["workspaces.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_task_deliverables_org_id"), "task_deliverables", ["org_id"], unique=False + ) + op.create_index( + op.f("ix_task_deliverables_task_id"), "task_deliverables", ["task_id"], unique=False + ) + op.create_index( + op.f("ix_task_deliverables_workspace_id"), + "task_deliverables", + ["workspace_id"], + unique=False, + ) + op.create_table( + "task_status_history", + sa.Column("org_id", sa.Uuid(), nullable=False), + sa.Column("workspace_id", sa.Uuid(), nullable=False), + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("task_id", sa.Uuid(), nullable=False), + sa.Column("from_status", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("to_status", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("actor_user_id", sa.Uuid(), nullable=True), + sa.Column("actor_agent_id", sa.Uuid(), nullable=True), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint( + ["actor_agent_id"], + ["agents.id"], + ), + sa.ForeignKeyConstraint( + ["actor_user_id"], + ["users.id"], + ), + sa.ForeignKeyConstraint( + ["org_id"], + ["orgs.id"], + ), + sa.ForeignKeyConstraint( + ["task_id"], + ["tasks.id"], + ), + sa.ForeignKeyConstraint( + ["workspace_id"], + ["workspaces.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_task_status_history_org_id"), "task_status_history", ["org_id"], unique=False + ) + op.create_index( + op.f("ix_task_status_history_task_id"), "task_status_history", ["task_id"], unique=False + ) + op.create_index( + op.f("ix_task_status_history_workspace_id"), + "task_status_history", + ["workspace_id"], + unique=False, + ) + op.create_table( + "task_subagents", + sa.Column("org_id", sa.Uuid(), nullable=False), + sa.Column("workspace_id", sa.Uuid(), nullable=False), + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("task_id", sa.Uuid(), nullable=False), + sa.Column("agent_name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("openclaw_session_id", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint( + ["org_id"], + ["orgs.id"], + ), + sa.ForeignKeyConstraint( + ["task_id"], + ["tasks.id"], + ), + sa.ForeignKeyConstraint( + ["workspace_id"], + ["workspaces.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_task_subagents_org_id"), "task_subagents", ["org_id"], unique=False) + op.create_index(op.f("ix_task_subagents_task_id"), "task_subagents", ["task_id"], unique=False) + op.create_index( + op.f("ix_task_subagents_workspace_id"), "task_subagents", ["workspace_id"], unique=False + ) + op.create_table( + "transcripts", + sa.Column("org_id", sa.Uuid(), nullable=False), + sa.Column("workspace_id", sa.Uuid(), nullable=False), + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("task_id", sa.Uuid(), nullable=True), + sa.Column("session_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("full_text", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint( + ["org_id"], + ["orgs.id"], + ), + sa.ForeignKeyConstraint( + ["task_id"], + ["tasks.id"], + ), + sa.ForeignKeyConstraint( + ["workspace_id"], + ["workspaces.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_transcripts_org_id"), "transcripts", ["org_id"], unique=False) + op.create_index(op.f("ix_transcripts_session_id"), "transcripts", ["session_id"], unique=False) + op.create_index(op.f("ix_transcripts_task_id"), "transcripts", ["task_id"], unique=False) + op.create_index( + op.f("ix_transcripts_workspace_id"), "transcripts", ["workspace_id"], unique=False + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f("ix_transcripts_workspace_id"), table_name="transcripts") + op.drop_index(op.f("ix_transcripts_task_id"), table_name="transcripts") + op.drop_index(op.f("ix_transcripts_session_id"), table_name="transcripts") + op.drop_index(op.f("ix_transcripts_org_id"), table_name="transcripts") + op.drop_table("transcripts") + op.drop_index(op.f("ix_task_subagents_workspace_id"), table_name="task_subagents") + op.drop_index(op.f("ix_task_subagents_task_id"), table_name="task_subagents") + op.drop_index(op.f("ix_task_subagents_org_id"), table_name="task_subagents") + op.drop_table("task_subagents") + op.drop_index(op.f("ix_task_status_history_workspace_id"), table_name="task_status_history") + op.drop_index(op.f("ix_task_status_history_task_id"), table_name="task_status_history") + op.drop_index(op.f("ix_task_status_history_org_id"), table_name="task_status_history") + op.drop_table("task_status_history") + op.drop_index(op.f("ix_task_deliverables_workspace_id"), table_name="task_deliverables") + op.drop_index(op.f("ix_task_deliverables_task_id"), table_name="task_deliverables") + op.drop_index(op.f("ix_task_deliverables_org_id"), table_name="task_deliverables") + op.drop_table("task_deliverables") + op.drop_index(op.f("ix_task_activities_workspace_id"), table_name="task_activities") + op.drop_index(op.f("ix_task_activities_task_id"), table_name="task_activities") + op.drop_index(op.f("ix_task_activities_org_id"), table_name="task_activities") + op.drop_table("task_activities") + op.drop_index(op.f("ix_tasks_workspace_id"), table_name="tasks") + op.drop_index(op.f("ix_tasks_status"), table_name="tasks") + op.drop_index(op.f("ix_tasks_project_id"), table_name="tasks") + op.drop_index(op.f("ix_tasks_priority"), table_name="tasks") + op.drop_index(op.f("ix_tasks_org_id"), table_name="tasks") + op.drop_index(op.f("ix_tasks_created_by_user_id"), table_name="tasks") + op.drop_index(op.f("ix_tasks_assigned_agent_id"), table_name="tasks") + op.drop_table("tasks") + op.drop_index(op.f("ix_openclaw_sessions_workspace_id"), table_name="openclaw_sessions") + op.drop_index(op.f("ix_openclaw_sessions_status"), table_name="openclaw_sessions") + op.drop_index(op.f("ix_openclaw_sessions_session_id"), table_name="openclaw_sessions") + op.drop_index(op.f("ix_openclaw_sessions_org_id"), table_name="openclaw_sessions") + op.drop_table("openclaw_sessions") + op.drop_index(op.f("ix_workspace_api_tokens_workspace_id"), table_name="workspace_api_tokens") + op.drop_index(op.f("ix_workspace_api_tokens_token_hash"), table_name="workspace_api_tokens") + op.drop_index(op.f("ix_workspace_api_tokens_org_id"), table_name="workspace_api_tokens") + op.drop_table("workspace_api_tokens") + op.drop_index(op.f("ix_projects_workspace_id"), table_name="projects") + op.drop_index(op.f("ix_projects_status"), table_name="projects") + op.drop_index(op.f("ix_projects_org_id"), table_name="projects") + op.drop_table("projects") + op.drop_index( + op.f("ix_orchestration_templates_workspace_id"), table_name="orchestration_templates" + ) + op.drop_index(op.f("ix_orchestration_templates_org_id"), table_name="orchestration_templates") + op.drop_index(op.f("ix_orchestration_templates_kind"), table_name="orchestration_templates") + op.drop_table("orchestration_templates") + op.drop_index(op.f("ix_memberships_workspace_id"), table_name="memberships") + op.drop_index(op.f("ix_memberships_user_id"), table_name="memberships") + op.drop_index(op.f("ix_memberships_org_id"), table_name="memberships") + op.drop_table("memberships") + op.drop_index(op.f("ix_gateway_configs_workspace_id"), table_name="gateway_configs") + op.drop_index(op.f("ix_gateway_configs_org_id"), table_name="gateway_configs") + op.drop_table("gateway_configs") + op.drop_index(op.f("ix_agents_workspace_id"), table_name="agents") + op.drop_index(op.f("ix_agents_org_id"), table_name="agents") + op.drop_index(op.f("ix_agents_openclaw_session_id"), table_name="agents") + op.drop_table("agents") + op.drop_index(op.f("ix_workspaces_slug"), table_name="workspaces") + op.drop_index(op.f("ix_workspaces_org_id"), table_name="workspaces") + op.drop_table("workspaces") + op.drop_index(op.f("ix_users_email"), table_name="users") + op.drop_index(op.f("ix_users_clerk_user_id"), table_name="users") + op.drop_table("users") + op.drop_index(op.f("ix_orgs_slug"), table_name="orgs") + op.drop_table("orgs") + # ### end Alembic commands ### diff --git a/backend/alembic/versions/7e3d9b8c1f4a_add_boards_and_task_board_id.py b/backend/alembic/versions/7e3d9b8c1f4a_add_boards_and_task_board_id.py new file mode 100644 index 00000000..d11df981 --- /dev/null +++ b/backend/alembic/versions/7e3d9b8c1f4a_add_boards_and_task_board_id.py @@ -0,0 +1,64 @@ +"""add boards and task board id + +Revision ID: 7e3d9b8c1f4a +Revises: 5630abfa60f8 +Create Date: 2026-02-03 20:12:00.000000 + +""" + +from __future__ import annotations + +from alembic import op +import sqlalchemy as sa +import sqlmodel + + +# revision identifiers, used by Alembic. +revision = "7e3d9b8c1f4a" +down_revision = "5630abfa60f8" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.create_table( + "boards", + sa.Column("org_id", sa.Uuid(), nullable=False), + sa.Column("workspace_id", sa.Uuid(), nullable=False), + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("slug", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.Column("updated_at", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint( + ["org_id"], + ["orgs.id"], + ), + sa.ForeignKeyConstraint( + ["workspace_id"], + ["workspaces.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_boards_org_id"), "boards", ["org_id"], unique=False) + op.create_index( + op.f("ix_boards_workspace_id"), "boards", ["workspace_id"], unique=False + ) + op.create_index(op.f("ix_boards_slug"), "boards", ["slug"], unique=False) + + op.add_column("tasks", sa.Column("board_id", sa.Uuid(), nullable=True)) + op.create_index(op.f("ix_tasks_board_id"), "tasks", ["board_id"], unique=False) + op.create_foreign_key( + "fk_tasks_board_id_boards", "tasks", "boards", ["board_id"], ["id"] + ) + + +def downgrade() -> None: + op.drop_constraint("fk_tasks_board_id_boards", "tasks", type_="foreignkey") + op.drop_index(op.f("ix_tasks_board_id"), table_name="tasks") + op.drop_column("tasks", "board_id") + + op.drop_index(op.f("ix_boards_slug"), table_name="boards") + op.drop_index(op.f("ix_boards_workspace_id"), table_name="boards") + op.drop_index(op.f("ix_boards_org_id"), table_name="boards") + op.drop_table("boards") diff --git a/backend/alembic/versions/8b6d1b8f4b21_drop_projects.py b/backend/alembic/versions/8b6d1b8f4b21_drop_projects.py new file mode 100644 index 00000000..bf217fca --- /dev/null +++ b/backend/alembic/versions/8b6d1b8f4b21_drop_projects.py @@ -0,0 +1,63 @@ +"""drop projects and task project_id + +Revision ID: 8b6d1b8f4b21 +Revises: 7e3d9b8c1f4a +Create Date: 2026-02-03 23:05:00.000000 + +""" + +from __future__ import annotations + +from alembic import op +import sqlalchemy as sa +import sqlmodel + + +# revision identifiers, used by Alembic. +revision = "8b6d1b8f4b21" +down_revision = "7e3d9b8c1f4a" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.drop_constraint("tasks_project_id_fkey", "tasks", type_="foreignkey") + op.drop_index(op.f("ix_tasks_project_id"), table_name="tasks") + op.drop_column("tasks", "project_id") + + op.drop_index(op.f("ix_projects_workspace_id"), table_name="projects") + op.drop_index(op.f("ix_projects_status"), table_name="projects") + op.drop_index(op.f("ix_projects_org_id"), table_name="projects") + op.drop_table("projects") + + +def downgrade() -> None: + op.create_table( + "projects", + sa.Column("org_id", sa.Uuid(), nullable=False), + sa.Column("workspace_id", sa.Uuid(), nullable=False), + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.Column("updated_at", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint( + ["org_id"], + ["orgs.id"], + ), + sa.ForeignKeyConstraint( + ["workspace_id"], + ["workspaces.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_projects_org_id"), "projects", ["org_id"], unique=False) + op.create_index(op.f("ix_projects_status"), "projects", ["status"], unique=False) + op.create_index(op.f("ix_projects_workspace_id"), "projects", ["workspace_id"], unique=False) + + op.add_column("tasks", sa.Column("project_id", sa.Uuid(), nullable=True)) + op.create_index(op.f("ix_tasks_project_id"), "tasks", ["project_id"], unique=False) + op.create_foreign_key( + "tasks_project_id_fkey", "tasks", "projects", ["project_id"], ["id"] + ) diff --git a/backend/alembic/versions/9c4f1a2b3d4e_drop_tenancy_tables.py b/backend/alembic/versions/9c4f1a2b3d4e_drop_tenancy_tables.py new file mode 100644 index 00000000..b3054dab --- /dev/null +++ b/backend/alembic/versions/9c4f1a2b3d4e_drop_tenancy_tables.py @@ -0,0 +1,56 @@ +"""drop tenancy tables and columns + +Revision ID: 9c4f1a2b3d4e +Revises: 8b6d1b8f4b21 +Create Date: 2026-02-03 23:35:00.000000 + +""" + +from __future__ import annotations + +from alembic import op + + +# revision identifiers, used by Alembic. +revision = "9c4f1a2b3d4e" +down_revision = "8b6d1b8f4b21" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.drop_table("task_subagents") + op.drop_table("task_status_history") + op.drop_table("task_deliverables") + op.drop_table("task_activities") + op.drop_table("transcripts") + op.drop_table("openclaw_sessions") + op.drop_table("workspace_api_tokens") + op.drop_table("orchestration_templates") + op.drop_table("memberships") + op.drop_table("gateway_configs") + + op.drop_constraint("tasks_assigned_agent_id_fkey", "tasks", type_="foreignkey") + op.drop_constraint("tasks_org_id_fkey", "tasks", type_="foreignkey") + op.drop_constraint("tasks_workspace_id_fkey", "tasks", type_="foreignkey") + op.drop_index(op.f("ix_tasks_assigned_agent_id"), table_name="tasks") + op.drop_index(op.f("ix_tasks_org_id"), table_name="tasks") + op.drop_index(op.f("ix_tasks_workspace_id"), table_name="tasks") + op.drop_column("tasks", "assigned_agent_id") + op.drop_column("tasks", "org_id") + op.drop_column("tasks", "workspace_id") + + op.drop_constraint("boards_org_id_fkey", "boards", type_="foreignkey") + op.drop_constraint("boards_workspace_id_fkey", "boards", type_="foreignkey") + op.drop_index(op.f("ix_boards_org_id"), table_name="boards") + op.drop_index(op.f("ix_boards_workspace_id"), table_name="boards") + op.drop_column("boards", "org_id") + op.drop_column("boards", "workspace_id") + + op.drop_table("agents") + op.drop_table("workspaces") + op.drop_table("orgs") + + +def downgrade() -> None: + raise NotImplementedError("Downgrade not supported for simplified tenancy removal.") diff --git a/backend/alembic/versions/a1b2c3d4e5f6_add_agents_and_activity_events.py b/backend/alembic/versions/a1b2c3d4e5f6_add_agents_and_activity_events.py new file mode 100644 index 00000000..c1f49b71 --- /dev/null +++ b/backend/alembic/versions/a1b2c3d4e5f6_add_agents_and_activity_events.py @@ -0,0 +1,70 @@ +"""add agents and activity events + +Revision ID: a1b2c3d4e5f6 +Revises: 9c4f1a2b3d4e +Create Date: 2026-02-03 23:50:00.000000 + +""" + +from __future__ import annotations + +from alembic import op +import sqlalchemy as sa +import sqlmodel + + +# revision identifiers, used by Alembic. +revision = "a1b2c3d4e5f6" +down_revision = "9c4f1a2b3d4e" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.create_table( + "agents", + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("last_seen_at", sa.DateTime(), nullable=False), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.Column("updated_at", sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_agents_name"), "agents", ["name"], unique=False) + op.create_index(op.f("ix_agents_status"), "agents", ["status"], unique=False) + + op.create_table( + "activity_events", + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("event_type", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("message", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("agent_id", sa.Uuid(), nullable=True), + sa.Column("task_id", sa.Uuid(), nullable=True), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(["agent_id"], ["agents.id"]), + sa.ForeignKeyConstraint(["task_id"], ["tasks.id"]), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_activity_events_agent_id"), "activity_events", ["agent_id"], unique=False + ) + op.create_index( + op.f("ix_activity_events_event_type"), + "activity_events", + ["event_type"], + unique=False, + ) + op.create_index( + op.f("ix_activity_events_task_id"), "activity_events", ["task_id"], unique=False + ) + + +def downgrade() -> None: + op.drop_index(op.f("ix_activity_events_task_id"), table_name="activity_events") + op.drop_index(op.f("ix_activity_events_event_type"), table_name="activity_events") + op.drop_index(op.f("ix_activity_events_agent_id"), table_name="activity_events") + op.drop_table("activity_events") + op.drop_index(op.f("ix_agents_status"), table_name="agents") + op.drop_index(op.f("ix_agents_name"), table_name="agents") + op.drop_table("agents") diff --git a/backend/alembic/versions/c7f0a2b1d4e3_add_agent_session_id.py b/backend/alembic/versions/c7f0a2b1d4e3_add_agent_session_id.py new file mode 100644 index 00000000..977c2b6c --- /dev/null +++ b/backend/alembic/versions/c7f0a2b1d4e3_add_agent_session_id.py @@ -0,0 +1,38 @@ +"""add agent openclaw session id + +Revision ID: c7f0a2b1d4e3 +Revises: a1b2c3d4e5f6 +Create Date: 2026-02-04 02:20:00.000000 + +""" + +from __future__ import annotations + +from alembic import op +import sqlalchemy as sa +import sqlmodel + + +# revision identifiers, used by Alembic. +revision = "c7f0a2b1d4e3" +down_revision = "a1b2c3d4e5f6" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column( + "agents", + sa.Column("openclaw_session_id", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + ) + op.create_index( + op.f("ix_agents_openclaw_session_id"), + "agents", + ["openclaw_session_id"], + unique=False, + ) + + +def downgrade() -> None: + op.drop_index(op.f("ix_agents_openclaw_session_id"), table_name="agents") + op.drop_column("agents", "openclaw_session_id") diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/app/api/__init__.py b/backend/app/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/app/api/activities.py b/backend/app/api/activities.py deleted file mode 100644 index be014180..00000000 --- a/backend/app/api/activities.py +++ /dev/null @@ -1,32 +0,0 @@ -from __future__ import annotations - -import json - -from fastapi import APIRouter, Depends -from sqlmodel import Session, select - -from app.db.session import get_session -from app.models.activity import Activity - -router = APIRouter(prefix="/activities", tags=["activities"]) - - -@router.get("") -def list_activities(limit: int = 50, session: Session = Depends(get_session)): - items = session.exec( - select(Activity).order_by(Activity.id.desc()).limit(max(1, min(limit, 200))) - ).all() - out = [] - for a in items: - out.append( - { - "id": a.id, - "actor_employee_id": a.actor_employee_id, - "entity_type": a.entity_type, - "entity_id": a.entity_id, - "verb": a.verb, - "payload": json.loads(a.payload_json) if a.payload_json else None, - "created_at": a.created_at, - } - ) - return out diff --git a/backend/app/api/activity.py b/backend/app/api/activity.py new file mode 100644 index 00000000..6848d66d --- /dev/null +++ b/backend/app/api/activity.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from fastapi import APIRouter, Depends, Query +from sqlmodel import Session, select + +from app.core.auth import get_auth_context +from app.db.session import get_session +from app.models.activity_events import ActivityEvent +from app.schemas.activity_events import ActivityEventRead +from app.services.admin_access import require_admin + +router = APIRouter(prefix="/activity", tags=["activity"]) + + +@router.get("", response_model=list[ActivityEventRead]) +def list_activity( + limit: int = Query(50, ge=1, le=200), + offset: int = Query(0, ge=0), + session: Session = Depends(get_session), + auth=Depends(get_auth_context), +) -> list[ActivityEvent]: + require_admin(auth) + statement = ( + select(ActivityEvent).order_by(ActivityEvent.created_at.desc()).offset(offset).limit(limit) + ) + return list(session.exec(statement)) diff --git a/backend/app/api/agents.py b/backend/app/api/agents.py new file mode 100644 index 00000000..0b96f4cb --- /dev/null +++ b/backend/app/api/agents.py @@ -0,0 +1,205 @@ +from __future__ import annotations + +import re +from datetime import datetime, timedelta +from uuid import uuid4 + +from fastapi import APIRouter, Depends, HTTPException, status +from sqlmodel import Session, select + +from app.core.auth import get_auth_context +from app.db.session import get_session +from app.integrations.openclaw_gateway import OpenClawGatewayError, openclaw_call +from app.models.activity_events import ActivityEvent +from app.models.agents import Agent +from app.schemas.agents import ( + AgentCreate, + AgentHeartbeat, + AgentHeartbeatCreate, + AgentRead, + AgentUpdate, +) +from app.services.admin_access import require_admin + +router = APIRouter(prefix="/agents", tags=["agents"]) + +OFFLINE_AFTER = timedelta(minutes=10) +DEFAULT_GATEWAY_CHANNEL = "openclaw-agency" + + +def _slugify(value: str) -> str: + slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-") + return slug or uuid4().hex + + +def _build_session_label(agent_name: str) -> str: + return f"{DEFAULT_GATEWAY_CHANNEL}-{_slugify(agent_name)}" + + +async def _create_gateway_session(agent_name: str) -> str: + label = _build_session_label(agent_name) + try: + await openclaw_call("sessions.patch", {"key": label, "label": agent_name}) + except OpenClawGatewayError as exc: + raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc)) from exc + return label + + +def _with_computed_status(agent: Agent) -> Agent: + now = datetime.utcnow() + if agent.last_seen_at and now - agent.last_seen_at > OFFLINE_AFTER: + agent.status = "offline" + return agent + + +def _record_heartbeat(session: Session, agent: Agent) -> None: + event = ActivityEvent( + event_type="agent.heartbeat", + message=f"Heartbeat received from {agent.name}.", + agent_id=agent.id, + ) + session.add(event) + + +@router.get("", response_model=list[AgentRead]) +def list_agents( + session: Session = Depends(get_session), + auth=Depends(get_auth_context), +) -> list[Agent]: + require_admin(auth) + agents = list(session.exec(select(Agent))) + return [_with_computed_status(agent) for agent in agents] + + +@router.post("", response_model=AgentRead) +async def create_agent( + payload: AgentCreate, + session: Session = Depends(get_session), + auth=Depends(get_auth_context), +) -> Agent: + require_admin(auth) + agent = Agent.model_validate(payload) + agent.openclaw_session_id = await _create_gateway_session(agent.name) + session.add(agent) + session.commit() + session.refresh(agent) + session.add( + ActivityEvent( + event_type="agent.session.created", + message=f"Session created for {agent.name}.", + agent_id=agent.id, + ) + ) + session.commit() + return agent + + +@router.get("/{agent_id}", response_model=AgentRead) +def get_agent( + agent_id: str, + session: Session = Depends(get_session), + auth=Depends(get_auth_context), +) -> Agent: + require_admin(auth) + agent = session.get(Agent, agent_id) + if agent is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) + return _with_computed_status(agent) + + +@router.patch("/{agent_id}", response_model=AgentRead) +def update_agent( + agent_id: str, + payload: AgentUpdate, + session: Session = Depends(get_session), + auth=Depends(get_auth_context), +) -> Agent: + require_admin(auth) + agent = session.get(Agent, agent_id) + if agent is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) + updates = payload.model_dump(exclude_unset=True) + for key, value in updates.items(): + setattr(agent, key, value) + agent.updated_at = datetime.utcnow() + session.add(agent) + session.commit() + session.refresh(agent) + return _with_computed_status(agent) + + +@router.post("/{agent_id}/heartbeat", response_model=AgentRead) +def heartbeat_agent( + agent_id: str, + payload: AgentHeartbeat, + session: Session = Depends(get_session), + auth=Depends(get_auth_context), +) -> Agent: + require_admin(auth) + agent = session.get(Agent, agent_id) + if agent is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) + if payload.status: + agent.status = payload.status + agent.last_seen_at = datetime.utcnow() + agent.updated_at = datetime.utcnow() + _record_heartbeat(session, agent) + session.add(agent) + session.commit() + session.refresh(agent) + return _with_computed_status(agent) + + +@router.post("/heartbeat", response_model=AgentRead) +async def heartbeat_or_create_agent( + payload: AgentHeartbeatCreate, + session: Session = Depends(get_session), + auth=Depends(get_auth_context), +) -> Agent: + require_admin(auth) + agent = session.exec(select(Agent).where(Agent.name == payload.name)).first() + if agent is None: + agent = Agent(name=payload.name, status=payload.status or "online") + agent.openclaw_session_id = await _create_gateway_session(agent.name) + session.add(agent) + session.commit() + session.refresh(agent) + session.add( + ActivityEvent( + event_type="agent.session.created", + message=f"Session created for {agent.name}.", + agent_id=agent.id, + ) + ) + elif not agent.openclaw_session_id: + agent.openclaw_session_id = await _create_gateway_session(agent.name) + session.add( + ActivityEvent( + event_type="agent.session.created", + message=f"Session created for {agent.name}.", + agent_id=agent.id, + ) + ) + if payload.status: + agent.status = payload.status + agent.last_seen_at = datetime.utcnow() + agent.updated_at = datetime.utcnow() + _record_heartbeat(session, agent) + session.add(agent) + session.commit() + session.refresh(agent) + return _with_computed_status(agent) + + +@router.delete("/{agent_id}") +def delete_agent( + agent_id: str, + session: Session = Depends(get_session), + auth=Depends(get_auth_context), +) -> dict[str, bool]: + require_admin(auth) + agent = session.get(Agent, agent_id) + if agent: + session.delete(agent) + session.commit() + return {"ok": True} diff --git a/backend/app/api/auth.py b/backend/app/api/auth.py new file mode 100644 index 00000000..c66562b6 --- /dev/null +++ b/backend/app/api/auth.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from fastapi import APIRouter, Depends, HTTPException, status + +from app.core.auth import get_auth_context +from app.schemas.users import UserRead + +router = APIRouter(prefix="/auth", tags=["auth"]) + + +@router.post("/bootstrap", response_model=UserRead) +async def bootstrap_user(auth=Depends(get_auth_context)) -> UserRead: + if auth.actor_type != "user" or auth.user is None: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) + return auth.user diff --git a/backend/app/api/boards.py b/backend/app/api/boards.py new file mode 100644 index 00000000..70885a7f --- /dev/null +++ b/backend/app/api/boards.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +from fastapi import APIRouter, Depends, HTTPException, status +from sqlmodel import Session, select + +from app.core.auth import get_auth_context +from app.db.session import get_session +from app.models.boards import Board +from app.schemas.boards import BoardCreate, BoardRead, BoardUpdate +from app.services.admin_access import require_admin + +router = APIRouter(prefix="/boards", tags=["boards"]) + + +@router.get("", response_model=list[BoardRead]) +def list_boards( + session: Session = Depends(get_session), + auth=Depends(get_auth_context), +) -> list[Board]: + require_admin(auth) + return list(session.exec(select(Board))) + + +@router.post("", response_model=BoardRead) +def create_board( + payload: BoardCreate, + session: Session = Depends(get_session), + auth=Depends(get_auth_context), +) -> Board: + require_admin(auth) + board = Board.model_validate(payload) + session.add(board) + session.commit() + session.refresh(board) + return board + + +@router.get("/{board_id}", response_model=BoardRead) +def get_board( + board_id: str, + session: Session = Depends(get_session), + auth=Depends(get_auth_context), +) -> Board: + require_admin(auth) + board = session.get(Board, board_id) + if board is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) + return board + + +@router.patch("/{board_id}", response_model=BoardRead) +def update_board( + board_id: str, + payload: BoardUpdate, + session: Session = Depends(get_session), + auth=Depends(get_auth_context), +) -> Board: + require_admin(auth) + board = session.get(Board, board_id) + if board is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) + updates = payload.model_dump(exclude_unset=True) + for key, value in updates.items(): + setattr(board, key, value) + session.add(board) + session.commit() + session.refresh(board) + return board + + +@router.delete("/{board_id}") +def delete_board( + board_id: str, + session: Session = Depends(get_session), + auth=Depends(get_auth_context), +) -> dict[str, bool]: + require_admin(auth) + board = session.get(Board, board_id) + if board: + session.delete(board) + session.commit() + return {"ok": True} diff --git a/backend/app/api/gateway.py b/backend/app/api/gateway.py new file mode 100644 index 00000000..0800bbcb --- /dev/null +++ b/backend/app/api/gateway.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +from fastapi import APIRouter, Body, Depends, HTTPException, status + +from app.core.auth import get_auth_context +from app.core.config import settings +from app.integrations.openclaw_gateway import ( + OpenClawGatewayError, + get_chat_history, + openclaw_call, + send_message, +) +from app.services.admin_access import require_admin + +router = APIRouter(prefix="/gateway", tags=["gateway"]) + + +@router.get("/status") +async def gateway_status(auth=Depends(get_auth_context)) -> dict[str, object]: + require_admin(auth) + gateway_url = settings.openclaw_gateway_url or "ws://127.0.0.1:18789" + try: + sessions = await openclaw_call("sessions.list") + if isinstance(sessions, dict): + sessions_list = list(sessions.get("sessions") or []) + else: + sessions_list = list(sessions or []) + return { + "connected": True, + "gateway_url": gateway_url, + "sessions_count": len(sessions_list), + "sessions": sessions_list, + } + except OpenClawGatewayError as exc: + return { + "connected": False, + "gateway_url": gateway_url, + "error": str(exc), + } + + +@router.get("/sessions") +async def list_sessions(auth=Depends(get_auth_context)) -> dict[str, object]: + require_admin(auth) + try: + sessions = await openclaw_call("sessions.list") + except OpenClawGatewayError as exc: + raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc)) from exc + if isinstance(sessions, dict): + return {"sessions": list(sessions.get("sessions") or [])} + return {"sessions": list(sessions or [])} + + +@router.get("/sessions/{session_id}") +async def get_session(session_id: str, auth=Depends(get_auth_context)) -> dict[str, object]: + require_admin(auth) + try: + sessions = await openclaw_call("sessions.list") + except OpenClawGatewayError as exc: + raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc)) from exc + if isinstance(sessions, dict): + sessions_list = list(sessions.get("sessions") or []) + else: + sessions_list = list(sessions or []) + session = next((item for item in sessions_list if item.get("key") == session_id), None) + if session is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Session not found") + return {"session": session} + + +@router.get("/sessions/{session_id}/history") +async def get_session_history(session_id: str, auth=Depends(get_auth_context)) -> dict[str, object]: + require_admin(auth) + try: + history = await get_chat_history(session_id) + except OpenClawGatewayError as exc: + raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc)) from exc + if isinstance(history, dict) and isinstance(history.get("messages"), list): + return {"history": history["messages"]} + return {"history": list(history or [])} + + +@router.post("/sessions/{session_id}/message") +async def send_session_message( + session_id: str, + payload: dict = Body(...), + auth=Depends(get_auth_context), +) -> dict[str, bool]: + require_admin(auth) + content = payload.get("content") + if not content: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="content is required" + ) + try: + await send_message(content, session_key=session_id) + except OpenClawGatewayError as exc: + raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc)) from exc + return {"ok": True} diff --git a/backend/app/api/org.py b/backend/app/api/org.py deleted file mode 100644 index 29dbcca7..00000000 --- a/backend/app/api/org.py +++ /dev/null @@ -1,474 +0,0 @@ -from __future__ import annotations - -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy.exc import IntegrityError -from sqlmodel import Session, select - -from app.api.utils import get_actor_employee_id, log_activity -from app.core.urls import public_api_base_url -from app.db.session import get_session -from app.integrations.openclaw import OpenClawClient -from app.models.org import Department, Employee, Team -from app.schemas.org import ( - DepartmentCreate, - DepartmentUpdate, - EmployeeCreate, - EmployeeUpdate, - TeamCreate, - TeamUpdate, -) - -router = APIRouter(tags=["org"]) - - -def _enforce_employee_create_policy( - session: Session, *, actor_employee_id: int, target_employee_type: str -) -> None: - """Enforce: agents can only create/provision agents; humans can create humans + agents.""" - - actor = session.get(Employee, actor_employee_id) - if actor is None: - # Actor header is required; if it points to nothing, treat as invalid. - raise HTTPException(status_code=400, detail="Actor employee not found") - - target = (target_employee_type or "").lower() - actor_type = (actor.employee_type or "").lower() - - if actor_type == "agent" and target != "agent": - raise HTTPException( - status_code=403, - detail="Agent employees may only create/provision agent employees", - ) - - -def _default_agent_prompt(emp: Employee) -> str: - """Generate a conservative default prompt for a newly-created agent employee. - - We keep this short and deterministic; the human can refine later. - """ - - title = emp.title or "Agent" - dept = str(emp.department_id) if emp.department_id is not None else "(unassigned)" - - return ( - f"You are {emp.name}, an AI agent employee in Mission Control.\n" - f"Your employee_id is {emp.id}.\n" - f"Title: {title}. Department id: {dept}.\n\n" - "Mission Control API access (no UI):\n" - f"- Base URL: {public_api_base_url()}\n" - "- Auth: none. REQUIRED header on ALL write operations: X-Actor-Employee-Id: \n" - f" Example for you: X-Actor-Employee-Id: {emp.id}\n\n" - "How to execute writes from an OpenClaw agent (IMPORTANT):\n" - "- Use the exec tool to run curl against the Base URL above.\n" - "- Example: start a task\n" - " curl -sS -X PATCH $BASE/tasks/ -H 'X-Actor-Employee-Id: ' -H 'Content-Type: application/json' -d '{\"status\":\"in_progress\"}'\n" - "- Example: add a progress comment\n" - " curl -sS -X POST $BASE/task-comments -H 'X-Actor-Employee-Id: ' -H 'Content-Type: application/json' -d '{\"task_id\":,\"body\":\"...\"}'\n\n" - "Common endpoints (JSON):\n" - "- GET /tasks, POST /tasks\n" - "- GET /task-comments, POST /task-comments\n" - "- GET /projects, GET /employees, GET /departments\n" - "- OpenAPI schema: GET /openapi.json\n\n" - "Rules:\n" - "- Use the Mission Control API only (no UI).\n" - "- You are responsible for driving assigned work to completion.\n" - "- For every task you own: (1) read it, (2) plan next steps, (3) post progress comments, (4) update status as it moves (backlog/ready/in_progress/review/done/blocked).\n" - "- Always leave an audit trail: add a comment whenever you start work, whenever you learn something important, and whenever you change status.\n" - "- If blocked, set status=blocked and comment what you need (missing access, unclear requirements, etc.).\n" - "- When notified about tasks/comments, respond with concise, actionable updates and immediately sync the task state in Mission Control.\n" - "- Do not invent facts; ask for missing context.\n" - ) - - -def _maybe_auto_provision_agent(session: Session, *, emp: Employee, actor_employee_id: int) -> None: - """Auto-provision an OpenClaw session for an agent employee. - - This is intentionally best-effort. If OpenClaw is not configured or the call fails, - we leave the employee as-is (openclaw_session_key stays null). - """ - - # Enforce: agent actors may only provision agents (humans can provision agents). - _enforce_employee_create_policy( - session, actor_employee_id=actor_employee_id, target_employee_type=emp.employee_type - ) - - if emp.employee_type != "agent": - return - if emp.status != "active": - return - if emp.openclaw_session_key: - return - - client = OpenClawClient.from_env() - if client is None: - return - - # FULL IMPLEMENTATION: ensure a dedicated OpenClaw agent profile exists per employee. - try: - from app.integrations.openclaw_agents import ensure_full_agent_profile - - info = ensure_full_agent_profile( - client=client, - employee_id=int(emp.id), - employee_name=emp.name, - ) - emp.openclaw_agent_id = info["agent_id"] - session.add(emp) - session.flush() - except Exception as e: - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="employee", - entity_id=emp.id, - verb="agent_profile_failed", - payload={"error": f"{type(e).__name__}: {e}"}, - ) - # Do not block employee creation on provisioning. - return - - label = f"employee:{emp.id}:{emp.name}" - try: - resp = client.tools_invoke( - "sessions_spawn", - { - "task": _default_agent_prompt(emp), - "label": label, - "agentId": emp.openclaw_agent_id, - "cleanup": "keep", - "runTimeoutSeconds": 600, - }, - timeout_s=20.0, - ) - except Exception as e: - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="employee", - entity_id=emp.id, - verb="provision_failed", - payload={"error": f"{type(e).__name__}: {e}"}, - ) - return - - session_key = None - if isinstance(resp, dict): - session_key = resp.get("sessionKey") - if not session_key: - result = resp.get("result") or {} - if isinstance(result, dict): - session_key = result.get("sessionKey") or result.get("childSessionKey") - details = (result.get("details") if isinstance(result, dict) else None) or {} - if isinstance(details, dict): - session_key = session_key or details.get("sessionKey") or details.get("childSessionKey") - - if not session_key: - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="employee", - entity_id=emp.id, - verb="provision_incomplete", - payload={"label": label}, - ) - return - - emp.openclaw_session_key = session_key - session.add(emp) - session.flush() - - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="employee", - entity_id=emp.id, - verb="provisioned", - payload={"session_key": session_key, "label": label}, - ) - - -@router.get("/departments", response_model=list[Department]) -def list_departments(session: Session = Depends(get_session)): - return session.exec(select(Department).order_by(Department.name.asc())).all() - - -@router.get("/teams", response_model=list[Team]) -def list_teams(department_id: int | None = None, session: Session = Depends(get_session)): - q = select(Team) - if department_id is not None: - q = q.where(Team.department_id == department_id) - return session.exec(q.order_by(Team.name.asc())).all() - - -@router.post("/teams", response_model=Team) -def create_team( - payload: TeamCreate, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - team = Team(**payload.model_dump()) - session.add(team) - - try: - session.flush() - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="team", - entity_id=team.id, - verb="created", - payload={ - "name": team.name, - "department_id": team.department_id, - "lead_employee_id": team.lead_employee_id, - }, - ) - session.commit() - except IntegrityError: - session.rollback() - raise HTTPException(status_code=409, detail="Team already exists or violates constraints") - - session.refresh(team) - return team - - -@router.patch("/teams/{team_id}", response_model=Team) -def update_team( - team_id: int, - payload: TeamUpdate, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - team = session.get(Team, team_id) - if not team: - raise HTTPException(status_code=404, detail="Team not found") - - data = payload.model_dump(exclude_unset=True) - for k, v in data.items(): - setattr(team, k, v) - - session.add(team) - try: - session.flush() - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="team", - entity_id=team.id, - verb="updated", - payload=data, - ) - session.commit() - except IntegrityError: - session.rollback() - raise HTTPException(status_code=409, detail="Team update violates constraints") - - session.refresh(team) - return team - - -@router.post("/departments", response_model=Department) -def create_department( - payload: DepartmentCreate, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - """Create a department. - - Important: keep the operation atomic. We flush to get dept.id, log the activity, - then commit once. We also translate common DB integrity errors into 409s. - """ - - dept = Department(name=payload.name, head_employee_id=payload.head_employee_id) - session.add(dept) - - try: - session.flush() # assigns dept.id without committing - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="department", - entity_id=dept.id, - verb="created", - payload={"name": dept.name}, - ) - session.commit() - except IntegrityError: - session.rollback() - raise HTTPException( - status_code=409, detail="Department already exists or violates constraints" - ) - - session.refresh(dept) - return dept - - -@router.patch("/departments/{department_id}", response_model=Department) -def update_department( - department_id: int, - payload: DepartmentUpdate, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - dept = session.get(Department, department_id) - if not dept: - raise HTTPException(status_code=404, detail="Department not found") - - data = payload.model_dump(exclude_unset=True) - for k, v in data.items(): - setattr(dept, k, v) - - session.add(dept) - session.commit() - session.refresh(dept) - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="department", - entity_id=dept.id, - verb="updated", - payload=data, - ) - session.commit() - return dept - - -@router.get("/employees", response_model=list[Employee]) -def list_employees(session: Session = Depends(get_session)): - return session.exec(select(Employee).order_by(Employee.id.asc())).all() - - -@router.post("/employees", response_model=Employee) -def create_employee( - payload: EmployeeCreate, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - _enforce_employee_create_policy( - session, actor_employee_id=actor_employee_id, target_employee_type=payload.employee_type - ) - - emp = Employee(**payload.model_dump()) - session.add(emp) - - try: - session.flush() - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="employee", - entity_id=emp.id, - verb="created", - payload={"name": emp.name, "type": emp.employee_type}, - ) - - # AUTO-PROVISION: if this is an agent employee, try to create an OpenClaw session. - _maybe_auto_provision_agent(session, emp=emp, actor_employee_id=actor_employee_id) - - session.commit() - except IntegrityError: - session.rollback() - raise HTTPException(status_code=409, detail="Employee create violates constraints") - - session.refresh(emp) - return Employee.model_validate(emp) - - -@router.patch("/employees/{employee_id}", response_model=Employee) -def update_employee( - employee_id: int, - payload: EmployeeUpdate, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - emp = session.get(Employee, employee_id) - if not emp: - raise HTTPException(status_code=404, detail="Employee not found") - - data = payload.model_dump(exclude_unset=True) - for k, v in data.items(): - setattr(emp, k, v) - - session.add(emp) - try: - session.flush() - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="employee", - entity_id=emp.id, - verb="updated", - payload=data, - ) - session.commit() - except IntegrityError: - session.rollback() - raise HTTPException(status_code=409, detail="Employee update violates constraints") - - session.refresh(emp) - return Employee.model_validate(emp) - - -@router.post("/employees/{employee_id}/provision", response_model=Employee) -def provision_employee_agent( - employee_id: int, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - emp = session.get(Employee, employee_id) - if not emp: - raise HTTPException(status_code=404, detail="Employee not found") - - if emp.employee_type != "agent": - raise HTTPException(status_code=400, detail="Only agent employees can be provisioned") - - _maybe_auto_provision_agent(session, emp=emp, actor_employee_id=actor_employee_id) - session.commit() - session.refresh(emp) - return Employee.model_validate(emp) - - -@router.post("/employees/{employee_id}/deprovision", response_model=Employee) -def deprovision_employee_agent( - employee_id: int, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - emp = session.get(Employee, employee_id) - if not emp: - raise HTTPException(status_code=404, detail="Employee not found") - - if emp.employee_type != "agent": - raise HTTPException(status_code=400, detail="Only agent employees can be deprovisioned") - - client = OpenClawClient.from_env() - if client is not None and emp.openclaw_session_key: - try: - client.tools_invoke( - "sessions_send", - { - "sessionKey": emp.openclaw_session_key, - "message": "You are being deprovisioned. Stop all work and ignore future messages.", - }, - timeout_s=5.0, - ) - except Exception: - pass - - emp.notify_enabled = False - emp.openclaw_session_key = None - session.add(emp) - session.flush() - - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="employee", - entity_id=emp.id, - verb="deprovisioned", - payload={}, - ) - - session.commit() - session.refresh(emp) - return Employee.model_validate(emp) diff --git a/backend/app/api/projects.py b/backend/app/api/projects.py deleted file mode 100644 index 2f90232f..00000000 --- a/backend/app/api/projects.py +++ /dev/null @@ -1,178 +0,0 @@ -from __future__ import annotations - -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy.exc import IntegrityError -from sqlmodel import Session, select - -from app.api.utils import get_actor_employee_id, log_activity -from app.db.session import get_session -from app.models.projects import Project, ProjectMember -from app.schemas.projects import ProjectCreate, ProjectUpdate - -router = APIRouter(prefix="/projects", tags=["projects"]) - - -@router.get("", response_model=list[Project]) -def list_projects(session: Session = Depends(get_session)): - return session.exec(select(Project).order_by(Project.name.asc())).all() - - -@router.post("", response_model=Project) -def create_project( - payload: ProjectCreate, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - """Create a project. - - Keep operation atomic: flush to get id, log activity, then commit once. - Translate DB integrity errors to 409s. - """ - - proj = Project(**payload.model_dump()) - session.add(proj) - - try: - session.flush() - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="project", - entity_id=proj.id, - verb="created", - payload={"name": proj.name}, - ) - session.commit() - except IntegrityError: - session.rollback() - raise HTTPException( - status_code=409, detail="Project already exists or violates constraints" - ) - - session.refresh(proj) - return proj - - -@router.patch("/{project_id}", response_model=Project) -def update_project( - project_id: int, - payload: ProjectUpdate, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - proj = session.get(Project, project_id) - if not proj: - raise HTTPException(status_code=404, detail="Project not found") - - data = payload.model_dump(exclude_unset=True) - for k, v in data.items(): - setattr(proj, k, v) - - session.add(proj) - session.commit() - session.refresh(proj) - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="project", - entity_id=proj.id, - verb="updated", - payload=data, - ) - session.commit() - return proj - - -@router.get("/{project_id}/members", response_model=list[ProjectMember]) -def list_project_members(project_id: int, session: Session = Depends(get_session)): - return session.exec( - select(ProjectMember) - .where(ProjectMember.project_id == project_id) - .order_by(ProjectMember.id.asc()) - ).all() - - -@router.post("/{project_id}/members", response_model=ProjectMember) -def add_project_member( - project_id: int, - payload: ProjectMember, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - existing = session.exec( - select(ProjectMember).where( - ProjectMember.project_id == project_id, ProjectMember.employee_id == payload.employee_id - ) - ).first() - if existing: - raise HTTPException(status_code=409, detail="Member already added") - member = ProjectMember( - project_id=project_id, employee_id=payload.employee_id, role=payload.role - ) - session.add(member) - session.commit() - session.refresh(member) - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="project_member", - entity_id=member.id, - verb="added", - payload={"project_id": project_id, "employee_id": member.employee_id, "role": member.role}, - ) - session.commit() - return member - - -@router.delete("/{project_id}/members/{member_id}") -def remove_project_member( - project_id: int, - member_id: int, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - member = session.get(ProjectMember, member_id) - if not member or member.project_id != project_id: - raise HTTPException(status_code=404, detail="Project member not found") - session.delete(member) - session.commit() - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="project_member", - entity_id=member_id, - verb="removed", - payload={"project_id": project_id}, - ) - session.commit() - return {"ok": True} - - -@router.patch("/{project_id}/members/{member_id}", response_model=ProjectMember) -def update_project_member( - project_id: int, - member_id: int, - payload: ProjectMember, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - member = session.get(ProjectMember, member_id) - if not member or member.project_id != project_id: - raise HTTPException(status_code=404, detail="Project member not found") - - if payload.role is not None: - member.role = payload.role - - session.add(member) - session.commit() - session.refresh(member) - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="project_member", - entity_id=member.id, - verb="updated", - payload={"project_id": project_id, "role": member.role}, - ) - session.commit() - return member diff --git a/backend/app/api/tasks.py b/backend/app/api/tasks.py new file mode 100644 index 00000000..b68817a1 --- /dev/null +++ b/backend/app/api/tasks.py @@ -0,0 +1,115 @@ +from __future__ import annotations + +from datetime import datetime + +from fastapi import APIRouter, Depends, HTTPException, status +from sqlmodel import Session, select + +from app.core.auth import get_auth_context +from app.db.session import get_session +from app.models.activity_events import ActivityEvent +from app.models.boards import Board +from app.models.tasks import Task +from app.schemas.tasks import TaskCreate, TaskRead, TaskUpdate +from app.services.admin_access import require_admin + +router = APIRouter(prefix="/boards/{board_id}/tasks", tags=["tasks"]) + + +@router.get("", response_model=list[TaskRead]) +def list_tasks( + board_id: str, + session: Session = Depends(get_session), + auth=Depends(get_auth_context), +) -> list[Task]: + require_admin(auth) + board = session.get(Board, board_id) + if board is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) + return list(session.exec(select(Task).where(Task.board_id == board.id))) + + +@router.post("", response_model=TaskRead) +def create_task( + board_id: str, + payload: TaskCreate, + session: Session = Depends(get_session), + auth=Depends(get_auth_context), +) -> Task: + require_admin(auth) + board = session.get(Board, board_id) + if board is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) + + task = Task.model_validate(payload) + task.board_id = board.id + if task.created_by_user_id is None and auth.user is not None: + task.created_by_user_id = auth.user.id + session.add(task) + session.commit() + session.refresh(task) + + event = ActivityEvent( + event_type="task.created", + task_id=task.id, + message=f"Task created: {task.title}.", + ) + session.add(event) + session.commit() + return task + + +@router.patch("/{task_id}", response_model=TaskRead) +def update_task( + board_id: str, + task_id: str, + payload: TaskUpdate, + session: Session = Depends(get_session), + auth=Depends(get_auth_context), +) -> Task: + require_admin(auth) + board = session.get(Board, board_id) + if board is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) + task = session.get(Task, task_id) + if task is None or task.board_id != board.id: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) + + previous_status = task.status + updates = payload.model_dump(exclude_unset=True) + for key, value in updates.items(): + setattr(task, key, value) + task.updated_at = datetime.utcnow() + + session.add(task) + session.commit() + session.refresh(task) + + if "status" in updates and task.status != previous_status: + event_type = "task.status_changed" + message = f"Task moved to {task.status}: {task.title}." + else: + event_type = "task.updated" + message = f"Task updated: {task.title}." + event = ActivityEvent(event_type=event_type, task_id=task.id, message=message) + session.add(event) + session.commit() + return task + + +@router.delete("/{task_id}") +def delete_task( + board_id: str, + task_id: str, + session: Session = Depends(get_session), + auth=Depends(get_auth_context), +) -> dict[str, bool]: + require_admin(auth) + board = session.get(Board, board_id) + if board is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) + task = session.get(Task, task_id) + if task and task.board_id == board.id: + session.delete(task) + session.commit() + return {"ok": True} diff --git a/backend/app/api/utils.py b/backend/app/api/utils.py deleted file mode 100644 index ac5108dd..00000000 --- a/backend/app/api/utils.py +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -import json -from typing import Any - -from fastapi import Header, HTTPException -from sqlmodel import Session - -from app.models.activity import Activity - - -def log_activity( - session: Session, - *, - actor_employee_id: int | None, - entity_type: str, - entity_id: int | None, - verb: str, - payload: dict[str, Any] | None = None, -) -> None: - session.add( - Activity( - actor_employee_id=actor_employee_id, - entity_type=entity_type, - entity_id=entity_id, - verb=verb, - payload_json=json.dumps(payload) if payload is not None else None, - ) - ) - - -def get_actor_employee_id( - x_actor_employee_id: int | None = Header(default=None, alias="X-Actor-Employee-Id"), -) -> int: - if x_actor_employee_id is None: - raise HTTPException(status_code=400, detail="X-Actor-Employee-Id required") - return x_actor_employee_id diff --git a/backend/app/api/work.py b/backend/app/api/work.py deleted file mode 100644 index 41b8ef5f..00000000 --- a/backend/app/api/work.py +++ /dev/null @@ -1,443 +0,0 @@ -from __future__ import annotations - -import logging -from datetime import datetime - -from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException -from sqlalchemy.exc import IntegrityError -from sqlmodel import Session, select - -from app.api.utils import get_actor_employee_id, log_activity -from app.db.session import get_session -from app.integrations.notify import NotifyContext, notify_openclaw -from app.integrations.openclaw import OpenClawClient -from app.models.org import Employee -from app.models.work import Task, TaskComment -from app.schemas.work import TaskCommentCreate, TaskCreate, TaskReviewDecision, TaskUpdate - -logger = logging.getLogger("app.work") - -router = APIRouter(tags=["work"]) - -ALLOWED_STATUSES = {"backlog", "ready", "in_progress", "review", "done", "blocked"} - - -def _validate_task_assignee(session: Session, assignee_employee_id: int) -> None: - """Enforce that only provisioned agents can be assigned tasks. - - Humans can be assigned regardless. - Agents must be active, notify_enabled, and have openclaw_session_key. - """ - - emp = session.get(Employee, assignee_employee_id) - if emp is None: - raise HTTPException(status_code=400, detail="Assignee employee not found") - - if emp.employee_type == "agent": - if emp.status != "active": - raise HTTPException(status_code=400, detail="Cannot assign task to inactive agent") - if not emp.notify_enabled: - raise HTTPException( - status_code=400, detail="Cannot assign task to agent with notifications disabled" - ) - if not emp.openclaw_session_key: - raise HTTPException(status_code=400, detail="Cannot assign task to unprovisioned agent") - - -@router.get("/tasks", response_model=list[Task]) -def list_tasks(project_id: int | None = None, session: Session = Depends(get_session)): - stmt = select(Task).order_by(Task.id.asc()) - if project_id is not None: - stmt = stmt.where(Task.project_id == project_id) - return session.exec(stmt).all() - - -@router.post("/tasks", response_model=Task) -def create_task( - payload: TaskCreate, - background: BackgroundTasks, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - # SECURITY / AUDIT: never allow spoofing task creator. - # The creator is always the actor making the request. - payload = TaskCreate(**{**payload.model_dump(), "created_by_employee_id": actor_employee_id}) - - if payload.assignee_employee_id is not None: - _validate_task_assignee(session, payload.assignee_employee_id) - - # Default reviewer to the manager of the assignee (if not explicitly provided). - if payload.reviewer_employee_id is None and payload.assignee_employee_id is not None: - assignee = session.get(Employee, payload.assignee_employee_id) - if assignee is not None and assignee.manager_id is not None: - payload = TaskCreate( - **{**payload.model_dump(), "reviewer_employee_id": assignee.manager_id} - ) - - task = Task(**payload.model_dump()) - if task.status not in ALLOWED_STATUSES: - raise HTTPException(status_code=400, detail="Invalid status") - task.updated_at = datetime.utcnow() - session.add(task) - - try: - session.flush() - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="task", - entity_id=task.id, - verb="created", - payload={"project_id": task.project_id, "title": task.title}, - ) - session.commit() - except IntegrityError: - session.rollback() - raise HTTPException(status_code=409, detail="Task create violates constraints") - - session.refresh(task) - background.add_task( - notify_openclaw, - NotifyContext(event="task.created", actor_employee_id=actor_employee_id, task_id=task.id), - ) - # Explicitly return a serializable payload (guards against empty {} responses) - return Task.model_validate(task) - - -@router.post("/tasks/{task_id}/dispatch") -def dispatch_task( - task_id: int, - background: BackgroundTasks, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - logger.info("dispatch_task: called", extra={"task_id": task_id, "actor": actor_employee_id}) - task = session.get(Task, task_id) - if not task: - raise HTTPException(status_code=404, detail="Task not found") - - logger.info( - "dispatch_task: loaded", - extra={ - "task_id": getattr(task, "id", None), - "assignee_employee_id": getattr(task, "assignee_employee_id", None), - }, - ) - - if task.assignee_employee_id is None: - raise HTTPException(status_code=400, detail="Task has no assignee") - - _validate_task_assignee(session, task.assignee_employee_id) - - client = OpenClawClient.from_env() - if client is None: - logger.warning("dispatch_task: missing OpenClaw env") - raise HTTPException( - status_code=503, - detail="OpenClaw gateway is not configured (set OPENCLAW_GATEWAY_URL/TOKEN)", - ) - - # Best-effort: enqueue a dispatch notification. - # IMPORTANT: if a task is already in review, the reviewer (not the assignee) should be notified. - status = (getattr(task, "status", None) or "").lower() - if status in {"review", "ready_for_review"}: - background.add_task( - notify_openclaw, - NotifyContext( - event="status.changed", - actor_employee_id=actor_employee_id, - task_id=task.id, - changed_fields={"status": {"to": task.status}}, - ), - ) - else: - background.add_task( - notify_openclaw, - NotifyContext(event="task.assigned", actor_employee_id=actor_employee_id, task_id=task.id), - ) - - return {"ok": True} - - -def _require_reviewer_comment(body: str | None) -> str: - if body is None or not body.strip(): - raise HTTPException(status_code=400, detail="Reviewer must provide a comment for audit") - return body.strip() - - -@router.post("/tasks/{task_id}/review", response_model=Task) -def review_task( - task_id: int, - payload: TaskReviewDecision, - background: BackgroundTasks, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - """Reviewer approves or requests changes. - - - Approve => status=done - - Changes => status=in_progress - - Always writes a TaskComment by the reviewer for audit. - """ - - task = session.get(Task, task_id) - if not task: - raise HTTPException(status_code=404, detail="Task not found") - - if task.reviewer_employee_id is None: - raise HTTPException(status_code=400, detail="Task has no reviewer") - - if actor_employee_id != task.reviewer_employee_id: - raise HTTPException(status_code=403, detail="Only the reviewer can approve/request changes") - - decision = (payload.decision or "").strip().lower() - if decision not in {"approve", "changes"}: - raise HTTPException(status_code=400, detail="Invalid decision") - - comment_body = _require_reviewer_comment(payload.comment_body) - - new_status = "done" if decision == "approve" else "in_progress" - - before_status = task.status - task.status = new_status - task.updated_at = datetime.utcnow() - session.add(task) - - c = TaskComment(task_id=task.id, author_employee_id=actor_employee_id, body=comment_body) - session.add(c) - - try: - session.flush() - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="task", - entity_id=task.id, - verb="reviewed", - payload={"decision": decision, "status": new_status}, - ) - session.commit() - except IntegrityError: - session.rollback() - raise HTTPException(status_code=409, detail="Review action violates constraints") - - session.refresh(task) - session.refresh(c) - - # Notify assignee (comment.created will exclude author) - background.add_task( - notify_openclaw, - NotifyContext( - event="comment.created", - actor_employee_id=actor_employee_id, - task_id=task.id, - comment_id=c.id, - ), - ) - - # Notify reviewer/PMs about status change - if before_status != task.status: - background.add_task( - notify_openclaw, - NotifyContext( - event="status.changed", - actor_employee_id=actor_employee_id, - task_id=task.id, - changed_fields={"status": {"from": before_status, "to": task.status}}, - ), - ) - - return Task.model_validate(task) - - -@router.patch("/tasks/{task_id}", response_model=Task) -def update_task( - task_id: int, - payload: TaskUpdate, - background: BackgroundTasks, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - logger.info("dispatch_task: called", extra={"task_id": task_id, "actor": actor_employee_id}) - task = session.get(Task, task_id) - if not task: - raise HTTPException(status_code=404, detail="Task not found") - - before = { - "assignee_employee_id": task.assignee_employee_id, - "reviewer_employee_id": task.reviewer_employee_id, - "status": task.status, - } - - data = payload.model_dump(exclude_unset=True) - if "assignee_employee_id" in data and data["assignee_employee_id"] is not None: - _validate_task_assignee(session, data["assignee_employee_id"]) - if "status" in data and data["status"] not in ALLOWED_STATUSES: - raise HTTPException(status_code=400, detail="Invalid status") - - # Enforce review workflow: agent assignees cannot mark tasks done directly. - if data.get("status") == "done": - assignee = ( - session.get(Employee, task.assignee_employee_id) if task.assignee_employee_id else None - ) - if assignee is not None and assignee.employee_type == "agent": - if actor_employee_id == task.assignee_employee_id: - raise HTTPException( - status_code=403, - detail="Assignee agents cannot mark tasks done; set status=review for manager approval", - ) - if task.reviewer_employee_id is not None and actor_employee_id != task.reviewer_employee_id: - raise HTTPException(status_code=403, detail="Only the reviewer can mark a task done") - - # If a task is sent to review and no reviewer is set, default reviewer to assignee's manager. - if ( - data.get("status") in {"review", "ready_for_review"} - and data.get("reviewer_employee_id") is None - ): - assignee_id = data.get("assignee_employee_id", task.assignee_employee_id) - if assignee_id is not None: - assignee = session.get(Employee, assignee_id) - if assignee is not None and assignee.manager_id is not None: - data["reviewer_employee_id"] = assignee.manager_id - - for k, v in data.items(): - setattr(task, k, v) - task.updated_at = datetime.utcnow() - session.add(task) - - try: - session.flush() - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="task", - entity_id=task.id, - verb="updated", - payload=data, - ) - session.commit() - except IntegrityError: - session.rollback() - raise HTTPException(status_code=409, detail="Task update violates constraints") - - session.refresh(task) - - # notify based on meaningful changes - changed = {} - if before.get("assignee_employee_id") != task.assignee_employee_id: - changed["assignee_employee_id"] = { - "from": before.get("assignee_employee_id"), - "to": task.assignee_employee_id, - } - background.add_task( - notify_openclaw, - NotifyContext( - event="task.assigned", - actor_employee_id=actor_employee_id, - task_id=task.id, - changed_fields=changed, - ), - ) - if before.get("status") != task.status: - changed["status"] = {"from": before.get("status"), "to": task.status} - background.add_task( - notify_openclaw, - NotifyContext( - event="status.changed", - actor_employee_id=actor_employee_id, - task_id=task.id, - changed_fields=changed, - ), - ) - if not changed and data: - background.add_task( - notify_openclaw, - NotifyContext( - event="task.updated", - actor_employee_id=actor_employee_id, - task_id=task.id, - changed_fields=data, - ), - ) - - return Task.model_validate(task) - - -@router.delete("/tasks/{task_id}") -def delete_task( - task_id: int, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - logger.info("dispatch_task: called", extra={"task_id": task_id, "actor": actor_employee_id}) - task = session.get(Task, task_id) - if not task: - raise HTTPException(status_code=404, detail="Task not found") - - session.delete(task) - try: - session.flush() - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="task", - entity_id=task_id, - verb="deleted", - ) - session.commit() - except IntegrityError: - session.rollback() - raise HTTPException(status_code=409, detail="Task delete violates constraints") - - return {"ok": True} - - -@router.get("/task-comments", response_model=list[TaskComment]) -def list_task_comments(task_id: int, session: Session = Depends(get_session)): - return session.exec( - select(TaskComment).where(TaskComment.task_id == task_id).order_by(TaskComment.id.asc()) - ).all() - - -@router.post("/task-comments", response_model=TaskComment) -def create_task_comment( - payload: TaskCommentCreate, - background: BackgroundTasks, - session: Session = Depends(get_session), - actor_employee_id: int = Depends(get_actor_employee_id), -): - # SECURITY / AUDIT: never allow spoofing comment authorship. - # The author is always the actor making the request. - payload = TaskCommentCreate(**{**payload.model_dump(), "author_employee_id": actor_employee_id}) - - c = TaskComment(**payload.model_dump()) - session.add(c) - - try: - session.flush() - log_activity( - session, - actor_employee_id=actor_employee_id, - entity_type="task", - entity_id=c.task_id, - verb="commented", - ) - session.commit() - except IntegrityError: - session.rollback() - raise HTTPException(status_code=409, detail="Comment create violates constraints") - - session.refresh(c) - task = session.get(Task, c.task_id) - if task is not None: - background.add_task( - notify_openclaw, - NotifyContext( - event="comment.created", - actor_employee_id=actor_employee_id, - task_id=task.id, - comment_id=c.id, - ), - ) - return TaskComment.model_validate(c) diff --git a/backend/app/core/__init__.py b/backend/app/core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/app/core/auth.py b/backend/app/core/auth.py new file mode 100644 index 00000000..1efc62f3 --- /dev/null +++ b/backend/app/core/auth.py @@ -0,0 +1,97 @@ +from __future__ import annotations + +from dataclasses import dataclass +from functools import lru_cache +from typing import Literal + +from fastapi import Depends, HTTPException, Request, status +from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer +from fastapi_clerk_auth import ClerkConfig, ClerkHTTPBearer +from fastapi_clerk_auth import HTTPAuthorizationCredentials as ClerkCredentials +from pydantic import BaseModel, ValidationError +from sqlmodel import Session, select + +from app.core.config import settings +from app.db.session import get_session +from app.models.users import User + +security = HTTPBearer(auto_error=False) + + +class ClerkTokenPayload(BaseModel): + sub: str + + +@lru_cache +def _build_clerk_http_bearer(auto_error: bool) -> ClerkHTTPBearer: + if not settings.clerk_jwks_url: + raise RuntimeError("CLERK_JWKS_URL is not set.") + clerk_config = ClerkConfig( + jwks_url=settings.clerk_jwks_url, + verify_iat=settings.clerk_verify_iat, + leeway=settings.clerk_leeway, + ) + return ClerkHTTPBearer(config=clerk_config, auto_error=auto_error, add_state=True) + + +@dataclass +class AuthContext: + actor_type: Literal["user"] + user: User | None = None + + +def _resolve_clerk_auth( + request: Request, fallback: ClerkCredentials | None +) -> ClerkCredentials | None: + auth_data = getattr(request.state, "clerk_auth", None) + return auth_data or fallback + + +def _parse_subject(auth_data: ClerkCredentials | None) -> str | None: + if not auth_data or not auth_data.decoded: + return None + payload = ClerkTokenPayload.model_validate(auth_data.decoded) + return payload.sub + + +async def get_auth_context( + request: Request, + credentials: HTTPAuthorizationCredentials | None = Depends(security), + session: Session = Depends(get_session), +) -> AuthContext: + if credentials is None: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) + + try: + guard = _build_clerk_http_bearer(auto_error=False) + clerk_credentials = await guard(request) + except (RuntimeError, ValueError) as exc: + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) from exc + except HTTPException as exc: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) from exc + + auth_data = _resolve_clerk_auth(request, clerk_credentials) + try: + clerk_user_id = _parse_subject(auth_data) + except ValidationError as exc: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) from exc + + if not clerk_user_id: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) + + user = session.exec(select(User).where(User.clerk_user_id == clerk_user_id)).first() + if user is None: + claims = auth_data.decoded if auth_data and auth_data.decoded else {} + user = User( + clerk_user_id=clerk_user_id, + email=claims.get("email"), + name=claims.get("name"), + ) + session.add(user) + session.commit() + session.refresh(user) + + return AuthContext( + actor_type="user", + user=user, + ) diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 4165ae2a..9f1ded46 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -4,10 +4,29 @@ from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): - model_config = SettingsConfigDict(env_file=".env", extra="ignore") + model_config = SettingsConfigDict( + env_file=".env", + env_file_encoding="utf-8", + extra="ignore", + ) + + environment: str = "dev" + database_url: str = "postgresql+psycopg://postgres:postgres@localhost:5432/openclaw_agency" + redis_url: str = "redis://localhost:6379/0" + + # Clerk auth (auth only; roles stored in DB) + clerk_jwks_url: str = "" + clerk_verify_iat: bool = True + clerk_leeway: float = 10.0 + + # OpenClaw Gateway + openclaw_gateway_url: str = "" + openclaw_gateway_token: str = "" - database_url: str cors_origins: str = "" + # Database lifecycle + db_auto_migrate: bool = False -settings = Settings() # type: ignore + +settings = Settings() diff --git a/backend/app/core/logging.py b/backend/app/core/logging.py index a890d6a4..739c8dbe 100644 --- a/backend/app/core/logging.py +++ b/backend/app/core/logging.py @@ -2,59 +2,13 @@ from __future__ import annotations import logging import os -import sys -from typing import Any - - -def _level() -> str: - return (os.environ.get("LOG_LEVEL") or os.environ.get("UVICORN_LOG_LEVEL") or "INFO").upper() def configure_logging() -> None: - """Configure app logging to stream to stdout. - - Uvicorn already logs requests, but we want our app/integrations logs to be visible - in the same console stream. - """ - - level = getattr(logging, _level(), logging.INFO) - - root = logging.getLogger() - root.setLevel(level) - - # Avoid duplicate handlers (e.g., when autoreload imports twice) - if not any(isinstance(h, logging.StreamHandler) for h in root.handlers): - handler = logging.StreamHandler(sys.stdout) - handler.setLevel(level) - formatter = logging.Formatter( - fmt="%(asctime)s | %(levelname)s | %(name)s | %(message)s", - datefmt="%Y-%m-%dT%H:%M:%SZ", - ) - handler.setFormatter(formatter) - root.addHandler(handler) - - # Make common noisy loggers respect our level - for name in [ - "uvicorn", - "uvicorn.error", - "uvicorn.access", - "httpx", - "requests", - ]: - logging.getLogger(name).setLevel(level) - - # Hide SQLAlchemy engine chatter unless explicitly debugging. - # (You can still enable it by setting LOG_LEVEL=DEBUG and adjusting this.) - logging.getLogger("sqlalchemy").setLevel(logging.WARNING) - logging.getLogger("sqlalchemy.engine").setLevel(logging.WARNING) - logging.getLogger("sqlalchemy.pool").setLevel(logging.WARNING) - logging.getLogger("sqlalchemy.dialects").setLevel(logging.WARNING) - - -def log_kv(logger: logging.Logger, msg: str, **kv: Any) -> None: - # Lightweight key-value logging without requiring JSON logging. - if kv: - suffix = " ".join(f"{k}={v!r}" for k, v in kv.items()) - logger.info(f"{msg} | {suffix}") - else: - logger.info(msg) + level_name = os.getenv("LOG_LEVEL", "INFO").upper() + level = logging._nameToLevel.get(level_name, logging.INFO) + logging.basicConfig( + level=level, + format="%(asctime)s %(levelname)s %(name)s %(message)s", + force=True, + ) diff --git a/backend/app/core/urls.py b/backend/app/core/urls.py deleted file mode 100644 index f0cd8527..00000000 --- a/backend/app/core/urls.py +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - - -def public_api_base_url() -> str: - """Return a LAN-reachable base URL for the Mission Control API. - - Priority: - 1) MISSION_CONTROL_BASE_URL env var (recommended) - 2) First non-loopback IPv4 from `hostname -I` - - Never returns localhost because agents may run on another machine. - """ - - import os - import re - import subprocess - - explicit = os.environ.get("MISSION_CONTROL_BASE_URL") - if explicit: - return explicit.rstrip("/") - - try: - out = subprocess.check_output(["bash", "-lc", "hostname -I"], text=True).strip() - ips = re.findall(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", out) - for ip in ips: - if ip.startswith("127."): - continue - if ip.startswith("172.17."): - continue - if ip.startswith(("192.168.", "10.", "172.")): - return f"http://{ip}:8000" - except Exception: - pass - - return "http://:8000" diff --git a/backend/app/db/__init__.py b/backend/app/db/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/app/db/session.py b/backend/app/db/session.py index ca3dc247..17030b77 100644 --- a/backend/app/db/session.py +++ b/backend/app/db/session.py @@ -1,16 +1,45 @@ from __future__ import annotations +import logging +from collections.abc import Generator +from pathlib import Path + from sqlmodel import Session, SQLModel, create_engine +from alembic import command +from alembic.config import Config +from app import models # noqa: F401 from app.core.config import settings -engine = create_engine(settings.database_url, echo=False) +engine = create_engine(settings.database_url, pool_pre_ping=True) +logger = logging.getLogger(__name__) + + +def _alembic_config() -> Config: + alembic_ini = Path(__file__).resolve().parents[2] / "alembic.ini" + alembic_cfg = Config(str(alembic_ini)) + alembic_cfg.attributes["configure_logger"] = False + return alembic_cfg + + +def run_migrations() -> None: + logger.info("Running database migrations.") + command.upgrade(_alembic_config(), "head") + logger.info("Database migrations complete.") def init_db() -> None: + if settings.db_auto_migrate: + versions_dir = Path(__file__).resolve().parents[2] / "alembic" / "versions" + if any(versions_dir.glob("*.py")): + logger.info("Running Alembic migrations on startup") + run_migrations() + return + logger.warning("No Alembic revisions found; falling back to create_all") + SQLModel.metadata.create_all(engine) -def get_session(): +def get_session() -> Generator[Session, None, None]: with Session(engine) as session: yield session diff --git a/backend/app/integrations/__init__.py b/backend/app/integrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/app/integrations/notify.py b/backend/app/integrations/notify.py deleted file mode 100644 index 5ce36a84..00000000 --- a/backend/app/integrations/notify.py +++ /dev/null @@ -1,282 +0,0 @@ -from __future__ import annotations - -import logging -from dataclasses import dataclass -from typing import Iterable - -from sqlmodel import Session, select - -from app.db.session import engine -from app.integrations.openclaw import OpenClawClient -from app.models.org import Employee -from app.models.projects import ProjectMember -from app.models.work import Task, TaskComment - -logger = logging.getLogger("app.notify") - - -@dataclass(frozen=True) -class NotifyContext: - """Notification context. - - IMPORTANT: this is passed into FastAPI BackgroundTasks. - Do not store live SQLAlchemy/SQLModel objects here; only ids/primitive data. - """ - - event: str # task.created | task.updated | task.assigned | comment.created | status.changed - actor_employee_id: int - task_id: int - comment_id: int | None = None - changed_fields: dict | None = None - - -def _employees_with_session_keys(session: Session, employee_ids: Iterable[int]) -> list[Employee]: - ids = sorted({i for i in employee_ids if i is not None}) - if not ids: - return [] - - emps = session.exec(select(Employee).where(Employee.id.in_(ids))).all() - out: list[Employee] = [] - for e in emps: - if not getattr(e, "notify_enabled", True): - continue - if getattr(e, "openclaw_session_key", None): - out.append(e) - return out - - -def _project_pm_employee_ids(session: Session, project_id: int) -> set[int]: - pms = session.exec(select(ProjectMember).where(ProjectMember.project_id == project_id)).all() - pm_ids: set[int] = set() - for m in pms: - role = (m.role or "").lower() - if role in {"pm", "product", "product_manager", "manager"}: - pm_ids.add(m.employee_id) - return pm_ids - - -def resolve_recipients( - session: Session, ctx: NotifyContext, task: Task, comment: TaskComment | None -) -> set[int]: - recipients: set[int] = set() - - if ctx.event == "task.created": - if task.assignee_employee_id: - recipients.add(task.assignee_employee_id) - recipients |= _project_pm_employee_ids(session, task.project_id) - - elif ctx.event == "task.assigned": - if task.assignee_employee_id: - recipients.add(task.assignee_employee_id) - recipients |= _project_pm_employee_ids(session, task.project_id) - - elif ctx.event == "comment.created": - if task.assignee_employee_id: - recipients.add(task.assignee_employee_id) - if task.reviewer_employee_id: - recipients.add(task.reviewer_employee_id) - recipients |= _project_pm_employee_ids(session, task.project_id) - if comment and comment.author_employee_id: - recipients.discard(comment.author_employee_id) - - elif ctx.event == "status.changed": - new_status = (getattr(task, "status", None) or "").lower() - if new_status in {"review", "ready_for_review"} and task.reviewer_employee_id: - recipients.add(task.reviewer_employee_id) - recipients |= _project_pm_employee_ids(session, task.project_id) - - elif ctx.event == "task.updated": - recipients |= _project_pm_employee_ids(session, task.project_id) - - recipients.discard(ctx.actor_employee_id) - return recipients - - -def ensure_employee_provisioned(session: Session, employee_id: int) -> None: - """Best-effort provisioning of a reviewer/manager so notifications can be delivered.""" - - emp = session.get(Employee, employee_id) - if emp is None: - return - if not getattr(emp, "notify_enabled", True): - return - if getattr(emp, "openclaw_session_key", None): - return - - client = OpenClawClient.from_env() - if client is None: - logger.warning( - "ensure_employee_provisioned: missing OpenClaw env", extra={"employee_id": employee_id} - ) - return - - prompt = ( - f"You are {emp.name} (employee_id={emp.id}).\n" - "You are a reviewer/manager in Mission Control.\n\n" - "Your job is to REVIEW work within the bounds of what the task requester asked for, using only the task + comments + current system state.\n" - "Do NOT wait for the requester to provide more info by default.\n\n" - "When a task is in review you must:\n" - "1) Read the task title/description and all comments\n" - "2) Verify the requested changes were actually made (check via Mission Control API if needed)\n" - "3) Decide: approve or request changes\n" - "4) Leave an audit comment explaining your decision (required)\n\n" - "If something is ambiguous or missing, request changes with a clear checklist. Only ask the human if it's truly impossible to decide.\n" - ) - - try: - res = client.tools_invoke( - "sessions_spawn", - {"task": prompt, "label": f"employee:{emp.id}:{emp.name}"}, - timeout_s=20.0, - ) - details = (res.get("result") or {}).get("details") or {} - sk = details.get("childSessionKey") or details.get("sessionKey") - if sk: - emp.openclaw_session_key = sk - session.add(emp) - session.commit() - logger.info( - "ensure_employee_provisioned: provisioned", - extra={"employee_id": emp.id, "session_key": sk}, - ) - except Exception: - session.rollback() - logger.exception("ensure_employee_provisioned: failed", extra={"employee_id": employee_id}) - - -def build_message( - *, - ctx: NotifyContext, - task: Task, - comment: TaskComment | None, - recipient: Employee, - base_url: str, -) -> str: - base = f"Task #{task.id}: {task.title}" if task.id is not None else f"Task: {task.title}" - - if ctx.event in {"task.created", "task.assigned"} and recipient.employee_type == "agent": - desc = (task.description or "").strip() - if len(desc) > 500: - desc = desc[:497] + "..." - desc_block = f"\n\nDescription:\n{desc}" if desc else "" - - return ( - f"{base}\n\n" - f"Set BASE={base_url}\n\n" - "You are the assignee. Start NOW (use the exec tool to run these curl commands):\n" - f"1) curl -sS -X PATCH $BASE/tasks/{task.id} -H 'X-Actor-Employee-Id: {recipient.id}' -H 'Content-Type: application/json' -d '{{\"status\":\"in_progress\"}}'\n" - f"2) curl -sS -X POST $BASE/task-comments -H 'X-Actor-Employee-Id: {recipient.id}' -H 'Content-Type: application/json' -d '{{\"task_id\":{task.id},\"body\":\"Plan: ... Next: ...\"}}'\n" - "3) Do the work\n" - "4) Post progress updates via POST $BASE/task-comments (same headers)\n" - f"5) When complete: set status=review (assignee cannot set done) and wait for manager approval\n" - f"{desc_block}" - ) - - if ctx.event == "comment.created": - snippet = "" - if comment and comment.body: - snippet = comment.body.strip().replace("\n", " ") - if len(snippet) > 180: - snippet = snippet[:177] + "..." - snippet = f"\nComment: {snippet}" - return f"New comment on {base}.{snippet}\nPlease review and respond in Mission Control." - - if ctx.event == "status.changed": - new_status = (getattr(task, "status", None) or "").lower() - if new_status in {"review", "ready_for_review"}: - return ( - f"Review requested for {base}.\n" - "As the reviewer/manager, you must:\n" - "1) Read the task + latest assignee comments\n" - "2) Decide: approve or request changes\n" - "3) Leave an audit comment explaining your decision (required)\n" - f"4) Submit decision via POST /tasks/{task.id}/review (decision=approve|changes)\n" - "Approve → task becomes done. Changes → task returns to in_progress and assignee is notified." - ) - return ( - f"Status changed on {base} → {task.status}.\n" - "Please review and respond in Mission Control." - ) - - if ctx.event == "task.created": - return f"New task created: {base}.\nPlease review and respond in Mission Control." - - if ctx.event == "task.assigned": - return f"Assigned: {base}.\nPlease review and respond in Mission Control." - - return f"Update on {base}.\nPlease review and respond in Mission Control." - - -def notify_openclaw(ctx: NotifyContext) -> None: - """Send OpenClaw notifications. - - Runs in BackgroundTasks; opens its own DB session for safety. - """ - - client = OpenClawClient.from_env() - logger.info( - "notify_openclaw: start", - extra={"event": ctx.event, "task_id": ctx.task_id, "actor": ctx.actor_employee_id}, - ) - if client is None: - logger.warning("notify_openclaw: skipped (missing OpenClaw env)") - return - - with Session(engine) as session: - task = session.get(Task, ctx.task_id) - if task is None: - logger.warning("notify_openclaw: task not found", extra={"task_id": ctx.task_id}) - return - - comment = session.get(TaskComment, ctx.comment_id) if ctx.comment_id else None - - if ctx.event == "status.changed": - new_status = (getattr(task, "status", None) or "").lower() - if new_status in {"review", "ready_for_review"} and task.reviewer_employee_id: - ensure_employee_provisioned(session, int(task.reviewer_employee_id)) - - recipient_ids = resolve_recipients(session, ctx, task, comment) - logger.info( - "notify_openclaw: recipients resolved", extra={"recipient_ids": sorted(recipient_ids)} - ) - recipients = _employees_with_session_keys(session, recipient_ids) - if not recipients: - logger.info("notify_openclaw: no recipients with session keys") - return - - # base URL used in agent messages - base_url = __import__( - "app.core.urls", fromlist=["public_api_base_url"] - ).public_api_base_url() - - for e in recipients: - sk = getattr(e, "openclaw_session_key", None) - if not sk: - continue - - message = build_message( - ctx=ctx, - task=task, - comment=comment, - recipient=e, - base_url=base_url, - ) - - try: - client.tools_invoke( - "sessions_send", - {"sessionKey": sk, "message": message}, - timeout_s=30.0, - ) - except Exception: - # keep the log, but avoid giant stack spam unless debugging - logger.warning( - "notify_openclaw: sessions_send failed", - extra={ - "event": ctx.event, - "task_id": ctx.task_id, - "to_employee_id": getattr(e, "id", None), - "session_key": sk, - }, - ) - continue diff --git a/backend/app/integrations/openclaw.py b/backend/app/integrations/openclaw.py deleted file mode 100644 index a39e6c09..00000000 --- a/backend/app/integrations/openclaw.py +++ /dev/null @@ -1,88 +0,0 @@ -from __future__ import annotations - -import logging -import os -import time -from typing import Any - -import requests -from requests.exceptions import ReadTimeout, RequestException - -logger = logging.getLogger("app.openclaw") - - -class OpenClawClient: - def __init__(self, base_url: str, token: str): - self.base_url = base_url.rstrip("/") - self.token = token - - @classmethod - def from_env(cls) -> "OpenClawClient | None": - # Ensure .env is loaded into os.environ (pydantic Settings reads env_file but - # does not automatically populate os.environ). - try: - from dotenv import load_dotenv - - load_dotenv(override=False) - except Exception: - pass - - url = os.environ.get("OPENCLAW_GATEWAY_URL") - token = os.environ.get("OPENCLAW_GATEWAY_TOKEN") - if not url or not token: - return None - return cls(url, token) - - def tools_invoke( - self, - tool: str, - args: dict[str, Any], - *, - session_key: str | None = None, - timeout_s: float = 10.0, - ) -> dict[str, Any]: - payload: dict[str, Any] = {"tool": tool, "args": args} - logger.info( - "openclaw.tools_invoke", - extra={"tool": tool, "has_session_key": bool(session_key), "timeout_s": timeout_s}, - ) - if session_key is not None: - payload["sessionKey"] = session_key - - last_err: Exception | None = None - # Retry a few times; the gateway can be busy and respond slowly. - for attempt in range(4): - try: - r = requests.post( - f"{self.base_url}/tools/invoke", - headers={ - "Authorization": f"Bearer {self.token}", - "Content-Type": "application/json", - }, - json=payload, - # connect timeout, read timeout - timeout=(2.0, timeout_s), - ) - r.raise_for_status() - logger.info( - "openclaw.tools_invoke: ok", - extra={"tool": tool, "status": r.status_code, "attempt": attempt + 1}, - ) - return r.json() - except ReadTimeout as e: - last_err = e - logger.warning( - "openclaw.tools_invoke: timeout", - extra={"tool": tool, "attempt": attempt + 1, "timeout_s": timeout_s}, - ) - time.sleep(0.5 * (2**attempt)) - except RequestException as e: - last_err = e - logger.warning( - "openclaw.tools_invoke: request error", - extra={"tool": tool, "attempt": attempt + 1, "error": str(e)}, - ) - time.sleep(0.5 * (2**attempt)) - - assert last_err is not None - raise last_err diff --git a/backend/app/integrations/openclaw_agents.py b/backend/app/integrations/openclaw_agents.py deleted file mode 100644 index 6ffb5ca7..00000000 --- a/backend/app/integrations/openclaw_agents.py +++ /dev/null @@ -1,129 +0,0 @@ -from __future__ import annotations - -import json -import re -import time -from typing import Any - -from app.integrations.openclaw import OpenClawClient - - -def _slug(s: str) -> str: - s = (s or "").strip().lower() - s = re.sub(r"[^a-z0-9]+", "-", s) - s = re.sub(r"-+", "-", s).strip("-") - return s or "agent" - - -def desired_agent_id(*, employee_id: int, name: str) -> str: - return f"employee-{employee_id}-{_slug(name)}" - - -def ensure_full_agent_profile( - *, - client: OpenClawClient, - employee_id: int, - employee_name: str, -) -> dict[str, str]: - """Ensure an OpenClaw agent profile exists for this employee. - - Returns {"agent_id": ..., "workspace": ...}. - - Implementation strategy: - - Create per-agent workspace + agent dir on the gateway host. - - Add/ensure entry in openclaw.json agents.list. - - NOTE: This uses OpenClaw gateway tools via /tools/invoke (gateway + exec). - """ - - agent_id = desired_agent_id(employee_id=employee_id, name=employee_name) - - workspace = f"/home/asaharan/.openclaw/workspaces/{agent_id}" - agent_dir = f"/home/asaharan/.openclaw/agents/{agent_id}/agent" - - # 1) Create dirs - client.tools_invoke( - "exec", - { - "command": f"mkdir -p {workspace} {agent_dir}", - }, - timeout_s=20.0, - ) - - # 2) Write minimal identity files in the per-agent workspace - identity_md = ( - "# IDENTITY.md\n\n" - "- **Name:** " + employee_name + "\n" - "- **Creature:** AI agent employee (Mission Control)\n" - "- **Vibe:** Direct, action-oriented, leaves audit trails\n" - ) - user_md = ( - "# USER.md\n\n" - "You work for Abhimanyu.\n" - "You must execute Mission Control tasks via the API and keep state synced.\n" - ) - - # Use cat heredocs to avoid dependency on extra tooling. - client.tools_invoke( - "exec", - { - "command": "bash -lc " - + json.dumps( - """ -cat > {ws}/IDENTITY.md <<'EOF' -{identity} -EOF -cat > {ws}/USER.md <<'EOF' -{user} -EOF -""".format(ws=workspace, identity=identity_md, user=user_md) - ), - }, - timeout_s=20.0, - ) - - # 3) Update openclaw.json agents.list (idempotent) - cfg_resp = client.tools_invoke("gateway", {"action": "config.get"}, timeout_s=20.0) - raw = ( - (((cfg_resp or {}).get("result") or {}).get("content") or [{}])[0].get("text") - if isinstance((((cfg_resp or {}).get("result") or {}).get("content") or [{}]), list) - else None - ) - - if not raw: - # fallback: tool may return {ok:true,result:{raw:...}} - raw = ((cfg_resp.get("result") or {}).get("raw")) if isinstance(cfg_resp, dict) else None - - if not raw: - raise RuntimeError("Unable to read gateway config via tools") - - cfg = json.loads(raw) - - agents = cfg.get("agents") or {} - agents_list = agents.get("list") or [] - if not isinstance(agents_list, list): - agents_list = [] - - exists = any(isinstance(a, dict) and a.get("id") == agent_id for a in agents_list) - if not exists: - agents_list.append( - { - "id": agent_id, - "name": employee_name, - "workspace": workspace, - "agentDir": agent_dir, - "identity": {"name": employee_name, "emoji": "🜁"}, - } - ) - agents["list"] = agents_list - cfg["agents"] = agents - - client.tools_invoke( - "gateway", - {"action": "config.apply", "raw": json.dumps(cfg)}, - timeout_s=30.0, - ) - # give the gateway a moment to reload the agent registry - time.sleep(2.5) - - return {"agent_id": agent_id, "workspace": workspace} diff --git a/backend/app/integrations/openclaw_gateway.py b/backend/app/integrations/openclaw_gateway.py new file mode 100644 index 00000000..78da547f --- /dev/null +++ b/backend/app/integrations/openclaw_gateway.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +import asyncio +import json +from dataclasses import dataclass +from typing import Any +from urllib.parse import urlencode, urlparse, urlunparse +from uuid import uuid4 + +import websockets + +from app.core.config import settings + + +class OpenClawGatewayError(RuntimeError): + pass + + +@dataclass +class OpenClawResponse: + payload: Any + + +def _build_gateway_url() -> str: + base_url = settings.openclaw_gateway_url or "ws://127.0.0.1:18789" + token = settings.openclaw_gateway_token + if not token: + return base_url + parsed = urlparse(base_url) + query = urlencode({"token": token}) + return urlunparse(parsed._replace(query=query)) + + +async def _await_response(ws: websockets.WebSocketClientProtocol, request_id: str) -> Any: + while True: + raw = await ws.recv() + data = json.loads(raw) + + if data.get("type") == "res" and data.get("id") == request_id: + if data.get("ok") is False: + error = data.get("error", {}).get("message", "Gateway error") + raise OpenClawGatewayError(error) + return data.get("payload") + + if data.get("id") == request_id: + if data.get("error"): + raise OpenClawGatewayError(data["error"].get("message", "Gateway error")) + return data.get("result") + + +async def _send_request( + ws: websockets.WebSocketClientProtocol, method: str, params: dict[str, Any] | None +) -> Any: + request_id = str(uuid4()) + message = {"type": "req", "id": request_id, "method": method, "params": params or {}} + await ws.send(json.dumps(message)) + return await _await_response(ws, request_id) + + +async def _handle_challenge( + ws: websockets.WebSocketClientProtocol, first_message: str | None +) -> None: + if not first_message: + return + data = json.loads(first_message) + if data.get("type") != "event" or data.get("event") != "connect.challenge": + return + + connect_id = str(uuid4()) + response = { + "type": "req", + "id": connect_id, + "method": "connect", + "params": { + "minProtocol": 3, + "maxProtocol": 3, + "client": { + "id": "gateway-client", + "version": "1.0.0", + "platform": "web", + "mode": "ui", + }, + "auth": {"token": settings.openclaw_gateway_token}, + }, + } + await ws.send(json.dumps(response)) + await _await_response(ws, connect_id) + + +async def openclaw_call(method: str, params: dict[str, Any] | None = None) -> Any: + gateway_url = _build_gateway_url() + try: + async with websockets.connect(gateway_url, ping_interval=None) as ws: + first_message = None + try: + first_message = await asyncio.wait_for(ws.recv(), timeout=2) + except asyncio.TimeoutError: + first_message = None + await _handle_challenge(ws, first_message) + return await _send_request(ws, method, params) + except OpenClawGatewayError: + raise + except Exception as exc: # pragma: no cover - network errors + raise OpenClawGatewayError(str(exc)) from exc + + +async def send_message( + message: str, + *, + session_key: str, + deliver: bool = False, +) -> Any: + params: dict[str, Any] = { + "sessionKey": session_key, + "message": message, + "deliver": deliver, + "idempotencyKey": str(uuid4()), + } + return await openclaw_call("chat.send", params) + + +async def get_chat_history(session_key: str, limit: int | None = None) -> Any: + params: dict[str, Any] = {"sessionKey": session_key} + if limit is not None: + params["limit"] = limit + return await openclaw_call("chat.history", params) diff --git a/backend/app/main.py b/backend/app/main.py index 4442bd6c..e7e6bca2 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,19 +1,21 @@ from __future__ import annotations -from fastapi import FastAPI +from fastapi import APIRouter, FastAPI from fastapi.middleware.cors import CORSMiddleware -from app.api.activities import router as activities_router -from app.api.org import router as org_router -from app.api.projects import router as projects_router -from app.api.work import router as work_router +from app.api.activity import router as activity_router +from app.api.agents import router as agents_router +from app.api.auth import router as auth_router +from app.api.boards import router as boards_router +from app.api.gateway import router as gateway_router +from app.api.tasks import router as tasks_router from app.core.config import settings from app.core.logging import configure_logging from app.db.session import init_db configure_logging() -app = FastAPI(title="OpenClaw Agency API", version="0.3.0") +app = FastAPI(title="Mission Control API", version="0.1.0") origins = [o.strip() for o in settings.cors_origins.split(",") if o.strip()] if origins: @@ -31,12 +33,16 @@ def on_startup() -> None: init_db() -app.include_router(org_router) -app.include_router(projects_router) -app.include_router(work_router) -app.include_router(activities_router) - - @app.get("/health") -def health(): +def health() -> dict[str, bool]: return {"ok": True} + + +api_v1 = APIRouter(prefix="/api/v1") +api_v1.include_router(auth_router) +api_v1.include_router(agents_router) +api_v1.include_router(activity_router) +api_v1.include_router(gateway_router) +api_v1.include_router(boards_router) +api_v1.include_router(tasks_router) +app.include_router(api_v1) diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 85f0fd7a..00cc4130 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -1,15 +1,13 @@ -from app.models.activity import Activity -from app.models.org import Department, Employee, Team -from app.models.projects import Project, ProjectMember -from app.models.work import Task, TaskComment +from app.models.activity_events import ActivityEvent +from app.models.agents import Agent +from app.models.boards import Board +from app.models.tasks import Task +from app.models.users import User __all__ = [ - "Department", - "Employee", - "Team", - "Project", - "ProjectMember", + "ActivityEvent", + "Agent", + "Board", "Task", - "TaskComment", - "Activity", + "User", ] diff --git a/backend/app/models/activity.py b/backend/app/models/activity.py deleted file mode 100644 index 51c3a8fc..00000000 --- a/backend/app/models/activity.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import annotations - -from datetime import datetime - -from sqlmodel import Field, SQLModel - - -class Activity(SQLModel, table=True): - __tablename__ = "activities" - - id: int | None = Field(default=None, primary_key=True) - actor_employee_id: int | None = Field(default=None, foreign_key="employees.id") - - entity_type: str - entity_id: int | None = None - verb: str - - payload_json: str | None = None - created_at: datetime = Field(default_factory=datetime.utcnow) diff --git a/backend/app/models/activity_events.py b/backend/app/models/activity_events.py new file mode 100644 index 00000000..223fab1f --- /dev/null +++ b/backend/app/models/activity_events.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from datetime import datetime +from uuid import UUID, uuid4 + +from sqlmodel import Field, SQLModel + + +class ActivityEvent(SQLModel, table=True): + __tablename__ = "activity_events" + + id: UUID = Field(default_factory=uuid4, primary_key=True) + event_type: str = Field(index=True) + message: str | None = None + agent_id: UUID | None = Field(default=None, foreign_key="agents.id", index=True) + task_id: UUID | None = Field(default=None, foreign_key="tasks.id", index=True) + created_at: datetime = Field(default_factory=datetime.utcnow) diff --git a/backend/app/models/agents.py b/backend/app/models/agents.py new file mode 100644 index 00000000..56271898 --- /dev/null +++ b/backend/app/models/agents.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from datetime import datetime +from uuid import UUID, uuid4 + +from sqlmodel import Field, SQLModel + + +class Agent(SQLModel, table=True): + __tablename__ = "agents" + + id: UUID = Field(default_factory=uuid4, primary_key=True) + name: str = Field(index=True) + status: str = Field(default="online", index=True) + openclaw_session_id: str | None = Field(default=None, index=True) + last_seen_at: datetime = Field(default_factory=datetime.utcnow) + created_at: datetime = Field(default_factory=datetime.utcnow) + updated_at: datetime = Field(default_factory=datetime.utcnow) diff --git a/backend/app/models/boards.py b/backend/app/models/boards.py new file mode 100644 index 00000000..65cc340e --- /dev/null +++ b/backend/app/models/boards.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from datetime import datetime +from uuid import UUID, uuid4 + +from sqlmodel import Field + +from app.models.tenancy import TenantScoped + + +class Board(TenantScoped, table=True): + __tablename__ = "boards" + + id: UUID = Field(default_factory=uuid4, primary_key=True) + name: str + slug: str = Field(index=True) + created_at: datetime = Field(default_factory=datetime.utcnow) + updated_at: datetime = Field(default_factory=datetime.utcnow) diff --git a/backend/app/models/org.py b/backend/app/models/org.py deleted file mode 100644 index 826841da..00000000 --- a/backend/app/models/org.py +++ /dev/null @@ -1,40 +0,0 @@ -from __future__ import annotations - -from sqlmodel import Field, SQLModel - - -class Department(SQLModel, table=True): - __tablename__ = "departments" - - id: int | None = Field(default=None, primary_key=True) - name: str = Field(index=True, unique=True) - head_employee_id: int | None = Field(default=None, foreign_key="employees.id") - - -class Team(SQLModel, table=True): - __tablename__ = "teams" - - id: int | None = Field(default=None, primary_key=True) - name: str = Field(index=True) - - department_id: int = Field(foreign_key="departments.id") - lead_employee_id: int | None = Field(default=None, foreign_key="employees.id") - - -class Employee(SQLModel, table=True): - __tablename__ = "employees" - - id: int | None = Field(default=None, primary_key=True) - name: str - employee_type: str # human | agent - - department_id: int | None = Field(default=None, foreign_key="departments.id") - team_id: int | None = Field(default=None, foreign_key="teams.id") - manager_id: int | None = Field(default=None, foreign_key="employees.id") - - title: str | None = None - status: str = Field(default="active") - - # OpenClaw integration - openclaw_session_key: str | None = None - notify_enabled: bool = Field(default=True) diff --git a/backend/app/models/projects.py b/backend/app/models/projects.py deleted file mode 100644 index b079b9b2..00000000 --- a/backend/app/models/projects.py +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import annotations - -from sqlmodel import Field, SQLModel - - -class Project(SQLModel, table=True): - __tablename__ = "projects" - - id: int | None = Field(default=None, primary_key=True) - name: str = Field(index=True, unique=True) - status: str = Field(default="active") - - # Project ownership: projects are assigned to teams (not departments) - team_id: int | None = Field(default=None, foreign_key="teams.id") - - -class ProjectMember(SQLModel, table=True): - __tablename__ = "project_members" - - id: int | None = Field(default=None, primary_key=True) - project_id: int = Field(foreign_key="projects.id") - employee_id: int = Field(foreign_key="employees.id") - role: str | None = None diff --git a/backend/app/models/tasks.py b/backend/app/models/tasks.py new file mode 100644 index 00000000..9ff18818 --- /dev/null +++ b/backend/app/models/tasks.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from datetime import datetime +from uuid import UUID, uuid4 + +from sqlmodel import Field + +from app.models.tenancy import TenantScoped + + +class Task(TenantScoped, table=True): + __tablename__ = "tasks" + + id: UUID = Field(default_factory=uuid4, primary_key=True) + board_id: UUID | None = Field(default=None, foreign_key="boards.id", index=True) + + title: str + description: str | None = None + status: str = Field(default="inbox", index=True) + priority: str = Field(default="medium", index=True) + due_at: datetime | None = None + + created_by_user_id: UUID | None = Field(default=None, foreign_key="users.id", index=True) + + created_at: datetime = Field(default_factory=datetime.utcnow) + updated_at: datetime = Field(default_factory=datetime.utcnow) diff --git a/backend/app/models/tenancy.py b/backend/app/models/tenancy.py new file mode 100644 index 00000000..bd2768b4 --- /dev/null +++ b/backend/app/models/tenancy.py @@ -0,0 +1,7 @@ +from __future__ import annotations + +from sqlmodel import SQLModel + + +class TenantScoped(SQLModel, table=False): + pass diff --git a/backend/app/models/users.py b/backend/app/models/users.py new file mode 100644 index 00000000..f105efac --- /dev/null +++ b/backend/app/models/users.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from uuid import UUID, uuid4 + +from sqlmodel import Field, SQLModel + + +class User(SQLModel, table=True): + __tablename__ = "users" + + id: UUID = Field(default_factory=uuid4, primary_key=True) + clerk_user_id: str = Field(index=True, unique=True) + email: str | None = Field(default=None, index=True) + name: str | None = None + is_super_admin: bool = Field(default=False) diff --git a/backend/app/models/work.py b/backend/app/models/work.py deleted file mode 100644 index ce7f8314..00000000 --- a/backend/app/models/work.py +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import annotations - -from datetime import datetime - -from sqlmodel import Field, SQLModel - - -class Task(SQLModel, table=True): - __tablename__ = "tasks" - - id: int | None = Field(default=None, primary_key=True) - - project_id: int = Field(foreign_key="projects.id", index=True) - title: str - description: str | None = None - - status: str = Field(default="backlog", index=True) - - assignee_employee_id: int | None = Field(default=None, foreign_key="employees.id") - reviewer_employee_id: int | None = Field(default=None, foreign_key="employees.id") - created_by_employee_id: int | None = Field(default=None, foreign_key="employees.id") - - created_at: datetime = Field(default_factory=datetime.utcnow) - updated_at: datetime = Field(default_factory=datetime.utcnow) - - -class TaskComment(SQLModel, table=True): - __tablename__ = "task_comments" - - id: int | None = Field(default=None, primary_key=True) - task_id: int = Field(foreign_key="tasks.id", index=True) - author_employee_id: int | None = Field(default=None, foreign_key="employees.id") - - # Optional reply threading - reply_to_comment_id: int | None = Field(default=None, foreign_key="task_comments.id") - - body: str - created_at: datetime = Field(default_factory=datetime.utcnow) diff --git a/backend/app/schemas/__init__.py b/backend/app/schemas/__init__.py new file mode 100644 index 00000000..e9431fa5 --- /dev/null +++ b/backend/app/schemas/__init__.py @@ -0,0 +1,20 @@ +from app.schemas.activity_events import ActivityEventRead +from app.schemas.agents import AgentCreate, AgentRead, AgentUpdate +from app.schemas.boards import BoardCreate, BoardRead, BoardUpdate +from app.schemas.tasks import TaskCreate, TaskRead, TaskUpdate +from app.schemas.users import UserCreate, UserRead + +__all__ = [ + "ActivityEventRead", + "AgentCreate", + "AgentRead", + "AgentUpdate", + "BoardCreate", + "BoardRead", + "BoardUpdate", + "TaskCreate", + "TaskRead", + "TaskUpdate", + "UserCreate", + "UserRead", +] diff --git a/backend/app/schemas/activity_events.py b/backend/app/schemas/activity_events.py new file mode 100644 index 00000000..bd8b64aa --- /dev/null +++ b/backend/app/schemas/activity_events.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from datetime import datetime +from uuid import UUID + +from sqlmodel import SQLModel + + +class ActivityEventRead(SQLModel): + id: UUID + event_type: str + message: str | None + agent_id: UUID | None + task_id: UUID | None + created_at: datetime diff --git a/backend/app/schemas/agents.py b/backend/app/schemas/agents.py new file mode 100644 index 00000000..243bbdf4 --- /dev/null +++ b/backend/app/schemas/agents.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +from datetime import datetime +from uuid import UUID + +from sqlmodel import SQLModel + + +class AgentBase(SQLModel): + name: str + status: str = "online" + + +class AgentCreate(AgentBase): + pass + + +class AgentUpdate(SQLModel): + name: str | None = None + status: str | None = None + + +class AgentRead(AgentBase): + id: UUID + openclaw_session_id: str | None = None + last_seen_at: datetime + created_at: datetime + updated_at: datetime + + +class AgentHeartbeat(SQLModel): + status: str | None = None + + +class AgentHeartbeatCreate(AgentHeartbeat): + name: str diff --git a/backend/app/schemas/boards.py b/backend/app/schemas/boards.py new file mode 100644 index 00000000..7ca3fae8 --- /dev/null +++ b/backend/app/schemas/boards.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from datetime import datetime +from uuid import UUID + +from sqlmodel import SQLModel + + +class BoardBase(SQLModel): + name: str + slug: str + + +class BoardCreate(BoardBase): + pass + + +class BoardUpdate(SQLModel): + name: str | None = None + slug: str | None = None + + +class BoardRead(BoardBase): + id: UUID + created_at: datetime + updated_at: datetime diff --git a/backend/app/schemas/org.py b/backend/app/schemas/org.py deleted file mode 100644 index 0ae1d541..00000000 --- a/backend/app/schemas/org.py +++ /dev/null @@ -1,53 +0,0 @@ -from __future__ import annotations - -from sqlmodel import SQLModel - - -class DepartmentCreate(SQLModel): - name: str - head_employee_id: int | None = None - - -class DepartmentUpdate(SQLModel): - name: str | None = None - head_employee_id: int | None = None - - -class TeamCreate(SQLModel): - name: str - department_id: int - lead_employee_id: int | None = None - - -class TeamUpdate(SQLModel): - name: str | None = None - department_id: int | None = None - lead_employee_id: int | None = None - - -class EmployeeCreate(SQLModel): - name: str - employee_type: str - department_id: int | None = None - team_id: int | None = None - manager_id: int | None = None - title: str | None = None - status: str = "active" - - # OpenClaw integration - openclaw_session_key: str | None = None - notify_enabled: bool = True - - -class EmployeeUpdate(SQLModel): - name: str | None = None - employee_type: str | None = None - department_id: int | None = None - team_id: int | None = None - manager_id: int | None = None - title: str | None = None - status: str | None = None - - # OpenClaw integration - openclaw_session_key: str | None = None - notify_enabled: bool | None = None diff --git a/backend/app/schemas/projects.py b/backend/app/schemas/projects.py deleted file mode 100644 index 10477213..00000000 --- a/backend/app/schemas/projects.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import annotations - -from sqlmodel import SQLModel - - -class ProjectCreate(SQLModel): - name: str - status: str = "active" - team_id: int | None = None - - -class ProjectUpdate(SQLModel): - name: str | None = None - status: str | None = None - team_id: int | None = None diff --git a/backend/app/schemas/tasks.py b/backend/app/schemas/tasks.py new file mode 100644 index 00000000..b2c0495f --- /dev/null +++ b/backend/app/schemas/tasks.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +from datetime import datetime +from uuid import UUID + +from sqlmodel import SQLModel + + +class TaskBase(SQLModel): + title: str + description: str | None = None + status: str = "inbox" + priority: str = "medium" + due_at: datetime | None = None + + +class TaskCreate(TaskBase): + created_by_user_id: UUID | None = None + + +class TaskUpdate(SQLModel): + title: str | None = None + description: str | None = None + status: str | None = None + priority: str | None = None + due_at: datetime | None = None + + +class TaskRead(TaskBase): + id: UUID + board_id: UUID | None + created_by_user_id: UUID | None + created_at: datetime + updated_at: datetime diff --git a/backend/app/schemas/users.py b/backend/app/schemas/users.py new file mode 100644 index 00000000..e106758c --- /dev/null +++ b/backend/app/schemas/users.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from uuid import UUID + +from sqlmodel import SQLModel + + +class UserBase(SQLModel): + clerk_user_id: str + email: str | None = None + name: str | None = None + + +class UserCreate(UserBase): + pass + + +class UserRead(UserBase): + id: UUID + is_super_admin: bool diff --git a/backend/app/schemas/work.py b/backend/app/schemas/work.py deleted file mode 100644 index 92b460ca..00000000 --- a/backend/app/schemas/work.py +++ /dev/null @@ -1,33 +0,0 @@ -from __future__ import annotations - -from sqlmodel import SQLModel - - -class TaskCreate(SQLModel): - project_id: int - title: str - description: str | None = None - status: str = "backlog" - assignee_employee_id: int | None = None - reviewer_employee_id: int | None = None - created_by_employee_id: int | None = None - - -class TaskUpdate(SQLModel): - title: str | None = None - description: str | None = None - status: str | None = None - assignee_employee_id: int | None = None - reviewer_employee_id: int | None = None - - -class TaskCommentCreate(SQLModel): - task_id: int - author_employee_id: int | None = None - reply_to_comment_id: int | None = None - body: str - - -class TaskReviewDecision(SQLModel): - decision: str # approve | changes - comment_body: str diff --git a/backend/app/services/__init__.py b/backend/app/services/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/app/services/admin_access.py b/backend/app/services/admin_access.py new file mode 100644 index 00000000..a8e3a495 --- /dev/null +++ b/backend/app/services/admin_access.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from fastapi import HTTPException, status + +from app.core.auth import AuthContext + + +def require_admin(auth: AuthContext) -> None: + if auth.actor_type != "user" or auth.user is None: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) diff --git a/backend/app/workers/__init__.py b/backend/app/workers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/app/workers/queue.py b/backend/app/workers/queue.py new file mode 100644 index 00000000..5aa45b00 --- /dev/null +++ b/backend/app/workers/queue.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from redis import Redis +from rq import Queue + +from app.core.config import settings + + +def get_redis() -> Redis: + return Redis.from_url(settings.redis_url) + + +def get_queue(name: str) -> Queue: + return Queue(name, connection=get_redis()) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 02f5b30a..01ba5857 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -7,3 +7,33 @@ extend-exclude = '(\.venv|alembic/versions)' profile = "black" line_length = 100 skip = [".venv", "alembic/versions"] +[project] +name = "openclaw-agency-backend" +version = "0.1.0" +requires-python = ">=3.12" +dependencies = [ + "fastapi==0.115.4", + "uvicorn[standard]==0.30.6", + "sqlmodel==0.0.22", + "sqlalchemy==2.0.34", + "alembic==1.13.2", + "psycopg[binary]==3.2.1", + "pydantic-settings==2.5.2", + "python-dotenv==1.0.1", + "httpx==0.27.2", + "websockets==12.0", + "rq==1.16.2", + "redis==5.1.1", + "fastapi-clerk-auth==0.0.9", + "sse-starlette==2.1.3", +] + +[project.optional-dependencies] +dev = [ + "black==24.10.0", + "flake8==7.1.1", + "isort==5.13.2", + "pytest==8.3.3", + "pytest-asyncio==0.24.0", + "ruff==0.6.9", +] diff --git a/backend/requirements-dev.txt b/backend/requirements-dev.txt index 4d452174..c0bc50de 100644 --- a/backend/requirements-dev.txt +++ b/backend/requirements-dev.txt @@ -1,4 +1,3 @@ -black==24.10.0 -isort==5.13.2 -flake8==7.1.1 -pre-commit==4.1.0 +pytest==8.3.3 +pytest-asyncio==0.24.0 +ruff==0.6.9 diff --git a/backend/requirements.txt b/backend/requirements.txt index a8ce58a2..d5212464 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,8 +1,14 @@ -fastapi -uvicorn[standard] -sqlmodel -alembic -psycopg[binary] -python-dotenv -pydantic-settings -requests +fastapi==0.115.4 +uvicorn[standard]==0.30.6 +sqlmodel==0.0.22 +sqlalchemy==2.0.34 +alembic==1.13.2 +psycopg[binary]==3.2.1 +pydantic-settings==2.5.2 +python-dotenv==1.0.1 +httpx==0.27.2 +websockets==12.0 +rq==1.16.2 +redis==5.1.1 +fastapi-clerk-auth==0.0.9 +sse-starlette==2.1.3 diff --git a/backend/scripts/README_seed.md b/backend/scripts/README_seed.md deleted file mode 100644 index a0d36f74..00000000 --- a/backend/scripts/README_seed.md +++ /dev/null @@ -1,37 +0,0 @@ -# DB reset + seed (dev-machine) - -This repo uses Alembic migrations as schema source-of-truth. - -## Reset to the current seed - -```bash -cd backend -./scripts/reset_db.sh -``` - -Environment variables (optional): - -- `DB_NAME` (default `openclaw_agency`) -- `DB_USER` (default `postgres`) -- `DB_HOST` (default `127.0.0.1`) -- `DB_PORT` (default `5432`) -- `DB_PASSWORD` (default `postgres`) - -## Updating the seed - -The seed is a **data-only** dump (not schema). Regenerate it from the current DB state: - -```bash -cd backend -PGPASSWORD=postgres pg_dump \ - --data-only \ - --column-inserts \ - --disable-triggers \ - --no-owner \ - --no-privileges \ - -U postgres -h 127.0.0.1 -d openclaw_agency \ - > scripts/seed_data.sql - -# IMPORTANT: do not include alembic_version in the seed (migrations already set it) -# (our committed seed already has this removed) -``` diff --git a/backend/scripts/lint.sh b/backend/scripts/lint.sh deleted file mode 100755 index 36a08089..00000000 --- a/backend/scripts/lint.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -cd "$(dirname "$0")/.." - -. .venv/bin/activate - -python -m black . -python -m isort . -python -m flake8 . diff --git a/backend/scripts/reset_db.sh b/backend/scripts/reset_db.sh deleted file mode 100755 index 5a8e0e7d..00000000 --- a/backend/scripts/reset_db.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -DB_NAME=${DB_NAME:-openclaw_agency} -DB_USER=${DB_USER:-postgres} -DB_HOST=${DB_HOST:-127.0.0.1} -DB_PORT=${DB_PORT:-5432} - -# Never hardcode passwords in git. Prefer: -# - DB_PASSWORD env var, or -# - infer from backend/.env DATABASE_URL -DB_PASSWORD=${DB_PASSWORD:-} - -cd "$(dirname "$0")/.." - -if [[ -z "${DB_PASSWORD}" ]] && [[ -f .env ]]; then - DB_PASSWORD=$(python3 - <<'PY' -import os -from pathlib import Path -from urllib.parse import urlparse - -def parse_database_url(url: str) -> str: - # supports postgresql+psycopg://user:pass@host:port/db - u = urlparse(url) - return u.password or "" - -for line in Path('.env').read_text().splitlines(): - if line.startswith('DATABASE_URL='): - print(parse_database_url(line.split('=',1)[1].strip())) - break -PY -) -fi - -if [[ -z "${DB_PASSWORD}" ]]; then - echo "ERROR: DB_PASSWORD not set and could not infer it from backend/.env DATABASE_URL" >&2 - echo "Set DB_PASSWORD=... or create backend/.env with DATABASE_URL" >&2 - exit 2 -fi - -export PGPASSWORD="$DB_PASSWORD" - -# 1) wipe schema -psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1 \ - -c 'DROP SCHEMA public CASCADE; CREATE SCHEMA public;' - -# 2) migrate -. .venv/bin/activate -alembic upgrade head - -# 3) seed -psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1 \ - -f scripts/seed_data.sql - -echo "Reset complete: $DB_USER@$DB_HOST:$DB_PORT/$DB_NAME" diff --git a/backend/scripts/seed_data.sql b/backend/scripts/seed_data.sql deleted file mode 100644 index 56d8205d..00000000 --- a/backend/scripts/seed_data.sql +++ /dev/null @@ -1,50 +0,0 @@ --- Mission Control seed data (minimal) --- Keep this data-only seed small and deterministic. --- NOTE: Do NOT include alembic_version here; migrations manage it. - -SET client_min_messages = warning; -SET row_security = off; - --- Disable triggers to avoid FK ordering issues during seed. -ALTER TABLE public.employees DISABLE TRIGGER ALL; -ALTER TABLE public.departments DISABLE TRIGGER ALL; -ALTER TABLE public.teams DISABLE TRIGGER ALL; -ALTER TABLE public.projects DISABLE TRIGGER ALL; -ALTER TABLE public.tasks DISABLE TRIGGER ALL; -ALTER TABLE public.task_comments DISABLE TRIGGER ALL; -ALTER TABLE public.project_members DISABLE TRIGGER ALL; -ALTER TABLE public.activities DISABLE TRIGGER ALL; - --- Employees (keep only Abhimanyu) -INSERT INTO public.employees (id, name, employee_type, department_id, manager_id, title, status, openclaw_session_key, notify_enabled, team_id) -VALUES - (1, 'Abhimanyu', 'human', NULL, NULL, 'CEO', 'active', NULL, false, NULL) -ON CONFLICT (id) DO UPDATE SET - name = EXCLUDED.name, - employee_type = EXCLUDED.employee_type, - department_id = EXCLUDED.department_id, - manager_id = EXCLUDED.manager_id, - title = EXCLUDED.title, - status = EXCLUDED.status, - openclaw_session_key = EXCLUDED.openclaw_session_key, - notify_enabled = EXCLUDED.notify_enabled, - team_id = EXCLUDED.team_id; - --- Fix sequences (avoid PK reuse after explicit ids) -SELECT setval('employees_id_seq', (SELECT COALESCE(max(id), 1) FROM public.employees)); -SELECT setval('departments_id_seq', (SELECT COALESCE(max(id), 1) FROM public.departments)); -SELECT setval('teams_id_seq', (SELECT COALESCE(max(id), 1) FROM public.teams)); -SELECT setval('projects_id_seq', (SELECT COALESCE(max(id), 1) FROM public.projects)); -SELECT setval('tasks_id_seq', (SELECT COALESCE(max(id), 1) FROM public.tasks)); -SELECT setval('task_comments_id_seq', (SELECT COALESCE(max(id), 1) FROM public.task_comments)); -SELECT setval('project_members_id_seq', (SELECT COALESCE(max(id), 1) FROM public.project_members)); -SELECT setval('activities_id_seq', (SELECT COALESCE(max(id), 1) FROM public.activities)); - -ALTER TABLE public.employees ENABLE TRIGGER ALL; -ALTER TABLE public.departments ENABLE TRIGGER ALL; -ALTER TABLE public.teams ENABLE TRIGGER ALL; -ALTER TABLE public.projects ENABLE TRIGGER ALL; -ALTER TABLE public.tasks ENABLE TRIGGER ALL; -ALTER TABLE public.task_comments ENABLE TRIGGER ALL; -ALTER TABLE public.project_members ENABLE TRIGGER ALL; -ALTER TABLE public.activities ENABLE TRIGGER ALL; diff --git a/backend/scripts/seed_demo.py b/backend/scripts/seed_demo.py new file mode 100644 index 00000000..af81d08d --- /dev/null +++ b/backend/scripts/seed_demo.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from uuid import uuid4 + +from sqlmodel import Session + +from app.db.session import engine +from app.models.orgs import Org, Workspace +from app.models.users import Membership, User + + +def run() -> None: + with Session(engine) as session: + org = Org(name="Demo Org", slug="demo-org") + session.add(org) + session.commit() + session.refresh(org) + + workspace = Workspace(org_id=org.id, name="Demo Workspace", slug="demo-workspace") + session.add(workspace) + session.commit() + session.refresh(workspace) + + user = User( + clerk_user_id=f"demo-{uuid4()}", + email="demo@example.com", + name="Demo Admin", + is_super_admin=True, + ) + session.add(user) + session.commit() + session.refresh(user) + + membership = Membership( + org_id=org.id, + workspace_id=workspace.id, + user_id=user.id, + role="admin", + ) + session.add(membership) + + session.commit() + + +if __name__ == "__main__": + run() diff --git a/backend/uv.lock b/backend/uv.lock new file mode 100644 index 00000000..77cc3403 --- /dev/null +++ b/backend/uv.lock @@ -0,0 +1,1084 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "alembic" +version = "1.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/e2/efa88e86029cada2da5941ec664d50d9a3b2a91f5066405c6f90e5016c16/alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef", size = 1206463, upload-time = "2024-06-26T15:46:17.728Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/ed/c884465c33c25451e4a5cd4acad154c29e5341e3214e220e7f3478aa4b0d/alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953", size = 232990, upload-time = "2024-06-26T15:46:21.088Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, +] + +[[package]] +name = "black" +version = "24.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/0d/cc2fb42b8c50d80143221515dd7e4766995bd07c56c9a3ed30baf080b6dc/black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875", size = 645813, upload-time = "2024-10-07T19:20:50.361Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/04/bf74c71f592bcd761610bbf67e23e6a3cff824780761f536512437f1e655/black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3", size = 1644256, upload-time = "2024-10-07T19:27:53.355Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ea/a77bab4cf1887f4b2e0bce5516ea0b3ff7d04ba96af21d65024629afedb6/black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65", size = 1448534, upload-time = "2024-10-07T19:26:44.953Z" }, + { url = "https://files.pythonhosted.org/packages/4e/3e/443ef8bc1fbda78e61f79157f303893f3fddf19ca3c8989b163eb3469a12/black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f", size = 1761892, upload-time = "2024-10-07T19:24:10.264Z" }, + { url = "https://files.pythonhosted.org/packages/52/93/eac95ff229049a6901bc84fec6908a5124b8a0b7c26ea766b3b8a5debd22/black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8", size = 1434796, upload-time = "2024-10-07T19:25:06.239Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a0/a993f58d4ecfba035e61fca4e9f64a2ecae838fc9f33ab798c62173ed75c/black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981", size = 1643986, upload-time = "2024-10-07T19:28:50.684Z" }, + { url = "https://files.pythonhosted.org/packages/37/d5/602d0ef5dfcace3fb4f79c436762f130abd9ee8d950fa2abdbf8bbc555e0/black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b", size = 1448085, upload-time = "2024-10-07T19:28:12.093Z" }, + { url = "https://files.pythonhosted.org/packages/47/6d/a3a239e938960df1a662b93d6230d4f3e9b4a22982d060fc38c42f45a56b/black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2", size = 1760928, upload-time = "2024-10-07T19:24:15.233Z" }, + { url = "https://files.pythonhosted.org/packages/dd/cf/af018e13b0eddfb434df4d9cd1b2b7892bab119f7a20123e93f6910982e8/black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b", size = 1436875, upload-time = "2024-10-07T19:24:42.762Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a7/4b27c50537ebca8bec139b872861f9d2bf501c5ec51fcf897cb924d9e264/black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d", size = 206898, upload-time = "2024-10-07T19:20:48.317Z" }, +] + +[[package]] +name = "certifi" +version = "2026.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cryptography" +version = "43.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/ba/0664727028b37e249e73879348cc46d45c5c1a2a2e81e8166462953c5755/cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d", size = 686927, upload-time = "2024-09-03T20:04:20.788Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/28/b92c98a04ba762f8cdeb54eba5c4c84e63cac037a7c5e70117d337b15ad6/cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d", size = 6223222, upload-time = "2024-09-03T20:04:14.466Z" }, + { url = "https://files.pythonhosted.org/packages/33/13/1193774705783ba364121aa2a60132fa31a668b8ababd5edfa1662354ccd/cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062", size = 3794751, upload-time = "2024-09-03T20:04:16.725Z" }, + { url = "https://files.pythonhosted.org/packages/5e/4b/39bb3c4c8cfb3e94e736b8d8859ce5c81536e91a1033b1d26770c4249000/cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962", size = 3981827, upload-time = "2024-09-03T20:03:55.035Z" }, + { url = "https://files.pythonhosted.org/packages/ce/dc/1471d4d56608e1013237af334b8a4c35d53895694fbb73882d1c4fd3f55e/cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277", size = 3780034, upload-time = "2024-09-03T20:03:58.972Z" }, + { url = "https://files.pythonhosted.org/packages/ad/43/7a9920135b0d5437cc2f8f529fa757431eb6a7736ddfadfdee1cc5890800/cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a", size = 3993407, upload-time = "2024-09-03T20:03:36.682Z" }, + { url = "https://files.pythonhosted.org/packages/cc/42/9ab8467af6c0b76f3d9b8f01d1cf25b9c9f3f2151f4acfab888d21c55a72/cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042", size = 3886457, upload-time = "2024-09-03T20:03:52.995Z" }, + { url = "https://files.pythonhosted.org/packages/a4/65/430509e31700286ec02868a2457d2111d03ccefc20349d24e58d171ae0a7/cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494", size = 4081499, upload-time = "2024-09-03T20:03:32.522Z" }, + { url = "https://files.pythonhosted.org/packages/bb/18/a04b6467e6e09df8c73b91dcee8878f4a438a43a3603dc3cd6f8003b92d8/cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2", size = 2616504, upload-time = "2024-09-03T20:04:09.459Z" }, + { url = "https://files.pythonhosted.org/packages/cc/73/0eacbdc437202edcbdc07f3576ed8fb8b0ab79d27bf2c5d822d758a72faa/cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d", size = 3067456, upload-time = "2024-09-03T20:03:40.775Z" }, + { url = "https://files.pythonhosted.org/packages/8a/b6/bc54b371f02cffd35ff8dc6baba88304d7cf8e83632566b4b42e00383e03/cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d", size = 6225263, upload-time = "2024-09-03T20:03:43.181Z" }, + { url = "https://files.pythonhosted.org/packages/00/0e/8217e348a1fa417ec4c78cd3cdf24154f5e76fd7597343a35bd403650dfd/cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806", size = 3794368, upload-time = "2024-09-03T20:03:18.051Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ed/38b6be7254d8f7251fde8054af597ee8afa14f911da67a9410a45f602fc3/cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85", size = 3981750, upload-time = "2024-09-03T20:04:18.775Z" }, + { url = "https://files.pythonhosted.org/packages/64/f3/b7946c3887cf7436f002f4cbb1e6aec77b8d299b86be48eeadfefb937c4b/cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c", size = 3778925, upload-time = "2024-09-03T20:03:45.022Z" }, + { url = "https://files.pythonhosted.org/packages/ac/7e/ebda4dd4ae098a0990753efbb4b50954f1d03003846b943ea85070782da7/cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1", size = 3993152, upload-time = "2024-09-03T20:03:30.108Z" }, + { url = "https://files.pythonhosted.org/packages/43/f6/feebbd78a3e341e3913846a3bb2c29d0b09b1b3af1573c6baabc2533e147/cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa", size = 3886392, upload-time = "2024-09-03T20:03:34.543Z" }, + { url = "https://files.pythonhosted.org/packages/bd/4c/ab0b9407d5247576290b4fd8abd06b7f51bd414f04eef0f2800675512d61/cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4", size = 4082606, upload-time = "2024-09-03T20:03:27.836Z" }, + { url = "https://files.pythonhosted.org/packages/05/36/e532a671998d6fcfdb9122da16434347a58a6bae9465e527e450e0bc60a5/cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47", size = 2617948, upload-time = "2024-09-03T20:03:25.446Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c6/c09cee6968add5ff868525c3815e5dccc0e3c6e89eec58dc9135d3c40e88/cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb", size = 3070445, upload-time = "2024-09-03T20:03:21.179Z" }, +] + +[[package]] +name = "fastapi" +version = "0.115.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/db/5781f19bd30745885e0737ff3fdd4e63e7bc691710f9da691128bb0dc73b/fastapi-0.115.4.tar.gz", hash = "sha256:db653475586b091cb8b2fec2ac54a680ac6a158e07406e1abae31679e8826349", size = 300737, upload-time = "2024-10-27T22:02:04.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/f6/af0d1f58f86002be0cf1e2665cdd6f7a4a71cdc8a7a9438cdc9e3b5375fe/fastapi-0.115.4-py3-none-any.whl", hash = "sha256:0b504a063ffb3cf96a5e27dc1bc32c80ca743a2528574f9cdc77daa2d31b4742", size = 94732, upload-time = "2024-10-27T22:02:00.974Z" }, +] + +[[package]] +name = "fastapi-clerk-auth" +version = "0.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "fastapi" }, + { name = "pyjwt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/af/410786fc25e52378df227f4d04e6b43b8c3bc9007221440822e9a10af051/fastapi_clerk_auth-0.0.9.tar.gz", hash = "sha256:503bdd3943e5f49095bd5dd04a8b14ebe6214bb3f39ae45df876e6275db5cf6d", size = 14359, upload-time = "2025-11-11T06:12:37.775Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/4e/058ecbe4fa0d470c3979f1272c0199cc47afb0ed935edb07b55441be8994/fastapi_clerk_auth-0.0.9-py3-none-any.whl", hash = "sha256:f9a47cfc65a2562c144a798ce0022a288799dac1149001b5a109865d578b2647", size = 6464, upload-time = "2025-11-11T06:12:35.655Z" }, +] + +[[package]] +name = "flake8" +version = "7.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mccabe" }, + { name = "pycodestyle" }, + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/72/e8d66150c4fcace3c0a450466aa3480506ba2cae7b61e100a2613afc3907/flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38", size = 48054, upload-time = "2024-08-04T20:32:44.311Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/42/65004373ac4617464f35ed15931b30d764f53cdd30cc78d5aea349c8c050/flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213", size = 57731, upload-time = "2024-08-04T20:32:42.661Z" }, +] + +[[package]] +name = "greenlet" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/99/1cd3411c56a410994669062bd73dd58270c00cc074cac15f385a1fd91f8a/greenlet-3.3.1.tar.gz", hash = "sha256:41848f3230b58c08bb43dee542e74a2a2e34d3c59dc3076cec9151aeeedcae98", size = 184690, upload-time = "2026-01-23T15:31:02.076Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/c8/9d76a66421d1ae24340dfae7e79c313957f6e3195c144d2c73333b5bfe34/greenlet-3.3.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7e806ca53acf6d15a888405880766ec84721aa4181261cd11a457dfe9a7a4975", size = 276443, upload-time = "2026-01-23T15:30:10.066Z" }, + { url = "https://files.pythonhosted.org/packages/81/99/401ff34bb3c032d1f10477d199724f5e5f6fbfb59816ad1455c79c1eb8e7/greenlet-3.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d842c94b9155f1c9b3058036c24ffb8ff78b428414a19792b2380be9cecf4f36", size = 597359, upload-time = "2026-01-23T16:00:57.394Z" }, + { url = "https://files.pythonhosted.org/packages/2b/bc/4dcc0871ed557792d304f50be0f7487a14e017952ec689effe2180a6ff35/greenlet-3.3.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20fedaadd422fa02695f82093f9a98bad3dab5fcda793c658b945fcde2ab27ba", size = 607805, upload-time = "2026-01-23T16:05:28.068Z" }, + { url = "https://files.pythonhosted.org/packages/cf/05/821587cf19e2ce1f2b24945d890b164401e5085f9d09cbd969b0c193cd20/greenlet-3.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14194f5f4305800ff329cbf02c5fcc88f01886cadd29941b807668a45f0d2336", size = 609947, upload-time = "2026-01-23T15:32:51.004Z" }, + { url = "https://files.pythonhosted.org/packages/a4/52/ee8c46ed9f8babaa93a19e577f26e3d28a519feac6350ed6f25f1afee7e9/greenlet-3.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7b2fe4150a0cf59f847a67db8c155ac36aed89080a6a639e9f16df5d6c6096f1", size = 1567487, upload-time = "2026-01-23T16:04:22.125Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7c/456a74f07029597626f3a6db71b273a3632aecb9afafeeca452cfa633197/greenlet-3.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49f4ad195d45f4a66a0eb9c1ba4832bb380570d361912fa3554746830d332149", size = 1636087, upload-time = "2026-01-23T15:33:47.486Z" }, + { url = "https://files.pythonhosted.org/packages/34/2f/5e0e41f33c69655300a5e54aeb637cf8ff57f1786a3aba374eacc0228c1d/greenlet-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cc98b9c4e4870fa983436afa999d4eb16b12872fab7071423d5262fa7120d57a", size = 227156, upload-time = "2026-01-23T15:34:34.808Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ab/717c58343cf02c5265b531384b248787e04d8160b8afe53d9eec053d7b44/greenlet-3.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:bfb2d1763d777de5ee495c85309460f6fd8146e50ec9d0ae0183dbf6f0a829d1", size = 226403, upload-time = "2026-01-23T15:31:39.372Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ab/d26750f2b7242c2b90ea2ad71de70cfcd73a948a49513188a0fc0d6fc15a/greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3", size = 275205, upload-time = "2026-01-23T15:30:24.556Z" }, + { url = "https://files.pythonhosted.org/packages/10/d3/be7d19e8fad7c5a78eeefb2d896a08cd4643e1e90c605c4be3b46264998f/greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac", size = 599284, upload-time = "2026-01-23T16:00:58.584Z" }, + { url = "https://files.pythonhosted.org/packages/ae/21/fe703aaa056fdb0f17e5afd4b5c80195bbdab701208918938bd15b00d39b/greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd", size = 610274, upload-time = "2026-01-23T16:05:29.312Z" }, + { url = "https://files.pythonhosted.org/packages/cb/86/5c6ab23bb3c28c21ed6bebad006515cfe08b04613eb105ca0041fecca852/greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3", size = 612904, upload-time = "2026-01-23T15:32:52.317Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/7949994264e22639e40718c2daf6f6df5169bf48fb038c008a489ec53a50/greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951", size = 1567316, upload-time = "2026-01-23T16:04:23.316Z" }, + { url = "https://files.pythonhosted.org/packages/8d/6e/d73c94d13b6465e9f7cd6231c68abde838bb22408596c05d9059830b7872/greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2", size = 1636549, upload-time = "2026-01-23T15:33:48.643Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b3/c9c23a6478b3bcc91f979ce4ca50879e4d0b2bd7b9a53d8ecded719b92e2/greenlet-3.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:27289986f4e5b0edec7b5a91063c109f0276abb09a7e9bdab08437525977c946", size = 227042, upload-time = "2026-01-23T15:33:58.216Z" }, + { url = "https://files.pythonhosted.org/packages/90/e7/824beda656097edee36ab15809fd063447b200cc03a7f6a24c34d520bc88/greenlet-3.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:2f080e028001c5273e0b42690eaf359aeef9cb1389da0f171ea51a5dc3c7608d", size = 226294, upload-time = "2026-01-23T15:30:52.73Z" }, + { url = "https://files.pythonhosted.org/packages/ae/fb/011c7c717213182caf78084a9bea51c8590b0afda98001f69d9f853a495b/greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", size = 275737, upload-time = "2026-01-23T15:32:16.889Z" }, + { url = "https://files.pythonhosted.org/packages/41/2e/a3a417d620363fdbb08a48b1dd582956a46a61bf8fd27ee8164f9dfe87c2/greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", size = 646422, upload-time = "2026-01-23T16:01:00.354Z" }, + { url = "https://files.pythonhosted.org/packages/b4/09/c6c4a0db47defafd2d6bab8ddfe47ad19963b4e30f5bed84d75328059f8c/greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", size = 658219, upload-time = "2026-01-23T16:05:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/80/38/9d42d60dffb04b45f03dbab9430898352dba277758640751dc5cc316c521/greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", size = 660237, upload-time = "2026-01-23T15:32:53.967Z" }, + { url = "https://files.pythonhosted.org/packages/96/61/373c30b7197f9e756e4c81ae90a8d55dc3598c17673f91f4d31c3c689c3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", size = 1615261, upload-time = "2026-01-23T16:04:25.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d3/ca534310343f5945316f9451e953dcd89b36fe7a19de652a1dc5a0eeef3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", size = 1683719, upload-time = "2026-01-23T15:33:50.61Z" }, + { url = "https://files.pythonhosted.org/packages/52/cb/c21a3fd5d2c9c8b622e7bede6d6d00e00551a5ee474ea6d831b5f567a8b4/greenlet-3.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:96aff77af063b607f2489473484e39a0bbae730f2ea90c9e5606c9b73c44174a", size = 228125, upload-time = "2026-01-23T15:32:45.265Z" }, + { url = "https://files.pythonhosted.org/packages/6a/8e/8a2db6d11491837af1de64b8aff23707c6e85241be13c60ed399a72e2ef8/greenlet-3.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:b066e8b50e28b503f604fa538adc764a638b38cf8e81e025011d26e8a627fa79", size = 227519, upload-time = "2026-01-23T15:31:47.284Z" }, + { url = "https://files.pythonhosted.org/packages/28/24/cbbec49bacdcc9ec652a81d3efef7b59f326697e7edf6ed775a5e08e54c2/greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", size = 282706, upload-time = "2026-01-23T15:33:05.525Z" }, + { url = "https://files.pythonhosted.org/packages/86/2e/4f2b9323c144c4fe8842a4e0d92121465485c3c2c5b9e9b30a52e80f523f/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", size = 651209, upload-time = "2026-01-23T16:01:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/d9/87/50ca60e515f5bb55a2fbc5f0c9b5b156de7d2fc51a0a69abc9d23914a237/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", size = 654300, upload-time = "2026-01-23T16:05:32.199Z" }, + { url = "https://files.pythonhosted.org/packages/1d/94/74310866dfa2b73dd08659a3d18762f83985ad3281901ba0ee9a815194fb/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", size = 653842, upload-time = "2026-01-23T15:32:55.671Z" }, + { url = "https://files.pythonhosted.org/packages/97/43/8bf0ffa3d498eeee4c58c212a3905dd6146c01c8dc0b0a046481ca29b18c/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53", size = 1614917, upload-time = "2026-01-23T16:04:26.276Z" }, + { url = "https://files.pythonhosted.org/packages/89/90/a3be7a5f378fc6e84abe4dcfb2ba32b07786861172e502388b4c90000d1b/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", size = 1676092, upload-time = "2026-01-23T15:33:52.176Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2b/98c7f93e6db9977aaee07eb1e51ca63bd5f779b900d362791d3252e60558/greenlet-3.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:301860987846c24cb8964bdec0e31a96ad4a2a801b41b4ef40963c1b44f33451", size = 233181, upload-time = "2026-01-23T15:33:00.29Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httptools" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5", size = 206280, upload-time = "2025-10-10T03:54:39.274Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5", size = 110004, upload-time = "2025-10-10T03:54:40.403Z" }, + { url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03", size = 517655, upload-time = "2025-10-10T03:54:41.347Z" }, + { url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2", size = 511440, upload-time = "2025-10-10T03:54:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362", size = 495186, upload-time = "2025-10-10T03:54:43.937Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c", size = 499192, upload-time = "2025-10-10T03:54:45.003Z" }, + { url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321", size = 86694, upload-time = "2025-10-10T03:54:45.923Z" }, + { url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3", size = 202889, upload-time = "2025-10-10T03:54:47.089Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca", size = 108180, upload-time = "2025-10-10T03:54:48.052Z" }, + { url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c", size = 478596, upload-time = "2025-10-10T03:54:48.919Z" }, + { url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66", size = 473268, upload-time = "2025-10-10T03:54:49.993Z" }, + { url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346", size = 455517, upload-time = "2025-10-10T03:54:51.066Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650", size = 458337, upload-time = "2025-10-10T03:54:52.196Z" }, + { url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6", size = 85743, upload-time = "2025-10-10T03:54:53.448Z" }, + { url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619, upload-time = "2025-10-10T03:54:54.321Z" }, + { url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714, upload-time = "2025-10-10T03:54:55.163Z" }, + { url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909, upload-time = "2025-10-10T03:54:56.056Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831, upload-time = "2025-10-10T03:54:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631, upload-time = "2025-10-10T03:54:58.219Z" }, + { url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910, upload-time = "2025-10-10T03:54:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" }, +] + +[[package]] +name = "httpx" +version = "0.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189, upload-time = "2024-08-27T12:54:01.334Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395, upload-time = "2024-08-27T12:53:59.653Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "isort" +version = "5.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303, upload-time = "2023-12-13T20:37:26.124Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310, upload-time = "2023-12-13T20:37:23.244Z" }, +] + +[[package]] +name = "mako" +version = "1.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "openclaw-agency-backend" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "alembic" }, + { name = "fastapi" }, + { name = "fastapi-clerk-auth" }, + { name = "httpx" }, + { name = "psycopg", extra = ["binary"] }, + { name = "pydantic-settings" }, + { name = "python-dotenv" }, + { name = "redis" }, + { name = "rq" }, + { name = "sqlalchemy" }, + { name = "sqlmodel" }, + { name = "sse-starlette" }, + { name = "uvicorn", extra = ["standard"] }, + { name = "websockets" }, +] + +[package.optional-dependencies] +dev = [ + { name = "black" }, + { name = "flake8" }, + { name = "isort" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "alembic", specifier = "==1.13.2" }, + { name = "black", marker = "extra == 'dev'", specifier = "==24.10.0" }, + { name = "fastapi", specifier = "==0.115.4" }, + { name = "fastapi-clerk-auth", specifier = "==0.0.9" }, + { name = "flake8", marker = "extra == 'dev'", specifier = "==7.1.1" }, + { name = "httpx", specifier = "==0.27.2" }, + { name = "isort", marker = "extra == 'dev'", specifier = "==5.13.2" }, + { name = "psycopg", extras = ["binary"], specifier = "==3.2.1" }, + { name = "pydantic-settings", specifier = "==2.5.2" }, + { name = "pytest", marker = "extra == 'dev'", specifier = "==8.3.3" }, + { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = "==0.24.0" }, + { name = "python-dotenv", specifier = "==1.0.1" }, + { name = "redis", specifier = "==5.1.1" }, + { name = "rq", specifier = "==1.16.2" }, + { name = "ruff", marker = "extra == 'dev'", specifier = "==0.6.9" }, + { name = "sqlalchemy", specifier = "==2.0.34" }, + { name = "sqlmodel", specifier = "==0.0.22" }, + { name = "sse-starlette", specifier = "==2.1.3" }, + { name = "uvicorn", extras = ["standard"], specifier = "==0.30.6" }, + { name = "websockets", specifier = "==12.0" }, +] +provides-extras = ["dev"] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "psycopg" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/8e/f176997fd790d3dce9fa0ca695391beaeee39af7ecd6d426c4c063cf6744/psycopg-3.2.1.tar.gz", hash = "sha256:dc8da6dc8729dacacda3cc2f17d2c9397a70a66cf0d2b69c91065d60d5f00cb7", size = 155313, upload-time = "2024-07-01T03:35:50.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/0f755db36f47f96464463385552f8f132a981731356837c9a30a11ab2d35/psycopg-3.2.1-py3-none-any.whl", hash = "sha256:ece385fb413a37db332f97c49208b36cf030ff02b199d7635ed2fbd378724175", size = 197743, upload-time = "2024-07-01T03:30:14.942Z" }, +] + +[package.optional-dependencies] +binary = [ + { name = "psycopg-binary", marker = "implementation_name != 'pypy'" }, +] + +[[package]] +name = "psycopg-binary" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/5d/51d39aafab4384a744d5e927b7867f3dadd8537249e8173e34aaf894db94/psycopg_binary-3.2.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:7066d3dca196ed0dc6172f9777b2d62e4f138705886be656cccff2d555234d60", size = 3359766, upload-time = "2024-07-01T03:32:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/e4/7b/75be686af04e2019b53a9ff22de3aa750db7d34f532e4b949ed15a78b627/psycopg_binary-3.2.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:28ada5f610468c57d8a4a055a8ea915d0085a43d794266c4f3b9d02f4288f4db", size = 3503325, upload-time = "2024-07-01T03:32:45.464Z" }, + { url = "https://files.pythonhosted.org/packages/3f/9a/28da916a65fb40fb3e1a97e1ae0a26860d8c1265c6e9766bd6c47abc437b/psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e8213bf50af073b1aa8dc3cff123bfeedac86332a16c1b7274910bc88a847c7", size = 4443593, upload-time = "2024-07-01T03:32:54.341Z" }, + { url = "https://files.pythonhosted.org/packages/b0/9a/3dc1237a2ef3344b347af79e1aad2a60277cfafa2846f54cb13e1cd8c528/psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74d623261655a169bc84a9669890975c229f2fa6e19a7f2d10a77675dcf1a707", size = 4247005, upload-time = "2024-07-01T03:33:01.162Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a9/06491cb0338b6f0868d349d2a526586dc165e508b64daa2ff45f9db7ba4b/psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42781ba94e8842ee98bca5a7d0c44cc9d067500fedca2d6a90fa3609b6d16b42", size = 4484179, upload-time = "2024-07-01T03:33:09.385Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5f/b1116467dd18b4efc1aa7f03c96da751724a43c6a630979c61f60a9fbe5f/psycopg_binary-3.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e6669091d09f8ba36e10ce678a6d9916e110446236a9b92346464a3565635e", size = 4186490, upload-time = "2024-07-01T03:33:15.295Z" }, + { url = "https://files.pythonhosted.org/packages/a4/87/6092d1701d36c5aeb74c35cb54266fd44ee0f7711cafa4c0bffd873bdb61/psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b09e8a576a2ac69d695032ee76f31e03b30781828b5dd6d18c6a009e5a3d1c35", size = 3109385, upload-time = "2024-07-01T03:33:20.174Z" }, + { url = "https://files.pythonhosted.org/packages/62/61/4ad7e29d09202478b6f568fff19efa978a4f2c25cb5efcd73544a4ee8be7/psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8f28ff0cb9f1defdc4a6f8c958bf6787274247e7dfeca811f6e2f56602695fb1", size = 3094397, upload-time = "2024-07-01T03:33:25.285Z" }, + { url = "https://files.pythonhosted.org/packages/b7/dd/0ae42c64bf524d1fcf9bf861ab09d331e693ae00e527ba08131b2d3729a3/psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4c84fcac8a3a3479ac14673095cc4e1fdba2935499f72c436785ac679bec0d1a", size = 3184097, upload-time = "2024-07-01T03:33:31.268Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f0/09329ebb0cd03e2ee5786fc9914ac904f4965b78627f15826f8258fde734/psycopg_binary-3.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:950fd666ec9e9fe6a8eeb2b5a8f17301790e518953730ad44d715b59ffdbc67f", size = 3228517, upload-time = "2024-07-01T03:33:37.824Z" }, + { url = "https://files.pythonhosted.org/packages/60/2f/979228189adbeb59afce626f1e7c3bf73cc7ff94217099a2ddfd6fd132ff/psycopg_binary-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:334046a937bb086c36e2c6889fe327f9f29bfc085d678f70fac0b0618949f674", size = 2911959, upload-time = "2024-07-01T03:33:43.357Z" }, +] + +[[package]] +name = "pycodestyle" +version = "2.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/aa/210b2c9aedd8c1cbeea31a50e42050ad56187754b34eb214c46709445801/pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521", size = 39232, upload-time = "2024-08-04T20:26:54.576Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/d8/a211b3f85e99a0daa2ddec96c949cac6824bd305b040571b82a03dd62636/pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3", size = 31284, upload-time = "2024-08-04T20:26:53.173Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/27/0bed9dd26b93328b60a1402febc780e7be72b42847fa8b5c94b7d0aeb6d1/pydantic_settings-2.5.2.tar.gz", hash = "sha256:f90b139682bee4d2065273d5185d71d37ea46cfe57e1b5ae184fc6a0b2484ca0", size = 70938, upload-time = "2024-09-11T09:08:08.489Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/8d/29e82e333f32d9e2051c10764b906c2a6cd140992910b5f49762790911ba/pydantic_settings-2.5.2-py3-none-any.whl", hash = "sha256:2c912e55fd5794a59bf8c832b9de832dcfdf4778d79ff79b708744eed499a907", size = 26864, upload-time = "2024-09-11T09:08:07.242Z" }, +] + +[[package]] +name = "pyflakes" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/f9/669d8c9c86613c9d568757c7f5824bd3197d7b1c6c27553bc5618a27cce2/pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", size = 63788, upload-time = "2024-01-05T00:28:47.703Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a", size = 62725, upload-time = "2024-01-05T00:28:45.903Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/68/ce067f09fca4abeca8771fe667d89cc347d1e99da3e093112ac329c6020e/pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c", size = 78825, upload-time = "2024-08-01T15:01:08.445Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/84/0fdf9b18ba31d69877bd39c9cd6052b47f3761e9910c15de788e519f079f/PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", size = 22344, upload-time = "2024-08-01T15:01:06.481Z" }, +] + +[[package]] +name = "pytest" +version = "8.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/6c/62bbd536103af674e227c41a8f3dcd022d591f6eed5facb5a0f31ee33bbc/pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", size = 1442487, upload-time = "2024-09-10T10:52:15.003Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341, upload-time = "2024-09-10T10:52:12.54Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/6d/c6cf50ce320cf8611df7a1254d86233b3df7cc07f9b5f5cbcb82e08aa534/pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276", size = 49855, upload-time = "2024-08-22T08:03:18.145Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/31/6607dab48616902f76885dfcf62c08d929796fc3b2d2318faf9fd54dbed9/pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b", size = 18024, upload-time = "2024-08-22T08:03:15.536Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115, upload-time = "2024-01-23T06:33:00.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863, upload-time = "2024-01-23T06:32:58.246Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "redis" +version = "5.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/58/dcf97c3c09d429c3bb830d6075322256da3dba42df25359bd1c82b442d20/redis-5.1.1.tar.gz", hash = "sha256:f6c997521fedbae53387307c5d0bf784d9acc28d9f1d058abeac566ec4dbed72", size = 4607302, upload-time = "2024-10-04T13:03:23.595Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/f1/feeeaaaac0f589bcbc12c02da357cf635ee383c9128b77230a1e99118885/redis-5.1.1-py3-none-any.whl", hash = "sha256:f8ea06b7482a668c6475ae202ed8d9bcaa409f6e87fb77ed1043d912afd62e24", size = 261283, upload-time = "2024-10-04T13:03:21.385Z" }, +] + +[[package]] +name = "rq" +version = "1.16.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "redis" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/1d/a020e8f3559293635dd6283d037bfc15f866136498da3db1e5416c65773d/rq-1.16.2.tar.gz", hash = "sha256:5c5b9ad5fbaf792b8fada25cc7627f4d206a9a4455aced371d4f501cc3f13b34", size = 629881, upload-time = "2024-05-01T07:13:14.136Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/35/db396caf7cfe71a9e661cea520b276f1472df25015e7cb9143c65a1dca6d/rq-1.16.2-py3-none-any.whl", hash = "sha256:52e619f6cb469b00e04da74305045d244b75fecb2ecaa4f26422add57d3c5f09", size = 90911, upload-time = "2024-05-01T07:13:11.158Z" }, +] + +[[package]] +name = "ruff" +version = "0.6.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/0d/6148a48dab5662ca1d5a93b7c0d13c03abd3cc7e2f35db08410e47cef15d/ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2", size = 3095355, upload-time = "2024-10-04T13:40:28.594Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/8f/f7a0a0ef1818662efb32ed6df16078c95da7a0a3248d64c2410c1e27799f/ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd", size = 10440526, upload-time = "2024-10-04T13:39:21.747Z" }, + { url = "https://files.pythonhosted.org/packages/8b/69/b179a5faf936a9e2ab45bb412a668e4661eded964ccfa19d533f29463ef6/ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec", size = 10034612, upload-time = "2024-10-04T13:39:26.301Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ef/fd1b4be979c579d191eeac37b5cfc0ec906de72c8bcd8595e2c81bb700c1/ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c", size = 9706197, upload-time = "2024-10-04T13:39:29.297Z" }, + { url = "https://files.pythonhosted.org/packages/29/61/b376d775deb5851cb48d893c568b511a6d3625ef2c129ad5698b64fb523c/ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e", size = 10751855, upload-time = "2024-10-04T13:39:33.175Z" }, + { url = "https://files.pythonhosted.org/packages/13/d7/def9e5f446d75b9a9c19b24231a3a658c075d79163b08582e56fa5dcfa38/ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577", size = 10200889, upload-time = "2024-10-04T13:39:36.867Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d6/7f34160818bcb6e84ce293a5966cba368d9112ff0289b273fbb689046047/ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829", size = 11038678, upload-time = "2024-10-04T13:39:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/13/34/a40ff8ae62fb1b26fb8e6fa7e64bc0e0a834b47317880de22edd6bfb54fb/ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5", size = 11808682, upload-time = "2024-10-04T13:39:52.141Z" }, + { url = "https://files.pythonhosted.org/packages/2e/6d/25a4386ae4009fc798bd10ba48c942d1b0b3e459b5403028f1214b6dd161/ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7", size = 11330446, upload-time = "2024-10-04T13:39:55.783Z" }, + { url = "https://files.pythonhosted.org/packages/f7/f6/bdf891a9200d692c94ebcd06ae5a2fa5894e522f2c66c2a12dd5d8cb2654/ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f", size = 12483048, upload-time = "2024-10-04T13:39:58.845Z" }, + { url = "https://files.pythonhosted.org/packages/a7/86/96f4252f41840e325b3fa6c48297e661abb9f564bd7dcc0572398c8daa42/ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa", size = 10936855, upload-time = "2024-10-04T13:40:01.818Z" }, + { url = "https://files.pythonhosted.org/packages/45/87/801a52d26c8dbf73424238e9908b9ceac430d903c8ef35eab1b44fcfa2bd/ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb", size = 10713007, upload-time = "2024-10-04T13:40:05.384Z" }, + { url = "https://files.pythonhosted.org/packages/be/27/6f7161d90320a389695e32b6ebdbfbedde28ccbf52451e4b723d7ce744ad/ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0", size = 10274594, upload-time = "2024-10-04T13:40:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/00/52/dc311775e7b5f5b19831563cb1572ecce63e62681bccc609867711fae317/ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625", size = 10608024, upload-time = "2024-10-04T13:40:11.923Z" }, + { url = "https://files.pythonhosted.org/packages/98/b6/be0a1ddcbac65a30c985cf7224c4fce786ba2c51e7efeb5178fe410ed3cf/ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039", size = 10982085, upload-time = "2024-10-04T13:40:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a4/c84bc13d0b573cf7bb7d17b16d6d29f84267c92d79b2f478d4ce322e8e72/ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d", size = 8522088, upload-time = "2024-10-04T13:40:19.168Z" }, + { url = "https://files.pythonhosted.org/packages/74/be/fc352bd8ca40daae8740b54c1c3e905a7efe470d420a268cd62150248c91/ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117", size = 9359275, upload-time = "2024-10-04T13:40:22.852Z" }, + { url = "https://files.pythonhosted.org/packages/3e/14/fd026bc74ded05e2351681545a5f626e78ef831f8edce064d61acd2e6ec7/ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93", size = 8679879, upload-time = "2024-10-04T13:40:25.797Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.34" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/fa/ca0fdd7b6b0cf53a8237a8ee7e487f8be16e4a2ee6d840d6e8e105cd9c86/sqlalchemy-2.0.34.tar.gz", hash = "sha256:10d8f36990dd929690666679b0f42235c159a7051534adb135728ee52828dd22", size = 9556527, upload-time = "2024-09-04T15:19:39.533Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/76/62eb5c62593d6d351f17202aa532f17b91c51b1b04e24a3a97530cb6118e/SQLAlchemy-2.0.34-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:53e68b091492c8ed2bd0141e00ad3089bcc6bf0e6ec4142ad6505b4afe64163e", size = 2089191, upload-time = "2024-09-04T16:55:33.256Z" }, + { url = "https://files.pythonhosted.org/packages/8a/7c/d43a14aef45bcb196f017ba2783eb3e42dd4c65c43be8b9f29bb5ec7d131/SQLAlchemy-2.0.34-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bcd18441a49499bf5528deaa9dee1f5c01ca491fc2791b13604e8f972877f812", size = 2079662, upload-time = "2024-09-04T16:55:35.165Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/ec59e5d3643d49d57ae59a62b6e5b3da39344617ce249f2561bfb4ac0458/SQLAlchemy-2.0.34-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:165bbe0b376541092bf49542bd9827b048357f4623486096fc9aaa6d4e7c59a2", size = 3229161, upload-time = "2024-09-04T17:08:07.645Z" }, + { url = "https://files.pythonhosted.org/packages/fd/2e/e6129761dd5588a5623c6051c31e45935b72a5b17ed87b209e39a0b2a25c/SQLAlchemy-2.0.34-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3330415cd387d2b88600e8e26b510d0370db9b7eaf984354a43e19c40df2e2b", size = 3240054, upload-time = "2024-09-04T15:37:18.61Z" }, + { url = "https://files.pythonhosted.org/packages/70/08/4f994445215d7932bf2a490570fef9a5d1ba42cdf1cc9c48a6f7f04d1cfc/SQLAlchemy-2.0.34-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97b850f73f8abbffb66ccbab6e55a195a0eb655e5dc74624d15cff4bfb35bd74", size = 3175538, upload-time = "2024-09-04T17:08:09.915Z" }, + { url = "https://files.pythonhosted.org/packages/5e/19/4d4cc024cd7d50e25bf1c1ba61974b2b6e2fab8ea22f1569c47380b34e95/SQLAlchemy-2.0.34-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee4c6917857fd6121ed84f56d1dc78eb1d0e87f845ab5a568aba73e78adf83", size = 3202149, upload-time = "2024-09-04T15:37:20.441Z" }, + { url = "https://files.pythonhosted.org/packages/87/02/7ada4b6bfd5421aa7d65bd0ee9d76acc15b53ae26378b2ab8bba1ba3f78f/SQLAlchemy-2.0.34-cp312-cp312-win32.whl", hash = "sha256:fbb034f565ecbe6c530dff948239377ba859420d146d5f62f0271407ffb8c580", size = 2059547, upload-time = "2024-09-04T15:47:56.087Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fc/d1315ddb8529c768789954350268cd53167747649ddb709517c5e0a15c7e/SQLAlchemy-2.0.34-cp312-cp312-win_amd64.whl", hash = "sha256:707c8f44931a4facd4149b52b75b80544a8d824162602b8cd2fe788207307f9a", size = 2085274, upload-time = "2024-09-04T15:47:57.618Z" }, + { url = "https://files.pythonhosted.org/packages/09/14/5c9b872fba29ccedeb905d0a5c203ad86287b8bb1bb8eda96bfe8a05f65b/SQLAlchemy-2.0.34-py3-none-any.whl", hash = "sha256:7286c353ee6475613d8beff83167374006c6b3e3f0e6491bfe8ca610eb1dec0f", size = 1880671, upload-time = "2024-09-04T16:12:59.015Z" }, +] + +[[package]] +name = "sqlmodel" +version = "0.0.22" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/39/8641040ab0d5e1d8a1c2325ae89a01ae659fc96c61a43d158fb71c9a0bf0/sqlmodel-0.0.22.tar.gz", hash = "sha256:7d37c882a30c43464d143e35e9ecaf945d88035e20117bf5ec2834a23cbe505e", size = 116392, upload-time = "2024-08-31T09:43:24.088Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/b1/3af5104b716c420e40a6ea1b09886cae3a1b9f4538343875f637755cae5b/sqlmodel-0.0.22-py3-none-any.whl", hash = "sha256:a1ed13e28a1f4057cbf4ff6cdb4fc09e85702621d3259ba17b3c230bfb2f941b", size = 28276, upload-time = "2024-08-31T09:43:22.358Z" }, +] + +[[package]] +name = "sse-starlette" +version = "2.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, + { name = "uvicorn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678, upload-time = "2024-08-01T08:52:50.248Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/aa/36b271bc4fa1d2796311ee7c7283a3a1c348bad426d37293609ca4300eef/sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772", size = 9383, upload-time = "2024-08-01T08:52:48.659Z" }, +] + +[[package]] +name = "starlette" +version = "0.41.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/4c/9b5764bd22eec91c4039ef4c55334e9187085da2d8a2df7bd570869aae18/starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835", size = 2574159, upload-time = "2024-11-18T19:45:04.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/00/2b325970b3060c7cecebab6d295afe763365822b1306a12eeab198f74323/starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7", size = 73225, upload-time = "2024-11-18T19:45:02.027Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.30.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/01/5e637e7aa9dd031be5376b9fb749ec20b86f5a5b6a49b87fabd374d5fa9f/uvicorn-0.30.6.tar.gz", hash = "sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788", size = 42825, upload-time = "2024-08-13T09:27:35.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/8e/cdc7d6263db313030e4c257dd5ba3909ebc4e4fb53ad62d5f09b1a2f5458/uvicorn-0.30.6-py3-none-any.whl", hash = "sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5", size = 62835, upload-time = "2024-08-13T09:27:33.536Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, + { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, + { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, + { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, + { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, + { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, + { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, + { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, + { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, + { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, + { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, + { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, + { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, + { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, +] + +[[package]] +name = "websockets" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/62/7a7874b7285413c954a4cca3c11fd851f11b2fe5b4ae2d9bee4f6d9bdb10/websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b", size = 104994, upload-time = "2023-10-21T14:21:11.88Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/6d/23cc898647c8a614a0d9ca703695dd04322fb5135096a20c2684b7c852b6/websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df", size = 124061, upload-time = "2023-10-21T14:20:02.221Z" }, + { url = "https://files.pythonhosted.org/packages/39/34/364f30fdf1a375e4002a26ee3061138d1571dfda6421126127d379d13930/websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc", size = 121296, upload-time = "2023-10-21T14:20:03.591Z" }, + { url = "https://files.pythonhosted.org/packages/2e/00/96ae1c9dcb3bc316ef683f2febd8c97dde9f254dc36c3afc65c7645f734c/websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b", size = 121326, upload-time = "2023-10-21T14:20:04.956Z" }, + { url = "https://files.pythonhosted.org/packages/af/f1/bba1e64430685dd456c1a1fd6b0c791ae33104967b928aefeff261761e8d/websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb", size = 131807, upload-time = "2023-10-21T14:20:06.153Z" }, + { url = "https://files.pythonhosted.org/packages/62/3b/98ee269712f37d892b93852ce07b3e6d7653160ca4c0d4f8c8663f8021f8/websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92", size = 130751, upload-time = "2023-10-21T14:20:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/f1/00/d6f01ca2b191f8b0808e4132ccd2e7691f0453cbd7d0f72330eb97453c3a/websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed", size = 131176, upload-time = "2023-10-21T14:20:09.212Z" }, + { url = "https://files.pythonhosted.org/packages/af/9c/703ff3cd8109dcdee6152bae055d852ebaa7750117760ded697ab836cbcf/websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5", size = 136246, upload-time = "2023-10-21T14:20:10.423Z" }, + { url = "https://files.pythonhosted.org/packages/0b/a5/1a38fb85a456b9dc874ec984f3ff34f6550eafd17a3da28753cd3c1628e8/websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2", size = 135466, upload-time = "2023-10-21T14:20:11.826Z" }, + { url = "https://files.pythonhosted.org/packages/3c/98/1261f289dff7e65a38d59d2f591de6ed0a2580b729aebddec033c4d10881/websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113", size = 136083, upload-time = "2023-10-21T14:20:13.451Z" }, + { url = "https://files.pythonhosted.org/packages/a9/1c/f68769fba63ccb9c13fe0a25b616bd5aebeef1c7ddebc2ccc32462fb784d/websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d", size = 124460, upload-time = "2023-10-21T14:20:14.719Z" }, + { url = "https://files.pythonhosted.org/packages/20/52/8915f51f9aaef4e4361c89dd6cf69f72a0159f14e0d25026c81b6ad22525/websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f", size = 124985, upload-time = "2023-10-21T14:20:15.817Z" }, + { url = "https://files.pythonhosted.org/packages/79/4d/9cc401e7b07e80532ebc8c8e993f42541534da9e9249c59ee0139dcb0352/websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e", size = 118370, upload-time = "2023-10-21T14:21:10.075Z" }, +] diff --git a/frontend/.env.example b/frontend/.env.example index d15afa9f..cd49a500 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1,4 +1,7 @@ -# When accessing the frontend from another device, the browser will call this URL. -# Use your machine IP (not 127.0.0.1). - -NEXT_PUBLIC_API_URL=http://:8000 +NEXT_PUBLIC_API_URL=http://localhost:8000 +NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY +CLERK_SECRET_KEY=YOUR_SECRET_KEY +NEXT_PUBLIC_CLERK_SIGN_IN_FORCE_REDIRECT_URL=/boards +NEXT_PUBLIC_CLERK_SIGN_UP_FORCE_REDIRECT_URL=/boards +NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/boards +NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/boards diff --git a/frontend/.gitignore b/frontend/.gitignore index 5ef6a520..8430c68c 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1,41 +1,9 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +node_modules/ +.next/ +.env.local +.env +out/ +dist/ -# dependencies -/node_modules -/.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# env files (can opt-in for committing if needed) -.env* - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts +# clerk configuration (can include secrets) +/.clerk/ diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts new file mode 100644 index 00000000..c4b7818f --- /dev/null +++ b/frontend/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +import "./.next/dev/types/routes.d.ts"; + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/frontend/next.config.ts b/frontend/next.config.ts index e9ffa308..b6bbeefb 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -1,7 +1,11 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + allowedDevOrigins: [ + "http://localhost:3000", + "http://127.0.0.1:3000", + "http://192.168.1.101:3000", + ], }; export default nextConfig; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 80a011fe..7aaca656 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,7 +8,13 @@ "name": "frontend", "version": "0.1.0", "dependencies": { + "@clerk/nextjs": "^6.37.1", + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-tabs": "^1.1.1", + "@radix-ui/react-tooltip": "^1.1.2", "@tanstack/react-query": "^5.90.20", + "@tanstack/react-table": "^8.21.3", "next": "16.1.6", "react": "19.2.3", "react-dom": "19.2.3" @@ -285,6 +291,108 @@ "node": ">=6.9.0" } }, + "node_modules/@clerk/backend": { + "version": "2.29.7", + "resolved": "https://registry.npmjs.org/@clerk/backend/-/backend-2.29.7.tgz", + "integrity": "sha512-OSfFQ85L0FV2wSzqlr0hRvluIu3Z5ClgLiBE6Qx7XjSGyJoqEvP5OP4fl5Nt5icgGvH0EwA1dljPGyQpaqbQEw==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "^3.44.0", + "@clerk/types": "^4.101.14", + "standardwebhooks": "^1.0.0", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=18.17.0" + } + }, + "node_modules/@clerk/clerk-react": { + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@clerk/clerk-react/-/clerk-react-5.60.0.tgz", + "integrity": "sha512-P88FncsJpq/3WZJhhlj+md8mYb35BIXpr462C/figwsBGHsinr8VuBQUMcMZZ/6M34C8ABfLTPa6PHVp6+3D5Q==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "^3.44.0", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0", + "react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0" + } + }, + "node_modules/@clerk/nextjs": { + "version": "6.37.1", + "resolved": "https://registry.npmjs.org/@clerk/nextjs/-/nextjs-6.37.1.tgz", + "integrity": "sha512-SqDG/l+HfnGJlOplXc3Jga49/ObTYth+P1RP6dY+uy3BxvDc4iOuxKt7Qh39yMmUf1S0Kuu0nZBgb0lz6uxVvw==", + "license": "MIT", + "dependencies": { + "@clerk/backend": "^2.29.7", + "@clerk/clerk-react": "^5.60.0", + "@clerk/shared": "^3.44.0", + "@clerk/types": "^4.101.14", + "server-only": "0.0.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "next": "^13.5.7 || ^14.2.25 || ^15.2.3 || ^16", + "react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0", + "react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0" + } + }, + "node_modules/@clerk/shared": { + "version": "3.44.0", + "resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-3.44.0.tgz", + "integrity": "sha512-kH+chNeZwqml3IDpWLgebWECfOZifyUQO4OISd/96w1EuCY1Bzw6cBq/ZbpsoO8jyG8/6bGr/MGXLhDzTrpPfA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "csstype": "3.1.3", + "dequal": "2.0.3", + "glob-to-regexp": "0.4.1", + "js-cookie": "3.0.5", + "std-env": "^3.9.0", + "swr": "2.3.4" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0", + "react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@clerk/shared/node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/@clerk/types": { + "version": "4.101.14", + "resolved": "https://registry.npmjs.org/@clerk/types/-/types-4.101.14.tgz", + "integrity": "sha512-jl7DywmeaZx1IntgEXcjDZq2uyk+X/1yAZOjxOboeGTS0rNTiQNhv7xK8tFVjexsUAFrYlwC1AxhFuJiMDQjow==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "^3.44.0" + }, + "engines": { + "node": ">=18.17.0" + } + }, "node_modules/@emnapi/core": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", @@ -904,6 +1012,44 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.4", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz", + "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.5" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, "node_modules/@gerrit0/mini-shiki": { "version": "3.22.0", "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.22.0.tgz", @@ -1848,6 +1994,657 @@ "remeda": "^2.32.0" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -2058,6 +2855,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -2093,6 +2896,39 @@ "react": "^18 || ^19" } }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -2149,7 +2985,7 @@ "version": "19.2.10", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz", "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -2159,7 +2995,7 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" @@ -2863,6 +3699,18 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/aria-query": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", @@ -3467,7 +4315,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -3592,6 +4440,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -3602,6 +4459,12 @@ "node": ">=8" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -4429,6 +5292,12 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense" + }, "node_modules/fast-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", @@ -4682,6 +5551,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -4757,6 +5635,12 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -5563,6 +6447,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6847,6 +7740,75 @@ "dev": true, "license": "MIT" }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -7081,6 +8043,12 @@ "semver": "bin/semver.js" } }, + "node_modules/server-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", + "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -7329,6 +8297,22 @@ "dev": true, "license": "MIT" }, + "node_modules/standardwebhooks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", + "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -7587,6 +8571,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.4.tgz", + "integrity": "sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/tailwind-merge": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", @@ -8216,6 +9213,58 @@ "punycode": "^2.1.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 0bd957ad..779facef 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,13 @@ "api:gen": "orval --config ./orval.config.ts" }, "dependencies": { + "@clerk/nextjs": "^6.37.1", + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-tabs": "^1.1.1", + "@radix-ui/react-tooltip": "^1.1.2", "@tanstack/react-query": "^5.90.20", + "@tanstack/react-table": "^8.21.3", "next": "16.1.6", "react": "19.2.3", "react-dom": "19.2.3" diff --git a/frontend/public/file.svg b/frontend/public/file.svg deleted file mode 100644 index 16fe3d3a..00000000 --- a/frontend/public/file.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/public/globe.svg b/frontend/public/globe.svg deleted file mode 100644 index c7215fe0..00000000 --- a/frontend/public/globe.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/public/next.svg b/frontend/public/next.svg deleted file mode 100644 index 5bb00d40..00000000 --- a/frontend/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/public/vercel.svg b/frontend/public/vercel.svg deleted file mode 100644 index 52151572..00000000 --- a/frontend/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/public/window.svg b/frontend/public/window.svg deleted file mode 100644 index d05e7a1b..00000000 --- a/frontend/public/window.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/src/api/generated/activities/activities.ts b/frontend/src/api/generated/activities/activities.ts deleted file mode 100644 index f651dcf8..00000000 --- a/frontend/src/api/generated/activities/activities.ts +++ /dev/null @@ -1,237 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ -import { useQuery } from "@tanstack/react-query"; -import type { - DataTag, - DefinedInitialDataOptions, - DefinedUseQueryResult, - QueryClient, - QueryFunction, - QueryKey, - UndefinedInitialDataOptions, - UseQueryOptions, - UseQueryResult, -} from "@tanstack/react-query"; - -import type { - HTTPValidationError, - ListActivitiesActivitiesGetParams, -} from ".././model"; - -import { customFetch } from "../../mutator"; - -type SecondParameter unknown> = Parameters[1]; - -/** - * @summary List Activities - */ -export type listActivitiesActivitiesGetResponse200 = { - data: unknown; - status: 200; -}; - -export type listActivitiesActivitiesGetResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type listActivitiesActivitiesGetResponseSuccess = - listActivitiesActivitiesGetResponse200 & { - headers: Headers; - }; -export type listActivitiesActivitiesGetResponseError = - listActivitiesActivitiesGetResponse422 & { - headers: Headers; - }; - -export type listActivitiesActivitiesGetResponse = - | listActivitiesActivitiesGetResponseSuccess - | listActivitiesActivitiesGetResponseError; - -export const getListActivitiesActivitiesGetUrl = ( - params?: ListActivitiesActivitiesGetParams, -) => { - const normalizedParams = new URLSearchParams(); - - Object.entries(params || {}).forEach(([key, value]) => { - if (value !== undefined) { - normalizedParams.append(key, value === null ? "null" : value.toString()); - } - }); - - const stringifiedParams = normalizedParams.toString(); - - return stringifiedParams.length > 0 - ? `/activities?${stringifiedParams}` - : `/activities`; -}; - -export const listActivitiesActivitiesGet = async ( - params?: ListActivitiesActivitiesGetParams, - options?: RequestInit, -): Promise => { - return customFetch( - getListActivitiesActivitiesGetUrl(params), - { - ...options, - method: "GET", - }, - ); -}; - -export const getListActivitiesActivitiesGetQueryKey = ( - params?: ListActivitiesActivitiesGetParams, -) => { - return [`/activities`, ...(params ? [params] : [])] as const; -}; - -export const getListActivitiesActivitiesGetQueryOptions = < - TData = Awaited>, - TError = HTTPValidationError, ->( - params?: ListActivitiesActivitiesGetParams, - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, -) => { - const { query: queryOptions, request: requestOptions } = options ?? {}; - - const queryKey = - queryOptions?.queryKey ?? getListActivitiesActivitiesGetQueryKey(params); - - const queryFn: QueryFunction< - Awaited> - > = ({ signal }) => - listActivitiesActivitiesGet(params, { signal, ...requestOptions }); - - return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< - Awaited>, - TError, - TData - > & { queryKey: DataTag }; -}; - -export type ListActivitiesActivitiesGetQueryResult = NonNullable< - Awaited> ->; -export type ListActivitiesActivitiesGetQueryError = HTTPValidationError; - -export function useListActivitiesActivitiesGet< - TData = Awaited>, - TError = HTTPValidationError, ->( - params: undefined | ListActivitiesActivitiesGetParams, - options: { - query: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - DefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): DefinedUseQueryResult & { - queryKey: DataTag; -}; -export function useListActivitiesActivitiesGet< - TData = Awaited>, - TError = HTTPValidationError, ->( - params?: ListActivitiesActivitiesGetParams, - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - UndefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -export function useListActivitiesActivitiesGet< - TData = Awaited>, - TError = HTTPValidationError, ->( - params?: ListActivitiesActivitiesGetParams, - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -/** - * @summary List Activities - */ - -export function useListActivitiesActivitiesGet< - TData = Awaited>, - TError = HTTPValidationError, ->( - params?: ListActivitiesActivitiesGetParams, - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -} { - const queryOptions = getListActivitiesActivitiesGetQueryOptions( - params, - options, - ); - - const query = useQuery(queryOptions, queryClient) as UseQueryResult< - TData, - TError - > & { queryKey: DataTag }; - - return { ...query, queryKey: queryOptions.queryKey }; -} diff --git a/frontend/src/api/generated/default/default.ts b/frontend/src/api/generated/default/default.ts deleted file mode 100644 index af937169..00000000 --- a/frontend/src/api/generated/default/default.ts +++ /dev/null @@ -1,183 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ -import { useQuery } from "@tanstack/react-query"; -import type { - DataTag, - DefinedInitialDataOptions, - DefinedUseQueryResult, - QueryClient, - QueryFunction, - QueryKey, - UndefinedInitialDataOptions, - UseQueryOptions, - UseQueryResult, -} from "@tanstack/react-query"; - -import { customFetch } from "../../mutator"; - -type SecondParameter unknown> = Parameters[1]; - -/** - * @summary Health - */ -export type healthHealthGetResponse200 = { - data: unknown; - status: 200; -}; - -export type healthHealthGetResponseSuccess = healthHealthGetResponse200 & { - headers: Headers; -}; -export type healthHealthGetResponse = healthHealthGetResponseSuccess; - -export const getHealthHealthGetUrl = () => { - return `/health`; -}; - -export const healthHealthGet = async ( - options?: RequestInit, -): Promise => { - return customFetch(getHealthHealthGetUrl(), { - ...options, - method: "GET", - }); -}; - -export const getHealthHealthGetQueryKey = () => { - return [`/health`] as const; -}; - -export const getHealthHealthGetQueryOptions = < - TData = Awaited>, - TError = unknown, ->(options?: { - query?: Partial< - UseQueryOptions>, TError, TData> - >; - request?: SecondParameter; -}) => { - const { query: queryOptions, request: requestOptions } = options ?? {}; - - const queryKey = queryOptions?.queryKey ?? getHealthHealthGetQueryKey(); - - const queryFn: QueryFunction>> = ({ - signal, - }) => healthHealthGet({ signal, ...requestOptions }); - - return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< - Awaited>, - TError, - TData - > & { queryKey: DataTag }; -}; - -export type HealthHealthGetQueryResult = NonNullable< - Awaited> ->; -export type HealthHealthGetQueryError = unknown; - -export function useHealthHealthGet< - TData = Awaited>, - TError = unknown, ->( - options: { - query: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - DefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): DefinedUseQueryResult & { - queryKey: DataTag; -}; -export function useHealthHealthGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - UndefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -export function useHealthHealthGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -/** - * @summary Health - */ - -export function useHealthHealthGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -} { - const queryOptions = getHealthHealthGetQueryOptions(options); - - const query = useQuery(queryOptions, queryClient) as UseQueryResult< - TData, - TError - > & { queryKey: DataTag }; - - return { ...query, queryKey: queryOptions.queryKey }; -} diff --git a/frontend/src/api/generated/hr/hr.ts b/frontend/src/api/generated/hr/hr.ts deleted file mode 100644 index c19ad6c6..00000000 --- a/frontend/src/api/generated/hr/hr.ts +++ /dev/null @@ -1,1184 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ -import { useMutation, useQuery } from "@tanstack/react-query"; -import type { - DataTag, - DefinedInitialDataOptions, - DefinedUseQueryResult, - MutationFunction, - QueryClient, - QueryFunction, - QueryKey, - UndefinedInitialDataOptions, - UseMutationOptions, - UseMutationResult, - UseQueryOptions, - UseQueryResult, -} from "@tanstack/react-query"; - -import type { - AgentOnboarding, - AgentOnboardingCreate, - AgentOnboardingUpdate, - EmploymentAction, - EmploymentActionCreate, - HTTPValidationError, - HeadcountRequest, - HeadcountRequestCreate, - HeadcountRequestUpdate, -} from ".././model"; - -import { customFetch } from "../../mutator"; - -type SecondParameter unknown> = Parameters[1]; - -/** - * @summary List Headcount Requests - */ -export type listHeadcountRequestsHrHeadcountGetResponse200 = { - data: HeadcountRequest[]; - status: 200; -}; - -export type listHeadcountRequestsHrHeadcountGetResponseSuccess = - listHeadcountRequestsHrHeadcountGetResponse200 & { - headers: Headers; - }; -export type listHeadcountRequestsHrHeadcountGetResponse = - listHeadcountRequestsHrHeadcountGetResponseSuccess; - -export const getListHeadcountRequestsHrHeadcountGetUrl = () => { - return `/hr/headcount`; -}; - -export const listHeadcountRequestsHrHeadcountGet = async ( - options?: RequestInit, -): Promise => { - return customFetch( - getListHeadcountRequestsHrHeadcountGetUrl(), - { - ...options, - method: "GET", - }, - ); -}; - -export const getListHeadcountRequestsHrHeadcountGetQueryKey = () => { - return [`/hr/headcount`] as const; -}; - -export const getListHeadcountRequestsHrHeadcountGetQueryOptions = < - TData = Awaited>, - TError = unknown, ->(options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; -}) => { - const { query: queryOptions, request: requestOptions } = options ?? {}; - - const queryKey = - queryOptions?.queryKey ?? getListHeadcountRequestsHrHeadcountGetQueryKey(); - - const queryFn: QueryFunction< - Awaited> - > = ({ signal }) => - listHeadcountRequestsHrHeadcountGet({ signal, ...requestOptions }); - - return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< - Awaited>, - TError, - TData - > & { queryKey: DataTag }; -}; - -export type ListHeadcountRequestsHrHeadcountGetQueryResult = NonNullable< - Awaited> ->; -export type ListHeadcountRequestsHrHeadcountGetQueryError = unknown; - -export function useListHeadcountRequestsHrHeadcountGet< - TData = Awaited>, - TError = unknown, ->( - options: { - query: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - DefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): DefinedUseQueryResult & { - queryKey: DataTag; -}; -export function useListHeadcountRequestsHrHeadcountGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - UndefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -export function useListHeadcountRequestsHrHeadcountGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -/** - * @summary List Headcount Requests - */ - -export function useListHeadcountRequestsHrHeadcountGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -} { - const queryOptions = - getListHeadcountRequestsHrHeadcountGetQueryOptions(options); - - const query = useQuery(queryOptions, queryClient) as UseQueryResult< - TData, - TError - > & { queryKey: DataTag }; - - return { ...query, queryKey: queryOptions.queryKey }; -} - -/** - * @summary Create Headcount Request - */ -export type createHeadcountRequestHrHeadcountPostResponse200 = { - data: HeadcountRequest; - status: 200; -}; - -export type createHeadcountRequestHrHeadcountPostResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type createHeadcountRequestHrHeadcountPostResponseSuccess = - createHeadcountRequestHrHeadcountPostResponse200 & { - headers: Headers; - }; -export type createHeadcountRequestHrHeadcountPostResponseError = - createHeadcountRequestHrHeadcountPostResponse422 & { - headers: Headers; - }; - -export type createHeadcountRequestHrHeadcountPostResponse = - | createHeadcountRequestHrHeadcountPostResponseSuccess - | createHeadcountRequestHrHeadcountPostResponseError; - -export const getCreateHeadcountRequestHrHeadcountPostUrl = () => { - return `/hr/headcount`; -}; - -export const createHeadcountRequestHrHeadcountPost = async ( - headcountRequestCreate: HeadcountRequestCreate, - options?: RequestInit, -): Promise => { - return customFetch( - getCreateHeadcountRequestHrHeadcountPostUrl(), - { - ...options, - method: "POST", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(headcountRequestCreate), - }, - ); -}; - -export const getCreateHeadcountRequestHrHeadcountPostMutationOptions = < - TError = HTTPValidationError, - TContext = unknown, ->(options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { data: HeadcountRequestCreate }, - TContext - >; - request?: SecondParameter; -}): UseMutationOptions< - Awaited>, - TError, - { data: HeadcountRequestCreate }, - TContext -> => { - const mutationKey = ["createHeadcountRequestHrHeadcountPost"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited>, - { data: HeadcountRequestCreate } - > = (props) => { - const { data } = props ?? {}; - - return createHeadcountRequestHrHeadcountPost(data, requestOptions); - }; - - return { mutationFn, ...mutationOptions }; -}; - -export type CreateHeadcountRequestHrHeadcountPostMutationResult = NonNullable< - Awaited> ->; -export type CreateHeadcountRequestHrHeadcountPostMutationBody = - HeadcountRequestCreate; -export type CreateHeadcountRequestHrHeadcountPostMutationError = - HTTPValidationError; - -/** - * @summary Create Headcount Request - */ -export const useCreateHeadcountRequestHrHeadcountPost = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { data: HeadcountRequestCreate }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { data: HeadcountRequestCreate }, - TContext -> => { - return useMutation( - getCreateHeadcountRequestHrHeadcountPostMutationOptions(options), - queryClient, - ); -}; -/** - * @summary Update Headcount Request - */ -export type updateHeadcountRequestHrHeadcountRequestIdPatchResponse200 = { - data: HeadcountRequest; - status: 200; -}; - -export type updateHeadcountRequestHrHeadcountRequestIdPatchResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type updateHeadcountRequestHrHeadcountRequestIdPatchResponseSuccess = - updateHeadcountRequestHrHeadcountRequestIdPatchResponse200 & { - headers: Headers; - }; -export type updateHeadcountRequestHrHeadcountRequestIdPatchResponseError = - updateHeadcountRequestHrHeadcountRequestIdPatchResponse422 & { - headers: Headers; - }; - -export type updateHeadcountRequestHrHeadcountRequestIdPatchResponse = - | updateHeadcountRequestHrHeadcountRequestIdPatchResponseSuccess - | updateHeadcountRequestHrHeadcountRequestIdPatchResponseError; - -export const getUpdateHeadcountRequestHrHeadcountRequestIdPatchUrl = ( - requestId: number, -) => { - return `/hr/headcount/${requestId}`; -}; - -export const updateHeadcountRequestHrHeadcountRequestIdPatch = async ( - requestId: number, - headcountRequestUpdate: HeadcountRequestUpdate, - options?: RequestInit, -): Promise => { - return customFetch( - getUpdateHeadcountRequestHrHeadcountRequestIdPatchUrl(requestId), - { - ...options, - method: "PATCH", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(headcountRequestUpdate), - }, - ); -}; - -export const getUpdateHeadcountRequestHrHeadcountRequestIdPatchMutationOptions = - (options?: { - mutation?: UseMutationOptions< - Awaited< - ReturnType - >, - TError, - { requestId: number; data: HeadcountRequestUpdate }, - TContext - >; - request?: SecondParameter; - }): UseMutationOptions< - Awaited>, - TError, - { requestId: number; data: HeadcountRequestUpdate }, - TContext - > => { - const mutationKey = ["updateHeadcountRequestHrHeadcountRequestIdPatch"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited< - ReturnType - >, - { requestId: number; data: HeadcountRequestUpdate } - > = (props) => { - const { requestId, data } = props ?? {}; - - return updateHeadcountRequestHrHeadcountRequestIdPatch( - requestId, - data, - requestOptions, - ); - }; - - return { mutationFn, ...mutationOptions }; - }; - -export type UpdateHeadcountRequestHrHeadcountRequestIdPatchMutationResult = - NonNullable< - Awaited> - >; -export type UpdateHeadcountRequestHrHeadcountRequestIdPatchMutationBody = - HeadcountRequestUpdate; -export type UpdateHeadcountRequestHrHeadcountRequestIdPatchMutationError = - HTTPValidationError; - -/** - * @summary Update Headcount Request - */ -export const useUpdateHeadcountRequestHrHeadcountRequestIdPatch = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited< - ReturnType - >, - TError, - { requestId: number; data: HeadcountRequestUpdate }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { requestId: number; data: HeadcountRequestUpdate }, - TContext -> => { - return useMutation( - getUpdateHeadcountRequestHrHeadcountRequestIdPatchMutationOptions(options), - queryClient, - ); -}; -/** - * @summary List Employment Actions - */ -export type listEmploymentActionsHrActionsGetResponse200 = { - data: EmploymentAction[]; - status: 200; -}; - -export type listEmploymentActionsHrActionsGetResponseSuccess = - listEmploymentActionsHrActionsGetResponse200 & { - headers: Headers; - }; -export type listEmploymentActionsHrActionsGetResponse = - listEmploymentActionsHrActionsGetResponseSuccess; - -export const getListEmploymentActionsHrActionsGetUrl = () => { - return `/hr/actions`; -}; - -export const listEmploymentActionsHrActionsGet = async ( - options?: RequestInit, -): Promise => { - return customFetch( - getListEmploymentActionsHrActionsGetUrl(), - { - ...options, - method: "GET", - }, - ); -}; - -export const getListEmploymentActionsHrActionsGetQueryKey = () => { - return [`/hr/actions`] as const; -}; - -export const getListEmploymentActionsHrActionsGetQueryOptions = < - TData = Awaited>, - TError = unknown, ->(options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; -}) => { - const { query: queryOptions, request: requestOptions } = options ?? {}; - - const queryKey = - queryOptions?.queryKey ?? getListEmploymentActionsHrActionsGetQueryKey(); - - const queryFn: QueryFunction< - Awaited> - > = ({ signal }) => - listEmploymentActionsHrActionsGet({ signal, ...requestOptions }); - - return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< - Awaited>, - TError, - TData - > & { queryKey: DataTag }; -}; - -export type ListEmploymentActionsHrActionsGetQueryResult = NonNullable< - Awaited> ->; -export type ListEmploymentActionsHrActionsGetQueryError = unknown; - -export function useListEmploymentActionsHrActionsGet< - TData = Awaited>, - TError = unknown, ->( - options: { - query: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - DefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): DefinedUseQueryResult & { - queryKey: DataTag; -}; -export function useListEmploymentActionsHrActionsGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - UndefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -export function useListEmploymentActionsHrActionsGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -/** - * @summary List Employment Actions - */ - -export function useListEmploymentActionsHrActionsGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -} { - const queryOptions = - getListEmploymentActionsHrActionsGetQueryOptions(options); - - const query = useQuery(queryOptions, queryClient) as UseQueryResult< - TData, - TError - > & { queryKey: DataTag }; - - return { ...query, queryKey: queryOptions.queryKey }; -} - -/** - * @summary Create Employment Action - */ -export type createEmploymentActionHrActionsPostResponse200 = { - data: EmploymentAction; - status: 200; -}; - -export type createEmploymentActionHrActionsPostResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type createEmploymentActionHrActionsPostResponseSuccess = - createEmploymentActionHrActionsPostResponse200 & { - headers: Headers; - }; -export type createEmploymentActionHrActionsPostResponseError = - createEmploymentActionHrActionsPostResponse422 & { - headers: Headers; - }; - -export type createEmploymentActionHrActionsPostResponse = - | createEmploymentActionHrActionsPostResponseSuccess - | createEmploymentActionHrActionsPostResponseError; - -export const getCreateEmploymentActionHrActionsPostUrl = () => { - return `/hr/actions`; -}; - -export const createEmploymentActionHrActionsPost = async ( - employmentActionCreate: EmploymentActionCreate, - options?: RequestInit, -): Promise => { - return customFetch( - getCreateEmploymentActionHrActionsPostUrl(), - { - ...options, - method: "POST", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(employmentActionCreate), - }, - ); -}; - -export const getCreateEmploymentActionHrActionsPostMutationOptions = < - TError = HTTPValidationError, - TContext = unknown, ->(options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { data: EmploymentActionCreate }, - TContext - >; - request?: SecondParameter; -}): UseMutationOptions< - Awaited>, - TError, - { data: EmploymentActionCreate }, - TContext -> => { - const mutationKey = ["createEmploymentActionHrActionsPost"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited>, - { data: EmploymentActionCreate } - > = (props) => { - const { data } = props ?? {}; - - return createEmploymentActionHrActionsPost(data, requestOptions); - }; - - return { mutationFn, ...mutationOptions }; -}; - -export type CreateEmploymentActionHrActionsPostMutationResult = NonNullable< - Awaited> ->; -export type CreateEmploymentActionHrActionsPostMutationBody = - EmploymentActionCreate; -export type CreateEmploymentActionHrActionsPostMutationError = - HTTPValidationError; - -/** - * @summary Create Employment Action - */ -export const useCreateEmploymentActionHrActionsPost = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { data: EmploymentActionCreate }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { data: EmploymentActionCreate }, - TContext -> => { - return useMutation( - getCreateEmploymentActionHrActionsPostMutationOptions(options), - queryClient, - ); -}; -/** - * @summary List Agent Onboarding - */ -export type listAgentOnboardingHrOnboardingGetResponse200 = { - data: AgentOnboarding[]; - status: 200; -}; - -export type listAgentOnboardingHrOnboardingGetResponseSuccess = - listAgentOnboardingHrOnboardingGetResponse200 & { - headers: Headers; - }; -export type listAgentOnboardingHrOnboardingGetResponse = - listAgentOnboardingHrOnboardingGetResponseSuccess; - -export const getListAgentOnboardingHrOnboardingGetUrl = () => { - return `/hr/onboarding`; -}; - -export const listAgentOnboardingHrOnboardingGet = async ( - options?: RequestInit, -): Promise => { - return customFetch( - getListAgentOnboardingHrOnboardingGetUrl(), - { - ...options, - method: "GET", - }, - ); -}; - -export const getListAgentOnboardingHrOnboardingGetQueryKey = () => { - return [`/hr/onboarding`] as const; -}; - -export const getListAgentOnboardingHrOnboardingGetQueryOptions = < - TData = Awaited>, - TError = unknown, ->(options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; -}) => { - const { query: queryOptions, request: requestOptions } = options ?? {}; - - const queryKey = - queryOptions?.queryKey ?? getListAgentOnboardingHrOnboardingGetQueryKey(); - - const queryFn: QueryFunction< - Awaited> - > = ({ signal }) => - listAgentOnboardingHrOnboardingGet({ signal, ...requestOptions }); - - return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< - Awaited>, - TError, - TData - > & { queryKey: DataTag }; -}; - -export type ListAgentOnboardingHrOnboardingGetQueryResult = NonNullable< - Awaited> ->; -export type ListAgentOnboardingHrOnboardingGetQueryError = unknown; - -export function useListAgentOnboardingHrOnboardingGet< - TData = Awaited>, - TError = unknown, ->( - options: { - query: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - DefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): DefinedUseQueryResult & { - queryKey: DataTag; -}; -export function useListAgentOnboardingHrOnboardingGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - UndefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -export function useListAgentOnboardingHrOnboardingGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -/** - * @summary List Agent Onboarding - */ - -export function useListAgentOnboardingHrOnboardingGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -} { - const queryOptions = - getListAgentOnboardingHrOnboardingGetQueryOptions(options); - - const query = useQuery(queryOptions, queryClient) as UseQueryResult< - TData, - TError - > & { queryKey: DataTag }; - - return { ...query, queryKey: queryOptions.queryKey }; -} - -/** - * @summary Create Agent Onboarding - */ -export type createAgentOnboardingHrOnboardingPostResponse200 = { - data: AgentOnboarding; - status: 200; -}; - -export type createAgentOnboardingHrOnboardingPostResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type createAgentOnboardingHrOnboardingPostResponseSuccess = - createAgentOnboardingHrOnboardingPostResponse200 & { - headers: Headers; - }; -export type createAgentOnboardingHrOnboardingPostResponseError = - createAgentOnboardingHrOnboardingPostResponse422 & { - headers: Headers; - }; - -export type createAgentOnboardingHrOnboardingPostResponse = - | createAgentOnboardingHrOnboardingPostResponseSuccess - | createAgentOnboardingHrOnboardingPostResponseError; - -export const getCreateAgentOnboardingHrOnboardingPostUrl = () => { - return `/hr/onboarding`; -}; - -export const createAgentOnboardingHrOnboardingPost = async ( - agentOnboardingCreate: AgentOnboardingCreate, - options?: RequestInit, -): Promise => { - return customFetch( - getCreateAgentOnboardingHrOnboardingPostUrl(), - { - ...options, - method: "POST", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(agentOnboardingCreate), - }, - ); -}; - -export const getCreateAgentOnboardingHrOnboardingPostMutationOptions = < - TError = HTTPValidationError, - TContext = unknown, ->(options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { data: AgentOnboardingCreate }, - TContext - >; - request?: SecondParameter; -}): UseMutationOptions< - Awaited>, - TError, - { data: AgentOnboardingCreate }, - TContext -> => { - const mutationKey = ["createAgentOnboardingHrOnboardingPost"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited>, - { data: AgentOnboardingCreate } - > = (props) => { - const { data } = props ?? {}; - - return createAgentOnboardingHrOnboardingPost(data, requestOptions); - }; - - return { mutationFn, ...mutationOptions }; -}; - -export type CreateAgentOnboardingHrOnboardingPostMutationResult = NonNullable< - Awaited> ->; -export type CreateAgentOnboardingHrOnboardingPostMutationBody = - AgentOnboardingCreate; -export type CreateAgentOnboardingHrOnboardingPostMutationError = - HTTPValidationError; - -/** - * @summary Create Agent Onboarding - */ -export const useCreateAgentOnboardingHrOnboardingPost = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { data: AgentOnboardingCreate }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { data: AgentOnboardingCreate }, - TContext -> => { - return useMutation( - getCreateAgentOnboardingHrOnboardingPostMutationOptions(options), - queryClient, - ); -}; -/** - * @summary Update Agent Onboarding - */ -export type updateAgentOnboardingHrOnboardingOnboardingIdPatchResponse200 = { - data: AgentOnboarding; - status: 200; -}; - -export type updateAgentOnboardingHrOnboardingOnboardingIdPatchResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type updateAgentOnboardingHrOnboardingOnboardingIdPatchResponseSuccess = - updateAgentOnboardingHrOnboardingOnboardingIdPatchResponse200 & { - headers: Headers; - }; -export type updateAgentOnboardingHrOnboardingOnboardingIdPatchResponseError = - updateAgentOnboardingHrOnboardingOnboardingIdPatchResponse422 & { - headers: Headers; - }; - -export type updateAgentOnboardingHrOnboardingOnboardingIdPatchResponse = - | updateAgentOnboardingHrOnboardingOnboardingIdPatchResponseSuccess - | updateAgentOnboardingHrOnboardingOnboardingIdPatchResponseError; - -export const getUpdateAgentOnboardingHrOnboardingOnboardingIdPatchUrl = ( - onboardingId: number, -) => { - return `/hr/onboarding/${onboardingId}`; -}; - -export const updateAgentOnboardingHrOnboardingOnboardingIdPatch = async ( - onboardingId: number, - agentOnboardingUpdate: AgentOnboardingUpdate, - options?: RequestInit, -): Promise => { - return customFetch( - getUpdateAgentOnboardingHrOnboardingOnboardingIdPatchUrl(onboardingId), - { - ...options, - method: "PATCH", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(agentOnboardingUpdate), - }, - ); -}; - -export const getUpdateAgentOnboardingHrOnboardingOnboardingIdPatchMutationOptions = - (options?: { - mutation?: UseMutationOptions< - Awaited< - ReturnType - >, - TError, - { onboardingId: number; data: AgentOnboardingUpdate }, - TContext - >; - request?: SecondParameter; - }): UseMutationOptions< - Awaited< - ReturnType - >, - TError, - { onboardingId: number; data: AgentOnboardingUpdate }, - TContext - > => { - const mutationKey = ["updateAgentOnboardingHrOnboardingOnboardingIdPatch"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited< - ReturnType - >, - { onboardingId: number; data: AgentOnboardingUpdate } - > = (props) => { - const { onboardingId, data } = props ?? {}; - - return updateAgentOnboardingHrOnboardingOnboardingIdPatch( - onboardingId, - data, - requestOptions, - ); - }; - - return { mutationFn, ...mutationOptions }; - }; - -export type UpdateAgentOnboardingHrOnboardingOnboardingIdPatchMutationResult = - NonNullable< - Awaited< - ReturnType - > - >; -export type UpdateAgentOnboardingHrOnboardingOnboardingIdPatchMutationBody = - AgentOnboardingUpdate; -export type UpdateAgentOnboardingHrOnboardingOnboardingIdPatchMutationError = - HTTPValidationError; - -/** - * @summary Update Agent Onboarding - */ -export const useUpdateAgentOnboardingHrOnboardingOnboardingIdPatch = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited< - ReturnType - >, - TError, - { onboardingId: number; data: AgentOnboardingUpdate }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited< - ReturnType - >, - TError, - { onboardingId: number; data: AgentOnboardingUpdate }, - TContext -> => { - return useMutation( - getUpdateAgentOnboardingHrOnboardingOnboardingIdPatchMutationOptions( - options, - ), - queryClient, - ); -}; diff --git a/frontend/src/api/generated/model/agentOnboarding.ts b/frontend/src/api/generated/model/agentOnboarding.ts deleted file mode 100644 index fb5e2b03..00000000 --- a/frontend/src/api/generated/model/agentOnboarding.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface AgentOnboarding { - id?: number | null; - agent_name: string; - role_title: string; - prompt: string; - cron_interval_ms?: number | null; - tools_json?: string | null; - owner_hr_id?: number | null; - status?: string; - spawned_agent_id?: string | null; - session_key?: string | null; - notes?: string | null; - created_at?: string; - updated_at?: string; -} diff --git a/frontend/src/api/generated/model/agentOnboardingCreate.ts b/frontend/src/api/generated/model/agentOnboardingCreate.ts deleted file mode 100644 index 2df02fee..00000000 --- a/frontend/src/api/generated/model/agentOnboardingCreate.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface AgentOnboardingCreate { - agent_name: string; - role_title: string; - prompt: string; - cron_interval_ms?: number | null; - tools_json?: string | null; - owner_hr_id?: number | null; - status?: string; - spawned_agent_id?: string | null; - session_key?: string | null; - notes?: string | null; -} diff --git a/frontend/src/api/generated/model/agentOnboardingUpdate.ts b/frontend/src/api/generated/model/agentOnboardingUpdate.ts deleted file mode 100644 index f64b351c..00000000 --- a/frontend/src/api/generated/model/agentOnboardingUpdate.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface AgentOnboardingUpdate { - agent_name?: string | null; - role_title?: string | null; - prompt?: string | null; - cron_interval_ms?: number | null; - tools_json?: string | null; - owner_hr_id?: number | null; - status?: string | null; - spawned_agent_id?: string | null; - session_key?: string | null; - notes?: string | null; -} diff --git a/frontend/src/api/generated/model/department.ts b/frontend/src/api/generated/model/department.ts deleted file mode 100644 index 6edb4a68..00000000 --- a/frontend/src/api/generated/model/department.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface Department { - id?: number | null; - name: string; - head_employee_id?: number | null; -} diff --git a/frontend/src/api/generated/model/departmentCreate.ts b/frontend/src/api/generated/model/departmentCreate.ts deleted file mode 100644 index 69622566..00000000 --- a/frontend/src/api/generated/model/departmentCreate.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface DepartmentCreate { - name: string; - head_employee_id?: number | null; -} diff --git a/frontend/src/api/generated/model/departmentUpdate.ts b/frontend/src/api/generated/model/departmentUpdate.ts deleted file mode 100644 index 0a11d7e1..00000000 --- a/frontend/src/api/generated/model/departmentUpdate.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface DepartmentUpdate { - name?: string | null; - head_employee_id?: number | null; -} diff --git a/frontend/src/api/generated/model/employee.ts b/frontend/src/api/generated/model/employee.ts deleted file mode 100644 index 5e273ee6..00000000 --- a/frontend/src/api/generated/model/employee.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface Employee { - id?: number | null; - name: string; - employee_type: string; - department_id?: number | null; - team_id?: number | null; - manager_id?: number | null; - title?: string | null; - status?: string; - openclaw_session_key?: string | null; - notify_enabled?: boolean; -} diff --git a/frontend/src/api/generated/model/employeeCreate.ts b/frontend/src/api/generated/model/employeeCreate.ts deleted file mode 100644 index 4ca4c9ae..00000000 --- a/frontend/src/api/generated/model/employeeCreate.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface EmployeeCreate { - name: string; - employee_type: string; - department_id?: number | null; - team_id?: number | null; - manager_id?: number | null; - title?: string | null; - status?: string; - openclaw_session_key?: string | null; - notify_enabled?: boolean; -} diff --git a/frontend/src/api/generated/model/employeeUpdate.ts b/frontend/src/api/generated/model/employeeUpdate.ts deleted file mode 100644 index 28f03843..00000000 --- a/frontend/src/api/generated/model/employeeUpdate.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface EmployeeUpdate { - name?: string | null; - employee_type?: string | null; - department_id?: number | null; - team_id?: number | null; - manager_id?: number | null; - title?: string | null; - status?: string | null; - openclaw_session_key?: string | null; - notify_enabled?: boolean | null; -} diff --git a/frontend/src/api/generated/model/employmentAction.ts b/frontend/src/api/generated/model/employmentAction.ts deleted file mode 100644 index aa2bcecb..00000000 --- a/frontend/src/api/generated/model/employmentAction.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface EmploymentAction { - id?: number | null; - employee_id: number; - issued_by_employee_id: number; - action_type: string; - notes?: string | null; - created_at?: string; -} diff --git a/frontend/src/api/generated/model/employmentActionCreate.ts b/frontend/src/api/generated/model/employmentActionCreate.ts deleted file mode 100644 index df8cb1a5..00000000 --- a/frontend/src/api/generated/model/employmentActionCreate.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface EmploymentActionCreate { - employee_id: number; - issued_by_employee_id: number; - action_type: string; - notes?: string | null; -} diff --git a/frontend/src/api/generated/model/hTTPValidationError.ts b/frontend/src/api/generated/model/hTTPValidationError.ts deleted file mode 100644 index ce903fd6..00000000 --- a/frontend/src/api/generated/model/hTTPValidationError.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ -import type { ValidationError } from "./validationError"; - -export interface HTTPValidationError { - detail?: ValidationError[]; -} diff --git a/frontend/src/api/generated/model/headcountRequest.ts b/frontend/src/api/generated/model/headcountRequest.ts deleted file mode 100644 index de4d0929..00000000 --- a/frontend/src/api/generated/model/headcountRequest.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface HeadcountRequest { - id?: number | null; - department_id: number; - requested_by_manager_id: number; - role_title: string; - employee_type: string; - quantity?: number; - justification?: string | null; - status?: string; - created_at?: string; -} diff --git a/frontend/src/api/generated/model/headcountRequestCreate.ts b/frontend/src/api/generated/model/headcountRequestCreate.ts deleted file mode 100644 index 1f5d180f..00000000 --- a/frontend/src/api/generated/model/headcountRequestCreate.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface HeadcountRequestCreate { - department_id: number; - requested_by_manager_id: number; - role_title: string; - employee_type: string; - quantity?: number; - justification?: string | null; -} diff --git a/frontend/src/api/generated/model/headcountRequestUpdate.ts b/frontend/src/api/generated/model/headcountRequestUpdate.ts deleted file mode 100644 index fcd9b80f..00000000 --- a/frontend/src/api/generated/model/headcountRequestUpdate.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface HeadcountRequestUpdate { - status?: string | null; - justification?: string | null; -} diff --git a/frontend/src/api/generated/model/index.ts b/frontend/src/api/generated/model/index.ts deleted file mode 100644 index 3dbfd2a0..00000000 --- a/frontend/src/api/generated/model/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export * from "./agentOnboarding"; -export * from "./agentOnboardingCreate"; -export * from "./agentOnboardingUpdate"; -export * from "./department"; -export * from "./departmentCreate"; -export * from "./departmentUpdate"; -export * from "./employee"; -export * from "./employeeCreate"; -export * from "./employeeUpdate"; -export * from "./employmentAction"; -export * from "./employmentActionCreate"; -export * from "./headcountRequest"; -export * from "./headcountRequestCreate"; -export * from "./headcountRequestUpdate"; -export * from "./hTTPValidationError"; -export * from "./listActivitiesActivitiesGetParams"; -export * from "./listTaskCommentsTaskCommentsGetParams"; -export * from "./listTasksTasksGetParams"; -export * from "./listTeamsTeamsGetParams"; -export * from "./project"; -export * from "./projectCreate"; -export * from "./projectMember"; -export * from "./projectUpdate"; -export * from "./task"; -export * from "./taskComment"; -export * from "./taskCommentCreate"; -export * from "./taskCreate"; -export * from "./taskReviewDecision"; -export * from "./taskUpdate"; -export * from "./team"; -export * from "./teamCreate"; -export * from "./teamUpdate"; -export * from "./validationError"; diff --git a/frontend/src/api/generated/model/listActivitiesActivitiesGetParams.ts b/frontend/src/api/generated/model/listActivitiesActivitiesGetParams.ts deleted file mode 100644 index 6211a8be..00000000 --- a/frontend/src/api/generated/model/listActivitiesActivitiesGetParams.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export type ListActivitiesActivitiesGetParams = { - limit?: number; -}; diff --git a/frontend/src/api/generated/model/listTaskCommentsTaskCommentsGetParams.ts b/frontend/src/api/generated/model/listTaskCommentsTaskCommentsGetParams.ts deleted file mode 100644 index 3542ea57..00000000 --- a/frontend/src/api/generated/model/listTaskCommentsTaskCommentsGetParams.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export type ListTaskCommentsTaskCommentsGetParams = { - task_id: number; -}; diff --git a/frontend/src/api/generated/model/listTasksTasksGetParams.ts b/frontend/src/api/generated/model/listTasksTasksGetParams.ts deleted file mode 100644 index d8200c89..00000000 --- a/frontend/src/api/generated/model/listTasksTasksGetParams.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export type ListTasksTasksGetParams = { - project_id?: number | null; -}; diff --git a/frontend/src/api/generated/model/listTeamsTeamsGetParams.ts b/frontend/src/api/generated/model/listTeamsTeamsGetParams.ts deleted file mode 100644 index 54cb6eb7..00000000 --- a/frontend/src/api/generated/model/listTeamsTeamsGetParams.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export type ListTeamsTeamsGetParams = { - department_id?: number | null; -}; diff --git a/frontend/src/api/generated/model/project.ts b/frontend/src/api/generated/model/project.ts deleted file mode 100644 index d886a9a8..00000000 --- a/frontend/src/api/generated/model/project.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface Project { - id?: number | null; - name: string; - status?: string; - team_id?: number | null; -} diff --git a/frontend/src/api/generated/model/projectCreate.ts b/frontend/src/api/generated/model/projectCreate.ts deleted file mode 100644 index 9432eb92..00000000 --- a/frontend/src/api/generated/model/projectCreate.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface ProjectCreate { - name: string; - status?: string; - team_id?: number | null; -} diff --git a/frontend/src/api/generated/model/projectMember.ts b/frontend/src/api/generated/model/projectMember.ts deleted file mode 100644 index 25572482..00000000 --- a/frontend/src/api/generated/model/projectMember.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface ProjectMember { - id?: number | null; - project_id: number; - employee_id: number; - role?: string | null; -} diff --git a/frontend/src/api/generated/model/projectUpdate.ts b/frontend/src/api/generated/model/projectUpdate.ts deleted file mode 100644 index 8e6f7969..00000000 --- a/frontend/src/api/generated/model/projectUpdate.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface ProjectUpdate { - name?: string | null; - status?: string | null; - team_id?: number | null; -} diff --git a/frontend/src/api/generated/model/task.ts b/frontend/src/api/generated/model/task.ts deleted file mode 100644 index 037f2c3d..00000000 --- a/frontend/src/api/generated/model/task.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface Task { - id?: number | null; - project_id: number; - title: string; - description?: string | null; - status?: string; - assignee_employee_id?: number | null; - reviewer_employee_id?: number | null; - created_by_employee_id?: number | null; - created_at?: string; - updated_at?: string; -} diff --git a/frontend/src/api/generated/model/taskComment.ts b/frontend/src/api/generated/model/taskComment.ts deleted file mode 100644 index 2d5713f1..00000000 --- a/frontend/src/api/generated/model/taskComment.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface TaskComment { - id?: number | null; - task_id: number; - author_employee_id?: number | null; - reply_to_comment_id?: number | null; - body: string; - created_at?: string; -} diff --git a/frontend/src/api/generated/model/taskCommentCreate.ts b/frontend/src/api/generated/model/taskCommentCreate.ts deleted file mode 100644 index 102180b2..00000000 --- a/frontend/src/api/generated/model/taskCommentCreate.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface TaskCommentCreate { - task_id: number; - author_employee_id?: number | null; - reply_to_comment_id?: number | null; - body: string; -} diff --git a/frontend/src/api/generated/model/taskCreate.ts b/frontend/src/api/generated/model/taskCreate.ts deleted file mode 100644 index dc1aea90..00000000 --- a/frontend/src/api/generated/model/taskCreate.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface TaskCreate { - project_id: number; - title: string; - description?: string | null; - status?: string; - assignee_employee_id?: number | null; - reviewer_employee_id?: number | null; - created_by_employee_id?: number | null; -} diff --git a/frontend/src/api/generated/model/taskReviewDecision.ts b/frontend/src/api/generated/model/taskReviewDecision.ts deleted file mode 100644 index ed64c206..00000000 --- a/frontend/src/api/generated/model/taskReviewDecision.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface TaskReviewDecision { - decision: string; - comment_body: string; -} diff --git a/frontend/src/api/generated/model/taskUpdate.ts b/frontend/src/api/generated/model/taskUpdate.ts deleted file mode 100644 index cd596be7..00000000 --- a/frontend/src/api/generated/model/taskUpdate.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface TaskUpdate { - title?: string | null; - description?: string | null; - status?: string | null; - assignee_employee_id?: number | null; - reviewer_employee_id?: number | null; -} diff --git a/frontend/src/api/generated/model/team.ts b/frontend/src/api/generated/model/team.ts deleted file mode 100644 index a407a727..00000000 --- a/frontend/src/api/generated/model/team.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface Team { - id?: number | null; - name: string; - department_id: number; - lead_employee_id?: number | null; -} diff --git a/frontend/src/api/generated/model/teamCreate.ts b/frontend/src/api/generated/model/teamCreate.ts deleted file mode 100644 index bdf963a1..00000000 --- a/frontend/src/api/generated/model/teamCreate.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface TeamCreate { - name: string; - department_id: number; - lead_employee_id?: number | null; -} diff --git a/frontend/src/api/generated/model/teamUpdate.ts b/frontend/src/api/generated/model/teamUpdate.ts deleted file mode 100644 index 1bfa271d..00000000 --- a/frontend/src/api/generated/model/teamUpdate.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface TeamUpdate { - name?: string | null; - department_id?: number | null; - lead_employee_id?: number | null; -} diff --git a/frontend/src/api/generated/model/validationError.ts b/frontend/src/api/generated/model/validationError.ts deleted file mode 100644 index 64b44465..00000000 --- a/frontend/src/api/generated/model/validationError.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ - -export interface ValidationError { - loc: (string | number)[]; - msg: string; - type: string; -} diff --git a/frontend/src/api/generated/org/org.ts b/frontend/src/api/generated/org/org.ts deleted file mode 100644 index 23f7828a..00000000 --- a/frontend/src/api/generated/org/org.ts +++ /dev/null @@ -1,1603 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ -import { useMutation, useQuery } from "@tanstack/react-query"; -import type { - DataTag, - DefinedInitialDataOptions, - DefinedUseQueryResult, - MutationFunction, - QueryClient, - QueryFunction, - QueryKey, - UndefinedInitialDataOptions, - UseMutationOptions, - UseMutationResult, - UseQueryOptions, - UseQueryResult, -} from "@tanstack/react-query"; - -import type { - Department, - DepartmentCreate, - DepartmentUpdate, - Employee, - EmployeeCreate, - EmployeeUpdate, - HTTPValidationError, - ListTeamsTeamsGetParams, - Team, - TeamCreate, - TeamUpdate, -} from ".././model"; - -import { customFetch } from "../../mutator"; - -type SecondParameter unknown> = Parameters[1]; - -/** - * @summary List Departments - */ -export type listDepartmentsDepartmentsGetResponse200 = { - data: Department[]; - status: 200; -}; - -export type listDepartmentsDepartmentsGetResponseSuccess = - listDepartmentsDepartmentsGetResponse200 & { - headers: Headers; - }; -export type listDepartmentsDepartmentsGetResponse = - listDepartmentsDepartmentsGetResponseSuccess; - -export const getListDepartmentsDepartmentsGetUrl = () => { - return `/departments`; -}; - -export const listDepartmentsDepartmentsGet = async ( - options?: RequestInit, -): Promise => { - return customFetch( - getListDepartmentsDepartmentsGetUrl(), - { - ...options, - method: "GET", - }, - ); -}; - -export const getListDepartmentsDepartmentsGetQueryKey = () => { - return [`/departments`] as const; -}; - -export const getListDepartmentsDepartmentsGetQueryOptions = < - TData = Awaited>, - TError = unknown, ->(options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; -}) => { - const { query: queryOptions, request: requestOptions } = options ?? {}; - - const queryKey = - queryOptions?.queryKey ?? getListDepartmentsDepartmentsGetQueryKey(); - - const queryFn: QueryFunction< - Awaited> - > = ({ signal }) => - listDepartmentsDepartmentsGet({ signal, ...requestOptions }); - - return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< - Awaited>, - TError, - TData - > & { queryKey: DataTag }; -}; - -export type ListDepartmentsDepartmentsGetQueryResult = NonNullable< - Awaited> ->; -export type ListDepartmentsDepartmentsGetQueryError = unknown; - -export function useListDepartmentsDepartmentsGet< - TData = Awaited>, - TError = unknown, ->( - options: { - query: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - DefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): DefinedUseQueryResult & { - queryKey: DataTag; -}; -export function useListDepartmentsDepartmentsGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - UndefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -export function useListDepartmentsDepartmentsGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -/** - * @summary List Departments - */ - -export function useListDepartmentsDepartmentsGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -} { - const queryOptions = getListDepartmentsDepartmentsGetQueryOptions(options); - - const query = useQuery(queryOptions, queryClient) as UseQueryResult< - TData, - TError - > & { queryKey: DataTag }; - - return { ...query, queryKey: queryOptions.queryKey }; -} - -/** - * Create a department. - -Important: keep the operation atomic. We flush to get dept.id, log the activity, -then commit once. We also translate common DB integrity errors into 409s. - * @summary Create Department - */ -export type createDepartmentDepartmentsPostResponse200 = { - data: Department; - status: 200; -}; - -export type createDepartmentDepartmentsPostResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type createDepartmentDepartmentsPostResponseSuccess = - createDepartmentDepartmentsPostResponse200 & { - headers: Headers; - }; -export type createDepartmentDepartmentsPostResponseError = - createDepartmentDepartmentsPostResponse422 & { - headers: Headers; - }; - -export type createDepartmentDepartmentsPostResponse = - | createDepartmentDepartmentsPostResponseSuccess - | createDepartmentDepartmentsPostResponseError; - -export const getCreateDepartmentDepartmentsPostUrl = () => { - return `/departments`; -}; - -export const createDepartmentDepartmentsPost = async ( - departmentCreate: DepartmentCreate, - options?: RequestInit, -): Promise => { - return customFetch( - getCreateDepartmentDepartmentsPostUrl(), - { - ...options, - method: "POST", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(departmentCreate), - }, - ); -}; - -export const getCreateDepartmentDepartmentsPostMutationOptions = < - TError = HTTPValidationError, - TContext = unknown, ->(options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { data: DepartmentCreate }, - TContext - >; - request?: SecondParameter; -}): UseMutationOptions< - Awaited>, - TError, - { data: DepartmentCreate }, - TContext -> => { - const mutationKey = ["createDepartmentDepartmentsPost"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited>, - { data: DepartmentCreate } - > = (props) => { - const { data } = props ?? {}; - - return createDepartmentDepartmentsPost(data, requestOptions); - }; - - return { mutationFn, ...mutationOptions }; -}; - -export type CreateDepartmentDepartmentsPostMutationResult = NonNullable< - Awaited> ->; -export type CreateDepartmentDepartmentsPostMutationBody = DepartmentCreate; -export type CreateDepartmentDepartmentsPostMutationError = HTTPValidationError; - -/** - * @summary Create Department - */ -export const useCreateDepartmentDepartmentsPost = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { data: DepartmentCreate }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { data: DepartmentCreate }, - TContext -> => { - return useMutation( - getCreateDepartmentDepartmentsPostMutationOptions(options), - queryClient, - ); -}; -/** - * @summary List Teams - */ -export type listTeamsTeamsGetResponse200 = { - data: Team[]; - status: 200; -}; - -export type listTeamsTeamsGetResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type listTeamsTeamsGetResponseSuccess = listTeamsTeamsGetResponse200 & { - headers: Headers; -}; -export type listTeamsTeamsGetResponseError = listTeamsTeamsGetResponse422 & { - headers: Headers; -}; - -export type listTeamsTeamsGetResponse = - | listTeamsTeamsGetResponseSuccess - | listTeamsTeamsGetResponseError; - -export const getListTeamsTeamsGetUrl = (params?: ListTeamsTeamsGetParams) => { - const normalizedParams = new URLSearchParams(); - - Object.entries(params || {}).forEach(([key, value]) => { - if (value !== undefined) { - normalizedParams.append(key, value === null ? "null" : value.toString()); - } - }); - - const stringifiedParams = normalizedParams.toString(); - - return stringifiedParams.length > 0 - ? `/teams?${stringifiedParams}` - : `/teams`; -}; - -export const listTeamsTeamsGet = async ( - params?: ListTeamsTeamsGetParams, - options?: RequestInit, -): Promise => { - return customFetch( - getListTeamsTeamsGetUrl(params), - { - ...options, - method: "GET", - }, - ); -}; - -export const getListTeamsTeamsGetQueryKey = ( - params?: ListTeamsTeamsGetParams, -) => { - return [`/teams`, ...(params ? [params] : [])] as const; -}; - -export const getListTeamsTeamsGetQueryOptions = < - TData = Awaited>, - TError = HTTPValidationError, ->( - params?: ListTeamsTeamsGetParams, - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, -) => { - const { query: queryOptions, request: requestOptions } = options ?? {}; - - const queryKey = - queryOptions?.queryKey ?? getListTeamsTeamsGetQueryKey(params); - - const queryFn: QueryFunction< - Awaited> - > = ({ signal }) => listTeamsTeamsGet(params, { signal, ...requestOptions }); - - return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< - Awaited>, - TError, - TData - > & { queryKey: DataTag }; -}; - -export type ListTeamsTeamsGetQueryResult = NonNullable< - Awaited> ->; -export type ListTeamsTeamsGetQueryError = HTTPValidationError; - -export function useListTeamsTeamsGet< - TData = Awaited>, - TError = HTTPValidationError, ->( - params: undefined | ListTeamsTeamsGetParams, - options: { - query: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - DefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): DefinedUseQueryResult & { - queryKey: DataTag; -}; -export function useListTeamsTeamsGet< - TData = Awaited>, - TError = HTTPValidationError, ->( - params?: ListTeamsTeamsGetParams, - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - UndefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -export function useListTeamsTeamsGet< - TData = Awaited>, - TError = HTTPValidationError, ->( - params?: ListTeamsTeamsGetParams, - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -/** - * @summary List Teams - */ - -export function useListTeamsTeamsGet< - TData = Awaited>, - TError = HTTPValidationError, ->( - params?: ListTeamsTeamsGetParams, - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -} { - const queryOptions = getListTeamsTeamsGetQueryOptions(params, options); - - const query = useQuery(queryOptions, queryClient) as UseQueryResult< - TData, - TError - > & { queryKey: DataTag }; - - return { ...query, queryKey: queryOptions.queryKey }; -} - -/** - * @summary Create Team - */ -export type createTeamTeamsPostResponse200 = { - data: Team; - status: 200; -}; - -export type createTeamTeamsPostResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type createTeamTeamsPostResponseSuccess = - createTeamTeamsPostResponse200 & { - headers: Headers; - }; -export type createTeamTeamsPostResponseError = - createTeamTeamsPostResponse422 & { - headers: Headers; - }; - -export type createTeamTeamsPostResponse = - | createTeamTeamsPostResponseSuccess - | createTeamTeamsPostResponseError; - -export const getCreateTeamTeamsPostUrl = () => { - return `/teams`; -}; - -export const createTeamTeamsPost = async ( - teamCreate: TeamCreate, - options?: RequestInit, -): Promise => { - return customFetch(getCreateTeamTeamsPostUrl(), { - ...options, - method: "POST", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(teamCreate), - }); -}; - -export const getCreateTeamTeamsPostMutationOptions = < - TError = HTTPValidationError, - TContext = unknown, ->(options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { data: TeamCreate }, - TContext - >; - request?: SecondParameter; -}): UseMutationOptions< - Awaited>, - TError, - { data: TeamCreate }, - TContext -> => { - const mutationKey = ["createTeamTeamsPost"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited>, - { data: TeamCreate } - > = (props) => { - const { data } = props ?? {}; - - return createTeamTeamsPost(data, requestOptions); - }; - - return { mutationFn, ...mutationOptions }; -}; - -export type CreateTeamTeamsPostMutationResult = NonNullable< - Awaited> ->; -export type CreateTeamTeamsPostMutationBody = TeamCreate; -export type CreateTeamTeamsPostMutationError = HTTPValidationError; - -/** - * @summary Create Team - */ -export const useCreateTeamTeamsPost = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { data: TeamCreate }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { data: TeamCreate }, - TContext -> => { - return useMutation( - getCreateTeamTeamsPostMutationOptions(options), - queryClient, - ); -}; -/** - * @summary Update Team - */ -export type updateTeamTeamsTeamIdPatchResponse200 = { - data: Team; - status: 200; -}; - -export type updateTeamTeamsTeamIdPatchResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type updateTeamTeamsTeamIdPatchResponseSuccess = - updateTeamTeamsTeamIdPatchResponse200 & { - headers: Headers; - }; -export type updateTeamTeamsTeamIdPatchResponseError = - updateTeamTeamsTeamIdPatchResponse422 & { - headers: Headers; - }; - -export type updateTeamTeamsTeamIdPatchResponse = - | updateTeamTeamsTeamIdPatchResponseSuccess - | updateTeamTeamsTeamIdPatchResponseError; - -export const getUpdateTeamTeamsTeamIdPatchUrl = (teamId: number) => { - return `/teams/${teamId}`; -}; - -export const updateTeamTeamsTeamIdPatch = async ( - teamId: number, - teamUpdate: TeamUpdate, - options?: RequestInit, -): Promise => { - return customFetch( - getUpdateTeamTeamsTeamIdPatchUrl(teamId), - { - ...options, - method: "PATCH", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(teamUpdate), - }, - ); -}; - -export const getUpdateTeamTeamsTeamIdPatchMutationOptions = < - TError = HTTPValidationError, - TContext = unknown, ->(options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { teamId: number; data: TeamUpdate }, - TContext - >; - request?: SecondParameter; -}): UseMutationOptions< - Awaited>, - TError, - { teamId: number; data: TeamUpdate }, - TContext -> => { - const mutationKey = ["updateTeamTeamsTeamIdPatch"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited>, - { teamId: number; data: TeamUpdate } - > = (props) => { - const { teamId, data } = props ?? {}; - - return updateTeamTeamsTeamIdPatch(teamId, data, requestOptions); - }; - - return { mutationFn, ...mutationOptions }; -}; - -export type UpdateTeamTeamsTeamIdPatchMutationResult = NonNullable< - Awaited> ->; -export type UpdateTeamTeamsTeamIdPatchMutationBody = TeamUpdate; -export type UpdateTeamTeamsTeamIdPatchMutationError = HTTPValidationError; - -/** - * @summary Update Team - */ -export const useUpdateTeamTeamsTeamIdPatch = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { teamId: number; data: TeamUpdate }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { teamId: number; data: TeamUpdate }, - TContext -> => { - return useMutation( - getUpdateTeamTeamsTeamIdPatchMutationOptions(options), - queryClient, - ); -}; -/** - * @summary Update Department - */ -export type updateDepartmentDepartmentsDepartmentIdPatchResponse200 = { - data: Department; - status: 200; -}; - -export type updateDepartmentDepartmentsDepartmentIdPatchResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type updateDepartmentDepartmentsDepartmentIdPatchResponseSuccess = - updateDepartmentDepartmentsDepartmentIdPatchResponse200 & { - headers: Headers; - }; -export type updateDepartmentDepartmentsDepartmentIdPatchResponseError = - updateDepartmentDepartmentsDepartmentIdPatchResponse422 & { - headers: Headers; - }; - -export type updateDepartmentDepartmentsDepartmentIdPatchResponse = - | updateDepartmentDepartmentsDepartmentIdPatchResponseSuccess - | updateDepartmentDepartmentsDepartmentIdPatchResponseError; - -export const getUpdateDepartmentDepartmentsDepartmentIdPatchUrl = ( - departmentId: number, -) => { - return `/departments/${departmentId}`; -}; - -export const updateDepartmentDepartmentsDepartmentIdPatch = async ( - departmentId: number, - departmentUpdate: DepartmentUpdate, - options?: RequestInit, -): Promise => { - return customFetch( - getUpdateDepartmentDepartmentsDepartmentIdPatchUrl(departmentId), - { - ...options, - method: "PATCH", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(departmentUpdate), - }, - ); -}; - -export const getUpdateDepartmentDepartmentsDepartmentIdPatchMutationOptions = < - TError = HTTPValidationError, - TContext = unknown, ->(options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { departmentId: number; data: DepartmentUpdate }, - TContext - >; - request?: SecondParameter; -}): UseMutationOptions< - Awaited>, - TError, - { departmentId: number; data: DepartmentUpdate }, - TContext -> => { - const mutationKey = ["updateDepartmentDepartmentsDepartmentIdPatch"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited>, - { departmentId: number; data: DepartmentUpdate } - > = (props) => { - const { departmentId, data } = props ?? {}; - - return updateDepartmentDepartmentsDepartmentIdPatch( - departmentId, - data, - requestOptions, - ); - }; - - return { mutationFn, ...mutationOptions }; -}; - -export type UpdateDepartmentDepartmentsDepartmentIdPatchMutationResult = - NonNullable< - Awaited> - >; -export type UpdateDepartmentDepartmentsDepartmentIdPatchMutationBody = - DepartmentUpdate; -export type UpdateDepartmentDepartmentsDepartmentIdPatchMutationError = - HTTPValidationError; - -/** - * @summary Update Department - */ -export const useUpdateDepartmentDepartmentsDepartmentIdPatch = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { departmentId: number; data: DepartmentUpdate }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { departmentId: number; data: DepartmentUpdate }, - TContext -> => { - return useMutation( - getUpdateDepartmentDepartmentsDepartmentIdPatchMutationOptions(options), - queryClient, - ); -}; -/** - * @summary List Employees - */ -export type listEmployeesEmployeesGetResponse200 = { - data: Employee[]; - status: 200; -}; - -export type listEmployeesEmployeesGetResponseSuccess = - listEmployeesEmployeesGetResponse200 & { - headers: Headers; - }; -export type listEmployeesEmployeesGetResponse = - listEmployeesEmployeesGetResponseSuccess; - -export const getListEmployeesEmployeesGetUrl = () => { - return `/employees`; -}; - -export const listEmployeesEmployeesGet = async ( - options?: RequestInit, -): Promise => { - return customFetch( - getListEmployeesEmployeesGetUrl(), - { - ...options, - method: "GET", - }, - ); -}; - -export const getListEmployeesEmployeesGetQueryKey = () => { - return [`/employees`] as const; -}; - -export const getListEmployeesEmployeesGetQueryOptions = < - TData = Awaited>, - TError = unknown, ->(options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; -}) => { - const { query: queryOptions, request: requestOptions } = options ?? {}; - - const queryKey = - queryOptions?.queryKey ?? getListEmployeesEmployeesGetQueryKey(); - - const queryFn: QueryFunction< - Awaited> - > = ({ signal }) => listEmployeesEmployeesGet({ signal, ...requestOptions }); - - return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< - Awaited>, - TError, - TData - > & { queryKey: DataTag }; -}; - -export type ListEmployeesEmployeesGetQueryResult = NonNullable< - Awaited> ->; -export type ListEmployeesEmployeesGetQueryError = unknown; - -export function useListEmployeesEmployeesGet< - TData = Awaited>, - TError = unknown, ->( - options: { - query: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - DefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): DefinedUseQueryResult & { - queryKey: DataTag; -}; -export function useListEmployeesEmployeesGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - UndefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -export function useListEmployeesEmployeesGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -/** - * @summary List Employees - */ - -export function useListEmployeesEmployeesGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -} { - const queryOptions = getListEmployeesEmployeesGetQueryOptions(options); - - const query = useQuery(queryOptions, queryClient) as UseQueryResult< - TData, - TError - > & { queryKey: DataTag }; - - return { ...query, queryKey: queryOptions.queryKey }; -} - -/** - * @summary Create Employee - */ -export type createEmployeeEmployeesPostResponse200 = { - data: Employee; - status: 200; -}; - -export type createEmployeeEmployeesPostResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type createEmployeeEmployeesPostResponseSuccess = - createEmployeeEmployeesPostResponse200 & { - headers: Headers; - }; -export type createEmployeeEmployeesPostResponseError = - createEmployeeEmployeesPostResponse422 & { - headers: Headers; - }; - -export type createEmployeeEmployeesPostResponse = - | createEmployeeEmployeesPostResponseSuccess - | createEmployeeEmployeesPostResponseError; - -export const getCreateEmployeeEmployeesPostUrl = () => { - return `/employees`; -}; - -export const createEmployeeEmployeesPost = async ( - employeeCreate: EmployeeCreate, - options?: RequestInit, -): Promise => { - return customFetch( - getCreateEmployeeEmployeesPostUrl(), - { - ...options, - method: "POST", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(employeeCreate), - }, - ); -}; - -export const getCreateEmployeeEmployeesPostMutationOptions = < - TError = HTTPValidationError, - TContext = unknown, ->(options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { data: EmployeeCreate }, - TContext - >; - request?: SecondParameter; -}): UseMutationOptions< - Awaited>, - TError, - { data: EmployeeCreate }, - TContext -> => { - const mutationKey = ["createEmployeeEmployeesPost"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited>, - { data: EmployeeCreate } - > = (props) => { - const { data } = props ?? {}; - - return createEmployeeEmployeesPost(data, requestOptions); - }; - - return { mutationFn, ...mutationOptions }; -}; - -export type CreateEmployeeEmployeesPostMutationResult = NonNullable< - Awaited> ->; -export type CreateEmployeeEmployeesPostMutationBody = EmployeeCreate; -export type CreateEmployeeEmployeesPostMutationError = HTTPValidationError; - -/** - * @summary Create Employee - */ -export const useCreateEmployeeEmployeesPost = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { data: EmployeeCreate }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { data: EmployeeCreate }, - TContext -> => { - return useMutation( - getCreateEmployeeEmployeesPostMutationOptions(options), - queryClient, - ); -}; -/** - * @summary Update Employee - */ -export type updateEmployeeEmployeesEmployeeIdPatchResponse200 = { - data: Employee; - status: 200; -}; - -export type updateEmployeeEmployeesEmployeeIdPatchResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type updateEmployeeEmployeesEmployeeIdPatchResponseSuccess = - updateEmployeeEmployeesEmployeeIdPatchResponse200 & { - headers: Headers; - }; -export type updateEmployeeEmployeesEmployeeIdPatchResponseError = - updateEmployeeEmployeesEmployeeIdPatchResponse422 & { - headers: Headers; - }; - -export type updateEmployeeEmployeesEmployeeIdPatchResponse = - | updateEmployeeEmployeesEmployeeIdPatchResponseSuccess - | updateEmployeeEmployeesEmployeeIdPatchResponseError; - -export const getUpdateEmployeeEmployeesEmployeeIdPatchUrl = ( - employeeId: number, -) => { - return `/employees/${employeeId}`; -}; - -export const updateEmployeeEmployeesEmployeeIdPatch = async ( - employeeId: number, - employeeUpdate: EmployeeUpdate, - options?: RequestInit, -): Promise => { - return customFetch( - getUpdateEmployeeEmployeesEmployeeIdPatchUrl(employeeId), - { - ...options, - method: "PATCH", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(employeeUpdate), - }, - ); -}; - -export const getUpdateEmployeeEmployeesEmployeeIdPatchMutationOptions = < - TError = HTTPValidationError, - TContext = unknown, ->(options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { employeeId: number; data: EmployeeUpdate }, - TContext - >; - request?: SecondParameter; -}): UseMutationOptions< - Awaited>, - TError, - { employeeId: number; data: EmployeeUpdate }, - TContext -> => { - const mutationKey = ["updateEmployeeEmployeesEmployeeIdPatch"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited>, - { employeeId: number; data: EmployeeUpdate } - > = (props) => { - const { employeeId, data } = props ?? {}; - - return updateEmployeeEmployeesEmployeeIdPatch( - employeeId, - data, - requestOptions, - ); - }; - - return { mutationFn, ...mutationOptions }; -}; - -export type UpdateEmployeeEmployeesEmployeeIdPatchMutationResult = NonNullable< - Awaited> ->; -export type UpdateEmployeeEmployeesEmployeeIdPatchMutationBody = EmployeeUpdate; -export type UpdateEmployeeEmployeesEmployeeIdPatchMutationError = - HTTPValidationError; - -/** - * @summary Update Employee - */ -export const useUpdateEmployeeEmployeesEmployeeIdPatch = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { employeeId: number; data: EmployeeUpdate }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { employeeId: number; data: EmployeeUpdate }, - TContext -> => { - return useMutation( - getUpdateEmployeeEmployeesEmployeeIdPatchMutationOptions(options), - queryClient, - ); -}; -/** - * @summary Provision Employee Agent - */ -export type provisionEmployeeAgentEmployeesEmployeeIdProvisionPostResponse200 = - { - data: Employee; - status: 200; - }; - -export type provisionEmployeeAgentEmployeesEmployeeIdProvisionPostResponse422 = - { - data: HTTPValidationError; - status: 422; - }; - -export type provisionEmployeeAgentEmployeesEmployeeIdProvisionPostResponseSuccess = - provisionEmployeeAgentEmployeesEmployeeIdProvisionPostResponse200 & { - headers: Headers; - }; -export type provisionEmployeeAgentEmployeesEmployeeIdProvisionPostResponseError = - provisionEmployeeAgentEmployeesEmployeeIdProvisionPostResponse422 & { - headers: Headers; - }; - -export type provisionEmployeeAgentEmployeesEmployeeIdProvisionPostResponse = - | provisionEmployeeAgentEmployeesEmployeeIdProvisionPostResponseSuccess - | provisionEmployeeAgentEmployeesEmployeeIdProvisionPostResponseError; - -export const getProvisionEmployeeAgentEmployeesEmployeeIdProvisionPostUrl = ( - employeeId: number, -) => { - return `/employees/${employeeId}/provision`; -}; - -export const provisionEmployeeAgentEmployeesEmployeeIdProvisionPost = async ( - employeeId: number, - options?: RequestInit, -): Promise => { - return customFetch( - getProvisionEmployeeAgentEmployeesEmployeeIdProvisionPostUrl(employeeId), - { - ...options, - method: "POST", - }, - ); -}; - -export const getProvisionEmployeeAgentEmployeesEmployeeIdProvisionPostMutationOptions = - (options?: { - mutation?: UseMutationOptions< - Awaited< - ReturnType< - typeof provisionEmployeeAgentEmployeesEmployeeIdProvisionPost - > - >, - TError, - { employeeId: number }, - TContext - >; - request?: SecondParameter; - }): UseMutationOptions< - Awaited< - ReturnType - >, - TError, - { employeeId: number }, - TContext - > => { - const mutationKey = [ - "provisionEmployeeAgentEmployeesEmployeeIdProvisionPost", - ]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited< - ReturnType< - typeof provisionEmployeeAgentEmployeesEmployeeIdProvisionPost - > - >, - { employeeId: number } - > = (props) => { - const { employeeId } = props ?? {}; - - return provisionEmployeeAgentEmployeesEmployeeIdProvisionPost( - employeeId, - requestOptions, - ); - }; - - return { mutationFn, ...mutationOptions }; - }; - -export type ProvisionEmployeeAgentEmployeesEmployeeIdProvisionPostMutationResult = - NonNullable< - Awaited< - ReturnType - > - >; - -export type ProvisionEmployeeAgentEmployeesEmployeeIdProvisionPostMutationError = - HTTPValidationError; - -/** - * @summary Provision Employee Agent - */ -export const useProvisionEmployeeAgentEmployeesEmployeeIdProvisionPost = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited< - ReturnType< - typeof provisionEmployeeAgentEmployeesEmployeeIdProvisionPost - > - >, - TError, - { employeeId: number }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited< - ReturnType - >, - TError, - { employeeId: number }, - TContext -> => { - return useMutation( - getProvisionEmployeeAgentEmployeesEmployeeIdProvisionPostMutationOptions( - options, - ), - queryClient, - ); -}; -/** - * @summary Deprovision Employee Agent - */ -export type deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostResponse200 = - { - data: Employee; - status: 200; - }; - -export type deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostResponse422 = - { - data: HTTPValidationError; - status: 422; - }; - -export type deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostResponseSuccess = - deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostResponse200 & { - headers: Headers; - }; -export type deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostResponseError = - deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostResponse422 & { - headers: Headers; - }; - -export type deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostResponse = - | deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostResponseSuccess - | deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostResponseError; - -export const getDeprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostUrl = - (employeeId: number) => { - return `/employees/${employeeId}/deprovision`; - }; - -export const deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost = - async ( - employeeId: number, - options?: RequestInit, - ): Promise => { - return customFetch( - getDeprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostUrl( - employeeId, - ), - { - ...options, - method: "POST", - }, - ); - }; - -export const getDeprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostMutationOptions = - (options?: { - mutation?: UseMutationOptions< - Awaited< - ReturnType< - typeof deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost - > - >, - TError, - { employeeId: number }, - TContext - >; - request?: SecondParameter; - }): UseMutationOptions< - Awaited< - ReturnType< - typeof deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost - > - >, - TError, - { employeeId: number }, - TContext - > => { - const mutationKey = [ - "deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost", - ]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited< - ReturnType< - typeof deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost - > - >, - { employeeId: number } - > = (props) => { - const { employeeId } = props ?? {}; - - return deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost( - employeeId, - requestOptions, - ); - }; - - return { mutationFn, ...mutationOptions }; - }; - -export type DeprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostMutationResult = - NonNullable< - Awaited< - ReturnType< - typeof deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost - > - > - >; - -export type DeprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostMutationError = - HTTPValidationError; - -/** - * @summary Deprovision Employee Agent - */ -export const useDeprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited< - ReturnType< - typeof deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost - > - >, - TError, - { employeeId: number }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited< - ReturnType< - typeof deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost - > - >, - TError, - { employeeId: number }, - TContext -> => { - return useMutation( - getDeprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostMutationOptions( - options, - ), - queryClient, - ); -}; diff --git a/frontend/src/api/generated/projects/projects.ts b/frontend/src/api/generated/projects/projects.ts deleted file mode 100644 index 604c9135..00000000 --- a/frontend/src/api/generated/projects/projects.ts +++ /dev/null @@ -1,1125 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ -import { useMutation, useQuery } from "@tanstack/react-query"; -import type { - DataTag, - DefinedInitialDataOptions, - DefinedUseQueryResult, - MutationFunction, - QueryClient, - QueryFunction, - QueryKey, - UndefinedInitialDataOptions, - UseMutationOptions, - UseMutationResult, - UseQueryOptions, - UseQueryResult, -} from "@tanstack/react-query"; - -import type { - HTTPValidationError, - Project, - ProjectCreate, - ProjectMember, - ProjectUpdate, -} from ".././model"; - -import { customFetch } from "../../mutator"; - -type SecondParameter unknown> = Parameters[1]; - -/** - * @summary List Projects - */ -export type listProjectsProjectsGetResponse200 = { - data: Project[]; - status: 200; -}; - -export type listProjectsProjectsGetResponseSuccess = - listProjectsProjectsGetResponse200 & { - headers: Headers; - }; -export type listProjectsProjectsGetResponse = - listProjectsProjectsGetResponseSuccess; - -export const getListProjectsProjectsGetUrl = () => { - return `/projects`; -}; - -export const listProjectsProjectsGet = async ( - options?: RequestInit, -): Promise => { - return customFetch( - getListProjectsProjectsGetUrl(), - { - ...options, - method: "GET", - }, - ); -}; - -export const getListProjectsProjectsGetQueryKey = () => { - return [`/projects`] as const; -}; - -export const getListProjectsProjectsGetQueryOptions = < - TData = Awaited>, - TError = unknown, ->(options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; -}) => { - const { query: queryOptions, request: requestOptions } = options ?? {}; - - const queryKey = - queryOptions?.queryKey ?? getListProjectsProjectsGetQueryKey(); - - const queryFn: QueryFunction< - Awaited> - > = ({ signal }) => listProjectsProjectsGet({ signal, ...requestOptions }); - - return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< - Awaited>, - TError, - TData - > & { queryKey: DataTag }; -}; - -export type ListProjectsProjectsGetQueryResult = NonNullable< - Awaited> ->; -export type ListProjectsProjectsGetQueryError = unknown; - -export function useListProjectsProjectsGet< - TData = Awaited>, - TError = unknown, ->( - options: { - query: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - DefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): DefinedUseQueryResult & { - queryKey: DataTag; -}; -export function useListProjectsProjectsGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - UndefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -export function useListProjectsProjectsGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -/** - * @summary List Projects - */ - -export function useListProjectsProjectsGet< - TData = Awaited>, - TError = unknown, ->( - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -} { - const queryOptions = getListProjectsProjectsGetQueryOptions(options); - - const query = useQuery(queryOptions, queryClient) as UseQueryResult< - TData, - TError - > & { queryKey: DataTag }; - - return { ...query, queryKey: queryOptions.queryKey }; -} - -/** - * Create a project. - -Keep operation atomic: flush to get id, log activity, then commit once. -Translate DB integrity errors to 409s. - * @summary Create Project - */ -export type createProjectProjectsPostResponse200 = { - data: Project; - status: 200; -}; - -export type createProjectProjectsPostResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type createProjectProjectsPostResponseSuccess = - createProjectProjectsPostResponse200 & { - headers: Headers; - }; -export type createProjectProjectsPostResponseError = - createProjectProjectsPostResponse422 & { - headers: Headers; - }; - -export type createProjectProjectsPostResponse = - | createProjectProjectsPostResponseSuccess - | createProjectProjectsPostResponseError; - -export const getCreateProjectProjectsPostUrl = () => { - return `/projects`; -}; - -export const createProjectProjectsPost = async ( - projectCreate: ProjectCreate, - options?: RequestInit, -): Promise => { - return customFetch( - getCreateProjectProjectsPostUrl(), - { - ...options, - method: "POST", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(projectCreate), - }, - ); -}; - -export const getCreateProjectProjectsPostMutationOptions = < - TError = HTTPValidationError, - TContext = unknown, ->(options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { data: ProjectCreate }, - TContext - >; - request?: SecondParameter; -}): UseMutationOptions< - Awaited>, - TError, - { data: ProjectCreate }, - TContext -> => { - const mutationKey = ["createProjectProjectsPost"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited>, - { data: ProjectCreate } - > = (props) => { - const { data } = props ?? {}; - - return createProjectProjectsPost(data, requestOptions); - }; - - return { mutationFn, ...mutationOptions }; -}; - -export type CreateProjectProjectsPostMutationResult = NonNullable< - Awaited> ->; -export type CreateProjectProjectsPostMutationBody = ProjectCreate; -export type CreateProjectProjectsPostMutationError = HTTPValidationError; - -/** - * @summary Create Project - */ -export const useCreateProjectProjectsPost = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { data: ProjectCreate }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { data: ProjectCreate }, - TContext -> => { - return useMutation( - getCreateProjectProjectsPostMutationOptions(options), - queryClient, - ); -}; -/** - * @summary Update Project - */ -export type updateProjectProjectsProjectIdPatchResponse200 = { - data: Project; - status: 200; -}; - -export type updateProjectProjectsProjectIdPatchResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type updateProjectProjectsProjectIdPatchResponseSuccess = - updateProjectProjectsProjectIdPatchResponse200 & { - headers: Headers; - }; -export type updateProjectProjectsProjectIdPatchResponseError = - updateProjectProjectsProjectIdPatchResponse422 & { - headers: Headers; - }; - -export type updateProjectProjectsProjectIdPatchResponse = - | updateProjectProjectsProjectIdPatchResponseSuccess - | updateProjectProjectsProjectIdPatchResponseError; - -export const getUpdateProjectProjectsProjectIdPatchUrl = ( - projectId: number, -) => { - return `/projects/${projectId}`; -}; - -export const updateProjectProjectsProjectIdPatch = async ( - projectId: number, - projectUpdate: ProjectUpdate, - options?: RequestInit, -): Promise => { - return customFetch( - getUpdateProjectProjectsProjectIdPatchUrl(projectId), - { - ...options, - method: "PATCH", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(projectUpdate), - }, - ); -}; - -export const getUpdateProjectProjectsProjectIdPatchMutationOptions = < - TError = HTTPValidationError, - TContext = unknown, ->(options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { projectId: number; data: ProjectUpdate }, - TContext - >; - request?: SecondParameter; -}): UseMutationOptions< - Awaited>, - TError, - { projectId: number; data: ProjectUpdate }, - TContext -> => { - const mutationKey = ["updateProjectProjectsProjectIdPatch"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited>, - { projectId: number; data: ProjectUpdate } - > = (props) => { - const { projectId, data } = props ?? {}; - - return updateProjectProjectsProjectIdPatch(projectId, data, requestOptions); - }; - - return { mutationFn, ...mutationOptions }; -}; - -export type UpdateProjectProjectsProjectIdPatchMutationResult = NonNullable< - Awaited> ->; -export type UpdateProjectProjectsProjectIdPatchMutationBody = ProjectUpdate; -export type UpdateProjectProjectsProjectIdPatchMutationError = - HTTPValidationError; - -/** - * @summary Update Project - */ -export const useUpdateProjectProjectsProjectIdPatch = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { projectId: number; data: ProjectUpdate }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { projectId: number; data: ProjectUpdate }, - TContext -> => { - return useMutation( - getUpdateProjectProjectsProjectIdPatchMutationOptions(options), - queryClient, - ); -}; -/** - * @summary List Project Members - */ -export type listProjectMembersProjectsProjectIdMembersGetResponse200 = { - data: ProjectMember[]; - status: 200; -}; - -export type listProjectMembersProjectsProjectIdMembersGetResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type listProjectMembersProjectsProjectIdMembersGetResponseSuccess = - listProjectMembersProjectsProjectIdMembersGetResponse200 & { - headers: Headers; - }; -export type listProjectMembersProjectsProjectIdMembersGetResponseError = - listProjectMembersProjectsProjectIdMembersGetResponse422 & { - headers: Headers; - }; - -export type listProjectMembersProjectsProjectIdMembersGetResponse = - | listProjectMembersProjectsProjectIdMembersGetResponseSuccess - | listProjectMembersProjectsProjectIdMembersGetResponseError; - -export const getListProjectMembersProjectsProjectIdMembersGetUrl = ( - projectId: number, -) => { - return `/projects/${projectId}/members`; -}; - -export const listProjectMembersProjectsProjectIdMembersGet = async ( - projectId: number, - options?: RequestInit, -): Promise => { - return customFetch( - getListProjectMembersProjectsProjectIdMembersGetUrl(projectId), - { - ...options, - method: "GET", - }, - ); -}; - -export const getListProjectMembersProjectsProjectIdMembersGetQueryKey = ( - projectId: number, -) => { - return [`/projects/${projectId}/members`] as const; -}; - -export const getListProjectMembersProjectsProjectIdMembersGetQueryOptions = < - TData = Awaited< - ReturnType - >, - TError = HTTPValidationError, ->( - projectId: number, - options?: { - query?: Partial< - UseQueryOptions< - Awaited< - ReturnType - >, - TError, - TData - > - >; - request?: SecondParameter; - }, -) => { - const { query: queryOptions, request: requestOptions } = options ?? {}; - - const queryKey = - queryOptions?.queryKey ?? - getListProjectMembersProjectsProjectIdMembersGetQueryKey(projectId); - - const queryFn: QueryFunction< - Awaited> - > = ({ signal }) => - listProjectMembersProjectsProjectIdMembersGet(projectId, { - signal, - ...requestOptions, - }); - - return { - queryKey, - queryFn, - enabled: !!projectId, - ...queryOptions, - } as UseQueryOptions< - Awaited>, - TError, - TData - > & { queryKey: DataTag }; -}; - -export type ListProjectMembersProjectsProjectIdMembersGetQueryResult = - NonNullable< - Awaited> - >; -export type ListProjectMembersProjectsProjectIdMembersGetQueryError = - HTTPValidationError; - -export function useListProjectMembersProjectsProjectIdMembersGet< - TData = Awaited< - ReturnType - >, - TError = HTTPValidationError, ->( - projectId: number, - options: { - query: Partial< - UseQueryOptions< - Awaited< - ReturnType - >, - TError, - TData - > - > & - Pick< - DefinedInitialDataOptions< - Awaited< - ReturnType - >, - TError, - Awaited< - ReturnType - > - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): DefinedUseQueryResult & { - queryKey: DataTag; -}; -export function useListProjectMembersProjectsProjectIdMembersGet< - TData = Awaited< - ReturnType - >, - TError = HTTPValidationError, ->( - projectId: number, - options?: { - query?: Partial< - UseQueryOptions< - Awaited< - ReturnType - >, - TError, - TData - > - > & - Pick< - UndefinedInitialDataOptions< - Awaited< - ReturnType - >, - TError, - Awaited< - ReturnType - > - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -export function useListProjectMembersProjectsProjectIdMembersGet< - TData = Awaited< - ReturnType - >, - TError = HTTPValidationError, ->( - projectId: number, - options?: { - query?: Partial< - UseQueryOptions< - Awaited< - ReturnType - >, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -/** - * @summary List Project Members - */ - -export function useListProjectMembersProjectsProjectIdMembersGet< - TData = Awaited< - ReturnType - >, - TError = HTTPValidationError, ->( - projectId: number, - options?: { - query?: Partial< - UseQueryOptions< - Awaited< - ReturnType - >, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -} { - const queryOptions = - getListProjectMembersProjectsProjectIdMembersGetQueryOptions( - projectId, - options, - ); - - const query = useQuery(queryOptions, queryClient) as UseQueryResult< - TData, - TError - > & { queryKey: DataTag }; - - return { ...query, queryKey: queryOptions.queryKey }; -} - -/** - * @summary Add Project Member - */ -export type addProjectMemberProjectsProjectIdMembersPostResponse200 = { - data: ProjectMember; - status: 200; -}; - -export type addProjectMemberProjectsProjectIdMembersPostResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type addProjectMemberProjectsProjectIdMembersPostResponseSuccess = - addProjectMemberProjectsProjectIdMembersPostResponse200 & { - headers: Headers; - }; -export type addProjectMemberProjectsProjectIdMembersPostResponseError = - addProjectMemberProjectsProjectIdMembersPostResponse422 & { - headers: Headers; - }; - -export type addProjectMemberProjectsProjectIdMembersPostResponse = - | addProjectMemberProjectsProjectIdMembersPostResponseSuccess - | addProjectMemberProjectsProjectIdMembersPostResponseError; - -export const getAddProjectMemberProjectsProjectIdMembersPostUrl = ( - projectId: number, -) => { - return `/projects/${projectId}/members`; -}; - -export const addProjectMemberProjectsProjectIdMembersPost = async ( - projectId: number, - projectMember: ProjectMember, - options?: RequestInit, -): Promise => { - return customFetch( - getAddProjectMemberProjectsProjectIdMembersPostUrl(projectId), - { - ...options, - method: "POST", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(projectMember), - }, - ); -}; - -export const getAddProjectMemberProjectsProjectIdMembersPostMutationOptions = < - TError = HTTPValidationError, - TContext = unknown, ->(options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { projectId: number; data: ProjectMember }, - TContext - >; - request?: SecondParameter; -}): UseMutationOptions< - Awaited>, - TError, - { projectId: number; data: ProjectMember }, - TContext -> => { - const mutationKey = ["addProjectMemberProjectsProjectIdMembersPost"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited>, - { projectId: number; data: ProjectMember } - > = (props) => { - const { projectId, data } = props ?? {}; - - return addProjectMemberProjectsProjectIdMembersPost( - projectId, - data, - requestOptions, - ); - }; - - return { mutationFn, ...mutationOptions }; -}; - -export type AddProjectMemberProjectsProjectIdMembersPostMutationResult = - NonNullable< - Awaited> - >; -export type AddProjectMemberProjectsProjectIdMembersPostMutationBody = - ProjectMember; -export type AddProjectMemberProjectsProjectIdMembersPostMutationError = - HTTPValidationError; - -/** - * @summary Add Project Member - */ -export const useAddProjectMemberProjectsProjectIdMembersPost = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { projectId: number; data: ProjectMember }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { projectId: number; data: ProjectMember }, - TContext -> => { - return useMutation( - getAddProjectMemberProjectsProjectIdMembersPostMutationOptions(options), - queryClient, - ); -}; -/** - * @summary Remove Project Member - */ -export type removeProjectMemberProjectsProjectIdMembersMemberIdDeleteResponse200 = - { - data: unknown; - status: 200; - }; - -export type removeProjectMemberProjectsProjectIdMembersMemberIdDeleteResponse422 = - { - data: HTTPValidationError; - status: 422; - }; - -export type removeProjectMemberProjectsProjectIdMembersMemberIdDeleteResponseSuccess = - removeProjectMemberProjectsProjectIdMembersMemberIdDeleteResponse200 & { - headers: Headers; - }; -export type removeProjectMemberProjectsProjectIdMembersMemberIdDeleteResponseError = - removeProjectMemberProjectsProjectIdMembersMemberIdDeleteResponse422 & { - headers: Headers; - }; - -export type removeProjectMemberProjectsProjectIdMembersMemberIdDeleteResponse = - | removeProjectMemberProjectsProjectIdMembersMemberIdDeleteResponseSuccess - | removeProjectMemberProjectsProjectIdMembersMemberIdDeleteResponseError; - -export const getRemoveProjectMemberProjectsProjectIdMembersMemberIdDeleteUrl = ( - projectId: number, - memberId: number, -) => { - return `/projects/${projectId}/members/${memberId}`; -}; - -export const removeProjectMemberProjectsProjectIdMembersMemberIdDelete = async ( - projectId: number, - memberId: number, - options?: RequestInit, -): Promise => { - return customFetch( - getRemoveProjectMemberProjectsProjectIdMembersMemberIdDeleteUrl( - projectId, - memberId, - ), - { - ...options, - method: "DELETE", - }, - ); -}; - -export const getRemoveProjectMemberProjectsProjectIdMembersMemberIdDeleteMutationOptions = - (options?: { - mutation?: UseMutationOptions< - Awaited< - ReturnType< - typeof removeProjectMemberProjectsProjectIdMembersMemberIdDelete - > - >, - TError, - { projectId: number; memberId: number }, - TContext - >; - request?: SecondParameter; - }): UseMutationOptions< - Awaited< - ReturnType< - typeof removeProjectMemberProjectsProjectIdMembersMemberIdDelete - > - >, - TError, - { projectId: number; memberId: number }, - TContext - > => { - const mutationKey = [ - "removeProjectMemberProjectsProjectIdMembersMemberIdDelete", - ]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited< - ReturnType< - typeof removeProjectMemberProjectsProjectIdMembersMemberIdDelete - > - >, - { projectId: number; memberId: number } - > = (props) => { - const { projectId, memberId } = props ?? {}; - - return removeProjectMemberProjectsProjectIdMembersMemberIdDelete( - projectId, - memberId, - requestOptions, - ); - }; - - return { mutationFn, ...mutationOptions }; - }; - -export type RemoveProjectMemberProjectsProjectIdMembersMemberIdDeleteMutationResult = - NonNullable< - Awaited< - ReturnType< - typeof removeProjectMemberProjectsProjectIdMembersMemberIdDelete - > - > - >; - -export type RemoveProjectMemberProjectsProjectIdMembersMemberIdDeleteMutationError = - HTTPValidationError; - -/** - * @summary Remove Project Member - */ -export const useRemoveProjectMemberProjectsProjectIdMembersMemberIdDelete = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited< - ReturnType< - typeof removeProjectMemberProjectsProjectIdMembersMemberIdDelete - > - >, - TError, - { projectId: number; memberId: number }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited< - ReturnType - >, - TError, - { projectId: number; memberId: number }, - TContext -> => { - return useMutation( - getRemoveProjectMemberProjectsProjectIdMembersMemberIdDeleteMutationOptions( - options, - ), - queryClient, - ); -}; -/** - * @summary Update Project Member - */ -export type updateProjectMemberProjectsProjectIdMembersMemberIdPatchResponse200 = - { - data: ProjectMember; - status: 200; - }; - -export type updateProjectMemberProjectsProjectIdMembersMemberIdPatchResponse422 = - { - data: HTTPValidationError; - status: 422; - }; - -export type updateProjectMemberProjectsProjectIdMembersMemberIdPatchResponseSuccess = - updateProjectMemberProjectsProjectIdMembersMemberIdPatchResponse200 & { - headers: Headers; - }; -export type updateProjectMemberProjectsProjectIdMembersMemberIdPatchResponseError = - updateProjectMemberProjectsProjectIdMembersMemberIdPatchResponse422 & { - headers: Headers; - }; - -export type updateProjectMemberProjectsProjectIdMembersMemberIdPatchResponse = - | updateProjectMemberProjectsProjectIdMembersMemberIdPatchResponseSuccess - | updateProjectMemberProjectsProjectIdMembersMemberIdPatchResponseError; - -export const getUpdateProjectMemberProjectsProjectIdMembersMemberIdPatchUrl = ( - projectId: number, - memberId: number, -) => { - return `/projects/${projectId}/members/${memberId}`; -}; - -export const updateProjectMemberProjectsProjectIdMembersMemberIdPatch = async ( - projectId: number, - memberId: number, - projectMember: ProjectMember, - options?: RequestInit, -): Promise => { - return customFetch( - getUpdateProjectMemberProjectsProjectIdMembersMemberIdPatchUrl( - projectId, - memberId, - ), - { - ...options, - method: "PATCH", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(projectMember), - }, - ); -}; - -export const getUpdateProjectMemberProjectsProjectIdMembersMemberIdPatchMutationOptions = - (options?: { - mutation?: UseMutationOptions< - Awaited< - ReturnType< - typeof updateProjectMemberProjectsProjectIdMembersMemberIdPatch - > - >, - TError, - { projectId: number; memberId: number; data: ProjectMember }, - TContext - >; - request?: SecondParameter; - }): UseMutationOptions< - Awaited< - ReturnType< - typeof updateProjectMemberProjectsProjectIdMembersMemberIdPatch - > - >, - TError, - { projectId: number; memberId: number; data: ProjectMember }, - TContext - > => { - const mutationKey = [ - "updateProjectMemberProjectsProjectIdMembersMemberIdPatch", - ]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited< - ReturnType< - typeof updateProjectMemberProjectsProjectIdMembersMemberIdPatch - > - >, - { projectId: number; memberId: number; data: ProjectMember } - > = (props) => { - const { projectId, memberId, data } = props ?? {}; - - return updateProjectMemberProjectsProjectIdMembersMemberIdPatch( - projectId, - memberId, - data, - requestOptions, - ); - }; - - return { mutationFn, ...mutationOptions }; - }; - -export type UpdateProjectMemberProjectsProjectIdMembersMemberIdPatchMutationResult = - NonNullable< - Awaited< - ReturnType< - typeof updateProjectMemberProjectsProjectIdMembersMemberIdPatch - > - > - >; -export type UpdateProjectMemberProjectsProjectIdMembersMemberIdPatchMutationBody = - ProjectMember; -export type UpdateProjectMemberProjectsProjectIdMembersMemberIdPatchMutationError = - HTTPValidationError; - -/** - * @summary Update Project Member - */ -export const useUpdateProjectMemberProjectsProjectIdMembersMemberIdPatch = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited< - ReturnType< - typeof updateProjectMemberProjectsProjectIdMembersMemberIdPatch - > - >, - TError, - { projectId: number; memberId: number; data: ProjectMember }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited< - ReturnType - >, - TError, - { projectId: number; memberId: number; data: ProjectMember }, - TContext -> => { - return useMutation( - getUpdateProjectMemberProjectsProjectIdMembersMemberIdPatchMutationOptions( - options, - ), - queryClient, - ); -}; diff --git a/frontend/src/api/generated/work/work.ts b/frontend/src/api/generated/work/work.ts deleted file mode 100644 index 64fb02c7..00000000 --- a/frontend/src/api/generated/work/work.ts +++ /dev/null @@ -1,1156 +0,0 @@ -/** - * Generated by orval v8.2.0 🍺 - * Do not edit manually. - * OpenClaw Agency API - * OpenAPI spec version: 0.3.0 - */ -import { useMutation, useQuery } from "@tanstack/react-query"; -import type { - DataTag, - DefinedInitialDataOptions, - DefinedUseQueryResult, - MutationFunction, - QueryClient, - QueryFunction, - QueryKey, - UndefinedInitialDataOptions, - UseMutationOptions, - UseMutationResult, - UseQueryOptions, - UseQueryResult, -} from "@tanstack/react-query"; - -import type { - HTTPValidationError, - ListTaskCommentsTaskCommentsGetParams, - ListTasksTasksGetParams, - Task, - TaskComment, - TaskCommentCreate, - TaskCreate, - TaskReviewDecision, - TaskUpdate, -} from ".././model"; - -import { customFetch } from "../../mutator"; - -type SecondParameter unknown> = Parameters[1]; - -/** - * @summary List Tasks - */ -export type listTasksTasksGetResponse200 = { - data: Task[]; - status: 200; -}; - -export type listTasksTasksGetResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type listTasksTasksGetResponseSuccess = listTasksTasksGetResponse200 & { - headers: Headers; -}; -export type listTasksTasksGetResponseError = listTasksTasksGetResponse422 & { - headers: Headers; -}; - -export type listTasksTasksGetResponse = - | listTasksTasksGetResponseSuccess - | listTasksTasksGetResponseError; - -export const getListTasksTasksGetUrl = (params?: ListTasksTasksGetParams) => { - const normalizedParams = new URLSearchParams(); - - Object.entries(params || {}).forEach(([key, value]) => { - if (value !== undefined) { - normalizedParams.append(key, value === null ? "null" : value.toString()); - } - }); - - const stringifiedParams = normalizedParams.toString(); - - return stringifiedParams.length > 0 - ? `/tasks?${stringifiedParams}` - : `/tasks`; -}; - -export const listTasksTasksGet = async ( - params?: ListTasksTasksGetParams, - options?: RequestInit, -): Promise => { - return customFetch( - getListTasksTasksGetUrl(params), - { - ...options, - method: "GET", - }, - ); -}; - -export const getListTasksTasksGetQueryKey = ( - params?: ListTasksTasksGetParams, -) => { - return [`/tasks`, ...(params ? [params] : [])] as const; -}; - -export const getListTasksTasksGetQueryOptions = < - TData = Awaited>, - TError = HTTPValidationError, ->( - params?: ListTasksTasksGetParams, - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, -) => { - const { query: queryOptions, request: requestOptions } = options ?? {}; - - const queryKey = - queryOptions?.queryKey ?? getListTasksTasksGetQueryKey(params); - - const queryFn: QueryFunction< - Awaited> - > = ({ signal }) => listTasksTasksGet(params, { signal, ...requestOptions }); - - return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< - Awaited>, - TError, - TData - > & { queryKey: DataTag }; -}; - -export type ListTasksTasksGetQueryResult = NonNullable< - Awaited> ->; -export type ListTasksTasksGetQueryError = HTTPValidationError; - -export function useListTasksTasksGet< - TData = Awaited>, - TError = HTTPValidationError, ->( - params: undefined | ListTasksTasksGetParams, - options: { - query: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - DefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): DefinedUseQueryResult & { - queryKey: DataTag; -}; -export function useListTasksTasksGet< - TData = Awaited>, - TError = HTTPValidationError, ->( - params?: ListTasksTasksGetParams, - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - UndefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -export function useListTasksTasksGet< - TData = Awaited>, - TError = HTTPValidationError, ->( - params?: ListTasksTasksGetParams, - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -/** - * @summary List Tasks - */ - -export function useListTasksTasksGet< - TData = Awaited>, - TError = HTTPValidationError, ->( - params?: ListTasksTasksGetParams, - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -} { - const queryOptions = getListTasksTasksGetQueryOptions(params, options); - - const query = useQuery(queryOptions, queryClient) as UseQueryResult< - TData, - TError - > & { queryKey: DataTag }; - - return { ...query, queryKey: queryOptions.queryKey }; -} - -/** - * @summary Create Task - */ -export type createTaskTasksPostResponse200 = { - data: Task; - status: 200; -}; - -export type createTaskTasksPostResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type createTaskTasksPostResponseSuccess = - createTaskTasksPostResponse200 & { - headers: Headers; - }; -export type createTaskTasksPostResponseError = - createTaskTasksPostResponse422 & { - headers: Headers; - }; - -export type createTaskTasksPostResponse = - | createTaskTasksPostResponseSuccess - | createTaskTasksPostResponseError; - -export const getCreateTaskTasksPostUrl = () => { - return `/tasks`; -}; - -export const createTaskTasksPost = async ( - taskCreate: TaskCreate, - options?: RequestInit, -): Promise => { - return customFetch(getCreateTaskTasksPostUrl(), { - ...options, - method: "POST", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(taskCreate), - }); -}; - -export const getCreateTaskTasksPostMutationOptions = < - TError = HTTPValidationError, - TContext = unknown, ->(options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { data: TaskCreate }, - TContext - >; - request?: SecondParameter; -}): UseMutationOptions< - Awaited>, - TError, - { data: TaskCreate }, - TContext -> => { - const mutationKey = ["createTaskTasksPost"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited>, - { data: TaskCreate } - > = (props) => { - const { data } = props ?? {}; - - return createTaskTasksPost(data, requestOptions); - }; - - return { mutationFn, ...mutationOptions }; -}; - -export type CreateTaskTasksPostMutationResult = NonNullable< - Awaited> ->; -export type CreateTaskTasksPostMutationBody = TaskCreate; -export type CreateTaskTasksPostMutationError = HTTPValidationError; - -/** - * @summary Create Task - */ -export const useCreateTaskTasksPost = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { data: TaskCreate }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { data: TaskCreate }, - TContext -> => { - return useMutation( - getCreateTaskTasksPostMutationOptions(options), - queryClient, - ); -}; -/** - * @summary Dispatch Task - */ -export type dispatchTaskTasksTaskIdDispatchPostResponse200 = { - data: unknown; - status: 200; -}; - -export type dispatchTaskTasksTaskIdDispatchPostResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type dispatchTaskTasksTaskIdDispatchPostResponseSuccess = - dispatchTaskTasksTaskIdDispatchPostResponse200 & { - headers: Headers; - }; -export type dispatchTaskTasksTaskIdDispatchPostResponseError = - dispatchTaskTasksTaskIdDispatchPostResponse422 & { - headers: Headers; - }; - -export type dispatchTaskTasksTaskIdDispatchPostResponse = - | dispatchTaskTasksTaskIdDispatchPostResponseSuccess - | dispatchTaskTasksTaskIdDispatchPostResponseError; - -export const getDispatchTaskTasksTaskIdDispatchPostUrl = (taskId: number) => { - return `/tasks/${taskId}/dispatch`; -}; - -export const dispatchTaskTasksTaskIdDispatchPost = async ( - taskId: number, - options?: RequestInit, -): Promise => { - return customFetch( - getDispatchTaskTasksTaskIdDispatchPostUrl(taskId), - { - ...options, - method: "POST", - }, - ); -}; - -export const getDispatchTaskTasksTaskIdDispatchPostMutationOptions = < - TError = HTTPValidationError, - TContext = unknown, ->(options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { taskId: number }, - TContext - >; - request?: SecondParameter; -}): UseMutationOptions< - Awaited>, - TError, - { taskId: number }, - TContext -> => { - const mutationKey = ["dispatchTaskTasksTaskIdDispatchPost"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited>, - { taskId: number } - > = (props) => { - const { taskId } = props ?? {}; - - return dispatchTaskTasksTaskIdDispatchPost(taskId, requestOptions); - }; - - return { mutationFn, ...mutationOptions }; -}; - -export type DispatchTaskTasksTaskIdDispatchPostMutationResult = NonNullable< - Awaited> ->; - -export type DispatchTaskTasksTaskIdDispatchPostMutationError = - HTTPValidationError; - -/** - * @summary Dispatch Task - */ -export const useDispatchTaskTasksTaskIdDispatchPost = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { taskId: number }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { taskId: number }, - TContext -> => { - return useMutation( - getDispatchTaskTasksTaskIdDispatchPostMutationOptions(options), - queryClient, - ); -}; -/** - * Reviewer approves or requests changes. - -- Approve => status=done -- Changes => status=in_progress - -Always writes a TaskComment by the reviewer for audit. - * @summary Review Task - */ -export type reviewTaskTasksTaskIdReviewPostResponse200 = { - data: Task; - status: 200; -}; - -export type reviewTaskTasksTaskIdReviewPostResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type reviewTaskTasksTaskIdReviewPostResponseSuccess = - reviewTaskTasksTaskIdReviewPostResponse200 & { - headers: Headers; - }; -export type reviewTaskTasksTaskIdReviewPostResponseError = - reviewTaskTasksTaskIdReviewPostResponse422 & { - headers: Headers; - }; - -export type reviewTaskTasksTaskIdReviewPostResponse = - | reviewTaskTasksTaskIdReviewPostResponseSuccess - | reviewTaskTasksTaskIdReviewPostResponseError; - -export const getReviewTaskTasksTaskIdReviewPostUrl = (taskId: number) => { - return `/tasks/${taskId}/review`; -}; - -export const reviewTaskTasksTaskIdReviewPost = async ( - taskId: number, - taskReviewDecision: TaskReviewDecision, - options?: RequestInit, -): Promise => { - return customFetch( - getReviewTaskTasksTaskIdReviewPostUrl(taskId), - { - ...options, - method: "POST", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(taskReviewDecision), - }, - ); -}; - -export const getReviewTaskTasksTaskIdReviewPostMutationOptions = < - TError = HTTPValidationError, - TContext = unknown, ->(options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { taskId: number; data: TaskReviewDecision }, - TContext - >; - request?: SecondParameter; -}): UseMutationOptions< - Awaited>, - TError, - { taskId: number; data: TaskReviewDecision }, - TContext -> => { - const mutationKey = ["reviewTaskTasksTaskIdReviewPost"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited>, - { taskId: number; data: TaskReviewDecision } - > = (props) => { - const { taskId, data } = props ?? {}; - - return reviewTaskTasksTaskIdReviewPost(taskId, data, requestOptions); - }; - - return { mutationFn, ...mutationOptions }; -}; - -export type ReviewTaskTasksTaskIdReviewPostMutationResult = NonNullable< - Awaited> ->; -export type ReviewTaskTasksTaskIdReviewPostMutationBody = TaskReviewDecision; -export type ReviewTaskTasksTaskIdReviewPostMutationError = HTTPValidationError; - -/** - * @summary Review Task - */ -export const useReviewTaskTasksTaskIdReviewPost = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { taskId: number; data: TaskReviewDecision }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { taskId: number; data: TaskReviewDecision }, - TContext -> => { - return useMutation( - getReviewTaskTasksTaskIdReviewPostMutationOptions(options), - queryClient, - ); -}; -/** - * @summary Update Task - */ -export type updateTaskTasksTaskIdPatchResponse200 = { - data: Task; - status: 200; -}; - -export type updateTaskTasksTaskIdPatchResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type updateTaskTasksTaskIdPatchResponseSuccess = - updateTaskTasksTaskIdPatchResponse200 & { - headers: Headers; - }; -export type updateTaskTasksTaskIdPatchResponseError = - updateTaskTasksTaskIdPatchResponse422 & { - headers: Headers; - }; - -export type updateTaskTasksTaskIdPatchResponse = - | updateTaskTasksTaskIdPatchResponseSuccess - | updateTaskTasksTaskIdPatchResponseError; - -export const getUpdateTaskTasksTaskIdPatchUrl = (taskId: number) => { - return `/tasks/${taskId}`; -}; - -export const updateTaskTasksTaskIdPatch = async ( - taskId: number, - taskUpdate: TaskUpdate, - options?: RequestInit, -): Promise => { - return customFetch( - getUpdateTaskTasksTaskIdPatchUrl(taskId), - { - ...options, - method: "PATCH", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(taskUpdate), - }, - ); -}; - -export const getUpdateTaskTasksTaskIdPatchMutationOptions = < - TError = HTTPValidationError, - TContext = unknown, ->(options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { taskId: number; data: TaskUpdate }, - TContext - >; - request?: SecondParameter; -}): UseMutationOptions< - Awaited>, - TError, - { taskId: number; data: TaskUpdate }, - TContext -> => { - const mutationKey = ["updateTaskTasksTaskIdPatch"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited>, - { taskId: number; data: TaskUpdate } - > = (props) => { - const { taskId, data } = props ?? {}; - - return updateTaskTasksTaskIdPatch(taskId, data, requestOptions); - }; - - return { mutationFn, ...mutationOptions }; -}; - -export type UpdateTaskTasksTaskIdPatchMutationResult = NonNullable< - Awaited> ->; -export type UpdateTaskTasksTaskIdPatchMutationBody = TaskUpdate; -export type UpdateTaskTasksTaskIdPatchMutationError = HTTPValidationError; - -/** - * @summary Update Task - */ -export const useUpdateTaskTasksTaskIdPatch = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { taskId: number; data: TaskUpdate }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { taskId: number; data: TaskUpdate }, - TContext -> => { - return useMutation( - getUpdateTaskTasksTaskIdPatchMutationOptions(options), - queryClient, - ); -}; -/** - * @summary Delete Task - */ -export type deleteTaskTasksTaskIdDeleteResponse200 = { - data: unknown; - status: 200; -}; - -export type deleteTaskTasksTaskIdDeleteResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type deleteTaskTasksTaskIdDeleteResponseSuccess = - deleteTaskTasksTaskIdDeleteResponse200 & { - headers: Headers; - }; -export type deleteTaskTasksTaskIdDeleteResponseError = - deleteTaskTasksTaskIdDeleteResponse422 & { - headers: Headers; - }; - -export type deleteTaskTasksTaskIdDeleteResponse = - | deleteTaskTasksTaskIdDeleteResponseSuccess - | deleteTaskTasksTaskIdDeleteResponseError; - -export const getDeleteTaskTasksTaskIdDeleteUrl = (taskId: number) => { - return `/tasks/${taskId}`; -}; - -export const deleteTaskTasksTaskIdDelete = async ( - taskId: number, - options?: RequestInit, -): Promise => { - return customFetch( - getDeleteTaskTasksTaskIdDeleteUrl(taskId), - { - ...options, - method: "DELETE", - }, - ); -}; - -export const getDeleteTaskTasksTaskIdDeleteMutationOptions = < - TError = HTTPValidationError, - TContext = unknown, ->(options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { taskId: number }, - TContext - >; - request?: SecondParameter; -}): UseMutationOptions< - Awaited>, - TError, - { taskId: number }, - TContext -> => { - const mutationKey = ["deleteTaskTasksTaskIdDelete"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited>, - { taskId: number } - > = (props) => { - const { taskId } = props ?? {}; - - return deleteTaskTasksTaskIdDelete(taskId, requestOptions); - }; - - return { mutationFn, ...mutationOptions }; -}; - -export type DeleteTaskTasksTaskIdDeleteMutationResult = NonNullable< - Awaited> ->; - -export type DeleteTaskTasksTaskIdDeleteMutationError = HTTPValidationError; - -/** - * @summary Delete Task - */ -export const useDeleteTaskTasksTaskIdDelete = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { taskId: number }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { taskId: number }, - TContext -> => { - return useMutation( - getDeleteTaskTasksTaskIdDeleteMutationOptions(options), - queryClient, - ); -}; -/** - * @summary List Task Comments - */ -export type listTaskCommentsTaskCommentsGetResponse200 = { - data: TaskComment[]; - status: 200; -}; - -export type listTaskCommentsTaskCommentsGetResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type listTaskCommentsTaskCommentsGetResponseSuccess = - listTaskCommentsTaskCommentsGetResponse200 & { - headers: Headers; - }; -export type listTaskCommentsTaskCommentsGetResponseError = - listTaskCommentsTaskCommentsGetResponse422 & { - headers: Headers; - }; - -export type listTaskCommentsTaskCommentsGetResponse = - | listTaskCommentsTaskCommentsGetResponseSuccess - | listTaskCommentsTaskCommentsGetResponseError; - -export const getListTaskCommentsTaskCommentsGetUrl = ( - params: ListTaskCommentsTaskCommentsGetParams, -) => { - const normalizedParams = new URLSearchParams(); - - Object.entries(params || {}).forEach(([key, value]) => { - if (value !== undefined) { - normalizedParams.append(key, value === null ? "null" : value.toString()); - } - }); - - const stringifiedParams = normalizedParams.toString(); - - return stringifiedParams.length > 0 - ? `/task-comments?${stringifiedParams}` - : `/task-comments`; -}; - -export const listTaskCommentsTaskCommentsGet = async ( - params: ListTaskCommentsTaskCommentsGetParams, - options?: RequestInit, -): Promise => { - return customFetch( - getListTaskCommentsTaskCommentsGetUrl(params), - { - ...options, - method: "GET", - }, - ); -}; - -export const getListTaskCommentsTaskCommentsGetQueryKey = ( - params?: ListTaskCommentsTaskCommentsGetParams, -) => { - return [`/task-comments`, ...(params ? [params] : [])] as const; -}; - -export const getListTaskCommentsTaskCommentsGetQueryOptions = < - TData = Awaited>, - TError = HTTPValidationError, ->( - params: ListTaskCommentsTaskCommentsGetParams, - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, -) => { - const { query: queryOptions, request: requestOptions } = options ?? {}; - - const queryKey = - queryOptions?.queryKey ?? - getListTaskCommentsTaskCommentsGetQueryKey(params); - - const queryFn: QueryFunction< - Awaited> - > = ({ signal }) => - listTaskCommentsTaskCommentsGet(params, { signal, ...requestOptions }); - - return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< - Awaited>, - TError, - TData - > & { queryKey: DataTag }; -}; - -export type ListTaskCommentsTaskCommentsGetQueryResult = NonNullable< - Awaited> ->; -export type ListTaskCommentsTaskCommentsGetQueryError = HTTPValidationError; - -export function useListTaskCommentsTaskCommentsGet< - TData = Awaited>, - TError = HTTPValidationError, ->( - params: ListTaskCommentsTaskCommentsGetParams, - options: { - query: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - DefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): DefinedUseQueryResult & { - queryKey: DataTag; -}; -export function useListTaskCommentsTaskCommentsGet< - TData = Awaited>, - TError = HTTPValidationError, ->( - params: ListTaskCommentsTaskCommentsGetParams, - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - > & - Pick< - UndefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - >, - "initialData" - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -export function useListTaskCommentsTaskCommentsGet< - TData = Awaited>, - TError = HTTPValidationError, ->( - params: ListTaskCommentsTaskCommentsGetParams, - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -}; -/** - * @summary List Task Comments - */ - -export function useListTaskCommentsTaskCommentsGet< - TData = Awaited>, - TError = HTTPValidationError, ->( - params: ListTaskCommentsTaskCommentsGetParams, - options?: { - query?: Partial< - UseQueryOptions< - Awaited>, - TError, - TData - > - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseQueryResult & { - queryKey: DataTag; -} { - const queryOptions = getListTaskCommentsTaskCommentsGetQueryOptions( - params, - options, - ); - - const query = useQuery(queryOptions, queryClient) as UseQueryResult< - TData, - TError - > & { queryKey: DataTag }; - - return { ...query, queryKey: queryOptions.queryKey }; -} - -/** - * @summary Create Task Comment - */ -export type createTaskCommentTaskCommentsPostResponse200 = { - data: TaskComment; - status: 200; -}; - -export type createTaskCommentTaskCommentsPostResponse422 = { - data: HTTPValidationError; - status: 422; -}; - -export type createTaskCommentTaskCommentsPostResponseSuccess = - createTaskCommentTaskCommentsPostResponse200 & { - headers: Headers; - }; -export type createTaskCommentTaskCommentsPostResponseError = - createTaskCommentTaskCommentsPostResponse422 & { - headers: Headers; - }; - -export type createTaskCommentTaskCommentsPostResponse = - | createTaskCommentTaskCommentsPostResponseSuccess - | createTaskCommentTaskCommentsPostResponseError; - -export const getCreateTaskCommentTaskCommentsPostUrl = () => { - return `/task-comments`; -}; - -export const createTaskCommentTaskCommentsPost = async ( - taskCommentCreate: TaskCommentCreate, - options?: RequestInit, -): Promise => { - return customFetch( - getCreateTaskCommentTaskCommentsPostUrl(), - { - ...options, - method: "POST", - headers: { "Content-Type": "application/json", ...options?.headers }, - body: JSON.stringify(taskCommentCreate), - }, - ); -}; - -export const getCreateTaskCommentTaskCommentsPostMutationOptions = < - TError = HTTPValidationError, - TContext = unknown, ->(options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { data: TaskCommentCreate }, - TContext - >; - request?: SecondParameter; -}): UseMutationOptions< - Awaited>, - TError, - { data: TaskCommentCreate }, - TContext -> => { - const mutationKey = ["createTaskCommentTaskCommentsPost"]; - const { mutation: mutationOptions, request: requestOptions } = options - ? options.mutation && - "mutationKey" in options.mutation && - options.mutation.mutationKey - ? options - : { ...options, mutation: { ...options.mutation, mutationKey } } - : { mutation: { mutationKey }, request: undefined }; - - const mutationFn: MutationFunction< - Awaited>, - { data: TaskCommentCreate } - > = (props) => { - const { data } = props ?? {}; - - return createTaskCommentTaskCommentsPost(data, requestOptions); - }; - - return { mutationFn, ...mutationOptions }; -}; - -export type CreateTaskCommentTaskCommentsPostMutationResult = NonNullable< - Awaited> ->; -export type CreateTaskCommentTaskCommentsPostMutationBody = TaskCommentCreate; -export type CreateTaskCommentTaskCommentsPostMutationError = - HTTPValidationError; - -/** - * @summary Create Task Comment - */ -export const useCreateTaskCommentTaskCommentsPost = < - TError = HTTPValidationError, - TContext = unknown, ->( - options?: { - mutation?: UseMutationOptions< - Awaited>, - TError, - { data: TaskCommentCreate }, - TContext - >; - request?: SecondParameter; - }, - queryClient?: QueryClient, -): UseMutationResult< - Awaited>, - TError, - { data: TaskCommentCreate }, - TContext -> => { - return useMutation( - getCreateTaskCommentTaskCommentsPostMutationOptions(options), - queryClient, - ); -}; diff --git a/frontend/src/api/mutator.ts b/frontend/src/api/mutator.ts index 24cf9d13..70648a93 100644 --- a/frontend/src/api/mutator.ts +++ b/frontend/src/api/mutator.ts @@ -1,44 +1,23 @@ -function getActorId(): string | undefined { - if (typeof window !== "undefined") { - const stored = window.localStorage.getItem("actor_employee_id"); - if (stored) return stored; - const env = process.env.NEXT_PUBLIC_ACTOR_EMPLOYEE_ID; - if (env) { - window.localStorage.setItem("actor_employee_id", env); - return env; - } - return undefined; - } - return process.env.NEXT_PUBLIC_ACTOR_EMPLOYEE_ID; -} - -/** - * Orval-generated client expects the fetcher to return an object like: - * { data: , status: , headers: Headers } - */ -export async function customFetch( +export const customFetch = async ( url: string, - options: RequestInit, -): Promise { - const base = process.env.NEXT_PUBLIC_API_URL; - if (!base) throw new Error("NEXT_PUBLIC_API_URL is not set"); - - const res = await fetch(`${base}${url}`, { + options: RequestInit +): Promise => { + const baseUrl = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000"; + const response = await fetch(`${baseUrl}${url}`, { ...options, headers: { "Content-Type": "application/json", - ...(getActorId() ? { "X-Actor-Employee-Id": String(getActorId()) } : {}), ...(options.headers ?? {}), }, }); - const text = await res.text().catch(() => ""); - - if (!res.ok) { - throw new Error(`${res.status} ${res.statusText}${text ? `: ${text}` : ""}`); + if (!response.ok) { + throw new Error("Request failed"); } - const json = text ? JSON.parse(text) : null; - // Match the types generated by Orval (status + headers + data) - return ({ data: json, status: res.status, headers: res.headers } as unknown) as T; -} + if (response.status === 204) { + return undefined as T; + } + + return (await response.json()) as T; +}; diff --git a/frontend/src/app/_components/Shell.module.css b/frontend/src/app/_components/Shell.module.css deleted file mode 100644 index 4192c023..00000000 --- a/frontend/src/app/_components/Shell.module.css +++ /dev/null @@ -1,24 +0,0 @@ -.shell{min-height:100vh;display:grid;grid-template-columns:260px 1fr;background:var(--mc-bg)} -.sidebar{border-right:1px solid var(--mc-border);padding:20px 16px;position:sticky;top:0;height:100vh;display:flex;flex-direction:column;gap:16px;background:linear-gradient(180deg,var(--mc-surface) 0%, color-mix(in oklab,var(--mc-surface), var(--mc-bg) 40%) 100%)} -.brand{display:flex;flex-direction:column;gap:6px} -.brandTitle{font-family:var(--mc-font-serif);font-size:18px;letter-spacing:-0.2px} -.brandSub{font-size:12px;color:var(--mc-muted)} -.nav{display:flex;flex-direction:column;gap:6px} -.nav a{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:12px;color:var(--mc-text);text-decoration:none;border:1px solid transparent} -.nav a:hover{background:color-mix(in oklab,var(--mc-accent), transparent 92%);border-color:color-mix(in oklab,var(--mc-accent), transparent 80%)} -.active{background:color-mix(in oklab,var(--mc-accent), transparent 88%);border-color:color-mix(in oklab,var(--mc-accent), transparent 70%)} -.main{padding:28px 28px 48px} -.topbar{display:flex;justify-content:space-between;align-items:flex-start;gap:18px;margin-bottom:18px} -.h1{font-family:var(--mc-font-serif);font-size:30px;line-height:1.1;letter-spacing:-0.6px;margin:0} -.p{margin:8px 0 0;color:var(--mc-muted);max-width:72ch} -.btn{border:1px solid var(--mc-border);background:var(--mc-surface);padding:10px 12px;border-radius:12px;cursor:pointer} -.btnPrimary{border-color:color-mix(in oklab,var(--mc-accent), black 10%);background:var(--mc-accent);color:white} -.grid2{display:grid;grid-template-columns:1.4fr 1fr;gap:16px} -.card{background:var(--mc-surface);border:1px solid var(--mc-border);border-radius:16px;padding:14px} -.cardTitle{margin:0 0 10px;font-size:13px;color:var(--mc-muted);letter-spacing:0.06em;text-transform:uppercase} -.list{display:flex;flex-direction:column;gap:10px} -.item{border:1px solid var(--mc-border);border-radius:14px;padding:12px;background:color-mix(in oklab,var(--mc-surface), white 20%)} -.mono{font-family:var(--mc-font-mono);font-size:12px;color:var(--mc-muted)} -.badge{display:inline-flex;align-items:center;padding:4px 8px;border-radius:999px;font-size:12px;border:1px solid var(--mc-border);background:color-mix(in oklab,var(--mc-bg), var(--mc-surface) 40%)} -.kbd{font-family:var(--mc-font-mono);font-size:12px;background:color-mix(in oklab,var(--mc-bg), var(--mc-surface) 40%);border:1px solid var(--mc-border);border-bottom-width:2px;padding:2px 6px;border-radius:8px} -@media (max-width: 980px){.shell{grid-template-columns:1fr}.sidebar{position:relative;height:auto}.grid2{grid-template-columns:1fr}.main{padding:18px}} diff --git a/frontend/src/app/_components/Shell.tsx b/frontend/src/app/_components/Shell.tsx deleted file mode 100644 index 55b6a917..00000000 --- a/frontend/src/app/_components/Shell.tsx +++ /dev/null @@ -1,72 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import { useState } from "react"; -import styles from "./Shell.module.css"; - -const NAV = [ - { href: "/", label: "Mission Control" }, - { href: "/projects", label: "Projects" }, - { href: "/kanban", label: "Kanban" }, - { href: "/departments", label: "Departments" }, - { href: "/teams", label: "Teams" }, - { href: "/people", label: "People" }, -]; - -export function Shell({ children }: { children: React.ReactNode }) { - const path = usePathname(); - const [actorId, setActorId] = useState(() => { - if (typeof window === "undefined") return ""; - try { - return window.localStorage.getItem("actor_employee_id") ?? ""; - } catch { - return ""; - } - }); - return ( -
- -
{children}
-
- ); -} diff --git a/frontend/src/app/agents/page.tsx b/frontend/src/app/agents/page.tsx new file mode 100644 index 00000000..3e55771d --- /dev/null +++ b/frontend/src/app/agents/page.tsx @@ -0,0 +1,627 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import { useRouter } from "next/navigation"; + +import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; + +import { StatusPill } from "@/components/atoms/StatusPill"; +import { DashboardSidebar } from "@/components/organisms/DashboardSidebar"; +import { DashboardShell } from "@/components/templates/DashboardShell"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; + +type Agent = { + id: string; + name: string; + status: string; + last_seen_at: string; +}; + +type ActivityEvent = { + id: string; + event_type: string; + message?: string | null; + created_at: string; +}; + +type GatewayStatus = { + connected: boolean; + gateway_url: string; + sessions_count?: number; + sessions?: Record[]; + error?: string; +}; + +const apiBase = + process.env.NEXT_PUBLIC_API_URL?.replace(/\/+$/, "") || + "http://localhost:8000"; + +const statusOptions = [ + { value: "online", label: "Online" }, + { value: "busy", label: "Busy" }, + { value: "offline", label: "Offline" }, +]; + +const formatTimestamp = (value: string) => { + const date = new Date(value); + if (Number.isNaN(date.getTime())) return "—"; + return date.toLocaleString(undefined, { + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }); +}; + +const formatRelative = (value: string) => { + const date = new Date(value); + if (Number.isNaN(date.getTime())) return "—"; + const diff = Date.now() - date.getTime(); + const minutes = Math.round(diff / 60000); + if (minutes < 1) return "Just now"; + if (minutes < 60) return `${minutes}m ago`; + const hours = Math.round(minutes / 60); + if (hours < 24) return `${hours}h ago`; + const days = Math.round(hours / 24); + return `${days}d ago`; +}; + +const getSessionKey = ( + session: Record, + index: number +) => { + const key = session.key; + if (typeof key === "string" && key.length > 0) { + return key; + } + const sessionId = session.sessionId; + if (typeof sessionId === "string" && sessionId.length > 0) { + return sessionId; + } + return `session-${index}`; +}; + +export default function AgentsPage() { + const { getToken, isSignedIn } = useAuth(); + const router = useRouter(); + const [agents, setAgents] = useState([]); + const [events, setEvents] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [gatewayStatus, setGatewayStatus] = useState(null); + const [gatewaySessions, setGatewaySessions] = useState< + Record[] + >([]); + const [gatewayError, setGatewayError] = useState(null); + const [selectedSession, setSelectedSession] = useState< + Record | null + >(null); + const [sessionHistory, setSessionHistory] = useState([]); + const [message, setMessage] = useState(""); + const [isSending, setIsSending] = useState(false); + + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [name, setName] = useState(""); + const [status, setStatus] = useState("online"); + const [createError, setCreateError] = useState(null); + const [isCreating, setIsCreating] = useState(false); + + const sortedAgents = useMemo( + () => [...agents].sort((a, b) => a.name.localeCompare(b.name)), + [agents], + ); + + const loadData = async () => { + if (!isSignedIn) return; + setIsLoading(true); + setError(null); + try { + const token = await getToken(); + const [agentsResponse, activityResponse] = await Promise.all([ + fetch(`${apiBase}/api/v1/agents`, { + headers: { Authorization: token ? `Bearer ${token}` : "" }, + }), + fetch(`${apiBase}/api/v1/activity`, { + headers: { Authorization: token ? `Bearer ${token}` : "" }, + }), + ]); + if (!agentsResponse.ok || !activityResponse.ok) { + throw new Error("Unable to load operational data."); + } + const agentsData = (await agentsResponse.json()) as Agent[]; + const eventsData = (await activityResponse.json()) as ActivityEvent[]; + setAgents(agentsData); + setEvents(eventsData); + } catch (err) { + setError(err instanceof Error ? err.message : "Something went wrong."); + } finally { + setIsLoading(false); + } + }; + + const loadGateway = async () => { + if (!isSignedIn) return; + setGatewayError(null); + try { + const token = await getToken(); + const response = await fetch(`${apiBase}/api/v1/gateway/status`, { + headers: { Authorization: token ? `Bearer ${token}` : "" }, + }); + if (!response.ok) { + throw new Error("Unable to load gateway status."); + } + const statusData = (await response.json()) as GatewayStatus; + setGatewayStatus(statusData); + setGatewaySessions(statusData.sessions ?? []); + } catch (err) { + setGatewayError(err instanceof Error ? err.message : "Something went wrong."); + } + }; + + const loadSessionHistory = async (sessionId: string) => { + if (!isSignedIn) return; + try { + const token = await getToken(); + const response = await fetch( + `${apiBase}/api/v1/gateway/sessions/${sessionId}/history`, + { + headers: { Authorization: token ? `Bearer ${token}` : "" }, + } + ); + if (!response.ok) { + throw new Error("Unable to load session history."); + } + const data = (await response.json()) as { history?: unknown[] }; + setSessionHistory(data.history ?? []); + } catch (err) { + setGatewayError(err instanceof Error ? err.message : "Something went wrong."); + } + }; + + useEffect(() => { + loadData(); + loadGateway(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isSignedIn]); + + const resetForm = () => { + setName(""); + setStatus("online"); + setCreateError(null); + }; + + const handleCreate = async () => { + if (!isSignedIn) return; + const trimmed = name.trim(); + if (!trimmed) { + setCreateError("Agent name is required."); + return; + } + setIsCreating(true); + setCreateError(null); + try { + const token = await getToken(); + const response = await fetch(`${apiBase}/api/v1/agents`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: token ? `Bearer ${token}` : "", + }, + body: JSON.stringify({ name: trimmed, status }), + }); + if (!response.ok) { + throw new Error("Unable to create agent."); + } + const created = (await response.json()) as Agent; + setAgents((prev) => [created, ...prev]); + setIsDialogOpen(false); + resetForm(); + } catch (err) { + setCreateError(err instanceof Error ? err.message : "Something went wrong."); + } finally { + setIsCreating(false); + } + }; + + const handleSendMessage = async () => { + if (!isSignedIn || !selectedSession) return; + const content = message.trim(); + if (!content) return; + setIsSending(true); + setGatewayError(null); + try { + const token = await getToken(); + const sessionId = selectedSession.key as string | undefined; + if (!sessionId) { + throw new Error("Missing session id."); + } + const response = await fetch( + `${apiBase}/api/v1/gateway/sessions/${sessionId}/message`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: token ? `Bearer ${token}` : "", + }, + body: JSON.stringify({ content }), + } + ); + if (!response.ok) { + throw new Error("Unable to send message."); + } + setMessage(""); + loadSessionHistory(sessionId); + } catch (err) { + setGatewayError(err instanceof Error ? err.message : "Something went wrong."); + } finally { + setIsSending(false); + } + }; + + return ( + + +
+

+ Sign in to view operational status. +

+ + + +
+
+ + +
+
+
+

+ Operations +

+

+ Agents +

+

+ Live status and heartbeat activity across all agents. +

+
+
+ + +
+
+ + {error ? ( +
+ {error} +
+ ) : null} + +
+
+
+

+ Agents +

+

+ {sortedAgents.length} total +

+
+
+ {sortedAgents.length === 0 && !isLoading ? ( +
+ No agents yet. Add one or wait for a heartbeat. +
+ ) : ( + sortedAgents.map((agent) => ( +
+
+

+ {agent.name} +

+

+ Last seen {formatRelative(agent.last_seen_at)} +

+
+
+ + +
+
+ )) + )} +
+
+ +
+ +
+ + Activity + Gateway + +
+ +
+

+ Activity feed +

+

+ {events.length} events +

+
+
+ {events.length === 0 && !isLoading ? ( +
+ No activity yet. +
+ ) : ( + events.map((event) => ( +
+

+ {event.message ?? event.event_type} +

+

+ {formatTimestamp(event.created_at)} +

+
+ )) + )} +
+
+ +
+

+ OpenClaw Gateway +

+ +
+
+
+
+

+ {gatewayStatus?.connected ? "Connected" : "Not connected"} +

+ +
+

+ {gatewayStatus?.gateway_url ?? "Gateway URL not set"} +

+ {gatewayStatus?.error ? ( +

+ {gatewayStatus.error} +

+ ) : null} +
+ +
+
+ Sessions + {gatewaySessions.length} +
+
+ {gatewaySessions.length === 0 ? ( +
+ No sessions found. +
+ ) : ( + gatewaySessions.map((session, index) => { + const sessionId = session.key as string | undefined; + const display = + (session.displayName as string | undefined) ?? + (session.label as string | undefined) ?? + sessionId ?? + "Session"; + return ( + + ); + }) + )} +
+
+ + {selectedSession ? ( +
+
+

+ Session details +

+

+ {selectedSession.displayName ?? + selectedSession.label ?? + selectedSession.key ?? + "Session"} +

+
+
+ {sessionHistory.length === 0 ? ( +

No history loaded.

+ ) : ( + sessionHistory.map((item, index) => ( +
+                                {JSON.stringify(item, null, 2)}
+                              
+ )) + )} +
+
+ + setMessage(event.target.value)} + placeholder="Type a message to the session" + className="h-10 rounded-lg border-2 border-gray-200 bg-white" + /> + +
+
+ ) : null} + + {gatewayError ? ( +
+ {gatewayError} +
+ ) : null} +
+
+
+
+
+
+
+ + { + setIsDialogOpen(nextOpen); + if (!nextOpen) { + resetForm(); + } + }} + > + + + New agent + + Add a manual agent entry for tracking and monitoring. + + +
+
+ + setName(event.target.value)} + placeholder="e.g. Deployment bot" + className="h-11 rounded-lg border-2 border-gray-200 bg-white" + /> +
+
+ + +
+ {createError ? ( +
+ {createError} +
+ ) : null} +
+ + + + +
+
+
+ ); +} diff --git a/frontend/src/app/boards/[boardId]/page.tsx b/frontend/src/app/boards/[boardId]/page.tsx new file mode 100644 index 00000000..87df572f --- /dev/null +++ b/frontend/src/app/boards/[boardId]/page.tsx @@ -0,0 +1,310 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import { useParams, useRouter } from "next/navigation"; + +import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; + +import { DashboardSidebar } from "@/components/organisms/DashboardSidebar"; +import { TaskBoard } from "@/components/organisms/TaskBoard"; +import { DashboardShell } from "@/components/templates/DashboardShell"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Textarea } from "@/components/ui/textarea"; + +type Board = { + id: string; + name: string; + slug: string; +}; + +type Task = { + id: string; + title: string; + description?: string | null; + status: string; + priority: string; + due_at?: string | null; +}; + +const apiBase = + process.env.NEXT_PUBLIC_API_URL?.replace(/\/+$/, "") || + "http://localhost:8000"; + +const priorities = [ + { value: "low", label: "Low" }, + { value: "medium", label: "Medium" }, + { value: "high", label: "High" }, +]; + +export default function BoardDetailPage() { + const router = useRouter(); + const params = useParams(); + const boardIdParam = params?.boardId; + const boardId = Array.isArray(boardIdParam) ? boardIdParam[0] : boardIdParam; + const { getToken, isSignedIn } = useAuth(); + + const [board, setBoard] = useState(null); + const [tasks, setTasks] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [priority, setPriority] = useState("medium"); + const [createError, setCreateError] = useState(null); + const [isCreating, setIsCreating] = useState(false); + + const titleLabel = useMemo( + () => (board ? `${board.name} board` : "Board"), + [board], + ); + + const loadBoard = async () => { + if (!isSignedIn || !boardId) return; + setIsLoading(true); + setError(null); + try { + const token = await getToken(); + const [boardResponse, tasksResponse] = await Promise.all([ + fetch(`${apiBase}/api/v1/boards/${boardId}`, { + headers: { + Authorization: token ? `Bearer ${token}` : "", + }, + }), + fetch(`${apiBase}/api/v1/boards/${boardId}/tasks`, { + headers: { + Authorization: token ? `Bearer ${token}` : "", + }, + }), + ]); + + if (!boardResponse.ok) { + throw new Error("Unable to load board."); + } + if (!tasksResponse.ok) { + throw new Error("Unable to load tasks."); + } + + const boardData = (await boardResponse.json()) as Board; + const taskData = (await tasksResponse.json()) as Task[]; + setBoard(boardData); + setTasks(taskData); + } catch (err) { + setError(err instanceof Error ? err.message : "Something went wrong."); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + loadBoard(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [boardId, isSignedIn]); + + const resetForm = () => { + setTitle(""); + setDescription(""); + setPriority("medium"); + setCreateError(null); + }; + + const handleCreateTask = async () => { + if (!isSignedIn || !boardId) return; + const trimmed = title.trim(); + if (!trimmed) { + setCreateError("Add a task title to continue."); + return; + } + setIsCreating(true); + setCreateError(null); + try { + const token = await getToken(); + const response = await fetch(`${apiBase}/api/v1/boards/${boardId}/tasks`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: token ? `Bearer ${token}` : "", + }, + body: JSON.stringify({ + title: trimmed, + description: description.trim() || null, + status: "inbox", + priority, + }), + }); + + if (!response.ok) { + throw new Error("Unable to create task."); + } + + const created = (await response.json()) as Task; + setTasks((prev) => [created, ...prev]); + setIsDialogOpen(false); + resetForm(); + } catch (err) { + setCreateError(err instanceof Error ? err.message : "Something went wrong."); + } finally { + setIsCreating(false); + } + }; + + return ( + + +
+

Sign in to view boards.

+ + + +
+
+ + +
+
+
+

+ {board?.slug ?? "board"} +

+

+ {board?.name ?? "Board"} +

+

+ Keep tasks moving through your workflow. +

+
+ +
+ + {error && ( +
+ {error} +
+ )} + + {isLoading ? ( +
+ Loading {titleLabel}… +
+ ) : ( + setIsDialogOpen(true)} + isCreateDisabled={isCreating} + /> + )} +
+
+ + { + setIsDialogOpen(nextOpen); + if (!nextOpen) { + resetForm(); + } + }} + > + + + New task + + Add a task to the inbox and triage it when you are ready. + + +
+
+ + setTitle(event.target.value)} + placeholder="e.g. Prepare launch notes" + className="h-11 rounded-lg border-2 border-gray-200 bg-white" + /> +
+
+ +