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.
- okto_pulse_core-0.1.1/.gitignore +37 -0
- okto_pulse_core-0.1.1/CLA.md +47 -0
- okto_pulse_core-0.1.1/LICENSE +109 -0
- okto_pulse_core-0.1.1/PKG-INFO +54 -0
- okto_pulse_core-0.1.1/README.md +18 -0
- okto_pulse_core-0.1.1/TRADEMARKS.md +49 -0
- okto_pulse_core-0.1.1/pyproject.toml +55 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/__init__.py +12 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/api/__init__.py +4 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/api/agents.py +210 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/api/analytics.py +2229 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/api/attachments.py +109 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/api/boards.py +300 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/api/cards.py +412 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/api/comments.py +115 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/api/guidelines.py +168 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/api/ideations.py +285 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/api/presets.py +170 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/api/qa.py +81 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/api/refinements.py +324 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/api/router.py +33 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/api/specs.py +555 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/api/sprints.py +226 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/app.py +74 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/infra/__init__.py +40 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/infra/auth.py +83 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/infra/config.py +85 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/infra/database.py +1026 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/infra/permissions.py +937 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/infra/storage.py +60 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/mcp/__init__.py +4 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/mcp/agent_instructions.md +1404 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/mcp/server.py +10201 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/models/__init__.py +243 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/models/db.py +1319 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/models/schemas.py +1623 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/services/__init__.py +43 -0
- okto_pulse_core-0.1.1/src/okto_pulse/core/services/main.py +4786 -0
- okto_pulse_core-0.1.1/test_spec_validation_gate.py +460 -0
- 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,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()
|