turnloop 0.1.6__tar.gz → 0.1.8__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.
- {turnloop-0.1.6 → turnloop-0.1.8}/PKG-INFO +40 -7
- {turnloop-0.1.6 → turnloop-0.1.8}/README.md +39 -6
- {turnloop-0.1.6 → turnloop-0.1.8}/pyproject.toml +1 -1
- turnloop-0.1.8/tests/test_skills_install.py +523 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/tests/test_tui.py +30 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/__init__.py +1 -1
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/cli.py +242 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/config.py +29 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/configio.py +16 -1
- turnloop-0.1.8/turnloop/skills_install.py +328 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tui/app.py +32 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/.github/workflows/ci.yml +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/.gitignore +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/.turnloop/.gitignore +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/.turnloop/settings.local.json +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/tests/conftest.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/tests/fixtures/anthropic_tool_call.sse +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/tests/fixtures/glm_tool_call.sse +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/tests/fixtures/mcp_echo_server.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/tests/test_bash.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/tests/test_compaction.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/tests/test_config.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/tests/test_config_screen.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/tests/test_configio.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/tests/test_experiments.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/tests/test_hooks_mcp_commands.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/tests/test_loop.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/tests/test_permissions.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/tests/test_providers.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/tests/test_system_prompt_layout.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/tests/test_tools_files.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/tests/test_websearch.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/__main__.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/agent/__init__.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/agent/factory.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/agent/headless.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/agent/loop.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/agent/subagent.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/agent/system_prompt.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/commands/__init__.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/commands/dispatch.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/commands/loader.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/context/__init__.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/context/budget.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/context/compaction.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/context/memory.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/core/__init__.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/core/events.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/core/ids.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/core/messages.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/core/tokens.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/diagnostics.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/errors.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/__init__.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/configs/bakeoff.yaml +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/configs/ceiling-check.yaml +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/configs/context-glm.yaml +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/configs/context.yaml +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/configs/glm-selfhosted.yaml +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/configs/groq-free.yaml +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/configs/hard-calibration.yaml +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/configs/hard-glm.yaml +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/configs/loop.yaml +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/configs/nvidia-smoke.yaml +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/configs/reliability.yaml +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/configs/rewrite-calibration.yaml +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/configs/smoke.yaml +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/graders.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/report.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/runner.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/default.yaml +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/bulky/module_1.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/bulky/module_2.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/bulky/module_3.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/bulky/module_4.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/bulky/module_5.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/bulky/module_6.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/bulky/module_7.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/bulky/module_8.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/circular_import/models.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/circular_import/services.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/circular_import/test_cancel.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/cli_flag/app.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/cross_file_contract/decode.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/cross_file_contract/encode.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/cross_file_contract/test_roundtrip.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/cross_file_contract/test_wire_format.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/empty/.keep +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/failing_test/calc.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/failing_test/test_calc.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/generate_bulky.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/grep_edit/ingest.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/grep_edit/load.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/grep_edit/transform.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/misleading_traceback/checkout.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/misleading_traceback/invoice.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/misleading_traceback/pricing.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/misleading_traceback/test_checkout.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/misleading_traceback/test_invoice.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/needle/inventory.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/needle/pricing.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/needle/shipping.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/regression_trap/test_validators.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/regression_trap/validators.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/rename/billing.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/rename/report.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/rename/test_billing.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/spec/parser.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/spec/test_parser.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/subtle_spec_edge/leaderboard.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/subtle_spec_edge/test_leaderboard.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/experiments/suite/fixtures/util/util.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/hooks/__init__.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/hooks/runner.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/mcp/__init__.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/mcp/adapter.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/mcp/client.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/permissions/__init__.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/permissions/engine.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/permissions/rules.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/providers/__init__.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/providers/anthropic.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/providers/base.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/providers/gemini.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/providers/mock.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/providers/openai_compat.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/providers/pricing.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/providers/registry.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/providers/sse.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/sessions/__init__.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/sessions/models.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/sessions/store.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tools/__init__.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tools/ask.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tools/base.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tools/bash.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tools/builtin.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tools/edit.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tools/glob.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tools/grep.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tools/read.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tools/runner.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tools/shell.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tools/skill.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tools/task.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tools/textio.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tools/todo.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tools/webfetch.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tools/websearch.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tools/write.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tui/__init__.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tui/app.tcss +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tui/bridge.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tui/config_screen.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tui/mcp_screen.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tui/session_screen.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tui/widgets/__init__.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tui/widgets/permission.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tui/widgets/status.py +0 -0
- {turnloop-0.1.6 → turnloop-0.1.8}/turnloop/tui/widgets/transcript.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: turnloop
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: A local-first agentic coding harness: streaming tool loop, permission gating, context compaction, subagents, and any LLM provider.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Pranesh-2005/turnloop
|
|
6
6
|
Author: Pranesh
|
|
@@ -49,7 +49,7 @@ tl experiment run smoke # a measurement run, offline and free
|
|
|
49
49
|
|
|
50
50
|
Four runtime dependencies: `textual`, `pydantic`, `httpx`, `pyyaml`. No vendor
|
|
51
51
|
SDKs, no agent framework — the loop is the point, so the loop is written here.
|
|
52
|
-
~20,000 lines of Python,
|
|
52
|
+
~20,000 lines of Python, 346 tests, no network in the default test run.
|
|
53
53
|
|
|
54
54
|
---
|
|
55
55
|
|
|
@@ -117,7 +117,7 @@ point.
|
|
|
117
117
|
Verify:
|
|
118
118
|
|
|
119
119
|
```bash
|
|
120
|
-
tl --version # turnloop 0.1.
|
|
120
|
+
tl --version # turnloop 0.1.8
|
|
121
121
|
tl doctor # every provider, key presence, shell, context budget
|
|
122
122
|
```
|
|
123
123
|
|
|
@@ -1023,7 +1023,7 @@ one-line edit in a CRLF checkout does not produce a whole-file diff.
|
|
|
1023
1023
|
## Testing
|
|
1024
1024
|
|
|
1025
1025
|
```bash
|
|
1026
|
-
pytest #
|
|
1026
|
+
pytest # 346 tests, no network
|
|
1027
1027
|
ruff check turnloop
|
|
1028
1028
|
mypy turnloop
|
|
1029
1029
|
```
|
|
@@ -1035,8 +1035,9 @@ mypy turnloop
|
|
|
1035
1035
|
| `test_providers.py` | 35 | adapters vs recorded `.sse`, image blocks, thought signatures |
|
|
1036
1036
|
| `test_tools_files.py` | 28 | Read/Write/Edit/Glob/Grep, encodings, newlines, image detection |
|
|
1037
1037
|
| `test_hooks_mcp_commands.py` | 28 | lifecycle hooks, MCP client, slash commands |
|
|
1038
|
-
| `test_tui.py` |
|
|
1038
|
+
| `test_tui.py` | 25 | Textual snapshots, `/config` and `/mcp add` driven through a real app |
|
|
1039
1039
|
| `test_config.py` | 23 | layering, env overrides, presets, narrow-console guard |
|
|
1040
|
+
| `test_skills_install.py` | 27 | frontmatter validation, GitHub resolution, path dedupe, EOF on every prompt, drive-root refusal |
|
|
1040
1041
|
| `test_loop.py` | 23 | streaming, truncation, iteration cap |
|
|
1041
1042
|
| `test_compaction.py` | 16 | three tiers, tool_use/tool_result invariant |
|
|
1042
1043
|
| `test_configio.py` | 15 | minimal-diff writes, atomic save, secret-bearing MCP targets, the settings deny rules |
|
|
@@ -1107,8 +1108,9 @@ just because it is on disk.
|
|
|
1107
1108
|
### Editing configuration
|
|
1108
1109
|
|
|
1109
1110
|
`tl config --edit` edits the provider table, permission mode, tool verbosity, iteration
|
|
1110
|
-
cap, search backend, memory, and the three rule lists. `tl mcp` manages MCP servers.
|
|
1111
|
-
|
|
1111
|
+
cap, search backend, memory, and the three rule lists. `tl mcp` manages MCP servers.
|
|
1112
|
+
`tl skills add/list/remove` manages skills. The config and MCP screens open inside a
|
|
1113
|
+
running session as `/config` and `/mcp add`.
|
|
1112
1114
|
|
|
1113
1115
|
**Adding a provider** is a form for `kind`, `model`, `base_url`, and `api_key_env`, with
|
|
1114
1116
|
a live ✓/✗ showing whether that environment variable is currently set. Capabilities are
|
|
@@ -1185,6 +1187,37 @@ unavailable" and never blocks the loop. Connections are lazy and every call is b
|
|
|
1185
1187
|
None of that helps if you install a server that does exactly what it says. Read what you
|
|
1186
1188
|
add.
|
|
1187
1189
|
|
|
1190
|
+
### Skills are a trust decision too
|
|
1191
|
+
|
|
1192
|
+
`tl skills add <owner/repo>` resolves a GitHub repo shorthand, a repo URL, or a direct
|
|
1193
|
+
raw URL to a `SKILL.md`, and installs it to `.turnloop/skills/<name>/` (or
|
|
1194
|
+
`~/.turnloop/skills/` with `--user`). The same reasoning as the MCP section above
|
|
1195
|
+
applies without modification: a skill's body is not metadata, it is text that gets
|
|
1196
|
+
loaded straight into the model's context the moment the model decides the skill
|
|
1197
|
+
applies (`commands/loader.py`). A hostile `SKILL.md` can instruct the model exactly
|
|
1198
|
+
like a hostile MCP tool description can. `add` prints the source URL and the
|
|
1199
|
+
description and asks for confirmation before writing anything; `--yes` skips waiting
|
|
1200
|
+
for that confirmation but never skips printing it, and never skips printing what got
|
|
1201
|
+
installed and from where.
|
|
1202
|
+
|
|
1203
|
+
Before writing, `add` parses the frontmatter with the same `parse_frontmatter` the
|
|
1204
|
+
loader uses and requires a non-empty `description` — the exact rule `load_skills`
|
|
1205
|
+
enforces when deciding what to advertise (see "A skill that failed to parse vanished
|
|
1206
|
+
with no error" below). A `SKILL.md` that would silently vanish once installed is
|
|
1207
|
+
refused before it is ever written, rather than discovered later as "the skill isn't
|
|
1208
|
+
there."
|
|
1209
|
+
|
|
1210
|
+
**Importing from Claude Code.** If `~/.claude/skills/*/SKILL.md` exist and turnloop
|
|
1211
|
+
has never asked about them, one line appears once, in the TUI, pointing at
|
|
1212
|
+
`tl skills import` — not a prompt that blocks startup, and not a silent copy.
|
|
1213
|
+
Importing computes the token cost of each candidate with `rough_tokens` before you
|
|
1214
|
+
choose (34 skills advertise for about 2,961 tokens of permanent system-prompt
|
|
1215
|
+
overhead — most of a session's `CONFIG_LAYOUT` segment on its own), lets you import
|
|
1216
|
+
any subset, and copies the chosen files into turnloop's own skills directory rather
|
|
1217
|
+
than reading `~/.claude/skills` at runtime — turnloop stays uncoupled from another
|
|
1218
|
+
tool's directory layout. Whether you say yes or no, the question is recorded in the
|
|
1219
|
+
user-level settings file so it is never asked again.
|
|
1220
|
+
|
|
1188
1221
|
---
|
|
1189
1222
|
|
|
1190
1223
|
## Limitations
|
|
@@ -20,7 +20,7 @@ tl experiment run smoke # a measurement run, offline and free
|
|
|
20
20
|
|
|
21
21
|
Four runtime dependencies: `textual`, `pydantic`, `httpx`, `pyyaml`. No vendor
|
|
22
22
|
SDKs, no agent framework — the loop is the point, so the loop is written here.
|
|
23
|
-
~20,000 lines of Python,
|
|
23
|
+
~20,000 lines of Python, 346 tests, no network in the default test run.
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
@@ -88,7 +88,7 @@ point.
|
|
|
88
88
|
Verify:
|
|
89
89
|
|
|
90
90
|
```bash
|
|
91
|
-
tl --version # turnloop 0.1.
|
|
91
|
+
tl --version # turnloop 0.1.8
|
|
92
92
|
tl doctor # every provider, key presence, shell, context budget
|
|
93
93
|
```
|
|
94
94
|
|
|
@@ -994,7 +994,7 @@ one-line edit in a CRLF checkout does not produce a whole-file diff.
|
|
|
994
994
|
## Testing
|
|
995
995
|
|
|
996
996
|
```bash
|
|
997
|
-
pytest #
|
|
997
|
+
pytest # 346 tests, no network
|
|
998
998
|
ruff check turnloop
|
|
999
999
|
mypy turnloop
|
|
1000
1000
|
```
|
|
@@ -1006,8 +1006,9 @@ mypy turnloop
|
|
|
1006
1006
|
| `test_providers.py` | 35 | adapters vs recorded `.sse`, image blocks, thought signatures |
|
|
1007
1007
|
| `test_tools_files.py` | 28 | Read/Write/Edit/Glob/Grep, encodings, newlines, image detection |
|
|
1008
1008
|
| `test_hooks_mcp_commands.py` | 28 | lifecycle hooks, MCP client, slash commands |
|
|
1009
|
-
| `test_tui.py` |
|
|
1009
|
+
| `test_tui.py` | 25 | Textual snapshots, `/config` and `/mcp add` driven through a real app |
|
|
1010
1010
|
| `test_config.py` | 23 | layering, env overrides, presets, narrow-console guard |
|
|
1011
|
+
| `test_skills_install.py` | 27 | frontmatter validation, GitHub resolution, path dedupe, EOF on every prompt, drive-root refusal |
|
|
1011
1012
|
| `test_loop.py` | 23 | streaming, truncation, iteration cap |
|
|
1012
1013
|
| `test_compaction.py` | 16 | three tiers, tool_use/tool_result invariant |
|
|
1013
1014
|
| `test_configio.py` | 15 | minimal-diff writes, atomic save, secret-bearing MCP targets, the settings deny rules |
|
|
@@ -1078,8 +1079,9 @@ just because it is on disk.
|
|
|
1078
1079
|
### Editing configuration
|
|
1079
1080
|
|
|
1080
1081
|
`tl config --edit` edits the provider table, permission mode, tool verbosity, iteration
|
|
1081
|
-
cap, search backend, memory, and the three rule lists. `tl mcp` manages MCP servers.
|
|
1082
|
-
|
|
1082
|
+
cap, search backend, memory, and the three rule lists. `tl mcp` manages MCP servers.
|
|
1083
|
+
`tl skills add/list/remove` manages skills. The config and MCP screens open inside a
|
|
1084
|
+
running session as `/config` and `/mcp add`.
|
|
1083
1085
|
|
|
1084
1086
|
**Adding a provider** is a form for `kind`, `model`, `base_url`, and `api_key_env`, with
|
|
1085
1087
|
a live ✓/✗ showing whether that environment variable is currently set. Capabilities are
|
|
@@ -1156,6 +1158,37 @@ unavailable" and never blocks the loop. Connections are lazy and every call is b
|
|
|
1156
1158
|
None of that helps if you install a server that does exactly what it says. Read what you
|
|
1157
1159
|
add.
|
|
1158
1160
|
|
|
1161
|
+
### Skills are a trust decision too
|
|
1162
|
+
|
|
1163
|
+
`tl skills add <owner/repo>` resolves a GitHub repo shorthand, a repo URL, or a direct
|
|
1164
|
+
raw URL to a `SKILL.md`, and installs it to `.turnloop/skills/<name>/` (or
|
|
1165
|
+
`~/.turnloop/skills/` with `--user`). The same reasoning as the MCP section above
|
|
1166
|
+
applies without modification: a skill's body is not metadata, it is text that gets
|
|
1167
|
+
loaded straight into the model's context the moment the model decides the skill
|
|
1168
|
+
applies (`commands/loader.py`). A hostile `SKILL.md` can instruct the model exactly
|
|
1169
|
+
like a hostile MCP tool description can. `add` prints the source URL and the
|
|
1170
|
+
description and asks for confirmation before writing anything; `--yes` skips waiting
|
|
1171
|
+
for that confirmation but never skips printing it, and never skips printing what got
|
|
1172
|
+
installed and from where.
|
|
1173
|
+
|
|
1174
|
+
Before writing, `add` parses the frontmatter with the same `parse_frontmatter` the
|
|
1175
|
+
loader uses and requires a non-empty `description` — the exact rule `load_skills`
|
|
1176
|
+
enforces when deciding what to advertise (see "A skill that failed to parse vanished
|
|
1177
|
+
with no error" below). A `SKILL.md` that would silently vanish once installed is
|
|
1178
|
+
refused before it is ever written, rather than discovered later as "the skill isn't
|
|
1179
|
+
there."
|
|
1180
|
+
|
|
1181
|
+
**Importing from Claude Code.** If `~/.claude/skills/*/SKILL.md` exist and turnloop
|
|
1182
|
+
has never asked about them, one line appears once, in the TUI, pointing at
|
|
1183
|
+
`tl skills import` — not a prompt that blocks startup, and not a silent copy.
|
|
1184
|
+
Importing computes the token cost of each candidate with `rough_tokens` before you
|
|
1185
|
+
choose (34 skills advertise for about 2,961 tokens of permanent system-prompt
|
|
1186
|
+
overhead — most of a session's `CONFIG_LAYOUT` segment on its own), lets you import
|
|
1187
|
+
any subset, and copies the chosen files into turnloop's own skills directory rather
|
|
1188
|
+
than reading `~/.claude/skills` at runtime — turnloop stays uncoupled from another
|
|
1189
|
+
tool's directory layout. Whether you say yes or no, the question is recorded in the
|
|
1190
|
+
user-level settings file so it is never asked again.
|
|
1191
|
+
|
|
1159
1192
|
---
|
|
1160
1193
|
|
|
1161
1194
|
## Limitations
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "turnloop"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.8"
|
|
8
8
|
description = "A local-first agentic coding harness: streaming tool loop, permission gating, context compaction, subagents, and any LLM provider."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|