@weiyentan/opencode-plugin-awx 0.2.0

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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +262 -0
  3. package/dist/auth.d.ts +112 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +180 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/client.d.ts +148 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +334 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/contracts/job-detail.d.ts +141 -0
  12. package/dist/contracts/job-detail.d.ts.map +1 -0
  13. package/dist/contracts/job-detail.js +98 -0
  14. package/dist/contracts/job-detail.js.map +1 -0
  15. package/dist/contracts/sync-project.d.ts +31 -0
  16. package/dist/contracts/sync-project.d.ts.map +1 -0
  17. package/dist/contracts/sync-project.js +30 -0
  18. package/dist/contracts/sync-project.js.map +1 -0
  19. package/dist/index.d.ts +16 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +754 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/job-status.d.ts +116 -0
  24. package/dist/job-status.d.ts.map +1 -0
  25. package/dist/job-status.js +168 -0
  26. package/dist/job-status.js.map +1 -0
  27. package/dist/launch.d.ts +76 -0
  28. package/dist/launch.d.ts.map +1 -0
  29. package/dist/launch.js +133 -0
  30. package/dist/launch.js.map +1 -0
  31. package/dist/list-projects.d.ts +63 -0
  32. package/dist/list-projects.d.ts.map +1 -0
  33. package/dist/list-projects.js +84 -0
  34. package/dist/list-projects.js.map +1 -0
  35. package/dist/list-templates.d.ts +60 -0
  36. package/dist/list-templates.d.ts.map +1 -0
  37. package/dist/list-templates.js +120 -0
  38. package/dist/list-templates.js.map +1 -0
  39. package/dist/metrics.d.ts +174 -0
  40. package/dist/metrics.d.ts.map +1 -0
  41. package/dist/metrics.js +275 -0
  42. package/dist/metrics.js.map +1 -0
  43. package/dist/transforms.d.ts +52 -0
  44. package/dist/transforms.d.ts.map +1 -0
  45. package/dist/transforms.js +108 -0
  46. package/dist/transforms.js.map +1 -0
  47. package/package.json +56 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 weiyentan
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.
package/README.md ADDED
@@ -0,0 +1,262 @@
1
+ # AWX Plugin (`@weiyentan/opencode-plugin-awx`)
2
+
3
+ OpenCode server plugin for [AWX](https://github.com/ansible/awx) / Ansible Automation Platform (AAP). Provides native tool access to job templates, projects, and job lifecycle operations.
4
+
5
+ ## Status
6
+
7
+ ✅ **Phase 0 — Repository Scaffolding** (complete)
8
+ ✅ **Phase 1 — Client Infrastructure** (complete)
9
+ ✅ **Phase 2 — Tool Implementation** (complete)
10
+
11
+ The AWX plugin delivers these modules:
12
+
13
+ | Module | File | Purpose |
14
+ |--------|------|---------|
15
+ | **Plugin entry** | `src/index.ts` | Registers all AWX tools (awx-list-templates, awx-list-projects, awx-launch-job, awx-job-status, awx-wait-job, awx-get-job-events, awx-sync-project) + hello-world scaffold; wires HTTP client, metrics lifecycle, and dispose hook |
16
+ | **Auth hook** | `src/auth.ts` | Bearer token / PAT authentication via OpenCode's `type: "api"` auth hook with init-time validation |
17
+ | **Output contract** | `src/contracts/job-detail.ts` | Zod schemas and TypeScript types (`JobDetailOutput`) matching `awx_job_detail.py` v1.0 |
18
+ | **Transforms** | `src/transforms.ts` | Pure functions: SSH→HTTPS URL conversion, git branch inference, required-var validation |
19
+ | **Client middleware** | `src/client.ts` | HTTP middleware pipeline: circuit breaker, retry/backoff, timeout via native `fetch` |
20
+ | **Metrics** | `src/metrics.ts` | Per-tool counters with file-backed durability for operational visibility |
21
+ | **Node shim** | `src/node-shim.d.ts` | Minimal Node.js built-in declarations (avoids `@types/node` dependency) |
22
+ | **Snapshot generator** | `scripts/generate-snapshots.py` | Python script that regenerates contract snapshots from fixture data |
23
+
24
+ Tool implementation (Phase 2) is complete — all 7 AWX tools are implemented and tested. See the [issue tracker](https://github.com/weiyentan/opencode-plugins/issues) for upcoming enhancements.
25
+
26
+ ## Prerequisites
27
+
28
+ - **Node.js** >= 18.0.0 (Node 18 compatibility is handled transparently — the client middleware includes `anyAbortSignal()` and `createTimeoutSignal()` fallbacks)
29
+ - **npm** >= 9.0.0 (ships with Node.js 18)
30
+ - TypeScript knowledge for plugin development
31
+ - Access to an AAP instance for integration testing (optional — unit tests run offline)
32
+
33
+ ## Setup
34
+
35
+ ```bash
36
+ # From the monorepo root:
37
+ npm install
38
+
39
+ # Or from this package directly:
40
+ cd packages/awx
41
+ npm install
42
+ ```
43
+
44
+ This package is consumed by the OpenCode plugin server as a dependency — no standalone runtime is needed.
45
+
46
+ ## Scripts
47
+
48
+ | Command | Description |
49
+ |---------|-------------|
50
+ | `npm run build` | Compile TypeScript to `dist/` (`tsc`) |
51
+ | `npm test` | Run the Vitest test suite (`vitest run`) |
52
+ | `npm run test:watch` | Run tests in watch mode |
53
+ | `npm run lint` | Type-check without emitting (`tsc --noEmit`) |
54
+ | `npm run typecheck` | Alias for `lint` — strict type checking |
55
+
56
+ ## Testing
57
+
58
+ ```bash
59
+ # Unit tests (no AAP instance required)
60
+ npm test
61
+ ```
62
+
63
+ Tests follow TDD (test-driven development) with [Vitest](https://vitest.dev) and verify behavior through the public plugin interface.
64
+
65
+ ### Running Integration Tests
66
+
67
+ Integration tests in `tests/integration/` exercise the plugin's tools against a **live AAP instance** through the plugin's own tool registration mechanism.
68
+
69
+ #### Read-Only Tools
70
+
71
+ The suite `tests/integration/read-only.test.ts` covers `awx-list-templates` and `awx-list-projects`.
72
+
73
+ #### Job Lifecycle Tools
74
+
75
+ The suite `tests/integration/job-lifecycle.test.ts` covers the full AWX job lifecycle:
76
+
77
+ - `awx-launch-job` → launches a job template
78
+ - `awx-job-status` → fetches structured job detail (v1.0 output contract)
79
+ - `awx-wait-job` → non-blocking status check (no polling)
80
+ - `awx-get-job-events` → retrieves job events
81
+
82
+ #### Prerequisites
83
+
84
+ | Env Var | Default | Required | Description |
85
+ |---------|---------|----------|-------------|
86
+ | `AWX_TOKEN` | — | **Yes** | Valid AAP Personal Access Token (PAT) |
87
+ | `AAP_BASE_URL` | `https://example.com` | No | Base URL of the AAP instance |
88
+ | `JOB_TEMPLATE_ID` | `10` | No | Non-production AWX job template ID to launch |
89
+ | `EXTRA_VARS_INVENTORY` | `"test"` | No | Inventory name for extra_vars |
90
+ | `EXTRA_VARS_SCM_URL` | `"https://github.com/example/repo.git"` | No | SCM URL for extra_vars |
91
+ | `EXTRA_VARS_SCM_BRANCH` | `"main"` | No | SCM branch for extra_vars |
92
+
93
+ > **Important:** Use a non-production job template. The launch tool starts a real job on AAP. The plugin's transforms pipeline requires `inventory`, `scm_url`, and `scm_branch` in extra_vars — configure them via env vars to match your template's expectations.
94
+
95
+ #### Run Command
96
+
97
+ ```bash
98
+ # From packages/awx/
99
+ export AWX_TOKEN=your_pat_token_here
100
+ npx vitest run tests/integration/
101
+
102
+ # Run a specific suite:
103
+ npx vitest run tests/integration/read-only.test.ts
104
+ npx vitest run tests/integration/job-lifecycle.test.ts
105
+
106
+ # With custom AAP URL:
107
+ export AWX_TOKEN=your_pat_token_here
108
+ export AAP_BASE_URL=https://my-aap.internal.example.com
109
+ npx vitest run tests/integration/
110
+ ```
111
+
112
+ > **Note**: Integration tests are gated behind `AWX_TOKEN`. When `AWX_TOKEN` is not set, the live AAP tests are silently skipped using `describe.skipIf(!process.env.AWX_TOKEN)`.
113
+
114
+ #### Agent-Side Polling Pattern
115
+
116
+ Job lifecycle tools use an **agent-side polling** pattern (see ADR 0004):
117
+ - `awx-launch-job` returns immediately with a job ID.
118
+ - `awx-job-status` / `awx-wait-job` return the current status — the agent must loop to poll for completion.
119
+ - `awx-get-job-events` retrieves events from a completed or running job.
120
+
121
+ No tool blocks waiting for job completion. This avoids hanging the agent's execution loop and gives the agent control over polling strategy (poll interval, max attempts, timeout).
122
+
123
+ ### Contract Tests
124
+
125
+ Contract tests (`tests/contract.test.ts`) validate that the TypeScript `JobDetailOutput` interface and zod schema match the Python `awx_job_detail.py` v1.0 output contract. A **snapshot-based approach** is used for CI safety:
126
+
127
+ - Fixture JSON files in `tests/fixtures/` represent pre-baked snapshots of the Python output contract.
128
+ - Tests parse each fixture through the zod schema and assert structural correctness.
129
+ - No live Python subprocess is executed — the tests are pure TypeScript and run in any CI environment.
130
+
131
+ #### Fixture Files
132
+
133
+ | Fixture | Purpose |
134
+ |---------|---------|
135
+ | `awx_job_success.json` | A successful job with no failures or unreachable hosts |
136
+ | `awx_job_partial.json` | A job that completed with some unreachable hosts |
137
+ | `awx_job_failure.json` | A failed job with task-level errors |
138
+
139
+ #### Regenerating Contract Snapshots
140
+
141
+ When the Python `awx_job_detail.py` v1.0 output contract changes (e.g., new fields are added or field types change), the fixture snapshots must be regenerated:
142
+
143
+ 1. Run the Python `awx_job_detail.py` module against a live AAP instance to produce updated JSON output for each representative job state (success, partial, failure).
144
+ 2. Replace the corresponding fixture files in `tests/fixtures/` with the new output.
145
+ 3. Update `src/contracts/job-detail.ts` if the schema has changed (add/modify zod fields and TypeScript types).
146
+ 4. Run `npm test` to verify the new snapshots pass schema validation.
147
+ 5. Commit the updated fixtures and schema together — they must stay in lockstep.
148
+
149
+ > **Important**: Fixtures are checked into the repository. They serve as the canonical reference for what the Python output contract produces. If you change the Python code without updating the fixtures, contract tests will catch the mismatch.
150
+ ## Hot-Reload
151
+
152
+ The OpenCode plugin server watches plugin source files and **automatically reloads** when changes are detected — no server restart required. This was verified during initial scaffolding:
153
+
154
+ 1. The plugin is registered by the OpenCode server (consuming `src/index.ts` as the entry point).
155
+ 2. Modifying the tool's `description` field in `src/index.ts` (e.g., changing the hello-world description text) triggers a plugin reload.
156
+ 3. The server picks up the new description on the next tool invocation.
157
+
158
+ ### Known Limitation
159
+
160
+ Hot-reload verification is performed structurally (the `tsc --noEmit` / `vitest run` cycle confirms the module compiles and tool execute signature is correct) but end-to-end hot-reload testing requires a running OpenCode server instance. Full integration testing of hot-reload behavior is tracked for a future enhancement.
161
+
162
+ ### Entry Points
163
+
164
+ The `package.json` `main`, `types`, and `exports` fields point to the compiled `dist/` output. This is the production-safe configuration — consumers import the compiled JavaScript with type declarations.
165
+
166
+ For local development, the OpenCode plugin server can consume TypeScript source directly by overriding the entry point (e.g., changing `main` to `./src/index.ts`). The server watches source files and reloads automatically on change — no server restart required.
167
+
168
+ ## CI Requirements
169
+
170
+ CI pipelines must run the following in order:
171
+
172
+ ```bash
173
+ npm ci # Clean install
174
+ npm run lint # Type checking
175
+ npm test # Unit tests
176
+ npm run build # Production build
177
+ ```
178
+
179
+ ### Required CI Environment
180
+
181
+ - **Node.js** 18.x or 20.x LTS
182
+ - **npm** 9.x+
183
+ - No secrets required for unit tests (`npm test`)
184
+ - Integration tests are gated behind `AWX_TOKEN` and run separately
185
+
186
+ ## Toolchain Decisions
187
+
188
+ ### npm Workspaces (not pnpm)
189
+
190
+ This package lives inside an **npm workspaces** monorepo. The root `package.json` declares `packages/*` as workspaces. npm workspaces were chosen over pnpm for:
191
+
192
+ 1. **Zero-install overhead** — npm ships with Node.js, no extra package manager needed.
193
+ 2. **CI simplicity** — GitHub Actions runners include npm by default.
194
+ 3. **Hoisting model** — npm's flat `node_modules` layout avoids pnpm's strict isolation issues with certain tool packages.
195
+
196
+ ### Single tsconfig.json (not project references)
197
+
198
+ A single `tsconfig.json` at `packages/awx/tsconfig.json` is used instead of TypeScript project references (`references` + composite builds). This was chosen because:
199
+
200
+ 1. **Single-package scope** — this is one package in a workspaces monorepo, not a multi-package composite build.
201
+ 2. **Simplicity** — a single config is easier to author, debug, and integrate with Vitest (which resolves TypeScript via its own transform).
202
+ 3. **Future readiness** — if the plugin is split into sub-packages (e.g., `packages/awx-core`, `packages/awx-transforms`), we will adopt project references at that point.
203
+
204
+ ## Architecture
205
+
206
+ ### Module Structure
207
+
208
+ ```
209
+ packages/awx/
210
+ ├── src/
211
+ │ ├── index.ts # Plugin entry point — Hooks (auth + tools + dispose); client wiring & metrics lifecycle
212
+ │ ├── auth.ts # Bearer token auth hook (type: "api")
213
+ │ ├── client.ts # HTTP middleware pipeline (circuit breaker, retry, timeout)
214
+ │ ├── metrics.ts # Per-tool counters with file-backed durability
215
+ │ ├── node-shim.d.ts # Minimal Node.js declarations (fs/promises, path)
216
+ │ └── contracts/
217
+ │ └── job-detail.ts # JobDetailOutput v1.0 TypeScript interface
218
+ ├── tests/
219
+ │ ├── plugin.test.ts # Plugin registration and lifecycle tests
220
+ │ ├── client.test.ts # Client middleware pipeline tests
221
+ │ ├── lifecycle.test.ts # Lazy client/auth lifecycle tests (no-token → token → client-created)
222
+ │ ├── metrics.test.ts # MetricsStore persistence & counter tests incl. concurrent serialization
223
+ │ ├── plugin-init-timeout.test.ts # Init-time timeout cleanup tests (clear() called after validation)
224
+ │ ├── contracts/
225
+ │ │ ├── contract.test.ts # Contract compatibility tests
226
+ │ │ └── __snapshots__/ # Canonical contract output (ground truth)
227
+ │ └── fixtures/
228
+ │ ├── awx_job_success.json
229
+ │ ├── awx_job_partial.json
230
+ │ └── awx_job_failure.json
231
+ └── scripts/
232
+ └── generate-snapshots.py # Python script to regenerate snapshots
233
+ ```
234
+
235
+ ### Auth Hook
236
+
237
+ The plugin uses OpenCode's `type: "api"` auth hook for bearer token (Personal Access Token) authentication. On plugin load:
238
+
239
+ 1. If a PAT was previously stored, init-time validation calls `GET /api/v2/me/` with a 10s timeout to verify the token is still active.
240
+ 2. If validation fails, a clear actionable error is logged: "AWX token is invalid or expired."
241
+ 3. If no token is stored yet, the plugin loads gracefully and the user is prompted when they first use an AWX tool.
242
+
243
+ ### Output Contract
244
+
245
+ All job-related tools return output matching the `JobDetailOutput` interface (see `src/contracts/job-detail.ts`). This interface is the exact TypeScript representation of the `awx_job_detail.py` v1.0 schema. Key naming:
246
+
247
+ - Use `host_status_counts` — NOT `host_summary`
248
+ - Use `derived` — NOT `extra_vars_summary`
249
+ - `related` fields are resolved names, not raw URLs
250
+
251
+ See the [Architecture Decision Records](../../docs/adr/) in the monorepo for design rationale:
252
+
253
+ - **ADR 0001**: Auth strategy (bearer token / PAT)
254
+ - **ADR 0002**: Output contract schema
255
+ - **ADR 0003**: Resilience patterns (retry, timeout, circuit breaker)
256
+ - **ADR 0004**: Agent-side polling (job lifecycle)
257
+ - **ADR 0005**: Extra-variable transforms (SSH→HTTPS, branch inference)
258
+ - **ADR 0006**: Error taxonomy and structured error reporting
259
+
260
+ ## License
261
+
262
+ MIT
package/dist/auth.d.ts ADDED
@@ -0,0 +1,112 @@
1
+ /**
2
+ * AWX Auth Hook — Bearer Token (PAT) Authentication
3
+ *
4
+ * Implements OpenCode's `type: "api"` auth hook pattern for AAP bearer token
5
+ * credential storage and injection. The user provides a Personal Access Token
6
+ * (PAT) generated from their AAP instance, and this hook stores it as the
7
+ * plugin's auth key.
8
+ *
9
+ * ## Auth Flow
10
+ *
11
+ * 1. On first plugin load, OpenCode prompts for a PAT via the auth hook text prompt.
12
+ * 2. The `authorize()` function returns the PAT as the secret key.
13
+ * 3. On every plugin load, init-time validation calls `GET /api/v2/me/`
14
+ * to verify the token is still active.
15
+ * 4. If validation fails, the user receives a clear, actionable error message.
16
+ *
17
+ * ## Token Validation
18
+ *
19
+ * Validation happens at plugin init time (NOT on first tool call) to provide
20
+ * immediate, clear feedback. Failed init-time validation logs an actionable
21
+ * error, but plugin initialization continues so the user can re-authenticate
22
+ * or fix configuration.
23
+ *
24
+ * ## Error Messages
25
+ *
26
+ * Failed auth produces user-actionable error messages:
27
+ * - No token configured: "AWX auth not configured. Set your Personal Access
28
+ * Token in the plugin settings."
29
+ * - Network failure: "Cannot reach AAP at <baseUrl>. Check your baseUrl in
30
+ * opencode.jsonc and ensure the AAP instance is accessible."
31
+ * - Invalid token (401): "AWX token is invalid or expired. Generate a new
32
+ * Personal Access Token at <baseUrl>/api/v2/tokens/ or Profile → Tokens."
33
+ * - Forbidden (403): "AWX token lacks sufficient permissions. Ensure the
34
+ * token has at least Read access."
35
+ *
36
+ * ## Reference
37
+ *
38
+ * - ADR 0001: Bearer Token Authentication for AWX Plugin
39
+ * - ADR 0003: Plugin API Surface Discovery
40
+ * - @opencode-ai/plugin auth hook docs
41
+ */
42
+ /** Result of an auth validation attempt */
43
+ export interface AuthValidationResult {
44
+ /** Whether the token is valid and AAP is reachable */
45
+ valid: boolean;
46
+ /** User-facing error message if validation failed (null if valid) */
47
+ error: string | null;
48
+ /** HTTP status code from the validation request (null if network error) */
49
+ status: number | null;
50
+ }
51
+ /**
52
+ * Validates the bearer token by making a GET request to /api/v2/me/.
53
+ *
54
+ * This is the init-time validation called on plugin load. It checks:
55
+ * 1. The AAP instance is reachable at the configured baseUrl
56
+ * 2. The bearer token is active and valid
57
+ *
58
+ * Returns a structured result so the caller can surface clear error
59
+ * messages to the user.
60
+ *
61
+ * @param baseUrl The configured AAP base URL (e.g., "https://example.com")
62
+ * @param token The bearer token (PAT) to validate
63
+ * @param signal Optional AbortSignal for timeout (recommend 10s)
64
+ */
65
+ export declare function validateToken(baseUrl: string, token: string, signal?: AbortSignal): Promise<AuthValidationResult>;
66
+ /**
67
+ * Creates an AWX auth hook configuration for the OpenCode plugin server.
68
+ *
69
+ * Uses the `type: "api"` auth method — the standard OpenCode pattern for
70
+ * non-rotating, user-provided tokens (e.g., API keys, PATs).
71
+ *
72
+ * The `authorize()` function simply returns the user's PAT as the key.
73
+ * Validation is handled separately at init time via `validateToken()`.
74
+ *
75
+ * @returns Auth hook configuration compatible with OpenCode's Hooks.auth
76
+ */
77
+ export declare function createAwxAuthHook(): {
78
+ provider: string;
79
+ methods: {
80
+ type: "api";
81
+ label: string;
82
+ prompts: {
83
+ type: "text";
84
+ key: string;
85
+ message: string;
86
+ }[];
87
+ /**
88
+ * Authorize the user's PAT.
89
+ *
90
+ * Returns the raw token as the secret key. OpenCode stores this
91
+ * securely and injects it into tool requests as the auth key.
92
+ *
93
+ * Empty or whitespace-only tokens fail authorization.
94
+ *
95
+ * We include a message field for better UX. If OpenCode ignores unknown
96
+ * fields, the prompt text above still provides actionable guidance.
97
+ *
98
+ * If the OpenCode auth type narrows this contract in future, remove the
99
+ * message field and rely on the prompt text.
100
+ */
101
+ authorize(inputs: Record<string, string>): Promise<{
102
+ type: "failed";
103
+ message: string;
104
+ key?: undefined;
105
+ } | {
106
+ type: "success";
107
+ key: string;
108
+ message?: undefined;
109
+ }>;
110
+ }[];
111
+ };
112
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,2CAA2C;AAC3C,MAAM,WAAW,oBAAoB;IACnC,sDAAsD;IACtD,KAAK,EAAE,OAAO,CAAC;IACf,qEAAqE;IACrE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,oBAAoB,CAAC,CAyE/B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB;;;;;;;;;;QAezB;;;;;;;;;;;;;WAaG;0BACqB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;;;;;;;;;;EAiBrD"}
package/dist/auth.js ADDED
@@ -0,0 +1,180 @@
1
+ /**
2
+ * AWX Auth Hook — Bearer Token (PAT) Authentication
3
+ *
4
+ * Implements OpenCode's `type: "api"` auth hook pattern for AAP bearer token
5
+ * credential storage and injection. The user provides a Personal Access Token
6
+ * (PAT) generated from their AAP instance, and this hook stores it as the
7
+ * plugin's auth key.
8
+ *
9
+ * ## Auth Flow
10
+ *
11
+ * 1. On first plugin load, OpenCode prompts for a PAT via the auth hook text prompt.
12
+ * 2. The `authorize()` function returns the PAT as the secret key.
13
+ * 3. On every plugin load, init-time validation calls `GET /api/v2/me/`
14
+ * to verify the token is still active.
15
+ * 4. If validation fails, the user receives a clear, actionable error message.
16
+ *
17
+ * ## Token Validation
18
+ *
19
+ * Validation happens at plugin init time (NOT on first tool call) to provide
20
+ * immediate, clear feedback. Failed init-time validation logs an actionable
21
+ * error, but plugin initialization continues so the user can re-authenticate
22
+ * or fix configuration.
23
+ *
24
+ * ## Error Messages
25
+ *
26
+ * Failed auth produces user-actionable error messages:
27
+ * - No token configured: "AWX auth not configured. Set your Personal Access
28
+ * Token in the plugin settings."
29
+ * - Network failure: "Cannot reach AAP at <baseUrl>. Check your baseUrl in
30
+ * opencode.jsonc and ensure the AAP instance is accessible."
31
+ * - Invalid token (401): "AWX token is invalid or expired. Generate a new
32
+ * Personal Access Token at <baseUrl>/api/v2/tokens/ or Profile → Tokens."
33
+ * - Forbidden (403): "AWX token lacks sufficient permissions. Ensure the
34
+ * token has at least Read access."
35
+ *
36
+ * ## Reference
37
+ *
38
+ * - ADR 0001: Bearer Token Authentication for AWX Plugin
39
+ * - ADR 0003: Plugin API Surface Discovery
40
+ * - @opencode-ai/plugin auth hook docs
41
+ */
42
+ /**
43
+ * Validates the bearer token by making a GET request to /api/v2/me/.
44
+ *
45
+ * This is the init-time validation called on plugin load. It checks:
46
+ * 1. The AAP instance is reachable at the configured baseUrl
47
+ * 2. The bearer token is active and valid
48
+ *
49
+ * Returns a structured result so the caller can surface clear error
50
+ * messages to the user.
51
+ *
52
+ * @param baseUrl The configured AAP base URL (e.g., "https://example.com")
53
+ * @param token The bearer token (PAT) to validate
54
+ * @param signal Optional AbortSignal for timeout (recommend 10s)
55
+ */
56
+ export async function validateToken(baseUrl, token, signal) {
57
+ // Normalize trailing slash
58
+ const normalizedBase = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
59
+ const url = `${normalizedBase}api/v2/me/`;
60
+ try {
61
+ const response = await fetch(url, {
62
+ method: "GET",
63
+ headers: {
64
+ Accept: "application/json",
65
+ Authorization: `Bearer ${token}`,
66
+ },
67
+ signal,
68
+ });
69
+ if (response.ok) {
70
+ return { valid: true, error: null, status: response.status };
71
+ }
72
+ if (response.status === 401) {
73
+ return {
74
+ valid: false,
75
+ error: [
76
+ `AWX token is invalid or expired.`,
77
+ `Generate a new Personal Access Token at ${normalizedBase}api/v2/tokens/`,
78
+ `or Profile → Tokens in the AAP UI.`,
79
+ ].join(" "),
80
+ status: 401,
81
+ };
82
+ }
83
+ if (response.status === 403) {
84
+ return {
85
+ valid: false,
86
+ error: [
87
+ `AWX token lacks sufficient permissions.`,
88
+ `Ensure the token has at least Read access to the AWX API.`,
89
+ ].join(" "),
90
+ status: 403,
91
+ };
92
+ }
93
+ return {
94
+ valid: false,
95
+ error: `AWX returned HTTP ${response.status}. Check your AAP configuration.`,
96
+ status: response.status,
97
+ };
98
+ }
99
+ catch (err) {
100
+ // Handle AbortError (timeout) specifically
101
+ if (err instanceof DOMException && (err.name === "AbortError" || err.name === "TimeoutError")) {
102
+ return {
103
+ valid: false,
104
+ error: [
105
+ `Timeout connecting to AAP at ${baseUrl}.`,
106
+ `Check your baseUrl in opencode.jsonc and ensure the AAP instance is reachable.`,
107
+ ].join(" "),
108
+ status: null,
109
+ };
110
+ }
111
+ // Network or other errors
112
+ const message = err instanceof Error ? err.message : String(err);
113
+ return {
114
+ valid: false,
115
+ error: [
116
+ `Cannot reach AAP at ${baseUrl}.`,
117
+ `Check your baseUrl in opencode.jsonc and ensure the AAP instance is accessible.`,
118
+ `Details: ${message}`,
119
+ ].join(" "),
120
+ status: null,
121
+ };
122
+ }
123
+ }
124
+ /**
125
+ * Creates an AWX auth hook configuration for the OpenCode plugin server.
126
+ *
127
+ * Uses the `type: "api"` auth method — the standard OpenCode pattern for
128
+ * non-rotating, user-provided tokens (e.g., API keys, PATs).
129
+ *
130
+ * The `authorize()` function simply returns the user's PAT as the key.
131
+ * Validation is handled separately at init time via `validateToken()`.
132
+ *
133
+ * @returns Auth hook configuration compatible with OpenCode's Hooks.auth
134
+ */
135
+ export function createAwxAuthHook() {
136
+ return {
137
+ provider: "awx",
138
+ methods: [
139
+ {
140
+ type: "api",
141
+ label: "AWX Bearer Token",
142
+ prompts: [
143
+ {
144
+ type: "text",
145
+ key: "token",
146
+ message: "Enter your AWX Personal Access Token (PAT). Generate one at AAP → Profile → Tokens or /api/v2/tokens/.",
147
+ },
148
+ ],
149
+ /**
150
+ * Authorize the user's PAT.
151
+ *
152
+ * Returns the raw token as the secret key. OpenCode stores this
153
+ * securely and injects it into tool requests as the auth key.
154
+ *
155
+ * Empty or whitespace-only tokens fail authorization.
156
+ *
157
+ * We include a message field for better UX. If OpenCode ignores unknown
158
+ * fields, the prompt text above still provides actionable guidance.
159
+ *
160
+ * If the OpenCode auth type narrows this contract in future, remove the
161
+ * message field and rely on the prompt text.
162
+ */
163
+ async authorize(inputs) {
164
+ const token = inputs.token;
165
+ if (!token || token.trim().length === 0) {
166
+ return {
167
+ type: "failed",
168
+ message: "AWX auth not configured. Set your Personal Access Token in the plugin settings.",
169
+ };
170
+ }
171
+ return {
172
+ type: "success",
173
+ key: token.trim(),
174
+ };
175
+ },
176
+ },
177
+ ],
178
+ };
179
+ }
180
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAYH;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAe,EACf,KAAa,EACb,MAAoB;IAEpB,2BAA2B;IAC3B,MAAM,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC;IACvE,MAAM,GAAG,GAAG,GAAG,cAAc,YAAY,CAAC;IAE1C,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,KAAK;YACb,OAAO,EAAE;gBACP,MAAM,EAAE,kBAAkB;gBAC1B,aAAa,EAAE,UAAU,KAAK,EAAE;aACjC;YACD,MAAM;SACP,CAAC,CAAC;QAEH,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;YAChB,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC/D,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,OAAO;gBACL,KAAK,EAAE,KAAK;gBACZ,KAAK,EAAE;oBACL,kCAAkC;oBAClC,2CAA2C,cAAc,gBAAgB;oBACzE,oCAAoC;iBACrC,CAAC,IAAI,CAAC,GAAG,CAAC;gBACX,MAAM,EAAE,GAAG;aACZ,CAAC;QACJ,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,OAAO;gBACL,KAAK,EAAE,KAAK;gBACZ,KAAK,EAAE;oBACL,yCAAyC;oBACzC,2DAA2D;iBAC5D,CAAC,IAAI,CAAC,GAAG,CAAC;gBACX,MAAM,EAAE,GAAG;aACZ,CAAC;QACJ,CAAC;QAED,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,qBAAqB,QAAQ,CAAC,MAAM,iCAAiC;YAC5E,MAAM,EAAE,QAAQ,CAAC,MAAM;SACxB,CAAC;IACJ,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,2CAA2C;QAC3C,IAAI,GAAG,YAAY,YAAY,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,YAAY,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc,CAAC,EAAE,CAAC;YAC9F,OAAO;gBACL,KAAK,EAAE,KAAK;gBACZ,KAAK,EAAE;oBACL,gCAAgC,OAAO,GAAG;oBAC1C,gFAAgF;iBACjF,CAAC,IAAI,CAAC,GAAG,CAAC;gBACX,MAAM,EAAE,IAAI;aACb,CAAC;QACJ,CAAC;QAED,0BAA0B;QAC1B,MAAM,OAAO,GACX,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACnD,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE;gBACL,uBAAuB,OAAO,GAAG;gBACjC,iFAAiF;gBACjF,YAAY,OAAO,EAAE;aACtB,CAAC,IAAI,CAAC,GAAG,CAAC;YACX,MAAM,EAAE,IAAI;SACb,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO;QACL,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,KAAc;gBACpB,KAAK,EAAE,kBAAkB;gBACzB,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,GAAG,EAAE,OAAO;wBACZ,OAAO,EACL,wGAAwG;qBAC3G;iBACF;gBACD;;;;;;;;;;;;;mBAaG;gBACH,KAAK,CAAC,SAAS,CAAC,MAA8B;oBAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;oBAC3B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;wBACxC,OAAO;4BACL,IAAI,EAAE,QAAiB;4BACvB,OAAO,EAAE,iFAAiF;yBAC3F,CAAC;oBACJ,CAAC;oBAED,OAAO;wBACL,IAAI,EAAE,SAAkB;wBACxB,GAAG,EAAE,KAAK,CAAC,IAAI,EAAE;qBAClB,CAAC;gBACJ,CAAC;aACF;SACF;KACF,CAAC;AACJ,CAAC"}