apmw 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.
@@ -0,0 +1,119 @@
1
+ name: Publish apmw
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: write
11
+ packages: write
12
+
13
+ jobs:
14
+ determine-version:
15
+ name: Determine Version
16
+ runs-on: ubuntu-latest
17
+ outputs:
18
+ semver: ${{ steps.version.outputs.semver }}
19
+ python-version: ${{ steps.version.outputs.python-version }}
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ with:
23
+ fetch-depth: 0
24
+ - name: Determine version
25
+ id: version
26
+ uses: GravionLabs/ci/versioning/determine-version@main
27
+ with:
28
+ gitversion-config-file: GitVersion.yml
29
+
30
+ build-test:
31
+ name: Build & Test
32
+ runs-on: ubuntu-latest
33
+ needs: determine-version
34
+ steps:
35
+ - name: Build
36
+ uses: GravionLabs/ci/python/build@main
37
+ with:
38
+ version: ${{ needs.determine-version.outputs.python-version }}
39
+
40
+ - name: Test
41
+ uses: GravionLabs/ci/python/test@main
42
+
43
+ build-executables:
44
+ name: Build executable (${{ matrix.suffix }})
45
+ runs-on: ${{ matrix.os }}
46
+ needs: determine-version
47
+ strategy:
48
+ matrix:
49
+ include:
50
+ - os: ubuntu-latest
51
+ suffix: linux
52
+ - os: macos-latest
53
+ suffix: macos-arm64
54
+ - os: windows-latest
55
+ suffix: windows.exe
56
+ steps:
57
+ - uses: actions/checkout@v4
58
+
59
+ - name: Setup Python (uv)
60
+ uses: GravionLabs/ci/python/setup@main
61
+
62
+ - name: Install dependencies
63
+ run: uv sync
64
+
65
+ - name: Build executable
66
+ run: uv run --with pyinstaller pyinstaller --onefile --name apmw-${{ matrix.suffix }} scripts/apmw_entrypoint.py
67
+
68
+ - name: Upload executable artifact
69
+ uses: actions/upload-artifact@v4
70
+ with:
71
+ name: apmw-${{ matrix.suffix }}
72
+ path: dist/apmw-${{ matrix.suffix }}
73
+ if-no-files-found: error
74
+
75
+ publish:
76
+ name: Publish to PyPI
77
+ if: github.event_name != 'pull_request'
78
+ runs-on: ubuntu-latest
79
+ permissions:
80
+ id-token: write
81
+ needs:
82
+ - determine-version
83
+ - build-test
84
+ steps:
85
+ - name: Publish Python package
86
+ uses: GravionLabs/ci/python/pypi/publish@main
87
+ with:
88
+ download-artifact: 'true'
89
+
90
+ release:
91
+ name: Release
92
+ if: github.event_name != 'pull_request'
93
+ runs-on: ubuntu-latest
94
+ needs:
95
+ - determine-version
96
+ - publish
97
+ - build-executables
98
+ permissions:
99
+ contents: write
100
+ steps:
101
+ - name: Create GitHub Release
102
+ uses: GravionLabs/ci/versioning/gh-release@main
103
+ with:
104
+ version: ${{ needs.determine-version.outputs.semver }}
105
+ tag-prefix: v
106
+
107
+ - name: Download executable artifacts
108
+ uses: actions/download-artifact@v4
109
+ with:
110
+ pattern: apmw-*
111
+ path: executables
112
+ merge-multiple: true
113
+
114
+ - name: Attach executables to release
115
+ env:
116
+ GH_TOKEN: ${{ github.token }}
117
+ run: |
118
+ TAG="v${{ needs.determine-version.outputs.semver }}"
119
+ gh release upload "$TAG" executables/* --clobber
apmw-0.1.0/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ *.spec
7
+ .venv/
8
+ .pytest_cache/
9
+ .coverage
10
+ coverage.xml
apmw-0.1.0/AGENTS.md ADDED
@@ -0,0 +1,50 @@
1
+ # apmw
2
+
3
+ Cross-platform wrapper CLI for [apm](https://github.com/GravionLabs). `apm install`/`apm update`
4
+ deploy platform-neutral skills cleanly, but agent files carry platform-specific frontmatter
5
+ (`permission`, `mode`, `tools`, `model`, etc.) that each target — Claude Code, OpenCode, GitHub
6
+ Copilot, Cursor, Codex — interprets differently. `apmw` runs the real `apm` command and then
7
+ strips/rewrites the installed agent frontmatter for the target platform. `apmw sanitize` exposes
8
+ the stripping step standalone, for re-sanitizing an already-installed agent directory without
9
+ invoking `apm` at all.
10
+
11
+ ## Repo layout
12
+
13
+ - `src/apmw/__init__.py` — the entire package: argument parsing, sanitization logic, and `apm`
14
+ subprocess invocation all live in this single file.
15
+ - `src/apmw/data/agent-strip-fields.json` — per-platform list of frontmatter fields to strip.
16
+ **Must be kept in sync with `apm-hub`'s `config/agent-strip-fields.json`** (used by
17
+ `src/generators/agent-target-generator.ts` there) — there is no automated sync, update both by
18
+ hand when the set of fields per platform changes.
19
+ - `tests/test_sanitize.py` — unit tests for the frontmatter-stripping logic (`sanitize_installed`,
20
+ `strip_fields`, `add_permission_block`).
21
+ - `tests/test_cli.py` — CLI-level tests: argument parsing and `main()` dispatch.
22
+
23
+ ## Key concepts
24
+
25
+ - `TARGET_DIRS` — local (CWD-relative) agent install dir per platform, e.g. `claude` → `.claude`.
26
+ - `GLOBAL_PATHS` — per-OS global agent install dirs per platform, used when `-g/--global` is passed.
27
+ - `TARGET_STRIP_FIELDS` — loaded from `agent-strip-fields.json`; which frontmatter keys to drop
28
+ per platform.
29
+ - `do_sanitize(platform, global_, source)` — the core operation: sanitizes either the local or
30
+ global agent dir for a platform, optionally also sanitizing source files under `packages/`.
31
+ - `sanitize_installed` / `sanitize_source` — the actual file-rewriting functions `do_sanitize`
32
+ calls.
33
+ - Subcommands (`build_parser()` in `src/apmw/__init__.py`): `install` and `update` run the real
34
+ `apm` binary via `run_apm()` and then call `do_sanitize()`; `sanitize` calls `do_sanitize()`
35
+ directly with no `apm` invocation.
36
+
37
+ ## Dev workflow
38
+
39
+ ```bash
40
+ uv sync
41
+ uv run pytest
42
+ ```
43
+
44
+ No linter or formatter is configured in `pyproject.toml` — don't introduce one speculatively.
45
+
46
+ ## Style
47
+
48
+ The codebase has no docstrings and minimal comments (only where a non-obvious rationale needs
49
+ capturing, e.g. the `claude` strip-fields comment). Keep additions flat and terse — match what's
50
+ already there rather than adding documentation blocks.
@@ -0,0 +1,2 @@
1
+ mode: ContinuousDeployment
2
+ next-version: 0.1.0
apmw-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.5
2
+ Name: apmw
3
+ Version: 0.1.0
4
+ Summary: Cross-platform wrapper for the apm CLI — sanitizes agent frontmatter per target
5
+ Requires-Python: >=3.10
apmw-0.1.0/README.md ADDED
@@ -0,0 +1,308 @@
1
+ # apmw
2
+
3
+ Cross-platform wrapper for the [apm](https://github.com/GravionLabs) CLI.
4
+
5
+ `apm install` and `apm update` deploy platform-neutral skills cleanly, but
6
+ agent files carry platform-specific frontmatter (`permission`, `mode`,
7
+ `tools`, etc.) that each target — Claude Code, OpenCode, GitHub Copilot,
8
+ Cursor, Codex — interprets differently. `apmw` runs the real `apm` command
9
+ and then sanitizes the installed agent frontmatter for the target platform
10
+ you specify.
11
+
12
+ ---
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pipx install apmw
18
+ # or
19
+ uvx apmw install -p claude
20
+ ```
21
+
22
+ Prebuilt single-file executables (linux, macOS-arm64, windows) that need no Python environment
23
+ are attached to each [GitHub Release](https://github.com/GravionLabs/apmw/releases).
24
+
25
+ ### Local development install
26
+
27
+ To install the local checkout (editable — picks up source edits immediately):
28
+
29
+ ```bash
30
+ ./install.sh # Linux / macOS
31
+ .\install.ps1 # Windows
32
+ ```
33
+
34
+ Uses `uv tool install` if `uv` is on `PATH`, falling back to `pipx`.
35
+
36
+ ---
37
+
38
+ ## Usage
39
+
40
+ ```bash
41
+ apmw install -p claude # apm install + sanitize for Claude Code
42
+ apmw install -p opencode # apm install + sanitize for OpenCode
43
+ apmw update -p copilot # apm update + sanitize for GitHub Copilot
44
+ apmw sanitize -p claude # sanitize an already-installed local agent dir (no apm call)
45
+ apmw sanitize -g -p claude # sanitize the global Claude Code agent dir
46
+ apmw sanitize # sanitize for every known platform
47
+ ```
48
+
49
+ **Flags:**
50
+
51
+ | Flag | Description |
52
+ | :--- | :---------- |
53
+ | `-p, --platform` | Target: `opencode`, `copilot`, `claude`, `cursor`, `codex`; omit to run for all |
54
+ | `-g, --global` | Sanitize the platform's global agent install directory instead of the project dir |
55
+ | `-s, --source` | Also inject `permission:` blocks into source agent files under `packages/` |
56
+ | `-y, --yes` | (`update` only) Skip consent prompt |
57
+ | `--dry-run` | (`update` only) Preview changes without writing |
58
+
59
+ `apmw install`/`update` require `apm` to be installed and on `PATH`.
60
+ `apmw sanitize` operates only on local files and does not invoke `apm`.
61
+
62
+ ---
63
+
64
+ ## Global install — all packages
65
+
66
+ Install the full apm-hub skill set into the global agent directories so every
67
+ repository on the machine benefits without a per-project `apm install`:
68
+
69
+ ```bash
70
+ # 1. Register the marketplace (once per machine)
71
+ apm marketplace add GravionLabs/apm-hub
72
+
73
+ # 2. Install the meta-package globally
74
+ apmw install all@apm-hub -g -p opencode
75
+ apmw install all@apm-hub -g -p claude
76
+ apmw install all@apm-hub -g -p copilot
77
+ ```
78
+
79
+ `apmw install` calls `apm install -g` (which deploys to global paths) and then
80
+ sanitizes all agent files in those paths for the target platform.
81
+
82
+ **Global agent paths per platform:**
83
+
84
+ | Platform | Linux / macOS | Windows |
85
+ | :------------- | :----------------------------------------------- | :------------------------------------------ |
86
+ | OpenCode | `~/.config/opencode/agents/` | `%APPDATA%\opencode\agents\` |
87
+ | Claude Code | `~/.claude/agents/` · `~/.config/claude/agents/` | `%APPDATA%\Claude\agents\` |
88
+ | GitHub Copilot | `~/.config/github-copilot/agents/` | `%APPDATA%\GitHub Copilot\agents\` |
89
+ | Cursor | `~/.cursor/agents/` | `%APPDATA%\Cursor\agents\` |
90
+ | Codex | `~/.codex/agents/` | `%APPDATA%\Codex\agents\` |
91
+
92
+ ### Transitive MCP server definitions
93
+
94
+ When a package declares MCP server entries in its `apm.yml`, `apm install`
95
+ propagates those definitions to consuming projects transitively. For example,
96
+ the `github` package may ship an MCP server configuration for the GitHub API:
97
+
98
+ ```yaml
99
+ # packages/github/apm.yml
100
+ name: github
101
+ version: 1.0.0
102
+ mcp:
103
+ servers:
104
+ - name: github
105
+ command: npx
106
+ args: ["-y", "@modelcontextprotocol/server-github"]
107
+ env:
108
+ GITHUB_PERSONAL_ACCESS_TOKEN: "${GITHUB_TOKEN}"
109
+ ```
110
+
111
+ When you run `apmw install all@apm-hub -g -p opencode`, the `all` meta-package
112
+ pulls in `github` (and every other package) transitively. `apm` writes the MCP
113
+ server entries to the platform's agent config file (e.g.
114
+ `~/.config/opencode/mcp.json`) so every project on the machine can use GitHub
115
+ MCP tools without any per-project setup.
116
+
117
+ ---
118
+
119
+ ## Multi-target agent authoring
120
+
121
+ Agent source files (`*.agent.md`) are written in **GitHub Copilot style** as
122
+ the primary format. Each platform understands only a subset of frontmatter
123
+ fields — `apmw` strips the incompatible ones and can also inject
124
+ platform-specific additions via a reserved `metadata:` key.
125
+
126
+ ### Field compatibility
127
+
128
+ | Field | Copilot | OpenCode | Claude Code | Cursor / Codex |
129
+ | :--------------- | :-----: | :------: | :---------: | :------------: |
130
+ | `name` | ✅ | ✅ | ✅ | ✅ |
131
+ | `description` | ✅ | ✅ | ✅ | ✅ |
132
+ | `tools` | ✅ | ❌ | ✅ * | ✅ |
133
+ | `mode` | ✅ | ❌ | ❌ | ❌ |
134
+ | `steps` | ✅ | ❌ | ❌ | ❌ |
135
+ | `color` | ✅ | ❌ | ❌ | ❌ |
136
+ | `permission` | ❌ | ✅ | ❌ | ❌ |
137
+ | `user-invocable` | ✅ | ❌ | ❌ | ❌ |
138
+ | `model` | ✅ | ✅ | ❌ | ✅ |
139
+ | `agents` | ✅ | ❌ | ❌ | ✅ |
140
+
141
+ *Claude Code uses different tool identifiers than Copilot (`Bash` vs `run_in_terminal`, etc.).
142
+
143
+ ### `metadata:` — platform-specific overrides
144
+
145
+ Add a `metadata:` block to supply fields that only apply to a specific
146
+ platform. `apmw` extracts the block for the target platform, injects the
147
+ lines into the sanitized frontmatter, and strips `metadata:` itself from all
148
+ outputs. **Metadata wins on conflict** — if both the top level and
149
+ `metadata.opencode` define `permission:`, the metadata version is used.
150
+
151
+ ```yaml
152
+ ---
153
+ name: dev-workflow
154
+ description: "Dev workflow orchestrator."
155
+ # Copilot-native fields — kept as-is in the Copilot output
156
+ mode: all
157
+ steps: 40
158
+ color: primary
159
+ tools: ['agent', 'read', 'search']
160
+ user-invocable: true
161
+ # Platform-specific overrides (never appear in any output file)
162
+ metadata:
163
+ opencode:
164
+ permission:
165
+ read: allow
166
+ edit: deny
167
+ glob: allow
168
+ grep: allow
169
+ bash:
170
+ "*": deny
171
+ task: allow
172
+ skill: allow
173
+ webfetch: allow
174
+ question: allow
175
+ claude:
176
+ tools:
177
+ - Bash
178
+ - Read
179
+ - Glob
180
+ - Grep
181
+ - WebFetch
182
+ - Task
183
+ ---
184
+
185
+ Agent body — unchanged across all platforms.
186
+ ```
187
+
188
+ ### What each platform receives
189
+
190
+ **`.github/agents/dev-workflow.agent.md`** — Copilot output (`metadata:` stripped, all else kept):
191
+ ```yaml
192
+ ---
193
+ name: dev-workflow
194
+ description: "Dev workflow orchestrator."
195
+ mode: all
196
+ steps: 40
197
+ color: primary
198
+ tools: ['agent', 'read', 'search']
199
+ user-invocable: true
200
+ ---
201
+ ```
202
+
203
+ **`.opencode/agents/dev-workflow.md`** — `permission:` injected from `metadata.opencode`; Copilot-only fields stripped by default:
204
+ ```yaml
205
+ ---
206
+ name: dev-workflow
207
+ description: "Dev workflow orchestrator."
208
+ mode: all
209
+ steps: 40
210
+ color: primary
211
+ permission:
212
+ read: allow
213
+ edit: deny
214
+ glob: allow
215
+ grep: allow
216
+ bash:
217
+ "*": deny
218
+ task: allow
219
+ skill: allow
220
+ webfetch: allow
221
+ question: allow
222
+ ---
223
+ ```
224
+
225
+ **`.claude/agents/dev-workflow.md`** — Claude-native `tools:` injected from `metadata.claude`; all incompatible fields stripped:
226
+ ```yaml
227
+ ---
228
+ name: dev-workflow
229
+ description: "Dev workflow orchestrator."
230
+ tools:
231
+ - Bash
232
+ - Read
233
+ - Glob
234
+ - Grep
235
+ - WebFetch
236
+ - Task
237
+ ---
238
+ ```
239
+
240
+ ### Strip rules
241
+
242
+ The strip rules are loaded from `src/apmw/data/agent-strip-fields.json` and
243
+ must be kept in sync with `apm-hub`'s `config/agent-strip-fields.json`:
244
+
245
+ ```json
246
+ {
247
+ "opencode": ["tools", "user-invocable", "agents", "metadata"],
248
+ "copilot": ["mode", "steps", "color", "permission", "task", "metadata"],
249
+ "claude": ["mode", "steps", "color", "tools", "permission", "user-invocable", "agents", "model", "metadata"],
250
+ "cursor": ["mode", "steps", "color", "permission", "metadata"],
251
+ "codex": ["mode", "steps", "color", "permission", "metadata"]
252
+ }
253
+ ```
254
+
255
+ `metadata` appears in every platform's list — it is always stripped after extraction.
256
+
257
+ ### Processing pipeline
258
+
259
+ For each `*.md` file in the target agent directory, `apmw` runs:
260
+
261
+ 1. **Extract** — find `metadata.<platform>` and de-indent its content lines.
262
+ 2. **Strip** — remove fields in the platform's strip list (including `metadata`).
263
+ 3. **Inject** — if step 1 produced lines: strip any conflicting top-level fields, then append the extracted lines. Metadata wins.
264
+ 4. **Rewrite** — write the file only if the frontmatter changed.
265
+
266
+ ### OpenCode `permission:` reference
267
+
268
+ ```yaml
269
+ permission:
270
+ read: allow # file read
271
+ edit: deny # file write/edit
272
+ glob: allow # file glob search
273
+ grep: allow # content search
274
+ bash:
275
+ "*": deny # deny all shell commands by default
276
+ "git *": allow # allow specific patterns
277
+ "gh *": allow
278
+ task: allow # spawn sub-agents
279
+ skill: allow # load skills
280
+ webfetch: allow # HTTP fetch
281
+ question: allow # interactive prompts
282
+ ```
283
+
284
+ ### Claude Code `tools:` reference
285
+
286
+ ```yaml
287
+ tools:
288
+ - Bash # shell commands
289
+ - Read # read files
290
+ - Write # create/overwrite files
291
+ - Edit # patch existing files
292
+ - Glob # file pattern search
293
+ - Grep # content search
294
+ - WebFetch # HTTP fetch
295
+ - Task # spawn sub-agents
296
+ - TodoWrite # task list management
297
+ ```
298
+
299
+ ---
300
+
301
+ ## Development
302
+
303
+ ```bash
304
+ uv sync
305
+ uv run pytest
306
+ ```
307
+
308
+ No linter or formatter is configured — don't introduce one speculatively.
apmw-0.1.0/install.ps1 ADDED
@@ -0,0 +1,13 @@
1
+ $ErrorActionPreference = "Stop"
2
+ $ScriptDir = $PSScriptRoot
3
+
4
+ if (Get-Command uv -ErrorAction SilentlyContinue) {
5
+ uv tool install --editable $ScriptDir --force
6
+ } elseif (Get-Command pipx -ErrorAction SilentlyContinue) {
7
+ pipx install --editable $ScriptDir --force
8
+ } else {
9
+ Write-Error "neither 'uv' nor 'pipx' found on PATH"
10
+ exit 1
11
+ }
12
+
13
+ Write-Host "apmw installed — run 'apmw --help' to verify."
apmw-0.1.0/install.sh ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4
+
5
+ if command -v uv >/dev/null 2>&1; then
6
+ uv tool install --editable "$SCRIPT_DIR" --force
7
+ elif command -v pipx >/dev/null 2>&1; then
8
+ pipx install --editable "$SCRIPT_DIR" --force
9
+ else
10
+ echo "error: neither 'uv' nor 'pipx' found on PATH" >&2
11
+ exit 1
12
+ fi
13
+
14
+ echo "apmw installed — run 'apmw --help' to verify."
@@ -0,0 +1,20 @@
1
+ [project]
2
+ name = "apmw"
3
+ version = "0.1.0"
4
+ description = "Cross-platform wrapper for the apm CLI — sanitizes agent frontmatter per target"
5
+ requires-python = ">=3.10"
6
+ dependencies = []
7
+
8
+ [project.scripts]
9
+ apmw = "apmw:main"
10
+
11
+ [build-system]
12
+ requires = ["hatchling"]
13
+ build-backend = "hatchling.build"
14
+
15
+ [dependency-groups]
16
+ dev = ["pytest>=8", "pytest-cov>=5"]
17
+
18
+ [tool.pytest.ini_options]
19
+ testpaths = ["tests"]
20
+ pythonpath = ["src"]
@@ -0,0 +1,4 @@
1
+ from apmw import main
2
+
3
+ if __name__ == "__main__":
4
+ main()