okto-pulse-core 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. okto_pulse_core-0.1.1/.gitignore +37 -0
  2. okto_pulse_core-0.1.1/CLA.md +47 -0
  3. okto_pulse_core-0.1.1/LICENSE +109 -0
  4. okto_pulse_core-0.1.1/PKG-INFO +54 -0
  5. okto_pulse_core-0.1.1/README.md +18 -0
  6. okto_pulse_core-0.1.1/TRADEMARKS.md +49 -0
  7. okto_pulse_core-0.1.1/pyproject.toml +55 -0
  8. okto_pulse_core-0.1.1/src/okto_pulse/core/__init__.py +12 -0
  9. okto_pulse_core-0.1.1/src/okto_pulse/core/api/__init__.py +4 -0
  10. okto_pulse_core-0.1.1/src/okto_pulse/core/api/agents.py +210 -0
  11. okto_pulse_core-0.1.1/src/okto_pulse/core/api/analytics.py +2229 -0
  12. okto_pulse_core-0.1.1/src/okto_pulse/core/api/attachments.py +109 -0
  13. okto_pulse_core-0.1.1/src/okto_pulse/core/api/boards.py +300 -0
  14. okto_pulse_core-0.1.1/src/okto_pulse/core/api/cards.py +412 -0
  15. okto_pulse_core-0.1.1/src/okto_pulse/core/api/comments.py +115 -0
  16. okto_pulse_core-0.1.1/src/okto_pulse/core/api/guidelines.py +168 -0
  17. okto_pulse_core-0.1.1/src/okto_pulse/core/api/ideations.py +285 -0
  18. okto_pulse_core-0.1.1/src/okto_pulse/core/api/presets.py +170 -0
  19. okto_pulse_core-0.1.1/src/okto_pulse/core/api/qa.py +81 -0
  20. okto_pulse_core-0.1.1/src/okto_pulse/core/api/refinements.py +324 -0
  21. okto_pulse_core-0.1.1/src/okto_pulse/core/api/router.py +33 -0
  22. okto_pulse_core-0.1.1/src/okto_pulse/core/api/specs.py +555 -0
  23. okto_pulse_core-0.1.1/src/okto_pulse/core/api/sprints.py +226 -0
  24. okto_pulse_core-0.1.1/src/okto_pulse/core/app.py +74 -0
  25. okto_pulse_core-0.1.1/src/okto_pulse/core/infra/__init__.py +40 -0
  26. okto_pulse_core-0.1.1/src/okto_pulse/core/infra/auth.py +83 -0
  27. okto_pulse_core-0.1.1/src/okto_pulse/core/infra/config.py +85 -0
  28. okto_pulse_core-0.1.1/src/okto_pulse/core/infra/database.py +1026 -0
  29. okto_pulse_core-0.1.1/src/okto_pulse/core/infra/permissions.py +937 -0
  30. okto_pulse_core-0.1.1/src/okto_pulse/core/infra/storage.py +60 -0
  31. okto_pulse_core-0.1.1/src/okto_pulse/core/mcp/__init__.py +4 -0
  32. okto_pulse_core-0.1.1/src/okto_pulse/core/mcp/agent_instructions.md +1404 -0
  33. okto_pulse_core-0.1.1/src/okto_pulse/core/mcp/server.py +10201 -0
  34. okto_pulse_core-0.1.1/src/okto_pulse/core/models/__init__.py +243 -0
  35. okto_pulse_core-0.1.1/src/okto_pulse/core/models/db.py +1319 -0
  36. okto_pulse_core-0.1.1/src/okto_pulse/core/models/schemas.py +1623 -0
  37. okto_pulse_core-0.1.1/src/okto_pulse/core/services/__init__.py +43 -0
  38. okto_pulse_core-0.1.1/src/okto_pulse/core/services/main.py +4786 -0
  39. okto_pulse_core-0.1.1/test_spec_validation_gate.py +460 -0
  40. okto_pulse_core-0.1.1/test_validation_gate.py +275 -0
@@ -0,0 +1,37 @@
1
+ # Python
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .eggs/
9
+ *.egg
10
+
11
+ # Environment
12
+ .env
13
+ .env.local
14
+
15
+ # IDE
16
+ .idea/
17
+ .vscode/
18
+ *.swp
19
+ *~
20
+
21
+ # OS
22
+ .DS_Store
23
+ Thumbs.db
24
+
25
+ # Claude Code
26
+ .claude/
27
+
28
+ # Test
29
+ .pytest_cache/
30
+ htmlcov/
31
+ .coverage
32
+
33
+ # SQLite
34
+ *.db
35
+
36
+ # MCP config
37
+ .mcp.json
@@ -0,0 +1,47 @@
1
+ # Contributor License Agreement
2
+
3
+ Thank you for your interest in contributing to Okto Pulse, a project by Okto Labs.
4
+
5
+ By submitting a contribution (code, documentation, or other materials) to this project, you agree to the following terms:
6
+
7
+ ## 1. Definitions
8
+
9
+ - **"You"** means the individual or legal entity submitting a contribution.
10
+ - **"Contribution"** means any original work of authorship submitted to the project, including modifications or additions to existing work.
11
+ - **"Project"** means the Okto Pulse software and related repositories maintained by Okto Labs.
12
+
13
+ ## 2. Grant of Rights
14
+
15
+ You grant to Okto Labs a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable license to:
16
+
17
+ - Use, reproduce, modify, display, perform, sublicense, and distribute your Contribution as part of the Project
18
+ - Relicense your Contribution under any license, including proprietary licenses
19
+
20
+ ## 3. Ownership
21
+
22
+ You retain copyright ownership of your Contribution. This agreement does not transfer ownership — it grants a license.
23
+
24
+ ## 4. Representations
25
+
26
+ You represent that:
27
+
28
+ - You are legally entitled to grant the above license
29
+ - Your Contribution is your original work (or you have the right to submit it)
30
+ - Your Contribution does not violate any third party's rights
31
+ - If your employer has rights to intellectual property that you create, you have received permission to make the Contribution on behalf of that employer, or your employer has waived such rights
32
+
33
+ ## 5. No Obligation
34
+
35
+ Okto Labs is not obligated to use your Contribution. Contributions may be accepted, modified, or declined at the project maintainers' discretion.
36
+
37
+ ## 6. How to Sign
38
+
39
+ By submitting a pull request to any Okto Pulse repository, you indicate your agreement with this CLA. No separate signature is required.
40
+
41
+ If you are contributing on behalf of a company, please ensure an authorized representative agrees to these terms.
42
+
43
+ ---
44
+
45
+ Contact: contact@oktolabs.ai
46
+
47
+ Copyright 2024-2026 Okto Labs. All rights reserved.
@@ -0,0 +1,109 @@
1
+ Elastic License 2.0
2
+
3
+ Acceptance. By using the software, you agree to all of the terms and
4
+ conditions below.
5
+
6
+ Copyright 2024-2026 Okto Labs
7
+
8
+ 1. Grant of License. The licensor grants you a non-exclusive, royalty-free,
9
+ worldwide, non-sublicensable, non-transferable license to use, copy,
10
+ distribute, make available, and prepare derivative works of the software,
11
+ in each case subject to the limitations and conditions below.
12
+
13
+ 2. Limitations. You may not provide the software to third parties as a
14
+ hosted or managed service, where the service provides users with access to
15
+ any substantial set of the features or functionality of the software.
16
+ You may not move, change, disable, or circumvent the license key
17
+ functionality in the software, and you may not remove or obscure any
18
+ functionality in the software that is protected by the license key.
19
+ You may not alter, remove, or obscure any licensing, copyright, or other
20
+ notices of the licensor in the software.
21
+
22
+ 3. Patent License. The licensor grants you a license, under any patent
23
+ claims the licensor can license, or becomes able to license, to make, have
24
+ made, use, sell, offer for sale, import and have imported the software.
25
+ However, this license does not cover any patent claims that you cause to be
26
+ infringed by modifications or additions to the software. If you or your
27
+ company make any written claim that the software infringes or contributes
28
+ to infringement of any patent, your patent license for the software granted
29
+ under these terms ends immediately. Your company's patent license granted
30
+ under these terms also ends immediately if your company makes any written
31
+ claim that the software infringes or contributes to infringement of any
32
+ patent.
33
+
34
+ 4. Distribution. You may not alter, remove, or obscure any licensing,
35
+ copyright, or other notices of the licensor in the software. Any
36
+ distribution of the software must include a copy of these terms and
37
+ conditions, and anyone who receives the software from you is bound by these
38
+ terms and conditions.
39
+
40
+ 5. Notices. You must include a copy of these terms with any distribution of
41
+ the software. If you modify the software, you must mark the modifications
42
+ clearly and include the date of the modifications.
43
+
44
+ 6. Termination. If you violate these terms, your licenses will terminate
45
+ automatically. If the licensor notifies you of your violation, and you cease
46
+ all violation of this license no later than 30 days after you receive that
47
+ notice, your licenses will be reinstated retroactively. However, if you
48
+ violate these terms after the reinstatement, all of your licenses will
49
+ terminate permanently.
50
+
51
+ 7. No Other Rights. Except as expressly stated herein, no other rights or
52
+ licenses are granted, express or implied.
53
+
54
+ 8. Limitation on Liability. As far as the law allows, the software comes as
55
+ is, without any warranty or condition, and the licensor will not be liable
56
+ to you for any damages arising out of these terms or the use or nature of
57
+ the software, under any kind of legal claim.
58
+
59
+ 9. Definitions.
60
+
61
+ "Licensor" means Okto Labs and its
62
+ affiliates.
63
+
64
+ "Software" means the software the licensor makes available under these
65
+ terms, including any portions, modifications, or derivative works.
66
+
67
+ "You" means you, individually.
68
+
69
+ "Your company" means any legal entity, sole proprietorship, or other
70
+ organization that you work for, plus all other organizations that control,
71
+ are controlled by, or are under common control with that organization. The
72
+ term "control" means ownership of substantially all the assets of an entity.
73
+
74
+ "Your licenses" means the licenses granted to you in Section 1.
75
+
76
+ "Use" means anything you do with the software in violation of these terms.
77
+
78
+
79
+ Addendum: SaaS and Competing Service Definition
80
+ ================================================
81
+
82
+ For the purposes of Section 2 ("Limitations"), the following constitute
83
+ providing the software as a "hosted or managed service":
84
+
85
+ (a) Offering a hosted platform, application, or API where end users
86
+ interact with the features or functionality of the software, whether
87
+ directly or through a wrapper, proxy, or abstraction layer.
88
+
89
+ (b) Providing the software as a white-label, embedded, or rebranded
90
+ offering to third parties.
91
+
92
+ (c) Operating a multi-tenant service where the software's capabilities
93
+ are a primary value driver for the service offering.
94
+
95
+ The following are expressly PERMITTED:
96
+
97
+ (a) Using the software internally within your organization, including
98
+ for commercial purposes, without restriction.
99
+
100
+ (b) Using the software to manage your own projects, teams, or products,
101
+ even in a commercial context.
102
+
103
+ (c) Integrating the software's MCP tools with AI agents for your own
104
+ use or your organization's use.
105
+
106
+ (d) Modifying the software for personal or internal organizational use.
107
+
108
+ If you are unsure whether your intended use constitutes a competing service,
109
+ contact the licensor at contact@oktolabs.ai for clarification.
@@ -0,0 +1,54 @@
1
+ Metadata-Version: 2.4
2
+ Name: okto-pulse-core
3
+ Version: 0.1.1
4
+ Summary: Okto Pulse Core — spec-driven project management engine with MCP support for AI agents
5
+ Project-URL: Homepage, https://github.com/OktoLabsAI/okto-pulse-core
6
+ Project-URL: Documentation, https://github.com/OktoLabsAI/okto-pulse-core#readme
7
+ Project-URL: Repository, https://github.com/OktoLabsAI/okto-pulse-core
8
+ Project-URL: Issues, https://github.com/OktoLabsAI/okto-pulse-core/issues
9
+ Author-email: Okto Labs <contact@oktolabs.ai>
10
+ License: Elastic-2.0
11
+ License-File: LICENSE
12
+ Keywords: ai-agents,kanban,mcp,project-management,spec-driven
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Framework :: FastAPI
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Office/Business :: Scheduling
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Requires-Python: >=3.11
24
+ Requires-Dist: aiofiles<26.0.0,>=23.2.0
25
+ Requires-Dist: aiosqlite<1.0.0,>=0.19.0
26
+ Requires-Dist: asyncpg<1.0.0,>=0.29
27
+ Requires-Dist: fastapi<1.0.0,>=0.109.0
28
+ Requires-Dist: fastmcp<3.0.0,>=2.0.0
29
+ Requires-Dist: mcp<2.0.0,>=1.0.0
30
+ Requires-Dist: pydantic-settings<3.0.0,>=2.1.0
31
+ Requires-Dist: pydantic<3.0.0,>=2.5.0
32
+ Requires-Dist: python-multipart<1.0.0,>=0.0.6
33
+ Requires-Dist: sqlalchemy[asyncio]<3.0.0,>=2.0.0
34
+ Requires-Dist: uvicorn[standard]<1.0.0,>=0.27.0
35
+ Description-Content-Type: text/markdown
36
+
37
+ # okto-pulse-core
38
+
39
+ Core engine for [Okto Pulse](https://github.com/OktoLabsAI/okto-pulse) — shared models, services, API routes, and MCP server.
40
+
41
+ > **You probably want to install [`okto-pulse`](https://pypi.org/project/okto-pulse/) instead.**
42
+ > This package is the internal engine. The `okto-pulse` package provides the CLI, frontend, and everything you need to get started.
43
+
44
+ ## What's inside
45
+
46
+ - **27 SQLAlchemy models** — Boards, Cards, Specs, Ideations, Refinements, Agents, etc.
47
+ - **18 service classes** — Full business logic with governance rules
48
+ - **11 API route modules** — FastAPI REST endpoints
49
+ - **119+ MCP tools** — Complete Model Context Protocol server for AI agent integration
50
+ - **App factory** — `create_app()` with dependency injection for auth and storage providers
51
+
52
+ ## License
53
+
54
+ [Elastic License 2.0](./LICENSE) — free for personal and commercial use. Cannot be offered as a hosted/managed service.
@@ -0,0 +1,18 @@
1
+ # okto-pulse-core
2
+
3
+ Core engine for [Okto Pulse](https://github.com/OktoLabsAI/okto-pulse) — shared models, services, API routes, and MCP server.
4
+
5
+ > **You probably want to install [`okto-pulse`](https://pypi.org/project/okto-pulse/) instead.**
6
+ > This package is the internal engine. The `okto-pulse` package provides the CLI, frontend, and everything you need to get started.
7
+
8
+ ## What's inside
9
+
10
+ - **27 SQLAlchemy models** — Boards, Cards, Specs, Ideations, Refinements, Agents, etc.
11
+ - **18 service classes** — Full business logic with governance rules
12
+ - **11 API route modules** — FastAPI REST endpoints
13
+ - **119+ MCP tools** — Complete Model Context Protocol server for AI agent integration
14
+ - **App factory** — `create_app()` with dependency injection for auth and storage providers
15
+
16
+ ## License
17
+
18
+ [Elastic License 2.0](./LICENSE) — free for personal and commercial use. Cannot be offered as a hosted/managed service.
@@ -0,0 +1,49 @@
1
+ # Trademark Policy
2
+
3
+ ## Trademarks
4
+
5
+ The following are trademarks of Okto Labs:
6
+
7
+ - **Okto Labs** — the company name and brand
8
+ - **Okto Pulse** — the product name
9
+ - **OktoLabs logo** — the octopus/circuit design mark (all variants)
10
+
11
+ ## Permitted Use
12
+
13
+ You **may**:
14
+
15
+ - State that your project is "based on Okto Pulse" or "compatible with Okto Pulse"
16
+ - Use the project name in factual references (documentation, articles, comparisons)
17
+ - Use the name when required by the license attribution notice
18
+
19
+ ## Prohibited Use
20
+
21
+ You **may not** without prior written authorization:
22
+
23
+ - Use "Okto Pulse", "Okto Labs", or the logo in your product name, service name, or domain name
24
+ - Create the impression that your product or service is endorsed, sponsored, or affiliated with Okto Labs
25
+ - Use the logo or any confusingly similar mark in your product's interface, marketing, or branding
26
+ - Offer a product or service under a name that includes "Okto" in combination with "Pulse", "Labs", or similar terms
27
+
28
+ ## Derivative Works and Forks
29
+
30
+ If you create a fork or derivative work:
31
+
32
+ - You **must remove** all Okto Labs trademarks, logos, and branding from the software
33
+ - You **must choose** a distinct name that does not suggest affiliation with Okto Labs
34
+ - You **must retain** the copyright and license notices as required by the license
35
+
36
+ ## Logo Usage
37
+
38
+ The OktoLabs logo files included in this repository (`logo-light.jpg`, `logo-dark.jpg`, `favicon.jpg`) are provided solely for use within unmodified distributions of the software. They may not be used in derivative works, forks, or third-party products.
39
+
40
+ ## Contact
41
+
42
+ For trademark usage requests or questions:
43
+
44
+ - Email: contact@oktolabs.ai
45
+ - GitHub: https://github.com/OktoLabsAI
46
+
47
+ ---
48
+
49
+ Copyright 2024-2026 Okto Labs. All rights reserved.
@@ -0,0 +1,55 @@
1
+ [project]
2
+ name = "okto-pulse-core"
3
+ version = "0.1.1"
4
+ description = "Okto Pulse Core — spec-driven project management engine with MCP support for AI agents"
5
+ requires-python = ">=3.11"
6
+ license = {text = "Elastic-2.0"}
7
+ authors = [
8
+ {name = "Okto Labs", email = "contact@oktolabs.ai"},
9
+ ]
10
+ keywords = ["kanban", "project-management", "mcp", "ai-agents", "spec-driven"]
11
+ classifiers = [
12
+ "Development Status :: 4 - Beta",
13
+ "Intended Audience :: Developers",
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.11",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Topic :: Software Development :: Quality Assurance",
19
+ "Topic :: Office/Business :: Scheduling",
20
+ "Framework :: FastAPI",
21
+ "Operating System :: OS Independent",
22
+ ]
23
+ dependencies = [
24
+ "fastapi>=0.109.0,<1.0.0",
25
+ "uvicorn[standard]>=0.27.0,<1.0.0",
26
+ "pydantic>=2.5.0,<3.0.0",
27
+ "pydantic-settings>=2.1.0,<3.0.0",
28
+ "aiofiles>=23.2.0,<26.0.0",
29
+ "python-multipart>=0.0.6,<1.0.0",
30
+ "sqlalchemy[asyncio]>=2.0.0,<3.0.0",
31
+ "aiosqlite>=0.19.0,<1.0.0",
32
+ "asyncpg>=0.29,<1.0.0",
33
+ "fastmcp>=2.0.0,<3.0.0",
34
+ "mcp>=1.0.0,<2.0.0",
35
+ ]
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/OktoLabsAI/okto-pulse-core"
39
+ Documentation = "https://github.com/OktoLabsAI/okto-pulse-core#readme"
40
+ Repository = "https://github.com/OktoLabsAI/okto-pulse-core"
41
+ Issues = "https://github.com/OktoLabsAI/okto-pulse-core/issues"
42
+
43
+ [project.readme]
44
+ file = "README.md"
45
+ content-type = "text/markdown"
46
+
47
+ [build-system]
48
+ requires = ["hatchling"]
49
+ build-backend = "hatchling.build"
50
+
51
+ [tool.hatch.build.targets.wheel]
52
+ packages = ["src/okto_pulse"]
53
+
54
+ [tool.hatch.build.targets.wheel.force-include]
55
+ "src/okto_pulse/core/mcp/agent_instructions.md" = "okto_pulse/core/mcp/agent_instructions.md"
@@ -0,0 +1,12 @@
1
+ """Okto Pulse Core — shared logic for all editions."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from okto_pulse.core.infra.auth import (
6
+ AuthProvider, configure_auth, get_auth_provider,
7
+ get_current_user, get_current_user_id, get_realm_id, require_user,
8
+ )
9
+ from okto_pulse.core.infra.config import CoreSettings, get_settings, configure_settings
10
+ from okto_pulse.core.infra.database import Base, get_db, get_db_session, init_db, close_db
11
+ from okto_pulse.core.infra.permissions import Permissions, check_permission, has_permission
12
+ from okto_pulse.core.infra.storage import StorageProvider, FileSystemStorageProvider, configure_storage, get_storage_provider
@@ -0,0 +1,4 @@
1
+ """Core API routes."""
2
+ from okto_pulse.core.api.router import api_router
3
+
4
+ __all__ = ["api_router"]
@@ -0,0 +1,210 @@
1
+ """Agent API endpoints."""
2
+
3
+ from fastapi import APIRouter, Depends, HTTPException, status
4
+ from sqlalchemy.ext.asyncio import AsyncSession
5
+
6
+ from okto_pulse.core.infra.auth import require_user
7
+ from okto_pulse.core.infra.database import get_db
8
+ from okto_pulse.core.models import (
9
+ AgentBoardOverridesUpdate,
10
+ AgentBoardResponse,
11
+ AgentCreate,
12
+ AgentResponse,
13
+ AgentSummary,
14
+ AgentUpdate,
15
+ )
16
+ from okto_pulse.core.services import AgentService, BoardService
17
+
18
+ router = APIRouter()
19
+
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # Global agent CRUD (ownership via created_by)
23
+ # ---------------------------------------------------------------------------
24
+
25
+
26
+ @router.post("", response_model=AgentResponse, status_code=status.HTTP_201_CREATED)
27
+ async def create_agent(
28
+ data: AgentCreate,
29
+ user_id: str = Depends(require_user),
30
+ db: AsyncSession = Depends(get_db),
31
+ ):
32
+ """Create a new global agent (not tied to any board)."""
33
+ service = AgentService(db)
34
+ agent, api_key = await service.create_agent(user_id, data)
35
+ await db.commit()
36
+ agent = await service.get_agent(agent.id)
37
+
38
+ response = AgentResponse.model_validate(agent)
39
+ response.api_key = api_key
40
+ return response
41
+
42
+
43
+ @router.get("", response_model=list[AgentResponse])
44
+ async def list_my_agents(
45
+ user_id: str = Depends(require_user),
46
+ db: AsyncSession = Depends(get_db),
47
+ ):
48
+ """List all agents owned by the current user (with api_key)."""
49
+ service = AgentService(db)
50
+ agents = await service.list_agents_for_user(user_id)
51
+ return agents
52
+
53
+
54
+ @router.get("/board/{board_id}", response_model=list[AgentSummary])
55
+ async def list_agents_for_board(
56
+ board_id: str,
57
+ user_id: str = Depends(require_user),
58
+ db: AsyncSession = Depends(get_db),
59
+ ):
60
+ """List all agents with access to a board."""
61
+ board_service = BoardService(db)
62
+ board = await board_service.get_board(board_id, user_id)
63
+ if not board:
64
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Board not found")
65
+
66
+ service = AgentService(db)
67
+ agents = await service.list_agents_for_board(board_id)
68
+ return agents
69
+
70
+
71
+ @router.get("/{agent_id}", response_model=AgentResponse)
72
+ async def get_agent(
73
+ agent_id: str,
74
+ user_id: str = Depends(require_user),
75
+ db: AsyncSession = Depends(get_db),
76
+ ):
77
+ """Get an agent by ID (owner only)."""
78
+ service = AgentService(db)
79
+ agent = await service.get_agent(agent_id)
80
+ if not agent or agent.created_by != user_id:
81
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Agent not found")
82
+ return agent
83
+
84
+
85
+ @router.patch("/{agent_id}", response_model=AgentResponse)
86
+ async def update_agent(
87
+ agent_id: str,
88
+ data: AgentUpdate,
89
+ user_id: str = Depends(require_user),
90
+ db: AsyncSession = Depends(get_db),
91
+ ):
92
+ """Update an agent (owner only)."""
93
+ service = AgentService(db)
94
+ agent = await service.get_agent(agent_id)
95
+ if not agent or agent.created_by != user_id:
96
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Agent not found")
97
+
98
+ updated = await service.update_agent(agent_id, data)
99
+ await db.commit()
100
+ updated = await service.get_agent(agent_id)
101
+ return updated
102
+
103
+
104
+ @router.post("/{agent_id}/regenerate-key", response_model=dict)
105
+ async def regenerate_agent_key(
106
+ agent_id: str,
107
+ user_id: str = Depends(require_user),
108
+ db: AsyncSession = Depends(get_db),
109
+ ):
110
+ """Regenerate an agent's API key (owner only)."""
111
+ service = AgentService(db)
112
+ agent = await service.get_agent(agent_id)
113
+ if not agent or agent.created_by != user_id:
114
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Agent not found")
115
+
116
+ updated, new_key = await service.regenerate_key(agent_id)
117
+ await db.commit()
118
+
119
+ return {"message": "API key regenerated", "api_key": new_key}
120
+
121
+
122
+ @router.delete("/{agent_id}", status_code=status.HTTP_204_NO_CONTENT)
123
+ async def delete_agent(
124
+ agent_id: str,
125
+ user_id: str = Depends(require_user),
126
+ db: AsyncSession = Depends(get_db),
127
+ ):
128
+ """Delete an agent (owner only)."""
129
+ service = AgentService(db)
130
+ agent = await service.get_agent(agent_id)
131
+ if not agent or agent.created_by != user_id:
132
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Agent not found")
133
+
134
+ await service.delete_agent(agent_id)
135
+ await db.commit()
136
+
137
+
138
+ # ---------------------------------------------------------------------------
139
+ # Board access grant / revoke
140
+ # ---------------------------------------------------------------------------
141
+
142
+
143
+ @router.post(
144
+ "/{agent_id}/boards/{board_id}",
145
+ response_model=AgentBoardResponse,
146
+ status_code=status.HTTP_201_CREATED,
147
+ )
148
+ async def grant_board_access(
149
+ agent_id: str,
150
+ board_id: str,
151
+ user_id: str = Depends(require_user),
152
+ db: AsyncSession = Depends(get_db),
153
+ ):
154
+ """Grant an agent access to a board. Requires owning both the agent and the board."""
155
+ service = AgentService(db)
156
+ agent = await service.get_agent(agent_id)
157
+ if not agent or agent.created_by != user_id:
158
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Agent not found")
159
+
160
+ board_service = BoardService(db)
161
+ board = await board_service.get_board(board_id, user_id)
162
+ if not board:
163
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Board not found")
164
+
165
+ if await service.agent_has_board_access(agent_id, board_id):
166
+ raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Access already granted")
167
+
168
+ grant = await service.grant_board_access(agent_id, board_id, user_id)
169
+ await db.commit()
170
+ return grant
171
+
172
+
173
+ @router.patch("/{agent_id}/boards/{board_id}", response_model=AgentBoardResponse)
174
+ async def update_board_overrides(
175
+ agent_id: str,
176
+ board_id: str,
177
+ data: AgentBoardOverridesUpdate,
178
+ user_id: str = Depends(require_user),
179
+ db: AsyncSession = Depends(get_db),
180
+ ):
181
+ """Update permission overrides for an agent on a board (ceiling model)."""
182
+ service = AgentService(db)
183
+ agent = await service.get_agent(agent_id)
184
+ if not agent or agent.created_by != user_id:
185
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Agent not found")
186
+
187
+ ab = await service.update_board_overrides(agent_id, board_id, data.permission_overrides)
188
+ if not ab:
189
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Board access not found")
190
+ await db.commit()
191
+ return ab
192
+
193
+
194
+ @router.delete("/{agent_id}/boards/{board_id}", status_code=status.HTTP_204_NO_CONTENT)
195
+ async def revoke_board_access(
196
+ agent_id: str,
197
+ board_id: str,
198
+ user_id: str = Depends(require_user),
199
+ db: AsyncSession = Depends(get_db),
200
+ ):
201
+ """Revoke an agent's access to a board. Requires owning the agent or the board."""
202
+ service = AgentService(db)
203
+ agent = await service.get_agent(agent_id)
204
+ if not agent or agent.created_by != user_id:
205
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Agent not found")
206
+
207
+ revoked = await service.revoke_board_access(agent_id, board_id)
208
+ if not revoked:
209
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Access not found")
210
+ await db.commit()