pico-cli 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.
Files changed (108) hide show
  1. pico_cli-0.1.0/.gitignore +25 -0
  2. pico_cli-0.1.0/.scratch/headless-agent/issues/01-unified-ai-call.md +13 -0
  3. pico_cli-0.1.0/.scratch/headless-agent/issues/02-tracer-bullet-headless-text-response.md +12 -0
  4. pico_cli-0.1.0/.scratch/headless-agent/issues/03-tools.md +14 -0
  5. pico_cli-0.1.0/.scratch/headless-agent/issues/04-session-fork.md +11 -0
  6. pico_cli-0.1.0/.scratch/headless-agent/issues/05-compaction.md +11 -0
  7. pico_cli-0.1.0/.scratch/headless-agent/issues/06-extension-binding.md +16 -0
  8. pico_cli-0.1.0/.scratch/headless-agent/issues/07-config-file.md +11 -0
  9. pico_cli-0.1.0/.scratch/headless-agent/spec.md +112 -0
  10. pico_cli-0.1.0/.scratch/pico-tui/spec.md +70 -0
  11. pico_cli-0.1.0/.scratch/trace-view/01-assistant-duration.md +16 -0
  12. pico_cli-0.1.0/.scratch/trace-view/02-row-assembly.md +18 -0
  13. pico_cli-0.1.0/.scratch/trace-view/03-overlay.md +18 -0
  14. pico_cli-0.1.0/AGENTS.md +13 -0
  15. pico_cli-0.1.0/CONTEXT.md +29 -0
  16. pico_cli-0.1.0/IMPLEMENTATION_SUMMARY.md +80 -0
  17. pico_cli-0.1.0/LICENSE +21 -0
  18. pico_cli-0.1.0/PKG-INFO +236 -0
  19. pico_cli-0.1.0/README.md +211 -0
  20. pico_cli-0.1.0/docs/adr/0001-monorepo-package-split.md +25 -0
  21. pico_cli-0.1.0/docs/adr/0002-tree-based-session.md +20 -0
  22. pico_cli-0.1.0/docs/adr/0003-hardcoded-core.md +48 -0
  23. pico_cli-0.1.0/docs/adr/0004-native-provider-adapters.md +47 -0
  24. pico_cli-0.1.0/docs/adr/0005-task-tool-subagents.md +57 -0
  25. pico_cli-0.1.0/docs/adr/0006-trace-view-durations.md +54 -0
  26. pico_cli-0.1.0/docs/agents/domain.md +14 -0
  27. pico_cli-0.1.0/docs/agents/issue-tracker.md +22 -0
  28. pico_cli-0.1.0/docs/agents/triage-labels.md +15 -0
  29. pico_cli-0.1.0/packages/pico_ai/README.md +5 -0
  30. pico_cli-0.1.0/packages/pico_ai/pyproject.toml +30 -0
  31. pico_cli-0.1.0/packages/pico_ai/src/pico_ai/__init__.py +1 -0
  32. pico_cli-0.1.0/packages/pico_ai/src/pico_ai/openrouter.py +262 -0
  33. pico_cli-0.1.0/packages/pico_ai/src/pico_ai/provider.py +16 -0
  34. pico_cli-0.1.0/packages/pico_ai/src/pico_ai/providers/__init__.py +86 -0
  35. pico_cli-0.1.0/packages/pico_ai/src/pico_ai/providers/_compat.py +244 -0
  36. pico_cli-0.1.0/packages/pico_ai/src/pico_ai/providers/anthropic.py +285 -0
  37. pico_cli-0.1.0/packages/pico_ai/src/pico_ai/providers/deepseek.py +78 -0
  38. pico_cli-0.1.0/packages/pico_ai/src/pico_ai/providers/gemini.py +309 -0
  39. pico_cli-0.1.0/packages/pico_ai/src/pico_ai/providers/ollama.py +239 -0
  40. pico_cli-0.1.0/packages/pico_ai/src/pico_ai/providers/openai.py +74 -0
  41. pico_cli-0.1.0/packages/pico_ai/src/pico_ai/providers/spec.py +45 -0
  42. pico_cli-0.1.0/packages/pico_ai/src/pico_ai/py.typed +1 -0
  43. pico_cli-0.1.0/packages/pico_ai/src/pico_ai/types.py +64 -0
  44. pico_cli-0.1.0/packages/pico_core/README.md +5 -0
  45. pico_cli-0.1.0/packages/pico_core/pyproject.toml +33 -0
  46. pico_cli-0.1.0/packages/pico_core/src/pico_core/__init__.py +71 -0
  47. pico_cli-0.1.0/packages/pico_core/src/pico_core/fsm.py +491 -0
  48. pico_cli-0.1.0/packages/pico_core/src/pico_core/py.typed +1 -0
  49. pico_cli-0.1.0/packages/pico_core/src/pico_core/session.py +192 -0
  50. pico_cli-0.1.0/packages/pico_core/src/pico_core/subagents.py +183 -0
  51. pico_cli-0.1.0/packages/pico_core/src/pico_core/todos.py +150 -0
  52. pico_cli-0.1.0/packages/pico_core/src/pico_core/tools.py +527 -0
  53. pico_cli-0.1.0/packages/pico_core/src/pico_core/trace.py +128 -0
  54. pico_cli-0.1.0/packages/pico_sdk/README.md +5 -0
  55. pico_cli-0.1.0/packages/pico_sdk/pyproject.toml +37 -0
  56. pico_cli-0.1.0/packages/pico_sdk/src/pico_sdk/__init__.py +49 -0
  57. pico_cli-0.1.0/packages/pico_sdk/src/pico_sdk/__main__.py +6 -0
  58. pico_cli-0.1.0/packages/pico_sdk/src/pico_sdk/cli.py +157 -0
  59. pico_cli-0.1.0/packages/pico_sdk/src/pico_sdk/config.py +57 -0
  60. pico_cli-0.1.0/packages/pico_sdk/src/pico_sdk/extensions.py +108 -0
  61. pico_cli-0.1.0/packages/pico_sdk/src/pico_sdk/providers.py +92 -0
  62. pico_cli-0.1.0/packages/pico_sdk/src/pico_sdk/py.typed +1 -0
  63. pico_cli-0.1.0/packages/pico_sdk/src/pico_sdk/session.py +316 -0
  64. pico_cli-0.1.0/packages/pico_sdk/src/pico_sdk/skills.py +102 -0
  65. pico_cli-0.1.0/packages/pico_tui/README.md +5 -0
  66. pico_cli-0.1.0/packages/pico_tui/pyproject.toml +41 -0
  67. pico_cli-0.1.0/packages/pico_tui/src/pico_tui/__init__.py +8 -0
  68. pico_cli-0.1.0/packages/pico_tui/src/pico_tui/app.py +964 -0
  69. pico_cli-0.1.0/packages/pico_tui/src/pico_tui/commands.py +48 -0
  70. pico_cli-0.1.0/packages/pico_tui/src/pico_tui/history_picker.py +55 -0
  71. pico_cli-0.1.0/packages/pico_tui/src/pico_tui/modal.py +204 -0
  72. pico_cli-0.1.0/packages/pico_tui/src/pico_tui/model_picker.py +62 -0
  73. pico_cli-0.1.0/packages/pico_tui/src/pico_tui/provider_form.py +91 -0
  74. pico_cli-0.1.0/packages/pico_tui/src/pico_tui/provider_picker.py +58 -0
  75. pico_cli-0.1.0/packages/pico_tui/src/pico_tui/py.typed +1 -0
  76. pico_cli-0.1.0/packages/pico_tui/src/pico_tui/render.py +157 -0
  77. pico_cli-0.1.0/packages/pico_tui/src/pico_tui/skill_picker.py +52 -0
  78. pico_cli-0.1.0/packages/pico_tui/src/pico_tui/status_bar.py +101 -0
  79. pico_cli-0.1.0/packages/pico_tui/src/pico_tui/todo_panel.py +45 -0
  80. pico_cli-0.1.0/packages/pico_tui/src/pico_tui/trace_view.py +226 -0
  81. pico_cli-0.1.0/pyproject.toml +57 -0
  82. pico_cli-0.1.0/src/pico/__init__.py +6 -0
  83. pico_cli-0.1.0/tests/conftest.py +33 -0
  84. pico_cli-0.1.0/tests/test_agent_session.py +96 -0
  85. pico_cli-0.1.0/tests/test_ai_call.py +69 -0
  86. pico_cli-0.1.0/tests/test_cli.py +101 -0
  87. pico_cli-0.1.0/tests/test_config.py +39 -0
  88. pico_cli-0.1.0/tests/test_extensions.py +277 -0
  89. pico_cli-0.1.0/tests/test_first_token_timeout.py +239 -0
  90. pico_cli-0.1.0/tests/test_fsm.py +268 -0
  91. pico_cli-0.1.0/tests/test_history_picker.py +196 -0
  92. pico_cli-0.1.0/tests/test_model_persistence.py +48 -0
  93. pico_cli-0.1.0/tests/test_model_picker.py +92 -0
  94. pico_cli-0.1.0/tests/test_picker_search.py +224 -0
  95. pico_cli-0.1.0/tests/test_pointer_shape.py +116 -0
  96. pico_cli-0.1.0/tests/test_providers.py +744 -0
  97. pico_cli-0.1.0/tests/test_result_display.py +245 -0
  98. pico_cli-0.1.0/tests/test_session.py +87 -0
  99. pico_cli-0.1.0/tests/test_skill_picker.py +196 -0
  100. pico_cli-0.1.0/tests/test_status_bar.py +39 -0
  101. pico_cli-0.1.0/tests/test_stream_order.py +129 -0
  102. pico_cli-0.1.0/tests/test_subagents.py +393 -0
  103. pico_cli-0.1.0/tests/test_todos.py +250 -0
  104. pico_cli-0.1.0/tests/test_tools.py +83 -0
  105. pico_cli-0.1.0/tests/test_trace.py +121 -0
  106. pico_cli-0.1.0/tests/test_trace_view.py +292 -0
  107. pico_cli-0.1.0/tests/test_tui.py +328 -0
  108. pico_cli-0.1.0/uv.lock +708 -0
@@ -0,0 +1,25 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ *.egg
7
+ dist/
8
+ build/
9
+ .eggs/
10
+
11
+ # Virtual environments
12
+ .venv/
13
+ venv/
14
+ env/
15
+
16
+ # Test / coverage
17
+ .pytest_cache/
18
+ .coverage
19
+ htmlcov/
20
+
21
+ # IDE / OS
22
+ .idea/
23
+ .vscode/
24
+ .DS_Store
25
+ Thumbs.db
@@ -0,0 +1,13 @@
1
+ # 01 — Unified AI call + OpenRouter client
2
+
3
+ **What to build:** a streaming, async, provider-agnostic "AI call" in `pico_ai`. Given a prompt (and optional tool definitions), it sends a request to OpenRouter and returns a normalized stream of text / thinking / tool-call blocks plus usage. The API key comes from the environment; the model is configurable with a default.
4
+
5
+ **Blocked by:** None — can start immediately.
6
+
7
+ **Status:** ready-for-agent
8
+
9
+ - [ ] A unified request/response type exists that is provider-agnostic (no OpenRouter-specific fields leak into callers).
10
+ - [ ] An async, streaming client sends a prompt to OpenRouter and yields normalized blocks (text, thinking, tool-call) as they arrive.
11
+ - [ ] The response includes usage (input/output tokens) when the provider reports it.
12
+ - [ ] Tool definitions round-trip: a caller can pass tool schemas and receive tool-call blocks with name + arguments.
13
+ - [ ] A fake transport lets tests exercise the client with no network.
@@ -0,0 +1,12 @@
1
+ # 02 — Tracer bullet: headless text response
2
+
3
+ **What to build:** `pico run "…"` returns a text response and persists an append-only session (user node + assistant node) to disk, driven by a minimal FSM (`idle → streaming → done`). No tools yet.
4
+
5
+ **Blocked by:** 01 — Unified AI call + OpenRouter client.
6
+
7
+ **Status:** ready-for-agent
8
+
9
+ - [ ] `pico run "hello"` prints the model's text response.
10
+ - [ ] A session is persisted as JSONL with a user node and an assistant node, each with id/parent_id/timestamp/payload.
11
+ - [ ] The FSM transitions idle → streaming → done for a simple prompt.
12
+ - [ ] A scripted fake provider drives the loop deterministically in tests (no network).
@@ -0,0 +1,14 @@
1
+ # 03 — Tools: read / write / edit / bash
2
+
3
+ **What to build:** the agent can invoke the four tools, feed results back, and loop `streaming ⇄ tool_executing` until done. Bash echoes commands and requires an opt-in flag.
4
+
5
+ **Blocked by:** 02 — Tracer bullet: headless text response.
6
+
7
+ **Status:** ready-for-agent
8
+
9
+ - [ ] The agent can read a file, write (create/overwrite) a file, and edit a file with a surgical search/replace patch.
10
+ - [ ] The agent can run a bash command and receive its output and exit code.
11
+ - [ ] Tool results are fed back into the conversation so the model can act on them.
12
+ - [ ] The loop continues streaming ⇄ tool_executing until the model stops requesting tools.
13
+ - [ ] Bash commands are echoed before execution, and bash is disabled unless an opt-in flag is set.
14
+ - [ ] Tool errors (non-zero exit, missing file) surface as tool results rather than crashing.
@@ -0,0 +1,11 @@
1
+ # 04 — Session fork & branch-scoped context
2
+
3
+ **What to build:** rewind to an earlier node and fork a new branch; only the active branch is sent to the model.
4
+
5
+ **Blocked by:** 02 — Tracer bullet: headless text response.
6
+
7
+ **Status:** ready-for-agent
8
+
9
+ - [ ] A session can fork from an earlier node, creating a new branch.
10
+ - [ ] Only the active branch's nodes are assembled into the model context; abandoned branches are excluded.
11
+ - [ ] Forking is append-only: existing nodes are never edited or deleted.
@@ -0,0 +1,11 @@
1
+ # 05 — Compaction (auto + manual)
2
+
3
+ **What to build:** auto-compaction at the token threshold (`contextTokens > window − reserve`) and manual `/compact [instructions]`, summarising old turns into a compaction-summary node.
4
+
5
+ **Blocked by:** 02 — Tracer bullet: headless text response.
6
+
7
+ **Status:** ready-for-agent
8
+
9
+ - [ ] Auto-compaction fires when context tokens exceed window − reserve (reserve default 16384).
10
+ - [ ] Compaction summarises old turns into a compaction-summary node and keeps the system prompt + recent window.
11
+ - [ ] Manual `/compact [instructions]` triggers compaction with steering instructions.
@@ -0,0 +1,16 @@
1
+ # 06 — Extension binding & plugin loading
2
+
3
+ > **Superseded by ADR-0003 (2026-09-30):** the generic plugin binding below was
4
+ > deliberately removed. The core is hardcoded; curated extensions only (fixed
5
+ > observe-only hooks + `SKILL.md` skills). Kept for history — do not implement.
6
+
7
+ **What to build:** `register_tool`, `register_provider`, and lifecycle hooks (`on_session_start`, `tool.before.*`, `tool.after.*`); plugins load from a directory and via explicit registration.
8
+
9
+ **Blocked by:** 03 — Tools: read / write / edit / bash.
10
+
11
+ **Status:** ready-for-agent
12
+
13
+ - [ ] A plugin can register a custom tool that the agent can invoke.
14
+ - [ ] A plugin can register a custom provider.
15
+ - [ ] Lifecycle hooks fire: on_session_start, tool.before.*, tool.after.*.
16
+ - [ ] Plugins load from a plugins directory and via explicit registration.
@@ -0,0 +1,11 @@
1
+ # 07 — Config file (settings.json)
2
+
3
+ **What to build:** `~/.pico/settings.json` supplies model, reserve tokens, and session location; sensible defaults when absent.
4
+
5
+ **Blocked by:** 02 — Tracer bullet: headless text response.
6
+
7
+ **Status:** ready-for-agent
8
+
9
+ - [ ] Settings load from ~/.pico/settings.json when present.
10
+ - [ ] Model, reserve tokens, and session location are configurable.
11
+ - [ ] Sensible defaults apply when the file is absent or a key is missing.
@@ -0,0 +1,112 @@
1
+ ---
2
+ title: Headless agent runtime
3
+ labels:
4
+ - ready-for-agent
5
+ ---
6
+
7
+ # Headless Agent Runtime
8
+
9
+ ## Problem Statement
10
+
11
+ The user wants a Python CLI coding agent — inspired by Pi's modular, plugin-driven architecture — that can autonomously (yolo mode) perform coding tasks in a repository: reading, writing, and editing files, and running bash commands, while keeping a resumable, branchable session history and automatically compacting context to stay within the model's token budget.
12
+
13
+ Today the codebase is only a scaffold: four empty packages (`pico_ai`, `pico_core`, `pico_sdk`, `pico_tui`) with no agent loop, no LLM integration, no session persistence, and no tools. There is nothing runnable yet.
14
+
15
+ ## Solution
16
+
17
+ Build the **headless agent runtime** across three packages, so that `pico run "…"` works end-to-end without a UI:
18
+
19
+ - **`pico_ai`** — a unified "AI call" type that normalises every provider behind a single request/response shape, with a streaming, async OpenRouter client as the first (and only) gateway.
20
+ - **`pico_core`** — an explicit finite-state-machine agent loop that maintains an append-only session tree and auto-compacts context.
21
+ - **`pico_sdk`** — a headless `AgentSession` API plus the extension/plugin binding, exposed through a `pico run "…"` command.
22
+
23
+ The TUI (`pico_tui`) is a later milestone.
24
+
25
+ ## User Stories
26
+
27
+ 1. As a developer, I want to run pico headlessly with a single prompt (`pico run "…"`), so that I can automate a coding task without an interactive UI.
28
+ 2. As a developer, I want pico to read files in my repo, so that the agent can understand the codebase before acting.
29
+ 3. As a developer, I want pico to write (create or overwrite) files, so that it can produce new code.
30
+ 4. As a developer, I want pico to edit files surgically with search/replace patches, so that it can make targeted changes without rewriting whole files.
31
+ 5. As a developer, I want pico to run bash commands, so that it can build, test, and inspect the repo.
32
+ 6. As a developer, I want pico to operate in yolo mode with no per-step approval, so that it can complete multi-step tasks autonomously.
33
+ 7. As a developer, I want to see each bash command echoed before it runs, so that I can audit what the agent is doing.
34
+ 8. As a developer, I want an opt-in flag that permits unsandboxed bash execution, so that I explicitly consent to the risk.
35
+ 9. As a developer, I want pico to loop between streaming and tool execution until the task is done, so that it can self-correct (e.g. run tests, then fix failures).
36
+ 10. As a developer, I want tool results fed back into the conversation, so that the agent can act on command output and file contents.
37
+ 11. As a developer, I want pico to handle tool errors (non-zero exit, missing file) gracefully, so that it can recover or report rather than crash.
38
+ 12. As a developer, I want to reach multiple LLM providers through one gateway, so that I can switch models without code changes.
39
+ 13. As a developer, I want streaming responses, so that I can watch output as it is generated.
40
+ 14. As a developer, I want thinking (reasoning) blocks preserved in the transcript, so that I can review the agent's reasoning.
41
+ 15. As a developer, I want assistant responses to carry usage/token counts, so that I can track cost.
42
+ 16. As a developer, I want my session persisted as an append-only tree of nodes, so that I can resume and review it later.
43
+ 17. As a developer, I want to fork from an earlier node, so that I can rewind after a broken change without starting over.
44
+ 18. As a developer, I want only the active branch sent to the model, so that abandoned branches do not waste tokens.
45
+ 19. As a developer, I want automatic context compaction, so that long sessions stay within the model's context window.
46
+ 20. As a developer, I want to manually trigger compaction with steering instructions, so that I can control what gets summarised.
47
+ 21. As a developer, I want reserve tokens held back for the model's response, so that compaction leaves room for output.
48
+ 22. As a developer, I want a config file for settings, so that I can tune the agent without editing code.
49
+ 23. As a developer, I want sessions stored per-session in a known location, so that I can find and inspect them.
50
+ 24. As a developer, I want an explicit, observable state machine, so that I can reason about and test the agent's behaviour.
51
+ 25. As a developer, I want failures surfaced as a clear error state, so that I can diagnose what went wrong.
52
+ 26. As a developer, I want a headless library API (`AgentSession`), so that I can embed pico in my own scripts and tools.
53
+ 27. As a developer, I want to feed input and harvest output streams programmatically, so that I can build automation on top of pico.
54
+ 28. As a plugin author, I want to register custom tools, so that I can extend the agent's capabilities.
55
+ 29. As a plugin author, I want to register custom providers, so that I can add LLM backends beyond the gateway.
56
+ 30. As a plugin author, I want lifecycle hooks (session start, before/after tool), so that I can intercept and react to agent events.
57
+ 31. As a plugin author, I want plugins loaded from a plugins directory and via explicit registration, so that I can choose how to distribute them.
58
+ 32. As a developer, I want the unified "AI call" shape used for every provider, so that provider-specific details are normalised away.
59
+
60
+ ## Implementation Decisions
61
+
62
+ - **Monorepo split (ADR-0001).** Four packages with a one-way dependency chain `pico_ai ← pico_core ← pico_sdk ← pico_tui`. This spec covers the first three; `pico_tui` is out of scope.
63
+ - **Tree-based append-only session (ADR-0002).** A session is a tree of immutable nodes; nodes are never edited or deleted, only built upon. Branches are root-to-leaf timelines; forking rewinds to an earlier node and starts a new branch. Only the active branch is sent to the model.
64
+ - **Unified "AI call".** A single provider-agnostic request/response shape. OpenRouter is the single gateway (one HTTP client, one key, all models). Native adapters are deferred to plugins.
65
+ - **Streaming + async.** The core is `asyncio`-based; responses stream token-by-token.
66
+ - **Explicit FSM.** The agent loop is a finite state machine. States (from the design):
67
+
68
+ ```
69
+ idle → streaming ⇄ tool_executing → done
70
+ ↓
71
+ compacting (triggered by token threshold)
72
+ error (reachable from any state)
73
+ ```
74
+
75
+ Yolo mode means there is no approval/confirmation state.
76
+ - **Node payload vocabulary.** Each node's payload is one of: user, assistant (block-granular: text / thinking / tool-call), tool request, tool result, compaction summary. Node shape (from the design):
77
+
78
+ ```
79
+ Node { id, parent_id, timestamp, payload }
80
+ payload: User | Assistant | ToolRequest | ToolResult | CompactionSummary
81
+ ```
82
+
83
+ - **Four core tools.** `read` (file), `write` (create/overwrite), `edit` (surgical search/replace patch), `bash` (shell). Bash runs unsandboxed with a visible command echo and an opt-in flag.
84
+ - **Compaction.** Auto-triggered when `contextTokens > contextWindow − reserveTokens` (reserve default 16384), plus a manual `/compact [instructions]` override. Compaction summarises older turns into a compaction-summary node, keeping the system prompt and a recent window.
85
+ - **Config & sessions.** Settings in `~/.pico/settings.json`; sessions persisted as `.jsonl` under `~/.pico/sessions/<id>.jsonl`.
86
+ - **Extension binding.** `register_tool`, `register_provider`, and lifecycle hooks (`on_session_start`, `tool.before.*`, `tool.after.*`). Plugins load from a plugins directory and via explicit registration.
87
+ > **Superseded by ADR-0003 (2026-09-30):** the generic binding was removed in favour of a hardcoded core with curated extensions (fixed observe-only hooks + `SKILL.md` skills). See `docs/adr/0003-hardcoded-core.md`.
88
+ - **Models in pydantic v2.**
89
+ - **Deferred as plugins.** MCP, sub-agents, plan mode, and local to-do tracking are explicitly out of the core and will be added later as plugins.
90
+
91
+ ## Testing Decisions
92
+
93
+ - **What makes a good test.** Assert on external behaviour — the observable session tree, the tool calls made, and the FSM outcome — not on internal implementation. Drive everything through the public `AgentSession` API.
94
+ - **Primary seam — the provider boundary.** Inject a scripted fake "AI call" (predetermined responses and tool requests), and the whole loop becomes deterministic and network-free. This is the single high seam.
95
+ - **Supporting seam — the filesystem boundary.** The four tools are tested against a temporary working directory, since they touch the disk.
96
+ - **Modules under test.** `pico_ai` (provider normalisation, streaming parsing), `pico_core` (FSM transitions, session-tree append/fork, compaction trigger), `pico_sdk` (`AgentSession` behaviour, extension registration and hooks).
97
+ - **Prior art.** None — this is a fresh codebase. Use `pytest` with `pytest-asyncio`; the fake provider returns scripted streams.
98
+
99
+ ## Out of Scope
100
+
101
+ - `pico_tui` (the terminal UI) — a follow-up spec.
102
+ - MCP, sub-agents, plan mode, and local to-do tracking — future plugins.
103
+ - Native provider adapters beyond the OpenRouter gateway — future plugins.
104
+ - Sandboxing / containerization of bash execution.
105
+ - Telemetry.
106
+ - Multi-session management beyond single-session persistence.
107
+
108
+ ## Further Notes
109
+
110
+ - Build order: `pico_ai` → `pico_core` → `pico_sdk`.
111
+ - First milestone: headless `pico run "…"` works end-to-end (prompt → loop → tools → persisted session).
112
+ - Respect ADR-0001 (monorepo split) and ADR-0002 (tree-based session) throughout.
@@ -0,0 +1,70 @@
1
+ ---
2
+ title: Terminal UI (interactive chat)
3
+ labels:
4
+ - ready-for-agent
5
+ ---
6
+
7
+ > **Status note (2026-09-30):** implemented and since exceeded — the TUI is now
8
+ > a full Textual + Rich app (`/help`, `/history`, `/compact`, `/model`,
9
+ > `/skills`, `/fork`, `/undo`, `/quit`, todo panel, status bar, pickers).
10
+ > The "empty scaffold / deferred rendering" framing below is historical.
11
+ > Kept for history — do not re-implement.
12
+
13
+ # Terminal UI (pico_tui)
14
+
15
+ ## Problem Statement
16
+
17
+ The headless `pico run "…"` command works end-to-end, but every invocation is a single prompt with no interactive loop. A developer who wants to explore a repo interactively — ask follow-ups, watch tool calls happen, rewind after a bad turn, compact when context grows — needs an interactive terminal interface, not one-shot commands.
18
+
19
+ Today `pico_tui` is an empty scaffold: no REPL, no streaming view, no command handling.
20
+
21
+ ## Solution
22
+
23
+ Build the **terminal UI view** in `pico_tui` on top of the existing `AgentSession` API, so that `pico-chat` launches an interactive, streaming session:
24
+
25
+ - A prompt loop (`pico> `) that streams the agent's response token-by-token.
26
+ - Visible tool activity: bash commands echoed before execution, other tool calls shown with name + arguments.
27
+ - Slash commands: `/help`, `/compact [instructions]`, `/fork <node-id>`, `/quit`.
28
+ - The session is persisted on exit and resumable via `--session`.
29
+
30
+ The TUI is a thin view over `pico_sdk`: it renders `LoopEvent`s and dispatches slash commands, and does **not** reimplement the loop, tools, or persistence. Per ADR-0001 it sits at the top of the one-way chain `pico_ai ← pico_core ← pico_sdk ← pico_tui`.
31
+
32
+ ## User Stories
33
+
34
+ 1. As a developer, I want an interactive prompt loop, so that I can have a back-and-forth conversation with the agent.
35
+ 2. As a developer, I want responses to stream token-by-token, so that I can watch output as it is generated.
36
+ 3. As a developer, I want bash commands echoed before they run, so that I can audit what the agent is doing.
37
+ 4. As a developer, I want other tool calls (read/write/edit) shown with their arguments, so that I can see what the agent is doing.
38
+ 5. As a developer, I want a `/help` command, so that I can discover the available commands.
39
+ 6. As a developer, I want `/compact [instructions]`, so that I can manually compact the context with steering text.
40
+ 7. As a developer, I want `/fork <node-id>`, so that I can rewind to an earlier node and start a new branch interactively.
41
+ 8. As a developer, I want `/quit`, so that I can save the session and exit cleanly.
42
+ 9. As a developer, I want to resume a prior session with `--session <id>`, so that I can continue where I left off.
43
+ 10. As a developer, I want the same flags as `pico run` (`--model`, `--allow-bash`, `--cwd`), so that I can configure an interactive session the same way.
44
+
45
+ ## Implementation Decisions
46
+
47
+ - **Thin view.** The TUI renders `LoopEvent`s and dispatches slash commands; the agent loop, tools, session tree, and persistence all come from `pico_sdk` unchanged. Interactive features belong here, not in the core.
48
+ - **Shared provider factory.** `pico_sdk.providers.create_provider()` builds the OpenRouter provider from the configured env var; both `pico run` and `pico-chat` use it (one source of truth for provider wiring).
49
+ - **Streaming.** The REPL iterates `AgentSession.stream(prompt)` and writes text chunks immediately (no buffering).
50
+ - **Blocking input on a thread.** `input()` runs via `asyncio.to_thread`, so the event loop is not blocked while a response streams.
51
+ - **Command vocabulary.** `/help` (aliases `/h`, `/?`), `/compact [instructions]`, `/fork <node-id>`, and `/quit` (aliases `/exit`, `/q`). Any other non-empty line is sent to the agent as a prompt.
52
+ - **Separate entry point.** `pico-chat` console script, distinct from `pico run`, preserving the one-way dependency (pico_sdk does not import pico_tui).
53
+
54
+ ## Testing Decisions
55
+
56
+ - **Pure functions first.** `parse_line` (input → command/prompt) and `render_event` (`LoopEvent` → display string) are unit-tested directly.
57
+ - **REPL seam.** `TUI` accepts injected `input_fn` and `write` callables, so the interactive loop is tested with a scripted input sequence and a captured output buffer, driven by the same scripted fake provider used for the headless agent.
58
+ - **Live smoke.** A piped-input run (`"say hi"` then `/quit`) against a real fast model confirms the end-to-end loop.
59
+
60
+ ## Out of Scope
61
+
62
+ - Rich terminal rendering (colors, spinners, curses/Textual) — deferred.
63
+ - A `/history` or node-browser command for discovering node ids — `/fork` currently takes an id directly.
64
+ - Multi-panel or split layouts.
65
+ - Any change to the agent loop, tools, or persistence — those live in `pico_core`/`pico_sdk`.
66
+
67
+ ## Further Notes
68
+
69
+ - `pico run` remains the headless path; `pico-chat` is the interactive path.
70
+ - Follow-up specs may add richer rendering and node discovery without touching the core.
@@ -0,0 +1,16 @@
1
+ # 01 — Assistant stream timing (`duration_ms`)
2
+
3
+ **What to build:** measure provider-stream wall-time in `AgentLoop.stream()`
4
+ (`packages/pico_core/src/pico_core/fsm.py`) and persist it as
5
+ `duration_ms: float | None = None` on `AssistantPayload`
6
+ (`packages/pico_core/src/pico_core/session.py`). `None` means "unknown"
7
+ (old sessions, unmeasurable runs) and renders as `—`.
8
+
9
+ **Blocked by:** nothing.
10
+
11
+ **Status:** ready-for-agent
12
+
13
+ - [ ] `AssistantPayload` gains `duration_ms: float | None = None`; old JSONL session files still validate and load.
14
+ - [ ] The loop records a monotonic start before `provider.stream(request)` and stamps the appended `AssistantPayload` with elapsed ms (both tool and non-tool turns).
15
+ - [ ] Interrupted/error streams still append with a measured (partial) duration or `None` — never crash timing.
16
+ - [ ] Fake-provider tests assert a non-negative `duration_ms` on the appended assistant node.
@@ -0,0 +1,18 @@
1
+ # 02 — Trace row assembly (pure function)
2
+
3
+ **What to build:** a pure function mapping the active branch (`list[Node]`)
4
+ to trace rows: `time (HH:MM:SS from node.timestamp)` | `kind` |
5
+ `summary (one line, ~80ch, same truncation as the history picker)` |
6
+ `status (ok/error from is_error, plus bash exit code where parseable)` |
7
+ `tokens (assistant total, else blank)` | `duration (computed
8
+ result.timestamp − request.timestamp paired by tool_call_id; assistant
9
+ duration_ms; else blank)`.
10
+
11
+ **Blocked by:** 01 — Assistant stream timing (for the assistant-duration branch).
12
+
13
+ **Status:** ready-for-agent
14
+
15
+ - [ ] One tested function, no Textual/Rich dependency (like `commands.parse_line`).
16
+ - [ ] Tool durations pair request/result by `tool_call_id`; orphan results (missing request, e.g. old/odd trees) show blank, never crash.
17
+ - [ ] Bash exit codes reuse the existing result-parsing rule (`[exit code: N]` suffix).
18
+ - [ ] Non-assistant token cells and non-duration rows are blank (`—`/`—`), never estimated.
@@ -0,0 +1,18 @@
1
+ # 03 — Trace view overlay (`Ctrl+T`, `/trace`)
2
+
3
+ **What to build:** a full-screen TUI overlay (`TraceViewScreen`) rendering
4
+ rows from 02 for the active branch: snapshot on open (not live), `r`
5
+ re-snapshots, `e` toggles errors-only, typing filters (substring over the
6
+ summary, reusing the `PickerScreen` filter bar), `↑/↓` moves, `Enter`/`Esc`
7
+ dismisses. Open via `Ctrl+T` binding plus `/trace` slash command (parsed in
8
+ `commands.py`, dispatched in `app.py`, listed in `HELP_TEXT`).
9
+
10
+ **Blocked by:** 02 — Trace row assembly.
11
+
12
+ **Status:** ready-for-agent
13
+
14
+ - [ ] `Ctrl+T` opens, `Esc`/`Ctrl+T` closes; `/trace` opens the same overlay; `/help` documents both.
15
+ - [ ] Overlay shows a snapshot taken on open; `r` refreshes from the current active branch (fork/undo-safe: no nodes are mutated).
16
+ - [ ] `e` toggles errors-only (`is_error` rows); the filter bar narrows by summary text; the two compose.
17
+ - [ ] Empty sessions render a `(no nodes)` state; 80-col terminals show all six columns without wrapping.
18
+ - [ ] TUI tests drive it with `FakeProvider` sessions (open, filter, errors-only, refresh, dismiss).
@@ -0,0 +1,13 @@
1
+ ## Agent skills
2
+
3
+ ### Issue tracker
4
+
5
+ Issues are tracked as local markdown files under `.scratch/<feature>/`. See `docs/agents/issue-tracker.md`.
6
+
7
+ ### Triage labels
8
+
9
+ The five canonical triage labels are: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`. See `docs/agents/triage-labels.md`.
10
+
11
+ ### Domain docs
12
+
13
+ Single-context: one `CONTEXT.md` and `docs/adr/` at the repo root; agents read `CONTEXT.md` before starting work and treat ADRs as authoritative recorded decisions. See `docs/agents/domain.md`.
@@ -0,0 +1,29 @@
1
+ # Context
2
+
3
+ The domain vocabulary for **pico**, a Python CLI coding agent with a hardcoded core and curated extensions (Claude Code-style, see ADR-0003).
4
+
5
+ ## Glossary
6
+
7
+ - **Agent** — the autonomous coding agent itself. It acts on its own, without asking for approval at each step (informally, *yolo mode*).
8
+ - **Session** — one persisted coding session, represented as a tree of nodes.
9
+ - **Node** — an immutable, append-only unit of event data in a session. Each node carries an id, a pointer to its parent, a timestamp, and a payload. Once written, a node is never edited or deleted — only built upon.
10
+ - **Payload** — the content of a node: a **user** message, an **assistant** message, a **tool request**, a **tool result**, or a **compaction summary**.
11
+ - **Branch** — a timeline: the sequence of nodes from the root to a leaf. A session can hold many parallel branches.
12
+ - **Fork** — rewinding to an earlier node and starting a new branch from it (for example, after a change that broke the codebase).
13
+ - **Turn** — one user message plus the agent's full response to it, including any tool requests it makes.
14
+ - **Tool** — a capability the agent can invoke. The core tools are **read**, **write**, **edit**, **grep**, **fetch**, **websearch**, **bash**, **todo**, and **task**.
15
+ - **Todo tool** — the agent-facing `todo` tool (actions `add` / `update` / `list` / `clear`) backed by a shared in-memory **todo list**; statuses are `pending`, `in_progress`, `completed`.
16
+ - **Sub-agent** — a child agent loop spawned by the parent through the model-invoked `task` tool. It runs on its own session (persisted as its own file) with restricted tools, and only its final summary returns to the parent.
17
+ - **Tool request** — the agent asking to run a tool.
18
+ - **Tool result** — the output returned by running a tool.
19
+ - **Compaction** — summarising older context so the session fits within the model's context window.
20
+ - **Context window** — the token budget of the model in use.
21
+ - **Reserve tokens** — the portion of the context window held back for the model's own response.
22
+ - **Provider** — an LLM backend. Every provider is reached through a single gateway and exposed as one unified **AI call**.
23
+ - **Adapter** — one Python module per provider (`pico_ai/providers/`) converting that backend's wire format to the app's default event shape. The registry is a hardcoded host-owned list, not a plugin API (see ADR-0004).
24
+ - **AI call** — the unified request/response shape used to talk to any provider.
25
+ - **Headless** — running the agent programmatically (as a library) with no terminal UI.
26
+ - **Hook** — a curated, observe-only lifecycle callback (`session_start`, `pre_tool_use`, `post_tool_use`, `post_tool_failure`). Hooks cannot mutate arguments/results or veto execution.
27
+ - **Skill** — a model-invoked `SKILL.md` markdown file (name + trigger description + instructions) discovered from `~/.pico/skills/*/SKILL.md` and `~/.agents/skills/*/SKILL.md` (plus project-local override) and inlined into the system prompt.
28
+ - **Trace view** — a full-screen overlay listing one row per node on the active branch.
29
+ - **Trace row** — one line in the trace view showing a single node's time, kind, summary, status, tokens, and duration.
@@ -0,0 +1,80 @@
1
+ # Context Status Bar Implementation Summary
2
+
3
+ ## Overview
4
+ Added a context status bar widget to the PicoCLI TUI that displays:
5
+ - Provider name (e.g., "OpenRouter")
6
+ - Model name (e.g., "nvidia/nemotron-3.5-lightning:free")
7
+ - Thinking indicator (shown when the model is reasoning)
8
+ - Context window progress bar (fills as context fills, color-coded by usage level)
9
+ - Current token count (formatted with commas, e.g., "38,932")
10
+
11
+ ## Files Modified
12
+
13
+ ### 1. `packages/pico_core/src/pico_core/fsm.py`
14
+ - Added public `estimate_tokens()` method to `AgentLoop` class
15
+ - This exposes the private `_estimate_tokens()` functionality
16
+
17
+ ### 2. `packages/pico_sdk/src/pico_sdk/session.py`
18
+ - Added `provider_name` property to `AgentSession` class
19
+ - Extracts provider name from the provider class name
20
+ - Returns "OpenRouter" for OpenRouterProvider
21
+ - Added `context_window` property to `AgentSession` class
22
+ - Returns the context window size from the agent loop
23
+ - Added `estimate_tokens()` method to `AgentSession` class
24
+ - Returns current estimated token count from the agent loop
25
+
26
+ ### 3. `packages/pico_tui/src/pico_tui/status_bar.py` (NEW FILE)
27
+ - Created `ContextStatusBar` widget extending Textual's `Static`
28
+ - Features:
29
+ - Displays provider | model information
30
+ - Shows "thinking" indicator in yellow italic when active
31
+ - Renders a 20-character progress bar for context window usage
32
+ - Color-codes the progress bar:
33
+ - Green: 0-50% usage (░ character)
34
+ - Yellow: 50-70% usage (▒ character)
35
+ - Orange: 70-90% usage (▓ character)
36
+ - Red: 90-100% usage (█ character)
37
+ - Shows token count with comma formatting in cyan
38
+ - Methods:
39
+ - `on_mount()`: Renders initial display after widget mount
40
+ - `update_info()`: Updates all status information
41
+ - `set_thinking()`: Updates thinking state
42
+ - `_update_display()`: Renders the status bar with Rich Text
43
+
44
+ ### 4. `packages/pico_tui/src/pico_tui/app.py`
45
+ - Imported `ContextStatusBar` widget
46
+ - Added CSS styling for `#status-bar`:
47
+ - Docked to bottom
48
+ - Height: 1 line
49
+ - Background: $surface color
50
+ - Padding: 0 1
51
+ - Added `ContextStatusBar` to `compose()` method after Input widget
52
+ - Added `_update_status_bar()` method to update the status bar with current session info
53
+ - Updated `on_mount()` to call `_update_status_bar()` on initialization
54
+ - Updated `_run_prompt()` to call `_update_status_bar()` when streaming starts
55
+ - Updated `_stream_worker()` to call `_update_status_bar()` when streaming completes
56
+
57
+ ### 5. `tests/test_status_bar.py` (NEW FILE)
58
+ - Added 3 tests for the ContextStatusBar widget:
59
+ - `test_status_bar_initialization`: Verifies default values
60
+ - `test_status_bar_stores_info`: Verifies info storage
61
+ - `test_status_bar_set_thinking`: Verifies thinking state updates
62
+
63
+ ## Test Results
64
+ All 92 tests pass successfully, including:
65
+ - 19 existing TUI tests
66
+ - 3 new status bar tests
67
+ - All other existing tests remain passing
68
+
69
+ ## Usage
70
+ The status bar automatically appears below the input bar in the TUI and updates:
71
+ - On application startup (shows initial provider, model, and 0 tokens)
72
+ - When a prompt is submitted (shows "thinking" indicator)
73
+ - When streaming completes (updates token count and hides "thinking")
74
+ - When the model changes via `/model` command
75
+
76
+ The status bar provides real-time visibility into:
77
+ - Which AI provider and model is being used
78
+ - Whether the model is currently thinking/reasoning
79
+ - How much of the context window is being used
80
+ - The exact token count in the current context
pico_cli-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arya-Ojha
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.