irc-lens 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 (66) hide show
  1. irc_lens-0.1.0/.github/workflows/ci.yml +47 -0
  2. irc_lens-0.1.0/.github/workflows/publish.yml +114 -0
  3. irc_lens-0.1.0/.gitignore +225 -0
  4. irc_lens-0.1.0/CITATION.md +23 -0
  5. irc_lens-0.1.0/CLAUDE.md +88 -0
  6. irc_lens-0.1.0/LICENSE +21 -0
  7. irc_lens-0.1.0/PKG-INFO +97 -0
  8. irc_lens-0.1.0/README.md +46 -0
  9. irc_lens-0.1.0/docs/architecture.md +245 -0
  10. irc_lens-0.1.0/docs/cli.md +166 -0
  11. irc_lens-0.1.0/docs/playwright.md +141 -0
  12. irc_lens-0.1.0/docs/slash-commands.md +68 -0
  13. irc_lens-0.1.0/docs/sse-events.md +107 -0
  14. irc_lens-0.1.0/docs/superpowers/plans/2026-04-27-irc-lens-build-plan.md +348 -0
  15. irc_lens-0.1.0/docs/superpowers/specs/2026-04-27-irc-lens-handover-design.md +365 -0
  16. irc_lens-0.1.0/pyproject.toml +62 -0
  17. irc_lens-0.1.0/src/irc_lens/__init__.py +12 -0
  18. irc_lens-0.1.0/src/irc_lens/__main__.py +10 -0
  19. irc_lens-0.1.0/src/irc_lens/cli/__init__.py +119 -0
  20. irc_lens-0.1.0/src/irc_lens/cli/_commands/__init__.py +0 -0
  21. irc_lens-0.1.0/src/irc_lens/cli/_commands/explain.py +38 -0
  22. irc_lens-0.1.0/src/irc_lens/cli/_commands/learn.py +110 -0
  23. irc_lens-0.1.0/src/irc_lens/cli/_commands/overview.py +180 -0
  24. irc_lens-0.1.0/src/irc_lens/cli/_commands/serve.py +238 -0
  25. irc_lens-0.1.0/src/irc_lens/cli/_errors.py +36 -0
  26. irc_lens-0.1.0/src/irc_lens/cli/_output.py +53 -0
  27. irc_lens-0.1.0/src/irc_lens/commands.py +103 -0
  28. irc_lens-0.1.0/src/irc_lens/explain/__init__.py +25 -0
  29. irc_lens-0.1.0/src/irc_lens/explain/catalog.py +144 -0
  30. irc_lens-0.1.0/src/irc_lens/irc/__init__.py +21 -0
  31. irc_lens-0.1.0/src/irc_lens/irc/buffer.py +85 -0
  32. irc_lens-0.1.0/src/irc_lens/irc/message.py +135 -0
  33. irc_lens-0.1.0/src/irc_lens/irc/transport.py +360 -0
  34. irc_lens-0.1.0/src/irc_lens/seed.py +284 -0
  35. irc_lens-0.1.0/src/irc_lens/session.py +761 -0
  36. irc_lens-0.1.0/src/irc_lens/static/lens.css +142 -0
  37. irc_lens-0.1.0/src/irc_lens/static/lens.js +89 -0
  38. irc_lens-0.1.0/src/irc_lens/static/vendor/htmx.min.js +1 -0
  39. irc_lens-0.1.0/src/irc_lens/static/vendor/sse.js +290 -0
  40. irc_lens-0.1.0/src/irc_lens/templates/_chat_line.html.j2 +10 -0
  41. irc_lens-0.1.0/src/irc_lens/templates/_info.html.j2 +56 -0
  42. irc_lens-0.1.0/src/irc_lens/templates/_sidebar.html.j2 +26 -0
  43. irc_lens-0.1.0/src/irc_lens/templates/index.html.j2 +50 -0
  44. irc_lens-0.1.0/src/irc_lens/web/__init__.py +14 -0
  45. irc_lens-0.1.0/src/irc_lens/web/app.py +51 -0
  46. irc_lens-0.1.0/src/irc_lens/web/events.py +38 -0
  47. irc_lens-0.1.0/src/irc_lens/web/render.py +54 -0
  48. irc_lens-0.1.0/src/irc_lens/web/routes.py +160 -0
  49. irc_lens-0.1.0/tests/README.md +50 -0
  50. irc_lens-0.1.0/tests/_agentirc_server.py +166 -0
  51. irc_lens-0.1.0/tests/conftest.py +103 -0
  52. irc_lens-0.1.0/tests/fixtures/basic.yaml +15 -0
  53. irc_lens-0.1.0/tests/test_cli.py +163 -0
  54. irc_lens-0.1.0/tests/test_commands.py +118 -0
  55. irc_lens-0.1.0/tests/test_e2e_http.py +241 -0
  56. irc_lens-0.1.0/tests/test_e2e_playwright.py +125 -0
  57. irc_lens-0.1.0/tests/test_irc_smoke.py +193 -0
  58. irc_lens-0.1.0/tests/test_lens_js.py +70 -0
  59. irc_lens-0.1.0/tests/test_render.py +157 -0
  60. irc_lens-0.1.0/tests/test_seed.py +249 -0
  61. irc_lens-0.1.0/tests/test_serve_cli.py +285 -0
  62. irc_lens-0.1.0/tests/test_session_dispatch.py +347 -0
  63. irc_lens-0.1.0/tests/test_session_unit.py +571 -0
  64. irc_lens-0.1.0/tests/test_web_events.py +228 -0
  65. irc_lens-0.1.0/tests/test_web_skeleton.py +206 -0
  66. irc_lens-0.1.0/uv.lock +1291 -0
@@ -0,0 +1,47 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ permissions:
13
+ contents: read
14
+ steps:
15
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
16
+
17
+ - uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
18
+
19
+ - run: uv python install 3.12
20
+
21
+ - run: uv venv
22
+
23
+ - run: uv pip install -e ".[dev]"
24
+
25
+ - name: pytest
26
+ run: uv run pytest -v
27
+
28
+ playwright:
29
+ runs-on: ubuntu-latest
30
+ permissions:
31
+ contents: read
32
+ steps:
33
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
34
+
35
+ - uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
36
+
37
+ - run: uv python install 3.12
38
+
39
+ - run: uv venv
40
+
41
+ - run: uv pip install -e ".[dev]"
42
+
43
+ - name: Install chromium for pytest-playwright
44
+ run: uv run playwright install --with-deps chromium
45
+
46
+ - name: pytest -m playwright
47
+ run: uv run pytest -m playwright -v
@@ -0,0 +1,114 @@
1
+ name: Publish to PyPI
2
+
3
+ # PR → TestPyPI (`.devN` build, `testpypi` environment).
4
+ # push to main → PyPI (`x.y.z` build, `pypi` environment).
5
+ #
6
+ # The path filter keeps doc-only PRs from churning TestPyPI; bumps to
7
+ # the workflow itself still trigger so changes get validated before
8
+ # they land. Trusted-publishing means no long-lived tokens — both
9
+ # environments are pre-provisioned with PyPI Trusted Publishers
10
+ # pointing at this repo.
11
+
12
+ on:
13
+ push:
14
+ branches: [main]
15
+ # Push triggers `publish` against PyPI, which rejects re-uploads of
16
+ # an existing version. Gate it on `pyproject.toml` so the publish
17
+ # only fires when `[project] version` bumps. `src/**` changes still
18
+ # run on PRs (test-publish) and are validated by `ci.yml`'s `test`
19
+ # job on push to main; we just don't try to re-publish them.
20
+ paths:
21
+ - "pyproject.toml"
22
+ - ".github/workflows/publish.yml"
23
+ pull_request:
24
+ branches: [main]
25
+ # PRs are safe to run on broader changes — `test-publish` mints a
26
+ # unique `.devN` per run_number so re-uploads can't collide.
27
+ paths:
28
+ - "pyproject.toml"
29
+ - "src/**"
30
+ - ".github/workflows/publish.yml"
31
+
32
+ jobs:
33
+ test:
34
+ runs-on: ubuntu-latest
35
+ permissions:
36
+ contents: read
37
+ steps:
38
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
39
+
40
+ - uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
41
+
42
+ - run: uv python install 3.12
43
+
44
+ - run: uv venv
45
+
46
+ - run: uv pip install -e ".[dev]"
47
+
48
+ - name: pytest
49
+ run: uv run pytest -v
50
+
51
+ test-publish:
52
+ if: github.event_name == 'pull_request'
53
+ needs: test
54
+ runs-on: ubuntu-latest
55
+ environment: testpypi
56
+ permissions:
57
+ contents: read
58
+ id-token: write
59
+ steps:
60
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
61
+
62
+ - uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
63
+
64
+ - run: uv python install 3.12
65
+
66
+ - name: Mint dev version
67
+ # Trusted-publishing won't accept the same version twice, so
68
+ # every PR build needs a unique suffix. `github.run_number` is
69
+ # monotonic per workflow file and survives re-runs of the same
70
+ # workflow run (those reuse the run_attempt, not run_number).
71
+ # Use the uv-installed 3.12 interpreter explicitly so this step
72
+ # doesn't depend on whatever `python` the runner happens to
73
+ # ship (tomllib is 3.11+).
74
+ run: |
75
+ BASE=$(uv run --python 3.12 python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
76
+ DEV_VERSION="${BASE}.dev${{ github.run_number }}"
77
+ sed -i "s/^version = .*/version = \"${DEV_VERSION}\"/" pyproject.toml
78
+ echo "DEV_VERSION=${DEV_VERSION}" >> "$GITHUB_ENV"
79
+ echo "Publishing ${DEV_VERSION} to TestPyPI"
80
+
81
+ - name: Build and publish to TestPyPI
82
+ run: |
83
+ uv build
84
+ uv publish \
85
+ --publish-url https://test.pypi.org/legacy/ \
86
+ --trusted-publishing always \
87
+ --check-url https://test.pypi.org/simple/
88
+
89
+ - name: Print install command
90
+ if: always()
91
+ run: |
92
+ echo "::notice::Test with: pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ irc-lens==${DEV_VERSION}"
93
+
94
+ publish:
95
+ if: github.event_name == 'push'
96
+ needs: test
97
+ runs-on: ubuntu-latest
98
+ environment: pypi
99
+ permissions:
100
+ contents: read
101
+ id-token: write
102
+ steps:
103
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
104
+
105
+ - uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
106
+
107
+ - run: uv python install 3.12
108
+
109
+ - name: Build and publish to PyPI
110
+ run: |
111
+ uv build
112
+ uv publish \
113
+ --trusted-publishing always \
114
+ --check-url https://pypi.org/simple/
@@ -0,0 +1,225 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ # Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ # poetry.lock
109
+ # poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ # pdm.lock
116
+ # pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ # pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ # .idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+ # Temporary file for partial code execution
204
+ tempCodeRunnerFile.py
205
+
206
+ # Ruff stuff:
207
+ .ruff_cache/
208
+
209
+ # PyPI configuration file
210
+ .pypirc
211
+
212
+ # Marimo
213
+ marimo/_static/
214
+ marimo/_lsp/
215
+ __marimo__/
216
+
217
+ # Streamlit
218
+ .streamlit/secrets.toml
219
+
220
+ # Cited references from afi cli cite (regenerate locally; never committed)
221
+ .afi/
222
+
223
+ # Per-user Claude Code config (settings, scheduled tasks, project-local
224
+ # skills, etc. — none of these belong in the repo).
225
+ .claude/
@@ -0,0 +1,23 @@
1
+ # Citation
2
+
3
+ `irc-lens` follows Culture's **cite-don't-import** rule. Cited code is
4
+ copied (and adapted) from upstream sources, then carried in this repo as
5
+ its own source. We track the upstream commit each file was lifted from
6
+ so future updates can be diffed against it deliberately.
7
+
8
+ ## Sources
9
+
10
+ | Local path | Source repo | Source path | Source SHA | Adaptation |
11
+ | --- | --- | --- | --- | --- |
12
+ | `src/irc_lens/irc/buffer.py` | `agentculture/culture` | `packages/agent-harness/message_buffer.py` | `57d3ba8` | One additive divergence: `MessageBuffer.add` accepts an optional `timestamp: float \| None = None` (defaults to `time.time()` when omitted, preserving upstream behaviour for every existing caller). The Phase 8 `--seed` loader uses this to preload fixture messages with fixed clocks so Playwright tests can assert on stable timestamps without reaching into `_buffers`. Safe to upstream. |
13
+ | `src/irc_lens/irc/message.py` | `agentculture/culture` | `culture/protocol/message.py` | `57d3ba8` | Byte-faithful copy. The IRC line parser is shared by every AgentIRC client; the lens needs it to decode wire bytes in the read loop. |
14
+ | `src/irc_lens/irc/transport.py` | `agentculture/culture` | `packages/agent-harness/irc_transport.py` | `57d3ba8` | Imports rewired to `irc_lens.irc.*`; `culture.aio.maybe_await` inlined as `_maybe_await` (3 lines). **`CAP REQ :message-tags` removed** per the spec — the lens doesn't render IRCv3 tags (precedent: `culture/console/client.py:50-55`). **All telemetry/OTEL infrastructure removed** — the `_span` helper, the `tracer`/`metrics`/`backend` constructor kwargs, the traceparent injection in `send_raw`, and the inbound traceparent extraction in `_handle`. The lens has no agent loop and the spec excludes telemetry. Persistent-connection + read-loop shape preserved. `system-` event filter retained. **`add_listener(command, cb)` added** so `Session` can observe inbound PRIVMSG/JOIN/PART without replacing the buffer-add handlers in `_cmd_handlers` — the dict has one slot per command and the existing handlers do load-bearing work; chaining via a parallel listener list keeps both responsibilities additive (Phase 5). Listeners run after the primary handler; exceptions are logged + swallowed so a misbehaving observer can't break the read loop. **Two upstream bug-fixes + one minor improvement carried (flag for back-port to culture):** (a) `disconnect()` now catches `OSError` on `wait_closed()` instead of just `ConnectionError`, matching the QUIT-send handling above it (`ConnectionResetError` is one of several transport errors that can raise from `wait_closed`). (b) `_read_loop` now buffers as bytes and decodes per complete line; per-chunk `decode("utf-8", errors="replace")` corrupts multibyte sequences split across `recv` boundaries with U+FFFD. (c) `_reconnect` now wraps the retry loop in `try/finally` so `self._reconnecting` is released even if the loop exits because `_should_run` flipped or an unexpected exception escapes — upstream only releases the gate on successful return, so the gate could leak. The `except OSError:` itself is unchanged from upstream and correct: `ConnectionError` (which `_do_connect` raises) is a subclass of `OSError` in Python 3.3+, so the bare `OSError` catch handles it. |
15
+ | `src/irc_lens/commands.py` | `agentculture/culture` | `culture/console/commands.py` | `57d3ba8` | Byte-faithful copy. |
16
+
17
+ ## Refresh
18
+
19
+ To refresh a cited file against a newer upstream, diff the new upstream
20
+ against the source SHA recorded above, port intentional changes, and
21
+ update the SHA in this table. Do not just overwrite — culture's
22
+ `packages/agent-harness` and `culture/console` evolve together with
23
+ agent-runtime concerns that `irc-lens` does not need.
@@ -0,0 +1,88 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What this repo is
6
+
7
+ `irc-lens` is the lens CLI for **AgentIRC** in the **Culture** ecosystem (a sibling repo named `culture` — typically checked out alongside this one). The repo is a fresh skeleton — at the time of writing there is no source code yet. Tracked files at the top level:
8
+
9
+ - `README.md` — one-line purpose statement.
10
+ - `LICENSE` — project license.
11
+ - `CLAUDE.md` — this file.
12
+ - `.gitignore` — standard Python ignores plus `.afi/` and `.claude/settings.local.json`.
13
+
14
+ Not tracked, but expected to exist locally:
15
+
16
+ - `.afi/reference/python-cli/` — generated by `afi cli cite python-cli` (see next section). Cited, not imported; ignored by git on purpose.
17
+ - `.claude/settings.local.json` — per-user Claude Code settings (allowlist etc.). Local-only.
18
+
19
+ The first non-trivial task in this repo will be to bootstrap the Python package by integrating the `.afi/reference/python-cli/` pattern. Generate that pattern locally and read it *before* writing any CLI scaffolding from scratch.
20
+
21
+ ## The `.afi/reference/python-cli/` reference (regenerate locally before reading)
22
+
23
+ This reference is **not committed**. It is produced on demand by `afi cli cite` (from the `citation-cli` / formerly `assimilai` project — typically a sibling checkout). Regenerate it with:
24
+
25
+ ```bash
26
+ afi cli cite python-cli # writes .afi/reference/python-cli/
27
+ ```
28
+
29
+ If `afi` isn't on your `PATH`, follow the install instructions in the `citation-cli` repo. Once `.afi/reference/python-cli/AGENT.md` and `MANIFEST.json` exist locally, defer to them — they are the authoritative spec for the **agent-first Python CLI** pattern this project conforms to. The summary below is just orientation; if it ever conflicts with the cited material, the cited material wins.
30
+
31
+ ### Token substitution
32
+
33
+ `{{project_name}}`, `{{slug}}`, `{{module}}` are placeholders. They are **not** substituted in the reference itself — substitute them when you copy a file into the host package. For this repo the natural choices are:
34
+
35
+ - `{{project_name}}` → `irc-lens`
36
+ - `{{slug}}` / `{{module}}` → `irc_lens`
37
+
38
+ ### File roles
39
+
40
+ `MANIFEST.json` tags each file as `stable-contract` or `shape-adapt`:
41
+
42
+ - **`stable-contract`** — copy verbatim, then token-substitute. Don't reshape unless the host already has equivalents. Covers `cli/_errors.py` (`AfiError`, exit codes), `cli/_output.py` (stdout/stderr split, `--json`), `cli/_commands/explain.py`, and `explain/` (catalog resolver).
43
+ - **`shape-adapt`** — keep the structure, rewrite the content for the host tool. Covers `cli/__init__.py` (parser + `_dispatch`, including the `_ArgumentParser` override and try/except), `cli/_commands/learn.py` (TEXT body + JSON payload), `explain/catalog.py`, the package `__init__.py` / `__main__.py`, and `tests/test_cli.py`.
44
+
45
+ ### Hard contracts to preserve
46
+
47
+ These are checked by `afi cli verify` and must not be weakened when shape-adapting:
48
+
49
+ 1. **Exit-code policy** — `0` success, `1` user error, `2` env error, `3+` reserved. All failures raise `AfiError`; the dispatcher catches and exits with `err.code`. No Python traceback ever leaks.
50
+ 2. **stdout/stderr split** — results to stdout, errors and diagnostics to stderr, even in `--json` mode. The streams are never mixed.
51
+ 3. **Errors have shape `{code, message, remediation}`** — text-mode errors render as `error: <msg>\nhint: <remediation>`. The `hint:` prefix is required by the rubric.
52
+ 4. **`_ArgumentParser` override** — argparse errors must route through `emit_error` so unknown verbs/flags exit with `error:` + `hint:` and no traceback.
53
+ 5. **Globals `learn`, `explain`, `overview` exist at the top level** — not nested under a noun. New command groups are registered as siblings via `register(sub)` in `cli/__init__.py` at the marked location. Every noun group with action-verbs must also expose its own `overview` verb.
54
+ 6. **`learn` output rubric** — stdout ≥ 200 chars, mentions purpose, commands, exit codes, `--json`, and `explain`. `learn --json` is parseable with stderr clean.
55
+ 7. **`overview` is descriptive, not verifying** — `overview <bogus-path>` exits **0** with a warning section. Hard-failing on a missing target is `afi cli verify`'s job, not `overview`'s.
56
+
57
+ ### `afi cli verify` rubric bundles
58
+
59
+ The six bundles checked by the verifier (run from the host project root once code exists):
60
+
61
+ 1. **Structure** — `pyproject.toml` with `[project.scripts]`, `tests/` dir, `<tool> --help` exits 0.
62
+ 2. **Learnability** — `<tool> learn` exits 0, stdout ≥ 200 chars, mentions purpose / commands / exit codes / `--json` / `explain`.
63
+ 3. **JSON** — `<tool> learn --json` parseable; stderr clean on success; `<tool> explain --json` works.
64
+ 4. **Errors** — bogus verb exits non-zero with a `hint:` line and no Python traceback.
65
+ 5. **Explain** — `explain`, `explain <tool>`, and bogus-path-failure with hint all work.
66
+ 6. **Overview** — global `<tool> overview` exits 0; `<tool> overview --json` returns `{subject, path, sections}`; every noun group with action-verbs also exposes an `overview` verb (e.g. `<tool> cli overview`); `<tool> overview <bogus-path>` exits **0** with a warning section (overview is descriptive, not verifying).
67
+
68
+ The `python-cli` reference template predates the `overview` bundle. The bootstrap phase explicitly adds an `overview` global + a minimal `cli` noun with `cli overview`. New noun groups added later must follow the same rule.
69
+
70
+ After integration completes and the rubric passes, the reference can be removed (`rm -rf .afi/reference/`) or refreshed with `afi cli cite`.
71
+
72
+ ## Workspace context (Culture / AgentIRC)
73
+
74
+ In this workspace's standard layout, `irc-lens` is checked out alongside `culture` and `citation-cli` as sibling directories. The exact location varies by contributor; what matters is the relationship, not the absolute path. Worth knowing when scoping changes:
75
+
76
+ - **Culture** is an IRC-based agent mesh; AgentIRC is the protocol/IRCd component. `irc-lens` is its **lens** CLI — read-only-ish observability/inspection tooling, by name. Confirm exact scope against the parent project before adding write paths.
77
+ - **citation-cli** (`afi`) provided the `.afi/reference/` here. The pattern is *cited, not imported* — this project owns its copy and may modify it, but divergences from the rubric should be deliberate.
78
+ - **All-backends rule** (Culture): if a feature lands on one agent backend (`claude` / `codex` / `copilot` / `acp`), it must be propagated to all of them. If `irc-lens` grows backend-aware code, that rule applies here too.
79
+
80
+ ## Tooling expectations once code exists
81
+
82
+ If a workspace-level `CLAUDE.md` is present in the parent directory of your checkouts, it sets the defaults. None of this is enforced *yet* because there's no code, but the next contributor should expect:
83
+
84
+ - **`uv`** for dependency management: `uv venv && uv pip install -e ".[dev]"`, then `pytest`.
85
+ - **Linting**: `flake8`, `pylint`, `bandit -r src/`, `black`, `isort`. Markdown via `markdownlint-cli2`.
86
+ - **Versioning**: a single source of truth in `pyproject.toml` / `irc_lens/__init__.py`. Bump before PRs.
87
+
88
+ When wiring `pyproject.toml`, register the CLI under `[project.scripts]` as `irc-lens = "irc_lens.cli:main"` (matching the reference's `cli.main`).
irc_lens-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 agentculture
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,97 @@
1
+ Metadata-Version: 2.4
2
+ Name: irc-lens
3
+ Version: 0.1.0
4
+ Summary: Reactive web console for AgentIRC — Playwright-driveable lens for the Culture mesh.
5
+ Project-URL: Homepage, https://github.com/agentculture/irc-lens
6
+ Project-URL: Issues, https://github.com/agentculture/irc-lens/issues
7
+ Author-email: Ori Nachum <ori.nachum@gmail.com>
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 agentculture
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Keywords: agent,agentirc,console,culture,htmx,irc,lens,sse
31
+ Classifier: Development Status :: 3 - Alpha
32
+ Classifier: Intended Audience :: Developers
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Programming Language :: Python :: 3.11
36
+ Classifier: Programming Language :: Python :: 3.12
37
+ Classifier: Programming Language :: Python :: 3.13
38
+ Classifier: Topic :: Communications :: Chat :: Internet Relay Chat
39
+ Requires-Python: >=3.11
40
+ Requires-Dist: aiohttp>=3.9
41
+ Requires-Dist: jinja2>=3.1
42
+ Requires-Dist: pyyaml>=6.0
43
+ Provides-Extra: dev
44
+ Requires-Dist: playwright>=1.40; extra == 'dev'
45
+ Requires-Dist: pytest-aiohttp>=1.0; extra == 'dev'
46
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
47
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
48
+ Requires-Dist: pytest-playwright>=0.5; extra == 'dev'
49
+ Requires-Dist: pytest>=8.0; extra == 'dev'
50
+ Description-Content-Type: text/markdown
51
+
52
+ # irc-lens
53
+
54
+ `irc-lens` is the agent-driven web console for **AgentIRC** in the
55
+ [Culture](https://github.com/agentculture/culture) ecosystem. Where
56
+ the existing Textual TUI requires a human at a terminal,
57
+ `irc-lens` re-implements the same console as a localhost aiohttp
58
+ app (HTMX + SSE, server-rendered fragments) so a Playwright agent
59
+ or human browser can drive it deterministically.
60
+
61
+ ## Quickstart
62
+
63
+ ```bash
64
+ pip install irc-lens
65
+ irc-lens serve --host 127.0.0.1 --port 6667 --nick lens --open
66
+ ```
67
+
68
+ The `--open` flag launches the default browser at the printed
69
+ URL. Quit with Ctrl-C.
70
+
71
+ ## Develop
72
+
73
+ ```bash
74
+ git clone https://github.com/agentculture/irc-lens && cd irc-lens
75
+ uv venv && uv pip install -e ".[dev]"
76
+ uv run pytest -v # default suite
77
+ uv run playwright install chromium # one-time
78
+ uv run pytest -m playwright -v # browser e2e
79
+ ```
80
+
81
+ ## Docs
82
+
83
+ * [`docs/cli.md`](docs/cli.md) — every flag, exit code, the
84
+ `--seed` schema.
85
+ * [`docs/slash-commands.md`](docs/slash-commands.md) — verb table
86
+ (`/join`, `/help`, `/send`, …).
87
+ * [`docs/sse-events.md`](docs/sse-events.md) — SSE event catalogue,
88
+ fragment templates, `data-testid` contract.
89
+ * [`docs/playwright.md`](docs/playwright.md) — driving the lens
90
+ with pytest-playwright or Playwright MCP.
91
+ * [`docs/architecture.md`](docs/architecture.md) — runtime
92
+ topology, module layout, decision log.
93
+ * [`CITATION.md`](CITATION.md) — culture citations + divergences.
94
+
95
+ ## License
96
+
97
+ See [LICENSE](LICENSE).
@@ -0,0 +1,46 @@
1
+ # irc-lens
2
+
3
+ `irc-lens` is the agent-driven web console for **AgentIRC** in the
4
+ [Culture](https://github.com/agentculture/culture) ecosystem. Where
5
+ the existing Textual TUI requires a human at a terminal,
6
+ `irc-lens` re-implements the same console as a localhost aiohttp
7
+ app (HTMX + SSE, server-rendered fragments) so a Playwright agent
8
+ or human browser can drive it deterministically.
9
+
10
+ ## Quickstart
11
+
12
+ ```bash
13
+ pip install irc-lens
14
+ irc-lens serve --host 127.0.0.1 --port 6667 --nick lens --open
15
+ ```
16
+
17
+ The `--open` flag launches the default browser at the printed
18
+ URL. Quit with Ctrl-C.
19
+
20
+ ## Develop
21
+
22
+ ```bash
23
+ git clone https://github.com/agentculture/irc-lens && cd irc-lens
24
+ uv venv && uv pip install -e ".[dev]"
25
+ uv run pytest -v # default suite
26
+ uv run playwright install chromium # one-time
27
+ uv run pytest -m playwright -v # browser e2e
28
+ ```
29
+
30
+ ## Docs
31
+
32
+ * [`docs/cli.md`](docs/cli.md) — every flag, exit code, the
33
+ `--seed` schema.
34
+ * [`docs/slash-commands.md`](docs/slash-commands.md) — verb table
35
+ (`/join`, `/help`, `/send`, …).
36
+ * [`docs/sse-events.md`](docs/sse-events.md) — SSE event catalogue,
37
+ fragment templates, `data-testid` contract.
38
+ * [`docs/playwright.md`](docs/playwright.md) — driving the lens
39
+ with pytest-playwright or Playwright MCP.
40
+ * [`docs/architecture.md`](docs/architecture.md) — runtime
41
+ topology, module layout, decision log.
42
+ * [`CITATION.md`](CITATION.md) — culture citations + divergences.
43
+
44
+ ## License
45
+
46
+ See [LICENSE](LICENSE).