Document the planned CRUD + PR management API for GNS3 skills (prompts, fault injection, device skills, packet analysis), enabling the Web UI to browse, edit, save, and contribute skill changes back to the upstream repository. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
9.7 KiB
This documentation is organized by AI with reference to actual code. AI can make mistakes — please verify against the source code when in doubt.
Skills Editor API
Overview
A management API that allows the Web UI to browse, edit, save, and contribute skill files (prompts, fault injection, device skills, packet analysis protocols) back to the upstream GNS3-Skills repository via Pull Requests.
Today, skills are read-only from the server's perspective — the only management endpoint is POST /copilot/reload/skills for hot-reloading. This API adds full CRUD operations on the local skills repository plus Git commit/push/PR workflows.
Architecture
graph TD
subgraph "Web UI"
UI[Skills Editor Page]
end
subgraph "GNS3 Server API"
API[skills_editor.py<br/>/copilot/skills/*]
FM[SkillsFileManager]
SM[SkillsManager]
end
subgraph "Local Git Repo"
INJ[injection/*.yaml]
DEV[device/*.yaml]
PA[packet_analysis/*.yaml]
PRM[prompts/*.md]
CFG[config/*.txt]
end
subgraph "Remote"
GH[GitHub API<br/>Pull Requests]
REPO[yueguobin/GNS3-Skills]
end
UI -->|CRUD + PR| API
API --> FM
FM -->|file read/write| INJ
FM -->|file read/write| DEV
FM -->|file read/write| PA
FM -->|file read/write| PRM
FM -->|file read/write| CFG
FM -->|git commit/push| REPO
FM -->|create PR| GH
SM -->|hot reload| INJ
SM -->|hot reload| DEV
SM -->|hot reload| PA
SM -->|hot reload| PRM
Business Process
Edit and Contribute Flow
sequenceDiagram
participant UI as Web UI
participant API as Skills Editor API
participant FM as SkillsFileManager
participant Git as Local Git Repo
participant GH as GitHub
UI->>API: GET /copilot/skills
API-->>UI: List categories + file counts
UI->>API: GET /copilot/skills/injection
API-->>UI: List YAML files
UI->>API: GET /copilot/skills/injection/ospf_issues
API->>FM: read_file("injection", "ospf_issues")
FM-->>API: YAML content
API-->>UI: File content
UI->>API: PUT /copilot/skills/injection/ospf_issues<br/>{content: "..."}
API->>FM: write_file("injection", "ospf_issues", content)
FM->>Git: Write file to disk
FM-->>API: Success
API-->>UI: Updated
UI->>API: POST /copilot/skills/commit<br/>{message: "fix: update OSPF fault"}
API->>FM: commit_changes(message, files)
FM->>Git: git add + git commit
FM-->>API: Commit hash
API-->>UI: Committed
UI->>API: POST /copilot/skills/pull-request<br/>{title, body, branch}
API->>FM: create_pull_request(...)
FM->>Git: git push origin <branch>
FM->>GH: POST /repos/{owner}/{repo}/pulls
GH-->>FM: PR URL
FM-->>API: PR created
API-->>UI: PR URL
UI->>API: POST /copilot/skills/reload
API->>FM: Hot reload all registries
API-->>UI: Reloaded
Valid Categories
| Category | Directory | File Extension | Content |
|---|---|---|---|
prompts |
prompts/ |
.md |
System prompts (teaching_assistant, lab_automation_assistant, etc.) |
injection |
injection/ |
.yaml |
Fault injection skill definitions (OSPF, BGP, VLAN, etc.) |
device |
device/ |
.yaml |
Device-specific command knowledge |
packet_analysis |
packet_analysis/ |
.yaml |
Protocol definitions for tshark-based analysis |
config |
config/ |
.txt |
Security and configuration files (forbidden_commands, etc.) |
API Endpoints
All endpoints require superadmin authentication. Prefix: /v3/copilot/skills.
| Method | Path | Description |
|---|---|---|
| GET | /copilot/skills |
List all categories with file counts |
| GET | /copilot/skills/{category} |
List files in a category |
| GET | /copilot/skills/{category}/{filename} |
Read file content (without extension) |
| POST | /copilot/skills/{category} |
Create a new skill file |
| PUT | /copilot/skills/{category}/{filename} |
Update existing file content |
| DELETE | /copilot/skills/{category}/{filename} |
Delete a skill file |
| GET | /copilot/skills/status |
Git status (modified/untracked/deleted files) + repo info |
| POST | /copilot/skills/commit |
Stage and commit changes |
| POST | /copilot/skills/push |
Push a branch to remote |
| POST | /copilot/skills/pull-request |
Create a Pull Request (direct or via fork) |
| POST | /copilot/skills/reload |
Hot reload all skills into memory (replaces /reload/skills) |
| POST | /copilot/skills/rollback/{commit_hash} |
Rollback repository to a specific commit |
Response Examples
GET /copilot/skills — List categories
{
"categories": [
{"category": "injection", "file_count": 39, "path": "injection/"},
{"category": "device", "file_count": 2, "path": "device/"},
{"category": "packet_analysis", "file_count": 8, "path": "packet_analysis/"},
{"category": "prompts", "file_count": 4, "path": "prompts/"},
{"category": "config", "file_count": 1, "path": "config/"}
],
"repository": {
"repo_url": "https://github.com/yueguobin/GNS3-Skills.git",
"branch": "main",
"current_version": "abc123def456...",
"is_dirty": false
}
}
GET /copilot/skills/injection — List files in category
{
"category": "injection",
"files": [
{"filename": "ospf_issues", "extension": ".yaml", "size": 4521, "last_modified": "2026-05-10T08:30:00Z"},
{"filename": "bgp_issues", "extension": ".yaml", "size": 3820, "last_modified": "2026-05-09T14:00:00Z"}
]
}
GET /copilot/skills/injection/ospf_issues — Read file content
{
"category": "injection",
"filename": "ospf_issues",
"extension": ".yaml",
"content": "name: OSPF Fault Injection\n...\n",
"size": 4521,
"last_modified": "2026-05-10T08:30:00Z"
}
PUT /copilot/skills/injection/ospf_issues — Update file
{
"content": "name: OSPF Fault Injection\n..."
}
Response:
{
"category": "injection",
"filename": "ospf_issues",
"size": 4600,
"last_modified": "2026-05-12T10:00:00Z",
"status": "modified"
}
POST /copilot/skills/commit — Commit changes
Request:
{
"message": "fix: update OSPF hello/dead interval fault descriptions",
"files": ["injection/ospf_issues.yaml"]
}
Response:
{
"success": true,
"commit_hash": "def456abc789...",
"message": "fix: update OSPF hello/dead interval fault descriptions",
"files_committed": 1
}
POST /copilot/skills/pull-request — Create PR
Request:
{
"title": "Fix OSPF fault injection descriptions",
"body": "Updated OSPF hello/dead interval fault descriptions for clarity.",
"branch": "fix/ospf-descriptions",
"target_branch": "main",
"fork_url": "https://github.com/user/GNS3-Skills.git"
}
Response:
{
"success": true,
"pr_url": "https://github.com/yueguobin/GNS3-Skills/pull/42",
"pr_number": 42,
"branch": "fix/ospf-descriptions"
}
GET /copilot/skills/status — Git status
{
"current_version": "abc123def456...",
"branch": "main",
"is_dirty": true,
"modified": ["injection/ospf_issues.yaml"],
"untracked": [],
"deleted": [],
"staged": [],
"available_versions": [
{"hash": "abc123def456", "message": "Add MPLS fault scenarios", "author": "dev", "date": "2026-05-10T08:00:00Z"}
]
}
Implementation Plan
New Files
| File | Purpose |
|---|---|
gns3server/agent/gns3_copilot/skills/file_manager.py |
SkillsFileManager class — file CRUD, git commit/push, GitHub PR API |
gns3server/api/routes/controller/skills_editor.py |
FastAPI router with all endpoints above |
Modified Files
| File | Change |
|---|---|
gns3server/agent/gns3_copilot/skills/manager.py |
Add get_file_manager() method returning a SkillsFileManager |
gns3server/api/routes/controller/__init__.py |
Register skills_editor router under /copilot/skills prefix |
gns3server/api/routes/controller/copilot.py |
Deprecate /reload/skills in favor of /skills/reload |
SkillsFileManager Key Methods
| Method | Returns |
|---|---|
list_categories() |
list[{category, file_count, path}] |
list_files(category) |
list[{filename, size, last_modified}] |
read_file(category, filename) |
File content as string |
write_file(category, filename, content) |
Write (create or update) |
delete_file(category, filename) |
Delete file |
get_git_status() |
Modified/untracked/deleted file lists |
commit_changes(message, files) |
git add + commit |
push_to_remote(branch) |
git push |
create_pull_request(title, body, branch, target, fork_url) |
Push + GitHub PR API |
Security
- Authentication: All endpoints require superadmin (
current_user.is_superadmincheck) - Path traversal prevention: Category validated against whitelist; filename sanitized (no
/,.., or absolute paths) - File extension enforcement:
.yamlfor injection/device/packet_analysis,.mdfor prompts,.txtfor config - File size limit: Reject files > 1MB
- YAML validation: Validate with
yaml.safe_loadbefore saving - GitHub token: Required for PR creation, stored in GNS3 server config
PR Creation Modes
- Direct push: Push to a new branch on the main repo, create PR via GitHub API
- Fork: Push to user's fork, create PR against upstream
The fork_url parameter selects the mode. When omitted, the API pushes to the same repository and creates a PR directly.