agr-opentui 0.2.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 (105) hide show
  1. agr_opentui-0.2.0/.github/renovate.json5 +19 -0
  2. agr_opentui-0.2.0/.github/workflows/build-bin.yml +75 -0
  3. agr_opentui-0.2.0/.github/workflows/ci.yml +48 -0
  4. agr_opentui-0.2.0/.github/workflows/publish-pypi.yml +76 -0
  5. agr_opentui-0.2.0/.gitignore +34 -0
  6. agr_opentui-0.2.0/Makefile +34 -0
  7. agr_opentui-0.2.0/PKG-INFO +6 -0
  8. agr_opentui-0.2.0/README.md +144 -0
  9. agr_opentui-0.2.0/agr_opentui/__init__.py +1 -0
  10. agr_opentui-0.2.0/agr_opentui/bridge.py +157 -0
  11. agr_opentui-0.2.0/bun.lock +206 -0
  12. agr_opentui-0.2.0/bunfig.toml +2 -0
  13. agr_opentui-0.2.0/package.json +19 -0
  14. agr_opentui-0.2.0/pyproject.toml +12 -0
  15. agr_opentui-0.2.0/skills.json +85 -0
  16. agr_opentui-0.2.0/src/app.ts +914 -0
  17. agr_opentui-0.2.0/src/app_logic.ts +371 -0
  18. agr_opentui-0.2.0/src/commands.ts +50 -0
  19. agr_opentui-0.2.0/src/deps.ts +33 -0
  20. agr_opentui-0.2.0/src/main.ts +1 -0
  21. agr_opentui-0.2.0/src/runtime/doctor.ts +56 -0
  22. agr_opentui-0.2.0/src/runtime/handlers.ts +261 -0
  23. agr_opentui-0.2.0/src/runtime/modal_input_handler.ts +218 -0
  24. agr_opentui-0.2.0/src/services/add_flow.ts +28 -0
  25. agr_opentui-0.2.0/src/services/agr.ts +85 -0
  26. agr_opentui-0.2.0/src/services/agr_actions.ts +262 -0
  27. agr_opentui-0.2.0/src/services/clipboard.ts +38 -0
  28. agr_opentui-0.2.0/src/services/data.ts +81 -0
  29. agr_opentui-0.2.0/src/services/discover_filter.ts +82 -0
  30. agr_opentui-0.2.0/src/services/discover_labels.ts +158 -0
  31. agr_opentui-0.2.0/src/services/feedback.ts +62 -0
  32. agr_opentui-0.2.0/src/services/handle_match.ts +32 -0
  33. agr_opentui-0.2.0/src/services/input_mode.ts +35 -0
  34. agr_opentui-0.2.0/src/services/navigation.ts +75 -0
  35. agr_opentui-0.2.0/src/services/preview.ts +19 -0
  36. agr_opentui-0.2.0/src/services/render_actions.ts +11 -0
  37. agr_opentui-0.2.0/src/services/render_all.ts +29 -0
  38. agr_opentui-0.2.0/src/services/render_details.ts +42 -0
  39. agr_opentui-0.2.0/src/services/render_footer.ts +34 -0
  40. agr_opentui-0.2.0/src/services/render_help.ts +20 -0
  41. agr_opentui-0.2.0/src/services/render_list.ts +37 -0
  42. agr_opentui-0.2.0/src/services/render_missing_config.ts +14 -0
  43. agr_opentui-0.2.0/src/services/render_preview.ts +23 -0
  44. agr_opentui-0.2.0/src/services/render_run_modal.ts +35 -0
  45. agr_opentui-0.2.0/src/services/render_run_options.ts +30 -0
  46. agr_opentui-0.2.0/src/services/render_tabs.ts +23 -0
  47. agr_opentui-0.2.0/src/services/render_update_confirm.ts +26 -0
  48. agr_opentui-0.2.0/src/services/render_verify.ts +39 -0
  49. agr_opentui-0.2.0/src/services/runtime_ops.ts +59 -0
  50. agr_opentui-0.2.0/src/services/selection.ts +76 -0
  51. agr_opentui-0.2.0/src/services/skills_file.ts +90 -0
  52. agr_opentui-0.2.0/src/services/skills_source.ts +353 -0
  53. agr_opentui-0.2.0/src/services/ui_feedback.ts +41 -0
  54. agr_opentui-0.2.0/src/services/update.ts +163 -0
  55. agr_opentui-0.2.0/src/services/verify.ts +125 -0
  56. agr_opentui-0.2.0/src/services/verify_coordinator.ts +129 -0
  57. agr_opentui-0.2.0/src/services/visible_items.ts +24 -0
  58. agr_opentui-0.2.0/src/state.ts +134 -0
  59. agr_opentui-0.2.0/src/ui/controller.ts +291 -0
  60. agr_opentui-0.2.0/src/ui/layout.ts +504 -0
  61. agr_opentui-0.2.0/src/ui/render.ts +24 -0
  62. agr_opentui-0.2.0/src/ui/rows_render.ts +40 -0
  63. agr_opentui-0.2.0/src/ui.ts +17 -0
  64. agr_opentui-0.2.0/test/add_flow_service.test.ts +83 -0
  65. agr_opentui-0.2.0/test/agr_actions.test.ts +123 -0
  66. agr_opentui-0.2.0/test/agr_service.test.ts +151 -0
  67. agr_opentui-0.2.0/test/app_logic.test.ts +328 -0
  68. agr_opentui-0.2.0/test/clipboard_service.test.ts +53 -0
  69. agr_opentui-0.2.0/test/commands_service.test.ts +36 -0
  70. agr_opentui-0.2.0/test/data_service.test.ts +91 -0
  71. agr_opentui-0.2.0/test/discover_filter_service.test.ts +64 -0
  72. agr_opentui-0.2.0/test/discover_labels.test.ts +106 -0
  73. agr_opentui-0.2.0/test/feedback_service.test.ts +113 -0
  74. agr_opentui-0.2.0/test/handle_match_service.test.ts +39 -0
  75. agr_opentui-0.2.0/test/input_mode_service.test.ts +26 -0
  76. agr_opentui-0.2.0/test/navigation_service.test.ts +132 -0
  77. agr_opentui-0.2.0/test/preview_service.test.ts +43 -0
  78. agr_opentui-0.2.0/test/render_actions_service.test.ts +22 -0
  79. agr_opentui-0.2.0/test/render_all_service.test.ts +40 -0
  80. agr_opentui-0.2.0/test/render_details_service.test.ts +74 -0
  81. agr_opentui-0.2.0/test/render_footer_service.test.ts +51 -0
  82. agr_opentui-0.2.0/test/render_help_service.test.ts +45 -0
  83. agr_opentui-0.2.0/test/render_list_service.test.ts +77 -0
  84. agr_opentui-0.2.0/test/render_missing_config_service.test.ts +36 -0
  85. agr_opentui-0.2.0/test/render_preview_service.test.ts +61 -0
  86. agr_opentui-0.2.0/test/render_run_modal_service.test.ts +48 -0
  87. agr_opentui-0.2.0/test/render_run_options_service.test.ts +71 -0
  88. agr_opentui-0.2.0/test/render_tabs_service.test.ts +27 -0
  89. agr_opentui-0.2.0/test/render_update_confirm_service.test.ts +42 -0
  90. agr_opentui-0.2.0/test/render_verify_service.test.ts +47 -0
  91. agr_opentui-0.2.0/test/rows_render.test.ts +44 -0
  92. agr_opentui-0.2.0/test/runtime_doctor.test.ts +75 -0
  93. agr_opentui-0.2.0/test/runtime_handlers.test.ts +226 -0
  94. agr_opentui-0.2.0/test/runtime_ops_service.test.ts +68 -0
  95. agr_opentui-0.2.0/test/selection_service.test.ts +47 -0
  96. agr_opentui-0.2.0/test/skills_file_service.test.ts +50 -0
  97. agr_opentui-0.2.0/test/skills_source.test.ts +71 -0
  98. agr_opentui-0.2.0/test/ui_controller.test.ts +94 -0
  99. agr_opentui-0.2.0/test/ui_feedback_adapter.test.ts +48 -0
  100. agr_opentui-0.2.0/test/update_service.test.ts +108 -0
  101. agr_opentui-0.2.0/test/verify_coordinator_service.test.ts +52 -0
  102. agr_opentui-0.2.0/test/verify_service.test.ts +65 -0
  103. agr_opentui-0.2.0/test/visible_items_service.test.ts +42 -0
  104. agr_opentui-0.2.0/tsconfig.json +12 -0
  105. agr_opentui-0.2.0/uv.lock +216 -0
@@ -0,0 +1,19 @@
1
+ {
2
+ $schema: "https://docs.renovatebot.com/renovate-schema.json",
3
+ extends: [
4
+ "config:recommended",
5
+ ":dependencyDashboard",
6
+ ":automergeMinor"
7
+ ],
8
+ timezone: "UTC",
9
+ schedule: ["before 6am on monday"],
10
+ labels: ["dependencies"],
11
+ rangeStrategy: "bump",
12
+ packageRules: [
13
+ {
14
+ matchManagers: ["npm"],
15
+ matchUpdateTypes: ["major"],
16
+ automerge: false
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,75 @@
1
+ name: build-bin
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ release:
6
+ types: [published]
7
+
8
+ jobs:
9
+ build-linux:
10
+ runs-on: ubuntu-latest
11
+ env:
12
+ BASE_NAME: agr-opentui
13
+ steps:
14
+ - name: Checkout
15
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
16
+
17
+ - name: Setup Bun
18
+ uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
19
+ with:
20
+ bun-version: '1.3.8'
21
+
22
+ - name: Cache Bun dependencies
23
+ uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
24
+ with:
25
+ path: |
26
+ ~/.bun/install/cache
27
+ node_modules
28
+ key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
29
+ restore-keys: |
30
+ ${{ runner.os }}-bun-
31
+
32
+ - name: Install deps
33
+ run: bun install
34
+
35
+ - name: Build binary
36
+ run: make build
37
+
38
+ - name: Compute artifact name
39
+ id: name
40
+ shell: bash
41
+ run: |
42
+ sanitize() {
43
+ local value="$1"
44
+ value="${value//\//-}"
45
+ value="${value//_/-}"
46
+ echo "$value"
47
+ }
48
+
49
+ ref="${GITHUB_REF#refs/heads/}"
50
+ if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
51
+ tag="${GITHUB_REF#refs/tags/}"
52
+ safe_tag="$(sanitize "$tag")"
53
+ name="${BASE_NAME}-tag-${safe_tag}"
54
+ elif [[ "${ref}" == "master" ]]; then
55
+ name="${BASE_NAME}"
56
+ else
57
+ safe_ref="$(sanitize "$ref")"
58
+ name="${BASE_NAME}-branch-${safe_ref}"
59
+ fi
60
+ echo "name=${name}" >> "$GITHUB_OUTPUT"
61
+
62
+ - name: Rename binary
63
+ run: mv bin/agr-tui "bin/${{ steps.name.outputs.name }}"
64
+
65
+ - name: Upload artifact
66
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
67
+ with:
68
+ name: ${{ steps.name.outputs.name }}-linux
69
+ path: bin/${{ steps.name.outputs.name }}
70
+
71
+ - name: Upload to release
72
+ if: github.event_name == 'release'
73
+ uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
74
+ with:
75
+ files: bin/${{ steps.name.outputs.name }}
@@ -0,0 +1,48 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ lint-workflows:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - name: Checkout
12
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
13
+
14
+ - name: Lint GitHub workflows
15
+ uses: rhysd/actionlint@0933c147c9d6587653d45fdcb4c497c57a65f9af # v1.7.10
16
+
17
+ test:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - name: Checkout
21
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
22
+
23
+ - name: Setup Bun
24
+ uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
25
+ with:
26
+ bun-version: "1.3.8"
27
+
28
+ - name: Cache Bun dependencies
29
+ uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
30
+ with:
31
+ path: |
32
+ ~/.bun/install/cache
33
+ node_modules
34
+ key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
35
+ restore-keys: |
36
+ ${{ runner.os }}-bun-
37
+
38
+ - name: Install dependencies
39
+ run: bun install
40
+
41
+ - name: Run checks
42
+ run: bun run check
43
+
44
+ - name: Build binary
45
+ run: bun run build
46
+
47
+ - name: Run strict unused checks
48
+ run: bunx tsc --noEmit --noUnusedLocals --noUnusedParameters
@@ -0,0 +1,76 @@
1
+ name: publish-pypi
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+ inputs:
8
+ target:
9
+ description: "Where to publish the package"
10
+ required: true
11
+ type: choice
12
+ options:
13
+ - pypi
14
+ - testpypi
15
+ default: pypi
16
+
17
+ jobs:
18
+ build:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - name: Checkout
22
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
23
+
24
+ - name: Setup Python
25
+ uses: actions/setup-python@v6
26
+ with:
27
+ python-version: "3.14.3"
28
+
29
+ - name: Build package
30
+ run: |
31
+ python -m pip install --upgrade pip build
32
+ python -m build
33
+
34
+ - name: Upload dist artifacts
35
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
36
+ with:
37
+ name: python-dist
38
+ path: dist/
39
+
40
+ publish-pypi:
41
+ if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.target == 'pypi')
42
+ needs: build
43
+ runs-on: ubuntu-latest
44
+ permissions:
45
+ id-token: write
46
+ environment:
47
+ name: pypi
48
+ steps:
49
+ - name: Download dist artifacts
50
+ uses: actions/download-artifact@v7
51
+ with:
52
+ name: python-dist
53
+ path: dist/
54
+
55
+ - name: Publish to PyPI
56
+ uses: pypa/gh-action-pypi-publish@release/v1
57
+
58
+ publish-testpypi:
59
+ if: github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi'
60
+ needs: build
61
+ runs-on: ubuntu-latest
62
+ permissions:
63
+ id-token: write
64
+ environment:
65
+ name: testpypi
66
+ steps:
67
+ - name: Download dist artifacts
68
+ uses: actions/download-artifact@v7
69
+ with:
70
+ name: python-dist
71
+ path: dist/
72
+
73
+ - name: Publish to TestPyPI
74
+ uses: pypa/gh-action-pypi-publish@release/v1
75
+ with:
76
+ repository-url: https://test.pypi.org/legacy/
@@ -0,0 +1,34 @@
1
+ node_modules/
2
+ dist/
3
+ bin/
4
+ .tmp/
5
+ .cache/
6
+ *.log
7
+ .env
8
+ .env.*
9
+ .DS_Store
10
+ __pycache__/
11
+ *.py[cod]
12
+ .python-version
13
+ .venv/
14
+ venv/
15
+ .ruff_cache/
16
+ .mypy_cache/
17
+ .pytest_cache/
18
+ .coverage
19
+ coverage/
20
+ htmlcov/
21
+ .idea/
22
+ .vscode/
23
+ *.tsbuildinfo
24
+ *.pid
25
+ *.pid.lock
26
+ .bun/
27
+ *.tmp
28
+ *.swp
29
+ *.swo
30
+ /tmp/
31
+ .uv/
32
+ .claude/
33
+ .agents/
34
+ agr.toml
@@ -0,0 +1,34 @@
1
+ BUN ?= bun
2
+ PYTHON ?= python3
3
+ ENTRY ?= src/main.ts
4
+ OUT_DIR ?= bin
5
+ OUT ?= $(OUT_DIR)/agr-tui
6
+ DIST_DIR ?= dist
7
+
8
+ .PHONY: build clean
9
+ .PHONY: test check
10
+ .PHONY: py-build py-publish py-publish-test
11
+
12
+ build:
13
+ @mkdir -p $(OUT_DIR)
14
+ $(BUN) build $(ENTRY) --compile --outfile $(OUT)
15
+
16
+ test:
17
+ $(BUN) test
18
+
19
+ check:
20
+ $(BUN) run typecheck
21
+ $(BUN) test
22
+
23
+ py-build:
24
+ rm -rf $(DIST_DIR)
25
+ $(PYTHON) -m build
26
+
27
+ py-publish: py-build
28
+ $(PYTHON) -m twine upload $(DIST_DIR)/*
29
+
30
+ py-publish-test: py-build
31
+ $(PYTHON) -m twine upload --repository testpypi $(DIST_DIR)/*
32
+
33
+ clean:
34
+ rm -rf $(OUT_DIR)
@@ -0,0 +1,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: agr-opentui
3
+ Version: 0.2.0
4
+ Summary: OpenTUI frontend for agr
5
+ Requires-Python: >=3.14.3
6
+ Requires-Dist: agr>=0.7.4
@@ -0,0 +1,144 @@
1
+ # OpenTUI for agr
2
+
3
+ OpenTUI front-end for managing `agr` skills: browse `agr.toml`, install/remove skills, preview `SKILL.md`, and run skills via `agrx`. Skill discovery is based on `https://github.com/kasperjunge/agent-resources`.
4
+
5
+ ## Usage
6
+
7
+ Tabs:
8
+ - `Skills`: shows dependencies from `agr.toml` and install state.
9
+ - `Discover`: shows skills from `skills.json` (optional).
10
+
11
+ Common keys:
12
+ - `Tab` / `Shift+Tab`: switch tabs
13
+ - Arrow keys: move selection
14
+ - `space`: toggle select
15
+ - `H`: help
16
+ - `q`: quit
17
+
18
+ Skills tab keys:
19
+ - `a`: add skill (handle or `owner/repo/path`)
20
+ - `i`: install selected (bulk)
21
+ - `r`: remove selected (bulk)
22
+ - `v`: preview `SKILL.md`
23
+ - `g`: run with default options (`agrx`)
24
+ - `G`: open run options (tool, interactive, prompt, extra args)
25
+ - `u`: check discover list updates
26
+ - `U`: apply update (no confirm)
27
+ - `s`: apply update (confirm)
28
+ - `S`: apply update + sync (no confirm)
29
+ - `c`: reload config
30
+
31
+ Discover tab keys:
32
+ - `i`: add selected
33
+ - `y`: copy handle/repo to clipboard
34
+ - `a`: add skill
35
+ - `c`: reload config
36
+
37
+ Run options keys (when the modal is open):
38
+ - `t`: cycle tool
39
+ - `u`: toggle `--interactive`
40
+ - `p`: edit `--prompt`
41
+ - `e`: edit extra args
42
+ - `Enter`: run
43
+ - `Esc`: close
44
+
45
+ ## Quick Cheat Sheet
46
+
47
+ Primary flow:
48
+ - `Tab` to `Skills`
49
+ - Arrow keys to select
50
+ - `g` to run, `G` for run options
51
+
52
+ Bulk actions:
53
+ - `space` to multi-select
54
+ - `i` install or `r` remove (Skills tab)
55
+
56
+ Discover flow:
57
+ - `Tab` to `Discover`
58
+ - Arrow keys to select
59
+ - `i` to add, `y` to copy handle
60
+
61
+ ## Requirements
62
+
63
+ - Bun 1.3.8+
64
+ - Zig (required by OpenTUI build tooling)
65
+ - Python 3.10+
66
+ - `uv`
67
+ - `agr` + `agrx` on your `PATH`
68
+
69
+ ## Install
70
+
71
+ ```bash
72
+ bun install
73
+ ```
74
+
75
+ ## Run
76
+
77
+ ```bash
78
+ bun run src/main.ts
79
+ ```
80
+
81
+ Run it from the repo you want to manage (the current working directory is the target repo).
82
+ `agr.toml` is expected in that target repo for `agr add/remove/sync` operations.
83
+
84
+ ### Run From Target Repo
85
+
86
+ ```bash
87
+ cd /path/to/your/project
88
+ agr-tui
89
+ ```
90
+
91
+ `agr-opentui` itself does not need to contain your target repo's `agr.toml`.
92
+
93
+ ## Build
94
+
95
+ ```bash
96
+ bun run build
97
+ ```
98
+
99
+ This creates `bin/agr-tui`.
100
+
101
+ ## Publish to PyPI
102
+
103
+ One-time setup:
104
+ - Create a `pypi` environment in this GitHub repo.
105
+ - In your PyPI project settings, add this repo/workflow as a Trusted Publisher for `.github/workflows/publish-pypi.yml`.
106
+ - Optional: add `testpypi` environment and TestPyPI Trusted Publisher too.
107
+
108
+ Publish via GitHub Actions:
109
+ - Release publish: creating a GitHub release triggers publish to PyPI.
110
+ - Manual: run `publish-pypi` workflow and choose `pypi` or `testpypi`.
111
+
112
+ Publish from local machine:
113
+
114
+ ```bash
115
+ python -m pip install --upgrade build twine
116
+ make py-publish # Upload to PyPI
117
+ make py-publish-test # Upload to TestPyPI
118
+ ```
119
+
120
+ ## Discover List (`skills.json`)
121
+
122
+ If `skills.json` exists, the `Discover` tab will list its entries. It supports:
123
+
124
+ - An array of strings or objects (`{ "label": "...", "handle": "...", "repo": "owner/repo" }`).
125
+ - An object with `source` metadata and `skills` array (see `skills.json` in this repo).
126
+
127
+ When a `source` is configured, the app checks the remote list periodically (about every 6 hours) and can update `skills.json` using the `u`/`U`/`s`/`S` controls.
128
+
129
+ ## Troubleshooting
130
+
131
+ - `Missing agr.toml`: run the app from a repo that has `agr.toml`, or create one.
132
+ - `uv not found`: install `uv` and ensure it’s on `PATH`.
133
+ - `agr/agrx not found`: install `agr` and ensure it’s on `PATH`.
134
+ - `python not found`: install Python 3.
135
+ - `skills.json not found` or parse errors: fix the file format (array or `{ "source": ..., "skills": [...] }`).
136
+ - Discover list not updating: check the `source` URL/repo/branch/path and network access.
137
+ - `SKILL.md` preview says “not found”: the skill may not ship a `SKILL.md` or the path is nonstandard.
138
+ - Copy to clipboard doesn’t work: install `pbcopy` (macOS), `wl-copy` (Wayland), or `xclip` (X11).
139
+
140
+ ## Notes
141
+
142
+ - All actions run through `uv` (`uv run agr`, `uv run agrx`, and `uv run python -m agr_opentui.bridge`).
143
+ - If `agr.toml` is missing in the current directory, install/remove/sync commands are blocked and a warning is shown.
144
+ - Logs are written to `/tmp/agr-opentui.log`.
@@ -0,0 +1 @@
1
+ """OpenTUI bridge package for agr."""
@@ -0,0 +1,157 @@
1
+ """JSON bridge for the OpenTUI interface."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+
10
+ from agr.config import AgrConfig, Dependency, find_config, find_repo_root
11
+ from agr.fetcher import get_installed_skills
12
+ from agr.handle import ParsedHandle, parse_handle
13
+ from agr.skill import SKILL_MARKER
14
+ from agr.tool import get_tool
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class BridgeDependency:
19
+ identifier: str
20
+ handle: str | None
21
+ path: str | None
22
+ is_local: bool
23
+ display: str
24
+ candidates: list[str]
25
+ candidates_by_tool: dict[str, list[str]]
26
+ installed: bool
27
+ skill_md_path: str | None
28
+
29
+
30
+ def _parse_dependency(dep: Dependency) -> ParsedHandle:
31
+ if dep.handle:
32
+ return parse_handle(dep.handle, prefer_local=False)
33
+ if dep.path:
34
+ path = Path(dep.path)
35
+ return ParsedHandle(is_local=True, name=path.name, local_path=path)
36
+ raise ValueError("Dependency missing handle/path")
37
+
38
+
39
+ def _candidates_for_install(handle: ParsedHandle, tool_name: str) -> list[str]:
40
+ tool = get_tool(tool_name)
41
+ candidates = [
42
+ handle.to_skill_path(tool).as_posix(),
43
+ handle.name,
44
+ handle.to_installed_name(),
45
+ ]
46
+ if handle.is_local:
47
+ candidates.append(f"local/{handle.name}")
48
+ return list(dict.fromkeys(candidates))
49
+
50
+
51
+ def _installed_by_tool(repo_root: Path, tools: list[str]) -> dict[str, list[str]]:
52
+ installed: dict[str, list[str]] = {}
53
+ for tool in tools:
54
+ try:
55
+ installed[tool] = get_installed_skills(repo_root, get_tool(tool))
56
+ except Exception:
57
+ installed[tool] = []
58
+ return installed
59
+
60
+
61
+ def _resolve_skill_md_path(
62
+ dep: Dependency,
63
+ *,
64
+ handle: ParsedHandle,
65
+ repo_root: Path | None,
66
+ default_tool: str | None,
67
+ ) -> str | None:
68
+ if repo_root is None:
69
+ return None
70
+
71
+ if dep.path:
72
+ path = Path(dep.path)
73
+ if not path.is_absolute():
74
+ path = repo_root / path
75
+ skill_md = path / SKILL_MARKER
76
+ return str(skill_md) if skill_md.exists() else None
77
+
78
+ if not default_tool:
79
+ return None
80
+
81
+ tool = get_tool(default_tool)
82
+ skills_dir = tool.get_skills_dir(repo_root)
83
+ candidates = _candidates_for_install(handle, default_tool)
84
+ for candidate in candidates:
85
+ skill_md = skills_dir / candidate / SKILL_MARKER
86
+ if skill_md.exists():
87
+ return str(skill_md)
88
+ return None
89
+
90
+
91
+ def build_payload() -> dict[str, object]:
92
+ repo_root = find_repo_root()
93
+ config_path = find_config()
94
+ config = AgrConfig.load(config_path) if config_path else AgrConfig()
95
+
96
+ tool_names = list(config.tools)
97
+ default_tool = config.default_tool or (tool_names[0] if tool_names else None)
98
+
99
+ installed = (
100
+ _installed_by_tool(repo_root, tool_names) if repo_root and tool_names else {}
101
+ )
102
+
103
+ deps_payload: list[dict[str, object]] = []
104
+ for dep in config.dependencies:
105
+ handle = _parse_dependency(dep)
106
+ candidates = (
107
+ _candidates_for_install(handle, default_tool)
108
+ if default_tool
109
+ else []
110
+ )
111
+ candidates_by_tool = {
112
+ tool_name: _candidates_for_install(handle, tool_name)
113
+ for tool_name in tool_names
114
+ }
115
+ installed_names = installed.get(default_tool or "", [])
116
+ is_installed = any(name in installed_names for name in candidates)
117
+ skill_md_path = _resolve_skill_md_path(
118
+ dep,
119
+ handle=handle,
120
+ repo_root=repo_root,
121
+ default_tool=default_tool,
122
+ )
123
+
124
+ deps_payload.append(
125
+ BridgeDependency(
126
+ identifier=dep.identifier,
127
+ handle=dep.handle,
128
+ path=dep.path,
129
+ is_local=dep.is_local,
130
+ display=dep.identifier,
131
+ candidates=candidates,
132
+ candidates_by_tool=candidates_by_tool,
133
+ installed=is_installed,
134
+ skill_md_path=skill_md_path,
135
+ ).__dict__
136
+ )
137
+
138
+ return {
139
+ "repo_root": str(repo_root) if repo_root else None,
140
+ "config_path": str(config_path) if config_path else None,
141
+ "tools": tool_names,
142
+ "default_tool": default_tool,
143
+ "dependencies": deps_payload,
144
+ "installed": installed,
145
+ }
146
+
147
+
148
+ def main() -> None:
149
+ parser = argparse.ArgumentParser(description="OpenTUI data bridge")
150
+ parser.parse_args()
151
+
152
+ payload = build_payload()
153
+ print(json.dumps(payload))
154
+
155
+
156
+ if __name__ == "__main__":
157
+ main()