Skip to content

Commit a63765e

Browse files
committed
Add Pi Coding Agent support to skills management
Adds Pi Coding Agent as a new supported tool alongside Claude, Codex, Copilot, Gemini, Droid, CodeBuddy, and Qwen. Includes: - New PiCodingAgentSkillHandler for managing Pi skills at ~/.pi/agent/skills/ - Tool configuration for Pi in tools.yaml and tool registries - CLI integration with app type validation - Comprehensive test coverage with 7 dedicated tests - Fixed plugin CLI tests to use --app option consistently All tests passing: 49/49 unit tests (7 pi_coding_agent + 42 related tests)
1 parent eb35c67 commit a63765e

File tree

13 files changed

+304
-25
lines changed

13 files changed

+304
-25
lines changed

code_assistant_manager/cli/app.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@
55
import typer
66
from typer import Context
77

8+
# Import get_registered_tools and ConfigManager at module level for dynamic command creation
9+
from code_assistant_manager.tools import (
10+
get_registered_tools,
11+
display_all_tool_endpoints,
12+
display_tool_endpoints,
13+
)
14+
from code_assistant_manager.config import ConfigManager
15+
816
try:
917
import tomllib
1018
except ImportError:
@@ -1425,6 +1433,10 @@ def command(
14251433
# Add the editor app as a subcommand to the main app
14261434
app.add_typer(editor_app, name="launch")
14271435
app.add_typer(editor_app, name="l", hidden=True)
1436+
1437+
# Create dynamic editor subcommands for each registered tool
1438+
create_editor_subcommands()
1439+
14281440
# Add the config app as a subcommand to the main app
14291441
app.add_typer(config_app, name="config")
14301442
app.add_typer(config_app, name="cf", hidden=True)

code_assistant_manager/cli/skills_commands.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
logger = logging.getLogger(__name__)
2020

2121
skill_app = typer.Typer(
22-
help="Manage skills for AI assistants (Claude, Codex, Copilot, Gemini, Droid, CodeBuddy, Qwen)",
22+
help="Manage skills for AI assistants (Claude, Codex, Copilot, Gemini, Droid, CodeBuddy, Qwen, Pi Coding Agent)",
2323
no_args_is_help=True,
2424
)
2525

@@ -32,7 +32,7 @@ def _get_skill_manager() -> SkillManager:
3232
@skill_app.command("list")
3333
def list_skills(
3434
app_type: str = typer.Option(
35-
"claude", "--app", help="App type(s) to check installed status (claude, codex, copilot, gemini, qwen, all)",
35+
"claude", "--app", help="App type(s) to check installed status (claude, codex, copilot, gemini, droid, codebuddy, qwen, pi-coding-agent, all)",
3636
),
3737
query: Optional[str] = typer.Option(
3838
None, "--query", help="Filter skills by repository name (e.g., 'BrownFineSecurity/iothackbot')",
@@ -367,7 +367,7 @@ def delete_skill(
367367
def install_skill(
368368
skill_key: str = typer.Argument(..., help="Skill identifier"),
369369
app_type: str = typer.Option(
370-
"claude", "--app", help="App type(s) to install to (claude, codex, gemini, qwen, all)",
370+
"claude", "--app", help="App type(s) to install to (claude, codex, copilot, gemini, droid, codebuddy, qwen, pi-coding-agent, all)",
371371
),
372372
):
373373
"""Install a skill to one or more app skills directories."""
@@ -392,7 +392,7 @@ def install_skill(
392392
def uninstall_skill(
393393
skill_key: str = typer.Argument(..., help="Skill identifier"),
394394
app_type: str = typer.Option(
395-
"claude", "--app", help="App type(s) to uninstall from (claude, codex, gemini, qwen, all)",
395+
"claude", "--app", help="App type(s) to uninstall from (claude, codex, copilot, gemini, droid, codebuddy, qwen, pi-coding-agent, all)",
396396
),
397397
):
398398
"""Uninstall a skill from one or more app skills directories."""
@@ -521,7 +521,7 @@ def export_skills(
521521
@skill_app.command("status")
522522
def skill_status(
523523
app_type: Optional[str] = typer.Option(
524-
None, "--app", help="App type(s) to show (claude, codex, gemini, qwen, all). Default shows all.",
524+
None, "--app", help="App type(s) to show (claude, codex, copilot, gemini, droid, codebuddy, qwen, pi-coding-agent, all). Default shows all.",
525525
),
526526
):
527527
"""Show skill installation status across apps (alias: installed)."""
@@ -531,7 +531,7 @@ def skill_status(
531531
@skill_app.command("installed")
532532
def list_installed_skills(
533533
app_type: Optional[str] = typer.Option(
534-
None, "--app", help="App type(s) to show (claude, codex, gemini, qwen, all). Default shows all.",
534+
None, "--app", help="App type(s) to show (claude, codex, copilot, gemini, droid, codebuddy, qwen, pi-coding-agent, all). Default shows all.",
535535
),
536536
):
537537
"""Show installed skills for each app."""
@@ -585,7 +585,7 @@ def list_installed_skills(
585585
@skill_app.command("uninstall-all")
586586
def uninstall_all_skills(
587587
app_type: str = typer.Option(
588-
..., "--app", help="App type(s) to uninstall all skills from (claude, codex, gemini, qwen, all)",
588+
..., "--app", help="App type(s) to uninstall all skills from (claude, codex, copilot, gemini, droid, codebuddy, qwen, pi-coding-agent, all)",
589589
),
590590
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
591591
):

code_assistant_manager/configs/tools/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from .qoder import QoderConfig
1818
from .zed import ZedConfig
1919
from .opencode import OpenCodeConfig
20+
from .pi_coding_agent import PiCodingAgentConfig
2021

2122
# Registry
2223
_CONFIG_CLASSES: Dict[str, Type[BaseToolConfig]] = {
@@ -34,6 +35,7 @@
3435
"qodercli": QoderConfig,
3536
"zed": ZedConfig,
3637
"opencode": OpenCodeConfig,
38+
"pi-coding-agent": PiCodingAgentConfig,
3739
}
3840

3941

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from pathlib import Path
2+
from typing import Dict, List
3+
from ..base import BaseToolConfig
4+
5+
class PiCodingAgentConfig(BaseToolConfig):
6+
def __init__(self):
7+
super().__init__("pi-coding-agent")
8+
9+
def get_scope_paths(self) -> Dict[str, List[Path]]:
10+
return {
11+
"user": [
12+
self.home / ".pi" / "agent" / "settings.json",
13+
],
14+
"project": [
15+
self.cwd / ".pi" / "settings.json",
16+
],
17+
}

code_assistant_manager/skills/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from .copilot import CopilotSkillHandler
1818
from .droid import DroidSkillHandler
1919
from .gemini import GeminiSkillHandler
20+
from .pi_coding_agent import PiCodingAgentSkillHandler
2021
from .qwen import QwenSkillHandler
2122
from .models import Skill, SkillRepo
2223
from .manager import SkillManager, VALID_APP_TYPES
@@ -32,6 +33,7 @@
3233
"GeminiSkillHandler",
3334
"DroidSkillHandler",
3435
"CodebuddySkillHandler",
36+
"PiCodingAgentSkillHandler",
3537
"QwenSkillHandler",
3638
"VALID_APP_TYPES",
3739
]

code_assistant_manager/skills/manager.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from .copilot import CopilotSkillHandler
1919
from .droid import DroidSkillHandler
2020
from .gemini import GeminiSkillHandler
21+
from .pi_coding_agent import PiCodingAgentSkillHandler
2122
from .qwen import QwenSkillHandler
2223
from .models import Skill, SkillRepo
2324

@@ -98,6 +99,7 @@ def _load_skill_repos_from_config(config_dir: Optional[Path] = None) -> List[Dic
9899
"gemini": GeminiSkillHandler,
99100
"droid": DroidSkillHandler,
100101
"codebuddy": CodebuddySkillHandler,
102+
"pi-coding-agent": PiCodingAgentSkillHandler,
101103
"qwen": QwenSkillHandler,
102104
}
103105

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""Pi Coding Agent skill handler implementation."""
2+
3+
import logging
4+
from pathlib import Path
5+
6+
from .base import BaseSkillHandler
7+
8+
logger = logging.getLogger(__name__)
9+
10+
11+
class PiCodingAgentSkillHandler(BaseSkillHandler):
12+
"""Handler for Pi Coding Agent skills."""
13+
14+
@property
15+
def app_name(self) -> str:
16+
return "pi-coding-agent"
17+
18+
@property
19+
def _default_skills_dir(self) -> Path:
20+
"""Get the default skills directory for Pi Coding Agent.
21+
22+
Pi Coding Agent loads skills from multiple locations:
23+
- Global: ~/.pi/agent/skills/
24+
- Project: .pi/skills/
25+
26+
Using the global location as primary default.
27+
"""
28+
return Path.home() / ".pi" / "agent" / "skills"
29+

code_assistant_manager/tools.yaml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,3 +436,41 @@ tools:
436436
filesystem:
437437
touched:
438438
- "~/.config/aichat/config.yaml (AIChat configuration file)"
439+
440+
pi-coding-agent:
441+
enabled: true
442+
install_cmd: npm install -g @mariozechner/pi-coding-agent
443+
cli_command: pi
444+
description: "Pi Coding Agent"
445+
env:
446+
managed:
447+
NODE_TLS_REJECT_UNAUTHORIZED: "0"
448+
configuration:
449+
required:
450+
defaultProvider: "LLM provider name (e.g., 'anthropic')"
451+
defaultModel: "Model ID to use (e.g., 'claude-sonnet-4-20250514')"
452+
optional:
453+
defaultThinkingLevel: "Valid values: off, minimal, low, medium, high, xhigh"
454+
hideThinkingBlock: "Hide thinking blocks from output (boolean)"
455+
theme: "UI theme (default: dark)"
456+
quietStartup: "Skip startup messages (boolean)"
457+
enableSkillCommands: "Enable interactive skill commands (boolean, default: true)"
458+
steeringMode: "Message delivery mode (one-at-a-time or all)"
459+
transport: "Message transport (sse, websocket, or auto)"
460+
notes:
461+
- "Settings are stored at ~/.pi/agent/settings.json (global) and .pi/settings.json (project)"
462+
- "Project settings override global settings via JSON merging"
463+
- "Skills are loaded from: ~/.pi/agent/skills/, ~/.agents/skills/, .pi/skills/, .agents/skills/"
464+
- "Custom skill paths can be added to the 'skills' array in settings.json"
465+
- "Use --no-skills to disable automatic skill discovery at startup"
466+
- "Use --skill <path> to explicitly load specific skills"
467+
cli_parameters:
468+
injected: []
469+
filesystem:
470+
touched:
471+
- "~/.pi/agent/settings.json (Global Pi configuration)"
472+
- ".pi/settings.json (Project-level Pi configuration)"
473+
- "~/.pi/agent/skills/ (Global skills directory)"
474+
- "~/.agents/skills/ (Alternative global skills directory)"
475+
- ".pi/skills/ (Project skills directory)"
476+
- ".agents/skills/ (Alternative project skills directory)"

code_assistant_manager/tools/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def _ensure_tools_loaded() -> None:
6363
kimi,
6464
neovate,
6565
opencode,
66+
pi_coding_agent,
6667
qodercli,
6768
qwen,
6869
zed,
@@ -141,6 +142,7 @@ def __getattr__(name: str):
141142
"KimiTool": "kimi",
142143
"NeovateTool": "neovate",
143144
"OpenCodeTool": "opencode",
145+
"PiCodingAgentTool": "pi_coding_agent",
144146
"QoderCLITool": "qodercli",
145147
"QwenTool": "qwen",
146148
"ZedTool": "zed",
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import os
2+
from typing import List
3+
4+
from .base import CLITool
5+
6+
7+
class PiCodingAgentTool(CLITool):
8+
"""Pi Coding Agent CLI wrapper."""
9+
10+
command_name = "pi"
11+
tool_key = "pi-coding-agent"
12+
install_description = "Pi Coding Agent"
13+
14+
def run(self, args: List[str] = None) -> int:
15+
"""
16+
Run the Pi Coding Agent with the specified arguments.
17+
18+
Args:
19+
args: List of arguments to pass to Pi Coding Agent
20+
21+
Returns:
22+
Exit code of the Pi Coding Agent process
23+
"""
24+
args = args or []
25+
26+
# Load environment
27+
load_env = __import__(
28+
"code_assistant_manager.env_loader", fromlist=["load_env"]
29+
).load_env
30+
load_env()
31+
32+
env = os.environ.copy()
33+
self._set_node_tls_env(env)
34+
35+
# Check if pi command is available
36+
if not self._ensure_tool_installed(
37+
self.command_name, self.tool_key, self.install_description
38+
):
39+
return 1
40+
41+
try:
42+
command = ["pi"] + args
43+
44+
# Display the complete command
45+
args_str = " ".join(args) if args else ""
46+
command_str = f"pi {args_str}".strip()
47+
print("")
48+
print("Complete command to execute:")
49+
print(command_str)
50+
print("")
51+
52+
result = self._run_command(command, env=env)
53+
return result.returncode
54+
except KeyboardInterrupt:
55+
return 130
56+
except Exception as e:
57+
return self._handle_error("Error running pi", e)

0 commit comments

Comments
 (0)