Compare commits
116 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 346b32236a | |||
| 8f7f682a92 | |||
| 2ece7074f6 | |||
| c7ab2de25e | |||
| c9b0259993 | |||
| 1f2c3dbe2a | |||
| d247e44985 | |||
| d60dec9d62 | |||
| 4913cc7297 | |||
| 86360661e9 | |||
| 1d09193652 | |||
| 4ced6141ae | |||
| 530225d37c | |||
| e6ab77d123 | |||
| 4e1477c4be | |||
| 2b5b8363ca | |||
| 1e0f95f8bd | |||
| 6a20c54da7 | |||
| db6ae38568 | |||
| 1e43ea48aa | |||
| efe5e4ae50 | |||
| 31f1ce00d3 | |||
| d395aaf574 | |||
| d78ca8a9d5 | |||
| 19291d6be9 | |||
| 5409ba2b13 | |||
| 8bde9a213c | |||
| 315cb33e3f | |||
| 474843ffa6 | |||
| 766f7ff0d1 | |||
| f8b162ec5f | |||
| 8a8a9bc9e4 | |||
| 61f9c52455 | |||
| 43d65840ea | |||
| ad5a4b5000 | |||
| 73088b75ba | |||
| f23fadf52b | |||
| 9101033019 | |||
| 72bf4ed962 | |||
| 3b772ac239 | |||
| 8c1948d226 | |||
| 43f3865f69 | |||
| 7ef8b0eb36 | |||
| c6073fe9d2 | |||
| 81b9a66ef5 | |||
| 79be4eb525 | |||
| 04225ef890 | |||
| 7f78d92fef | |||
| bbfcde3d1b | |||
| 9f4e9845c0 | |||
| ce8b5dc86d | |||
| efb62fe41a | |||
| c26e9eacfa | |||
| aa49efb236 | |||
| 6b947b7593 | |||
| 3da7ea6408 | |||
| 79aabd6f43 | |||
| 40ac931c65 | |||
| aa51ba219b | |||
| 7623f29ffb | |||
| 3979b1f4e7 | |||
| caadd59441 | |||
| 30549f4863 | |||
| 070cd4d5a5 | |||
| 44ef62271e | |||
| 7440720b7b | |||
| 110844e597 | |||
| b4d08b0232 | |||
| f1180f6053 | |||
| 59bec31046 | |||
| 8a7bec8df2 | |||
| c881bbdad3 | |||
| f1b968bb88 | |||
| 3c222d4f0f | |||
| 1d10283fc9 | |||
| 886be83af5 | |||
| 82091e31a8 | |||
| b2c84e2064 | |||
| 680417a0a2 | |||
| e5e29aca49 | |||
| 486f3cbc44 | |||
| 152f87a254 | |||
| 349066bcfa | |||
| c353bceb97 | |||
| 64dcdc9d0d | |||
| 4ea2e3659d | |||
| 734bd9529a | |||
| 1ef9d66eed | |||
| 2169b24875 | |||
| f2a3399f27 | |||
| 5266e64be2 | |||
| 9a17916dd2 | |||
| a388a8bec9 | |||
| 51a98a0c63 | |||
| 6efe524974 | |||
| 88c56a83b7 | |||
| b4aa4c5fcb | |||
| 183e910afd | |||
| d80ee4157c | |||
| d8ab7734cb | |||
| de6a6a3b00 | |||
| 9503f6cb4f | |||
| 6b118307eb | |||
| 070e960c05 | |||
| 96ce3f4c53 | |||
| e49d049455 | |||
| e7f219f7c3 | |||
| d7d5baa41a | |||
| 61072f4c07 | |||
| 7070867393 | |||
| d472c41092 | |||
| a9e2dd3552 | |||
| 6553a8845b | |||
| 994b1cf3b7 | |||
| 6aea83bf17 | |||
| 8d51877afa |
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"fingerprint": "c36b11ec5edebc02aa51b1113a7a11dc2559e812"
|
"fingerprint": "639c16d45210921c3c8ece071ef18bbe0c426ea2"
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
# Skill Registry — headquarter
|
# Skill Registry — workspace
|
||||||
|
|
||||||
<!-- Auto-generated by gentle-pi extensions/skill-registry.ts. Run /skill-registry:refresh to regenerate. -->
|
<!-- Auto-generated by gentle-pi extensions/skill-registry.ts. Run /skill-registry:refresh to regenerate. -->
|
||||||
|
|
||||||
Last updated: 2026-06-02
|
Last updated: 2026-06-05
|
||||||
|
|
||||||
## Sources scanned
|
## Sources scanned
|
||||||
|
|
||||||
- .opencode/skills
|
- .opencode/skills
|
||||||
- .claude/skills
|
- .claude/skills
|
||||||
- /home/alex/.config/opencode/skills
|
|
||||||
|
|
||||||
## Contract
|
## Contract
|
||||||
|
|
||||||
@@ -20,12 +19,11 @@ Last updated: 2026-06-02
|
|||||||
|
|
||||||
| Skill | Trigger / description | Scope | Path |
|
| Skill | Trigger / description | Scope | Path |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `auto-commit` | Use when you are making multiple edits or completing significant work in a git repository to automatically create commits | user | `/home/alex/.config/opencode/skills/auto-commit/SKILL.md` |
|
| `openspec-apply-change` | Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks. | project | `/workspace/.opencode/skills/openspec-apply-change/SKILL.md` |
|
||||||
| `openspec-apply-change` | Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks. | project | `/home/alex/projects/headquarter/.opencode/skills/openspec-apply-change/SKILL.md` |
|
| `openspec-archive-change` | Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete. | project | `/workspace/.opencode/skills/openspec-archive-change/SKILL.md` |
|
||||||
| `openspec-archive-change` | Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete. | project | `/home/alex/projects/headquarter/.opencode/skills/openspec-archive-change/SKILL.md` |
|
| `openspec-explore` | Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change. | project | `/workspace/.opencode/skills/openspec-explore/SKILL.md` |
|
||||||
| `openspec-explore` | Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change. | project | `/home/alex/projects/headquarter/.opencode/skills/openspec-explore/SKILL.md` |
|
| `openspec-propose` | Propose a new change with all artifacts generated in one step. Use when the user wants to quickly describe what they want to build and get a complete proposal with design, specs, and tasks ready for implementation. | project | `/workspace/.opencode/skills/openspec-propose/SKILL.md` |
|
||||||
| `openspec-propose` | Propose a new change with all artifacts generated in one step. Use when the user wants to quickly describe what they want to build and get a complete proposal with design, specs, and tasks ready for implementation. | project | `/home/alex/projects/headquarter/.opencode/skills/openspec-propose/SKILL.md` |
|
| `sift-backlog` | Triage and organize backlog tasks into actionable plans. Use when asked to review the backlog, prioritize tasks, create plans from backlog items, or move tasks from backlog to open status. Handles the full workflow of listing backlog tasks, grouping related tasks into plans, setting priorities and dependencies, activating plans, and changing task status from backlog to open. | project | `/workspace/.claude/skills/sift-backlog/SKILL.md` |
|
||||||
| `sift-backlog` | Triage and organize backlog tasks into actionable plans. Use when asked to review the backlog, prioritize tasks, create plans from backlog items, or move tasks from backlog to open status. Handles the full workflow of listing backlog tasks, grouping related tasks into plans, setting priorities and dependencies, activating plans, and changing task status from backlog to open. | project | `/home/alex/projects/headquarter/.claude/skills/sift-backlog/SKILL.md` |
|
|
||||||
|
|
||||||
## Loading protocol
|
## Loading protocol
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# .claude (index)
|
||||||
|
dir: .claude
|
||||||
|
|
||||||
|
## role
|
||||||
|
Configuration directory for Claude AI assistant integration and custom instructions.
|
||||||
|
## parent
|
||||||
|
index: ./.pi-map.index.md
|
||||||
|
map: ./.pi-map.md
|
||||||
|
## children
|
||||||
|
- .claude/skills
|
||||||
|
index: .claude/skills/.pi-map.index.md
|
||||||
|
map: .claude/skills/.pi-map.md
|
||||||
|
## files
|
||||||
|
## links
|
||||||
|
index: .claude/.pi-map.index.md
|
||||||
|
map: .claude/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# .claude
|
||||||
|
dir: .claude
|
||||||
|
|
||||||
|
index: .claude/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Configuration directory for Claude AI assistant integration and custom instructions.
|
||||||
|
## files
|
||||||
|
## arch
|
||||||
|
Project-specific AI tooling configuration using convention-based file organization for assistant context and behavior customization.
|
||||||
|
## tags
|
||||||
|
-
|
||||||
|
## symbols
|
||||||
|
-
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# .claude/skills (index)
|
||||||
|
dir: .claude/skills
|
||||||
|
|
||||||
|
## role
|
||||||
|
Contains reusable AI skill definitions and prompt templates that configure Claude's specialized capabilities for specific development tasks.
|
||||||
|
## parent
|
||||||
|
index: .claude/.pi-map.index.md
|
||||||
|
map: .claude/.pi-map.md
|
||||||
|
## children
|
||||||
|
- .claude/skills/sift-backlog
|
||||||
|
index: .claude/skills/sift-backlog/.pi-map.index.md
|
||||||
|
map: .claude/skills/sift-backlog/.pi-map.md
|
||||||
|
## files
|
||||||
|
## links
|
||||||
|
index: .claude/skills/.pi-map.index.md
|
||||||
|
map: .claude/skills/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# .claude/skills
|
||||||
|
dir: .claude/skills
|
||||||
|
|
||||||
|
index: .claude/skills/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Contains reusable AI skill definitions and prompt templates that configure Claude's specialized capabilities for specific development tasks.
|
||||||
|
## files
|
||||||
|
## arch
|
||||||
|
Modular skill-based architecture using declarative configuration files (likely YAML/JSON) to define context-specific behaviors, tool access patterns, and system prompts for different operational modes.
|
||||||
|
## tags
|
||||||
|
-
|
||||||
|
## symbols
|
||||||
|
-
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# .claude/skills/sift-backlog (index)
|
||||||
|
dir: .claude/skills/sift-backlog
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines a workflow skill for triaging, organizing, and activating backlog tasks into actionable plans using a custom CLI tool.
|
||||||
|
## parent
|
||||||
|
index: .claude/skills/.pi-map.index.md
|
||||||
|
map: .claude/skills/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- SKILL.md
|
||||||
|
## links
|
||||||
|
index: .claude/skills/sift-backlog/.pi-map.index.md
|
||||||
|
map: .claude/skills/sift-backlog/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# .claude/skills/sift-backlog
|
||||||
|
dir: .claude/skills/sift-backlog
|
||||||
|
|
||||||
|
index: .claude/skills/sift-backlog/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines a workflow skill for triaging, organizing, and activating backlog tasks into actionable plans using a custom CLI tool.
|
||||||
|
## files
|
||||||
|
- SKILL.md | Defines a workflow skill for triaging, organizing, and activating backlog tasks into actionable plans using a custom CLI tool. | dep: sf (custom CLI tool), task management system, plan management system
|
||||||
|
## arch
|
||||||
|
Documentation-driven skill definition using structured markdown with command specifications, workflow stages, and integration patterns for Claude CLI tooling.
|
||||||
|
## tags
|
||||||
|
skill, defines, workflow, triaging, organizing, activating, backlog, tasks
|
||||||
|
## symbols
|
||||||
|
-
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -17,6 +17,7 @@ __pycache__/
|
|||||||
*.so
|
*.so
|
||||||
.python-version
|
.python-version
|
||||||
.venv/
|
.venv/
|
||||||
|
.venv-test/
|
||||||
venv/
|
venv/
|
||||||
env/
|
env/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
@@ -54,3 +55,6 @@ Thumbs.db
|
|||||||
.atl/
|
.atl/
|
||||||
.sisyphus/
|
.sisyphus/
|
||||||
.pi-lens/
|
.pi-lens/
|
||||||
|
minerv3/
|
||||||
|
.cache/
|
||||||
|
openspec-audit-report.md
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# .opencode (index)
|
||||||
|
dir: .opencode
|
||||||
|
|
||||||
|
## role
|
||||||
|
Hidden directory for OpenCode IDE/editor configuration and workspace metadata
|
||||||
|
## parent
|
||||||
|
index: ./.pi-map.index.md
|
||||||
|
map: ./.pi-map.md
|
||||||
|
## children
|
||||||
|
- .opencode/commands
|
||||||
|
index: .opencode/commands/.pi-map.index.md
|
||||||
|
map: .opencode/commands/.pi-map.md
|
||||||
|
- .opencode/skills
|
||||||
|
index: .opencode/skills/.pi-map.index.md
|
||||||
|
map: .opencode/skills/.pi-map.md
|
||||||
|
## files
|
||||||
|
## links
|
||||||
|
index: .opencode/.pi-map.index.md
|
||||||
|
map: .opencode/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# .opencode
|
||||||
|
dir: .opencode
|
||||||
|
|
||||||
|
index: .opencode/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Hidden directory for OpenCode IDE/editor configuration and workspace metadata
|
||||||
|
## files
|
||||||
|
## arch
|
||||||
|
IDE-specific dot-directory pattern, no active code architecture; stores tool preferences and ephemeral state
|
||||||
|
## tags
|
||||||
|
-
|
||||||
|
## symbols
|
||||||
|
-
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# .opencode/commands (index)
|
||||||
|
dir: .opencode/commands
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines experimental workflow skills and AI assistant stances for an OpenSpec-based development system with structured change management.
|
||||||
|
## parent
|
||||||
|
index: .opencode/.pi-map.index.md
|
||||||
|
map: .opencode/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- opsx-apply.md
|
||||||
|
- opsx-archive.md
|
||||||
|
- opsx-explore.md
|
||||||
|
- opsx-propose.md
|
||||||
|
## links
|
||||||
|
index: .opencode/commands/.pi-map.index.md
|
||||||
|
map: .opencode/commands/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# .opencode/commands
|
||||||
|
dir: .opencode/commands
|
||||||
|
|
||||||
|
index: .opencode/commands/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines experimental workflow skills and AI assistant stances for an OpenSpec-based development system with structured change management.
|
||||||
|
## files
|
||||||
|
- opsx-apply.md | Defines an experimental workflow skill for implementing tasks from an OpenSpec change through a structured, interactive process with CLI integration and progress tracking. | dep: openspec CLI, AskUserQuestion tool, filesystem (for reading context files)
|
||||||
|
- opsx-archive.md | Defines a workflow for archiving completed changes in an experimental openspec-based development system | dep: openspec CLI, AskUserQuestion tool, Task tool, Skill tool, filesystem (mkdir, mv), JSON parsing
|
||||||
|
- opsx-explore.md | Defines the "explore mode" stance for an AI assistant - a thinking/discovery mode for investigating problems and clarifying requirements without implementing code | dep: OpenSpec system
|
||||||
|
- opsx-propose.md | Defines a workflow for proposing new changes in the openspec system by creating a change directory and generating all required artifacts (proposal.md, design.md, tasks.md) in dependency order | dep: openspec CLI, AskUserQuestion tool, TodoWrite tool, JSON parsing
|
||||||
|
## arch
|
||||||
|
Markdown-based command definitions using a workflow pattern with interactive CLI integration, progress tracking, and dependency-ordered artifact generation across explore/propose/apply/archive lifecycle phases.
|
||||||
|
## tags
|
||||||
|
opsx, defines, workflow, openspec, openspec cli, askuserquestion tool, explore, experimental
|
||||||
|
## symbols
|
||||||
|
-
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# .opencode/skills (index)
|
||||||
|
dir: .opencode/skills
|
||||||
|
|
||||||
|
## role
|
||||||
|
Contains reusable AI skill modules that provide specialized capabilities for the OpenCode assistant.
|
||||||
|
## parent
|
||||||
|
index: .opencode/.pi-map.index.md
|
||||||
|
map: .opencode/.pi-map.md
|
||||||
|
## children
|
||||||
|
- .opencode/skills/openspec-apply-change
|
||||||
|
index: .opencode/skills/openspec-apply-change/.pi-map.index.md
|
||||||
|
map: .opencode/skills/openspec-apply-change/.pi-map.md
|
||||||
|
- .opencode/skills/openspec-archive-change
|
||||||
|
index: .opencode/skills/openspec-archive-change/.pi-map.index.md
|
||||||
|
map: .opencode/skills/openspec-archive-change/.pi-map.md
|
||||||
|
- .opencode/skills/openspec-explore
|
||||||
|
index: .opencode/skills/openspec-explore/.pi-map.index.md
|
||||||
|
map: .opencode/skills/openspec-explore/.pi-map.md
|
||||||
|
- .opencode/skills/openspec-propose
|
||||||
|
index: .opencode/skills/openspec-propose/.pi-map.index.md
|
||||||
|
map: .opencode/skills/openspec-propose/.pi-map.md
|
||||||
|
## files
|
||||||
|
## links
|
||||||
|
index: .opencode/skills/.pi-map.index.md
|
||||||
|
map: .opencode/skills/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# .opencode/skills
|
||||||
|
dir: .opencode/skills
|
||||||
|
|
||||||
|
index: .opencode/skills/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Contains reusable AI skill modules that provide specialized capabilities for the OpenCode assistant.
|
||||||
|
## files
|
||||||
|
## arch
|
||||||
|
Modular plugin-based architecture where each skill is a self-contained module with defined interfaces, enabling dynamic loading and composition of AI capabilities.
|
||||||
|
## tags
|
||||||
|
-
|
||||||
|
## symbols
|
||||||
|
-
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# .opencode/skills/openspec-apply-change (index)
|
||||||
|
dir: .opencode/skills/openspec-apply-change
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines an AI assistant skill that implements OpenSpec changes through a spec-driven workflow with structured planning, validation, and execution phases.
|
||||||
|
## parent
|
||||||
|
index: .opencode/skills/.pi-map.index.md
|
||||||
|
map: .opencode/skills/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- SKILL.md
|
||||||
|
## links
|
||||||
|
index: .opencode/skills/openspec-apply-change/.pi-map.index.md
|
||||||
|
map: .opencode/skills/openspec-apply-change/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# .opencode/skills/openspec-apply-change
|
||||||
|
dir: .opencode/skills/openspec-apply-change
|
||||||
|
|
||||||
|
index: .opencode/skills/openspec-apply-change/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines an AI assistant skill that implements OpenSpec changes through a spec-driven workflow with structured planning, validation, and execution phases.
|
||||||
|
## files
|
||||||
|
- SKILL.md | Defines an AI assistant skill for implementing tasks from an OpenSpec change using a spec-driven workflow | dep: openspec CLI, AskUserQuestion tool, filesystem access
|
||||||
|
## arch
|
||||||
|
Template-based skill definition using markdown documentation with structured workflow phases (planning, validation, execution) and integration points for external tools (OpenSpec CLI, OpenCode agent).
|
||||||
|
## tags
|
||||||
|
skill, defines, assistant, implementing, tasks, openspec, change, spec
|
||||||
|
## symbols
|
||||||
|
-
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# .opencode/skills/openspec-archive-change (index)
|
||||||
|
dir: .opencode/skills/openspec-archive-change
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides a reusable automation skill for archiving completed experimental changes via the openspec CLI
|
||||||
|
## parent
|
||||||
|
index: .opencode/skills/.pi-map.index.md
|
||||||
|
map: .opencode/skills/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- SKILL.md
|
||||||
|
## links
|
||||||
|
index: .opencode/skills/openspec-archive-change/.pi-map.index.md
|
||||||
|
map: .opencode/skills/openspec-archive-change/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# .opencode/skills/openspec-archive-change
|
||||||
|
dir: .opencode/skills/openspec-archive-change
|
||||||
|
|
||||||
|
index: .opencode/skills/openspec-archive-change/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides a reusable automation skill for archiving completed experimental changes via the openspec CLI
|
||||||
|
## files
|
||||||
|
- SKILL.md | Defines a skill for archiving completed changes in an experimental workflow using the openspec CLI. | dep: openspec CLI, AskUserQuestion tool, Task tool, file system (mkdir, mv, read), JSON parsing
|
||||||
|
## arch
|
||||||
|
Skill-based modular automation pattern using markdown-defined CLI operations with structured metadata and command templates
|
||||||
|
## tags
|
||||||
|
skill, defines, archiving, completed, changes, experimental, workflow, openspec
|
||||||
|
## symbols
|
||||||
|
-
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# .opencode/skills/openspec-explore (index)
|
||||||
|
dir: .opencode/skills/openspec-explore
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines a conversational AI skill/persona for "explore mode" that serves as a thinking partner for exploring ideas, investigating problems, and clarifying requirements without implementing code.
|
||||||
|
## parent
|
||||||
|
index: .opencode/skills/.pi-map.index.md
|
||||||
|
map: .opencode/skills/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- SKILL.md
|
||||||
|
## links
|
||||||
|
index: .opencode/skills/openspec-explore/.pi-map.index.md
|
||||||
|
map: .opencode/skills/openspec-explore/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# .opencode/skills/openspec-explore
|
||||||
|
dir: .opencode/skills/openspec-explore
|
||||||
|
|
||||||
|
index: .opencode/skills/openspec-explore/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines a conversational AI skill/persona for "explore mode" that serves as a thinking partner for exploring ideas, investigating problems, and clarifying requirements without implementing code.
|
||||||
|
## files
|
||||||
|
- SKILL.md | Defines a conversational AI skill/persona for "explore mode" - a thinking partner for exploring ideas, investigating problems, and clarifying requirements without implementing code. | dep: openspec CLI
|
||||||
|
## arch
|
||||||
|
Single-file skill definition using markdown-based persona specification with structured sections for description, usage guidelines, and behavioral constraints.
|
||||||
|
## tags
|
||||||
|
skill, defines, conversational, persona, explore, mode, thinking, partner
|
||||||
|
## symbols
|
||||||
|
-
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# .opencode/skills/openspec-propose (index)
|
||||||
|
dir: .opencode/skills/openspec-propose
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides a structured workflow skill for proposing new changes using the openspec CLI with artifact generation in dependency order.
|
||||||
|
## parent
|
||||||
|
index: .opencode/skills/.pi-map.index.md
|
||||||
|
map: .opencode/skills/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- SKILL.md
|
||||||
|
## links
|
||||||
|
index: .opencode/skills/openspec-propose/.pi-map.index.md
|
||||||
|
map: .opencode/skills/openspec-propose/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# .opencode/skills/openspec-propose
|
||||||
|
dir: .opencode/skills/openspec-propose
|
||||||
|
|
||||||
|
index: .opencode/skills/openspec-propose/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides a structured workflow skill for proposing new changes using the openspec CLI with artifact generation in dependency order.
|
||||||
|
## files
|
||||||
|
- SKILL.md | Defines a structured workflow for proposing new changes using the openspec CLI, generating proposal, design, and task artifacts in dependency order. | dep: openspec CLI, AskUserQuestion tool, TodoWrite tool
|
||||||
|
## arch
|
||||||
|
Template-based skill definition using markdown documentation with sequential artifact generation (proposal → design → tasks) following dependency ordering.
|
||||||
|
## tags
|
||||||
|
skill, defines, structured, workflow, proposing, new, changes, openspec
|
||||||
|
## symbols
|
||||||
|
-
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# . (index)
|
||||||
|
dir: .
|
||||||
|
|
||||||
|
## Project Map Protocol
|
||||||
|
|
||||||
|
1. Read this protocol and the root `.pi-map.index.md` first.
|
||||||
|
2. Use `index:` / `map:` references to open relevant directory indexes and maps.
|
||||||
|
3. Load indexes before rich maps during task-start navigation.
|
||||||
|
4. Read the local rich map and actual source before editing.
|
||||||
|
5. Treat non-empty `## dirty` sections in either artifact as stale.
|
||||||
|
6. If source and generated artifacts disagree, trust source.
|
||||||
|
7. If map and index disagree, trust neither blindly; verify from source and regenerate the pair.
|
||||||
|
8. After editing source, run `project_map_patch` for each changed file.
|
||||||
|
9. Before broad architectural claims or final handoff, run `project_map_validate` when freshness matters.
|
||||||
|
|
||||||
|
Trust boundary: index routes, map orients, source decides.
|
||||||
|
|
||||||
|
## role
|
||||||
|
Infrastructure and deployment configuration package for a self-hosted project management platform with OAuth2 authentication, providing containerized orchestration, environment templates, and development tooling.
|
||||||
|
## parent
|
||||||
|
-
|
||||||
|
## children
|
||||||
|
- .atl
|
||||||
|
index: .atl/.pi-map.index.md
|
||||||
|
map: .atl/.pi-map.md
|
||||||
|
- .claude
|
||||||
|
index: .claude/.pi-map.index.md
|
||||||
|
map: .claude/.pi-map.md
|
||||||
|
- .opencode
|
||||||
|
index: .opencode/.pi-map.index.md
|
||||||
|
map: .opencode/.pi-map.md
|
||||||
|
- .pi
|
||||||
|
index: .pi/.pi-map.index.md
|
||||||
|
map: .pi/.pi-map.md
|
||||||
|
- .sisyphus
|
||||||
|
index: .sisyphus/.pi-map.index.md
|
||||||
|
map: .sisyphus/.pi-map.md
|
||||||
|
- .stoneforge
|
||||||
|
index: .stoneforge/.pi-map.index.md
|
||||||
|
map: .stoneforge/.pi-map.md
|
||||||
|
- apps
|
||||||
|
index: apps/.pi-map.index.md
|
||||||
|
map: apps/.pi-map.md
|
||||||
|
- docs
|
||||||
|
index: docs/.pi-map.index.md
|
||||||
|
map: docs/.pi-map.md
|
||||||
|
- e2e
|
||||||
|
index: e2e/.pi-map.index.md
|
||||||
|
map: e2e/.pi-map.md
|
||||||
|
- minerv3
|
||||||
|
index: minerv3/.pi-map.index.md
|
||||||
|
map: minerv3/.pi-map.md
|
||||||
|
- openspec
|
||||||
|
index: openspec/.pi-map.index.md
|
||||||
|
map: openspec/.pi-map.md
|
||||||
|
- scripts
|
||||||
|
index: scripts/.pi-map.index.md
|
||||||
|
map: scripts/.pi-map.md
|
||||||
|
- tool-images
|
||||||
|
index: tool-images/.pi-map.index.md
|
||||||
|
map: tool-images/.pi-map.md
|
||||||
|
- uploads
|
||||||
|
index: uploads/.pi-map.index.md
|
||||||
|
map: uploads/.pi-map.md
|
||||||
|
## files
|
||||||
|
- .env.example
|
||||||
|
- .gitignore
|
||||||
|
- AGENTS.md
|
||||||
|
- CHANGELOG.md
|
||||||
|
- Makefile
|
||||||
|
- README.md
|
||||||
|
- docker-compose.traefik.yml
|
||||||
|
- docker-compose.yml
|
||||||
|
- progress.md
|
||||||
|
- swap-pane
|
||||||
|
## links
|
||||||
|
index: ./.pi-map.index.md
|
||||||
|
map: ./.pi-map.md
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
# .
|
||||||
|
dir: .
|
||||||
|
|
||||||
|
index: ./.pi-map.index.md
|
||||||
|
|
||||||
|
## Project Map Protocol
|
||||||
|
|
||||||
|
1. Read this protocol and the root `.pi-map.index.md` first.
|
||||||
|
2. Use `index:` / `map:` references to open relevant directory indexes and maps.
|
||||||
|
3. Load indexes before rich maps during task-start navigation.
|
||||||
|
4. Read the local rich map and actual source before editing.
|
||||||
|
5. Treat non-empty `## dirty` sections in either artifact as stale.
|
||||||
|
6. If source and generated artifacts disagree, trust source.
|
||||||
|
7. If map and index disagree, trust neither blindly; verify from source and regenerate the pair.
|
||||||
|
8. After editing source, run `project_map_patch` for each changed file.
|
||||||
|
9. Before broad architectural claims or final handoff, run `project_map_validate` when freshness matters.
|
||||||
|
|
||||||
|
Trust boundary: index routes, map orients, source decides.
|
||||||
|
|
||||||
|
## role
|
||||||
|
Infrastructure and deployment configuration package for a self-hosted project management platform with OAuth2 authentication, providing containerized orchestration, environment templates, and development tooling.
|
||||||
|
## files
|
||||||
|
- .env.example | Provides a template of environment variables for configuring a Headquarter application with PostgreSQL, Redis, Authentik SSO, and Docker/Traefik deployment
|
||||||
|
- .gitignore | Specifies files and directories for Git to ignore across a multi-language project with Python, Node, and custom tooling | dep: Git
|
||||||
|
- AGENTS.md | Defines operational rules, workflows, and constraints for AI agents working within an OpenSpec-driven software development project. | dep: OpenSpec, superpowers, git, docker compose, conventional commits
|
||||||
|
- CHANGELOG.md | Documents version history and notable changes for a Git-based project management web application
|
||||||
|
- Makefile | Provides standard development commands for containerized web application lifecycle management via Docker Compose | dep: docker compose, alembic, pytest, ruff, mypy, playwright, npm, postgres, redis
|
||||||
|
- README.md | A self-hosted platform for managing projects, git repositories, and development tools with OAuth2 authentication. | dep: FastAPI, SQLAlchemy, Pydantic, Alembic, python-jose, React, TypeScript, Vite, React Router, Docker, PostgreSQL, Traefik, Authentik, Git
|
||||||
|
- docker-compose.traefik.yml | Deploys a multi-service web application (frontend, API, PostgreSQL, Redis) behind an existing Traefik reverse proxy with TLS termination and environment-configurable domains. | dep: docker, traefik, postgres, redis, authentik, docker-compose
|
||||||
|
- docker-compose.yml | Defines a multi-service Docker Compose stack with PostgreSQL, Redis, API backend, and web frontend services for a "headquarter" application | dep: Docker, PostgreSQL, Redis, Vite, asyncpg, nginx
|
||||||
|
- progress.md | Tracks completed and remaining tasks for a backend-frontend code refactoring project organized in 7 phases
|
||||||
|
- swap-pane | Empty file with no functionality
|
||||||
|
## arch
|
||||||
|
Docker Compose-based microservices architecture with frontend/backend separation, PostgreSQL/Redis data layer, Traefik reverse proxy integration, and environment-driven configuration management following twelve-factor app principles.
|
||||||
|
## tags
|
||||||
|
docker, redis, git, application, postgresql, compose, traefik, project
|
||||||
|
## symbols
|
||||||
|
-
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# .stoneforge (index)
|
||||||
|
dir: .stoneforge
|
||||||
|
|
||||||
|
## role
|
||||||
|
Internal configuration and state tracking directory for the Stoneforge application
|
||||||
|
## parent
|
||||||
|
index: ./.pi-map.index.md
|
||||||
|
map: ./.pi-map.md
|
||||||
|
## children
|
||||||
|
- .stoneforge/sync
|
||||||
|
index: .stoneforge/sync/.pi-map.index.md
|
||||||
|
map: .stoneforge/sync/.pi-map.md
|
||||||
|
## files
|
||||||
|
- .dashboard-opened
|
||||||
|
- .gitignore
|
||||||
|
- config.yaml
|
||||||
|
## links
|
||||||
|
index: .stoneforge/.pi-map.index.md
|
||||||
|
map: .stoneforge/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# .stoneforge
|
||||||
|
dir: .stoneforge
|
||||||
|
|
||||||
|
index: .stoneforge/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Internal configuration and state tracking directory for the Stoneforge application
|
||||||
|
## files
|
||||||
|
- .dashboard-opened | Stores timestamp and identifier data for tracking when a dashboard was opened
|
||||||
|
- .gitignore | Specifies files and patterns for Git to ignore in version control
|
||||||
|
- config.yaml | Configuration file for the Stoneforge application defining database, sync, playbook, identity, merge, workflow, and agent settings.
|
||||||
|
## arch
|
||||||
|
Simple dot-directory pattern storing metadata (.dashboard-opened), version control exclusions (.gitignore), and hierarchical YAML configuration (config.yaml) with domain-separated settings
|
||||||
|
## tags
|
||||||
|
config, stores, timestamp, identifier, data, tracking, dashboard, was
|
||||||
|
## symbols
|
||||||
|
-
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# .stoneforge/sync (index)
|
||||||
|
dir: .stoneforge/sync
|
||||||
|
|
||||||
|
## role
|
||||||
|
Persists distributed task execution state by storing dependency graphs and ephemeral worker agent records for a collaborative workflow system.
|
||||||
|
## parent
|
||||||
|
index: .stoneforge/.pi-map.index.md
|
||||||
|
map: .stoneforge/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- dependencies.jsonl
|
||||||
|
- elements.jsonl
|
||||||
|
## links
|
||||||
|
index: .stoneforge/sync/.pi-map.index.md
|
||||||
|
map: .stoneforge/sync/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# .stoneforge/sync
|
||||||
|
dir: .stoneforge/sync
|
||||||
|
|
||||||
|
index: .stoneforge/sync/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Persists distributed task execution state by storing dependency graphs and ephemeral worker agent records for a collaborative workflow system.
|
||||||
|
## files
|
||||||
|
- dependencies.jsonl | Stores a sequence of dependency relationships between entities in JSON Lines format, tracking parent-child, blocking, and reply relationships with timestamps and creators.
|
||||||
|
- elements.jsonl | Stores JSONL records of ephemeral worker agents with their session history, worktree assignments, and lifecycle metadata for a distributed task execution system.
|
||||||
|
## arch
|
||||||
|
Event-sourced JSONL append-only logs with entity-relationship modeling (parent-child, blocking, reply) and session-based worker lifecycle tracking.
|
||||||
|
## tags
|
||||||
|
stores, relationships, dependencies, elements, sequence, dependency, entities, json
|
||||||
|
## symbols
|
||||||
|
-
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# apps (index)
|
||||||
|
dir: apps
|
||||||
|
|
||||||
|
## role
|
||||||
|
Contains the main deployable application modules or entry points for the project.
|
||||||
|
## parent
|
||||||
|
index: ./.pi-map.index.md
|
||||||
|
map: ./.pi-map.md
|
||||||
|
## children
|
||||||
|
- apps/api
|
||||||
|
index: apps/api/.pi-map.index.md
|
||||||
|
map: apps/api/.pi-map.md
|
||||||
|
- apps/web
|
||||||
|
index: apps/web/.pi-map.index.md
|
||||||
|
map: apps/web/.pi-map.md
|
||||||
|
## files
|
||||||
|
## links
|
||||||
|
index: apps/.pi-map.index.md
|
||||||
|
map: apps/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# apps
|
||||||
|
dir: apps
|
||||||
|
|
||||||
|
index: apps/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Contains the main deployable application modules or entry points for the project.
|
||||||
|
## files
|
||||||
|
## arch
|
||||||
|
Modular monolith or microservices architecture with separate application boundaries, each potentially having its own configuration, dependencies, and lifecycle.
|
||||||
|
## tags
|
||||||
|
-
|
||||||
|
## symbols
|
||||||
|
-
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# apps/api (index)
|
||||||
|
dir: apps/api
|
||||||
|
|
||||||
|
## role
|
||||||
|
Self-hosted FastAPI backend API that manages projects, git repositories, and development tools via Docker instances.
|
||||||
|
## parent
|
||||||
|
index: apps/.pi-map.index.md
|
||||||
|
map: apps/.pi-map.md
|
||||||
|
## children
|
||||||
|
- apps/api/.pi-lens
|
||||||
|
index: apps/api/.pi-lens/.pi-map.index.md
|
||||||
|
map: apps/api/.pi-lens/.pi-map.md
|
||||||
|
- apps/api/.pytest_cache
|
||||||
|
index: apps/api/.pytest_cache/.pi-map.index.md
|
||||||
|
map: apps/api/.pytest_cache/.pi-map.md
|
||||||
|
- apps/api/.venv-test
|
||||||
|
index: apps/api/.venv-test/.pi-map.index.md
|
||||||
|
map: apps/api/.venv-test/.pi-map.md
|
||||||
|
- apps/api/alembic
|
||||||
|
index: apps/api/alembic/.pi-map.index.md
|
||||||
|
map: apps/api/alembic/.pi-map.md
|
||||||
|
- apps/api/src
|
||||||
|
index: apps/api/src/.pi-map.index.md
|
||||||
|
map: apps/api/src/.pi-map.md
|
||||||
|
- apps/api/tests
|
||||||
|
index: apps/api/tests/.pi-map.index.md
|
||||||
|
map: apps/api/tests/.pi-map.md
|
||||||
|
- apps/api/uploads
|
||||||
|
index: apps/api/uploads/.pi-map.index.md
|
||||||
|
map: apps/api/uploads/.pi-map.md
|
||||||
|
## files
|
||||||
|
- .dockerignore
|
||||||
|
- Dockerfile
|
||||||
|
- README.md
|
||||||
|
- alembic.ini
|
||||||
|
- pyproject.toml
|
||||||
|
- uv.lock
|
||||||
|
- wait-for-db.sh
|
||||||
|
## links
|
||||||
|
index: apps/api/.pi-map.index.md
|
||||||
|
map: apps/api/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# apps/api
|
||||||
|
dir: apps/api
|
||||||
|
|
||||||
|
index: apps/api/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Self-hosted FastAPI backend API that manages projects, git repositories, and development tools via Docker instances.
|
||||||
|
## files
|
||||||
|
- .dockerignore | Specifies files and directories to exclude from Docker build context to reduce image size and avoid copying unnecessary files into containers. | dep: Docker
|
||||||
|
- Dockerfile | Multi-stage Docker build for a Python application with Docker socket access, Cloudflare tunneling, and database dependency waiting | dep: python:3.11-slim, gcc, libpq-dev, docker-ce-cli, docker-compose-plugin, cloudflared, uvicorn, pyproject.toml dependencies
|
||||||
|
- README.md | Documentation for a self-hosted FastAPI backend API that manages projects, git repositories, and development tools via Docker instances. | dep: FastAPI, SQLAlchemy, PostgreSQL, asyncpg, Alembic, Docker, Docker Compose, Authentik, uvicorn, pytest, ruff, mypy
|
||||||
|
- alembic.ini | Configuration file for Alembic database migration tool connecting to a PostgreSQL database with async driver | dep: alembic, sqlalchemy, asyncpg, PostgreSQL
|
||||||
|
- pyproject.toml | Defines Python project metadata, dependencies, and tool configurations for a FastAPI-based backend API called "headquarter-api" | dep: fastapi, uvicorn, sqlalchemy, asyncpg, alembic, pydantic, pydantic-settings, python-multipart, httpx, structlog, cryptography, pytest, pytest-asyncio, mypy, ruff, aiosqlite
|
||||||
|
- uv.lock | Lock file for the uv Python package manager that pins exact dependency versions and their artifact hashes for reproducible installations | dep: uv, Python 3.11+, aiosqlite, alembic, annotated-doc, annotated-types, anyio, ast-serialize, asyncpg, and many other PyPI packages
|
||||||
|
- wait-for-db.sh | Wait for a PostgreSQL database to become available before executing a command, with configurable retry logic. | dep: nc (netcat), sh (POSIX shell), sleep
|
||||||
|
## arch
|
||||||
|
Async Python/FastAPI with PostgreSQL (Alembic migrations), multi-stage Docker deployment with Cloudflare tunneling, uv package management, and containerized service orchestration.
|
||||||
|
## tags
|
||||||
|
docker, alembic, python, database, fastapi, postgresql, asyncpg, uvicorn
|
||||||
|
## symbols
|
||||||
|
-
|
||||||
|
## workflows
|
||||||
|
-
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# apps/api/alembic (index)
|
||||||
|
dir: apps/api/alembic
|
||||||
|
|
||||||
|
## role
|
||||||
|
Database migration infrastructure for the API application, providing version-controlled schema evolution with async SQLAlchemy support.
|
||||||
|
## parent
|
||||||
|
index: apps/api/.pi-map.index.md
|
||||||
|
map: apps/api/.pi-map.md
|
||||||
|
## children
|
||||||
|
- apps/api/alembic/versions
|
||||||
|
index: apps/api/alembic/versions/.pi-map.index.md
|
||||||
|
map: apps/api/alembic/versions/.pi-map.md
|
||||||
|
## files
|
||||||
|
- env.py
|
||||||
|
- script.py.mako
|
||||||
|
## links
|
||||||
|
index: apps/api/alembic/.pi-map.index.md
|
||||||
|
map: apps/api/alembic/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change alembic behavior
|
||||||
|
read: env.py, script.py.mako
|
||||||
|
- explore alembic subdirectories
|
||||||
|
index: apps/api/alembic/versions/.pi-map.index.md
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# apps/api/alembic
|
||||||
|
dir: apps/api/alembic
|
||||||
|
|
||||||
|
index: apps/api/alembic/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Database migration infrastructure for the API application, providing version-controlled schema evolution with async SQLAlchemy support.
|
||||||
|
## files
|
||||||
|
- env.py | Configures Alembic database migration environment with async SQLAlchemy support for a project. | exp: func:run_migrations_offline() → None, call:context.configure, call:context.begin_transaction, call:context.run_migrations, func:do_run_migrations(connection: Connection) → None, call:context.configure, call:context.begin_transaction, call:context.run_migrations, func:run_async_migrations() → None, call:async_engine_from_config, call:config.get_section, call:connectable.connect, call:connection.run_sync, call:connectable.dispose, func:run_migrations_online() → None, call:asyncio.run, call:run_async_migrations | dep: logging.config, alembic, sqlalchemy, sqlalchemy.engine, sqlalchemy.ext.asyncio, src.config, src.models, asyncio
|
||||||
|
- script.py.mako | Alembic database migration script template that generates upgrade/downgrade functions for SQLAlchemy schema migrations | dep: alembic, sqlalchemy
|
||||||
|
## arch
|
||||||
|
Alembic migration framework with Mako templating for generating revision scripts, async SQLAlchemy engine configuration, and autogenerate capabilities for schema change tracking.
|
||||||
|
## tags
|
||||||
|
migrations, run, sqlalchemy, async, alembic, call:context.configure, call:context.begin, transaction
|
||||||
|
## symbols
|
||||||
|
- run_migrations_offline
|
||||||
|
- do_run_migrations
|
||||||
|
- run_async_migrations
|
||||||
|
- run_migrations_online
|
||||||
|
- call:context.configure
|
||||||
|
- call:context.begin_transaction
|
||||||
|
- call:context.run_migrations
|
||||||
|
- call:async_engine_from_config
|
||||||
|
## workflows
|
||||||
|
- change alembic behavior
|
||||||
|
read: env.py, script.py.mako
|
||||||
|
- explore alembic subdirectories
|
||||||
|
index: apps/api/alembic/versions/.pi-map.index.md
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# apps/api/alembic/versions (index)
|
||||||
|
dir: apps/api/alembic/versions
|
||||||
|
|
||||||
|
## role
|
||||||
|
Manages incremental database schema evolution for the API application using Alembic migrations, tracking all table creations, column additions, relationship changes, and data transformations over the project's lifecycle.
|
||||||
|
## parent
|
||||||
|
index: apps/api/alembic/.pi-map.index.md
|
||||||
|
map: apps/api/alembic/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- 0001_initial_schema.py
|
||||||
|
- 0002_refresh_tokens.py
|
||||||
|
- 0003_user_configs.py
|
||||||
|
- 0004_tool_types.py
|
||||||
|
- 0005_ssh_keys_timestamps.py
|
||||||
|
- 0006_tool_instances.py
|
||||||
|
- 0007_instance_container_name.py
|
||||||
|
- 0008_tool_type_category.py
|
||||||
|
- 0009_tool_configs.py
|
||||||
|
- 0010_tool_type_default_port.py
|
||||||
|
- 0011_tool_instance_tunnel_fields.py
|
||||||
|
- 0012_default_port_req.py
|
||||||
|
- 0013_add_config_profiles.py
|
||||||
|
- 0013_add_probe_result.py
|
||||||
|
- 0014_add_profile_resolver_fields.py
|
||||||
|
- 0014_merge_heads.py
|
||||||
|
- 0015_single_interface.py
|
||||||
|
- 069d3da4dc9b_add_ssh_key_id_to_config_profiles.py
|
||||||
|
- 20260527160017_add_pi_agent_tool_type.py
|
||||||
|
- 2026_05_22_add_clone_mode.py
|
||||||
|
- 2026_05_23_remove_is_builtin.py
|
||||||
|
- 2026_05_24_220141_add_startup_command.py
|
||||||
|
- 2026_05_24_add_config_profiles.py
|
||||||
|
- 2026_05_26_add_git_mounts.py
|
||||||
|
- 2026_05_27_external_repos.py
|
||||||
|
- 2026_05_28_add_monitoring_tables.py
|
||||||
|
- 2026_05_28_add_terminal_sessions_table.py
|
||||||
|
- 2026_05_28_add_tool_definition_manifests.py
|
||||||
|
- 2026_05_28_drop_tool_configs_and_config_folders.py
|
||||||
|
- 2026_05_29_add_notifications_table.py
|
||||||
|
- 2026_05_29_add_ssh_key_ids_to_tool_instances.py
|
||||||
|
- 2026_05_29_drop_ssh_key_id_from_config_profiles.py
|
||||||
|
- 2026_05_29_fix_code_server_bind_addr.py
|
||||||
|
- 2026_05_29_fix_code_server_bind_addr_port.py
|
||||||
|
- 2026_05_29_fix_web_tool_bind_address.py
|
||||||
|
- 2026_05_29_remove_lsio_command_override.py
|
||||||
|
- 2026_05_29_remove_ssh_keys_mount_from_manifest.py
|
||||||
|
- 2026_06_01_add_workspaces.py
|
||||||
|
- 2026_06_13_make_clone_mode_nullable.py
|
||||||
|
- 398082499c30_add_tool_config_fields.py
|
||||||
|
- 6fc7bfcf199f_merge_remove_is_builtin_and_add_config_.py
|
||||||
|
- 86cec91fdb00_merge_profile_resolver_and_workspaces_.py
|
||||||
|
- 8c6d1dbd4798_remove_pi_config_and_state_mounts_from_.py
|
||||||
|
- 8ed7dd80973d_create_config_folders_table.py
|
||||||
|
- af8512103d67_add_tool_type_fields.py
|
||||||
|
- f3d2dc90ba3a_merge_single_interface_and_clone_mode.py
|
||||||
|
## links
|
||||||
|
index: apps/api/alembic/versions/.pi-map.index.md
|
||||||
|
map: apps/api/alembic/versions/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change versions behavior
|
||||||
|
read: 0001_initial_schema.py, 0002_refresh_tokens.py, 0003_user_configs.py
|
||||||
|
- change versions CLI
|
||||||
|
read: 2026_05_24_220141_add_startup_command.py, 2026_05_29_remove_lsio_command_override.py
|
||||||
|
- change versions config
|
||||||
|
read: 0003_user_configs.py, 0009_tool_configs.py, 0013_add_config_profiles.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# apps/api/alembic/versions
|
||||||
|
dir: apps/api/alembic/versions
|
||||||
|
|
||||||
|
index: apps/api/alembic/versions/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Manages incremental database schema evolution for the API application using Alembic migrations, tracking all table creations, column additions, relationship changes, and data transformations over the project's lifecycle.
|
||||||
|
## files
|
||||||
|
- 0001_initial_schema.py | Defines the initial database schema migration creating five tables (users, ssh_keys, projects, git_repositories, user_configs) with relationships, indexes, and constraints using Alembic. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.String, call:postgresql.UUID, call:sa.DateTime, call:sa.func.now, call:sa.PrimaryKeyConstraint, call:sa.UniqueConstraint, call:op.create_index, call:op.f, call:sa.Text, call:sa.ForeignKeyConstraint, call:sa.Boolean, call:postgresql.JSONB, func:downgrade() → None, call:op.drop_table, call:op.drop_index, call:op.f | dep: alembic, sqlalchemy.dialects, sqlalchemy, postgresql dialect
|
||||||
|
- 0002_refresh_tokens.py | Alembic database migration that creates a refresh_tokens table with indexes for user authentication token management | exp: func:upgrade() → None, call:op.get_bind, call:sa.inspect, call:inspector.has_table, call:op.create_table, call:sa.Column, call:postgresql.UUID, call:sa.String, call:sa.DateTime, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:sa.UniqueConstraint, call:inspector.get_indexes, call:op.f, call:op.create_index, func:downgrade() → None, call:op.get_bind, call:sa.inspect, call:inspector.has_table, call:inspector.get_indexes, call:op.f, call:op.drop_index, call:op.drop_table | dep: alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
|
||||||
|
- 0003_user_configs.py | Alembic database migration that creates a user_configs table with JSON configuration storage linked to users | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.UUID, call:sa.JSON, call:sa.DateTime, call:sa.text, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:sa.UniqueConstraint, func:downgrade() → None, call:op.drop_table | dep: typing, alembic, sqlalchemy
|
||||||
|
- 0004_tool_types.py | Alembic database migration that creates a tool_types table with metadata, templates, and versioning columns for a tool management system. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.Uuid, call:sa.String, call:sa.Text, call:sa.JSON, call:sa.Boolean, call:sa.ForeignKey, call:sa.DateTime, call:sa.text, func:downgrade() → None, call:op.drop_table | dep: typing, alembic, sqlalchemy
|
||||||
|
- 0005_ssh_keys_timestamps.py | Alembic database migration that adds created_at and updated_at timestamp columns to the ssh_keys table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.DateTime, call:sa.text, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy
|
||||||
|
- 0006_tool_instances.py | Alembic database migration that creates a tool_instances table with columns for tracking deployed tool instances, their status, container info, and foreign key relationships to tool_types, git_repositories, projects, and users. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:postgresql.UUID, call:sa.text, call:sa.String, call:sa.Integer, call:sa.DateTime, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:op.create_index, func:downgrade() → None, call:op.drop_index, call:op.drop_table | dep: typing, alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
|
||||||
|
- 0007_instance_container_name.py | Alembic database migration that adds a nullable container_name column to the tool_instances table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.String, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy
|
||||||
|
- 0008_tool_type_category.py | Alembic database migration that adds `category` and `interfaces` columns to the `tool_types` table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.String, call:sa.JSON, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy
|
||||||
|
- 0009_tool_configs.py | Alembic database migration that creates a tool_configs table with UUID keys, foreign key relationships, and indexes for storing user/project tool configuration settings. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:postgresql.UUID, call:sa.text, call:sa.String, call:sa.Text, call:sa.DateTime, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:op.create_index, func:downgrade() → None, call:op.drop_index, call:op.drop_table | dep: typing, alembic, sqlalchemy.dialects, sqlalchemy, postgresql dialect
|
||||||
|
- 0010_tool_type_default_port.py | Alembic database migration that adds a nullable default_port column to the tool_types table. | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.Integer, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy
|
||||||
|
- 0011_tool_instance_tunnel_fields.py | Alembic database migration that adds tunnel-related fields (public_url and tunnel_id) to the tool_instances table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.String, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy
|
||||||
|
- 0012_default_port_req.py | Alembic database migration that populates null default_port values for existing tool types and then makes the column non-nullable | exp: func:upgrade() → None, call:op.execute, call:op.alter_column, call:sa.Integer, func:downgrade() → None, call:op.alter_column, call:sa.Integer | dep: typing, alembic, sqlalchemy
|
||||||
|
- 0013_add_config_profiles.py | Alembic database migration that adds config profiles, includes, mounts tables and links tool instances to profiles with defensive idempotent checks | exp: func:_table_exists(table_name: str) → bool, call:sa.inspect(op.get_bind()).has_table, call:op.get_bind, func:_column_exists(table_name: str, column_name: str) → bool, call:_table_exists, call:sa.inspect(op.get_bind()).get_columns, call:op.get_bind, func:_index_exists(table_name: str, index_name: str) → bool, call:_table_exists, call:sa.inspect(op.get_bind()).get_indexes, call:op.get_bind, func:_foreign_key_exists(table_name: str, constrained_columns: list[str], referred_table: str) → bool, call:_table_exists, call:sa.inspect(op.get_bind()).get_foreign_keys, call:op.get_bind, call:foreign_key.get, func:upgrade() → None, call:_table_exists, call:op.create_table, call:sa.Column, call:postgresql.UUID, call:sa.String, call:sa.Text, call:sa.DateTime, call:sa.text, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:sa.UniqueConstraint, call:_index_exists, call:op.create_index, call:sa.Integer, call:_column_exists, call:op.add_column, call:_foreign_key_exists, call:op.create_foreign_key, func:downgrade() → None, call:op.drop_index, call:op.drop_constraint, call:op.drop_column, call:op.drop_table | dep: collections.abc, alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
|
||||||
|
- 0013_add_probe_result.py | Alembic database migration that adds a JSON probe_result column to the tool_instances table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
|
||||||
|
- 0014_add_profile_resolver_fields.py | Alembic database migration that adds profile resolver fields (project_id, tool_type_id, environment_variables, etc.) to config_profiles table and restructures config_mounts table (renaming mount_path to target_path, adding mode/files, removing content/source_profile_id). | exp: func:_table_exists(table_name: str) → bool, call:sa.inspect(op.get_bind()).has_table, call:op.get_bind, func:_column_exists(table_name: str, column_name: str) → bool, call:_table_exists, call:sa.inspect(op.get_bind()).get_columns, call:op.get_bind, func:_index_exists(table_name: str, index_name: str) → bool, call:_table_exists, call:sa.inspect(op.get_bind()).get_indexes, call:op.get_bind, func:_foreign_key_exists(table_name: str, constrained_columns: list[str], referred_table: str) → bool, call:_table_exists, call:sa.inspect(op.get_bind()).get_foreign_keys, call:op.get_bind, call:foreign_key.get, func:_foreign_key_names_for_column(table_name: str, column_name: str) → list[str], call:_table_exists, call:sa.inspect(op.get_bind()).get_foreign_keys, call:op.get_bind, call:foreign_key.get, call:names.append, func:upgrade() → None, call:_column_exists, call:op.add_column, call:sa.Column, call:postgresql.UUID, call:sa.JSON, call:sa.Text, call:sa.Integer, call:sa.Boolean, call:_foreign_key_exists, call:op.create_foreign_key, call:_index_exists, call:op.create_index, call:op.alter_column, call:sa.String, call:_foreign_key_names_for_column, call:op.drop_constraint, call:op.drop_column, func:downgrade() → None, call:op.add_column, call:sa.Column, call:postgresql.UUID, call:sa.Text, call:op.drop_column, call:op.alter_column, call:op.drop_index, call:op.drop_constraint | dep: collections.abc, alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
|
||||||
|
- 0014_merge_heads.py | Alembic merge migration that reconciles two divergent migration branches into a single history line | exp: func:upgrade() → None, func:downgrade() → None | dep: typing, alembic
|
||||||
|
- 0015_single_interface.py | Alembic database migration that replaces a JSON array `interfaces` column with `interface_type` string and `requires_port` boolean columns in the `tool_types` table, with dialect-specific data migration for PostgreSQL and SQLite. | exp: func:_get_dialect() → str, call:op.get_bind, func:upgrade() → None, call:_get_dialect, call:op.add_column, call:sa.Column, call:sa.String, call:sa.Boolean, call:op.execute, call:op.alter_column, call:op.drop_column, call:op.create_check_constraint, call:sa.text, func:downgrade() → None, call:_get_dialect, call:op.drop_constraint, call:op.add_column, call:sa.Column, call:postgresql.JSONB, call:sa.Text, call:op.execute, call:sa.JSON, call:op.drop_column | dep: typing, alembic, sqlalchemy.dialects, sqlalchemy, postgresql (dialect)
|
||||||
|
- 069d3da4dc9b_add_ssh_key_id_to_config_profiles.py | Alembic database migration that adds a nullable UUID foreign key column `ssh_key_id` to the `config_profiles` table referencing `ssh_keys.id` with SET NULL on delete | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.Uuid, call:sa.ForeignKey, func:downgrade() → None, call:op.drop_column | dep: alembic, sqlalchemy
|
||||||
|
- 20260527160017_add_pi_agent_tool_type.py | Alembic database migration that adds a "pi-agent" terminal-based coding tool type to a tool_types table with Docker configuration templates | exp: func:upgrade() → None, call:op.get_bind, call:conn.execute( sa.text("SELECT id FROM tool_types WHERE name = 'pi-agent'") ).fetchone, call:sa.text, call:json.dumps, func:downgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text | dep: json, typing, alembic, uuid, sqlalchemy
|
||||||
|
- 2026_05_22_add_clone_mode.py | Alembic database migration that adds ssh_key_id foreign key to git_repositories table and clone_mode/branch columns to tool_instances table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:postgresql.UUID, call:op.create_foreign_key, call:sa.String, func:downgrade() → None, call:op.drop_column, call:op.drop_constraint | dep: alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
|
||||||
|
- 2026_05_23_remove_is_builtin.py | Alembic database migration to remove the `is_builtin` column from the `tool_types` table | exp: func:upgrade() → None, call:op.execute, func:downgrade() → None, call:op.add_column, call:sa.Column, call:sa.Boolean | dep: alembic, sqlalchemy
|
||||||
|
- 2026_05_24_220141_add_startup_command.py | Alembic database migration that adds a nullable `startup_command` text column to the `tool_types` table. | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.Text, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy
|
||||||
|
- 2026_05_24_add_config_profiles.py | Alembic database migration that creates config_profiles and config_profile_includes tables with indexes, and adds a foreign key column to tool_instances for managing user configuration profiles. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:postgresql.UUID, call:sa.text, call:sa.ForeignKey, call:sa.String, call:sa.Text, call:postgresql.JSONB, call:sa.Boolean, call:sa.DateTime, call:sa.PrimaryKeyConstraint, call:sa.UniqueConstraint, call:op.create_index, call:sa.Integer, call:op.add_column, func:downgrade() → None, call:op.drop_index, call:op.drop_column, call:op.drop_table | dep: typing, alembic, sqlalchemy.dialects, sqlalchemy, postgresql dialect
|
||||||
|
- 2026_05_26_add_git_mounts.py | Alembic database migration that adds a git_mounts JSON column to the config_profiles table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.JSON, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy
|
||||||
|
- 2026_05_27_external_repos.py | Alembic database migration that makes project_id nullable in git_repositories table to support external repositories and expands alembic_version version_num column to 64 characters. | exp: func:upgrade() → None, call:op.execute, call:op.alter_column, call:sa.UUID, func:downgrade() → None, call:op.alter_column, call:sa.UUID, call:op.execute | dep: typing, alembic, sqlalchemy
|
||||||
|
- 2026_05_28_add_monitoring_tables.py | Alembic database migration that creates monitoring tables (instance_events and health_checks) with indexes for tracking tool instance events and health checks | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.Uuid, call:sa.String, call:sa.Text, call:sa.JSON, call:sa.DateTime, call:sa.func.now, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:op.create_index, call:sa.Boolean, call:sa.Integer, func:downgrade() → None, call:op.drop_index, call:op.drop_table | dep: collections.abc, alembic, sqlalchemy
|
||||||
|
- 2026_05_28_add_terminal_sessions_table.py | Alembic database migration that creates a terminal_sessions table with tracking columns and foreign key to tool_instances | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.UUID, call:sa.String, call:sa.DateTime, call:sa.text, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:op.create_index, call:op.f, func:downgrade() → None, call:op.drop_index, call:op.f, call:op.drop_table | dep: collections.abc, alembic, sqlalchemy
|
||||||
|
- 2026_05_28_add_tool_definition_manifests.py | Alembic database migration that creates a tool_definition_manifests table, adds manifest-related columns to tool_types and tool_instances, and migrates the pi-agent tool from Dockerfile-based to manifest-based definitions with seed data. | exp: func:upgrade() → None, call:op.get_bind, call:op.create_table, call:sa.Column, call:sa.UUID, call:sa.String, call:sa.Text, call:sa.JSON, call:sa.Boolean, call:sa.TIMESTAMP, call:sa.func.now, call:sa.PrimaryKeyConstraint, call:sa.UniqueConstraint, call:sa.ForeignKeyConstraint, call:sa.CheckConstraint, call:conn.execute, call:sa.text, call:result.fetchone, call:op.add_column, call:op.create_foreign_key, call:op.drop_constraint, call:op.execute, call:json.dumps, call:str, func:downgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text, call:result.fetchone, call:op.drop_column, call:op.drop_constraint, call:op.drop_table | dep: json, uuid, typing, alembic, sqlalchemy
|
||||||
|
- 2026_05_28_drop_tool_configs_and_config_folders.py | Alembic database migration that drops `tool_configs` and `config_folders` tables with conditional existence checks and full downgrade recreation | exp: func:upgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text, call:result.fetchone, call:op.drop_table, func:downgrade() → None, call:op.create_table, call:sa.Column, call:sa.UUID, call:sa.String, call:sa.Text, call:sa.JSON, call:sa.Boolean, call:sa.TIMESTAMP, call:sa.func.now, call:sa.PrimaryKeyConstraint, call:sa.Integer | dep: typing, alembic, sqlalchemy
|
||||||
|
- 2026_05_29_add_notifications_table.py | Alembic database migration that creates a notifications table with user-linked, categorized, severity-graded messages supporting read/dismissed tracking and optimized querying indexes. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.Uuid, call:sa.String, call:sa.Text, call:sa.JSON, call:sa.DateTime, call:sa.func.now, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:op.create_index, call:sa.text, func:downgrade() → None, call:op.drop_index, call:op.drop_table | dep: collections.abc, alembic, sqlalchemy
|
||||||
|
- 2026_05_29_add_ssh_key_ids_to_tool_instances.py | Alembic database migration that adds a JSON column named ssh_key_ids to the tool_instances table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.JSON, func:downgrade() → None, call:op.drop_column | dep: alembic, sqlalchemy
|
||||||
|
- 2026_05_29_drop_ssh_key_id_from_config_profiles.py | Alembic database migration that removes the ssh_key_id column from the config_profiles table | exp: func:upgrade() → None, call:op.drop_column, func:downgrade() → None, call:op.add_column, call:sa.Column, call:sa.Uuid, call:sa.ForeignKey | dep: alembic, sqlalchemy
|
||||||
|
- 2026_05_29_fix_code_server_bind_addr.py | Alembic database migration that fixes code-server tool type compose templates by replacing deprecated `--bind-addr` flag with `--host` flag | exp: func:upgrade() → None, call:op.get_bind, call:conn.execute( sa.text(""" SELECT id, compose_template FROM tool_types WHERE name = 'code-server' AND compose_template LIKE '%--bind-addr%' """) ).fetchall, call:sa.text, call:compose_template.replace( "--bind-addr 0.0.0.0:8443", "--host 0.0.0.0" ).replace, call:print, func:downgrade() → None | dep: typing, alembic, sqlalchemy
|
||||||
|
- 2026_05_29_fix_code_server_bind_addr_port.py | Alembic database migration that fixes code-server Docker compose templates and instance files by replacing broken `--host` flags with correct `--bind-addr 0.0.0.0:port` configurations | exp: func:_fix_tool_type_templates(conn) → None, call:conn.execute( sa.text(""" SELECT id, compose_template, default_port FROM tool_types WHERE name = 'code-server' AND compose_template LIKE '%--host%' """) ).fetchall, call:sa.text, call:compose_template.split, call:len, call:line.lstrip, call:new_lines.append, call:"\n".join, call:print, func:_fix_instance_compose_files(conn) → None, call:conn.execute( sa.text(""" SELECT column_name FROM information_schema.columns WHERE table_name = 'tool_instances' AND column_name = 'compose_path' """) ).fetchone, call:sa.text, call:print, call:conn.execute( sa.text(""" SELECT id, compose_path, tool_type_id FROM tool_instances WHERE compose_path IS NOT NULL """) ).fetchall, call:Path, call:path.exists, call:path.read_text, call:conn.execute( sa.text(""" SELECT default_port FROM tool_types WHERE id = :id """), {"id": tool_type_id}, ).fetchone, call:yaml.safe_load, call:data["services"].values, call:path.write_text, call:yaml.dump, func:upgrade() → None, call:op.get_bind, call:_fix_tool_type_templates, call:_fix_instance_compose_files, func:downgrade() → None | dep: typing, alembic, yaml, pathlib, sqlalchemy
|
||||||
|
- 2026_05_29_fix_web_tool_bind_address.py | Alembic database migration that updates code-server and jupyter-notebook tool type compose templates to bind to 0.0.0.0 | exp: func:_fix_code_server_compose(conn) → None, call:conn.execute( sa.text(""" SELECT id, compose_template, definition_type FROM tool_types WHERE name = 'code-server' """) ).fetchone, call:sa.text, call:compose_template.split, call:enumerate, call:len, call:line.lstrip, call:new_lines.append, call:image_line.lstrip, call:new_lines.index, call:new_lines.insert, call:"\n".join, call:print, func:_fix_jupyter_compose(conn) → None, call:conn.execute( sa.text(""" SELECT id, compose_template, definition_type FROM tool_types WHERE name = 'jupyter-notebook' """) ).fetchone, call:sa.text, call:compose_template.split, call:enumerate, call:new_lines.append, call:len, call:line.lstrip, call:"\n".join, call:print, func:upgrade() → None, call:op.get_bind, call:_fix_code_server_compose, call:_fix_jupyter_compose, func:downgrade() → None | dep: typing, alembic, sqlalchemy
|
||||||
|
- 2026_05_29_remove_lsio_command_override.py | Alembic database migration that removes broken command overrides containing --bind-addr or --host flags from LinuxServer.io code-server Docker Compose templates in both database tool_types records and on-disk instance compose files. | exp: func:upgrade() → None, call:op.get_bind, call:conn.execute( sa.text(""" SELECT id, compose_template FROM tool_types WHERE name = 'code-server' """) ).fetchall, call:sa.text, call:yaml.safe_load, call:data["services"].values, call:svc.get, call:yaml.dump, call:print, call:conn.execute( sa.text(""" SELECT column_name FROM information_schema.columns WHERE table_name = 'tool_instances' AND column_name = 'compose_path' """) ).fetchone, call:conn.execute( sa.text(""" SELECT id, compose_path FROM tool_instances WHERE compose_path IS NOT NULL """) ).fetchall, call:Path, call:path.exists, call:path.read_text, call:path.write_text, func:downgrade() → None | dep: collections.abc, alembic, yaml, pathlib, sqlalchemy, pathlib.Path, information_schema
|
||||||
|
- 2026_05_29_remove_ssh_keys_mount_from_manifest.py | Alembic database migration that removes (or restores) the ssh_keys mount from a JSON manifest stored in the tool_definition_manifests table for the pi-agent tool definition. | exp: func:upgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text, call:result.fetchone, call:isinstance, call:json.loads, call:manifest.get, call:len, call:m.get, call:json.dumps, func:downgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text, call:result.fetchone, call:isinstance, call:json.loads, call:manifest.get, call:any, call:m.get, call:mounts.append, call:json.dumps | dep: json, typing, alembic, sqlalchemy
|
||||||
|
- 2026_06_01_add_workspaces.py | Alembic database migration that creates a workspaces table with foreign keys to git_repositories and users, adds indexes, and adds a workspace_id column to tool_instances | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.Uuid, call:sa.String, call:sa.ForeignKey, call:sa.DateTime, call:sa.text, call:sa.UniqueConstraint, call:op.create_index, call:op.add_column, func:downgrade() → None, call:op.drop_index, call:op.drop_column, call:op.drop_table | dep: collections.abc, alembic, sqlalchemy
|
||||||
|
- 2026_06_13_make_clone_mode_nullable.py | Alembic database migration that makes the `clone_mode` column in `tool_instances` table nullable to allow NULL values for new rows | exp: func:upgrade() → None, call:op.alter_column, call:sa.String, func:downgrade() → None, call:op.alter_column, call:sa.String | dep: alembic, sqlalchemy
|
||||||
|
- 398082499c30_add_tool_config_fields.py | Alembic database migration that adds five new columns (port_override, start_command, working_directory, environment_variables, volumes) to the tool_configs table with a port range check constraint. | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.Integer, call:sa.Text, call:postgresql.JSONB, call:op.create_check_constraint, call:sa.text, func:downgrade() → None, call:op.drop_constraint, call:op.drop_column | dep: alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
|
||||||
|
- 6fc7bfcf199f_merge_remove_is_builtin_and_add_config_.py | Alembic database migration that merges two parallel revision branches (removing is_builtin and adding config_profiles) into a single history line | exp: func:upgrade() → None, func:downgrade() → None | dep: alembic
|
||||||
|
- 86cec91fdb00_merge_profile_resolver_and_workspaces_.py | Alembic database migration that merges two divergent migration branches (profile resolver and workspaces) into a single head | exp: func:upgrade() → None, func:downgrade() → None | dep: alembic
|
||||||
|
- 8c6d1dbd4798_remove_pi_config_and_state_mounts_from_.py | Alembic database migration that removes pi_state and pi_config mounts from the pi-agent manifest in upgrade, and restores them in downgrade | exp: func:_load_manifest(manifest_json), call:isinstance, call:json.loads, func:upgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text, call:result.fetchone, call:_load_manifest, call:manifest.get, call:len, call:m.get, call:json.dumps, func:downgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text, call:result.fetchone, call:_load_manifest, call:manifest.get, call:m.get, call:mounts.append, call:json.dumps | dep: json, alembic, sqlalchemy
|
||||||
|
- 8ed7dd80973d_create_config_folders_table.py | Alembic database migration that creates a config_folders table with user-owned configuration folders supporting JSONB file storage and project overrides | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:postgresql.UUID, call:sa.text, call:sa.ForeignKey, call:sa.String, call:sa.Text, call:postgresql.JSONB, call:sa.Boolean, call:sa.DateTime, call:sa.UniqueConstraint, call:op.create_index, func:downgrade() → None, call:op.drop_index, call:op.drop_table | dep: alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
|
||||||
|
- af8512103d67_add_tool_type_fields.py | Alembic database migration that adds new columns (definition_type, dockerfile_template, build_context, readiness_probe) to the tool_types table with a CHECK constraint on definition_type. | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.String, call:sa.Text, call:postgresql.JSONB, call:op.create_check_constraint, call:sa.text, func:downgrade() → None, call:op.drop_constraint, call:op.drop_column | dep: alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
|
||||||
|
- f3d2dc90ba3a_merge_single_interface_and_clone_mode.py | Alembic database migration that merges two prior revisions (single_interface and clone_mode) into a single migration path | exp: func:upgrade() → None, func:downgrade() → None | dep: typing, alembic
|
||||||
|
## arch
|
||||||
|
Linear and branched migration pattern using Alembic's revision system with merge migrations to reconcile divergent branches; each migration is an imperative upgrade/downgrade script containing raw SQL/DDL operations, with some migrations including data seeding and dialect-specific logic (PostgreSQL/SQLite), but lacks consistent naming convention (mixed timestamp and numeric prefixes) indicating organic evolution rather than planned schema design.
|
||||||
|
## tags
|
||||||
|
column, table, call:op.drop, downgrade, alembic, upgrade, key, call:sa.text
|
||||||
|
## symbols
|
||||||
|
- upgrade
|
||||||
|
- downgrade
|
||||||
|
- _table_exists
|
||||||
|
- _column_exists
|
||||||
|
- _index_exists
|
||||||
|
- _foreign_key_exists
|
||||||
|
- _foreign_key_names_for_column
|
||||||
|
- _get_dialect
|
||||||
|
## workflows
|
||||||
|
- change versions behavior
|
||||||
|
read: 0001_initial_schema.py, 0002_refresh_tokens.py, 0003_user_configs.py
|
||||||
|
- change versions CLI
|
||||||
|
read: 2026_05_24_220141_add_startup_command.py, 2026_05_29_remove_lsio_command_override.py
|
||||||
|
- change versions config
|
||||||
|
read: 0003_user_configs.py, 0009_tool_configs.py, 0013_add_config_profiles.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -232,20 +232,6 @@ def upgrade() -> None:
|
|||||||
"writable": True,
|
"writable": True,
|
||||||
"owner": "user",
|
"owner": "user",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "pi_state",
|
|
||||||
"target": "/tmp/.pi/agents",
|
|
||||||
"source_type": "instance",
|
|
||||||
"writable": True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pi_config",
|
|
||||||
"target": "/home/user/.pi",
|
|
||||||
"source_type": "git_mount",
|
|
||||||
"git_mount_ref": "dotfiles",
|
|
||||||
"writable": True,
|
|
||||||
"owner": "user",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"command": ["/bin/bash"],
|
"command": ["/bin/bash"],
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
"""make clone_mode nullable
|
||||||
|
|
||||||
|
Revision ID: 2026_06_13_make_clone_mode_nullable
|
||||||
|
Revises: 86cec91fdb00
|
||||||
|
Create Date: 2026-06-13 10:00:00.000000
|
||||||
|
"""
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "2026_06_13_make_clone_mode_nullable"
|
||||||
|
down_revision = "86cec91fdb00"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# The workspace-first cleanup no longer writes clone_mode; existing rows
|
||||||
|
# keep their value, but new rows may be NULL.
|
||||||
|
op.alter_column(
|
||||||
|
"tool_instances",
|
||||||
|
"clone_mode",
|
||||||
|
existing_type=sa.String(20),
|
||||||
|
nullable=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.alter_column(
|
||||||
|
"tool_instances",
|
||||||
|
"clone_mode",
|
||||||
|
existing_type=sa.String(20),
|
||||||
|
nullable=False,
|
||||||
|
)
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
"""remove pi config and state mounts from pi-agent manifest
|
||||||
|
|
||||||
|
Revision ID: 8c6d1dbd4798
|
||||||
|
Revises: 2026_06_13_make_clone_mode_nullable
|
||||||
|
Create Date: 2026-06-13 11:21:25.983178
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '8c6d1dbd4798'
|
||||||
|
down_revision = '2026_06_13_make_clone_mode_nullable'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def _load_manifest(manifest_json):
|
||||||
|
return manifest_json if isinstance(manifest_json, dict) else json.loads(manifest_json)
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Remove pi_state and pi_config mounts from the pi-agent manifest."""
|
||||||
|
conn = op.get_bind()
|
||||||
|
|
||||||
|
result = conn.execute(
|
||||||
|
sa.text(
|
||||||
|
"SELECT id, manifest FROM tool_definition_manifests WHERE name = 'pi-agent'"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
row = result.fetchone()
|
||||||
|
if not row:
|
||||||
|
return
|
||||||
|
|
||||||
|
manifest_id, manifest_json = row
|
||||||
|
manifest = _load_manifest(manifest_json)
|
||||||
|
|
||||||
|
mounts = manifest.get("mounts", [])
|
||||||
|
original_count = len(mounts)
|
||||||
|
|
||||||
|
filtered_mounts = [
|
||||||
|
m for m in mounts if m.get("name") not in ("pi_state", "pi_config")
|
||||||
|
]
|
||||||
|
|
||||||
|
if len(filtered_mounts) < original_count:
|
||||||
|
manifest["mounts"] = filtered_mounts
|
||||||
|
conn.execute(
|
||||||
|
sa.text(
|
||||||
|
"UPDATE tool_definition_manifests SET manifest = :manifest WHERE id = :id"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"manifest": json.dumps(manifest),
|
||||||
|
"id": manifest_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Restore pi_state and pi_config mounts to the pi-agent manifest."""
|
||||||
|
conn = op.get_bind()
|
||||||
|
|
||||||
|
result = conn.execute(
|
||||||
|
sa.text(
|
||||||
|
"SELECT id, manifest FROM tool_definition_manifests WHERE name = 'pi-agent'"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
row = result.fetchone()
|
||||||
|
if not row:
|
||||||
|
return
|
||||||
|
|
||||||
|
manifest_id, manifest_json = row
|
||||||
|
manifest = _load_manifest(manifest_json)
|
||||||
|
|
||||||
|
mounts = manifest.get("mounts", [])
|
||||||
|
existing_names = {m.get("name") for m in mounts}
|
||||||
|
|
||||||
|
if "pi_state" not in existing_names:
|
||||||
|
mounts.append(
|
||||||
|
{
|
||||||
|
"name": "pi_state",
|
||||||
|
"target": "/tmp/.pi/agents",
|
||||||
|
"source_type": "instance",
|
||||||
|
"writable": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if "pi_config" not in existing_names:
|
||||||
|
mounts.append(
|
||||||
|
{
|
||||||
|
"name": "pi_config",
|
||||||
|
"target": "/home/user/.pi",
|
||||||
|
"source_type": "git_mount",
|
||||||
|
"git_mount_ref": "dotfiles",
|
||||||
|
"writable": True,
|
||||||
|
"owner": "user",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
manifest["mounts"] = mounts
|
||||||
|
conn.execute(
|
||||||
|
sa.text(
|
||||||
|
"UPDATE tool_definition_manifests SET manifest = :manifest WHERE id = :id"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"manifest": json.dumps(manifest),
|
||||||
|
"id": manifest_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# apps/api/src (index)
|
||||||
|
dir: apps/api/src
|
||||||
|
|
||||||
|
## role
|
||||||
|
Core API application package that initializes and configures the Headquarter FastAPI backend with database, authentication, logging, and middleware infrastructure.
|
||||||
|
## parent
|
||||||
|
index: apps/api/.pi-map.index.md
|
||||||
|
map: apps/api/.pi-map.md
|
||||||
|
## children
|
||||||
|
- apps/api/src/api
|
||||||
|
index: apps/api/src/api/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/.pi-map.md
|
||||||
|
- apps/api/src/auth
|
||||||
|
index: apps/api/src/auth/.pi-map.index.md
|
||||||
|
map: apps/api/src/auth/.pi-map.md
|
||||||
|
- apps/api/src/headquarter_api.egg-info
|
||||||
|
index: apps/api/src/headquarter_api.egg-info/.pi-map.index.md
|
||||||
|
map: apps/api/src/headquarter_api.egg-info/.pi-map.md
|
||||||
|
- apps/api/src/models
|
||||||
|
index: apps/api/src/models/.pi-map.index.md
|
||||||
|
map: apps/api/src/models/.pi-map.md
|
||||||
|
- apps/api/src/schemas
|
||||||
|
index: apps/api/src/schemas/.pi-map.index.md
|
||||||
|
map: apps/api/src/schemas/.pi-map.md
|
||||||
|
- apps/api/src/scripts
|
||||||
|
index: apps/api/src/scripts/.pi-map.index.md
|
||||||
|
map: apps/api/src/scripts/.pi-map.md
|
||||||
|
- apps/api/src/seeds
|
||||||
|
index: apps/api/src/seeds/.pi-map.index.md
|
||||||
|
map: apps/api/src/seeds/.pi-map.md
|
||||||
|
- apps/api/src/services
|
||||||
|
index: apps/api/src/services/.pi-map.index.md
|
||||||
|
map: apps/api/src/services/.pi-map.md
|
||||||
|
- apps/api/src/utils
|
||||||
|
index: apps/api/src/utils/.pi-map.index.md
|
||||||
|
map: apps/api/src/utils/.pi-map.md
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- config.py
|
||||||
|
- database.py
|
||||||
|
- logging_config.py
|
||||||
|
- main.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/.pi-map.index.md
|
||||||
|
map: apps/api/src/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change src behavior
|
||||||
|
read: __init__.py, config.py, database.py
|
||||||
|
- change src config
|
||||||
|
read: config.py, logging_config.py
|
||||||
|
- explore src subdirectories
|
||||||
|
index: apps/api/src/api/.pi-map.index.md, apps/api/src/auth/.pi-map.index.md, apps/api/src/headquarter_api.egg-info/.pi-map.index.md
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# apps/api/src
|
||||||
|
dir: apps/api/src
|
||||||
|
|
||||||
|
index: apps/api/src/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Core API application package that initializes and configures the Headquarter FastAPI backend with database, authentication, logging, and middleware infrastructure.
|
||||||
|
## files
|
||||||
|
- __init__.py | Marks the directory as a Python package for the Headquarter API.
|
||||||
|
- config.py | Defines application configuration settings with environment-based overrides using Pydantic, including database URLs, service domains, OAuth/Authentik integration, JWT/session settings, and computed properties for environment-specific behavior. | exp: class:Settings, func:build_database_url(user: str, password: str, host: str, port: int, database: str) → str | dep: pydantic, pydantic_settings
|
||||||
|
- database.py | Sets up async SQLAlchemy database engine and session factory, with retry logic for database connectivity testing and Alembic migration execution via subprocess. | exp: func:init_database(max_retries, retry_delay) → bool, call:range, call:engine.connect, call:test_conn.execute, call:text, call:test_conn.close, call:logger.info, call:asyncio.get_event_loop().run_in_executor, call:subprocess.run, call:os.path.dirname, call:os.path.abspath, call:logger.debug, call:logger.error, call:asyncio.sleep, call:str(exc).lower, call:logger.warning | dep: asyncio, logging, os, subprocess, sqlalchemy.ext.asyncio, sqlalchemy.pool, src.config, sqlalchemy
|
||||||
|
- logging_config.py | Configures structured JSON logging with correlation ID injection, custom formatters, and HTTP request/exception middleware for a FastAPI application. | exp: class:CorrelationIdFilter, method:filter(self, record: logging.LogRecord) → bool, call:get_correlation_id, class:JSONFormatter, method:format(self, record: logging.LogRecord) → str, call:self.formatTime, call:record.getMessage, call:getattr, call:self.formatException, call:json.dumps, method:formatTime(self, record: logging.LogRecord, datefmt) → str, call:time.strftime, call:time.gmtime, class:RequestLoggingMiddleware, method:dispatch(self, request: Request, call_next: Callable) → Response, call:time.time, call:logger.info, call:call_next, call:int, call:logger.error, call:type, call:traceback.format_exc, class:ExceptionLoggingMiddleware, method:dispatch(self, request: Request, call_next: Callable) → Response, call:call_next, call:logger.critical, call:traceback.format_exc, func:configure_logging(level) → None, call:JSONFormatter, call:logging.StreamHandler, call:console_handler.setFormatter, call:console_handler.addFilter, call:CorrelationIdFilter, call:root_logger.setLevel, call:logging.getLogger("uvicorn").setLevel, call:logging.getLogger("uvicorn.access").setLevel, call:logging.getLogger("sqlalchemy.engine").setLevel, call:logger.info, call:logging.getLevelName | dep: json, logging, sys, time, traceback, collections.abc, fastapi, starlette.middleware.base, src.services.shared.correlation
|
||||||
|
- main.py | Initializes and configures a FastAPI application for the "Headquarter API" with database setup, middleware, routing, and background services. | exp: func:_sanitize_validation_errors(errors), call:error.get, call:str, call:ctx.items, call:isinstance, call:type, call:sanitized.append, func:validation_exception_handler(request: Request, exc: RequestValidationError), call:exc.errors, call:logger.warning, call:_sanitize_validation_errors, call:JSONResponse, func:on_startup(), call:logger.info, call:init_database, call:logger.error, call:sys.exit, call:_health_monitor.start, call:seed_builtin_tool_types, func:on_shutdown(), call:logger.info, call:_health_monitor.stop | dep: logging, os, fastapi, fastapi.exceptions, fastapi.middleware.cors, fastapi.responses, fastapi.staticfiles, src.api.config, src.api.project, src.api.system, src.api.tool, src.api.user, src.api.workspace, src.config, src.models, src.database, src.logging_config, src.seeds.builtin_tool_types, src.services.instance, src.services.shared, sys, src.api.*
|
||||||
|
## arch
|
||||||
|
Layered architecture using Pydantic for environment-based configuration, async SQLAlchemy with Alembic migrations, structured JSON logging with correlation IDs, and FastAPI middleware/routing pattern for a service-oriented backend.
|
||||||
|
## tags
|
||||||
|
src, database, logging, call:logger.info, api, middleware, fastapi, filter
|
||||||
|
## symbols
|
||||||
|
- Settings
|
||||||
|
- CorrelationIdFilter
|
||||||
|
- JSONFormatter
|
||||||
|
- RequestLoggingMiddleware
|
||||||
|
- ExceptionLoggingMiddleware
|
||||||
|
- build_database_url
|
||||||
|
- init_database
|
||||||
|
- filter
|
||||||
|
## workflows
|
||||||
|
- change src behavior
|
||||||
|
read: __init__.py, config.py, database.py
|
||||||
|
- change src config
|
||||||
|
read: config.py, logging_config.py
|
||||||
|
- explore src subdirectories
|
||||||
|
index: apps/api/src/api/.pi-map.index.md, apps/api/src/auth/.pi-map.index.md, apps/api/src/headquarter_api.egg-info/.pi-map.index.md
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# apps/api/src/api (index)
|
||||||
|
dir: apps/api/src/api
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines shared API infrastructure including reusable Pydantic validators for consistent input validation across API endpoints.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/.pi-map.index.md
|
||||||
|
map: apps/api/src/.pi-map.md
|
||||||
|
## children
|
||||||
|
- apps/api/src/api/config
|
||||||
|
index: apps/api/src/api/config/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/config/.pi-map.md
|
||||||
|
- apps/api/src/api/project
|
||||||
|
index: apps/api/src/api/project/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/project/.pi-map.md
|
||||||
|
- apps/api/src/api/system
|
||||||
|
index: apps/api/src/api/system/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/system/.pi-map.md
|
||||||
|
- apps/api/src/api/tool
|
||||||
|
index: apps/api/src/api/tool/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/tool/.pi-map.md
|
||||||
|
- apps/api/src/api/user
|
||||||
|
index: apps/api/src/api/user/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/user/.pi-map.md
|
||||||
|
- apps/api/src/api/workspace
|
||||||
|
index: apps/api/src/api/workspace/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/workspace/.pi-map.md
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- shared_validators.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/api/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change api behavior
|
||||||
|
read: __init__.py, shared_validators.py
|
||||||
|
- explore api subdirectories
|
||||||
|
index: apps/api/src/api/config/.pi-map.index.md, apps/api/src/api/project/.pi-map.index.md, apps/api/src/api/system/.pi-map.index.md
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# apps/api/src/api
|
||||||
|
dir: apps/api/src/api
|
||||||
|
|
||||||
|
index: apps/api/src/api/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines shared API infrastructure including reusable Pydantic validators for consistent input validation across API endpoints.
|
||||||
|
## files
|
||||||
|
- __init__.py | Marks the directory as a Python package for API routers.
|
||||||
|
- shared_validators.py | Provides reusable Pydantic validator functions for API schema validation including mount paths, files, environment variables, and volume mounts. | exp: func:validate_mount_path(v: str | None) → str | None, call:v.startswith, raise:ValueError, func:validate_files(v: dict | None, max_size_bytes) → dict | None, call:v.items, call:path.startswith, call:len, call:content.encode, raise:ValueError, func:validate_env_vars(v: dict | None) → dict | None, call:isinstance, raise:ValueError, func:validate_volumes(v: list | None) → list | None, call:isinstance, call:enumerate, raise:ValueError
|
||||||
|
## arch
|
||||||
|
Modular utility package with functional validation helpers using Pydantic for declarative schema enforcement.
|
||||||
|
## tags
|
||||||
|
validate, raise:value, error, call:isinstance, mount, api, init, path
|
||||||
|
## symbols
|
||||||
|
- validate_mount_path
|
||||||
|
- validate_files
|
||||||
|
- validate_env_vars
|
||||||
|
- validate_volumes
|
||||||
|
- call:v.startswith
|
||||||
|
- raise:ValueError
|
||||||
|
- call:v.items
|
||||||
|
- call:path.startswith
|
||||||
|
## workflows
|
||||||
|
- change api behavior
|
||||||
|
read: __init__.py, shared_validators.py
|
||||||
|
- explore api subdirectories
|
||||||
|
index: apps/api/src/api/config/.pi-map.index.md, apps/api/src/api/project/.pi-map.index.md, apps/api/src/api/system/.pi-map.index.md
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# apps/api/src/api/config (index)
|
||||||
|
dir: apps/api/src/api/config
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides FastAPI API endpoints for managing user configuration settings and configuration profiles.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/api/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- config_profiles.py
|
||||||
|
- user_config.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/api/config/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/config/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change config behavior
|
||||||
|
read: __init__.py, config_profiles.py, user_config.py
|
||||||
|
- change config config
|
||||||
|
read: config_profiles.py, user_config.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# apps/api/src/api/config
|
||||||
|
dir: apps/api/src/api/config
|
||||||
|
|
||||||
|
index: apps/api/src/api/config/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides FastAPI API endpoints for managing user configuration settings and configuration profiles.
|
||||||
|
## files
|
||||||
|
- __init__.py | Aggregates and exports configuration-related API routers for the config module. | dep: src.api.config.config_profiles, src.api.config.user_config
|
||||||
|
- config_profiles.py | FastAPI router providing CRUD endpoints for user config profiles with includes, resolution, defaults, and git URL validation | exp: func:list_config_profiles(project_id, tool_type_id, current_user_id, session), call:select(ConfigProfile) .where(ConfigProfile.user_id == user_uuid) .options, call:selectinload, call:uuid.UUID, call:conditions.append, call:ConfigProfile.project_id.is_, call:ConfigProfile.tool_type_id.is_, call:query.where, call:or_, call:session.execute, call:result.scalars().all, call:profile_to_response, func:create_config_profile(data: ConfigProfileCreate, current_user_id, session), call:create_profile, call:logger.debug, call:profile_to_response, func:get_config_profile(profile_id: str, current_user_id, session), call:get_profile_with_includes, call:uuid.UUID, call:profile_to_response, raise:HTTPException, func:update_config_profile(profile_id: str, data: ConfigProfileUpdate, current_user_id, session), call:get_profile_with_includes, call:uuid.UUID, call:update_profile, call:logger.debug, call:profile_to_response, raise:HTTPException, func:delete_config_profile(profile_id: str, current_user_id, session), call:get_profile_with_includes, call:uuid.UUID, call:session.delete, call:session.commit, call:logger.debug, raise:HTTPException, func:update_profile_includes_endpoint(profile_id: str, data: ConfigProfileIncludeUpdate, current_user_id, session), call:get_profile_with_includes, call:uuid.UUID, call:update_includes, call:session.execute, call:select(ConfigProfileInclude).where, call:inc_result.scalars().all, call:logger.debug, call:profile_to_response, call:list, raise:HTTPException, func:preview_config_profile(profile_id: str, current_user_id, session), call:get_profile_with_includes, call:uuid.UUID, call:resolve_profile, call:resolved_profile_to_dict, raise:HTTPException, func:resolve_default_profile_endpoint(project_id, tool_type_id, current_user_id, session), call:resolve_default_profile, call:uuid.UUID, func:get_default_profiles_endpoint(user_id, session) → dict, call:session.execute, call:select(UserConfig).where, call:result.scalar_one_or_none, func:set_default_profiles_endpoint(data: DefaultProfilesUpdate, user_id, session) → dict, call:validate_default_profiles, call:get_or_create_user_config, call:session.commit, call:session.refresh, func:get_default_profile_for_tool_type_endpoint(tool_type_id: str, user_id, session) → dict, call:session.execute, call:select(UserConfig).where, call:result.scalar_one_or_none, call:user_config.default_profiles.get, func:validate_git_url_endpoint(data: ValidateGitUrlRequest, current_user_id, session) → ValidateGitUrlResponse, call:validate_git_url | dep: logging, uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, sqlalchemy.orm, src.auth.dependencies, src.models, src.schemas.config, src.services.config.config_profile_resolver, src.services.config.crud_service, src.services.config.resolver_service
|
||||||
|
- user_config.py | Provides FastAPI endpoints to get and update the current user's configuration settings, creating a default config if none exists. | exp: func:_get_or_create_config(session: AsyncSession, user_id: uuid.UUID) → UserConfig, call:session.execute, call:select(UserConfig).where, call:result.scalar_one_or_none, call:UserConfig, call:session.add, call:session.commit, call:session.refresh, func:get_user_config(user_id, session) → UserConfigResponse, call:_get_user, call:_get_or_create_config, call:UserConfigResponse.model_validate, func:update_user_config(data: UserConfigUpdate, user_id, session) → UserConfigResponse, call:_get_user, call:_get_or_create_config, call:data.model_dump, call:logger.debug, call:session.commit, call:session.refresh, call:UserConfigResponse.model_validate | dep: logging, uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.schemas.user
|
||||||
|
## arch
|
||||||
|
Modular FastAPI router pattern with separate route modules for distinct resource domains (user config vs config profiles), each implementing standard CRUD operations with validation and default initialization logic.
|
||||||
|
## tags
|
||||||
|
config, profile, get, user, includes, call:uuid.uuid, or, default
|
||||||
|
## symbols
|
||||||
|
- list_config_profiles
|
||||||
|
- create_config_profile
|
||||||
|
- get_config_profile
|
||||||
|
- update_config_profile
|
||||||
|
- delete_config_profile
|
||||||
|
- update_profile_includes_endpoint
|
||||||
|
- preview_config_profile
|
||||||
|
- resolve_default_profile_endpoint
|
||||||
|
## workflows
|
||||||
|
- change config behavior
|
||||||
|
read: __init__.py, config_profiles.py, user_config.py
|
||||||
|
- change config config
|
||||||
|
read: config_profiles.py, user_config.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
"""Config profile API endpoints."""
|
"""Config profile API endpoints."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
@@ -12,10 +9,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
from src.auth.dependencies import get_current_user_id, get_db_session
|
from src.auth.dependencies import get_current_user_id, get_db_session
|
||||||
from src.models import ConfigProfile, ConfigProfileInclude
|
from src.models import ConfigProfile, ConfigProfileInclude, UserConfig
|
||||||
from src.models.project import Project
|
|
||||||
from src.models import ToolType
|
|
||||||
from src.models import UserConfig
|
|
||||||
from src.schemas.config import (
|
from src.schemas.config import (
|
||||||
ConfigProfileCreate,
|
ConfigProfileCreate,
|
||||||
ConfigProfileIncludeUpdate,
|
ConfigProfileIncludeUpdate,
|
||||||
@@ -27,125 +21,27 @@ from src.schemas.config import (
|
|||||||
)
|
)
|
||||||
from src.services.config.config_profile_resolver import (
|
from src.services.config.config_profile_resolver import (
|
||||||
ConfigProfileCycleError,
|
ConfigProfileCycleError,
|
||||||
check_include_cycle,
|
|
||||||
resolve_profile,
|
resolve_profile,
|
||||||
resolved_profile_to_dict,
|
resolved_profile_to_dict,
|
||||||
)
|
)
|
||||||
from src.utils.git_url_parser import parse_git_url
|
from src.services.config.crud_service import (
|
||||||
|
create_profile,
|
||||||
|
get_or_create_user_config,
|
||||||
|
get_profile_with_includes,
|
||||||
|
profile_to_response,
|
||||||
|
update_includes,
|
||||||
|
update_profile,
|
||||||
|
validate_default_profiles,
|
||||||
|
)
|
||||||
|
from src.services.config.resolver_service import (
|
||||||
|
resolve_default_profile,
|
||||||
|
validate_git_url,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter(prefix="/config-profiles", tags=["config-profiles"])
|
router = APIRouter(prefix="/config-profiles", tags=["config-profiles"])
|
||||||
|
|
||||||
MAX_PROFILE_SIZE_MB = 10
|
|
||||||
MAX_PROFILE_SIZE_BYTES = MAX_PROFILE_SIZE_MB * 1024 * 1024
|
|
||||||
|
|
||||||
|
|
||||||
def _calculate_profile_size(data: dict) -> int:
|
|
||||||
"""Calculate approximate serialized size of profile data."""
|
|
||||||
total = 0
|
|
||||||
for key, value in data.get("env_vars", {}).items():
|
|
||||||
total += len(key.encode("utf-8")) + len(str(value).encode("utf-8"))
|
|
||||||
for key, value in data.get("runtime_hints", {}).items():
|
|
||||||
total += len(key.encode("utf-8")) + len(str(value).encode("utf-8"))
|
|
||||||
for mount in data.get("mounts", []):
|
|
||||||
total += len(str(mount.get("target", "")).encode("utf-8"))
|
|
||||||
total += len(str(mount.get("mode", "")).encode("utf-8"))
|
|
||||||
for path, content in mount.get("files", {}).items():
|
|
||||||
total += len(path.encode("utf-8")) + len(content.encode("utf-8"))
|
|
||||||
for path, content in data.get("files", {}).items():
|
|
||||||
total += len(path.encode("utf-8")) + len(content.encode("utf-8"))
|
|
||||||
return total
|
|
||||||
|
|
||||||
|
|
||||||
async def _get_profile_with_includes(
|
|
||||||
session: AsyncSession, profile_id: uuid.UUID
|
|
||||||
) -> ConfigProfile | None:
|
|
||||||
"""Fetch a profile with includes eagerly loaded."""
|
|
||||||
result = await session.execute(
|
|
||||||
select(ConfigProfile)
|
|
||||||
.where(ConfigProfile.id == profile_id)
|
|
||||||
.options(selectinload(ConfigProfile.includes))
|
|
||||||
)
|
|
||||||
return result.scalar_one_or_none()
|
|
||||||
|
|
||||||
|
|
||||||
async def _check_access(
|
|
||||||
session: AsyncSession,
|
|
||||||
user_id: uuid.UUID,
|
|
||||||
project_id: uuid.UUID | None = None,
|
|
||||||
tool_type_id: uuid.UUID | None = None,
|
|
||||||
) -> None:
|
|
||||||
"""Verify user has access to referenced project and tool type."""
|
|
||||||
if project_id is not None:
|
|
||||||
project = await session.get(Project, project_id)
|
|
||||||
if project is None:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
|
||||||
)
|
|
||||||
# Add ownership check if needed; for now just verify existence
|
|
||||||
if tool_type_id is not None:
|
|
||||||
tool_type = await session.get(ToolType, tool_type_id)
|
|
||||||
if tool_type is None:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Tool type not found"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def _validate_git_mounts(
|
|
||||||
session: AsyncSession,
|
|
||||||
user_id: uuid.UUID,
|
|
||||||
git_mounts: list[Any],
|
|
||||||
project_id: uuid.UUID | None = None,
|
|
||||||
) -> None:
|
|
||||||
"""Validate git mount URLs.
|
|
||||||
|
|
||||||
Simply checks that remote_url looks like a valid git URL.
|
|
||||||
Actual clone validation happens at instance startup time.
|
|
||||||
"""
|
|
||||||
for mount in git_mounts:
|
|
||||||
remote_url = mount.get("remote_url")
|
|
||||||
if not remote_url:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
|
||||||
detail="Git mount missing remote_url",
|
|
||||||
)
|
|
||||||
|
|
||||||
if not remote_url.startswith(("http://", "https://", "git@", "ssh://")):
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
|
||||||
detail=f"Invalid git URL: {remote_url}",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _profile_to_response(
|
|
||||||
profile: ConfigProfile, includes: list[ConfigProfileInclude] | None = None
|
|
||||||
) -> dict:
|
|
||||||
return {
|
|
||||||
"id": str(profile.id),
|
|
||||||
"user_id": str(profile.user_id),
|
|
||||||
"name": profile.name,
|
|
||||||
"description": profile.description,
|
|
||||||
"project_id": str(profile.project_id) if profile.project_id else None,
|
|
||||||
"tool_type_id": str(profile.tool_type_id) if profile.tool_type_id else None,
|
|
||||||
"env_vars": profile.env_vars or {},
|
|
||||||
"runtime_hints": profile.runtime_hints or {},
|
|
||||||
"mounts": profile.mounts or [],
|
|
||||||
"git_mounts": profile.git_mounts or [],
|
|
||||||
"files": profile.files or {},
|
|
||||||
"is_default": profile.is_default,
|
|
||||||
"includes": [
|
|
||||||
{
|
|
||||||
"id": str(inc.id),
|
|
||||||
"included_profile_id": str(inc.included_profile_id),
|
|
||||||
"order_index": inc.order_index,
|
|
||||||
}
|
|
||||||
for inc in (includes or profile.includes)
|
|
||||||
],
|
|
||||||
"created_at": profile.created_at.isoformat() if profile.created_at else None,
|
|
||||||
"updated_at": profile.updated_at.isoformat() if profile.updated_at else None,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=list[ConfigProfileResponse])
|
@router.get("", response_model=list[ConfigProfileResponse])
|
||||||
async def list_config_profiles(
|
async def list_config_profiles(
|
||||||
@@ -165,26 +61,21 @@ async def list_config_profiles(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if project_id or tool_type_id:
|
if project_id or tool_type_id:
|
||||||
# Compatibility filter: include portable profiles and matching scoped profiles
|
from sqlalchemy import or_
|
||||||
|
|
||||||
project_uuid = uuid.UUID(project_id) if project_id else None
|
project_uuid = uuid.UUID(project_id) if project_id else None
|
||||||
tool_uuid = uuid.UUID(tool_type_id) if tool_type_id else None
|
tool_uuid = uuid.UUID(tool_type_id) if tool_type_id else None
|
||||||
|
|
||||||
from sqlalchemy import or_
|
|
||||||
|
|
||||||
conditions: list = []
|
conditions: list = []
|
||||||
# Portable profiles (no project, no tool)
|
|
||||||
conditions.append(
|
conditions.append(
|
||||||
(ConfigProfile.project_id.is_(None))
|
(ConfigProfile.project_id.is_(None))
|
||||||
& (ConfigProfile.tool_type_id.is_(None))
|
& (ConfigProfile.tool_type_id.is_(None))
|
||||||
)
|
)
|
||||||
if project_uuid:
|
if project_uuid:
|
||||||
# Profiles matching this project (with or without tool)
|
|
||||||
conditions.append(ConfigProfile.project_id == project_uuid)
|
conditions.append(ConfigProfile.project_id == project_uuid)
|
||||||
if tool_uuid:
|
if tool_uuid:
|
||||||
# Profiles matching this tool (with or without project)
|
|
||||||
conditions.append(ConfigProfile.tool_type_id == tool_uuid)
|
conditions.append(ConfigProfile.tool_type_id == tool_uuid)
|
||||||
if project_uuid and tool_uuid:
|
if project_uuid and tool_uuid:
|
||||||
# Exact match
|
|
||||||
conditions.append(
|
conditions.append(
|
||||||
(ConfigProfile.project_id == project_uuid)
|
(ConfigProfile.project_id == project_uuid)
|
||||||
& (ConfigProfile.tool_type_id == tool_uuid)
|
& (ConfigProfile.tool_type_id == tool_uuid)
|
||||||
@@ -194,7 +85,7 @@ async def list_config_profiles(
|
|||||||
|
|
||||||
result = await session.execute(query)
|
result = await session.execute(query)
|
||||||
profiles = result.scalars().all()
|
profiles = result.scalars().all()
|
||||||
return [_profile_to_response(p) for p in profiles]
|
return [profile_to_response(p) for p in profiles]
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
@@ -206,69 +97,9 @@ async def create_config_profile(
|
|||||||
session: AsyncSession = Depends(get_db_session),
|
session: AsyncSession = Depends(get_db_session),
|
||||||
):
|
):
|
||||||
"""Create a new config profile."""
|
"""Create a new config profile."""
|
||||||
user_uuid = current_user_id
|
profile = await create_profile(session, current_user_id, data)
|
||||||
|
logger.debug("Created config profile %s for user %s", profile.id, current_user_id)
|
||||||
# Check for duplicate name
|
return profile_to_response(profile)
|
||||||
existing = await session.execute(
|
|
||||||
select(ConfigProfile)
|
|
||||||
.where(
|
|
||||||
ConfigProfile.user_id == user_uuid,
|
|
||||||
ConfigProfile.name == data.name,
|
|
||||||
)
|
|
||||||
.options(selectinload(ConfigProfile.includes))
|
|
||||||
)
|
|
||||||
if existing.scalar_one_or_none() is not None:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_409_CONFLICT,
|
|
||||||
detail=f"Profile with name '{data.name}' already exists",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Validate references
|
|
||||||
project_uuid = uuid.UUID(data.project_id) if data.project_id else None
|
|
||||||
tool_uuid = uuid.UUID(data.tool_type_id) if data.tool_type_id else None
|
|
||||||
await _check_access(session, user_uuid, project_uuid, tool_uuid)
|
|
||||||
|
|
||||||
# Validate git mounts reference existing repositories
|
|
||||||
if data.git_mounts:
|
|
||||||
git_mounts_data = [
|
|
||||||
m.model_dump() if hasattr(m, "model_dump") else m for m in data.git_mounts
|
|
||||||
]
|
|
||||||
await _validate_git_mounts(session, user_uuid, git_mounts_data, project_uuid)
|
|
||||||
|
|
||||||
# Check size
|
|
||||||
size = _calculate_profile_size(data.model_dump())
|
|
||||||
if size > MAX_PROFILE_SIZE_BYTES:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
|
|
||||||
detail=f"Profile size exceeds {MAX_PROFILE_SIZE_MB}MB limit",
|
|
||||||
)
|
|
||||||
|
|
||||||
profile = ConfigProfile(
|
|
||||||
user_id=user_uuid,
|
|
||||||
name=data.name,
|
|
||||||
description=data.description,
|
|
||||||
project_id=project_uuid,
|
|
||||||
tool_type_id=tool_uuid,
|
|
||||||
env_vars=data.env_vars,
|
|
||||||
runtime_hints=data.runtime_hints,
|
|
||||||
mounts=[m.model_dump() for m in data.mounts],
|
|
||||||
git_mounts=[m.model_dump() for m in data.git_mounts],
|
|
||||||
files=data.files,
|
|
||||||
is_default=data.is_default,
|
|
||||||
)
|
|
||||||
session.add(profile)
|
|
||||||
await session.commit()
|
|
||||||
|
|
||||||
# Re-fetch with includes to avoid lazy loading issues
|
|
||||||
result = await session.execute(
|
|
||||||
select(ConfigProfile)
|
|
||||||
.where(ConfigProfile.id == profile.id)
|
|
||||||
.options(selectinload(ConfigProfile.includes))
|
|
||||||
)
|
|
||||||
profile = result.scalar_one()
|
|
||||||
|
|
||||||
logger.debug("Created config profile %s for user %s", profile.id, user_uuid)
|
|
||||||
return _profile_to_response(profile)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{profile_id}", response_model=ConfigProfileResponse)
|
@router.get("/{profile_id}", response_model=ConfigProfileResponse)
|
||||||
@@ -278,7 +109,7 @@ async def get_config_profile(
|
|||||||
session: AsyncSession = Depends(get_db_session),
|
session: AsyncSession = Depends(get_db_session),
|
||||||
):
|
):
|
||||||
"""Get a config profile by ID."""
|
"""Get a config profile by ID."""
|
||||||
profile = await _get_profile_with_includes(session, uuid.UUID(profile_id))
|
profile = await get_profile_with_includes(session, uuid.UUID(profile_id))
|
||||||
if profile is None:
|
if profile is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Profile not found"
|
status_code=status.HTTP_404_NOT_FOUND, detail="Profile not found"
|
||||||
@@ -287,7 +118,7 @@ async def get_config_profile(
|
|||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized"
|
status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized"
|
||||||
)
|
)
|
||||||
return _profile_to_response(profile)
|
return profile_to_response(profile)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{profile_id}", response_model=ConfigProfileResponse)
|
@router.put("/{profile_id}", response_model=ConfigProfileResponse)
|
||||||
@@ -298,7 +129,7 @@ async def update_config_profile(
|
|||||||
session: AsyncSession = Depends(get_db_session),
|
session: AsyncSession = Depends(get_db_session),
|
||||||
):
|
):
|
||||||
"""Update a config profile."""
|
"""Update a config profile."""
|
||||||
profile = await _get_profile_with_includes(session, uuid.UUID(profile_id))
|
profile = await get_profile_with_includes(session, uuid.UUID(profile_id))
|
||||||
if profile is None:
|
if profile is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Profile not found"
|
status_code=status.HTTP_404_NOT_FOUND, detail="Profile not found"
|
||||||
@@ -308,78 +139,9 @@ async def update_config_profile(
|
|||||||
status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized"
|
status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized"
|
||||||
)
|
)
|
||||||
|
|
||||||
update_data = data.model_dump(exclude_unset=True)
|
profile = await update_profile(session, profile, data)
|
||||||
|
|
||||||
# Handle name uniqueness
|
|
||||||
if "name" in update_data:
|
|
||||||
existing = await session.execute(
|
|
||||||
select(ConfigProfile).where(
|
|
||||||
ConfigProfile.user_id == profile.user_id,
|
|
||||||
ConfigProfile.name == update_data["name"],
|
|
||||||
ConfigProfile.id != profile.id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if existing.scalar_one_or_none() is not None:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_409_CONFLICT,
|
|
||||||
detail=f"Profile with name '{update_data['name']}' already exists",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Validate references
|
|
||||||
project_uuid = (
|
|
||||||
uuid.UUID(update_data["project_id"])
|
|
||||||
if "project_id" in update_data and update_data["project_id"]
|
|
||||||
else (profile.project_id if "project_id" not in update_data else None)
|
|
||||||
)
|
|
||||||
tool_uuid = (
|
|
||||||
uuid.UUID(update_data["tool_type_id"])
|
|
||||||
if "tool_type_id" in update_data and update_data["tool_type_id"]
|
|
||||||
else (profile.tool_type_id if "tool_type_id" not in update_data else None)
|
|
||||||
)
|
|
||||||
await _check_access(session, profile.user_id, project_uuid, tool_uuid)
|
|
||||||
|
|
||||||
# Validate git mounts reference existing repositories
|
|
||||||
if "git_mounts" in update_data and update_data["git_mounts"] is not None:
|
|
||||||
git_mounts_data = [
|
|
||||||
m.model_dump() if hasattr(m, "model_dump") else m
|
|
||||||
for m in update_data["git_mounts"]
|
|
||||||
]
|
|
||||||
await _validate_git_mounts(
|
|
||||||
session, profile.user_id, git_mounts_data, project_uuid
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check size
|
|
||||||
current_data = _profile_to_response(profile)
|
|
||||||
merged = {**current_data, **update_data}
|
|
||||||
size = _calculate_profile_size(merged)
|
|
||||||
if size > MAX_PROFILE_SIZE_BYTES:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
|
|
||||||
detail=f"Profile size exceeds {MAX_PROFILE_SIZE_MB}MB limit",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Apply updates
|
|
||||||
for field_name, value in update_data.items():
|
|
||||||
if field_name in ("project_id", "tool_type_id"):
|
|
||||||
value = uuid.UUID(value) if value else None
|
|
||||||
elif field_name == "mounts" and value is not None:
|
|
||||||
value = [m.model_dump() if not isinstance(m, dict) else m for m in value]
|
|
||||||
elif field_name == "git_mounts" and value is not None:
|
|
||||||
value = [m.model_dump() if not isinstance(m, dict) else m for m in value]
|
|
||||||
setattr(profile, field_name, value)
|
|
||||||
|
|
||||||
await session.commit()
|
|
||||||
|
|
||||||
# Re-fetch with includes to avoid lazy loading issues
|
|
||||||
result = await session.execute(
|
|
||||||
select(ConfigProfile)
|
|
||||||
.where(ConfigProfile.id == profile.id)
|
|
||||||
.options(selectinload(ConfigProfile.includes))
|
|
||||||
)
|
|
||||||
profile = result.scalar_one()
|
|
||||||
|
|
||||||
logger.debug("Updated config profile %s", profile.id)
|
logger.debug("Updated config profile %s", profile.id)
|
||||||
return _profile_to_response(profile)
|
return profile_to_response(profile)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{profile_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{profile_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
@@ -389,7 +151,7 @@ async def delete_config_profile(
|
|||||||
session: AsyncSession = Depends(get_db_session),
|
session: AsyncSession = Depends(get_db_session),
|
||||||
):
|
):
|
||||||
"""Delete a config profile."""
|
"""Delete a config profile."""
|
||||||
profile = await _get_profile_with_includes(session, uuid.UUID(profile_id))
|
profile = await get_profile_with_includes(session, uuid.UUID(profile_id))
|
||||||
if profile is None:
|
if profile is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Profile not found"
|
status_code=status.HTTP_404_NOT_FOUND, detail="Profile not found"
|
||||||
@@ -407,14 +169,14 @@ async def delete_config_profile(
|
|||||||
|
|
||||||
|
|
||||||
@router.put("/{profile_id}/includes", response_model=ConfigProfileResponse)
|
@router.put("/{profile_id}/includes", response_model=ConfigProfileResponse)
|
||||||
async def update_profile_includes(
|
async def update_profile_includes_endpoint(
|
||||||
profile_id: str,
|
profile_id: str,
|
||||||
data: ConfigProfileIncludeUpdate,
|
data: ConfigProfileIncludeUpdate,
|
||||||
current_user_id: uuid.UUID = Depends(get_current_user_id),
|
current_user_id: uuid.UUID = Depends(get_current_user_id),
|
||||||
session: AsyncSession = Depends(get_db_session),
|
session: AsyncSession = Depends(get_db_session),
|
||||||
):
|
):
|
||||||
"""Update the ordered includes for a config profile."""
|
"""Update the ordered includes for a config profile."""
|
||||||
profile = await _get_profile_with_includes(session, uuid.UUID(profile_id))
|
profile = await get_profile_with_includes(session, uuid.UUID(profile_id))
|
||||||
if profile is None:
|
if profile is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Profile not found"
|
status_code=status.HTTP_404_NOT_FOUND, detail="Profile not found"
|
||||||
@@ -424,69 +186,8 @@ async def update_profile_includes(
|
|||||||
status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized"
|
status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Validate all included profiles exist and belong to the user
|
|
||||||
included_uuids = [uuid.UUID(inc_id) for inc_id in data.includes]
|
included_uuids = [uuid.UUID(inc_id) for inc_id in data.includes]
|
||||||
for inc_uuid in included_uuids:
|
profile = await update_includes(session, profile, included_uuids, current_user_id)
|
||||||
inc_profile = await session.get(ConfigProfile, inc_uuid)
|
|
||||||
if inc_profile is None:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
|
||||||
detail=f"Included profile not found: {inc_uuid}",
|
|
||||||
)
|
|
||||||
if inc_profile.user_id != current_user_id:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
|
||||||
detail=f"Not authorized to include profile: {inc_uuid}",
|
|
||||||
)
|
|
||||||
if inc_uuid == profile.id:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
|
||||||
detail="Profile cannot include itself",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check for cycles
|
|
||||||
cycle = await check_include_cycle(session, profile.id, None)
|
|
||||||
if cycle is None and included_uuids:
|
|
||||||
# Check each new include would not create a cycle
|
|
||||||
for inc_uuid in included_uuids:
|
|
||||||
cycle = await check_include_cycle(session, profile.id, inc_uuid)
|
|
||||||
if cycle is not None:
|
|
||||||
break
|
|
||||||
|
|
||||||
if cycle is not None:
|
|
||||||
cycle_str = " -> ".join(str(c) for c in cycle)
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
|
||||||
detail=f"Include cycle detected: {cycle_str}",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Remove existing includes
|
|
||||||
result = await session.execute(
|
|
||||||
select(ConfigProfileInclude).where(
|
|
||||||
ConfigProfileInclude.profile_id == profile.id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for existing in result.scalars().all():
|
|
||||||
await session.delete(existing)
|
|
||||||
await session.flush()
|
|
||||||
|
|
||||||
# Add new includes
|
|
||||||
for order_index, inc_uuid in enumerate(included_uuids):
|
|
||||||
include = ConfigProfileInclude(
|
|
||||||
profile_id=profile.id,
|
|
||||||
included_profile_id=inc_uuid,
|
|
||||||
order_index=order_index,
|
|
||||||
)
|
|
||||||
session.add(include)
|
|
||||||
await session.flush()
|
|
||||||
|
|
||||||
await session.commit()
|
|
||||||
|
|
||||||
# Re-fetch profile (includes loaded separately due to SQLite async issue)
|
|
||||||
result = await session.execute(
|
|
||||||
select(ConfigProfile).where(ConfigProfile.id == profile.id)
|
|
||||||
)
|
|
||||||
profile = result.scalar_one()
|
|
||||||
|
|
||||||
inc_result = await session.execute(
|
inc_result = await session.execute(
|
||||||
select(ConfigProfileInclude).where(
|
select(ConfigProfileInclude).where(
|
||||||
@@ -496,7 +197,7 @@ async def update_profile_includes(
|
|||||||
direct_includes = inc_result.scalars().all()
|
direct_includes = inc_result.scalars().all()
|
||||||
|
|
||||||
logger.debug("Updated includes for config profile %s", profile.id)
|
logger.debug("Updated includes for config profile %s", profile.id)
|
||||||
return _profile_to_response(profile, list(direct_includes))
|
return profile_to_response(profile, list(direct_includes))
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{profile_id}/preview")
|
@router.get("/{profile_id}/preview")
|
||||||
@@ -506,7 +207,7 @@ async def preview_config_profile(
|
|||||||
session: AsyncSession = Depends(get_db_session),
|
session: AsyncSession = Depends(get_db_session),
|
||||||
):
|
):
|
||||||
"""Preview the resolved output of a config profile."""
|
"""Preview the resolved output of a config profile."""
|
||||||
profile = await _get_profile_with_includes(session, uuid.UUID(profile_id))
|
profile = await get_profile_with_includes(session, uuid.UUID(profile_id))
|
||||||
if profile is None:
|
if profile is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Profile not found"
|
status_code=status.HTTP_404_NOT_FOUND, detail="Profile not found"
|
||||||
@@ -528,120 +229,19 @@ async def preview_config_profile(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/defaults/resolve")
|
@router.get("/defaults/resolve")
|
||||||
async def resolve_default_profile(
|
async def resolve_default_profile_endpoint(
|
||||||
project_id: str = Query(..., description="Project ID"),
|
project_id: str = Query(..., description="Project ID"),
|
||||||
tool_type_id: str = Query(..., description="Tool type ID"),
|
tool_type_id: str = Query(..., description="Tool type ID"),
|
||||||
current_user_id: uuid.UUID = Depends(get_current_user_id),
|
current_user_id: uuid.UUID = Depends(get_current_user_id),
|
||||||
session: AsyncSession = Depends(get_db_session),
|
session: AsyncSession = Depends(get_db_session),
|
||||||
):
|
):
|
||||||
"""Resolve the default config profile for a project/tool combination.
|
"""Resolve the default config profile for a project/tool combination."""
|
||||||
|
return await resolve_default_profile(
|
||||||
Selects by specificity:
|
session,
|
||||||
1. project+tool explicit default
|
current_user_id,
|
||||||
2. project explicit default
|
uuid.UUID(project_id),
|
||||||
3. tool explicit default
|
uuid.UUID(tool_type_id),
|
||||||
4. global/user explicit default
|
|
||||||
5. first created compatible profile
|
|
||||||
6. none (returns null)
|
|
||||||
"""
|
|
||||||
user_uuid = current_user_id
|
|
||||||
project_uuid = uuid.UUID(project_id)
|
|
||||||
tool_uuid = uuid.UUID(tool_type_id)
|
|
||||||
|
|
||||||
# Fetch all compatible profiles ordered by created_at
|
|
||||||
query = (
|
|
||||||
select(ConfigProfile)
|
|
||||||
.where(ConfigProfile.user_id == user_uuid)
|
|
||||||
.where(
|
|
||||||
(ConfigProfile.project_id.is_(None) & ConfigProfile.tool_type_id.is_(None))
|
|
||||||
| (ConfigProfile.project_id == project_uuid)
|
|
||||||
| (ConfigProfile.tool_type_id == tool_uuid)
|
|
||||||
| (
|
|
||||||
(ConfigProfile.project_id == project_uuid)
|
|
||||||
& (ConfigProfile.tool_type_id == tool_uuid)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.order_by(ConfigProfile.created_at)
|
|
||||||
)
|
)
|
||||||
result = await session.execute(query)
|
|
||||||
profiles = result.scalars().all()
|
|
||||||
|
|
||||||
if not profiles:
|
|
||||||
return {"profile_id": None, "profile_name": None}
|
|
||||||
|
|
||||||
# Check explicit defaults by specificity
|
|
||||||
explicit_defaults = [p for p in profiles if p.is_default]
|
|
||||||
|
|
||||||
# Most specific: project+tool
|
|
||||||
for p in explicit_defaults:
|
|
||||||
if p.project_id == project_uuid and p.tool_type_id == tool_uuid:
|
|
||||||
return {"profile_id": str(p.id), "profile_name": p.name}
|
|
||||||
|
|
||||||
# Next: project only
|
|
||||||
for p in explicit_defaults:
|
|
||||||
if p.project_id == project_uuid and p.tool_type_id is None:
|
|
||||||
return {"profile_id": str(p.id), "profile_name": p.name}
|
|
||||||
|
|
||||||
# Next: tool only
|
|
||||||
for p in explicit_defaults:
|
|
||||||
if p.project_id is None and p.tool_type_id == tool_uuid:
|
|
||||||
return {"profile_id": str(p.id), "profile_name": p.name}
|
|
||||||
|
|
||||||
# Next: global/user (no project, no tool)
|
|
||||||
for p in explicit_defaults:
|
|
||||||
if p.project_id is None and p.tool_type_id is None:
|
|
||||||
return {"profile_id": str(p.id), "profile_name": p.name}
|
|
||||||
|
|
||||||
# Fall back to first created compatible profile
|
|
||||||
first = profiles[0]
|
|
||||||
return {"profile_id": str(first.id), "profile_name": first.name}
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Default profile management
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
async def _get_or_create_user_config(
|
|
||||||
session: AsyncSession,
|
|
||||||
user_id: uuid.UUID,
|
|
||||||
) -> UserConfig:
|
|
||||||
"""Get existing user config or create a new one."""
|
|
||||||
result = await session.execute(
|
|
||||||
select(UserConfig).where(UserConfig.user_id == user_id)
|
|
||||||
)
|
|
||||||
user_config = result.scalar_one_or_none()
|
|
||||||
if user_config is None:
|
|
||||||
user_config = UserConfig(user_id=user_id, config={})
|
|
||||||
session.add(user_config)
|
|
||||||
return user_config
|
|
||||||
|
|
||||||
|
|
||||||
async def _validate_default_profiles(
|
|
||||||
session: AsyncSession,
|
|
||||||
user_id: uuid.UUID,
|
|
||||||
default_profiles: dict[str, str],
|
|
||||||
) -> None:
|
|
||||||
"""Validate that all profile IDs in default_profiles belong to the user."""
|
|
||||||
for tool_type_id, profile_id_str in default_profiles.items():
|
|
||||||
try:
|
|
||||||
profile_uuid = uuid.UUID(profile_id_str)
|
|
||||||
except ValueError:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
|
||||||
detail=f"Invalid profile ID for tool type {tool_type_id}: {profile_id_str}",
|
|
||||||
)
|
|
||||||
profile = await session.get(ConfigProfile, profile_uuid)
|
|
||||||
if profile is None:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
|
||||||
detail=f"Profile not found: {profile_id_str}",
|
|
||||||
)
|
|
||||||
if profile.user_id != user_id:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
|
||||||
detail=f"Profile does not belong to user: {profile_id_str}",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/defaults")
|
@router.get("/defaults")
|
||||||
@@ -664,8 +264,8 @@ async def set_default_profiles_endpoint(
|
|||||||
session: AsyncSession = Depends(get_db_session),
|
session: AsyncSession = Depends(get_db_session),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Set default profile mappings for the current user."""
|
"""Set default profile mappings for the current user."""
|
||||||
await _validate_default_profiles(session, user_id, data.default_profiles)
|
await validate_default_profiles(session, user_id, data.default_profiles)
|
||||||
user_config = await _get_or_create_user_config(session, user_id)
|
user_config = await get_or_create_user_config(session, user_id)
|
||||||
user_config.config = {
|
user_config.config = {
|
||||||
**user_config.config,
|
**user_config.config,
|
||||||
"default_profiles": data.default_profiles,
|
"default_profiles": data.default_profiles,
|
||||||
@@ -691,152 +291,10 @@ async def get_default_profile_for_tool_type_endpoint(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/validate-git-url", response_model=ValidateGitUrlResponse)
|
@router.post("/validate-git-url", response_model=ValidateGitUrlResponse)
|
||||||
async def validate_git_url(
|
async def validate_git_url_endpoint(
|
||||||
data: ValidateGitUrlRequest,
|
data: ValidateGitUrlRequest,
|
||||||
current_user_id: uuid.UUID = Depends(get_current_user_id),
|
current_user_id: uuid.UUID = Depends(get_current_user_id),
|
||||||
session: AsyncSession = Depends(get_db_session),
|
session: AsyncSession = Depends(get_db_session),
|
||||||
) -> ValidateGitUrlResponse:
|
) -> ValidateGitUrlResponse:
|
||||||
"""Validate a git remote URL and list available branches.
|
"""Validate a git remote URL and list available branches."""
|
||||||
|
return await validate_git_url(session, current_user_id, data.url, data.ssh_key_id)
|
||||||
Parses the URL, suggests corrections for browser URLs, and runs
|
|
||||||
git ls-remote to verify reachability and enumerate branches.
|
|
||||||
"""
|
|
||||||
parse_result = parse_git_url(data.url)
|
|
||||||
original_url = data.url.strip()
|
|
||||||
url_to_check = parse_result.get("base_url") or original_url
|
|
||||||
|
|
||||||
if not url_to_check:
|
|
||||||
return ValidateGitUrlResponse(
|
|
||||||
valid=False,
|
|
||||||
error=parse_result.get("message", "Invalid URL"),
|
|
||||||
error_code=parse_result.get("error_code", "INVALID_URL"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# If the URL needed parsing, return suggestion without checking remote
|
|
||||||
if parse_result.get("needs_parsing") and url_to_check != original_url:
|
|
||||||
return ValidateGitUrlResponse(
|
|
||||||
valid=False,
|
|
||||||
suggested_url=url_to_check,
|
|
||||||
error=parse_result.get("message"),
|
|
||||||
error_code=parse_result.get("error_code", "URL_NEEDS_PARSING"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Optional SSH key for private repos
|
|
||||||
env = None
|
|
||||||
key_path = None
|
|
||||||
if data.ssh_key_id:
|
|
||||||
from src.models import SSHKey
|
|
||||||
from src.services.shared.ssh_keys import _get_fernet
|
|
||||||
|
|
||||||
try:
|
|
||||||
ssh_key_uuid = uuid.UUID(data.ssh_key_id)
|
|
||||||
except ValueError:
|
|
||||||
return ValidateGitUrlResponse(
|
|
||||||
valid=False,
|
|
||||||
error="Invalid SSH key ID format",
|
|
||||||
error_code="INVALID_SSH_KEY",
|
|
||||||
)
|
|
||||||
|
|
||||||
ssh_key = await session.get(SSHKey, ssh_key_uuid)
|
|
||||||
if ssh_key is None or ssh_key.user_id != current_user_id:
|
|
||||||
return ValidateGitUrlResponse(
|
|
||||||
valid=False,
|
|
||||||
error="SSH key not found or not authorized",
|
|
||||||
error_code="SSH_KEY_NOT_FOUND",
|
|
||||||
)
|
|
||||||
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
fernet = _get_fernet()
|
|
||||||
private_key = fernet.decrypt(ssh_key.private_key_encrypted.encode()).decode()
|
|
||||||
fd, key_path = tempfile.mkstemp(prefix="ssh_key_")
|
|
||||||
try:
|
|
||||||
os.write(fd, private_key.encode())
|
|
||||||
finally:
|
|
||||||
os.close(fd)
|
|
||||||
os.chmod(key_path, 0o600)
|
|
||||||
env = {
|
|
||||||
"GIT_SSH_COMMAND": f"ssh -i {key_path} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
|
||||||
["git", "ls-remote", "--heads", url_to_check],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=30,
|
|
||||||
env={**os.environ, **env} if env else None,
|
|
||||||
)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
if key_path and os.path.exists(key_path):
|
|
||||||
os.unlink(key_path)
|
|
||||||
return ValidateGitUrlResponse(
|
|
||||||
valid=False,
|
|
||||||
error="Remote repository check timed out",
|
|
||||||
error_code="TIMEOUT",
|
|
||||||
)
|
|
||||||
except FileNotFoundError:
|
|
||||||
if key_path and os.path.exists(key_path):
|
|
||||||
os.unlink(key_path)
|
|
||||||
return ValidateGitUrlResponse(
|
|
||||||
valid=False,
|
|
||||||
error="git command not found on server",
|
|
||||||
error_code="GIT_NOT_FOUND",
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
if key_path and os.path.exists(key_path):
|
|
||||||
os.unlink(key_path)
|
|
||||||
|
|
||||||
if result.returncode != 0:
|
|
||||||
stderr = result.stderr.strip()
|
|
||||||
if (
|
|
||||||
"could not resolve" in stderr.lower()
|
|
||||||
or "unable to access" in stderr.lower()
|
|
||||||
):
|
|
||||||
error_msg = "Could not reach repository. Check the URL and network access."
|
|
||||||
error_code = "UNREACHABLE"
|
|
||||||
elif (
|
|
||||||
"authentication" in stderr.lower() or "permission denied" in stderr.lower()
|
|
||||||
):
|
|
||||||
error_msg = (
|
|
||||||
"Authentication failed. Provide an SSH key for private repositories."
|
|
||||||
)
|
|
||||||
error_code = "AUTH_FAILED"
|
|
||||||
else:
|
|
||||||
error_msg = f"Repository not accessible: {stderr[:200]}"
|
|
||||||
error_code = "REMOTE_ERROR"
|
|
||||||
return ValidateGitUrlResponse(
|
|
||||||
valid=False,
|
|
||||||
error=error_msg,
|
|
||||||
error_code=error_code,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Parse branches from ls-remote output
|
|
||||||
branches: list[str] = []
|
|
||||||
default_branch = "main"
|
|
||||||
for line in result.stdout.strip().split("\n"):
|
|
||||||
if not line.strip():
|
|
||||||
continue
|
|
||||||
parts = line.split()
|
|
||||||
if len(parts) == 2:
|
|
||||||
ref = parts[1]
|
|
||||||
# refs/heads/branch-name
|
|
||||||
if ref.startswith("refs/heads/"):
|
|
||||||
branch_name = ref[len("refs/heads/") :]
|
|
||||||
branches.append(branch_name)
|
|
||||||
if branch_name in ("main", "master"):
|
|
||||||
default_branch = branch_name
|
|
||||||
|
|
||||||
if not branches:
|
|
||||||
return ValidateGitUrlResponse(
|
|
||||||
valid=False,
|
|
||||||
error="No branches found in remote repository",
|
|
||||||
error_code="NO_BRANCHES",
|
|
||||||
)
|
|
||||||
|
|
||||||
return ValidateGitUrlResponse(
|
|
||||||
valid=True,
|
|
||||||
suggested_url=url_to_check if url_to_check != original_url else None,
|
|
||||||
branches=branches,
|
|
||||||
default_branch=default_branch,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# apps/api/src/api/project (index)
|
||||||
|
dir: apps/api/src/api/project
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides FastAPI REST API endpoints for project and Git repository management, serving as the HTTP interface layer for the project's core domain operations.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/api/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- git_repositories.py
|
||||||
|
- projects.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/api/project/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/project/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change project behavior
|
||||||
|
read: __init__.py, git_repositories.py, projects.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
|||||||
|
# apps/api/src/api/system (index)
|
||||||
|
dir: apps/api/src/api/system
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides system-level API endpoints for monitoring, administration, and infrastructure interaction including dashboards, health checks, event streaming, notifications, and container terminal access.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/api/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- dashboard.py
|
||||||
|
- events.py
|
||||||
|
- health.py
|
||||||
|
- instance_proxy.py
|
||||||
|
- notifications.py
|
||||||
|
- terminal.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/api/system/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/system/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change system behavior
|
||||||
|
read: __init__.py, dashboard.py, events.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# apps/api/src/api/system
|
||||||
|
dir: apps/api/src/api/system
|
||||||
|
|
||||||
|
index: apps/api/src/api/system/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides system-level API endpoints for monitoring, administration, and infrastructure interaction including dashboards, health checks, event streaming, notifications, and container terminal access.
|
||||||
|
## files
|
||||||
|
- __init__.py | Aggregates and re-exports system API router modules from submodules for centralized access. | dep: src.api.system.dashboard, src.api.system.events, src.api.system.health, src.api.system.instance_proxy, src.api.system.notifications, src.api.system.terminal
|
||||||
|
- dashboard.py | Provides a FastAPI endpoint that returns a dashboard summary with aggregated counts of projects, repositories, SSH keys, and recent activity for the authenticated user. | exp: func:get_dashboard_summary(user_id, session) → dict, call:session.execute, call:select(func.count()).select_from(Project).where, call:func.count, call:projects_result.scalar, call:select(func.count()).select_from(GitRepository).where, call:repos_result.scalar, call:select(func.count()).select_from(SSHKey).where, call:ssh_keys_result.scalar, call:select(Project) .where(Project.owner_id == user_id) .order_by(Project.created_at.desc()) .limit, call:Project.created_at.desc, call:recent_projects.scalars().all | dep: uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.models.project, src.models (GitRepository, Project, SSHKey)
|
||||||
|
- events.py | Implements an SSE streaming endpoint that delivers instance events to authenticated users with per-user connection limits and keep-alive pings. | exp: func:events_stream(request: Request, user_id) → StreamingResponse, call:_connection_counts.get, call:InstanceEventBus, call:asyncio.Queue, call:queue.put_nowait, call:contextlib.suppress, call:queue.get_nowait, call:event_bus.subscribe, call:asyncio.wait_for, call:queue.get, call:json.dumps, call:unsubscribe, call:max, call:_connection_counts.pop, call:StreamingResponse, call:event_generator, raise:HTTPException, func:event_generator() → AsyncGenerator[str, None], call:InstanceEventBus, call:asyncio.Queue, call:queue.put_nowait, call:contextlib.suppress, call:queue.get_nowait, call:event_bus.subscribe, call:asyncio.wait_for, call:queue.get, call:json.dumps, call:unsubscribe, call:max, call:_connection_counts.get, call:_connection_counts.pop, func:on_event(payload: InstanceEventPayload) → None, call:queue.put_nowait, call:contextlib.suppress, call:queue.get_nowait | dep: asyncio, contextlib, json, uuid, collections.abc, fastapi, fastapi.responses, src.auth.dependencies, src.services.instance.event_bus
|
||||||
|
- health.py | Implements FastAPI health check endpoints for monitoring system status including database connectivity and disk space. | exp: func:health_check() → dict[str, Any], call:HealthChecks, call:time_module.perf_counter, call:SessionLocal, call:session.execute, call:text, call:DatabaseHealth, call:round, call:shutil.disk_usage, call:DiskHealth, call:HealthResponse( status=overall_status, timestamp=datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"), version="0.1.0", checks=checks, uptime_seconds=round(time.time() - _start_time, 2), ).model_dump, call:datetime.now(timezone.utc).isoformat().replace, call:time.time, func:health_check_db() → dict[str, Any], call:time_module.perf_counter, call:SessionLocal, call:session.execute, call:text, call:DatabaseHealthResponse( status="healthy", response_time_ms=round(db_time, 2), ).model_dump, call:round, call:DatabaseHealthResponse( status="unhealthy", response_time_ms=0.0, ).model_dump | dep: time, datetime, typing, fastapi, sqlalchemy, src.database, src.schemas.system, shutil
|
||||||
|
- instance_proxy.py | Proxies HTTP requests from FastAPI endpoints to running containerized tool instances after verifying ownership and status. | exp: func:_proxy_request(request: Request, instance_id: uuid.UUID, path: str, user_id: uuid.UUID, session: AsyncSession) → Response, call:session.get, call:str, call:request.headers.items, call:key.lower, call:httpx.AsyncClient, call:request.body, call:client.request, call:logger.error, call:dict, call:response_headers.pop, call:Response, raise:HTTPException, func:proxy_to_instance(request: Request, instance_id: uuid.UUID, path, user_id, session) → Response, call:_proxy_request | dep: logging, uuid, httpx, fastapi, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models
|
||||||
|
- notifications.py | Defines FastAPI REST endpoints for managing user notifications (list, unread count, mark read, dismiss/clear) with support for muted categories from user config. | exp: class:NotificationItem, class:NotificationListResponse, class:UnreadCountResponse, class:MarkAllReadResponse, class:ClearAllResponse, func:_get_mute_categories(session: AsyncSession, user_id: uuid.UUID) → list[str], call:session.execute, call:select(UserConfig).where, call:result.scalar_one_or_none, call:config.config.get, call:isinstance, func:list_notifications(limit, offset, unread_only, user, session) → NotificationListResponse, call:_get_mute_categories, call:notification_service.list_notifications, call:NotificationListResponse, call:NotificationItem.model_validate, func:get_unread_count(user, session) → UnreadCountResponse, call:notification_service.get_unread_count, call:UnreadCountResponse, func:mark_notification_read(notification_id: uuid.UUID, user, session) → NotificationItem, call:notification_service.mark_read, call:NotificationItem.model_validate, raise:HTTPException, func:mark_all_read(user, session) → MarkAllReadResponse, call:notification_service.mark_all_read, call:MarkAllReadResponse, func:clear_all_notifications(user, session) → ClearAllResponse, call:notification_service.dismiss_all, call:ClearAllResponse, func:dismiss_notification(notification_id: uuid.UUID, user, session) → None, call:notification_service.dismiss, raise:HTTPException | dep: uuid, datetime, fastapi, pydantic, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models.user, src.models, src.services.shared.notification_service, sqlalchemy
|
||||||
|
- terminal.py | Provides WebSocket and HTTP endpoints for managing interactive terminal sessions attached to running Docker container tool instances, including session creation, attachment, input/output streaming, resize/reset control messages, and session listing. | exp: class:SessionRef, method:__init__(self, session, slot_session_id), func:terminal_websocket_default(websocket: WebSocket, instance_id: str, db_session) → None, call:_handle_terminal_websocket, func:terminal_websocket_specific(websocket: WebSocket, instance_id: str, session_id: str, db_session) → None, call:_handle_terminal_websocket, func:_handle_terminal_websocket(websocket: WebSocket, instance_id: str, target_session_id: str | None, db_session: AsyncSession) → None, call:logger.debug, call:websocket.accept, call:uuid.UUID, call:logger.error, call:websocket.close, call:_get_user_from_websocket, call:logger.warning, call:db_session.get, call:get_container_status, call:terminal_manager.get_or_create_session, call:terminal_manager.get_session, call:logger.info, call:terminal_manager.create_session, call:terminal_manager._find_key_by_internal_id, call:terminal_manager.attach_websocket, call:websocket.send_json, call:SessionRef, call:asyncio.create_task, call:_write_loop, call:_heartbeat_loop, call:asyncio.wait, call:len, call:task.cancel, call:str, call:suppress, call:terminal_manager.detach_websocket, func:_write_loop(session_ref: SessionRef, websocket, instance_id: str) → None, call:session.is_alive, call:asyncio.sleep, call:websocket.receive, call:session.write_input, call:text.startswith, call:json.loads, call:ctrl.get, call:logger.debug, call:session.resize, call:session.acknowledge_data, call:websocket.send_json, call:terminal_manager.reset_session, call:terminal_manager.attach_websocket, call:text.encode, func:_heartbeat_loop(websocket: WebSocket) → None, call:asyncio.sleep, call:websocket.send_json, func:_get_terminal_instance(instance_id: uuid.UUID, user_id: uuid.UUID, db_session: AsyncSession) → ToolInstance, call:db_session.get, raise:HTTPException, func:list_terminal_sessions(instance_id: uuid.UUID, user_id, db_session) → dict, call:_get_terminal_instance, call:db_session.execute, call:select(TerminalSessionModel) .where(TerminalSessionModel.instance_id == instance_id) .where(TerminalSessionModel.status != "closed") .order_by, call:TerminalSessionModel.created_at.asc, call:result.scalars().all, call:terminal_manager.get_session, call:str, call:sessions.append, call:live_session.has_websockets, call:row.created_at.isoformat, call:row.last_activity_at.isoformat, func:create_terminal_session(instance_id: uuid.UUID, data: dict, user_id, db_session) → dict, call:_get_terminal_instance, call:db_session.get, call:data.get, call:terminal_manager.create_session, raise:HTTPException, func:close_terminal_session(instance_id: uuid.UUID, session_id: str, user_id, db_session) → dict, call:_get_terminal_instance, call:terminal_manager._find_key_by_internal_id, call:str, call:terminal_manager.get_session, call:terminal_manager.close_session, raise:HTTPException, func:reset_specific_terminal_session(instance_id: uuid.UUID, session_id: str, user_id, db_session) → dict, call:_get_terminal_instance, call:terminal_manager._find_key_by_internal_id, call:str, call:terminal_manager.get_session, call:db_session.get, call:terminal_manager.reset_session, raise:HTTPException, func:rename_terminal_session(instance_id: uuid.UUID, session_id: str, data: dict, user_id, db_session) → dict, call:_get_terminal_instance, call:data.get, call:isinstance, call:terminal_manager.get_session, call:str, call:db_session.get, call:uuid.UUID, call:db_session.commit, raise:HTTPException, func:reset_terminal_session(instance_id: uuid.UUID, user_id, db_session) → dict, call:_get_terminal_instance, call:db_session.get, call:terminal_manager.reset_session, call:logger.info, call:str, call:logger.error, raise:HTTPException, func:_get_user_from_websocket(websocket: WebSocket, db_session: AsyncSession) → uuid.UUID | None, call:websocket.cookies.get, call:Settings, call:decode_session_cookie, call:uuid.UUID, call:str | dep: asyncio, json, logging, uuid, contextlib, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, starlette.websockets, src.auth.dependencies, src.models, src.services.terminal.terminal_manager, src.services.docker, src.auth.session, src.config, starlette
|
||||||
|
## arch
|
||||||
|
FastAPI router modules organized by domain concern with async/await patterns, SSE/WebSocket for real-time streaming, proxy pattern for container instance forwarding, and per-user authentication/authorization with connection limiting.
|
||||||
|
## tags
|
||||||
|
session, terminal, call:terminal, call:, src, get, response, websocket
|
||||||
|
## symbols
|
||||||
|
- NotificationItem
|
||||||
|
- NotificationListResponse
|
||||||
|
- UnreadCountResponse
|
||||||
|
- MarkAllReadResponse
|
||||||
|
- ClearAllResponse
|
||||||
|
- SessionRef
|
||||||
|
- get_dashboard_summary
|
||||||
|
- events_stream
|
||||||
|
## workflows
|
||||||
|
- change system behavior
|
||||||
|
read: __init__.py, dashboard.py, events.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# apps/api/src/api/tool (index)
|
||||||
|
dir: apps/api/src/api/tool
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides FastAPI REST API endpoints for managing Docker-based tools, including their definitions, types, instances, lifecycle operations, and active sessions.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/api/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- sessions.py
|
||||||
|
- tool_definitions.py
|
||||||
|
- tool_instances.py
|
||||||
|
- tool_lifecycle.py
|
||||||
|
- tool_types.py
|
||||||
|
- tool_types_validation.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/api/tool/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/tool/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change tool behavior
|
||||||
|
read: __init__.py, sessions.py, tool_definitions.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# apps/api/src/api/tool
|
||||||
|
dir: apps/api/src/api/tool
|
||||||
|
|
||||||
|
index: apps/api/src/api/tool/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides FastAPI REST API endpoints for managing Docker-based tools, including their definitions, types, instances, lifecycle operations, and active sessions.
|
||||||
|
## files
|
||||||
|
- __init__.py | Aggregates and exports all tool-related API routers from submodules as a unified module interface. | dep: src.api.tool.sessions, src.api.tool.tool_definitions, src.api.tool.tool_instances, src.api.tool.tool_lifecycle, src.api.tool.tool_types
|
||||||
|
- sessions.py | Provides a FastAPI endpoint to retrieve all active tool instances (sessions) for the currently authenticated user with related metadata. | exp: func:get_user_sessions(user_id, session) → dict, call:_get_user, call:session.execute, call:select(ToolInstance) .where(ToolInstance.owner_id == user_id) .where( ToolInstance.status.in_( ["running", "building", "pending", "stopped", "error"] ) ) .order_by, call:ToolInstance.status.in_, call:ToolInstance.created_at.desc, call:result.scalars().all, call:session.get, call:sessions.append, call:str, call:instance.created_at.isoformat | dep: uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.models (GitRepository, Project, ToolInstance, ToolType, Workspace)
|
||||||
|
- tool_definitions.py | FastAPI router providing CRUD endpoints and manifest compilation for tool definition manifests stored in a database. | exp: class:CreateToolDefinitionRequest, class:UpdateToolDefinitionRequest, func:create_tool_definition(data: CreateToolDefinitionRequest, user_id, session) → dict, call:uuid.UUID, call:session.get, call:session.execute, call:select(ToolDefinitionManifest).where, call:existing.scalar_one_or_none, call:ToolDefinitionManifest, call:session.add, call:session.commit, call:session.refresh, call:logger.info, call:str, call:definition.created_at.isoformat, raise:HTTPException, func:list_tool_definitions(user_id, session, include_bases) → dict, call:select, call:query.where, call:ToolDefinitionManifest.is_base.is_, call:session.execute, call:query.order_by, call:ToolDefinitionManifest.created_at.desc, call:result.scalars().all, call:str, call:d.created_at.isoformat, func:get_tool_definition(definition_id: uuid.UUID, user_id, session) → dict, call:session.get, call:str, call:definition.created_at.isoformat, call:definition.updated_at.isoformat, raise:HTTPException, func:update_tool_definition(definition_id: uuid.UUID, data: UpdateToolDefinitionRequest, user_id, session) → dict, call:session.get, call:session.commit, call:session.refresh, call:logger.info, call:str, call:definition.updated_at.isoformat, raise:HTTPException, func:delete_tool_definition(definition_id: uuid.UUID, user_id, session) → dict, call:session.get, call:session.execute, call:select(ToolType).where, call:result.scalars().all, call:", ".join, call:session.delete, call:session.commit, call:logger.info, call:str, raise:HTTPException, func:compile_tool_definition(definition_id: uuid.UUID, user_id, session) → dict, call:session.get, call:dict, call:resolve_base, call:deep_merge, call:compile_dockerfile, call:compile_entrypoint, call:compute_image_tag, call:compile_compose, call:session.commit, call:str, raise:HTTPException | dep: logging, uuid, fastapi, pydantic, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.services.build.manifest_compiler
|
||||||
|
- tool_instances.py | Provides FastAPI REST endpoints for managing tool instances including CRUD operations, logs, health checks, tunnel recreation, event history, and HTTP proxying to running containers. | exp: func:create_instance(project_id: uuid.UUID, repo_id: uuid.UUID, data: CreateInstanceRequest, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:create_tool_instance, call:str, call:instance.created_at.isoformat, raise:HTTPException, func:list_instances(project_id: uuid.UUID, repo_id: uuid.UUID, user_id, session) → list[dict], call:_get_user, call:_get_owned_project, call:session.execute, call:select(ToolInstance) .where(ToolInstance.repository_id == repo_id) .where(ToolInstance.owner_id == user_id) .order_by, call:ToolInstance.created_at.desc, call:result.scalars().all, call:str, call:i.created_at.isoformat, func:get_instance(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:session.get, call:get_container_status, call:str, call:instance.created_at.isoformat, call:instance.last_started_at.isoformat, raise:HTTPException, func:rename_instance(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:data.get("display_name", "").strip, call:rename_tool_instance, call:str, raise:HTTPException, func:get_instance_logs(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, tail, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:session.get, call:get_container_logs, raise:HTTPException, func:recreate_tunnel_endpoint(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:recreate_instance_tunnel, raise:HTTPException, func:check_instance_tunnel_health(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:session.get, call:get_container_status, call:instance.probe_result.get, call:"\n".join, call:check_tunnel_health, call:tunnel_health.get, raise:HTTPException, func:get_instance_events(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, limit, user_id, session) → list[dict], call:_get_user, call:_get_owned_project, call:session.get, call:session.execute, call:select(InstanceEvent) .where(InstanceEvent.instance_id == instance_id) .order_by(InstanceEvent.created_at.desc()) .limit, call:InstanceEvent.created_at.desc, call:result.scalars().all, call:str, call:row.created_at.isoformat, raise:HTTPException, func:proxy_to_instance(request: Request, project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, path, user_id, session) → Response, call:session.get, call:str, call:dict, call:headers.pop, call:httpx.AsyncClient, call:request.body, call:client.request, call:logger.error, call:response_headers.pop, call:Response, raise:HTTPException | dep: logging, uuid, httpx, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.services.docker, src.services.shared.tunnel, src.schemas.tool, src.services.tool.instance_service
|
||||||
|
- tool_lifecycle.py | FastAPI router providing REST endpoints for managing Docker-based tool instance lifecycle operations (start, stop, restart, delete). | exp: func:start_instance(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, data, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:start_tool_instance, raise:HTTPException, func:stop_instance(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:stop_tool_instance, raise:HTTPException, func:restart_instance(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:restart_tool_instance, raise:HTTPException, func:delete_instance(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, force, user_id, session) → None, call:_get_user, call:_get_owned_project, call:delete_tool_instance, call:str, call:detail.lower, raise:HTTPException | dep: logging, uuid, fastapi, sqlalchemy.ext.asyncio, src.auth.dependencies, src.schemas.tool, src.services.tool.instance_service
|
||||||
|
- tool_types.py | Defines FastAPI routes for CRUD operations and validation of Docker tool types with compose/dockerfile/manifest definitions. | exp: func:_require_admin(user: User) → None, func:create_tool_type(data: ToolTypeCreate, user_id, session) → ToolType, call:_get_user, call:_require_admin, call:session.scalar, call:select(ToolType).where, call:ToolType, call:session.add, call:session.commit, call:session.refresh, raise:HTTPException, func:list_tool_types(user_id, session) → list[ToolType], call:_get_user, call:session.execute, call:select(ToolType).order_by, call:list, call:result.scalars().all, func:get_tool_type(tool_type_id: uuid.UUID, user_id, session) → ToolType, call:_get_user, call:session.get, raise:HTTPException, func:update_tool_type(tool_type_id: uuid.UUID, data: ToolTypeUpdate, user_id, session) → ToolType, call:_get_user, call:_require_admin, call:session.get, call:data.model_dump, call:update_data.get, call:validate_compose_yaml, call:check_port_exposed, call:validate_required_variables, call:update_data.items, call:setattr, call:session.commit, call:session.refresh, raise:HTTPException, func:validate_tool_type_template(data: ToolTypeValidateRequest, user_id, session) → dict, call:_get_user, call:errors.append, call:validate_compose_yaml, call:str, call:data.dockerfile_template.strip().startswith, call:len, func:validate_tool_type(tool_type_id: uuid.UUID, user_id, session) → dict, call:_get_user, call:session.get, call:errors.append, call:validate_compose_yaml, call:str, call:tool_type.dockerfile_template.strip().startswith, call:len, raise:HTTPException, func:delete_tool_type(tool_type_id: uuid.UUID, user_id, session) → None, call:_get_user, call:_require_admin, call:session.get, call:session.delete, call:session.commit, raise:HTTPException | dep: uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.api.tool.tool_types_validation, src.auth.dependencies, src.models, src.models.user, src.schemas.tool
|
||||||
|
- tool_types_validation.py | Validates Docker Compose YAML templates by sanitizing template variables, parsing YAML structure, checking port exposure, and verifying required variables exist. | exp: func:sanitize_template_vars(template: str) → str, call:re.sub, func:validate_compose_yaml(template: str) → dict, call:sanitize_template_vars, call:yaml.safe_load, call:isinstance, raise:ValueError, func:check_port_exposed(parsed: dict, port: int) → bool, call:str, call:isinstance, call:parsed["services"].values, call:port_mapping.split, call:mapping.split, func:validate_required_variables(template: str, variables: list[str]) → None, raise:HTTPException | dep: re, yaml, fastapi
|
||||||
|
## arch
|
||||||
|
Modular FastAPI router organization with clear separation of concerns across CRUD operations, Docker lifecycle management, YAML/Compose validation, and container proxying/tunneling.
|
||||||
|
## tags
|
||||||
|
get, call:, tool, raise:httpexception, user, instance, call:str, call:session.get
|
||||||
|
## symbols
|
||||||
|
- CreateToolDefinitionRequest
|
||||||
|
- UpdateToolDefinitionRequest
|
||||||
|
- get_user_sessions
|
||||||
|
- create_tool_definition
|
||||||
|
- list_tool_definitions
|
||||||
|
- get_tool_definition
|
||||||
|
- update_tool_definition
|
||||||
|
- delete_tool_definition
|
||||||
|
## workflows
|
||||||
|
- change tool behavior
|
||||||
|
read: __init__.py, sessions.py, tool_definitions.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -3,11 +3,13 @@
|
|||||||
from src.api.tool.sessions import sessions_router
|
from src.api.tool.sessions import sessions_router
|
||||||
from src.api.tool.tool_definitions import router as tool_definitions_router
|
from src.api.tool.tool_definitions import router as tool_definitions_router
|
||||||
from src.api.tool.tool_instances import router as tool_instances_router
|
from src.api.tool.tool_instances import router as tool_instances_router
|
||||||
|
from src.api.tool.tool_lifecycle import router as tool_lifecycle_router
|
||||||
from src.api.tool.tool_types import router as tool_types_router
|
from src.api.tool.tool_types import router as tool_types_router
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"sessions_router",
|
"sessions_router",
|
||||||
"tool_definitions_router",
|
"tool_definitions_router",
|
||||||
"tool_instances_router",
|
"tool_instances_router",
|
||||||
|
"tool_lifecycle_router",
|
||||||
"tool_types_router",
|
"tool_types_router",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -53,6 +53,13 @@ async def get_user_sessions(
|
|||||||
repo = await session.get(GitRepository, instance.repository_id)
|
repo = await session.get(GitRepository, instance.repository_id)
|
||||||
project = await session.get(Project, instance.project_id)
|
project = await session.get(Project, instance.project_id)
|
||||||
|
|
||||||
|
workspace_name = None
|
||||||
|
if instance.workspace_id:
|
||||||
|
from src.models import Workspace as WorkspaceModel
|
||||||
|
workspace = await session.get(WorkspaceModel, instance.workspace_id)
|
||||||
|
if workspace:
|
||||||
|
workspace_name = workspace.name
|
||||||
|
|
||||||
sessions.append(
|
sessions.append(
|
||||||
{
|
{
|
||||||
"id": str(instance.id),
|
"id": str(instance.id),
|
||||||
@@ -64,10 +71,9 @@ async def get_user_sessions(
|
|||||||
"repository_id": str(instance.repository_id),
|
"repository_id": str(instance.repository_id),
|
||||||
"project_name": project.name if project else "unknown",
|
"project_name": project.name if project else "unknown",
|
||||||
"project_id": str(instance.project_id),
|
"project_id": str(instance.project_id),
|
||||||
|
"workspace_name": workspace_name,
|
||||||
"status": instance.status,
|
"status": instance.status,
|
||||||
"url": instance.url,
|
"url": instance.url,
|
||||||
"clone_mode": instance.clone_mode,
|
|
||||||
"branch": instance.branch,
|
|
||||||
"selected_config_profile_id": str(instance.selected_config_profile_id)
|
"selected_config_profile_id": str(instance.selected_config_profile_id)
|
||||||
if instance.selected_config_profile_id
|
if instance.selected_config_profile_id
|
||||||
else None,
|
else None,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,135 @@
|
|||||||
|
"""Tool instance lifecycle API endpoints."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from src.auth.dependencies import (
|
||||||
|
_get_owned_project,
|
||||||
|
_get_user,
|
||||||
|
get_current_user_id,
|
||||||
|
get_db_session,
|
||||||
|
)
|
||||||
|
from src.schemas.tool import StartInstanceRequest
|
||||||
|
from src.services.tool.instance_service import (
|
||||||
|
delete_tool_instance,
|
||||||
|
restart_tool_instance,
|
||||||
|
start_tool_instance,
|
||||||
|
stop_tool_instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/projects", tags=["tool-instances"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/{project_id}/repositories/{repo_id}/instances/{instance_id}/start",
|
||||||
|
summary="Start instance",
|
||||||
|
description="Start a tool instance using Docker Compose.",
|
||||||
|
)
|
||||||
|
async def start_instance(
|
||||||
|
project_id: uuid.UUID,
|
||||||
|
repo_id: uuid.UUID,
|
||||||
|
instance_id: uuid.UUID,
|
||||||
|
data: StartInstanceRequest | None = None,
|
||||||
|
user_id: uuid.UUID = Depends(get_current_user_id),
|
||||||
|
session: AsyncSession = Depends(get_db_session),
|
||||||
|
) -> dict:
|
||||||
|
_user = await _get_user(session, user_id)
|
||||||
|
_project = await _get_owned_project(project_id, user_id, session)
|
||||||
|
try:
|
||||||
|
return await start_tool_instance(
|
||||||
|
session, user_id, project_id, repo_id, instance_id, data
|
||||||
|
)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))
|
||||||
|
except RuntimeError as exc:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/{project_id}/repositories/{repo_id}/instances/{instance_id}/stop",
|
||||||
|
summary="Stop instance",
|
||||||
|
description="Stop a running tool instance.",
|
||||||
|
)
|
||||||
|
async def stop_instance(
|
||||||
|
project_id: uuid.UUID,
|
||||||
|
repo_id: uuid.UUID,
|
||||||
|
instance_id: uuid.UUID,
|
||||||
|
user_id: uuid.UUID = Depends(get_current_user_id),
|
||||||
|
session: AsyncSession = Depends(get_db_session),
|
||||||
|
) -> dict:
|
||||||
|
_user = await _get_user(session, user_id)
|
||||||
|
_project = await _get_owned_project(project_id, user_id, session)
|
||||||
|
try:
|
||||||
|
return await stop_tool_instance(session, user_id, project_id, repo_id, instance_id)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/{project_id}/repositories/{repo_id}/instances/{instance_id}/restart",
|
||||||
|
summary="Restart instance",
|
||||||
|
description="Restart a tool instance.",
|
||||||
|
)
|
||||||
|
async def restart_instance(
|
||||||
|
project_id: uuid.UUID,
|
||||||
|
repo_id: uuid.UUID,
|
||||||
|
instance_id: uuid.UUID,
|
||||||
|
user_id: uuid.UUID = Depends(get_current_user_id),
|
||||||
|
session: AsyncSession = Depends(get_db_session),
|
||||||
|
) -> dict:
|
||||||
|
_user = await _get_user(session, user_id)
|
||||||
|
_project = await _get_owned_project(project_id, user_id, session)
|
||||||
|
try:
|
||||||
|
return await restart_tool_instance(
|
||||||
|
session, user_id, project_id, repo_id, instance_id
|
||||||
|
)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))
|
||||||
|
except RuntimeError as exc:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete(
|
||||||
|
"/{project_id}/repositories/{repo_id}/instances/{instance_id}",
|
||||||
|
summary="Delete instance",
|
||||||
|
description="Delete a tool instance and remove its Docker containers and files.",
|
||||||
|
)
|
||||||
|
async def delete_instance(
|
||||||
|
project_id: uuid.UUID,
|
||||||
|
repo_id: uuid.UUID,
|
||||||
|
instance_id: uuid.UUID,
|
||||||
|
force: bool = False,
|
||||||
|
user_id: uuid.UUID = Depends(get_current_user_id),
|
||||||
|
session: AsyncSession = Depends(get_db_session),
|
||||||
|
) -> None:
|
||||||
|
_user = await _get_user(session, user_id)
|
||||||
|
_project = await _get_owned_project(project_id, user_id, session)
|
||||||
|
try:
|
||||||
|
await delete_tool_instance(
|
||||||
|
session, user_id, project_id, repo_id, instance_id, force
|
||||||
|
)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||||
|
except RuntimeError as exc:
|
||||||
|
detail = str(exc)
|
||||||
|
if "uncommitted changes" in detail.lower():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_409_CONFLICT,
|
||||||
|
detail={
|
||||||
|
"message": "Repository has uncommitted changes",
|
||||||
|
"changed_files": detail,
|
||||||
|
"force_required": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=detail
|
||||||
|
)
|
||||||
@@ -60,8 +60,14 @@ def check_port_exposed(parsed: dict, port: int) -> bool:
|
|||||||
for service_config in parsed["services"].values():
|
for service_config in parsed["services"].values():
|
||||||
if isinstance(service_config, dict) and "ports" in service_config:
|
if isinstance(service_config, dict) and "ports" in service_config:
|
||||||
for port_mapping in service_config["ports"]:
|
for port_mapping in service_config["ports"]:
|
||||||
if isinstance(port_mapping, str) and port_str in port_mapping:
|
if isinstance(port_mapping, str):
|
||||||
return True
|
# Port mappings can be "host:container", "ip:host:container",
|
||||||
|
# "container", or ".../protocol". The container port is the
|
||||||
|
# last numeric segment before any protocol suffix.
|
||||||
|
mapping = port_mapping.split("/")[0]
|
||||||
|
parts = mapping.split(":")
|
||||||
|
if parts[-1] == port_str:
|
||||||
|
return True
|
||||||
elif isinstance(port_mapping, int) and port_mapping == port:
|
elif isinstance(port_mapping, int) and port_mapping == port:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# apps/api/src/api/user (index)
|
||||||
|
dir: apps/api/src/api/user
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides authentication, user profile management, and SSH key operations for the API backend.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/api/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- auth.py
|
||||||
|
- ssh_keys.py
|
||||||
|
- users.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/api/user/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/user/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change user behavior
|
||||||
|
read: __init__.py, auth.py, ssh_keys.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# apps/api/src/api/user
|
||||||
|
dir: apps/api/src/api/user
|
||||||
|
|
||||||
|
index: apps/api/src/api/user/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides authentication, user profile management, and SSH key operations for the API backend.
|
||||||
|
## files
|
||||||
|
- __init__.py | Aggregates and exports user-related API routers from submodules for easy importing | dep: src.api.user.auth, src.api.user.ssh_keys, src.api.user.users
|
||||||
|
- auth.py | Implements OAuth2/OIDC authentication endpoints for a FastAPI application using Authentik as the identity provider, including login initiation, callback handling, logout, and current user retrieval. | exp: func:get_db_session() → AsyncGenerator[AsyncSession, None], call:SessionLocal, func:login(next) → RedirectResponse, call:Settings, call:token_urlsafe, call:build_login_redirect_url, call:logger.debug, call:RedirectResponse, call:response.set_cookie, func:callback(code: str, state: str, auth_state, auth_next, session) → RedirectResponse, call:logger.debug, call:logger.warning, call:Settings, call:httpx.AsyncClient, call:exchange_code_for_tokens, call:logger.info, call:logger.error, call:fetch_user_info, call:str, call:user_info.get, call:session.scalar, call:select(User).where, call:User, call:session.add, call:session.commit, call:session.refresh, call:create_session_cookie, call:build_cookie_options, call:cast, call:bool, call:cookie_options.get, call:RedirectResponse, call:redirect_response.set_cookie, call:redirect_response.delete_cookie, raise:HTTPException, func:logout(response: Response) → dict[str, str], call:Settings, call:build_cookie_options, call:cast, call:bool, call:str, call:cookie_options.get, call:response.delete_cookie, func:me(session_cookie, session) → dict[str, Any], call:logger.debug, call:bool, call:logger.warning, call:Settings, call:decode_session_cookie, call:session.get, call:logger.info, call:str, raise:HTTPException | dep: logging, secrets, typing, httpx, fastapi, fastapi.responses, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.cookies, src.auth.oidc, src.auth.session, src.config, src.database, src.models.user
|
||||||
|
- ssh_keys.py | Implements a FastAPI router for managing Ed25519 SSH key pairs, including generation, storage with encrypted private keys, listing, deletion, and cryptographic signing/verification operations. | exp: func:_get_fernet() → Fernet, call:Settings, call:hashlib.sha256(settings.session_secret.encode()).digest, call:settings.session_secret.encode, call:base64.urlsafe_b64encode, call:Fernet, func:generate_ssh_key_pair() → tuple[str, str], call:Ed25519PrivateKey.generate, call:private_key.public_key, call:private_key.private_bytes, call:serialization.NoEncryption, call:public_key.public_bytes, call:private_bytes.decode, call:public_bytes.decode, func:create_ssh_key(data: SSHKeyCreate, user_id, session) → SSHKey, call:_get_user, call:generate_ssh_key_pair, call:_get_fernet, call:fernet.encrypt(private_key.encode()).decode, call:private_key.encode, call:SSHKey, call:session.add, call:session.commit, call:session.refresh, func:list_ssh_keys(user_id, session) → list[SSHKey], call:_get_user, call:session.execute, call:select(SSHKey).where, call:list, call:result.scalars().all, func:delete_ssh_key(key_id: uuid.UUID, user_id, session) → None, call:_get_user, call:session.get, call:session.delete, call:session.commit, raise:HTTPException, func:sign_payload(key_id: uuid.UUID, data: SignPayloadRequest, user_id, session) → SignatureResponse, call:_get_user, call:session.get, call:_get_fernet, call:fernet.decrypt(ssh_key.private_key_encrypted.encode()).decode, call:ssh_key.private_key_encrypted.encode, call:serialization.load_ssh_private_key, call:private_key_pem.encode, call:private_key.sign, call:data.payload.encode, call:SignatureResponse, call:base64.b64encode(signature).decode, raise:HTTPException, func:verify_signature(key_id: uuid.UUID, data: VerifySignatureRequest, user_id, session) → VerifySignatureResponse, call:_get_user, call:session.get, call:serialization.load_ssh_public_key, call:ssh_key.public_key.encode, call:base64.b64decode, call:public_key.verify, call:data.payload.encode, call:VerifySignatureResponse, raise:HTTPException | dep: base64, uuid, cryptography.fernet, cryptography.hazmat.primitives, cryptography.hazmat.primitives.asymmetric.ed25519, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.config, src.models, src.schemas.project, hashlib, cryptography
|
||||||
|
- users.py | Implements FastAPI endpoints for authenticated user profile management including retrieval, updates, and avatar uploads. | exp: func:get_profile(user_id, session) → User, call:_get_user, func:update_profile(data: UserProfileUpdate, user_id, session) → User, call:_get_user, call:len, call:data.name.strip, call:data.email.strip, call:session.commit, call:session.refresh, raise:HTTPException, func:upload_avatar(file: UploadFile, user_id, session) → User, call:_get_user, call:file.read, call:len, call:Path, call:old_path.exists, call:old_path.unlink, call:filename_part.split(".")[-1].lower, call:uuid.uuid4, call:file_path.write_bytes, call:session.commit, call:session.refresh, raise:HTTPException | dep: uuid, pathlib, fastapi, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models.user, src.schemas.user
|
||||||
|
## arch
|
||||||
|
Modular FastAPI router pattern with OAuth2/OIDC external identity provider integration, encrypted key storage, and file-based avatar handling.
|
||||||
|
## tags
|
||||||
|
user, get, call:, key, ssh, src, cookie, raise:httpexception
|
||||||
|
## symbols
|
||||||
|
- get_db_session
|
||||||
|
- login
|
||||||
|
- callback
|
||||||
|
- logout
|
||||||
|
- me
|
||||||
|
- _get_fernet
|
||||||
|
- generate_ssh_key_pair
|
||||||
|
- create_ssh_key
|
||||||
|
## workflows
|
||||||
|
- change user behavior
|
||||||
|
read: __init__.py, auth.py, ssh_keys.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# apps/api/src/api/workspace (index)
|
||||||
|
dir: apps/api/src/api/workspace
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides FastAPI REST endpoints for workspace management, file operations, git integration, and tool instance management within user workspaces.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/api/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- workspace_files.py
|
||||||
|
- workspace_git.py
|
||||||
|
- workspace_instances.py
|
||||||
|
- workspaces.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/api/workspace/.pi-map.index.md
|
||||||
|
map: apps/api/src/api/workspace/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change workspace behavior
|
||||||
|
read: __init__.py, workspace_files.py, workspace_git.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# apps/api/src/api/workspace
|
||||||
|
dir: apps/api/src/api/workspace
|
||||||
|
|
||||||
|
index: apps/api/src/api/workspace/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides FastAPI REST endpoints for workspace management, file operations, git integration, and tool instance management within user workspaces.
|
||||||
|
## files
|
||||||
|
- __init__.py | Aggregates and re-exports workspace API router modules for centralized access | dep: src.api.workspace.workspace_files, src.api.workspace.workspace_git, src.api.workspace.workspace_instances, src.api.workspace.workspaces
|
||||||
|
- workspace_files.py | Provides FastAPI endpoints for listing, reading, and writing files within user workspaces with optional git commit integration. | exp: func:_get_workspace(session: AsyncSession, workspace_id: uuid.UUID, user_id: uuid.UUID) → Workspace, call:session.execute, call:select(Workspace).where, call:result.scalar_one_or_none, raise:HTTPException, func:list_files(workspace_id: uuid.UUID, path, user_id, session) → dict, call:_get_workspace, call:FileService, call:service.list_directory, raise:HTTPException, func:get_file_content(workspace_id: uuid.UUID, path: str, user_id, session) → dict, call:_get_workspace, call:FileService, call:service.read_file, raise:HTTPException, func:write_file(workspace_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_workspace, call:FileService, call:data.get("path", "").strip, call:data.get("message", "").strip, call:service.write_file, call:GitOperations, call:git.commit, raise:HTTPException | dep: uuid, fastapi, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.services.shared.file_service, sqlalchemy, src.services.git.git_operations
|
||||||
|
- workspace_git.py | Provides FastAPI REST endpoints for git operations (status, branches, commit, push, pull, fetch, checkout, history) scoped to user workspaces. | exp: func:_get_workspace(session: AsyncSession, workspace_id: uuid.UUID, user_id: uuid.UUID) → Workspace, call:session.execute, call:select(Workspace).where, call:result.scalar_one_or_none, raise:HTTPException, func:git_status(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.status, raise:HTTPException, func:git_branches(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.branches, raise:HTTPException, func:git_commit(workspace_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_workspace, call:data.get("message", "").strip, call:GitOperations, call:git.commit, raise:HTTPException, func:git_push(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.push, raise:HTTPException, func:git_pull(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.pull, raise:HTTPException, func:git_fetch(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.fetch, raise:HTTPException, func:git_checkout(workspace_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_workspace, call:data.get("branch", "").strip, call:GitOperations, call:git.checkout, call:session.commit, raise:HTTPException, func:git_history(workspace_id: uuid.UUID, path, limit, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.history, raise:HTTPException | dep: uuid, fastapi, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.services.git.git_operations, sqlalchemy
|
||||||
|
- workspace_instances.py | FastAPI router providing endpoints to create and list tool instances associated with a specific workspace. | exp: func:_get_workspace(session: AsyncSession, workspace_id: uuid.UUID, user_id: uuid.UUID) → Workspace, call:session.execute, call:select(Workspace).where, call:result.scalar_one_or_none, raise:HTTPException, func:create_workspace_instance(workspace_id: uuid.UUID, data: CreateWorkspaceInstanceRequest, user_id, session) → dict, call:_get_workspace, call:session.get, call:CreateInstanceRequest, call:str, call:create_tool_instance, call:instance.created_at.isoformat, raise:HTTPException, func:list_workspace_instances(workspace_id: uuid.UUID, user_id, session) → list[dict], call:_get_workspace, call:session.execute, call:select(ToolInstance) .where(ToolInstance.workspace_id == workspace_id) .order_by, call:ToolInstance.created_at.desc, call:result.scalars().all, call:str, call:i.created_at.isoformat | dep: uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.schemas.tool, src.services.tool.instance_service
|
||||||
|
- workspaces.py | FastAPI router providing CRUD endpoints for workspace management with nested and top-level URL structures, including listing, creating, updating, deleting, and syncing workspaces tied to Git repositories. | exp: func:list_all_workspaces(user_id, session) → list[dict], call:select(func.count(ToolInstance.id)) .where(ToolInstance.workspace_id == Workspace.id) .correlate(Workspace) .scalar_subquery, call:func.count, call:session.execute, call:select( Workspace, GitRepository, instance_count.label("instance_count"), ) .join(GitRepository, Workspace.repo_id == GitRepository.id) .options(selectinload(GitRepository.project)) .where(Workspace.user_id == user_id) .order_by, call:instance_count.label, call:selectinload, call:Workspace.created_at.desc, call:result.all, call:str, call:ws.last_sync_at.isoformat, call:ws.created_at.isoformat, call:ws.updated_at.isoformat, func:delete_workspace_top_level(workspace_id: uuid.UUID, force, user_id, session) → dict, call:session.get, call:WorkspaceManager, call:manager.delete, call:session.commit, call:session.rollback, call:logger.error, raise:HTTPException, func:create_workspace_top_level(data: dict, user_id, session) → dict, call:data.get("repo_id", "").strip, call:uuid.UUID, call:session.get, call:data.get("name", "").strip, call:data.get("branch", "main").strip, call:WorkspaceManager, call:manager.create, call:session.add, call:session.commit, call:session.rollback, call:logger.error, call:session.refresh, call:str, call:workspace.created_at.isoformat, raise:HTTPException, func:list_workspaces(project_id: uuid.UUID, repo_id: uuid.UUID, user_id, session) → list[dict], call:_get_repo, call:select(func.count(ToolInstance.id)) .where(ToolInstance.workspace_id == Workspace.id) .correlate(Workspace) .scalar_subquery, call:func.count, call:session.execute, call:select( Workspace, instance_count.label("instance_count"), ) .where(Workspace.repo_id == repo_id) .order_by, call:instance_count.label, call:Workspace.created_at.desc, call:result.all, call:str, call:ws.last_sync_at.isoformat, call:ws.created_at.isoformat, call:ws.updated_at.isoformat, func:create_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_repo, call:data.get("name", "").strip, call:data.get("branch", "main").strip, call:WorkspaceManager, call:manager.create, call:session.add, call:session.commit, call:session.rollback, call:logger.error, call:session.refresh, call:str, call:workspace.created_at.isoformat, raise:HTTPException, func:get_workspace_detail(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:session.execute, call:select(func.count(ToolInstance.id)).where, call:func.count, call:result.scalar, call:str, call:workspace.last_sync_at.isoformat, call:workspace.created_at.isoformat, call:workspace.updated_at.isoformat, func:update_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:data.get("name", "").strip, call:data.get("branch", "").strip, call:session.commit, call:session.rollback, call:logger.error, call:str, raise:HTTPException, func:delete_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, force, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:WorkspaceManager, call:manager.delete, call:session.commit, call:session.rollback, call:logger.error, raise:HTTPException, func:sync_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:WorkspaceManager, call:manager.sync, call:session.commit, call:workspace.last_sync_at.isoformat, raise:HTTPException, func:_get_repo(session: AsyncSession, repo_id: uuid.UUID, project_id: uuid.UUID, user_id: uuid.UUID) → GitRepository, call:session.execute, call:select(GitRepository) .where( GitRepository.id == repo_id, GitRepository.project_id == project_id, ) .options, call:selectinload, call:result.scalar_one_or_none, raise:HTTPException, func:_get_workspace(session: AsyncSession, workspace_id: uuid.UUID, repo_id: uuid.UUID) → Workspace, call:session.execute, call:select(Workspace).where, call:result.scalar_one_or_none, raise:HTTPException | dep: logging, uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, sqlalchemy.orm, src.auth.dependencies, src.models, src.services.shared.workspace_manager
|
||||||
|
## arch
|
||||||
|
Modular FastAPI router pattern with domain-driven separation (files, git, instances, workspaces) using nested URL structures and git-backed workspace synchronization.
|
||||||
|
## tags
|
||||||
|
workspace, get, raise:httpexception, call:, at.isoformat, git, call:select, call:data.get
|
||||||
|
## symbols
|
||||||
|
- _get_workspace
|
||||||
|
- list_files
|
||||||
|
- get_file_content
|
||||||
|
- write_file
|
||||||
|
- git_status
|
||||||
|
- git_branches
|
||||||
|
- git_commit
|
||||||
|
- git_push
|
||||||
|
## workflows
|
||||||
|
- change workspace behavior
|
||||||
|
read: __init__.py, workspace_files.py, workspace_git.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -2,13 +2,16 @@
|
|||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from src.auth.dependencies import get_current_user_id, get_db_session
|
from src.auth.dependencies import get_current_user_id, get_db_session
|
||||||
|
from src.models import GitRepository
|
||||||
from src.models import ToolInstance
|
from src.models import ToolInstance
|
||||||
from src.models import Workspace
|
from src.models import Workspace
|
||||||
|
from src.schemas.tool import CreateInstanceRequest, CreateWorkspaceInstanceRequest
|
||||||
|
from src.services.tool.instance_service import create_tool_instance
|
||||||
|
|
||||||
router = APIRouter(prefix="/workspaces/{workspace_id}/instances")
|
router = APIRouter(prefix="/workspaces/{workspace_id}/instances")
|
||||||
|
|
||||||
@@ -30,6 +33,63 @@ async def _get_workspace(
|
|||||||
return workspace
|
return workspace
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/",
|
||||||
|
summary="Create instance from workspace",
|
||||||
|
description="Create a new tool instance mounted on this workspace.",
|
||||||
|
status_code=status.HTTP_201_CREATED,
|
||||||
|
)
|
||||||
|
async def create_workspace_instance(
|
||||||
|
workspace_id: uuid.UUID,
|
||||||
|
data: CreateWorkspaceInstanceRequest,
|
||||||
|
user_id: uuid.UUID = Depends(get_current_user_id),
|
||||||
|
session: AsyncSession = Depends(get_db_session),
|
||||||
|
) -> dict:
|
||||||
|
"""Create a tool instance directly on a workspace."""
|
||||||
|
workspace = await _get_workspace(session, workspace_id, user_id)
|
||||||
|
|
||||||
|
repo = await session.get(GitRepository, workspace.repo_id)
|
||||||
|
if repo is None:
|
||||||
|
raise HTTPException(status_code=404, detail="Repository not found")
|
||||||
|
if repo.project_id is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail="Repository is not associated with a project",
|
||||||
|
)
|
||||||
|
|
||||||
|
request = CreateInstanceRequest(
|
||||||
|
tool_type_id=data.tool_type_id,
|
||||||
|
display_name=data.display_name,
|
||||||
|
workspace_id=str(workspace.id),
|
||||||
|
config_profile_id=data.config_profile_id,
|
||||||
|
ssh_key_ids=data.ssh_key_ids,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
instance = await create_tool_instance(
|
||||||
|
session, user_id, repo.project_id, repo.id, request
|
||||||
|
)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))
|
||||||
|
except RuntimeError as exc:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"id": str(instance.id),
|
||||||
|
"name": instance.name,
|
||||||
|
"display_name": instance.display_name,
|
||||||
|
"tool_type_id": str(instance.tool_type_id),
|
||||||
|
"status": instance.status,
|
||||||
|
"workspace_id": str(instance.workspace_id) if instance.workspace_id else None,
|
||||||
|
"selected_config_profile_id": str(instance.selected_config_profile_id)
|
||||||
|
if instance.selected_config_profile_id
|
||||||
|
else None,
|
||||||
|
"created_at": instance.created_at.isoformat(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
async def list_workspace_instances(
|
async def list_workspace_instances(
|
||||||
workspace_id: uuid.UUID,
|
workspace_id: uuid.UUID,
|
||||||
|
|||||||
@@ -36,12 +36,11 @@ async def list_all_workspaces(
|
|||||||
result = await session.execute(
|
result = await session.execute(
|
||||||
select(
|
select(
|
||||||
Workspace,
|
Workspace,
|
||||||
GitRepository.name.label("repo_name"),
|
GitRepository,
|
||||||
GitRepository.project_id,
|
|
||||||
GitRepository.ssh_key_id.label("repo_ssh_key_id"),
|
|
||||||
instance_count.label("instance_count"),
|
instance_count.label("instance_count"),
|
||||||
)
|
)
|
||||||
.join(GitRepository, Workspace.repo_id == GitRepository.id)
|
.join(GitRepository, Workspace.repo_id == GitRepository.id)
|
||||||
|
.options(selectinload(GitRepository.project))
|
||||||
.where(Workspace.user_id == user_id)
|
.where(Workspace.user_id == user_id)
|
||||||
.order_by(Workspace.created_at.desc())
|
.order_by(Workspace.created_at.desc())
|
||||||
)
|
)
|
||||||
@@ -51,11 +50,11 @@ async def list_all_workspaces(
|
|||||||
{
|
{
|
||||||
"id": str(ws.id),
|
"id": str(ws.id),
|
||||||
"name": ws.name,
|
"name": ws.name,
|
||||||
"repo_id": str(ws.repo_id),
|
"repo_id": str(repo.id),
|
||||||
"repo_name": repo_name or "",
|
"repo_name": repo.name or "",
|
||||||
"repo_ssh_key_id": str(ssh_key_id) if ssh_key_id else None,
|
"repo_ssh_key_id": str(repo.ssh_key_id) if repo.ssh_key_id else None,
|
||||||
"project_id": str(project_id) if project_id else "",
|
"project_id": str(repo.project_id) if repo.project_id else "",
|
||||||
"project_name": "",
|
"project_name": repo.project.name if repo.project else "",
|
||||||
"user_id": str(ws.user_id),
|
"user_id": str(ws.user_id),
|
||||||
"branch": ws.branch,
|
"branch": ws.branch,
|
||||||
"path": ws.path,
|
"path": ws.path,
|
||||||
@@ -65,7 +64,7 @@ async def list_all_workspaces(
|
|||||||
"updated_at": ws.updated_at.isoformat() if ws.updated_at else None,
|
"updated_at": ws.updated_at.isoformat() if ws.updated_at else None,
|
||||||
"instance_count": count or 0,
|
"instance_count": count or 0,
|
||||||
}
|
}
|
||||||
for ws, repo_name, project_id, ssh_key_id, count in rows
|
for ws, repo, count in rows
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# apps/api/src/auth (index)
|
||||||
|
dir: apps/api/src/auth
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides authentication and authorization infrastructure for the API using OIDC/OAuth2 with Authentik, session cookies, and FastAPI dependencies.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/.pi-map.index.md
|
||||||
|
map: apps/api/src/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- cookies.py
|
||||||
|
- dependencies.py
|
||||||
|
- oidc.py
|
||||||
|
- session.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/auth/.pi-map.index.md
|
||||||
|
map: apps/api/src/auth/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change auth behavior
|
||||||
|
read: __init__.py, cookies.py, dependencies.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# apps/api/src/auth
|
||||||
|
dir: apps/api/src/auth
|
||||||
|
|
||||||
|
index: apps/api/src/auth/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides authentication and authorization infrastructure for the API using OIDC/OAuth2 with Authentik, session cookies, and FastAPI dependencies.
|
||||||
|
## files
|
||||||
|
- __init__.py | Re-exports authentication utility functions from submodules to provide a unified public API for the auth package | dep: src.auth.cookies, src.auth.oidc, src.auth.session
|
||||||
|
- cookies.py | Builds a dictionary of HTTP cookie options from application settings for secure cookie configuration. | exp: func:build_cookie_options(settings: Settings) → dict[str, str | bool | None] | dep: src.config, src.config.Settings
|
||||||
|
- dependencies.py | Provides FastAPI dependency functions for database sessions, user authentication, and project authorization. | exp: func:get_db_session(), call:SessionLocal, func:get_current_user_id(session_cookie) → uuid.UUID, call:Settings, call:decode_session_cookie, call:uuid.UUID, call:str, raise:HTTPException, func:get_current_user(session_cookie, db_session) → User, call:Settings, call:decode_session_cookie, call:uuid.UUID, call:str, call:db_session.get, raise:HTTPException, func:_get_user(session: AsyncSession, user_id: uuid.UUID) → User, call:session.get, raise:HTTPException, func:_get_owned_project(project_id: uuid.UUID, user_id: uuid.UUID, session: AsyncSession) → "Project", call:session.get, raise:HTTPException | dep: uuid, typing, fastapi, sqlalchemy.ext.asyncio, src.auth.session, src.config, src.database, src.models.project, src.models.user, sqlalchemy
|
||||||
|
- oidc.py | Implements OIDC/OAuth2 authentication flow with Authentik, including login URL construction, authorization code exchange for tokens, and user info retrieval. | exp: func:build_login_redirect_url(settings: Settings, redirect_uri: str, state: str) → str, call:urlencode, func:exchange_code_for_tokens(settings: Settings, code: str, redirect_uri: str, client: httpx.AsyncClient) → dict[str, str], call:client.post, call:response.raise_for_status, call:response.json, call:payload.get, func:fetch_user_info(settings: Settings, access_token: str, client: httpx.AsyncClient) → dict[str, Any], call:client.get, call:response.raise_for_status, call:response.json | dep: typing, urllib.parse, httpx, src.config
|
||||||
|
- session.py | Implements creation and verification of signed session cookies using HMAC-SHA256 in a JWS-like format. | exp: func:_base64url_encode(data: bytes) → str, call:base64.urlsafe_b64encode(data).rstrip(b"=").decode, func:_base64url_decode(data: str) → bytes, call:len, call:base64.urlsafe_b64decode, func:create_session_cookie(settings: Settings, user_id: str) → str, call:int, call:(datetime.now(timezone.utc) + timedelta(hours=settings.session_ttl_hours)).timestamp, call:datetime.now, call:timedelta, call:_base64url_encode, call:json.dumps({"alg": "HS256", "typ": "session"}).encode, call:json.dumps(payload).encode, call:hmac.new( settings.session_secret.encode(), message.encode(), hashlib.sha256, ).digest, call:settings.session_secret.encode, call:message.encode, func:decode_session_cookie(settings: Settings, cookie_value: str) → dict[str, Any], call:cookie_value.split, call:len, call:hmac.new( settings.session_secret.encode(), message.encode(), hashlib.sha256, ).digest, call:settings.session_secret.encode, call:message.encode, call:_base64url_encode, call:hmac.compare_digest, call:_base64url_decode, call:json.loads, call:payload.get, call:int, call:datetime.now(timezone.utc).timestamp, raise:ValueError | dep: hmac, hashlib, json, base64, datetime, typing, src.config, src.config.Settings
|
||||||
|
## arch
|
||||||
|
Modular security layer with separation of concerns: protocol handling (OIDC), transport security (cookies/sessions), framework integration (dependencies), and clean public API (re-exports).
|
||||||
|
## tags
|
||||||
|
session, src, cookie, user, get, base64url, raise:httpexception, call:
|
||||||
|
## symbols
|
||||||
|
- build_cookie_options
|
||||||
|
- get_db_session
|
||||||
|
- get_current_user_id
|
||||||
|
- get_current_user
|
||||||
|
- _get_user
|
||||||
|
- _get_owned_project
|
||||||
|
- build_login_redirect_url
|
||||||
|
- exchange_code_for_tokens
|
||||||
|
## workflows
|
||||||
|
- change auth behavior
|
||||||
|
read: __init__.py, cookies.py, dependencies.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -47,6 +47,11 @@ class Settings(BaseSettings):
|
|||||||
session_secret: str = "change-me-session-secret"
|
session_secret: str = "change-me-session-secret"
|
||||||
session_ttl_hours: int = 24
|
session_ttl_hours: int = 24
|
||||||
|
|
||||||
|
# Internal JWT configuration (used for service-to-service tokens)
|
||||||
|
jwt_algorithm: str = "HS256"
|
||||||
|
access_token_ttl_minutes: int = 15
|
||||||
|
refresh_token_ttl_days: int = 7
|
||||||
|
|
||||||
# Repository storage
|
# Repository storage
|
||||||
repo_base_path: str = "/data/repos"
|
repo_base_path: str = "/data/repos"
|
||||||
|
|
||||||
@@ -122,7 +127,7 @@ class Settings(BaseSettings):
|
|||||||
@property
|
@property
|
||||||
def cookie_samesite(self) -> str:
|
def cookie_samesite(self) -> str:
|
||||||
if self.app_env == "production":
|
if self.app_env == "production":
|
||||||
return "none"
|
return "strict"
|
||||||
|
|
||||||
return "lax"
|
return "lax"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||||
@@ -54,10 +55,10 @@ async def init_database(
|
|||||||
result = await asyncio.get_event_loop().run_in_executor(
|
result = await asyncio.get_event_loop().run_in_executor(
|
||||||
None,
|
None,
|
||||||
lambda: subprocess.run(
|
lambda: subprocess.run(
|
||||||
["alembic", "upgrade", "head"],
|
["python3", "-m", "alembic", "upgrade", "head"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
cwd="/app",
|
cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from src.api.tool import (
|
|||||||
sessions_router,
|
sessions_router,
|
||||||
tool_definitions_router,
|
tool_definitions_router,
|
||||||
tool_instances_router,
|
tool_instances_router,
|
||||||
|
tool_lifecycle_router,
|
||||||
tool_types_router,
|
tool_types_router,
|
||||||
)
|
)
|
||||||
from src.api.user import auth_router, ssh_keys_router, users_router
|
from src.api.user import auth_router, ssh_keys_router, users_router
|
||||||
@@ -165,6 +166,7 @@ app.include_router(tool_types_router)
|
|||||||
app.include_router(tool_definitions_router)
|
app.include_router(tool_definitions_router)
|
||||||
app.include_router(config_profiles_router)
|
app.include_router(config_profiles_router)
|
||||||
app.include_router(tool_instances_router)
|
app.include_router(tool_instances_router)
|
||||||
|
app.include_router(tool_lifecycle_router)
|
||||||
app.include_router(sessions_router)
|
app.include_router(sessions_router)
|
||||||
app.include_router(instance_proxy_router)
|
app.include_router(instance_proxy_router)
|
||||||
app.include_router(terminal_router)
|
app.include_router(terminal_router)
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# apps/api/src/models (index)
|
||||||
|
dir: apps/api/src/models
|
||||||
|
|
||||||
|
## role
|
||||||
|
Centralized database model definitions and shared infrastructure for the API's data layer.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/.pi-map.index.md
|
||||||
|
map: apps/api/src/.pi-map.md
|
||||||
|
## children
|
||||||
|
- apps/api/src/models/config
|
||||||
|
index: apps/api/src/models/config/.pi-map.index.md
|
||||||
|
map: apps/api/src/models/config/.pi-map.md
|
||||||
|
- apps/api/src/models/project
|
||||||
|
index: apps/api/src/models/project/.pi-map.index.md
|
||||||
|
map: apps/api/src/models/project/.pi-map.md
|
||||||
|
- apps/api/src/models/system
|
||||||
|
index: apps/api/src/models/system/.pi-map.index.md
|
||||||
|
map: apps/api/src/models/system/.pi-map.md
|
||||||
|
- apps/api/src/models/tool
|
||||||
|
index: apps/api/src/models/tool/.pi-map.index.md
|
||||||
|
map: apps/api/src/models/tool/.pi-map.md
|
||||||
|
- apps/api/src/models/user
|
||||||
|
index: apps/api/src/models/user/.pi-map.index.md
|
||||||
|
map: apps/api/src/models/user/.pi-map.md
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- base.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/models/.pi-map.index.md
|
||||||
|
map: apps/api/src/models/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change models behavior
|
||||||
|
read: __init__.py, base.py
|
||||||
|
- explore models subdirectories
|
||||||
|
index: apps/api/src/models/config/.pi-map.index.md, apps/api/src/models/project/.pi-map.index.md, apps/api/src/models/system/.pi-map.index.md
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# apps/api/src/models
|
||||||
|
dir: apps/api/src/models
|
||||||
|
|
||||||
|
index: apps/api/src/models/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Centralized database model definitions and shared infrastructure for the API's data layer.
|
||||||
|
## files
|
||||||
|
- __init__.py | Re-exports model classes from submodules to provide a centralized public API for the src.models package | dep: src.models.base, src.models.config.config_profile, src.models.project.git_repository, src.models.project.project, src.models.project.workspace, src.models.system.health_check, src.models.system.instance_event, src.models.system.notification, src.models.system.terminal_session, src.models.tool.tool_definition_manifest, src.models.tool.tool_instance, src.models.tool.tool_type, src.models.user.ssh_key, src.models.user.user, src.models.user.user_config
|
||||||
|
- base.py | Defines SQLAlchemy base model and reusable mixins for UUID primary keys and automatic timestamp tracking in database models. | exp: class:Base, class:UUIDPrimaryKeyMixin, class:TimestampMixin | dep: uuid, datetime, sqlalchemy, sqlalchemy.orm
|
||||||
|
## arch
|
||||||
|
SQLAlchemy ORM with declarative base, mixin-based composition for cross-cutting concerns (UUIDs, timestamps), and explicit package-level re-exports for clean public API surface.
|
||||||
|
## tags
|
||||||
|
models, src, base, project, system, user, mixin, tool
|
||||||
|
## symbols
|
||||||
|
- Base
|
||||||
|
- UUIDPrimaryKeyMixin
|
||||||
|
- TimestampMixin
|
||||||
|
## workflows
|
||||||
|
- change models behavior
|
||||||
|
read: __init__.py, base.py
|
||||||
|
- explore models subdirectories
|
||||||
|
index: apps/api/src/models/config/.pi-map.index.md, apps/api/src/models/project/.pi-map.index.md, apps/api/src/models/system/.pi-map.index.md
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# apps/api/src/models/config (index)
|
||||||
|
dir: apps/api/src/models/config
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides SQLAlchemy ORM models for configuration profiles that manage environment variables, mounts, files, and git mounts with support for profile inheritance and user/project/tool associations.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/models/.pi-map.index.md
|
||||||
|
map: apps/api/src/models/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- config_profile.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/models/config/.pi-map.index.md
|
||||||
|
map: apps/api/src/models/config/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change config behavior
|
||||||
|
read: __init__.py, config_profile.py
|
||||||
|
- change config config
|
||||||
|
read: config_profile.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# apps/api/src/models/config
|
||||||
|
dir: apps/api/src/models/config
|
||||||
|
|
||||||
|
index: apps/api/src/models/config/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides SQLAlchemy ORM models for configuration profiles that manage environment variables, mounts, files, and git mounts with support for profile inheritance and user/project/tool associations.
|
||||||
|
## files
|
||||||
|
- __init__.py | Exports config model classes from a subpackage for simplified importing | dep: src.models.config.config_profile
|
||||||
|
- config_profile.py | Defines SQLAlchemy ORM models for configuration profiles and their includes, storing environment variables, runtime hints, mounts, files, and git mounts with user/project/tool associations and self-referential inclusion relationships. | exp: class:ConfigProfile, class:ConfigProfileInclude | dep: uuid, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.project, src.models, src.models.user, src.models (ToolType)
|
||||||
|
## arch
|
||||||
|
Uses SQLAlchemy ORM with declarative base pattern, implementing self-referential many-to-many relationships for profile inclusion, association tables for user/project/tool ownership, and polymorphic-like configuration through profile inheritance rather than class inheritance.
|
||||||
|
## tags
|
||||||
|
config, src, models, profile, sqlalchemy, init, orm, mounts
|
||||||
|
## symbols
|
||||||
|
- ConfigProfile
|
||||||
|
- ConfigProfileInclude
|
||||||
|
## workflows
|
||||||
|
- change config behavior
|
||||||
|
read: __init__.py, config_profile.py
|
||||||
|
- change config config
|
||||||
|
read: config_profile.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# apps/api/src/models/project (index)
|
||||||
|
dir: apps/api/src/models/project
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines the core domain data models for project management, representing Git repositories, projects, and workspaces as SQLAlchemy ORM entities with their relationships.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/models/.pi-map.index.md
|
||||||
|
map: apps/api/src/models/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- git_repository.py
|
||||||
|
- project.py
|
||||||
|
- workspace.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/models/project/.pi-map.index.md
|
||||||
|
map: apps/api/src/models/project/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change project behavior
|
||||||
|
read: __init__.py, git_repository.py, project.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# apps/api/src/models/project
|
||||||
|
dir: apps/api/src/models/project
|
||||||
|
|
||||||
|
index: apps/api/src/models/project/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines the core domain data models for project management, representing Git repositories, projects, and workspaces as SQLAlchemy ORM entities with their relationships.
|
||||||
|
## files
|
||||||
|
- __init__.py | Exports the three main project model classes (GitRepository, Project, Workspace) as the public API for the models.project package. | dep: src.models.project.git_repository, src.models.project.project, src.models.project.workspace
|
||||||
|
- git_repository.py | Defines a SQLAlchemy ORM model for Git repositories with relationships to projects, users, and SSH keys. | exp: class:GitRepository | dep: uuid, datetime, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.project, src.models, src.models.user, src.models.ssh_key
|
||||||
|
- project.py | Defines a SQLAlchemy ORM model for a Project entity with relationships to users, Git repositories, and SSH keys. | exp: class:Project | dep: uuid, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models, src.models.user, src.models.GitRepository, src.models.SSHKey, src.models.user.User
|
||||||
|
- workspace.py | Defines a SQLAlchemy ORM model for persistent writable Git repository clones associated with users. | exp: class:Workspace | dep: uuid, datetime, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models, src.models.user, src.models.GitRepository, src.models.user.User
|
||||||
|
## arch
|
||||||
|
Standard SQLAlchemy ORM model layer with declarative base pattern, using __init__.py as a public API facade to expose the three main entity classes while keeping implementation details in separate modules.
|
||||||
|
## tags
|
||||||
|
models, src, project, sqlalchemy, git, user, workspace, orm
|
||||||
|
## symbols
|
||||||
|
- GitRepository
|
||||||
|
- Project
|
||||||
|
- Workspace
|
||||||
|
## workflows
|
||||||
|
- change project behavior
|
||||||
|
read: __init__.py, git_repository.py, project.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# apps/api/src/models/system (index)
|
||||||
|
dir: apps/api/src/models/system
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides database persistence models for system-level operational concerns including health monitoring, audit logging, user notifications, and terminal session management.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/models/.pi-map.index.md
|
||||||
|
map: apps/api/src/models/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- health_check.py
|
||||||
|
- instance_event.py
|
||||||
|
- notification.py
|
||||||
|
- terminal_session.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/models/system/.pi-map.index.md
|
||||||
|
map: apps/api/src/models/system/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change system behavior
|
||||||
|
read: __init__.py, health_check.py, instance_event.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# apps/api/src/models/system
|
||||||
|
dir: apps/api/src/models/system
|
||||||
|
|
||||||
|
index: apps/api/src/models/system/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides database persistence models for system-level operational concerns including health monitoring, audit logging, user notifications, and terminal session management.
|
||||||
|
## files
|
||||||
|
- __init__.py | Exports system-related model classes from a package for convenient importing. | dep: src.models.system.health_check, src.models.system.instance_event, src.models.system.notification, src.models.system.terminal_session
|
||||||
|
- health_check.py | Defines a SQLAlchemy ORM model for storing health check snapshots of tool instances with various status and probe fields. | exp: class:HealthCheck | dep: uuid, datetime, sqlalchemy, sqlalchemy.orm, src.models.base
|
||||||
|
- instance_event.py | Defines a SQLAlchemy ORM model for auditing instance lifecycle events with metadata, status tracking, and user attribution. | exp: class:InstanceEvent | dep: uuid, datetime, typing, sqlalchemy, sqlalchemy.orm, src.models.base
|
||||||
|
- notification.py | Defines a SQLAlchemy ORM model for storing user notifications with metadata, read/dismissed tracking, and polymorphic source references. | exp: class:Notification | dep: datetime, typing, uuid, sqlalchemy, sqlalchemy.orm, sqlalchemy.sql, src.models.base
|
||||||
|
- terminal_session.py | Defines a SQLAlchemy database model for storing terminal session metadata with lifecycle tracking. | exp: class:TerminalSessionModel | dep: uuid, datetime, sqlalchemy, sqlalchemy.orm, src.models.base
|
||||||
|
## arch
|
||||||
|
Uses SQLAlchemy ORM declarative models with consistent patterns including timestamp tracking, JSON metadata columns, enum-based status fields, and polymorphic relationships for extensible source attribution.
|
||||||
|
## tags
|
||||||
|
sqlalchemy, src, models, model, orm, system, notification, health
|
||||||
|
## symbols
|
||||||
|
- HealthCheck
|
||||||
|
- InstanceEvent
|
||||||
|
- Notification
|
||||||
|
- TerminalSessionModel
|
||||||
|
## workflows
|
||||||
|
- change system behavior
|
||||||
|
read: __init__.py, health_check.py, instance_event.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# apps/api/src/models/tool (index)
|
||||||
|
dir: apps/api/src/models/tool
|
||||||
|
|
||||||
|
## role
|
||||||
|
Database models for containerized tool lifecycle management, covering tool definitions, deployment instances, and categorization types.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/models/.pi-map.index.md
|
||||||
|
map: apps/api/src/models/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- tool_definition_manifest.py
|
||||||
|
- tool_instance.py
|
||||||
|
- tool_type.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/models/tool/.pi-map.index.md
|
||||||
|
map: apps/api/src/models/tool/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change tool behavior
|
||||||
|
read: __init__.py, tool_definition_manifest.py, tool_instance.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# apps/api/src/models/tool
|
||||||
|
dir: apps/api/src/models/tool
|
||||||
|
|
||||||
|
index: apps/api/src/models/tool/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Database models for containerized tool lifecycle management, covering tool definitions, deployment instances, and categorization types.
|
||||||
|
## files
|
||||||
|
- __init__.py | Exports the public API for the tool models module by re-exporting three key classes. | dep: src.models.tool.tool_definition_manifest, src.models.tool.tool_instance, src.models.tool.tool_type
|
||||||
|
- tool_definition_manifest.py | Defines a SQLAlchemy ORM model for storing tool definition manifests that compile to Dockerfiles and Compose files, supporting both base definitions and tool-specific definitions with inheritance. | exp: class:ToolDefinitionManifest | dep: uuid, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.user
|
||||||
|
- tool_instance.py | Defines a SQLAlchemy ORM model for tool instances that represent deployed tools with container metadata, status tracking, and relationships to users, projects, workspaces, and other entities. | exp: class:ToolInstance | dep: uuid, datetime, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models, src.models.project, src.models.user, src.models (ConfigProfile, GitRepository, Project, ToolType, User, Workspace)
|
||||||
|
- tool_type.py | Defines a SQLAlchemy ORM model for tool types that represent configurable categories of tools with deployment templates, manifest references, and metadata. | exp: class:ToolType | dep: uuid, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.tool.tool_definition_manifest, src.models.user
|
||||||
|
## arch
|
||||||
|
SQLAlchemy ORM with declarative models using inheritance hierarchies, relationship mappings, and polymorphic manifest compilation for Docker/Compose deployment.
|
||||||
|
## tags
|
||||||
|
tool, models, src, sqlalchemy, orm, definition, manifest, base
|
||||||
|
## symbols
|
||||||
|
- ToolDefinitionManifest
|
||||||
|
- ToolInstance
|
||||||
|
- ToolType
|
||||||
|
## workflows
|
||||||
|
- change tool behavior
|
||||||
|
read: __init__.py, tool_definition_manifest.py, tool_instance.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -53,9 +53,11 @@ class ToolInstance(UUIDPrimaryKeyMixin, TimestampMixin, Base):
|
|||||||
)
|
)
|
||||||
image_tag: Mapped[str | None] = mapped_column(String(256), nullable=True)
|
image_tag: Mapped[str | None] = mapped_column(String(256), nullable=True)
|
||||||
probe_result: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
probe_result: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
||||||
clone_mode: Mapped[str] = mapped_column(String(20), nullable=False, default="mount")
|
clone_mode: Mapped[str | None] = mapped_column(
|
||||||
|
String(20), nullable=True, default=None
|
||||||
|
)
|
||||||
branch: Mapped[str | None] = mapped_column(
|
branch: Mapped[str | None] = mapped_column(
|
||||||
String(255), nullable=True, default="main"
|
String(255), nullable=True, default=None
|
||||||
)
|
)
|
||||||
selected_config_profile_id: Mapped[uuid.UUID | None] = mapped_column(
|
selected_config_profile_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||||
UUID(), ForeignKey("config_profiles.id", ondelete="SET NULL"), nullable=True
|
UUID(), ForeignKey("config_profiles.id", ondelete="SET NULL"), nullable=True
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# apps/api/src/models/user (index)
|
||||||
|
dir: apps/api/src/models/user
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides SQLAlchemy ORM data models for user authentication, SSH key storage, and per-user configuration management.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/models/.pi-map.index.md
|
||||||
|
map: apps/api/src/models/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- ssh_key.py
|
||||||
|
- user.py
|
||||||
|
- user_config.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/models/user/.pi-map.index.md
|
||||||
|
map: apps/api/src/models/user/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change user behavior
|
||||||
|
read: __init__.py, ssh_key.py, user.py
|
||||||
|
- change user config
|
||||||
|
read: user_config.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# apps/api/src/models/user
|
||||||
|
dir: apps/api/src/models/user
|
||||||
|
|
||||||
|
index: apps/api/src/models/user/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides SQLAlchemy ORM data models for user authentication, SSH key storage, and per-user configuration management.
|
||||||
|
## files
|
||||||
|
- __init__.py | Exports the user-related model classes (SSHKey, User, UserConfig) from a package for convenient importing. | dep: src.models.user.ssh_key, src.models.user.user, src.models.user.user_config
|
||||||
|
- ssh_key.py | Defines an SSHKey SQLAlchemy ORM model for storing encrypted SSH key pairs with user and optional project associations. | exp: class:SSHKey | dep: uuid, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.project, src.models.user
|
||||||
|
- user.py | Defines a SQLAlchemy ORM model for a User entity with authentication fields and relationships to projects, SSH keys, and user configuration. | exp: class:User | dep: typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.project, src.models, src.models.SSHKey, src.models.UserConfig
|
||||||
|
- user_config.py | Defines a SQLAlchemy model for storing per-user JSON configuration with typed property accessors for default profile IDs. | exp: class:UserConfig | dep: uuid, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.user
|
||||||
|
## arch
|
||||||
|
Classic SQLAlchemy ORM model package pattern with declarative models, explicit table relationships, and a package-level __init__.py facade for clean imports.
|
||||||
|
## tags
|
||||||
|
user, src, models, sqlalchemy, sshkey, orm, model, ssh
|
||||||
|
## symbols
|
||||||
|
- SSHKey
|
||||||
|
- User
|
||||||
|
- UserConfig
|
||||||
|
## workflows
|
||||||
|
- change user behavior
|
||||||
|
read: __init__.py, ssh_key.py, user.py
|
||||||
|
- change user config
|
||||||
|
read: user_config.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# apps/api/src/schemas (index)
|
||||||
|
dir: apps/api/src/schemas
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines Pydantic schemas for data validation and serialization in the API layer.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/.pi-map.index.md
|
||||||
|
map: apps/api/src/.pi-map.md
|
||||||
|
## children
|
||||||
|
- apps/api/src/schemas/config
|
||||||
|
index: apps/api/src/schemas/config/.pi-map.index.md
|
||||||
|
map: apps/api/src/schemas/config/.pi-map.md
|
||||||
|
- apps/api/src/schemas/project
|
||||||
|
index: apps/api/src/schemas/project/.pi-map.index.md
|
||||||
|
map: apps/api/src/schemas/project/.pi-map.md
|
||||||
|
- apps/api/src/schemas/system
|
||||||
|
index: apps/api/src/schemas/system/.pi-map.index.md
|
||||||
|
map: apps/api/src/schemas/system/.pi-map.md
|
||||||
|
- apps/api/src/schemas/tool
|
||||||
|
index: apps/api/src/schemas/tool/.pi-map.index.md
|
||||||
|
map: apps/api/src/schemas/tool/.pi-map.md
|
||||||
|
- apps/api/src/schemas/user
|
||||||
|
index: apps/api/src/schemas/user/.pi-map.index.md
|
||||||
|
map: apps/api/src/schemas/user/.pi-map.md
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/schemas/.pi-map.index.md
|
||||||
|
map: apps/api/src/schemas/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change schemas behavior
|
||||||
|
read: __init__.py
|
||||||
|
- explore schemas subdirectories
|
||||||
|
index: apps/api/src/schemas/config/.pi-map.index.md, apps/api/src/schemas/project/.pi-map.index.md, apps/api/src/schemas/system/.pi-map.index.md
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# apps/api/src/schemas
|
||||||
|
dir: apps/api/src/schemas
|
||||||
|
|
||||||
|
index: apps/api/src/schemas/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines Pydantic schemas for data validation and serialization in the API layer.
|
||||||
|
## files
|
||||||
|
- __init__.py | Empty file with no functionality
|
||||||
|
## arch
|
||||||
|
Standard Python package structure using __init__.py to mark the directory as an importable package, likely intended for request/response models following FastAPI/Pydantic patterns.
|
||||||
|
## tags
|
||||||
|
init, empty, functionality
|
||||||
|
## symbols
|
||||||
|
-
|
||||||
|
## workflows
|
||||||
|
- change schemas behavior
|
||||||
|
read: __init__.py
|
||||||
|
- explore schemas subdirectories
|
||||||
|
index: apps/api/src/schemas/config/.pi-map.index.md, apps/api/src/schemas/project/.pi-map.index.md, apps/api/src/schemas/system/.pi-map.index.md
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# apps/api/src/schemas/config (index)
|
||||||
|
dir: apps/api/src/schemas/config
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines Pydantic validation schemas for configuration profile API request/response models with strict input validation.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/schemas/.pi-map.index.md
|
||||||
|
map: apps/api/src/schemas/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- config_profile.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/schemas/config/.pi-map.index.md
|
||||||
|
map: apps/api/src/schemas/config/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change config behavior
|
||||||
|
read: __init__.py, config_profile.py
|
||||||
|
- change config config
|
||||||
|
read: config_profile.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# apps/api/src/schemas/config
|
||||||
|
dir: apps/api/src/schemas/config
|
||||||
|
|
||||||
|
index: apps/api/src/schemas/config/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines Pydantic validation schemas for configuration profile API request/response models with strict input validation.
|
||||||
|
## files
|
||||||
|
- __init__.py | Re-exports Pydantic schemas for configuration profile operations from a submodule to provide a clean public API. | dep: src.schemas.config.config_profile
|
||||||
|
- config_profile.py | Defines Pydantic request/response schemas for config profile API operations with validation for mounts, git URLs, UUIDs, and file paths. | exp: class:GitMountMapping, class:GitMountItem, class:MountItem, class:ConfigProfileCreate, class:ConfigProfileUpdate, class:ConfigProfileIncludeUpdate, class:ConfigProfileResponse, class:DefaultProfilesUpdate, class:ValidateGitUrlRequest, class:ValidateGitUrlResponse, func:_validate_uuid(v: str | None) → str | None, call:uuid.UUID, raise:ValueError | dep: uuid, pydantic, src.api.shared_validators
|
||||||
|
## arch
|
||||||
|
Standard Python package pattern using `__init__.py` re-exports to expose a clean public API from internal modules, with Pydantic-based schema-per-resource organization.
|
||||||
|
## tags
|
||||||
|
profile, config, git, mount, update, validate, response, item
|
||||||
|
## symbols
|
||||||
|
- GitMountMapping
|
||||||
|
- GitMountItem
|
||||||
|
- MountItem
|
||||||
|
- ConfigProfileCreate
|
||||||
|
- ConfigProfileUpdate
|
||||||
|
- ConfigProfileIncludeUpdate
|
||||||
|
- ConfigProfileResponse
|
||||||
|
- DefaultProfilesUpdate
|
||||||
|
## workflows
|
||||||
|
- change config behavior
|
||||||
|
read: __init__.py, config_profile.py
|
||||||
|
- change config config
|
||||||
|
read: config_profile.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# apps/api/src/schemas/project (index)
|
||||||
|
dir: apps/api/src/schemas/project
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines Pydantic data validation schemas for project-related API endpoints, serving as the contract layer between HTTP requests/responses and internal business logic.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/schemas/.pi-map.index.md
|
||||||
|
map: apps/api/src/schemas/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- git_repository.py
|
||||||
|
- project.py
|
||||||
|
- ssh_key.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/schemas/project/.pi-map.index.md
|
||||||
|
map: apps/api/src/schemas/project/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change project behavior
|
||||||
|
read: __init__.py, git_repository.py, project.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# apps/api/src/schemas/project
|
||||||
|
dir: apps/api/src/schemas/project
|
||||||
|
|
||||||
|
index: apps/api/src/schemas/project/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines Pydantic data validation schemas for project-related API endpoints, serving as the contract layer between HTTP requests/responses and internal business logic.
|
||||||
|
## files
|
||||||
|
- __init__.py | Re-exports Pydantic schema classes from submodules to provide a unified public API for the project schemas module | dep: src.schemas.project.git_repository, src.schemas.project.project, src.schemas.project.ssh_key
|
||||||
|
- git_repository.py | Defines Pydantic request/response schemas for Git repository operations in an API. | exp: class:GitRepositoryCreate, class:URLParseRequest, class:URLParseResponse, class:GitRepositoryResponse, class:UpdateSSHKeyRequest, class:FileListResponse, class:FileContentResponse, class:BranchesResponse, class:FileUpdateRequest, class:FileUpdateResponse, class:StatusResponse, class:BranchCreateRequest, class:CheckoutRequest, class:CommitRequest, class:CommitResponse, class:FetchResponse, class:PullResponse, class:PushResponse, class:MergeRequest, class:MergeResponse | dep: uuid, datetime, pydantic
|
||||||
|
- project.py | Defines Pydantic request/response schemas for project-related API operations. | exp: class:ProjectCreate, class:ProjectUpdate, class:ProjectResponse, class:SetDefaultSSHKeyRequest | dep: uuid, pydantic
|
||||||
|
- ssh_key.py | Defines Pydantic schemas for SSH key creation, response, signing, and signature verification in an API. | exp: class:SSHKeyCreate, class:SSHKeyResponse, class:SignPayloadRequest, class:SignatureResponse, class:VerifySignatureRequest, class:VerifySignatureResponse | dep: uuid, datetime, pydantic
|
||||||
|
## arch
|
||||||
|
Modular schema decomposition with submodule-per-domain pattern (project, git_repository, ssh_key) aggregated through __init__.py barrel exports, using Pydantic models for declarative validation and serialization.
|
||||||
|
## tags
|
||||||
|
response, request, project, create, update, sshkey, pydantic, schemas
|
||||||
|
## symbols
|
||||||
|
- GitRepositoryCreate
|
||||||
|
- URLParseRequest
|
||||||
|
- URLParseResponse
|
||||||
|
- GitRepositoryResponse
|
||||||
|
- UpdateSSHKeyRequest
|
||||||
|
- FileListResponse
|
||||||
|
- FileContentResponse
|
||||||
|
- BranchesResponse
|
||||||
|
## workflows
|
||||||
|
- change project behavior
|
||||||
|
read: __init__.py, git_repository.py, project.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -45,3 +45,91 @@ class GitRepositoryResponse(BaseModel):
|
|||||||
|
|
||||||
class UpdateSSHKeyRequest(BaseModel):
|
class UpdateSSHKeyRequest(BaseModel):
|
||||||
ssh_key_id: str | None = None
|
ssh_key_id: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class FileListResponse(BaseModel):
|
||||||
|
path: str
|
||||||
|
branch: str
|
||||||
|
entries: list[dict]
|
||||||
|
|
||||||
|
|
||||||
|
class FileContentResponse(BaseModel):
|
||||||
|
path: str
|
||||||
|
branch: str
|
||||||
|
content: str
|
||||||
|
size: int
|
||||||
|
encoding: str
|
||||||
|
language: str | None
|
||||||
|
is_binary: bool
|
||||||
|
last_commit: dict | None
|
||||||
|
|
||||||
|
|
||||||
|
class BranchesResponse(BaseModel):
|
||||||
|
branches: list[dict]
|
||||||
|
default_branch: str
|
||||||
|
|
||||||
|
|
||||||
|
class FileUpdateRequest(BaseModel):
|
||||||
|
path: str
|
||||||
|
branch: str
|
||||||
|
content: str
|
||||||
|
commit_message: str
|
||||||
|
|
||||||
|
|
||||||
|
class FileUpdateResponse(BaseModel):
|
||||||
|
commit_hash: str
|
||||||
|
message: str
|
||||||
|
branch: str
|
||||||
|
|
||||||
|
|
||||||
|
class StatusResponse(BaseModel):
|
||||||
|
branch: str
|
||||||
|
modified: list[str]
|
||||||
|
added: list[str]
|
||||||
|
deleted: list[str]
|
||||||
|
untracked: list[str]
|
||||||
|
renamed: list[str]
|
||||||
|
ahead: int
|
||||||
|
behind: int
|
||||||
|
|
||||||
|
|
||||||
|
class BranchCreateRequest(BaseModel):
|
||||||
|
name: str
|
||||||
|
base_branch: str = "HEAD"
|
||||||
|
|
||||||
|
|
||||||
|
class CheckoutRequest(BaseModel):
|
||||||
|
branch: str
|
||||||
|
|
||||||
|
|
||||||
|
class CommitRequest(BaseModel):
|
||||||
|
message: str
|
||||||
|
files: list[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class CommitResponse(BaseModel):
|
||||||
|
commit_hash: str
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
class FetchResponse(BaseModel):
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
class PullResponse(BaseModel):
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
class PushResponse(BaseModel):
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
class MergeRequest(BaseModel):
|
||||||
|
source_branch: str
|
||||||
|
target_branch: str | None = None
|
||||||
|
message: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class MergeResponse(BaseModel):
|
||||||
|
commit_hash: str
|
||||||
|
message: str
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# apps/api/src/schemas/system (index)
|
||||||
|
dir: apps/api/src/schemas/system
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides Pydantic schema definitions for system health monitoring API responses.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/schemas/.pi-map.index.md
|
||||||
|
map: apps/api/src/schemas/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- health.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/schemas/system/.pi-map.index.md
|
||||||
|
map: apps/api/src/schemas/system/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change system behavior
|
||||||
|
read: __init__.py, health.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# apps/api/src/schemas/system
|
||||||
|
dir: apps/api/src/schemas/system
|
||||||
|
|
||||||
|
index: apps/api/src/schemas/system/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides Pydantic schema definitions for system health monitoring API responses.
|
||||||
|
## files
|
||||||
|
- __init__.py | Re-exports health check schema classes from a submodule to provide a unified interface for the system schemas module. | dep: src.schemas.system.health
|
||||||
|
- health.py | Defines Pydantic data models for health check API responses including database, disk, and overall system health status. | exp: class:DatabaseHealth, class:DiskHealth, class:HealthChecks, class:HealthResponse, class:DatabaseHealthResponse | dep: pydantic
|
||||||
|
## arch
|
||||||
|
Simple modular schema layer with submodule organization and re-export pattern for clean API surface.
|
||||||
|
## tags
|
||||||
|
health, database, response, system, disk, check, schemas, init
|
||||||
|
## symbols
|
||||||
|
- DatabaseHealth
|
||||||
|
- DiskHealth
|
||||||
|
- HealthChecks
|
||||||
|
- HealthResponse
|
||||||
|
- DatabaseHealthResponse
|
||||||
|
## workflows
|
||||||
|
- change system behavior
|
||||||
|
read: __init__.py, health.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# apps/api/src/schemas/tool (index)
|
||||||
|
dir: apps/api/src/schemas/tool
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines Pydantic data validation schemas for tool instance and tool type API operations in the API layer.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/schemas/.pi-map.index.md
|
||||||
|
map: apps/api/src/schemas/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- tool_instance.py
|
||||||
|
- tool_type.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/schemas/tool/.pi-map.index.md
|
||||||
|
map: apps/api/src/schemas/tool/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change tool behavior
|
||||||
|
read: __init__.py, tool_instance.py, tool_type.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# apps/api/src/schemas/tool
|
||||||
|
dir: apps/api/src/schemas/tool
|
||||||
|
|
||||||
|
index: apps/api/src/schemas/tool/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Defines Pydantic data validation schemas for tool instance and tool type API operations in the API layer.
|
||||||
|
## files
|
||||||
|
- __init__.py | Package initialization file that exposes tool-related schema classes for import convenience. | dep: src.schemas.tool.tool_instance, src.schemas.tool.tool_type
|
||||||
|
- tool_instance.py | Defines Pydantic request/response schemas for tool instance lifecycle operations (create, create-on-workspace, start). | exp: class:CreateInstanceRequest, class:CreateWorkspaceInstanceRequest, class:StartInstanceRequest | dep: pydantic
|
||||||
|
- tool_type.py | Defines Pydantic request/response schemas for tool type CRUD operations with conditional validation logic based on definition type (compose/dockerfile/manifest). | exp: class:ToolTypeCreate, class:ToolTypeUpdate, class:ToolTypeResponse, class:ToolTypeValidateRequest | dep: uuid, datetime, pydantic, src.api.tool.tool_types_validation
|
||||||
|
## arch
|
||||||
|
Standard Pydantic schema package pattern with type-specific conditional validation using discriminated unions or field validators based on definition type.
|
||||||
|
## tags
|
||||||
|
tool, type, request, instance, create, schemas, pydantic, response
|
||||||
|
## symbols
|
||||||
|
- CreateInstanceRequest
|
||||||
|
- CreateWorkspaceInstanceRequest
|
||||||
|
- StartInstanceRequest
|
||||||
|
- ToolTypeCreate
|
||||||
|
- ToolTypeUpdate
|
||||||
|
- ToolTypeResponse
|
||||||
|
- ToolTypeValidateRequest
|
||||||
|
## workflows
|
||||||
|
- change tool behavior
|
||||||
|
read: __init__.py, tool_instance.py, tool_type.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
"""Tool schemas module."""
|
"""Tool schemas module."""
|
||||||
|
|
||||||
from src.schemas.tool.tool_instance import CreateInstanceRequest, StartInstanceRequest
|
from src.schemas.tool.tool_instance import (
|
||||||
|
CreateInstanceRequest,
|
||||||
|
CreateWorkspaceInstanceRequest,
|
||||||
|
StartInstanceRequest,
|
||||||
|
)
|
||||||
from src.schemas.tool.tool_type import (
|
from src.schemas.tool.tool_type import (
|
||||||
ToolTypeCreate,
|
ToolTypeCreate,
|
||||||
ToolTypeResponse,
|
ToolTypeResponse,
|
||||||
@@ -10,6 +14,7 @@ from src.schemas.tool.tool_type import (
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"CreateInstanceRequest",
|
"CreateInstanceRequest",
|
||||||
|
"CreateWorkspaceInstanceRequest",
|
||||||
"StartInstanceRequest",
|
"StartInstanceRequest",
|
||||||
"ToolTypeCreate",
|
"ToolTypeCreate",
|
||||||
"ToolTypeResponse",
|
"ToolTypeResponse",
|
||||||
|
|||||||
@@ -13,16 +13,24 @@ class CreateInstanceRequest(BaseModel):
|
|||||||
default=None, description="Optional display name for the instance"
|
default=None, description="Optional display name for the instance"
|
||||||
)
|
)
|
||||||
workspace_id: str | None = Field(
|
workspace_id: str | None = Field(
|
||||||
default=None, description="UUID of workspace to mount (replaces clone_mode)"
|
default=None, description="UUID of workspace to mount"
|
||||||
)
|
)
|
||||||
clone_mode: str = Field(
|
config_profile_id: str | None = Field(
|
||||||
default="mount", description="Repository access mode: 'mount' or 'clone'"
|
default=None, description="Optional config profile ID for launch"
|
||||||
)
|
)
|
||||||
branch: str | None = Field(
|
ssh_key_ids: list[str] = Field(
|
||||||
default="main", description="Branch to clone (when clone_mode='clone')"
|
default_factory=list, description="SSH key IDs to mount into container ~/.ssh"
|
||||||
)
|
)
|
||||||
new_branch: str | None = Field(
|
|
||||||
default=None, description="Create a new local branch after cloning"
|
|
||||||
|
class CreateWorkspaceInstanceRequest(BaseModel):
|
||||||
|
"""Request body for creating a tool instance directly on a workspace."""
|
||||||
|
|
||||||
|
model_config = {"extra": "ignore"}
|
||||||
|
|
||||||
|
tool_type_id: str = Field(description="UUID of the tool type to instantiate")
|
||||||
|
display_name: str | None = Field(
|
||||||
|
default=None, description="Optional display name for the instance"
|
||||||
)
|
)
|
||||||
config_profile_id: str | None = Field(
|
config_profile_id: str | None = Field(
|
||||||
default=None, description="Optional config profile ID for launch"
|
default=None, description="Optional config profile ID for launch"
|
||||||
|
|||||||
@@ -82,8 +82,8 @@ class ToolTypeCreate(BaseModel):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def validate_default_port(cls, v: int, info) -> int:
|
def validate_default_port(cls, v: int, info) -> int:
|
||||||
data = info.data
|
data = info.data
|
||||||
requires_port = data.get("requires_port", True)
|
requires_port = data.get("requires_port")
|
||||||
if not requires_port:
|
if requires_port is False:
|
||||||
return v
|
return v
|
||||||
if v <= 0 or v > 65535:
|
if v <= 0 or v > 65535:
|
||||||
raise ValueError("Port must be between 1 and 65535")
|
raise ValueError("Port must be between 1 and 65535")
|
||||||
@@ -140,7 +140,7 @@ class ToolTypeCreate(BaseModel):
|
|||||||
|
|
||||||
if not check_port_exposed(parsed, self.default_port):
|
if not check_port_exposed(parsed, self.default_port):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Port {self.default_port} is not exposed in the compose template. Add it to the 'ports' section."
|
f"default_port {self.default_port} is not exposed in the compose template. Add it to the 'ports' section."
|
||||||
)
|
)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# apps/api/src/schemas/user (index)
|
||||||
|
dir: apps/api/src/schemas/user
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides Pydantic data validation schemas for user-related API requests and responses.
|
||||||
|
## parent
|
||||||
|
index: apps/api/src/schemas/.pi-map.index.md
|
||||||
|
map: apps/api/src/schemas/.pi-map.md
|
||||||
|
## children
|
||||||
|
-
|
||||||
|
## files
|
||||||
|
- __init__.py
|
||||||
|
- user.py
|
||||||
|
- user_config.py
|
||||||
|
## links
|
||||||
|
index: apps/api/src/schemas/user/.pi-map.index.md
|
||||||
|
map: apps/api/src/schemas/user/.pi-map.md
|
||||||
|
## workflows
|
||||||
|
- change user behavior
|
||||||
|
read: __init__.py, user.py, user_config.py
|
||||||
|
- change user config
|
||||||
|
read: user_config.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# apps/api/src/schemas/user
|
||||||
|
dir: apps/api/src/schemas/user
|
||||||
|
|
||||||
|
index: apps/api/src/schemas/user/.pi-map.index.md
|
||||||
|
|
||||||
|
## role
|
||||||
|
Provides Pydantic data validation schemas for user-related API requests and responses.
|
||||||
|
## files
|
||||||
|
- __init__.py | Re-exports user-related Pydantic schemas from submodules to provide a unified public API for the user schemas package. | dep: src.schemas.user.user, src.schemas.user.user_config
|
||||||
|
- user.py | Defines Pydantic response and update schemas for user profile data | exp: class:UserProfileResponse, class:UserProfileUpdate | dep: uuid, pydantic
|
||||||
|
- user_config.py | Defines Pydantic models for user configuration data transfer objects (response and update schemas). | exp: class:UserConfigResponse, class:UserConfigUpdate | dep: pydantic
|
||||||
|
## arch
|
||||||
|
Modular schema organization with submodule separation of concerns and unified package-level re-exports for clean imports.
|
||||||
|
## tags
|
||||||
|
user, schemas, response, update, config, pydantic, profile, src
|
||||||
|
## symbols
|
||||||
|
- UserProfileResponse
|
||||||
|
- UserProfileUpdate
|
||||||
|
- UserConfigResponse
|
||||||
|
- UserConfigUpdate
|
||||||
|
## workflows
|
||||||
|
- change user behavior
|
||||||
|
read: __init__.py, user.py, user_config.py
|
||||||
|
- change user config
|
||||||
|
read: user_config.py
|
||||||
|
## dirty
|
||||||
|
-
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user