pcli-agent 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pcli_agent-0.1.0/.claude/scheduled_tasks.lock +1 -0
- pcli_agent-0.1.0/.claude/settings.local.json +34 -0
- pcli_agent-0.1.0/.github/workflows/ci.yml +52 -0
- pcli_agent-0.1.0/.gitignore +58 -0
- pcli_agent-0.1.0/LICENSE +21 -0
- pcli_agent-0.1.0/PKG-INFO +259 -0
- pcli_agent-0.1.0/README.md +207 -0
- pcli_agent-0.1.0/assets/logo-dark.svg +6 -0
- pcli_agent-0.1.0/assets/logo.jpg +0 -0
- pcli_agent-0.1.0/assets/logo.svg +6 -0
- pcli_agent-0.1.0/docs/README.md +76 -0
- pcli_agent-0.1.0/docs/agent-tools-guide.md +178 -0
- pcli_agent-0.1.0/docs/browser-automation.md +193 -0
- pcli_agent-0.1.0/docs/configuration.md +747 -0
- pcli_agent-0.1.0/docs/development.md +103 -0
- pcli_agent-0.1.0/docs/headless-and-scheduled-runs.md +231 -0
- pcli_agent-0.1.0/docs/memory.md +304 -0
- pcli_agent-0.1.0/docs/sandbox-and-permissions.md +436 -0
- pcli_agent-0.1.0/docs/scheduling.md +316 -0
- pcli_agent-0.1.0/docs/sessions-and-cost.md +603 -0
- pcli_agent-0.1.0/docs/telegram-bot.md +669 -0
- pcli_agent-0.1.0/docs/toolbox-plugins.md +143 -0
- pcli_agent-0.1.0/docs/tools.md +1506 -0
- pcli_agent-0.1.0/docs/tui-guide.md +1680 -0
- pcli_agent-0.1.0/pyproject.toml +87 -0
- pcli_agent-0.1.0/requirements-dev.txt +10 -0
- pcli_agent-0.1.0/requirements.txt +19 -0
- pcli_agent-0.1.0/src/pcli/__init__.py +1 -0
- pcli_agent-0.1.0/src/pcli/__main__.py +4 -0
- pcli_agent-0.1.0/src/pcli/agent/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/agent/activity.py +116 -0
- pcli_agent-0.1.0/src/pcli/agent/compaction.py +205 -0
- pcli_agent-0.1.0/src/pcli/agent/context_pruning.py +88 -0
- pcli_agent-0.1.0/src/pcli/agent/headless.py +209 -0
- pcli_agent-0.1.0/src/pcli/agent/loop.py +442 -0
- pcli_agent-0.1.0/src/pcli/agent/prompt.py +371 -0
- pcli_agent-0.1.0/src/pcli/agent/runtime.py +240 -0
- pcli_agent-0.1.0/src/pcli/browser/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/browser/session.py +135 -0
- pcli_agent-0.1.0/src/pcli/cli.py +757 -0
- pcli_agent-0.1.0/src/pcli/config/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/config/paths.py +95 -0
- pcli_agent-0.1.0/src/pcli/config/settings.py +435 -0
- pcli_agent-0.1.0/src/pcli/cost/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/cost/context.py +275 -0
- pcli_agent-0.1.0/src/pcli/cost/context_detect.py +183 -0
- pcli_agent-0.1.0/src/pcli/cost/pricing_table.py +141 -0
- pcli_agent-0.1.0/src/pcli/cost/tracker.py +126 -0
- pcli_agent-0.1.0/src/pcli/llm/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/llm/client.py +285 -0
- pcli_agent-0.1.0/src/pcli/llm/errors.py +37 -0
- pcli_agent-0.1.0/src/pcli/llm/models.py +100 -0
- pcli_agent-0.1.0/src/pcli/llm/streaming.py +108 -0
- pcli_agent-0.1.0/src/pcli/memory/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/memory/extraction.py +106 -0
- pcli_agent-0.1.0/src/pcli/memory/models.py +103 -0
- pcli_agent-0.1.0/src/pcli/memory/store.py +88 -0
- pcli_agent-0.1.0/src/pcli/permissions/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/permissions/guardrails.py +219 -0
- pcli_agent-0.1.0/src/pcli/permissions/manager.py +215 -0
- pcli_agent-0.1.0/src/pcli/permissions/policy.py +70 -0
- pcli_agent-0.1.0/src/pcli/sandbox/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/sandbox/base.py +50 -0
- pcli_agent-0.1.0/src/pcli/sandbox/docker_backend.py +107 -0
- pcli_agent-0.1.0/src/pcli/sandbox/limits.py +63 -0
- pcli_agent-0.1.0/src/pcli/sandbox/null_backend.py +92 -0
- pcli_agent-0.1.0/src/pcli/sandbox/selector.py +75 -0
- pcli_agent-0.1.0/src/pcli/sandbox/subprocess_backend.py +376 -0
- pcli_agent-0.1.0/src/pcli/scheduler/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/scheduler/daemon.py +194 -0
- pcli_agent-0.1.0/src/pcli/scheduler/models.py +97 -0
- pcli_agent-0.1.0/src/pcli/scheduler/runner.py +84 -0
- pcli_agent-0.1.0/src/pcli/scheduler/store.py +75 -0
- pcli_agent-0.1.0/src/pcli/scheduler/triggers.py +84 -0
- pcli_agent-0.1.0/src/pcli/session/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/session/audit.py +122 -0
- pcli_agent-0.1.0/src/pcli/session/directory_check.py +28 -0
- pcli_agent-0.1.0/src/pcli/session/export.py +57 -0
- pcli_agent-0.1.0/src/pcli/session/importer.py +92 -0
- pcli_agent-0.1.0/src/pcli/session/models.py +168 -0
- pcli_agent-0.1.0/src/pcli/session/store.py +127 -0
- pcli_agent-0.1.0/src/pcli/telegram/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/telegram/bot.py +266 -0
- pcli_agent-0.1.0/src/pcli/telegram/daemon.py +1197 -0
- pcli_agent-0.1.0/src/pcli/telegram/permissions.py +131 -0
- pcli_agent-0.1.0/src/pcli/telegram/sender.py +58 -0
- pcli_agent-0.1.0/src/pcli/tools/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/tools/_nested_agent.py +204 -0
- pcli_agent-0.1.0/src/pcli/tools/agent_tools.py +264 -0
- pcli_agent-0.1.0/src/pcli/tools/agent_tools_store.py +69 -0
- pcli_agent-0.1.0/src/pcli/tools/artifacts.py +47 -0
- pcli_agent-0.1.0/src/pcli/tools/base.py +185 -0
- pcli_agent-0.1.0/src/pcli/tools/builtin/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/tools/builtin/agent_tool_register_tool.py +100 -0
- pcli_agent-0.1.0/src/pcli/tools/builtin/artifact_tool.py +212 -0
- pcli_agent-0.1.0/src/pcli/tools/builtin/ask_tool.py +77 -0
- pcli_agent-0.1.0/src/pcli/tools/builtin/browser_tool.py +253 -0
- pcli_agent-0.1.0/src/pcli/tools/builtin/decision_tool.py +73 -0
- pcli_agent-0.1.0/src/pcli/tools/builtin/describe_tool.py +389 -0
- pcli_agent-0.1.0/src/pcli/tools/builtin/diff_tools.py +225 -0
- pcli_agent-0.1.0/src/pcli/tools/builtin/fs_tools.py +371 -0
- pcli_agent-0.1.0/src/pcli/tools/builtin/grep_tool.py +88 -0
- pcli_agent-0.1.0/src/pcli/tools/builtin/memory_tool.py +108 -0
- pcli_agent-0.1.0/src/pcli/tools/builtin/network_tools.py +107 -0
- pcli_agent-0.1.0/src/pcli/tools/builtin/pip_tool.py +106 -0
- pcli_agent-0.1.0/src/pcli/tools/builtin/shell_tool.py +240 -0
- pcli_agent-0.1.0/src/pcli/tools/builtin/subagent_tool.py +146 -0
- pcli_agent-0.1.0/src/pcli/tools/builtin/todo_tool.py +122 -0
- pcli_agent-0.1.0/src/pcli/tools/builtin/toolbox_register_tool.py +76 -0
- pcli_agent-0.1.0/src/pcli/tools/builtin/web_tools.py +322 -0
- pcli_agent-0.1.0/src/pcli/tools/pydiscovery/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/tools/pydiscovery/cache.py +51 -0
- pcli_agent-0.1.0/src/pcli/tools/pydiscovery/index.py +48 -0
- pcli_agent-0.1.0/src/pcli/tools/pydiscovery/invoke.py +181 -0
- pcli_agent-0.1.0/src/pcli/tools/pydiscovery/search.py +117 -0
- pcli_agent-0.1.0/src/pcli/tools/registry.py +138 -0
- pcli_agent-0.1.0/src/pcli/tools/toolbox/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/tools/toolbox/introspect.py +48 -0
- pcli_agent-0.1.0/src/pcli/tools/toolbox/manager.py +336 -0
- pcli_agent-0.1.0/src/pcli/tools/toolbox/plugin_base.py +51 -0
- pcli_agent-0.1.0/src/pcli/tools/toolbox/plugins/__init__.py +6 -0
- pcli_agent-0.1.0/src/pcli/tools/toolbox/plugins/httpd.py +99 -0
- pcli_agent-0.1.0/src/pcli/tools/toolbox/plugins/kafka.py +162 -0
- pcli_agent-0.1.0/src/pcli/tools/toolbox/plugins/kubectl.py +211 -0
- pcli_agent-0.1.0/src/pcli/tools/toolbox/plugins/sge.py +146 -0
- pcli_agent-0.1.0/src/pcli/tools/toolbox/store.py +65 -0
- pcli_agent-0.1.0/src/pcli/tools/toolbox/synthesize.py +100 -0
- pcli_agent-0.1.0/src/pcli/tui/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/tui/app.py +37 -0
- pcli_agent-0.1.0/src/pcli/tui/screens/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/tui/screens/ask_question_modal.py +54 -0
- pcli_agent-0.1.0/src/pcli/tui/screens/chat.py +2070 -0
- pcli_agent-0.1.0/src/pcli/tui/screens/confirm_modal.py +39 -0
- pcli_agent-0.1.0/src/pcli/tui/screens/models.py +43 -0
- pcli_agent-0.1.0/src/pcli/tui/screens/permission_modal.py +71 -0
- pcli_agent-0.1.0/src/pcli/tui/screens/sessions.py +162 -0
- pcli_agent-0.1.0/src/pcli/tui/screens/subagent_activity_modal.py +71 -0
- pcli_agent-0.1.0/src/pcli/tui/shell_passthrough.py +56 -0
- pcli_agent-0.1.0/src/pcli/tui/styles/pcli.tcss +241 -0
- pcli_agent-0.1.0/src/pcli/tui/themes.py +84 -0
- pcli_agent-0.1.0/src/pcli/tui/widgets/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/tui/widgets/chat_input.py +240 -0
- pcli_agent-0.1.0/src/pcli/tui/widgets/command_suggestions.py +33 -0
- pcli_agent-0.1.0/src/pcli/tui/widgets/message_view.py +328 -0
- pcli_agent-0.1.0/src/pcli/tui/widgets/paste_input.py +99 -0
- pcli_agent-0.1.0/src/pcli/tui/widgets/paste_marker.py +69 -0
- pcli_agent-0.1.0/src/pcli/tui/widgets/status_bar.py +133 -0
- pcli_agent-0.1.0/src/pcli/tui/widgets/status_pane.py +58 -0
- pcli_agent-0.1.0/src/pcli/util/__init__.py +0 -0
- pcli_agent-0.1.0/src/pcli/util/ids.py +15 -0
- pcli_agent-0.1.0/src/pcli/util/logging.py +18 -0
- pcli_agent-0.1.0/src/pcli/util/text.py +10 -0
- pcli_agent-0.1.0/tests/conftest.py +31 -0
- pcli_agent-0.1.0/tests/test_activity.py +131 -0
- pcli_agent-0.1.0/tests/test_agent_headless.py +535 -0
- pcli_agent-0.1.0/tests/test_agent_loop.py +1532 -0
- pcli_agent-0.1.0/tests/test_agent_runtime.py +270 -0
- pcli_agent-0.1.0/tests/test_agent_tool_register_tool.py +135 -0
- pcli_agent-0.1.0/tests/test_agent_tools.py +533 -0
- pcli_agent-0.1.0/tests/test_agent_tools_store.py +78 -0
- pcli_agent-0.1.0/tests/test_artifact_tool.py +393 -0
- pcli_agent-0.1.0/tests/test_artifact_truncation.py +322 -0
- pcli_agent-0.1.0/tests/test_artifacts.py +44 -0
- pcli_agent-0.1.0/tests/test_ask_question_modal.py +95 -0
- pcli_agent-0.1.0/tests/test_ask_tool.py +149 -0
- pcli_agent-0.1.0/tests/test_browser_session.py +307 -0
- pcli_agent-0.1.0/tests/test_browser_tool.py +246 -0
- pcli_agent-0.1.0/tests/test_builtin_tools.py +577 -0
- pcli_agent-0.1.0/tests/test_chat_input.py +479 -0
- pcli_agent-0.1.0/tests/test_chat_screen_ask_artifact.py +62 -0
- pcli_agent-0.1.0/tests/test_chat_screen_ask_question.py +123 -0
- pcli_agent-0.1.0/tests/test_chat_screen_auto_continue.py +205 -0
- pcli_agent-0.1.0/tests/test_chat_screen_autocomplete.py +100 -0
- pcli_agent-0.1.0/tests/test_chat_screen_budget.py +221 -0
- pcli_agent-0.1.0/tests/test_chat_screen_cancel.py +189 -0
- pcli_agent-0.1.0/tests/test_chat_screen_compaction.py +435 -0
- pcli_agent-0.1.0/tests/test_chat_screen_config_persist.py +338 -0
- pcli_agent-0.1.0/tests/test_chat_screen_context_detect.py +323 -0
- pcli_agent-0.1.0/tests/test_chat_screen_context_limit.py +207 -0
- pcli_agent-0.1.0/tests/test_chat_screen_decisions.py +171 -0
- pcli_agent-0.1.0/tests/test_chat_screen_error_handling.py +145 -0
- pcli_agent-0.1.0/tests/test_chat_screen_help.py +87 -0
- pcli_agent-0.1.0/tests/test_chat_screen_local_api.py +71 -0
- pcli_agent-0.1.0/tests/test_chat_screen_memory.py +421 -0
- pcli_agent-0.1.0/tests/test_chat_screen_paste.py +124 -0
- pcli_agent-0.1.0/tests/test_chat_screen_plan_mode.py +215 -0
- pcli_agent-0.1.0/tests/test_chat_screen_pruning.py +284 -0
- pcli_agent-0.1.0/tests/test_chat_screen_queueing.py +214 -0
- pcli_agent-0.1.0/tests/test_chat_screen_reasoning.py +230 -0
- pcli_agent-0.1.0/tests/test_chat_screen_reload.py +207 -0
- pcli_agent-0.1.0/tests/test_chat_screen_rename.py +88 -0
- pcli_agent-0.1.0/tests/test_chat_screen_session_pruning.py +77 -0
- pcli_agent-0.1.0/tests/test_chat_screen_subagent_activity_panel.py +229 -0
- pcli_agent-0.1.0/tests/test_chat_screen_theme.py +99 -0
- pcli_agent-0.1.0/tests/test_chat_screen_tool_call_count.py +145 -0
- pcli_agent-0.1.0/tests/test_chat_screen_toolbox_errors.py +66 -0
- pcli_agent-0.1.0/tests/test_chat_screen_turn_config.py +790 -0
- pcli_agent-0.1.0/tests/test_cli_config_persist.py +123 -0
- pcli_agent-0.1.0/tests/test_cli_resume.py +106 -0
- pcli_agent-0.1.0/tests/test_cli_run.py +380 -0
- pcli_agent-0.1.0/tests/test_cli_schedule.py +296 -0
- pcli_agent-0.1.0/tests/test_cli_sessions.py +72 -0
- pcli_agent-0.1.0/tests/test_cli_telegram.py +99 -0
- pcli_agent-0.1.0/tests/test_cli_toolbox_discover.py +60 -0
- pcli_agent-0.1.0/tests/test_cli_tools.py +70 -0
- pcli_agent-0.1.0/tests/test_command_suggestions.py +104 -0
- pcli_agent-0.1.0/tests/test_compaction.py +431 -0
- pcli_agent-0.1.0/tests/test_config_settings.py +211 -0
- pcli_agent-0.1.0/tests/test_confirm_modal.py +53 -0
- pcli_agent-0.1.0/tests/test_context_detect.py +275 -0
- pcli_agent-0.1.0/tests/test_context_pruning.py +241 -0
- pcli_agent-0.1.0/tests/test_context_tracking.py +522 -0
- pcli_agent-0.1.0/tests/test_cost_tracker.py +286 -0
- pcli_agent-0.1.0/tests/test_decision_tool.py +130 -0
- pcli_agent-0.1.0/tests/test_describe_tool.py +141 -0
- pcli_agent-0.1.0/tests/test_diff_tools.py +181 -0
- pcli_agent-0.1.0/tests/test_guardrails_config.py +52 -0
- pcli_agent-0.1.0/tests/test_interactive_shell.py +85 -0
- pcli_agent-0.1.0/tests/test_live_gateway_tool_calling.py +102 -0
- pcli_agent-0.1.0/tests/test_llm_client.py +571 -0
- pcli_agent-0.1.0/tests/test_memory_extraction.py +228 -0
- pcli_agent-0.1.0/tests/test_memory_models.py +91 -0
- pcli_agent-0.1.0/tests/test_memory_store.py +105 -0
- pcli_agent-0.1.0/tests/test_memory_tool.py +104 -0
- pcli_agent-0.1.0/tests/test_message_view.py +529 -0
- pcli_agent-0.1.0/tests/test_modal_layout.py +70 -0
- pcli_agent-0.1.0/tests/test_network_tools.py +146 -0
- pcli_agent-0.1.0/tests/test_paste_input.py +325 -0
- pcli_agent-0.1.0/tests/test_paste_marker.py +60 -0
- pcli_agent-0.1.0/tests/test_permissions.py +591 -0
- pcli_agent-0.1.0/tests/test_pip_tool.py +142 -0
- pcli_agent-0.1.0/tests/test_prompt.py +278 -0
- pcli_agent-0.1.0/tests/test_pydiscovery.py +239 -0
- pcli_agent-0.1.0/tests/test_sandbox_docker.py +72 -0
- pcli_agent-0.1.0/tests/test_sandbox_null.py +47 -0
- pcli_agent-0.1.0/tests/test_sandbox_selector.py +125 -0
- pcli_agent-0.1.0/tests/test_sandbox_subprocess.py +216 -0
- pcli_agent-0.1.0/tests/test_scheduler_daemon.py +546 -0
- pcli_agent-0.1.0/tests/test_scheduler_store.py +83 -0
- pcli_agent-0.1.0/tests/test_scheduler_triggers.py +162 -0
- pcli_agent-0.1.0/tests/test_session_audit.py +144 -0
- pcli_agent-0.1.0/tests/test_session_directory_check.py +31 -0
- pcli_agent-0.1.0/tests/test_session_roundtrip.py +178 -0
- pcli_agent-0.1.0/tests/test_session_store.py +97 -0
- pcli_agent-0.1.0/tests/test_sessions_screen.py +262 -0
- pcli_agent-0.1.0/tests/test_shell_background.py +254 -0
- pcli_agent-0.1.0/tests/test_shell_passthrough.py +61 -0
- pcli_agent-0.1.0/tests/test_status_bar.py +68 -0
- pcli_agent-0.1.0/tests/test_status_pane.py +122 -0
- pcli_agent-0.1.0/tests/test_streaming.py +76 -0
- pcli_agent-0.1.0/tests/test_subagent_tool.py +964 -0
- pcli_agent-0.1.0/tests/test_telegram_bot.py +714 -0
- pcli_agent-0.1.0/tests/test_telegram_daemon.py +1472 -0
- pcli_agent-0.1.0/tests/test_telegram_permissions.py +175 -0
- pcli_agent-0.1.0/tests/test_telegram_sender.py +130 -0
- pcli_agent-0.1.0/tests/test_todo_tool.py +252 -0
- pcli_agent-0.1.0/tests/test_tool_registry.py +98 -0
- pcli_agent-0.1.0/tests/test_toolbox_introspect.py +140 -0
- pcli_agent-0.1.0/tests/test_toolbox_manager.py +220 -0
- pcli_agent-0.1.0/tests/test_toolbox_plugin_kubectl.py +140 -0
- pcli_agent-0.1.0/tests/test_toolbox_self_authored.py +232 -0
- pcli_agent-0.1.0/tests/test_tui_app.py +63 -0
- pcli_agent-0.1.0/tests/test_tui_themes.py +31 -0
- pcli_agent-0.1.0/tests/test_web_tools.py +281 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sessionId":"f7c9a8bb-83bd-44c2-96a1-23ca4a0c00a5","pid":15356,"procStart":"134354052549494784","acquiredAt":1790961361998}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(git add *)",
|
|
5
|
+
"Bash(git commit -m ' *)",
|
|
6
|
+
"Bash(git push *)",
|
|
7
|
+
"Bash(xargs grep -ln \"base_url\\\\|api_key\\\\|gateway\")",
|
|
8
|
+
"Bash(python -m pytest tests/ -q)",
|
|
9
|
+
"Bash(.venv/Scripts/python -m pytest tests/ -q)",
|
|
10
|
+
"Bash(python -m pip show pytest pytest-asyncio respx)",
|
|
11
|
+
"Bash(python -m pip install -q -r requirements-dev.txt)",
|
|
12
|
+
"Bash(python -m pip install -q -e .)",
|
|
13
|
+
"Bash(python -m ruff check src/pcli tests)",
|
|
14
|
+
"Bash(python -m ruff check src/pcli)",
|
|
15
|
+
"Bash(python /tmp/_modal_smoke.py)",
|
|
16
|
+
"Bash(PYTHONIOENCODING=utf-8 python /tmp/_spinner_smoke.py)",
|
|
17
|
+
"Bash(python -m pytest tests/test_status_pane.py tests/test_subagent_tool.py -q)",
|
|
18
|
+
"Bash(sed -i 's/app.query_one\\(StatusPane\\)/app.screen.query_one\\(StatusPane\\)/' /tmp/_pane_integration.py)",
|
|
19
|
+
"Bash(python /tmp/_pane_integration.py)",
|
|
20
|
+
"Bash(PYTHONIOENCODING=utf-8 python /tmp/_pane_integration.py)",
|
|
21
|
+
"Bash(python -m pytest tests/test_config_settings.py tests/test_cli_config_persist.py tests/test_chat_screen_config_persist.py -q)",
|
|
22
|
+
"Bash(python -m pytest tests/test_sandbox_subprocess.py -v)",
|
|
23
|
+
"Bash(python -m pytest tests/test_cli_config_persist.py -q)",
|
|
24
|
+
"Bash(python -m pytest tests/test_cli_config_persist.py -v)",
|
|
25
|
+
"Bash(python -m pytest tests/test_sandbox_null.py -v)",
|
|
26
|
+
"Bash(python -m pytest tests/test_permissions.py -v)",
|
|
27
|
+
"Bash(python -m pytest tests/test_agent_loop.py -v)",
|
|
28
|
+
"Bash(python -m pytest tests/test_config_settings.py -v)",
|
|
29
|
+
"Bash(python -c ' *)",
|
|
30
|
+
"Bash(python -m pytest tests/ -q -p no:randomly)",
|
|
31
|
+
"Bash(python -m pytest tests/test_chat_screen_local_api.py -v)"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
runs-on: ${{ matrix.os }}
|
|
14
|
+
strategy:
|
|
15
|
+
fail-fast: false
|
|
16
|
+
matrix:
|
|
17
|
+
os: [ubuntu-latest, windows-latest]
|
|
18
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
24
|
+
uses: actions/setup-python@v5
|
|
25
|
+
with:
|
|
26
|
+
python-version: ${{ matrix.python-version }}
|
|
27
|
+
cache: "pip"
|
|
28
|
+
cache-dependency-path: pyproject.toml
|
|
29
|
+
|
|
30
|
+
# browser (Playwright)/docker extras are deliberately left out here:
|
|
31
|
+
# browser's own binary is a separate ~300MB download pcli doesn't need
|
|
32
|
+
# for its own tests (they importorskip when the real browser isn't
|
|
33
|
+
# available), and docker's extra is a no-op anyway - the sandbox tests
|
|
34
|
+
# that need either just skip cleanly without them. schedule/telegram
|
|
35
|
+
# are pure-Python and cheap, so there's no reason to skip those.
|
|
36
|
+
- name: Install pcli with dev dependencies
|
|
37
|
+
run: pip install -e ".[dev,schedule,telegram,win]"
|
|
38
|
+
|
|
39
|
+
- name: Lint with ruff
|
|
40
|
+
run: ruff check .
|
|
41
|
+
|
|
42
|
+
# Informational only for now: mypy currently reports a real,
|
|
43
|
+
# pre-existing backlog of ~24 errors spread across several files that
|
|
44
|
+
# predate this workflow and aren't part of any one change - fixing them
|
|
45
|
+
# is a separate cleanup, not something to gate every PR on until it's
|
|
46
|
+
# done. Flip this to a hard failure once that backlog is cleared.
|
|
47
|
+
- name: Type-check with mypy (non-blocking)
|
|
48
|
+
continue-on-error: true
|
|
49
|
+
run: mypy src/pcli
|
|
50
|
+
|
|
51
|
+
- name: Run tests
|
|
52
|
+
run: pytest tests/ -q
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Python bytecode/cache
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Virtual environments
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
env/
|
|
10
|
+
|
|
11
|
+
# Packaging / build artifacts
|
|
12
|
+
build/
|
|
13
|
+
dist/
|
|
14
|
+
*.egg-info/
|
|
15
|
+
.eggs/
|
|
16
|
+
*.egg
|
|
17
|
+
|
|
18
|
+
# Testing & coverage
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.coverage
|
|
21
|
+
.coverage.*
|
|
22
|
+
coverage.xml
|
|
23
|
+
htmlcov/
|
|
24
|
+
|
|
25
|
+
# Type-checking / linting caches
|
|
26
|
+
.mypy_cache/
|
|
27
|
+
.ruff_cache/
|
|
28
|
+
.pytype/
|
|
29
|
+
|
|
30
|
+
# Environment / secrets
|
|
31
|
+
.env
|
|
32
|
+
.env.*
|
|
33
|
+
!.env.example
|
|
34
|
+
|
|
35
|
+
# Editors / IDEs
|
|
36
|
+
.vscode/
|
|
37
|
+
.idea/
|
|
38
|
+
|
|
39
|
+
# OS-generated files
|
|
40
|
+
.DS_Store
|
|
41
|
+
Thumbs.db
|
|
42
|
+
desktop.ini
|
|
43
|
+
|
|
44
|
+
# Logs
|
|
45
|
+
*.log
|
|
46
|
+
|
|
47
|
+
# Local model weights and anything dropped alongside them (chat templates,
|
|
48
|
+
# readmes, ...) - this is scratch space for locally-tested models, not part
|
|
49
|
+
# of the project; large binaries also never belong in git (GitHub hard-rejects
|
|
50
|
+
# anything over 100MB anyway).
|
|
51
|
+
models/
|
|
52
|
+
*.gguf
|
|
53
|
+
|
|
54
|
+
pcli-session.json
|
|
55
|
+
pcli-session.txt
|
|
56
|
+
|
|
57
|
+
# Informal personal project-brief notes - not part of the public project.
|
|
58
|
+
requirements.md
|
pcli_agent-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vikas NV
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pcli-agent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An opencode-style AI coding agent CLI/TUI for Python, with a Python discovery tool and an OS/software toolbox.
|
|
5
|
+
Project-URL: Homepage, https://github.com/vikasna/pcli
|
|
6
|
+
Project-URL: Repository, https://github.com/vikasna/pcli
|
|
7
|
+
Project-URL: Issues, https://github.com/vikasna/pcli/issues
|
|
8
|
+
Author-email: Vikas NV <vikas.nv@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agent,ai,cli,coding-assistant,llm,tui
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: httpx>=0.27
|
|
24
|
+
Requires-Dist: jsonschema>=4.21
|
|
25
|
+
Requires-Dist: platformdirs>=4.2
|
|
26
|
+
Requires-Dist: psutil>=5.9
|
|
27
|
+
Requires-Dist: pydantic-settings>=2.2
|
|
28
|
+
Requires-Dist: pydantic>=2.7
|
|
29
|
+
Requires-Dist: pyperclip>=1.8
|
|
30
|
+
Requires-Dist: tenacity>=8.2
|
|
31
|
+
Requires-Dist: textual>=0.58
|
|
32
|
+
Requires-Dist: typer>=0.12
|
|
33
|
+
Provides-Extra: browser
|
|
34
|
+
Requires-Dist: playwright>=1.40; extra == 'browser'
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
40
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
41
|
+
Requires-Dist: types-croniter>=2; extra == 'dev'
|
|
42
|
+
Requires-Dist: types-jsonschema>=4.21; extra == 'dev'
|
|
43
|
+
Requires-Dist: types-pyperclip>=1.8; extra == 'dev'
|
|
44
|
+
Provides-Extra: docker
|
|
45
|
+
Provides-Extra: schedule
|
|
46
|
+
Requires-Dist: croniter>=2; extra == 'schedule'
|
|
47
|
+
Provides-Extra: telegram
|
|
48
|
+
Requires-Dist: python-telegram-bot>=21; extra == 'telegram'
|
|
49
|
+
Provides-Extra: win
|
|
50
|
+
Requires-Dist: pywin32>=306; (sys_platform == 'win32') and extra == 'win'
|
|
51
|
+
Description-Content-Type: text/markdown
|
|
52
|
+
|
|
53
|
+
<picture>
|
|
54
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/logo-dark.svg">
|
|
55
|
+
<img src="assets/logo.svg" alt="pcli logo" width="72" height="72">
|
|
56
|
+
</picture>
|
|
57
|
+
|
|
58
|
+
# pcli
|
|
59
|
+
|
|
60
|
+
**An opencode-style AI coding agent, as a Python TUI, that talks to any OpenAI-compatible gateway.**
|
|
61
|
+
|
|
62
|
+
pcli is a terminal coding agent: a tool-calling agent loop with real filesystem/shell/browser access, running against your working directory, rendered in a Textual TUI. It connects to any OpenAI-compatible chat-completions endpoint — a hosted API, an aggregator, or a model running entirely on your own machine — rather than being locked to one vendor. Beyond the usual session management, cost tracking, and permission/guardrail system you'd expect from a coding agent, it adds a few things built specifically because no other agent CLI does them well: a hard, enforced per-session cost budget; a scheduler that can react to file changes and git commits, not just cron time; and an opt-in tamper-evident audit log for unattended runs in regulated environments.
|
|
63
|
+
|
|
64
|
+
See [`docs/README.md`](docs/README.md) for the full architecture reference and every per-topic doc.
|
|
65
|
+
|
|
66
|
+
## What sets pcli apart
|
|
67
|
+
|
|
68
|
+
### Hard session cost-budget enforcement
|
|
69
|
+
|
|
70
|
+
Most agent CLIs only *report* what a session cost after the fact. pcli can actually stop itself once a configured budget is hit — it doesn't just log an overrun, it refuses to keep spending.
|
|
71
|
+
|
|
72
|
+
The check (`cost_budget_reason`) runs at the start of **every internal LLM round-trip** inside a turn, not once per user-visible turn — so a single tool-call-heavy turn can't blow through the cap across many internal iterations before control ever returns to you. It's wired into all four places that drive an agent loop: the main TUI session, headless/scheduled runs, subagents, and memory extraction, since subagent and compaction spend fold into the same session total.
|
|
73
|
+
|
|
74
|
+
- `/budget [amount|off]` — view or set the cap live in the TUI
|
|
75
|
+
- `PCLI_MAX_SESSION_COST_USD` / `max_session_cost_usd` in `config.toml` — the persisted cap (unset by default: no limit)
|
|
76
|
+
- `pcli run --max-cost <amount>` / `pcli schedule add --max-cost <amount>` — a per-invocation override that never touches the persisted setting
|
|
77
|
+
|
|
78
|
+
### Event-triggered scheduling
|
|
79
|
+
|
|
80
|
+
`pcli schedule` is pcli's own recurring-task daemon, and it isn't limited to cron timing. Besides `--cron "*/15 * * * *"`, a job can be told to fire on an *event*:
|
|
81
|
+
|
|
82
|
+
- `--on-file-change <path>` — fires when a watched file or directory (hashed recursively by path/mtime/size, no contents read) changes
|
|
83
|
+
- `--on-git-commit [--git-repo PATH] [--git-branch NAME]` — fires when a new commit lands on a watched branch
|
|
84
|
+
|
|
85
|
+
Both still run through the same poll loop as a cron job (`pcli schedule run --poll-interval`, default 30s) — there's no OS-level file-watching integration or git hook, just a cheap check on every tick, so no new dependency was needed. The detail worth knowing before you rely on it: **self-trigger-loop prevention**. After an event job fires, the daemon doesn't reuse the signature/SHA that triggered the run as its new baseline — it re-checks *after* the job's own run finishes and stores that as the baseline instead. So a task that edits its own watched file, or commits to its own watched branch (a very plausible "auto-commit the output" pattern), does not see its own output as one more change and fire again in a loop.
|
|
86
|
+
|
|
87
|
+
### Tamper-evident, hash-chained audit log
|
|
88
|
+
|
|
89
|
+
Opt-in (`audit_mode_enabled`, off by default — real per-tool-call overhead most users don't need). When it's on, pcli keeps a second, independent record of what a run actually did: every tool call and every permission/guardrail decision is appended as an `AuditEntry` to `Session.audit_log`, each entry's hash computed over its own fields plus the previous entry's hash — the same tamper-evidence primitive a git commit chain uses. Edit, reorder, or delete an old entry and the chain breaks from that point forward; the break can't be silently repaired without re-deriving every hash after it.
|
|
90
|
+
|
|
91
|
+
`pcli sessions verify <id>` recomputes the chain and tells you whether it's intact or exactly where it broke:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
$ pcli sessions verify sess_a1b2c3d4
|
|
95
|
+
12 audit entries, chain valid.
|
|
96
|
+
|
|
97
|
+
$ pcli sessions verify sess_tampered
|
|
98
|
+
Chain broken at entry 3: entry_hash does not match the recomputed hash - this entry's content was modified after it was recorded.
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
This is aimed at regulated-industry use — finance, healthcare, government — where someone needs to prove, after the fact, what an unattended agent did, and that the record wasn't quietly edited afterward. Turn it on for a single invocation without touching the persisted setting via `pcli run --audit` or `pcli schedule add --audit`.
|
|
102
|
+
|
|
103
|
+
## Also notable
|
|
104
|
+
|
|
105
|
+
- **`pcli tools list`** — a static CLI listing of all 43 built-in tools, each with an audited read-only-vs-mutating classification, `needs_permission`, `plan_mode_safe`, and a one-line description. No gateway, session, or config needed to run it.
|
|
106
|
+
- **Toolbox plugins** — `pcli toolbox discover <name>` turns an installed OS/software CLI (kubectl, Sun Grid Engine, Kafka, Apache httpd, or anything with a `--help`) into LLM-callable tools automatically — curated plugins for the common ones, LLM-synthesized tool schemas (cached, validated) for anything else.
|
|
107
|
+
- **`--local-api` mode** — uncaps tool-iteration and rate-limit guardrails and forces cost to `$0` for a given local gateway, paired to that specific gateway URL rather than a global switch. Local models get first-class treatment, not an afterthought.
|
|
108
|
+
- **Global cross-session memory** — a small, durable profile of what pcli has learned about you (nature of work, preferences, recurring task patterns), injected into every new session's system prompt, extended automatically on compaction.
|
|
109
|
+
- **Real browser automation** — seven `browser_*` tools drive an actual Chromium via Playwright (click, type, wait, read, screenshot), watchable live and headed in the TUI, or headless under `pcli run`/`pcli schedule`.
|
|
110
|
+
- **Telegram bot front end** — `pcli telegram` runs pcli as a two-way bot with inline-button permission approval, so you can approve or deny a tool call from your phone.
|
|
111
|
+
|
|
112
|
+
## Install
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
pip install -e ".[dev,docker,win,browser,telegram,schedule]"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Pick the extras you actually need — none of them are required for the core TUI/CLI to run:
|
|
119
|
+
|
|
120
|
+
| Extra | What it's for |
|
|
121
|
+
|---|---|
|
|
122
|
+
| `dev` | pytest, ruff, mypy — only needed for working on pcli itself. |
|
|
123
|
+
| `docker` | Intentionally empty — the Docker sandbox backend shells out to the `docker` CLI directly, no Python SDK needed. Just makes `pip install -e ".[docker]"` valid; requires `docker` on `PATH` at runtime. |
|
|
124
|
+
| `win` | `pywin32`, for Windows-specific functionality. |
|
|
125
|
+
| `browser` | `playwright`, for the `browser_*` tools (see below — needs a one-time extra step). |
|
|
126
|
+
| `telegram` | `python-telegram-bot`, for `pcli telegram`. Self-sufficient — no separate download. |
|
|
127
|
+
| `schedule` | `croniter`, for `pcli schedule`'s cron expression parsing. Self-sufficient — no separate download. |
|
|
128
|
+
|
|
129
|
+
If you installed the `browser` extra, Chromium itself is a separate ~300MB download, fetched once:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
playwright install chromium
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Quickstart
|
|
136
|
+
|
|
137
|
+
The fastest way to see pcli working needs no API key or signup at all: point it at a local model server.
|
|
138
|
+
|
|
139
|
+
With [LM Studio](https://lmstudio.ai/) (load a model, start its local server from the Developer tab — it defaults to `http://localhost:1234/v1`):
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
pcli --gateway-url http://localhost:1234/v1 --model <model-id-from-lm-studio>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
With [Ollama](https://ollama.com/) (serves an OpenAI-compatible endpoint under `/v1`):
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
pcli --gateway-url http://localhost:11434/v1 --model llama3
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Either way, the gateway URL and model are saved to `config.toml` on first use, so a bare `pcli` afterward reconnects without repeating the flags. Not sure of the exact model id your server expects? Launch pcli and run `/models` — it lists whatever the server reports and lets you pick one interactively.
|
|
152
|
+
|
|
153
|
+
Add `--local-api` to lift the default tool-iteration/rate-limit guardrails (sized for paid, rate-limited gateways) and force cost tracking to `$0` for that gateway:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
pcli --gateway-url http://localhost:1234/v1 --model <model-id> --local-api
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Connecting to a provider
|
|
160
|
+
|
|
161
|
+
**pcli's gateway client speaks the OpenAI chat-completions wire format only** — `POST {base_url}/chat/completions`, `GET {base_url}/models`. There is no native Anthropic Messages API client and no native Google Gemini API client in this codebase. This genuinely covers a lot of ground, but it's worth being precise about what it does and doesn't mean:
|
|
162
|
+
|
|
163
|
+
- **Direct, native support**: OpenAI itself, plus any server or provider that exposes an OpenAI-compatible `/v1/chat/completions` endpoint — which includes most of the ecosystem. Local servers: LM Studio, Ollama, vLLM, text-generation-webui, llama.cpp server. Hosted APIs: Groq, Mistral, DeepSeek, Together AI, Fireworks AI, and OpenRouter.
|
|
164
|
+
- **Not natively supported**: Anthropic's own Messages API and Google's own Gemini API use different request/response shapes than OpenAI's chat-completions format, so pcli cannot talk to `api.anthropic.com` or Google's native Gemini endpoint directly. The honest way to reach a Claude or Gemini model *through* pcli is via an OpenAI-compatible proxy or aggregator — **OpenRouter** is the simplest one, and it fronts both model families behind one OpenAI-compatible endpoint.
|
|
165
|
+
- **Azure OpenAI is not currently supported.** Its deployment-based URL carries a mandatory `api-version` query parameter, and pcli's gateway client always appends `/chat/completions` as a literal path segment onto `gateway_base_url` — the two don't compose into a URL Azure will accept. See the "Azure OpenAI" section below for details.
|
|
166
|
+
|
|
167
|
+
### OpenAI
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
# PowerShell
|
|
171
|
+
$env:PCLI_GATEWAY_URL = "https://api.openai.com/v1"
|
|
172
|
+
$env:PCLI_GATEWAY_API_KEY = "sk-..."
|
|
173
|
+
pcli --model gpt-4o
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### LM Studio / Ollama (local)
|
|
177
|
+
|
|
178
|
+
See [Quickstart](#quickstart) above — no API key needed.
|
|
179
|
+
|
|
180
|
+
### OpenRouter (the path to Claude, Gemini, and others)
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
$env:PCLI_GATEWAY_URL = "https://openrouter.ai/api/v1"
|
|
184
|
+
$env:PCLI_GATEWAY_API_KEY = "sk-or-..."
|
|
185
|
+
pcli --model anthropic/claude-sonnet-4.5
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
OpenRouter exposes one OpenAI-compatible endpoint in front of dozens of providers' models, including Anthropic's and Google's — swap `--model` for `google/gemini-2.5-pro` or any other model it hosts, and the same gateway URL/key keep working.
|
|
189
|
+
|
|
190
|
+
### Also just works, same pattern
|
|
191
|
+
|
|
192
|
+
Groq, Mistral, DeepSeek, Together AI, and Fireworks AI all expose an OpenAI-compatible `/chat/completions` endpoint — point `gateway_base_url`/`PCLI_GATEWAY_URL` at theirs, set the matching API key, and pick one of their model ids. No special-casing needed.
|
|
193
|
+
|
|
194
|
+
### Azure OpenAI
|
|
195
|
+
|
|
196
|
+
**Not supported.** `GatewayClient` builds an `httpx.AsyncClient` from `gateway_base_url` and then issues every request against a relative path — `POST /chat/completions`, `GET /models` — which httpx appends as a literal path segment onto whatever `gateway_base_url` is. Azure's deployment URL already ends in a mandatory `api-version` query string (e.g. `.../deployments/<deployment>?api-version=2024-02-01`), and httpx's URL-joining doesn't relocate that query string when a path is appended — it just concatenates, producing `.../deployments/<deployment>?api-version=2024-02-01/chat/completions`. That's not a valid request: `/chat/completions` ends up folded into the `api-version` value instead of becoming a real path segment, so there's no URL shape you can put in `gateway_base_url` that works. Reaching Azure-hosted models through pcli would need dedicated client-side support (a special case for the Azure URL shape); until then, use OpenAI directly or an aggregator such as OpenRouter.
|
|
197
|
+
|
|
198
|
+
## Configuration
|
|
199
|
+
|
|
200
|
+
Settings come from environment variables, CLI flags, or `config.toml`. Precedence, highest to lowest: **CLI flags > environment variables > `config.toml` > built-in defaults**. `config.toml` lives at `<config_dir>/config.toml` (e.g. `%APPDATA%\pcli\config.toml` on Windows, `~/.config/pcli/config.toml` on Linux) — created on first access if missing.
|
|
201
|
+
|
|
202
|
+
| Setting | Env var | CLI flag | config.toml key | Default |
|
|
203
|
+
|---|---|---|---|---|
|
|
204
|
+
| Gateway base URL | `PCLI_GATEWAY_URL` | `--gateway-url` | `gateway_base_url` | `""` |
|
|
205
|
+
| Gateway API key | `PCLI_GATEWAY_API_KEY` | `--api-key` | `gateway_api_key` | `""` |
|
|
206
|
+
| Default model | `PCLI_MODEL` | `--model` | `default_model` | `""` |
|
|
207
|
+
| Artifact threshold (chars) | `PCLI_ARTIFACT_THRESHOLD_CHARS` | `--artifact-threshold` | `artifact_threshold_chars` | `4000` |
|
|
208
|
+
| Local-API gateways | `PCLI_LOCAL_API_GATEWAYS` | `--local-api` | `local_api_gateways` | `[]` |
|
|
209
|
+
| Hard session cost cap (USD) | `PCLI_MAX_SESSION_COST_USD` | *(none — see `pcli run --max-cost`)* | `max_session_cost_usd` | unset (no cap) |
|
|
210
|
+
| Tamper-evident audit log | `PCLI_AUDIT_MODE_ENABLED` | *(none — see `pcli run --audit`)* | `audit_mode_enabled` | `false` |
|
|
211
|
+
| Sandbox backend | `PCLI_SANDBOX_BACKEND` | *(none)* | `sandbox_backend` | `"auto"` (`auto`\|`docker`\|`subprocess`\|`none`) |
|
|
212
|
+
| Request timeout (s) | `PCLI_REQUEST_TIMEOUT_S` | *(none)* | `request_timeout_s` | `120.0` |
|
|
213
|
+
| Max tool-call iterations/turn | `PCLI_MAX_TOOL_ITERATIONS` | *(none)* | `max_tool_iterations` | `25` |
|
|
214
|
+
|
|
215
|
+
The API key is deliberately never auto-persisted to `config.toml` — pass `--api-key` or set `PCLI_GATEWAY_API_KEY` each time if your gateway needs one. The gateway URL, model, and artifact threshold *are* auto-persisted when passed as a flag, so a bare `pcli` afterward picks them up. See [`docs/configuration.md`](docs/configuration.md) for the complete settings table — this covers only the ones you're most likely to touch.
|
|
216
|
+
|
|
217
|
+
Example `config.toml`:
|
|
218
|
+
|
|
219
|
+
```toml
|
|
220
|
+
gateway_base_url = "http://localhost:1234/v1"
|
|
221
|
+
default_model = "qwen2.5-coder-32b-instruct"
|
|
222
|
+
max_session_cost_usd = 5.0
|
|
223
|
+
sandbox_backend = "subprocess"
|
|
224
|
+
audit_mode_enabled = false
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## FAQ
|
|
228
|
+
|
|
229
|
+
**Does my code or data ever leave my machine?**
|
|
230
|
+
Only to whatever gateway you've configured. Point `gateway_base_url` at a local server (LM Studio, Ollama, vLLM, ...) and nothing leaves your machine at all — pcli's own cost/pricing tables and sandbox execution are entirely local regardless of gateway. Point it at a hosted API and your conversation (including file contents the model reads) goes wherever that API sends it, same as any coding agent.
|
|
231
|
+
|
|
232
|
+
**How is this different from other AI coding agent CLIs?**
|
|
233
|
+
Three things specifically: a cost budget that's actually *enforced* mid-turn rather than just reported after the fact, a scheduler that can react to file changes or git commits instead of cron time alone, and an opt-in hash-chained audit log for proving what an unattended run did. See [What sets pcli apart](#what-sets-pcli-apart) above. It's also provider-agnostic by construction — any OpenAI-compatible gateway, local or hosted — rather than built around one vendor's API.
|
|
234
|
+
|
|
235
|
+
**Can I run this unattended, in CI, or on a schedule?**
|
|
236
|
+
Yes — `pcli run --task "..."` is a one-shot, non-interactive invocation with a proper exit code (0 on success, 1 on a hit iteration/cost cap or a usage error), suitable for cron/Task Scheduler. `pcli schedule` is pcli's own recurring-task daemon on top of the same machinery, with cron or event triggers. Both support `--max-cost` and `--audit` as safety nets for an unattended run, since there's no one present to approve a permission prompt (a tool call with no standing "Always Allow" grant is simply denied, not paused to ask).
|
|
237
|
+
|
|
238
|
+
**What stops the model from running something destructive?**
|
|
239
|
+
Two independent layers. Guardrails (`guardrails.toml`) are hard, non-negotiable denials — a shell command denylist, filesystem allow/deny roots, a Python module denylist — checked before any permission prompt and never bypassable by asking. On top of that, the permission system gates every tool call that isn't explicitly read-only, either against a remembered grant or an interactive prompt (fails closed with no UI, e.g. in a headless run). Underneath both, a sandbox backend (Docker, a restricted subprocess, or — only if you explicitly opt out — none) bounds what an allowed command can actually touch. See [`docs/sandbox-and-permissions.md`](docs/sandbox-and-permissions.md).
|
|
240
|
+
|
|
241
|
+
**Does it work fully offline?**
|
|
242
|
+
Yes, with a local gateway (LM Studio, Ollama, vLLM, ...) — no network call leaves your machine except whatever the model server itself does. `web_search`/`web_fetch`/`browser_*` are the only tools that reach the internet, and the model only calls them if it decides to.
|
|
243
|
+
|
|
244
|
+
**Does it work on Windows?**
|
|
245
|
+
Yes. pcli's system prompt tells the model up front that it's running on Windows and that `run_shell` executes via `cmd.exe`, not bash — no heredoc syntax, no `$VAR` expansion, chain with `&&` not `;` — rather than letting the model default to Unix assumptions and discover it's wrong by trial and error. The `win` extra (`pywin32`) covers Windows-specific functionality beyond that.
|
|
246
|
+
|
|
247
|
+
**What does it cost to run?**
|
|
248
|
+
Entirely up to the gateway and model you point it at — pcli has no pricing of its own, it tracks whatever a model actually costs against a best-effort pricing table you can edit. Point it at a local model with `--local-api` and it's `$0`, enforced (cost is literally zeroed, not just expected to be low, since a locally-served model's name can coincidentally match a paid builtin pricing pattern otherwise).
|
|
249
|
+
|
|
250
|
+
**Is this an editor plugin? Does it replace my IDE?**
|
|
251
|
+
No. pcli is a standalone terminal TUI you run alongside your editor, not a plugin inside one — it complements an IDE rather than replacing it.
|
|
252
|
+
|
|
253
|
+
## Learn more
|
|
254
|
+
|
|
255
|
+
[`docs/README.md`](docs/README.md) is the full reference: architecture, every setting, every built-in tool, the TUI guide, sandboxing/permissions, sessions/cost, memory, headless/scheduled runs, browser automation, the Telegram bot, and the toolbox plugin system.
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
|
|
259
|
+
MIT — see [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/logo-dark.svg">
|
|
3
|
+
<img src="assets/logo.svg" alt="pcli logo" width="72" height="72">
|
|
4
|
+
</picture>
|
|
5
|
+
|
|
6
|
+
# pcli
|
|
7
|
+
|
|
8
|
+
**An opencode-style AI coding agent, as a Python TUI, that talks to any OpenAI-compatible gateway.**
|
|
9
|
+
|
|
10
|
+
pcli is a terminal coding agent: a tool-calling agent loop with real filesystem/shell/browser access, running against your working directory, rendered in a Textual TUI. It connects to any OpenAI-compatible chat-completions endpoint — a hosted API, an aggregator, or a model running entirely on your own machine — rather than being locked to one vendor. Beyond the usual session management, cost tracking, and permission/guardrail system you'd expect from a coding agent, it adds a few things built specifically because no other agent CLI does them well: a hard, enforced per-session cost budget; a scheduler that can react to file changes and git commits, not just cron time; and an opt-in tamper-evident audit log for unattended runs in regulated environments.
|
|
11
|
+
|
|
12
|
+
See [`docs/README.md`](docs/README.md) for the full architecture reference and every per-topic doc.
|
|
13
|
+
|
|
14
|
+
## What sets pcli apart
|
|
15
|
+
|
|
16
|
+
### Hard session cost-budget enforcement
|
|
17
|
+
|
|
18
|
+
Most agent CLIs only *report* what a session cost after the fact. pcli can actually stop itself once a configured budget is hit — it doesn't just log an overrun, it refuses to keep spending.
|
|
19
|
+
|
|
20
|
+
The check (`cost_budget_reason`) runs at the start of **every internal LLM round-trip** inside a turn, not once per user-visible turn — so a single tool-call-heavy turn can't blow through the cap across many internal iterations before control ever returns to you. It's wired into all four places that drive an agent loop: the main TUI session, headless/scheduled runs, subagents, and memory extraction, since subagent and compaction spend fold into the same session total.
|
|
21
|
+
|
|
22
|
+
- `/budget [amount|off]` — view or set the cap live in the TUI
|
|
23
|
+
- `PCLI_MAX_SESSION_COST_USD` / `max_session_cost_usd` in `config.toml` — the persisted cap (unset by default: no limit)
|
|
24
|
+
- `pcli run --max-cost <amount>` / `pcli schedule add --max-cost <amount>` — a per-invocation override that never touches the persisted setting
|
|
25
|
+
|
|
26
|
+
### Event-triggered scheduling
|
|
27
|
+
|
|
28
|
+
`pcli schedule` is pcli's own recurring-task daemon, and it isn't limited to cron timing. Besides `--cron "*/15 * * * *"`, a job can be told to fire on an *event*:
|
|
29
|
+
|
|
30
|
+
- `--on-file-change <path>` — fires when a watched file or directory (hashed recursively by path/mtime/size, no contents read) changes
|
|
31
|
+
- `--on-git-commit [--git-repo PATH] [--git-branch NAME]` — fires when a new commit lands on a watched branch
|
|
32
|
+
|
|
33
|
+
Both still run through the same poll loop as a cron job (`pcli schedule run --poll-interval`, default 30s) — there's no OS-level file-watching integration or git hook, just a cheap check on every tick, so no new dependency was needed. The detail worth knowing before you rely on it: **self-trigger-loop prevention**. After an event job fires, the daemon doesn't reuse the signature/SHA that triggered the run as its new baseline — it re-checks *after* the job's own run finishes and stores that as the baseline instead. So a task that edits its own watched file, or commits to its own watched branch (a very plausible "auto-commit the output" pattern), does not see its own output as one more change and fire again in a loop.
|
|
34
|
+
|
|
35
|
+
### Tamper-evident, hash-chained audit log
|
|
36
|
+
|
|
37
|
+
Opt-in (`audit_mode_enabled`, off by default — real per-tool-call overhead most users don't need). When it's on, pcli keeps a second, independent record of what a run actually did: every tool call and every permission/guardrail decision is appended as an `AuditEntry` to `Session.audit_log`, each entry's hash computed over its own fields plus the previous entry's hash — the same tamper-evidence primitive a git commit chain uses. Edit, reorder, or delete an old entry and the chain breaks from that point forward; the break can't be silently repaired without re-deriving every hash after it.
|
|
38
|
+
|
|
39
|
+
`pcli sessions verify <id>` recomputes the chain and tells you whether it's intact or exactly where it broke:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
$ pcli sessions verify sess_a1b2c3d4
|
|
43
|
+
12 audit entries, chain valid.
|
|
44
|
+
|
|
45
|
+
$ pcli sessions verify sess_tampered
|
|
46
|
+
Chain broken at entry 3: entry_hash does not match the recomputed hash - this entry's content was modified after it was recorded.
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This is aimed at regulated-industry use — finance, healthcare, government — where someone needs to prove, after the fact, what an unattended agent did, and that the record wasn't quietly edited afterward. Turn it on for a single invocation without touching the persisted setting via `pcli run --audit` or `pcli schedule add --audit`.
|
|
50
|
+
|
|
51
|
+
## Also notable
|
|
52
|
+
|
|
53
|
+
- **`pcli tools list`** — a static CLI listing of all 43 built-in tools, each with an audited read-only-vs-mutating classification, `needs_permission`, `plan_mode_safe`, and a one-line description. No gateway, session, or config needed to run it.
|
|
54
|
+
- **Toolbox plugins** — `pcli toolbox discover <name>` turns an installed OS/software CLI (kubectl, Sun Grid Engine, Kafka, Apache httpd, or anything with a `--help`) into LLM-callable tools automatically — curated plugins for the common ones, LLM-synthesized tool schemas (cached, validated) for anything else.
|
|
55
|
+
- **`--local-api` mode** — uncaps tool-iteration and rate-limit guardrails and forces cost to `$0` for a given local gateway, paired to that specific gateway URL rather than a global switch. Local models get first-class treatment, not an afterthought.
|
|
56
|
+
- **Global cross-session memory** — a small, durable profile of what pcli has learned about you (nature of work, preferences, recurring task patterns), injected into every new session's system prompt, extended automatically on compaction.
|
|
57
|
+
- **Real browser automation** — seven `browser_*` tools drive an actual Chromium via Playwright (click, type, wait, read, screenshot), watchable live and headed in the TUI, or headless under `pcli run`/`pcli schedule`.
|
|
58
|
+
- **Telegram bot front end** — `pcli telegram` runs pcli as a two-way bot with inline-button permission approval, so you can approve or deny a tool call from your phone.
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
pip install -e ".[dev,docker,win,browser,telegram,schedule]"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Pick the extras you actually need — none of them are required for the core TUI/CLI to run:
|
|
67
|
+
|
|
68
|
+
| Extra | What it's for |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `dev` | pytest, ruff, mypy — only needed for working on pcli itself. |
|
|
71
|
+
| `docker` | Intentionally empty — the Docker sandbox backend shells out to the `docker` CLI directly, no Python SDK needed. Just makes `pip install -e ".[docker]"` valid; requires `docker` on `PATH` at runtime. |
|
|
72
|
+
| `win` | `pywin32`, for Windows-specific functionality. |
|
|
73
|
+
| `browser` | `playwright`, for the `browser_*` tools (see below — needs a one-time extra step). |
|
|
74
|
+
| `telegram` | `python-telegram-bot`, for `pcli telegram`. Self-sufficient — no separate download. |
|
|
75
|
+
| `schedule` | `croniter`, for `pcli schedule`'s cron expression parsing. Self-sufficient — no separate download. |
|
|
76
|
+
|
|
77
|
+
If you installed the `browser` extra, Chromium itself is a separate ~300MB download, fetched once:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
playwright install chromium
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Quickstart
|
|
84
|
+
|
|
85
|
+
The fastest way to see pcli working needs no API key or signup at all: point it at a local model server.
|
|
86
|
+
|
|
87
|
+
With [LM Studio](https://lmstudio.ai/) (load a model, start its local server from the Developer tab — it defaults to `http://localhost:1234/v1`):
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
pcli --gateway-url http://localhost:1234/v1 --model <model-id-from-lm-studio>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
With [Ollama](https://ollama.com/) (serves an OpenAI-compatible endpoint under `/v1`):
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
pcli --gateway-url http://localhost:11434/v1 --model llama3
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Either way, the gateway URL and model are saved to `config.toml` on first use, so a bare `pcli` afterward reconnects without repeating the flags. Not sure of the exact model id your server expects? Launch pcli and run `/models` — it lists whatever the server reports and lets you pick one interactively.
|
|
100
|
+
|
|
101
|
+
Add `--local-api` to lift the default tool-iteration/rate-limit guardrails (sized for paid, rate-limited gateways) and force cost tracking to `$0` for that gateway:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
pcli --gateway-url http://localhost:1234/v1 --model <model-id> --local-api
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Connecting to a provider
|
|
108
|
+
|
|
109
|
+
**pcli's gateway client speaks the OpenAI chat-completions wire format only** — `POST {base_url}/chat/completions`, `GET {base_url}/models`. There is no native Anthropic Messages API client and no native Google Gemini API client in this codebase. This genuinely covers a lot of ground, but it's worth being precise about what it does and doesn't mean:
|
|
110
|
+
|
|
111
|
+
- **Direct, native support**: OpenAI itself, plus any server or provider that exposes an OpenAI-compatible `/v1/chat/completions` endpoint — which includes most of the ecosystem. Local servers: LM Studio, Ollama, vLLM, text-generation-webui, llama.cpp server. Hosted APIs: Groq, Mistral, DeepSeek, Together AI, Fireworks AI, and OpenRouter.
|
|
112
|
+
- **Not natively supported**: Anthropic's own Messages API and Google's own Gemini API use different request/response shapes than OpenAI's chat-completions format, so pcli cannot talk to `api.anthropic.com` or Google's native Gemini endpoint directly. The honest way to reach a Claude or Gemini model *through* pcli is via an OpenAI-compatible proxy or aggregator — **OpenRouter** is the simplest one, and it fronts both model families behind one OpenAI-compatible endpoint.
|
|
113
|
+
- **Azure OpenAI is not currently supported.** Its deployment-based URL carries a mandatory `api-version` query parameter, and pcli's gateway client always appends `/chat/completions` as a literal path segment onto `gateway_base_url` — the two don't compose into a URL Azure will accept. See the "Azure OpenAI" section below for details.
|
|
114
|
+
|
|
115
|
+
### OpenAI
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
# PowerShell
|
|
119
|
+
$env:PCLI_GATEWAY_URL = "https://api.openai.com/v1"
|
|
120
|
+
$env:PCLI_GATEWAY_API_KEY = "sk-..."
|
|
121
|
+
pcli --model gpt-4o
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### LM Studio / Ollama (local)
|
|
125
|
+
|
|
126
|
+
See [Quickstart](#quickstart) above — no API key needed.
|
|
127
|
+
|
|
128
|
+
### OpenRouter (the path to Claude, Gemini, and others)
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
$env:PCLI_GATEWAY_URL = "https://openrouter.ai/api/v1"
|
|
132
|
+
$env:PCLI_GATEWAY_API_KEY = "sk-or-..."
|
|
133
|
+
pcli --model anthropic/claude-sonnet-4.5
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
OpenRouter exposes one OpenAI-compatible endpoint in front of dozens of providers' models, including Anthropic's and Google's — swap `--model` for `google/gemini-2.5-pro` or any other model it hosts, and the same gateway URL/key keep working.
|
|
137
|
+
|
|
138
|
+
### Also just works, same pattern
|
|
139
|
+
|
|
140
|
+
Groq, Mistral, DeepSeek, Together AI, and Fireworks AI all expose an OpenAI-compatible `/chat/completions` endpoint — point `gateway_base_url`/`PCLI_GATEWAY_URL` at theirs, set the matching API key, and pick one of their model ids. No special-casing needed.
|
|
141
|
+
|
|
142
|
+
### Azure OpenAI
|
|
143
|
+
|
|
144
|
+
**Not supported.** `GatewayClient` builds an `httpx.AsyncClient` from `gateway_base_url` and then issues every request against a relative path — `POST /chat/completions`, `GET /models` — which httpx appends as a literal path segment onto whatever `gateway_base_url` is. Azure's deployment URL already ends in a mandatory `api-version` query string (e.g. `.../deployments/<deployment>?api-version=2024-02-01`), and httpx's URL-joining doesn't relocate that query string when a path is appended — it just concatenates, producing `.../deployments/<deployment>?api-version=2024-02-01/chat/completions`. That's not a valid request: `/chat/completions` ends up folded into the `api-version` value instead of becoming a real path segment, so there's no URL shape you can put in `gateway_base_url` that works. Reaching Azure-hosted models through pcli would need dedicated client-side support (a special case for the Azure URL shape); until then, use OpenAI directly or an aggregator such as OpenRouter.
|
|
145
|
+
|
|
146
|
+
## Configuration
|
|
147
|
+
|
|
148
|
+
Settings come from environment variables, CLI flags, or `config.toml`. Precedence, highest to lowest: **CLI flags > environment variables > `config.toml` > built-in defaults**. `config.toml` lives at `<config_dir>/config.toml` (e.g. `%APPDATA%\pcli\config.toml` on Windows, `~/.config/pcli/config.toml` on Linux) — created on first access if missing.
|
|
149
|
+
|
|
150
|
+
| Setting | Env var | CLI flag | config.toml key | Default |
|
|
151
|
+
|---|---|---|---|---|
|
|
152
|
+
| Gateway base URL | `PCLI_GATEWAY_URL` | `--gateway-url` | `gateway_base_url` | `""` |
|
|
153
|
+
| Gateway API key | `PCLI_GATEWAY_API_KEY` | `--api-key` | `gateway_api_key` | `""` |
|
|
154
|
+
| Default model | `PCLI_MODEL` | `--model` | `default_model` | `""` |
|
|
155
|
+
| Artifact threshold (chars) | `PCLI_ARTIFACT_THRESHOLD_CHARS` | `--artifact-threshold` | `artifact_threshold_chars` | `4000` |
|
|
156
|
+
| Local-API gateways | `PCLI_LOCAL_API_GATEWAYS` | `--local-api` | `local_api_gateways` | `[]` |
|
|
157
|
+
| Hard session cost cap (USD) | `PCLI_MAX_SESSION_COST_USD` | *(none — see `pcli run --max-cost`)* | `max_session_cost_usd` | unset (no cap) |
|
|
158
|
+
| Tamper-evident audit log | `PCLI_AUDIT_MODE_ENABLED` | *(none — see `pcli run --audit`)* | `audit_mode_enabled` | `false` |
|
|
159
|
+
| Sandbox backend | `PCLI_SANDBOX_BACKEND` | *(none)* | `sandbox_backend` | `"auto"` (`auto`\|`docker`\|`subprocess`\|`none`) |
|
|
160
|
+
| Request timeout (s) | `PCLI_REQUEST_TIMEOUT_S` | *(none)* | `request_timeout_s` | `120.0` |
|
|
161
|
+
| Max tool-call iterations/turn | `PCLI_MAX_TOOL_ITERATIONS` | *(none)* | `max_tool_iterations` | `25` |
|
|
162
|
+
|
|
163
|
+
The API key is deliberately never auto-persisted to `config.toml` — pass `--api-key` or set `PCLI_GATEWAY_API_KEY` each time if your gateway needs one. The gateway URL, model, and artifact threshold *are* auto-persisted when passed as a flag, so a bare `pcli` afterward picks them up. See [`docs/configuration.md`](docs/configuration.md) for the complete settings table — this covers only the ones you're most likely to touch.
|
|
164
|
+
|
|
165
|
+
Example `config.toml`:
|
|
166
|
+
|
|
167
|
+
```toml
|
|
168
|
+
gateway_base_url = "http://localhost:1234/v1"
|
|
169
|
+
default_model = "qwen2.5-coder-32b-instruct"
|
|
170
|
+
max_session_cost_usd = 5.0
|
|
171
|
+
sandbox_backend = "subprocess"
|
|
172
|
+
audit_mode_enabled = false
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## FAQ
|
|
176
|
+
|
|
177
|
+
**Does my code or data ever leave my machine?**
|
|
178
|
+
Only to whatever gateway you've configured. Point `gateway_base_url` at a local server (LM Studio, Ollama, vLLM, ...) and nothing leaves your machine at all — pcli's own cost/pricing tables and sandbox execution are entirely local regardless of gateway. Point it at a hosted API and your conversation (including file contents the model reads) goes wherever that API sends it, same as any coding agent.
|
|
179
|
+
|
|
180
|
+
**How is this different from other AI coding agent CLIs?**
|
|
181
|
+
Three things specifically: a cost budget that's actually *enforced* mid-turn rather than just reported after the fact, a scheduler that can react to file changes or git commits instead of cron time alone, and an opt-in hash-chained audit log for proving what an unattended run did. See [What sets pcli apart](#what-sets-pcli-apart) above. It's also provider-agnostic by construction — any OpenAI-compatible gateway, local or hosted — rather than built around one vendor's API.
|
|
182
|
+
|
|
183
|
+
**Can I run this unattended, in CI, or on a schedule?**
|
|
184
|
+
Yes — `pcli run --task "..."` is a one-shot, non-interactive invocation with a proper exit code (0 on success, 1 on a hit iteration/cost cap or a usage error), suitable for cron/Task Scheduler. `pcli schedule` is pcli's own recurring-task daemon on top of the same machinery, with cron or event triggers. Both support `--max-cost` and `--audit` as safety nets for an unattended run, since there's no one present to approve a permission prompt (a tool call with no standing "Always Allow" grant is simply denied, not paused to ask).
|
|
185
|
+
|
|
186
|
+
**What stops the model from running something destructive?**
|
|
187
|
+
Two independent layers. Guardrails (`guardrails.toml`) are hard, non-negotiable denials — a shell command denylist, filesystem allow/deny roots, a Python module denylist — checked before any permission prompt and never bypassable by asking. On top of that, the permission system gates every tool call that isn't explicitly read-only, either against a remembered grant or an interactive prompt (fails closed with no UI, e.g. in a headless run). Underneath both, a sandbox backend (Docker, a restricted subprocess, or — only if you explicitly opt out — none) bounds what an allowed command can actually touch. See [`docs/sandbox-and-permissions.md`](docs/sandbox-and-permissions.md).
|
|
188
|
+
|
|
189
|
+
**Does it work fully offline?**
|
|
190
|
+
Yes, with a local gateway (LM Studio, Ollama, vLLM, ...) — no network call leaves your machine except whatever the model server itself does. `web_search`/`web_fetch`/`browser_*` are the only tools that reach the internet, and the model only calls them if it decides to.
|
|
191
|
+
|
|
192
|
+
**Does it work on Windows?**
|
|
193
|
+
Yes. pcli's system prompt tells the model up front that it's running on Windows and that `run_shell` executes via `cmd.exe`, not bash — no heredoc syntax, no `$VAR` expansion, chain with `&&` not `;` — rather than letting the model default to Unix assumptions and discover it's wrong by trial and error. The `win` extra (`pywin32`) covers Windows-specific functionality beyond that.
|
|
194
|
+
|
|
195
|
+
**What does it cost to run?**
|
|
196
|
+
Entirely up to the gateway and model you point it at — pcli has no pricing of its own, it tracks whatever a model actually costs against a best-effort pricing table you can edit. Point it at a local model with `--local-api` and it's `$0`, enforced (cost is literally zeroed, not just expected to be low, since a locally-served model's name can coincidentally match a paid builtin pricing pattern otherwise).
|
|
197
|
+
|
|
198
|
+
**Is this an editor plugin? Does it replace my IDE?**
|
|
199
|
+
No. pcli is a standalone terminal TUI you run alongside your editor, not a plugin inside one — it complements an IDE rather than replacing it.
|
|
200
|
+
|
|
201
|
+
## Learn more
|
|
202
|
+
|
|
203
|
+
[`docs/README.md`](docs/README.md) is the full reference: architecture, every setting, every built-in tool, the TUI guide, sandboxing/permissions, sessions/cost, memory, headless/scheduled runs, browser automation, the Telegram bot, and the toolbox plugin system.
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
MIT — see [`LICENSE`](LICENSE).
|