shufler 0.3.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.
- shufler-0.3.0/.gitignore +3 -0
- shufler-0.3.0/LICENSE +21 -0
- shufler-0.3.0/PKG-INFO +38 -0
- shufler-0.3.0/README.md +21 -0
- shufler-0.3.0/config.example.toml +390 -0
- shufler-0.3.0/hatch_build.py +32 -0
- shufler-0.3.0/pyproject.toml +53 -0
- shufler-0.3.0/shufler/__init__.py +1 -0
- shufler-0.3.0/shufler/__main__.py +7 -0
- shufler-0.3.0/shufler/actions.py +707 -0
- shufler-0.3.0/shufler/adapters/__init__.py +95 -0
- shufler-0.3.0/shufler/adapters/_util.py +13 -0
- shufler-0.3.0/shufler/adapters/agent/__init__.py +127 -0
- shufler-0.3.0/shufler/adapters/agent/claude_code.py +176 -0
- shufler-0.3.0/shufler/adapters/agent/codex.py +815 -0
- shufler-0.3.0/shufler/adapters/agent/skills.py +151 -0
- shufler-0.3.0/shufler/adapters/review/__init__.py +61 -0
- shufler-0.3.0/shufler/adapters/scm/__init__.py +29 -0
- shufler-0.3.0/shufler/adapters/scm/github.py +131 -0
- shufler-0.3.0/shufler/adapters/terminal/__init__.py +156 -0
- shufler-0.3.0/shufler/adapters/terminal/base.py +73 -0
- shufler-0.3.0/shufler/adapters/terminal/cmux.py +115 -0
- shufler-0.3.0/shufler/adapters/terminal/iterm2.applescript +136 -0
- shufler-0.3.0/shufler/adapters/terminal/iterm2.py +214 -0
- shufler-0.3.0/shufler/adapters/terminal/none.py +124 -0
- shufler-0.3.0/shufler/adapters/terminal/tmux.py +175 -0
- shufler-0.3.0/shufler/adapters/tracker/__init__.py +19 -0
- shufler-0.3.0/shufler/adapters/tracker/board.py +56 -0
- shufler-0.3.0/shufler/adapters/tracker/intake.py +65 -0
- shufler-0.3.0/shufler/adapters/tracker/ledgers.py +56 -0
- shufler-0.3.0/shufler/app.py +684 -0
- shufler-0.3.0/shufler/backfill.py +86 -0
- shufler-0.3.0/shufler/cli.py +355 -0
- shufler-0.3.0/shufler/collectors.py +489 -0
- shufler-0.3.0/shufler/config.py +526 -0
- shufler-0.3.0/shufler/conversation.py +255 -0
- shufler-0.3.0/shufler/events.py +70 -0
- shufler-0.3.0/shufler/history.py +238 -0
- shufler-0.3.0/shufler/library.py +610 -0
- shufler-0.3.0/shufler/library_cli.py +144 -0
- shufler-0.3.0/shufler/mcp_server.py +743 -0
- shufler-0.3.0/shufler/processes.py +170 -0
- shufler-0.3.0/shufler/runner.py +40 -0
- shufler-0.3.0/shufler/setup_cli.py +252 -0
- shufler-0.3.0/shufler/skills_view.py +65 -0
- shufler-0.3.0/shufler/snapshot.py +559 -0
- shufler-0.3.0/shufler/terminals.py +218 -0
- shufler-0.3.0/shufler/transcripts.py +329 -0
- shufler-0.3.0/shufler/web/assets/index-B0oIrpMl.css +32 -0
- shufler-0.3.0/shufler/web/assets/index-C3zh6shu.js +5416 -0
- shufler-0.3.0/shufler/web/assets/inter-cyrillic-400-normal-BLGc9T1a.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-cyrillic-400-normal-ZzOtrSSW.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-cyrillic-500-normal-D4Vwzodn.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-cyrillic-500-normal-DH2hs3aW.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-cyrillic-600-normal-BGBWG807.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-cyrillic-600-normal-BuzJQFbW.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-cyrillic-700-normal-Bc8_fv8J.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-cyrillic-700-normal-bGtGjVdZ.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-cyrillic-ext-400-normal-BPnxn4xp.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-cyrillic-ext-400-normal-Dc4VJyIJ.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-cyrillic-ext-500-normal-BShVwWPj.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-cyrillic-ext-500-normal-CUiC4oBV.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-cyrillic-ext-600-normal-Bt9VVOA-.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-cyrillic-ext-600-normal-CaqZN2hq.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-cyrillic-ext-700-normal-Ced3hgUT.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-cyrillic-ext-700-normal-ClVoMEGq.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-greek-400-normal-BZzXV7-1.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-greek-400-normal-DxZsaF_h.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-greek-500-normal-CeQXL5ds.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-greek-500-normal-d_eO-yCQ.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-greek-600-normal-CwicyhtI.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-greek-600-normal-Dhlb-90d.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-greek-700-normal-BRYTaFLL.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-greek-700-normal-Cxpycf-U.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-greek-ext-400-normal-Bput3-QP.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-greek-ext-400-normal-DCpCPQOf.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-greek-ext-500-normal-B6guLgqG.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-greek-ext-500-normal-M2hEX8vc.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-greek-ext-600-normal-C9WLioJ8.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-greek-ext-600-normal-Cnui8OiR.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-greek-ext-700-normal-DXvzx4Na.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-greek-ext-700-normal-SzCdnevJ.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-latin-400-normal-BOOGhInR.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-latin-400-normal-gitzw0hO.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-latin-500-normal-D2bGa7uu.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-latin-500-normal-deR1Tlfd.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-latin-600-normal-B5cFAncS.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-latin-600-normal-D273HNI0.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-latin-700-normal-B8MtJ_2k.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-latin-700-normal-Sckx8rpT.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-latin-ext-400-normal-C1t-h-pH.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-latin-ext-400-normal-hnt3BR84.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-latin-ext-500-normal-CIS2RHJS.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-latin-ext-500-normal-UMdmhHu2.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-latin-ext-600-normal-BnYJhD27.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-latin-ext-600-normal-CAF0vJDd.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-latin-ext-700-normal-6V9MnIL5.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-latin-ext-700-normal-CzikT_rs.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-vietnamese-400-normal-BUNmGMP1.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-vietnamese-500-normal-DQPw2Hwd.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-vietnamese-600-normal-Cm6aH8_k.woff +0 -0
- shufler-0.3.0/shufler/web/assets/inter-vietnamese-700-normal-CGpBpxLq.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/inter-vietnamese-700-normal-dAnkLlTo.woff +0 -0
- shufler-0.3.0/shufler/web/assets/jetbrains-mono-cyrillic-400-normal-Ddg-ESa-.woff +0 -0
- shufler-0.3.0/shufler/web/assets/jetbrains-mono-cyrillic-400-normal-DduX7Cxz.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/jetbrains-mono-greek-400-normal-CL28JpsE.woff +0 -0
- shufler-0.3.0/shufler/web/assets/jetbrains-mono-greek-400-normal-SMrb6SvR.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/jetbrains-mono-latin-400-normal-BKFenxV8.woff +0 -0
- shufler-0.3.0/shufler/web/assets/jetbrains-mono-latin-400-normal-BrRFx4uz.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/jetbrains-mono-latin-ext-400-normal-Br5SKFH9.woff +0 -0
- shufler-0.3.0/shufler/web/assets/jetbrains-mono-latin-ext-400-normal-Dn-Q5RzH.woff2 +0 -0
- shufler-0.3.0/shufler/web/assets/jetbrains-mono-vietnamese-400-normal-DQPePN4I.woff +0 -0
- shufler-0.3.0/shufler/web/index.html +15 -0
- shufler-0.3.0/shufler/web/skins/casino/ballroom_1k.hdr +0 -0
- shufler-0.3.0/shufler/web/skins/casino/dark_wood_diff.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/casino/dark_wood_nor.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/casino/felt_nor.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/casino/leather_nor.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/skyislands/brown_planks_03_diff.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/skyislands/brown_planks_03_nor.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/skyislands/cliff_side_diff.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/skyislands/cliff_side_nor.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/skyislands/kloppenheim_06_puresky.hdr +0 -0
- shufler-0.3.0/shufler/web/skins/skyislands/leafy_grass_diff.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/skyislands/leafy_grass_nor.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/skyislands/qwantani_sunset_puresky.hdr +0 -0
- shufler-0.3.0/shufler/web/skins/skyislands/rock_wall_08_diff.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/skyislands/rock_wall_08_nor.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/skyislands/roof_slates_02_diff.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/skyislands/roof_slates_02_nor.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/themepark/brown_planks_05_diff.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/themepark/brown_planks_05_nor.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/themepark/grass_ground_diff.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/themepark/grass_ground_nor.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/themepark/herringbone_pavement_diff.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/themepark/herringbone_pavement_nor.jpg +0 -0
- shufler-0.3.0/shufler/web/skins/themepark/industrial_sunset_02_puresky.hdr +0 -0
- shufler-0.3.0/shufler/world.py +670 -0
- shufler-0.3.0/tests/__init__.py +0 -0
- shufler-0.3.0/tests/conftest.py +64 -0
- shufler-0.3.0/tests/fixtures/cmux_groups.json +1 -0
- shufler-0.3.0/tests/fixtures/cmux_groups_real.json +56 -0
- shufler-0.3.0/tests/fixtures/cmux_workspaces.json +11 -0
- shufler-0.3.0/tests/fixtures/codex/sessions/2026/10/21/rollout-2026-10-21T09-30-00-cccc3333-0000-0000-0000-000000000000.jsonl +23 -0
- shufler-0.3.0/tests/fixtures/codex/sessions/2026/10/21/rollout-2026-10-21T09-55-00-dddd4444-0000-0000-0000-000000000000.jsonl +6 -0
- shufler-0.3.0/tests/fixtures/codex/sessions/rollout-2025-05-02-eeee5555-0000-0000-0000-000000000000.json +9 -0
- shufler-0.3.0/tests/fixtures/config.toml +172 -0
- shufler-0.3.0/tests/fixtures/gh_merged.json +4 -0
- shufler-0.3.0/tests/fixtures/gh_open.json +15 -0
- shufler-0.3.0/tests/fixtures/intake/shipped-one.json +1 -0
- shufler-0.3.0/tests/fixtures/intake/shipped-two.json +1 -0
- shufler-0.3.0/tests/fixtures/intake/support-queue.events.jsonl +4 -0
- shufler-0.3.0/tests/fixtures/intake/support-queue.json +1 -0
- shufler-0.3.0/tests/fixtures/ledgers/auth/ledger.md +14 -0
- shufler-0.3.0/tests/fixtures/notes/aaaa1111-0000-0000-0000-000000000000.json +1 -0
- shufler-0.3.0/tests/fixtures/notes/bbbb2222-0000-0000-0000-000000000000.json +1 -0
- shufler-0.3.0/tests/fixtures/projects/-Users-demo-widgets/aaaa1111-0000-0000-0000-000000000000.jsonl +4 -0
- shufler-0.3.0/tests/fixtures/projects/-Users-demo-widgets/bbbb2222-0000-0000-0000-000000000000.jsonl +8 -0
- shufler-0.3.0/tests/fixtures/ps_claude.txt +18 -0
- shufler-0.3.0/tests/fixtures/ps_codex.txt +11 -0
- shufler-0.3.0/tests/test_actions.py +340 -0
- shufler-0.3.0/tests/test_adapters.py +137 -0
- shufler-0.3.0/tests/test_api.py +295 -0
- shufler-0.3.0/tests/test_cli.py +85 -0
- shufler-0.3.0/tests/test_codex.py +485 -0
- shufler-0.3.0/tests/test_conversation.py +55 -0
- shufler-0.3.0/tests/test_events.py +51 -0
- shufler-0.3.0/tests/test_history.py +120 -0
- shufler-0.3.0/tests/test_ledger_branches.py +179 -0
- shufler-0.3.0/tests/test_library.py +350 -0
- shufler-0.3.0/tests/test_mcp.py +313 -0
- shufler-0.3.0/tests/test_no_terminal.py +154 -0
- shufler-0.3.0/tests/test_processes.py +186 -0
- shufler-0.3.0/tests/test_skills.py +167 -0
- shufler-0.3.0/tests/test_snapshot.py +244 -0
- shufler-0.3.0/tests/test_stacked_deploys.py +79 -0
- shufler-0.3.0/tests/test_stage.py +83 -0
- shufler-0.3.0/tests/test_telemetry_history.py +119 -0
- shufler-0.3.0/tests/test_terminal_adapters.py +399 -0
- shufler-0.3.0/tests/test_terminals.py +90 -0
- shufler-0.3.0/tests/test_transcripts.py +49 -0
- shufler-0.3.0/tests/test_world.py +313 -0
shufler-0.3.0/.gitignore
ADDED
shufler-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The Shufler authors
|
|
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.
|
shufler-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: shufler
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A 3D control room for developers who run many AI coding agent sessions at once.
|
|
5
|
+
Project-URL: Homepage, https://github.com/jgrichardson/shufler
|
|
6
|
+
Project-URL: Repository, https://github.com/jgrichardson/shufler
|
|
7
|
+
Project-URL: Issues, https://github.com/jgrichardson/shufler/issues
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: fastapi>=0.159.1
|
|
12
|
+
Requires-Dist: httpx>=0.28.1
|
|
13
|
+
Requires-Dist: tomlkit>=0.15.2
|
|
14
|
+
Requires-Dist: uvicorn[standard]>=0.54.0
|
|
15
|
+
Requires-Dist: watchfiles>=1.3.0
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# Shufler
|
|
19
|
+
|
|
20
|
+
A 3D control room for developers who run many AI coding agent sessions at once.
|
|
21
|
+
|
|
22
|
+
Shufler reads your terminal sessions (cmux, iTerm2 or tmux, or any other terminal in a watch-only mode), your agent transcripts (Claude Code and the OpenAI
|
|
23
|
+
Codex CLI) and your GitHub pull requests, and turns them into an interactive 3D world. Each terminal session is a
|
|
24
|
+
row, its PRs move left to right through stages of work, and anything that needs you lights up red.
|
|
25
|
+
|
|
26
|
+
This package is the local daemon and the `shufler` command. The built web app ships inside it, so nothing else is
|
|
27
|
+
needed to run it:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
shufler setup # pick your terminal; writes ~/.config/shufler/config.toml
|
|
31
|
+
shufler start # http://127.0.0.1:4700
|
|
32
|
+
shufler mcp --print-config # let Claude Code or Codex ask Shufler what is running
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The full guide, screenshots and configuration reference are in the repository:
|
|
36
|
+
https://github.com/jgrichardson/shufler
|
|
37
|
+
|
|
38
|
+
MIT licensed.
|
shufler-0.3.0/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Shufler
|
|
2
|
+
|
|
3
|
+
A 3D control room for developers who run many AI coding agent sessions at once.
|
|
4
|
+
|
|
5
|
+
Shufler reads your terminal sessions (cmux, iTerm2 or tmux, or any other terminal in a watch-only mode), your agent transcripts (Claude Code and the OpenAI
|
|
6
|
+
Codex CLI) and your GitHub pull requests, and turns them into an interactive 3D world. Each terminal session is a
|
|
7
|
+
row, its PRs move left to right through stages of work, and anything that needs you lights up red.
|
|
8
|
+
|
|
9
|
+
This package is the local daemon and the `shufler` command. The built web app ships inside it, so nothing else is
|
|
10
|
+
needed to run it:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
shufler setup # pick your terminal; writes ~/.config/shufler/config.toml
|
|
14
|
+
shufler start # http://127.0.0.1:4700
|
|
15
|
+
shufler mcp --print-config # let Claude Code or Codex ask Shufler what is running
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The full guide, screenshots and configuration reference are in the repository:
|
|
19
|
+
https://github.com/jgrichardson/shufler
|
|
20
|
+
|
|
21
|
+
MIT licensed.
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
# Shufler configuration.
|
|
2
|
+
# `bin/shufler init` copies this file to ~/.config/shufler/config.toml
|
|
3
|
+
# (override the location with SHUFLER_CONFIG). Commented lines are worked
|
|
4
|
+
# examples: uncomment and edit the ones that apply to you. Anything left out
|
|
5
|
+
# falls back to the defaults shown, and optional adapters stay off until you
|
|
6
|
+
# enable them.
|
|
7
|
+
|
|
8
|
+
[app]
|
|
9
|
+
name = "Shufler"
|
|
10
|
+
port = 4700
|
|
11
|
+
owner_aliases = ["me"] # names in ledger "Ball" cells and notes that mean you
|
|
12
|
+
# owner_aliases = ["sam", "me"]
|
|
13
|
+
# actions_log = "~/shufler/actions.jsonl" # default: <repo>/actions.jsonl
|
|
14
|
+
# cache_dir = "~/.cache/shufler"
|
|
15
|
+
|
|
16
|
+
# ---------------------------------------------------------------- terminal
|
|
17
|
+
# Which terminal hosts your agent sessions: "cmux", "iterm2", "tmux", "none", or "auto" (the first one running,
|
|
18
|
+
# else "none"; resolved once at daemon start, so set a kind if the daemon starts before your terminal). "none" works with any terminal: one row per running claude or codex
|
|
19
|
+
# session, read from ps, with sending, answering, Focus and new tabs off. `shufler setup` writes this for you.
|
|
20
|
+
# docs/TERMINALS.md explains each mapping.
|
|
21
|
+
[terminal]
|
|
22
|
+
kind = "auto"
|
|
23
|
+
# Workspace titles treated as the control room (they get the "command" domain).
|
|
24
|
+
command_titles = []
|
|
25
|
+
# command_titles = ["CONTROL", "SHARED"]
|
|
26
|
+
# Utility rows (tools, not work): no row in the world, still reachable from the terminal drawer.
|
|
27
|
+
utility = []
|
|
28
|
+
# utility = ["SHARED"]
|
|
29
|
+
# Read running agent processes (`claude`, `codex`) every 10 s (ps + lsof) to tell open sessions from closed ones.
|
|
30
|
+
# With kind = "none" the processes are the rows, so they are always read (every 3 s) and this has no effect.
|
|
31
|
+
processes = true
|
|
32
|
+
|
|
33
|
+
[cmux]
|
|
34
|
+
# One row per cmux workspace, groups = cmux workspace groups, order = the sidebar.
|
|
35
|
+
bin = "/Applications/cmux.app/Contents/Resources/bin/cmux"
|
|
36
|
+
|
|
37
|
+
[iterm2]
|
|
38
|
+
# One row per iTerm2 session (pane), order = window (creation order), tab, pane. Uses AppleScript (osascript);
|
|
39
|
+
# macOS asks once to let the app running Shufler control iTerm2 (Privacy & Security > Automation).
|
|
40
|
+
# group_by = "window" one group per iTerm2 window, named from its title
|
|
41
|
+
# group_by = "tab_prefix" group by the tab title's prefix: "AUTH · login" goes in group AUTH
|
|
42
|
+
group_by = "window"
|
|
43
|
+
prefix_separator = " · "
|
|
44
|
+
# osascript = "/usr/bin/osascript"
|
|
45
|
+
|
|
46
|
+
[tmux]
|
|
47
|
+
# Groups = tmux sessions; rows = panes (rows = "pane") or windows (rows = "window"); order = session, window, pane.
|
|
48
|
+
rows = "pane"
|
|
49
|
+
# bin = "/opt/homebrew/bin/tmux"
|
|
50
|
+
# socket_name = "work" # tmux -L work
|
|
51
|
+
# socket_path = "/tmp/tmux.sock" # tmux -S /tmp/tmux.sock
|
|
52
|
+
|
|
53
|
+
# ---------------------------------------------------------------- coding agents
|
|
54
|
+
# Which agent harnesses run in your rows: "auto" (Claude Code always, plus Codex when it is installed) or a list.
|
|
55
|
+
# Claude Code and Codex rows mix freely. docs/AGENTS.md explains each harness.
|
|
56
|
+
[agents]
|
|
57
|
+
enabled = "auto"
|
|
58
|
+
# enabled = ["claude", "codex"]
|
|
59
|
+
|
|
60
|
+
# Codex CLI: rollouts under <home>/sessions. Defaults: $CODEX_HOME, else ~/.codex.
|
|
61
|
+
[codex]
|
|
62
|
+
# home = "~/.codex"
|
|
63
|
+
# bin = "codex"
|
|
64
|
+
|
|
65
|
+
# Claude Code.
|
|
66
|
+
[agent]
|
|
67
|
+
adapter = "claude-code" # the Claude Code adapter (Codex is configured above)
|
|
68
|
+
|
|
69
|
+
# One entry per Claude Code config directory you run sessions from.
|
|
70
|
+
[[agent.accounts]]
|
|
71
|
+
name = "personal"
|
|
72
|
+
label = "personal"
|
|
73
|
+
projects_dir = "~/.claude/projects"
|
|
74
|
+
claude_json = "~/.claude.json"
|
|
75
|
+
settings = "~/.claude/settings.json"
|
|
76
|
+
usage_cache = "~/.claude/usage-cache/personal.json" # written by a statusline script, optional
|
|
77
|
+
|
|
78
|
+
# [[agent.accounts]]
|
|
79
|
+
# name = "work"
|
|
80
|
+
# label = "work"
|
|
81
|
+
# projects_dir = "~/.claude-work/projects"
|
|
82
|
+
# claude_json = "~/.claude-work/.claude.json"
|
|
83
|
+
# settings = "~/.claude-work/settings.json"
|
|
84
|
+
# usage_cache = "~/.claude/usage-cache/work.json"
|
|
85
|
+
|
|
86
|
+
# ---------------------------------------------------------------- source control
|
|
87
|
+
[scm]
|
|
88
|
+
adapter = "github"
|
|
89
|
+
gh = "gh"
|
|
90
|
+
repo = "" # "owner/name"; empty = PR sources off
|
|
91
|
+
# repo = "your-org/your-app"
|
|
92
|
+
deploy_workflow = "" # production deploy workflow file; empty = no deploy tracking
|
|
93
|
+
# deploy_workflow = "deploy_production.yml"
|
|
94
|
+
deploy_branch = "main"
|
|
95
|
+
# deploy_branch = "develop"
|
|
96
|
+
preview_url = "" # "{pr}" is replaced with the PR number
|
|
97
|
+
# preview_url = "https://pr-{pr}.preview.example.com"
|
|
98
|
+
worktree_roots = ["~/worktrees"] # <root>/*/<branch> counts as a live worktree (stale ledger rows)
|
|
99
|
+
|
|
100
|
+
# ---------------------------------------------------------------- external reviewer bot (optional)
|
|
101
|
+
# A bot that reviews PRs under its own GitHub login. When enabled, its latest review is the reviewer gate:
|
|
102
|
+
# a PR is ready to merge only once the bot approved the current head commit. When off, CI decides.
|
|
103
|
+
[reviewer]
|
|
104
|
+
enabled = false
|
|
105
|
+
login_regex = "" # matches the bot's GitHub login, e.g. "my-review-bot"
|
|
106
|
+
# Optional watcher that prints {"prs": {"<n>": {"status": "...", "cycle": 2}}} as JSON (review rounds).
|
|
107
|
+
# status_command = ["~/bin/review-watch", "status", "--json"]
|
|
108
|
+
# How to ask for a re-review: a PR comment ({pr} is filled in) ...
|
|
109
|
+
# retrigger_comment = "@my-review-bot review"
|
|
110
|
+
# ... or a command of your own.
|
|
111
|
+
# retrigger_command = ["~/bin/review-watch", "retrigger", "{pr}"]
|
|
112
|
+
|
|
113
|
+
# ---------------------------------------------------------------- work item sources (optional)
|
|
114
|
+
# Intake: one JSON record per work item (<dir>/<slug>.json + <slug>.events.jsonl), and a workspace that
|
|
115
|
+
# takes new items from the "New work item" form (the prompt is typed there; {text} is what you entered).
|
|
116
|
+
[intake]
|
|
117
|
+
enabled = false
|
|
118
|
+
dir = ""
|
|
119
|
+
set_command = "" # CLI taking `set <slug> key=value`
|
|
120
|
+
workspace = "" # workspace title, e.g. "CONTROL"
|
|
121
|
+
prompt = "{text}" # e.g. "/intake {text}" to run a skill of your own
|
|
122
|
+
# reply_events = ["user_replied", "ready_for_owner"] # record events that put the item in "Your move"
|
|
123
|
+
|
|
124
|
+
# Area ledgers: <dir>/<area>/ledger.md with an "## Open" markdown table
|
|
125
|
+
# (Branch | PR | Topic | State | Ball | Next | Worktree | Session), one per long-lived work area.
|
|
126
|
+
[ledgers]
|
|
127
|
+
enabled = false
|
|
128
|
+
dir = ""
|
|
129
|
+
area_env = "LEDGER_AREA" # env var naming a session's area (LEDGER_AREA=billing claude)
|
|
130
|
+
session_marker = "area" # a Session cell with this word belongs to that area's session
|
|
131
|
+
|
|
132
|
+
# Board: an external board CLI with one card per intake record (the record's `card` = {id, stage}).
|
|
133
|
+
[board]
|
|
134
|
+
enabled = false
|
|
135
|
+
command = ""
|
|
136
|
+
stages = ["backlog", "in_progress", "in_review", "done"]
|
|
137
|
+
get_args = ["get", "{id}"] # prints the card; its stage is read with stage_pattern
|
|
138
|
+
stage_args = ["stage", "{id}", "{stage}"]
|
|
139
|
+
note_args = ["note", "{id}", "{text}"]
|
|
140
|
+
stage_pattern = 'stage=(\S+)'
|
|
141
|
+
record_key = "card"
|
|
142
|
+
|
|
143
|
+
# ---------------------------------------------------------------- work phases (districts, left to right)
|
|
144
|
+
[pipeline]
|
|
145
|
+
stages = [
|
|
146
|
+
{ id = "brainstorm", label = "Brainstorm", color = "#9aa4b2", description = "Exploring the idea." },
|
|
147
|
+
{ id = "plan", label = "Plan", color = "#7c8cf8", description = "Planning the work." },
|
|
148
|
+
{ id = "build", label = "Build", color = "#f2b544", description = "Building on a branch or draft PR." },
|
|
149
|
+
{ id = "qa", label = "QA", color = "#45c7e0", description = "Testers have it on preview." },
|
|
150
|
+
{ id = "review", label = "Review", color = "#f28c38", description = "In review: PR review, review bot, CI." },
|
|
151
|
+
{ id = "merge", label = "Merge", color = "#a56cf0", description = "Ready to merge." },
|
|
152
|
+
{ id = "deploy", label = "Deploy", color = "#3dd68c", description = "Merged, waiting on the production deploy." },
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
# ---------------------------------------------------------------- session notes (optional)
|
|
156
|
+
# One JSON file per agent session ({session, state, text, ts, cwd, ws_ref}) written by a tool of your own;
|
|
157
|
+
# the note shows on the session's row. States: working, me, waiting, staging, merged, done.
|
|
158
|
+
[notes]
|
|
159
|
+
enabled = false
|
|
160
|
+
dir = ""
|
|
161
|
+
# dir = "~/.claude/session-notes"
|
|
162
|
+
|
|
163
|
+
# ---------------------------------------------------------------- custom commands (optional)
|
|
164
|
+
# Named commands shown as levers (World panel, Session panel, palette, and the command drawer). Each run goes
|
|
165
|
+
# through the confirm panel, which shows the exact command. Tokens in argv and env: the input names, and for
|
|
166
|
+
# scope = "station" also {station_id} {station} {session_id} {cwd}. A whole-argument token that is empty (an
|
|
167
|
+
# unset flag or an empty optional input) drops that argument. confirm = "none" | "tap" | "typed";
|
|
168
|
+
# prod = true marks the confirm panel red. Input types: "text" (max, multiline, placeholder, optional),
|
|
169
|
+
# "choice" (choices), "flag" (arg, the argument passed when checked).
|
|
170
|
+
#
|
|
171
|
+
# [[commands]]
|
|
172
|
+
# id = "test"
|
|
173
|
+
# label = "Run tests"
|
|
174
|
+
# description = "Run the test suite in a repo"
|
|
175
|
+
# argv = ["make", "-C", "{dir}", "test"]
|
|
176
|
+
# inputs = [{ name = "dir", label = "Repo", placeholder = "~/code/myapp" }]
|
|
177
|
+
#
|
|
178
|
+
# [[commands]]
|
|
179
|
+
# id = "session_note"
|
|
180
|
+
# label = "Session note"
|
|
181
|
+
# scope = "station"
|
|
182
|
+
# argv = ["~/bin/session-note", "{state}", "{text}"]
|
|
183
|
+
# env = { AGENT_SESSION_ID = "{session_id}" }
|
|
184
|
+
# inputs = [
|
|
185
|
+
# { name = "state", type = "choice", choices = ["working", "me", "waiting", "staging", "merged", "done"] },
|
|
186
|
+
# { name = "text", label = "Note", max = 80 },
|
|
187
|
+
# ]
|
|
188
|
+
#
|
|
189
|
+
# [[commands]]
|
|
190
|
+
# id = "query"
|
|
191
|
+
# label = "Query"
|
|
192
|
+
# description = "Read-only query against the reporting replica"
|
|
193
|
+
# argv = ["~/bin/query", "{raw}", "{text}"]
|
|
194
|
+
# prod = true
|
|
195
|
+
# timeout = 15
|
|
196
|
+
# inputs = [
|
|
197
|
+
# { name = "text", label = "SQL", multiline = true, max = 10000 },
|
|
198
|
+
# { name = "raw", type = "flag", arg = "--raw", label = "Raw output" },
|
|
199
|
+
# ]
|
|
200
|
+
|
|
201
|
+
# ---------------------------------------------------------------- dispatch prompts
|
|
202
|
+
[prompts]
|
|
203
|
+
postmerge = "{pr} is merged and deploying. Draft a short note for the requester and tell me where to post it."
|
|
204
|
+
|
|
205
|
+
# Dispatch buttons, grouped. Tokens {pr} {person} {topic} {url} are filled by the web before sending.
|
|
206
|
+
# Plain strings (canned = ["..."]) also load, as group "Custom".
|
|
207
|
+
[[prompts.canned]]
|
|
208
|
+
group = "PR flow"
|
|
209
|
+
label = "Status"
|
|
210
|
+
text = "What is the status of {pr}? Tell me what is left before it can merge."
|
|
211
|
+
|
|
212
|
+
[[prompts.canned]]
|
|
213
|
+
group = "PR flow"
|
|
214
|
+
label = "Drive to ready"
|
|
215
|
+
text = "Get {pr} ready to merge. Resolve conflicts, address review comments, push, and watch CI until it is green and approved. Tell me when I can merge."
|
|
216
|
+
|
|
217
|
+
[[prompts.canned]]
|
|
218
|
+
group = "PR flow"
|
|
219
|
+
label = "Fix conflicts"
|
|
220
|
+
text = "Fix the conflicts in {pr} and tell me when I can merge."
|
|
221
|
+
|
|
222
|
+
[[prompts.canned]]
|
|
223
|
+
group = "PR flow"
|
|
224
|
+
label = "Review fixes"
|
|
225
|
+
text = "Review comments came in on {pr}. Address them, push, and tell me when it is ready for another look."
|
|
226
|
+
|
|
227
|
+
[[prompts.canned]]
|
|
228
|
+
group = "PR flow"
|
|
229
|
+
label = "Watch the review"
|
|
230
|
+
text = "I asked for a review of {pr}. Watch it and fix any requested changes until it is approved and CI is green."
|
|
231
|
+
|
|
232
|
+
[[prompts.canned]]
|
|
233
|
+
group = "Post-merge"
|
|
234
|
+
label = "Close the loop"
|
|
235
|
+
text = "{pr} is merged and deploying. Draft a short note for the requester and tell me where to post it."
|
|
236
|
+
|
|
237
|
+
[[prompts.canned]]
|
|
238
|
+
group = "Post-merge"
|
|
239
|
+
label = "Verify in production"
|
|
240
|
+
text = "{pr} is in production. Check that the fix works and tell me what you saw."
|
|
241
|
+
|
|
242
|
+
[[prompts.canned]]
|
|
243
|
+
group = "Session"
|
|
244
|
+
label = "Plain speak"
|
|
245
|
+
text = "Explain in plain words where we are and what you need from me to unblock."
|
|
246
|
+
|
|
247
|
+
[[prompts.canned]]
|
|
248
|
+
group = "Session"
|
|
249
|
+
label = "Keep going"
|
|
250
|
+
text = "Keep going until it is ready. Watch every PR in this session and stay on top of reviews, CI and conflicts."
|
|
251
|
+
|
|
252
|
+
[[prompts.canned]]
|
|
253
|
+
group = "Session"
|
|
254
|
+
label = "Summary"
|
|
255
|
+
text = "Summarise what this session has done today in five bullets."
|
|
256
|
+
|
|
257
|
+
[[prompts.canned]]
|
|
258
|
+
group = "QA"
|
|
259
|
+
label = "Browser QA"
|
|
260
|
+
text = "Do a browser QA pass on {pr} on its preview. Save screenshots and tell me what failed."
|
|
261
|
+
|
|
262
|
+
[[prompts.canned]]
|
|
263
|
+
group = "QA"
|
|
264
|
+
label = "Test plan"
|
|
265
|
+
text = "Write a short test plan for {pr}: what to click, what should happen."
|
|
266
|
+
|
|
267
|
+
[[prompts.canned]]
|
|
268
|
+
group = "Messages"
|
|
269
|
+
label = "Draft to person"
|
|
270
|
+
text = "Give me a short message I can send to {person} about {topic}. No opening line, no sign-off."
|
|
271
|
+
|
|
272
|
+
[[prompts.canned]]
|
|
273
|
+
group = "Messages"
|
|
274
|
+
label = "Read thread"
|
|
275
|
+
text = "Read the latest message in this thread and tell me what to do: {url}"
|
|
276
|
+
|
|
277
|
+
# ---------------------------------------------------------------- time travel
|
|
278
|
+
[history]
|
|
279
|
+
enabled = true
|
|
280
|
+
path = "~/.local/share/shufler/history.sqlite"
|
|
281
|
+
retention_days = 90
|
|
282
|
+
backfill_days = 30 # one-time best-effort import on first start; 0 = off
|
|
283
|
+
|
|
284
|
+
# ---------------------------------------------------------------- web terminals
|
|
285
|
+
[terminals]
|
|
286
|
+
enabled = true
|
|
287
|
+
max = 6
|
|
288
|
+
idle_close_s = 7200
|
|
289
|
+
# Presets only type text into the shell; you press Enter yourself. A preset with no command is hidden.
|
|
290
|
+
# prod_console gets the red PROD tab; any preset can set `prod = true`.
|
|
291
|
+
[terminals.presets.prod_console]
|
|
292
|
+
cwd = ""
|
|
293
|
+
command = ""
|
|
294
|
+
# cwd = "~/code/myapp"
|
|
295
|
+
# command = "./bin/console production"
|
|
296
|
+
|
|
297
|
+
[terminals.presets.preview_console]
|
|
298
|
+
cwd = ""
|
|
299
|
+
command = "" # "{pr}" is replaced with the PR number
|
|
300
|
+
# command = "./bin/console preview # pick the preview for #{pr}"
|
|
301
|
+
|
|
302
|
+
[terminals.presets.local_console]
|
|
303
|
+
command = "" # runs in the session's working directory
|
|
304
|
+
# command = "./bin/console"
|
|
305
|
+
|
|
306
|
+
# ---------------------------------------------------------------- skills
|
|
307
|
+
# Discovered from each account's skills/, commands/ and plugin cache, plus each
|
|
308
|
+
# station's repo (.claude/skills, .claude/commands, docs/claude/skills).
|
|
309
|
+
# `expose` lists skills shown up front in the palette; `hide` removes skills
|
|
310
|
+
# entirely. Per-skill tables set the default station (`target`, a workspace
|
|
311
|
+
# title), the args template ({pr} {branch} {station} {link} {link_or_text}
|
|
312
|
+
# {text}), the confirm level, whether it is pinned as a button, and where that
|
|
313
|
+
# button goes (`placement`: "station", "pr", "global", "question" or "none").
|
|
314
|
+
# The web's Settings drawer rewrites this section (comments in it are not kept).
|
|
315
|
+
[skills]
|
|
316
|
+
expose = []
|
|
317
|
+
hide = []
|
|
318
|
+
# expose = ["code-review", "describe-pr"]
|
|
319
|
+
|
|
320
|
+
# [skills.code-review]
|
|
321
|
+
# args_template = "{pr}"
|
|
322
|
+
# placement = "pr"
|
|
323
|
+
# pin = true
|
|
324
|
+
|
|
325
|
+
# ---------------------------------------------------------------- MCP server
|
|
326
|
+
# `shufler mcp` lets your agents ask Shufler what is running (read-only tools, always on). With
|
|
327
|
+
# allow_dispatch = true it also offers send_prompt and answer_question, which preview a prompt to another session
|
|
328
|
+
# and send it only through a second confirm_dispatch call. One agent can then steer another, so read the threat
|
|
329
|
+
# model in docs/MCP.md before turning it on.
|
|
330
|
+
[mcp]
|
|
331
|
+
allow_dispatch = false
|
|
332
|
+
|
|
333
|
+
# ---------------------------------------------------------------- world layout domains
|
|
334
|
+
# One lane per domain, in this order. Aliases match lower-case tokens of workspace titles, PR title scopes
|
|
335
|
+
# (`feat(billing): ...`), branches and ledger areas. The palette below is a sample; replace it with your own
|
|
336
|
+
# areas. `fallback_domain` (always the last lane) is used when nothing matches.
|
|
337
|
+
# `manual = [...]` in a [[domain]] table holds aliases written by the set_domain action.
|
|
338
|
+
[world]
|
|
339
|
+
fallback_domain = "unassigned"
|
|
340
|
+
command_domain = "command"
|
|
341
|
+
fix_pattern = "data|backfill|migrat|repair" # branches and ledger topics drawn as data fixes
|
|
342
|
+
|
|
343
|
+
[[domain]]
|
|
344
|
+
id = "auth"
|
|
345
|
+
label = "Auth"
|
|
346
|
+
code = "AU"
|
|
347
|
+
color = "#E3C04B"
|
|
348
|
+
aliases = ["auth", "login", "sso", "session", "oauth"]
|
|
349
|
+
|
|
350
|
+
[[domain]]
|
|
351
|
+
id = "billing"
|
|
352
|
+
label = "Billing"
|
|
353
|
+
code = "BI"
|
|
354
|
+
color = "#2BB3A0"
|
|
355
|
+
aliases = ["billing", "invoice", "payments", "stripe", "subscription"]
|
|
356
|
+
|
|
357
|
+
[[domain]]
|
|
358
|
+
id = "search"
|
|
359
|
+
label = "Search"
|
|
360
|
+
code = "SE"
|
|
361
|
+
color = "#8C94F0"
|
|
362
|
+
aliases = ["search", "index", "ranking", "query"]
|
|
363
|
+
|
|
364
|
+
[[domain]]
|
|
365
|
+
id = "mobile"
|
|
366
|
+
label = "Mobile"
|
|
367
|
+
code = "MO"
|
|
368
|
+
color = "#D0509A"
|
|
369
|
+
aliases = ["mobile", "ios", "android", "app"]
|
|
370
|
+
|
|
371
|
+
[[domain]]
|
|
372
|
+
id = "infra"
|
|
373
|
+
label = "Infra"
|
|
374
|
+
code = "IN"
|
|
375
|
+
color = "#9CCB4A"
|
|
376
|
+
aliases = ["infra", "ci", "deploy", "terraform", "k8s", "observability"]
|
|
377
|
+
|
|
378
|
+
[[domain]]
|
|
379
|
+
id = "docs"
|
|
380
|
+
label = "Docs"
|
|
381
|
+
code = "DO"
|
|
382
|
+
color = "#F08C5A"
|
|
383
|
+
aliases = ["docs", "readme", "guide"]
|
|
384
|
+
|
|
385
|
+
[[domain]]
|
|
386
|
+
id = "unassigned"
|
|
387
|
+
label = "Unassigned"
|
|
388
|
+
code = "??"
|
|
389
|
+
color = "#8A8F98"
|
|
390
|
+
aliases = ["unassigned"]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Build hook: put the built web app (../web/dist) into the wheel and sdist as shufler/web.
|
|
2
|
+
|
|
3
|
+
A wheel built from the sdist finds it already in place. Editable installs (uv sync) skip it: a source checkout
|
|
4
|
+
serves web/dist directly. Without a web build the package still builds, with a warning, and the daemon answers
|
|
5
|
+
503 with a hint; set SHUFLER_REQUIRE_WEB=1 (release.sh and CI do) to make that an error instead.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WebBuildHook(BuildHookInterface):
|
|
17
|
+
PLUGIN_NAME = "custom"
|
|
18
|
+
|
|
19
|
+
def initialize(self, version: str, build_data: dict) -> None:
|
|
20
|
+
if version == "editable":
|
|
21
|
+
return
|
|
22
|
+
root = Path(self.root)
|
|
23
|
+
repo_dist = root.parent / "web" / "dist"
|
|
24
|
+
if (repo_dist / "index.html").is_file():
|
|
25
|
+
build_data["force_include"][str(repo_dist)] = "shufler/web"
|
|
26
|
+
return
|
|
27
|
+
if (root / "shufler" / "web" / "index.html").is_file():
|
|
28
|
+
return
|
|
29
|
+
msg = f"no web build at {repo_dist}; the package will not include the web app (cd web && bun run build)"
|
|
30
|
+
if os.environ.get("SHUFLER_REQUIRE_WEB") == "1":
|
|
31
|
+
raise RuntimeError(msg)
|
|
32
|
+
self.app.display_warning(msg)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "shufler"
|
|
3
|
+
version = "0.3.0"
|
|
4
|
+
description = "A 3D control room for developers who run many AI coding agent sessions at once."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"fastapi>=0.159.1",
|
|
11
|
+
"httpx>=0.28.1",
|
|
12
|
+
"tomlkit>=0.15.2",
|
|
13
|
+
"uvicorn[standard]>=0.54.0",
|
|
14
|
+
"watchfiles>=1.3.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
shufler = "shufler.cli:main"
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://github.com/jgrichardson/shufler"
|
|
22
|
+
Repository = "https://github.com/jgrichardson/shufler"
|
|
23
|
+
Issues = "https://github.com/jgrichardson/shufler/issues"
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["hatchling>=1.27"]
|
|
27
|
+
build-backend = "hatchling.build"
|
|
28
|
+
|
|
29
|
+
# The wheel carries the example config and the built web app (web/dist, as shufler/web), so an installed
|
|
30
|
+
# `shufler` needs no checkout. hatch_build.py adds web/dist when it exists; packaging/release.sh builds it first.
|
|
31
|
+
[tool.hatch.build.targets.wheel]
|
|
32
|
+
packages = ["shufler"]
|
|
33
|
+
artifacts = ["shufler/web/**"]
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
36
|
+
"config.example.toml" = "shufler/config.example.toml"
|
|
37
|
+
|
|
38
|
+
[tool.hatch.build.targets.wheel.hooks.custom]
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.sdist]
|
|
41
|
+
include = ["shufler", "tests", "config.example.toml", "hatch_build.py", "README.md", "LICENSE"]
|
|
42
|
+
artifacts = ["shufler/web/**"]
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.sdist.hooks.custom]
|
|
45
|
+
|
|
46
|
+
[dependency-groups]
|
|
47
|
+
dev = [
|
|
48
|
+
"pytest>=9.1.1",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.pytest.ini_options]
|
|
52
|
+
testpaths = ["tests"]
|
|
53
|
+
pythonpath = ["."]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Shufler daemon: collects local session/PR state and serves it to the web floor."""
|