OXYGENHUB

0.1.0: sign in, hub client with generated types, remote provider, proposals and issues views, review threads on atoms, seal and land through oxy, checks status bar f0b06deac4b9

Author
Andy McCall @andymccall <[email protected]>
When
· 2026-09-27 19:40 UTC
Signature
✔ signed by Andy McCall
Hash
f0b06deac4b9384bffb3371cdeb3145de69692fc7347b5418af5f5f36d0e455c
Depends on
nothing: a root change
Depended on by
991516a107fa f162b1532f9f a6b460b1ec04 3b8b25ec33f9 d21a85954a34 85a87a2d7d5b d41ae0fdfae6 d8ac8f93fd34 b03ac49048d6 d8c9bdc51ee0 ed89cb46725d bd69712343d2 b1047516e064 263de209e5ba 5888949525ec 44e7f2609517 e04950748142 426ddc5039b5 092b8bf9f2ac 1cc0ff44e5ca 95f69b4a3ac2 f902d5b8bfeb b2dd082c57e4 bf73998c5198 52cef00143db 486c15b00314 6e249bbf7777
In bookmarks
main
In tags
v0.10.0 v0.10.1 v0.10.2 v0.11.0 v0.11.1 v0.11.2 v0.13.0
Edits
24 atoms, 30 tree operations, 24 files

Browse the tree at this change

added .oxyignore +6 −0
1 +
/node_modules
2 +
/out
3 +
/dist
4 +
*.vsix
5 +
/.vscode-test
6 +
/.claude
added .vscode/launch.json +13 −0
1 +
{
2 +
  "version": "0.2.0",
3 +
  "configurations": [
4 +
    {
5 +
      "name": "Run Extension",
6 +
      "type": "extensionHost",
7 +
      "request": "launch",
8 +
      "args": ["--extensionDevelopmentPath=${workspaceFolder}", "${workspaceFolder}/../hellooxy"],
9 +
      "outFiles": ["${workspaceFolder}/dist/**/*.js"],
10 +
      "preLaunchTask": "npm: build"
11 +
    }
12 +
  ]
13 +
}
added .vscodeignore +10 −0
1 +
.oxy/**
2 +
.oxyignore
3 +
.vscode/**
4 +
src/**
5 +
test/**
6 +
out/**
7 +
node_modules/**
8 +
tsconfig.json
9 +
**/*.map
10 +
docs/**
added CHANGELOG.md +11 −0
1 +
# Changelog
2 +
3 +
## 0.1.0
4 +
5 +
- Sign in to a hub with a personal access token (an `oxygenhub` authentication provider).
6 +
- Hub client over `/api/v1` with the hub's generated types.
7 +
- Remote provider for the Oxygen extension: Open on Remote for hubs.
8 +
- Proposals view with detail, open on hub, check out, review, seal approval, land, close, and propose the current bookmark.
9 +
- Review threads anchored to atoms on the checked-out proposal.
10 +
- Issues view with create, comment, close, reopen and assign to me.
11 +
- Status bar with the checks of the current bookmark's proposal, refreshed from the hub's event stream.
added LICENSE +30 −0
1 +
Oxygen for Visual Studio Code is licensed under either of
2 +
3 +
  * the MIT License (below), or
4 +
  * the Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
5 +
6 +
at your option, as is Oxygen itself.
7 +
8 +
---
9 +
10 +
MIT License
11 +
12 +
Copyright (c) 2026 Andy McCall
13 +
14 +
Permission is hereby granted, free of charge, to any person obtaining a copy
15 +
of this software and associated documentation files (the "Software"), to deal
16 +
in the Software without restriction, including without limitation the rights
17 +
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 +
copies of the Software, and to permit persons to whom the Software is
19 +
furnished to do so, subject to the following conditions:
20 +
21 +
The above copyright notice and this permission notice shall be included in all
22 +
copies or substantial portions of the Software.
23 +
24 +
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 +
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 +
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 +
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 +
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 +
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 +
SOFTWARE.
added README.md +15 −0
1 +
# Oxygenhub for Visual Studio Code
2 +
3 +
Proposals, reviews, issues and checks from an [Oxygenhub](../oxygenhub) in
4 +
the editor, built on the [Oxygen extension](../oxygen-vscode) the way the
5 +
GitHub Pull Requests extension builds on the built-in Git extension. The
6 +
specification is [docs/spec.md](docs/spec.md).
7 +
8 +
- **Sign in** with a hub URL and a personal access token (Settings › Tokens on the hub).
9 +
- **Proposals** view: needing me, mine, open; detail with blockers, approvals, checks and changes; check out the projected state; review; seal your approval and land, both through `oxy` in a terminal with your own key.
10 +
- **Review threads** on the checked-out proposal's files, anchored to atoms so they follow lines through rebases.
11 +
- **Issues** view: open, create, comment, close, assign.
12 +
- **Open on Remote** in the Oxygen extension works for hubs once this extension is installed.
13 +
- **Status bar**: the checks of the proposal whose source is your current bookmark, live.
14 +
15 +
`npm run check` type-checks, bundles and runs the Node tests; the hub client tests start a real hub from `../oxygenhub/target/release/oxygenhub` when it exists.
added assets/images/oxygenhub.png +0 −0

No line changes.

added assets/images/oxygenhub.svg +20 −0
1 +
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="100%" height="100%">
2 +
  <defs>
3 +
    <linearGradient id="oxyGrad" x1="0%" y1="0%" x2="100%" y2="100%">
4 +
      <stop offset="0%" stop-color="#00F0FF" />
5 +
      <stop offset="100%" stop-color="#0072FF" />
6 +
    </linearGradient>
7 +
8 +
    <g id="oxy-mark-mini">
9 +
      <path d="M -11 -6 L 11 -6" stroke="url(#oxyGrad)" stroke-width="3" stroke-linecap="round" />
10 +
      <path d="M -11 6 L 11 6" stroke="url(#oxyGrad)" stroke-width="3" stroke-linecap="round" />
11 +
      <circle cx="-11" cy="0" r="10" fill="none" stroke="url(#oxyGrad)" stroke-width="3" />
12 +
      <circle cx="-11" cy="0" r="3.5" fill="#00F0FF" />
13 +
      <circle cx="11" cy="0" r="10" fill="none" stroke="url(#oxyGrad)" stroke-width="3" />
14 +
      <circle cx="11" cy="0" r="3.5" fill="#FFFFFF" />
15 +
    </g>
16 +
  </defs>
17 +
18 +
  <rect width="64" height="64" rx="14" fill="#070c18" />
19 +
  <use href="#oxy-mark-mini" x="32" y="32" />
20 +
</svg>
added docs/spec.md +92 −0
1 +
# Oxygenhub for Visual Studio Code
2 +
3 +
| | |
4 +
|---|---|
5 +
| **Status** | Draft 0.1 |
6 +
| **Scope** | The forge in the editor: sign in to an Oxygenhub, see and review proposals with comments anchored to atoms, approve with a seal, land, work with issues, watch checks, and open anything on the hub. The counterpart of the GitHub Pull Requests and Issues extension. |
7 +
| **Depends on** | `oxygen-vscode` ≥ 0.7.0 (its exported API and remote providers), Oxygenhub ≥ 0.5 (`/api/v1`), `oxy` with `seal --proposal` and `proposal land` |
8 +
| **Date** | 2026-09-27 |
9 +
10 +
## 1. Goals
11 +
12 +
1. **Everything the hub can do about a proposal or an issue is reachable without leaving the editor**, and everything that changes history still goes through `oxy` on the user's machine with the user's key: sealing an approval and landing run `oxy`, never a hub-side signature.
13 +
2. **Build on oxygen-vscode, never beside it.** Repositories, status, bookmarks and remotes come from its exported API; this extension registers a remote provider so "Open on Remote" works for hubs.
14 +
3. **Review where the code is.** Proposal comments appear as VS Code comment threads on the projected state's files, anchored to atoms exactly as the hub stores them, so they follow the line through rebases.
15 +
4. **Honest about identity.** An approval shows as pending until the hub confirms the seal; a check shows what the hub reports.
16 +
17 +
Non-goals for 1.0: creating repositories or organisations, administration, notifications beyond the status bar count, editing files on the hub.
18 +
19 +
## 2. How it compares
20 +
21 +
| GitHub Pull Requests and Issues | Oxygenhub for VS Code |
22 +
|---|---|
23 +
| Sign in with GitHub (OAuth) | Sign in with a personal access token; an authentication provider `oxygenhub` other extensions can request |
24 +
| Pull Requests view: waiting for my review, created by me, all | Proposals view: **Assigned to me** (review requested or approval needed), **Mine**, **Open**, per repository |
25 +
| Checkout PR branch | Fetch the proposal's changes and check out its projected state into a bookmark (`oxy` fetch + bookmark set) |
26 +
| Review comments in the diff, threads | Comment threads on the projected state's files via the Comments API, anchored to atoms |
27 +
| Approve, Request changes | Same; **Approve** also offers to run `oxy seal --proposal` so it counts |
28 +
| Merge button | **Land** runs `oxy proposal land` (or the hub lands when it has no policy) |
29 +
| Issues view, create issue | Same, with labels, assignees and milestone |
30 +
| Checks under the PR | Checks on the projected state, live over the hub's event stream |
31 +
| Open on GitHub | Open on Remote, through the remote provider |
32 +
33 +
## 3. Architecture
34 +
35 +
```
36 +
src/
37 +
  extension.ts      activation, wiring, commands
38 +
  auth.ts           AuthenticationProvider "oxygenhub": sessions = {hub base URL, token}, stored in SecretStorage
39 +
  hub.ts            HubClient: typed fetch over /api/v1 using hub-api's generated types (src/hubapi.d.ts, copied by `npm run types`)
40 +
  provider.ts       the RemoteProvider registered with oxygen-vscode: matches a session's base URL, builds hub URLs
41 +
  proposals.ts      TreeDataProvider: proposals by repository and bucket; detail webview; actions
42 +
  review.ts         CommentController over oxy: content of the projected state, threads placed by atom, reply/resolve
43 +
  issues.ts         TreeDataProvider: issues; create, comment, close
44 +
  checks.ts         status bar: checks of the current bookmark's proposal; SSE subscription
45 +
  hubapi.d.ts       generated from the hub (do not edit)
46 +
```
47 +
48 +
Pure modules (`hub.ts` URL building and parsing, `provider.ts`) are tested with Node against a real hub started from the oxygenhub binary when present; the rest is exercised manually.
49 +
50 +
## 4. Sign in
51 +
52 +
`oxygenhub.signIn` asks for the hub URL (default: the first remote that looks like a hub, `https://…/owner/name` minus the last two segments) and a personal access token (Settings › Tokens on the hub; scopes `repo:read repo:write proposals:write issues:write`). The token is kept in `SecretStorage` under the base URL; sessions are exposed through `vscode.authentication` so oxygen-vscode or others can reuse them. `oxygenhub.signOut` forgets it.
53 +
54 +
## 5. Proposals
55 +
56 +
The **Oxygenhub: Proposals** view lists, per repository that has a hub remote, proposals in three groups: needing me (open, not mine, where I have not approved or where my approval is pending a seal), mine, and other open ones. Each row shows number, title, author, target, state chips (draft, conflicted, landable) and the check summary. Actions: open detail, open on hub, check out, review (comment / request changes / approve), seal approval, land, close.
57 +
58 +
**Detail** is a webview: description, blockers, approvals (sealed or pending), checks, the changes list with signatures, and buttons for the actions. **Check out** fetches from the hub remote and sets a local bookmark `proposal/N` to the projected heads, then switches to it, so the files in the editor are the projected state; review threads then appear in place.
59 +
60 +
## 6. Review threads
61 +
62 +
A `CommentController` for the checked-out proposal. Threads come from `GET …/proposals/N/comments`; each anchored comment is placed by asking oxy for the current line of its atom (`oxy blame --json` gives atoms per line; the extension finds the line whose `(hash, index)` matches). Replying posts a comment anchored to the same atom; a new thread on a line posts with the line's atom. Resolve toggles the hub's resolved flag. Threads whose atom is no longer live are shown in a separate "outdated" list in the view.
63 +
64 +
## 7. Approvals and landing
65 +
66 +
**Approve** posts the review, then offers to run `oxy seal --proposal <url>` in the repository's terminal so the approval is backed by the user's seal; the row shows "pending seal" until the hub reports it sealed. **Land** runs `oxy proposal land <url>` in a terminal (the hub's own landing is offered when the hub reports it can land, for repositories without a policy). Both use the integrated terminal so the user sees exactly what runs with their key.
67 +
68 +
## 8. Issues
69 +
70 +
The **Oxygenhub: Issues** view lists open issues per repository (assigned to me first). Actions: open detail (webview with the timeline and a comment box), open on hub, create issue (title and body inputs), close, reopen, assign to me.
71 +
72 +
## 9. Checks and status bar
73 +
74 +
A status bar item shows, for the current repository's current bookmark, the open proposal whose source it is and the state of its checks (✔ n passed, ✘ failing, … running). It subscribes to the hub's proposal event stream and refreshes on `check` events; clicking opens the proposal.
75 +
76 +
## 10. Configuration
77 +
78 +
`oxygenhub.hubs`: known hub base URLs (also learnt from sign in). `oxygenhub.proposalBookmarkPrefix`: `proposal/`. `oxygenhub.autoRefresh`: seconds between view refreshes (60).
79 +
80 +
## 11. Milestones
81 +
82 +
| | Delivers |
83 +
|---|---|
84 +
| **0.1** | Sign in, hub client with generated types, remote provider (Open on Remote works), proposals view with detail and open-on-hub, issues view with create and comment |
85 +
| **0.2** | Check out a proposal, review threads on atoms, reviews, seal and land through the terminal, checks in the status bar with live events |
86 +
| **0.3** | Issues: labels, assignees, milestones; notifications count; create proposal from the current bookmark |
87 +
| **1.0** | Marketplace listing beside oxygen-vscode, three-platform tests |
88 +
89 +
## 12. Open questions
90 +
91 +
1. Whether to render the proposal diff in a native diff editor (base vs projected content via an `oxyhub:` content provider) instead of the webview's file list. Likely yes in 0.2, since threads need real editors anyway.
92 +
2. Token scopes: the extension could ask for a narrower token per action. Not for 1.0.
added package-lock.json +2372 −0
1 +
{
2 +
  "name": "oxygenhub-vscode",
3 +
  "version": "0.1.0",
4 +
  "lockfileVersion": 3,
5 +
  "requires": true,
6 +
  "packages": {
7 +
    "": {
8 +
      "name": "oxygenhub-vscode",
9 +
      "version": "0.1.0",
10 +
      "license": "MIT OR Apache-2.0",
11 +
      "devDependencies": {
12 +
        "@types/node": "^24.0.0",
13 +
        "@types/vscode": "^1.137.0",
14 +
        "@vscode/vsce": "^4.0.0",
15 +
        "esbuild": "^0.28.0",
16 +
        "typescript": "^5.9.0"
17 +
      },
18 +
      "engines": {
19 +
        "vscode": "^1.137.0"
20 +
      }
21 +
    },
22 +
    "node_modules/@azure/abort-controller": {
23 +
      "version": "2.2.0",
24 +
      "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.2.0.tgz",
25 +
      "integrity": "sha512-fNAjWnA/nZ2jz31kxR/AqRaUT8ewHBw/WuBIosK0moMy1C9e5ValbDfFdIxJzVOOYaYkV/b2F1S4H/aHiqfVQg==",
26 +
      "dev": true,
27 +
      "license": "MIT",
28 +
      "dependencies": {
29 +
        "tslib": "^2.6.2"
30 +
      },
31 +
      "engines": {
32 +
        "node": ">=22.0.0"
33 +
      }
34 +
    },
35 +
    "node_modules/@azure/core-auth": {
36 +
      "version": "1.11.0",
37 +
      "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.11.0.tgz",
38 +
      "integrity": "sha512-IUZydyTUkDnYdstOW9pFOOUQlBjAepK5teihDE3x6yxsPJs/hsAaaYpeGxdxrgtOiJbBKSjKW7MDk7AEhb4LRg==",
39 +
      "dev": true,
40 +
      "license": "MIT",
41 +
      "dependencies": {
42 +
        "@azure/abort-controller": "^2.1.2",
43 +
        "@azure/core-util": "^1.13.0",
44 +
        "tslib": "^2.6.2"
45 +
      },
46 +
      "engines": {
47 +
        "node": ">=22.0.0"
48 +
      }
49 +
    },
50 +
    "node_modules/@azure/core-client": {
51 +
      "version": "1.11.1",
52 +
      "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.11.1.tgz",
53 +
      "integrity": "sha512-2QygG2F76ZpMP2eMztiJvAiFMu71M9rDeU7vO/QKg5Css7MgM4frUOslFjhVjRhbGaCNPtz/S8M6y46/fFKVuQ==",
54 +
      "dev": true,
55 +
      "license": "MIT",
56 +
      "dependencies": {
57 +
        "@azure/abort-controller": "^2.1.2",
58 +
        "@azure/core-auth": "^1.10.0",
59 +
        "@azure/core-rest-pipeline": "^1.22.0",
60 +
        "@azure/core-tracing": "^1.3.0",
61 +
        "@azure/core-util": "^1.13.0",
62 +
        "@azure/logger": "^1.3.0",
63 +
        "tslib": "^2.6.2"
64 +
      },
65 +
      "engines": {
66 +
        "node": ">=22.0.0"
67 +
      }
68 +
    },
69 +
    "node_modules/@azure/core-process": {
70 +
      "version": "1.0.0",
71 +
      "resolved": "https://registry.npmjs.org/@azure/core-process/-/core-process-1.0.0.tgz",
72 +
      "integrity": "sha512-/shnJ+ooO8WPxDhPEeI/2oRQuubn16gZ6CvlbpWbEswZfzwI9tI/sMAHmF3x1LuQ9yZYXfLW3TjzGMLEC5blKg==",
73 +
      "dev": true,
74 +
      "license": "MIT",
75 +
      "engines": {
76 +
        "node": ">=22.0.0"
77 +
      }
78 +
    },
79 +
    "node_modules/@azure/core-rest-pipeline": {
80 +
      "version": "1.25.0",
81 +
      "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.25.0.tgz",
82 +
      "integrity": "sha512-bMs8ekJLjX8wPV+9IPBges1SLPyuDtE9g5gLDWOpxzKcoOFQnpLGkbcT1tdw3FaAmDS1gnPmMmJ6y/T5B96kIA==",
83 +
      "dev": true,
84 +
      "license": "MIT",
85 +
      "dependencies": {
86 +
        "@azure/abort-controller": "^2.1.2",
87 +
        "@azure/core-auth": "^1.10.0",
88 +
        "@azure/core-tracing": "^1.3.0",
89 +
        "@azure/core-util": "^1.13.0",
90 +
        "@azure/logger": "^1.3.0",
91 +
        "@typespec/ts-http-runtime": "^0.3.4",
92 +
        "tslib": "^2.6.2"
93 +
      },
94 +
      "engines": {
95 +
        "node": ">=22.0.0"
96 +
      }
97 +
    },
98 +
    "node_modules/@azure/core-tracing": {
99 +
      "version": "1.4.0",
100 +
      "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.4.0.tgz",
101 +
      "integrity": "sha512-eGwxD0AtncrxeBM4tG8R55Pc3rdX1hNW2WibJAgYpCVA6E93mvvVH+LcssoVjOBrSKWS55yEIHsk0X8ctHmfOQ==",
102 +
      "dev": true,
103 +
      "license": "MIT",
104 +
      "dependencies": {
105 +
        "tslib": "^2.6.2"
106 +
      },
107 +
      "engines": {
108 +
        "node": ">=22.0.0"
109 +
      }
110 +
    },
111 +
    "node_modules/@azure/core-util": {
112 +
      "version": "1.14.0",
113 +
      "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.14.0.tgz",
114 +
      "integrity": "sha512-9n2pWK61veAuN0V20t9lOuoV4CFMdyAZ1ygZzvBGk/pBBJRib/PjL9PLXa/aI2CcPpyHfqVsxxqLCYl6uZlfDw==",
115 +
      "dev": true,
116 +
      "license": "MIT",
117 +
      "dependencies": {
118 +
        "@azure/abort-controller": "^2.1.2",
119 +
        "@typespec/ts-http-runtime": "^0.3.0",
120 +
        "tslib": "^2.6.2"
121 +
      },
122 +
      "engines": {
123 +
        "node": ">=22.0.0"
124 +
      }
125 +
    },
126 +
    "node_modules/@azure/identity": {
127 +
      "version": "4.13.3",
128 +
      "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.3.tgz",
129 +
      "integrity": "sha512-zGQPtqvXPgSA8yfV2CkIQ1qirqk0p9AIVpC5uEkdXQYcKl07QHvyaGYRnZOk0AsQUmxNb4wfkcwY5di8Z5xa9A==",
130 +
      "dev": true,
131 +
      "license": "MIT",
132 +
      "dependencies": {
133 +
        "@azure/abort-controller": "^2.0.0",
134 +
        "@azure/core-auth": "^1.9.0",
135 +
        "@azure/core-client": "^1.9.2",
136 +
        "@azure/core-process": "^1.0.0",
137 +
        "@azure/core-rest-pipeline": "^1.17.0",
138 +
        "@azure/core-tracing": "^1.0.0",
139 +
        "@azure/core-util": "^1.11.0",
140 +
        "@azure/logger": "^1.0.0",
141 +
        "@azure/msal-browser": "^5.5.0",
142 +
        "@azure/msal-node": "^6.0.0",
143 +
        "open": "^10.1.0",
144 +
        "tslib": "^2.2.0"
145 +
      },
146 +
      "engines": {
147 +
        "node": ">=22.0.0"
148 +
      }
149 +
    },
150 +
    "node_modules/@azure/logger": {
151 +
      "version": "1.4.0",
152 +
      "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.4.0.tgz",
153 +
      "integrity": "sha512-rbAE25KUfjU/s3XHUdJgceoCP5dEOpMx85J04kF+QMdta73XkuG9JGHHinch+XIoKpBdqljin+KqURpJriSzLA==",
154 +
      "dev": true,
155 +
      "license": "MIT",
156 +
      "dependencies": {
157 +
        "@typespec/ts-http-runtime": "^0.3.0",
158 +
        "tslib": "^2.6.2"
159 +
      },
160 +
      "engines": {
161 +
        "node": ">=22.0.0"
162 +
      }
163 +
    },
164 +
    "node_modules/@azure/msal-browser": {
165 +
      "version": "5.23.0",
166 +
      "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.23.0.tgz",
167 +
      "integrity": "sha512-IRCwkRCK47hBXK+7bu67UZWY7HS0Jx1dZgi4C1HVbjlBum8Jy8fb2l87xFr6HxdQJWy951zRUHtwwDITHFAtaA==",
168 +
      "dev": true,
169 +
      "license": "MIT",
170 +
      "dependencies": {
171 +
        "@azure/msal-common": "16.14.1"
172 +
      },
173 +
      "engines": {
174 +
        "node": ">=0.8.0"
175 +
      }
176 +
    },
177 +
    "node_modules/@azure/msal-common": {
178 +
      "version": "16.14.1",
179 +
      "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.14.1.tgz",
180 +
      "integrity": "sha512-Or6xhPNyi4zHW25158yxBoyxuCqNSPa5YBVqfF1J5Ks4MJWBo/USXdp05DQIPu1Zli00YZu6t0+h6KvHJealxQ==",
181 +
      "dev": true,
182 +
      "license": "MIT",
183 +
      "engines": {
184 +
        "node": ">=0.8.0"
185 +
      }
186 +
    },
187 +
    "node_modules/@azure/msal-node": {
188 +
      "version": "6.0.1",
189 +
      "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-6.0.1.tgz",
190 +
      "integrity": "sha512-ixSO1Y/kCVRthRs+hSx/5qkwaunX1/RAePhlMN0wIpIQ4WEZ6AREGGnGd1AP0qspHVsAwzWQKGubpjh50JyJIQ==",
191 +
      "dev": true,
192 +
      "license": "MIT",
193 +
      "dependencies": {
194 +
        "@azure/msal-common": "16.14.1",
195 +
        "jsonwebtoken": "^9.0.0"
196 +
      },
197 +
      "engines": {
198 +
        "node": ">=20"
199 +
      }
200 +
    },
201 +
    "node_modules/@esbuild/aix-ppc64": {
202 +
      "version": "0.28.2",
203 +
      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
204 +
      "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
205 +
      "cpu": [
206 +
        "ppc64"
207 +
      ],
208 +
      "dev": true,
209 +
      "license": "MIT",
210 +
      "optional": true,
211 +
      "os": [
212 +
        "aix"
213 +
      ],
214 +
      "engines": {
215 +
        "node": ">=18"
216 +
      }
217 +
    },
218 +
    "node_modules/@esbuild/android-arm": {
219 +
      "version": "0.28.2",
220 +
      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
221 +
      "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
222 +
      "cpu": [
223 +
        "arm"
224 +
      ],
225 +
      "dev": true,
226 +
      "license": "MIT",
227 +
      "optional": true,
228 +
      "os": [
229 +
        "android"
230 +
      ],
231 +
      "engines": {
232 +
        "node": ">=18"
233 +
      }
234 +
    },
235 +
    "node_modules/@esbuild/android-arm64": {
236 +
      "version": "0.28.2",
237 +
      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
238 +
      "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
239 +
      "cpu": [
240 +
        "arm64"
241 +
      ],
242 +
      "dev": true,
243 +
      "license": "MIT",
244 +
      "optional": true,
245 +
      "os": [
246 +
        "android"
247 +
      ],
248 +
      "engines": {
249 +
        "node": ">=18"
250 +
      }
251 +
    },
252 +
    "node_modules/@esbuild/android-x64": {
253 +
      "version": "0.28.2",
254 +
      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
255 +
      "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
256 +
      "cpu": [
257 +
        "x64"
258 +
      ],
259 +
      "dev": true,
260 +
      "license": "MIT",
261 +
      "optional": true,
262 +
      "os": [
263 +
        "android"
264 +
      ],
265 +
      "engines": {
266 +
        "node": ">=18"
267 +
      }
268 +
    },
269 +
    "node_modules/@esbuild/darwin-arm64": {
270 +
      "version": "0.28.2",
271 +
      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
272 +
      "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
273 +
      "cpu": [
274 +
        "arm64"
275 +
      ],
276 +
      "dev": true,
277 +
      "license": "MIT",
278 +
      "optional": true,
279 +
      "os": [
280 +
        "darwin"
281 +
      ],
282 +
      "engines": {
283 +
        "node": ">=18"
284 +
      }
285 +
    },
286 +
    "node_modules/@esbuild/darwin-x64": {
287 +
      "version": "0.28.2",
288 +
      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
289 +
      "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
290 +
      "cpu": [
291 +
        "x64"
292 +
      ],
293 +
      "dev": true,
294 +
      "license": "MIT",
295 +
      "optional": true,
296 +
      "os": [
297 +
        "darwin"
298 +
      ],
299 +
      "engines": {
300 +
        "node": ">=18"
301 +
      }
302 +
    },
303 +
    "node_modules/@esbuild/freebsd-arm64": {
304 +
      "version": "0.28.2",
305 +
      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
306 +
      "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
307 +
      "cpu": [
308 +
        "arm64"
309 +
      ],
310 +
      "dev": true,
311 +
      "license": "MIT",
312 +
      "optional": true,
313 +
      "os": [
314 +
        "freebsd"
315 +
      ],
316 +
      "engines": {
317 +
        "node": ">=18"
318 +
      }
319 +
    },
320 +
    "node_modules/@esbuild/freebsd-x64": {
321 +
      "version": "0.28.2",
322 +
      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
323 +
      "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
324 +
      "cpu": [
325 +
        "x64"
326 +
      ],
327 +
      "dev": true,
328 +
      "license": "MIT",
329 +
      "optional": true,
330 +
      "os": [
331 +
        "freebsd"
332 +
      ],
333 +
      "engines": {
334 +
        "node": ">=18"
335 +
      }
336 +
    },
337 +
    "node_modules/@esbuild/linux-arm": {
338 +
      "version": "0.28.2",
339 +
      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
340 +
      "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
341 +
      "cpu": [
342 +
        "arm"
343 +
      ],
344 +
      "dev": true,
345 +
      "license": "MIT",
346 +
      "optional": true,
347 +
      "os": [
348 +
        "linux"
349 +
      ],
350 +
      "engines": {
351 +
        "node": ">=18"
352 +
      }
353 +
    },
354 +
    "node_modules/@esbuild/linux-arm64": {
355 +
      "version": "0.28.2",
356 +
      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
357 +
      "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
358 +
      "cpu": [
359 +
        "arm64"
360 +
      ],
361 +
      "dev": true,
362 +
      "license": "MIT",
363 +
      "optional": true,
364 +
      "os": [
365 +
        "linux"
366 +
      ],
367 +
      "engines": {
368 +
        "node": ">=18"
369 +
      }
370 +
    },
371 +
    "node_modules/@esbuild/linux-ia32": {
372 +
      "version": "0.28.2",
373 +
      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
374 +
      "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
375 +
      "cpu": [
376 +
        "ia32"
377 +
      ],
378 +
      "dev": true,
379 +
      "license": "MIT",
380 +
      "optional": true,
381 +
      "os": [
382 +
        "linux"
383 +
      ],
384 +
      "engines": {
385 +
        "node": ">=18"
386 +
      }
387 +
    },
388 +
    "node_modules/@esbuild/linux-loong64": {
389 +
      "version": "0.28.2",
390 +
      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
391 +
      "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
392 +
      "cpu": [
393 +
        "loong64"
394 +
      ],
395 +
      "dev": true,
396 +
      "license": "MIT",
397 +
      "optional": true,
398 +
      "os": [
399 +
        "linux"
400 +
      ],
401 +
      "engines": {
402 +
        "node": ">=18"
403 +
      }
404 +
    },
405 +
    "node_modules/@esbuild/linux-mips64el": {
406 +
      "version": "0.28.2",
407 +
      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
408 +
      "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
409 +
      "cpu": [
410 +
        "mips64el"
411 +
      ],
412 +
      "dev": true,
413 +
      "license": "MIT",
414 +
      "optional": true,
415 +
      "os": [
416 +
        "linux"
417 +
      ],
418 +
      "engines": {
419 +
        "node": ">=18"
420 +
      }
421 +
    },
422 +
    "node_modules/@esbuild/linux-ppc64": {
423 +
      "version": "0.28.2",
424 +
      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
425 +
      "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
426 +
      "cpu": [
427 +
        "ppc64"
428 +
      ],
429 +
      "dev": true,
430 +
      "license": "MIT",
431 +
      "optional": true,
432 +
      "os": [
433 +
        "linux"
434 +
      ],
435 +
      "engines": {
436 +
        "node": ">=18"
437 +
      }
438 +
    },
439 +
    "node_modules/@esbuild/linux-riscv64": {
440 +
      "version": "0.28.2",
441 +
      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
442 +
      "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
443 +
      "cpu": [
444 +
        "riscv64"
445 +
      ],
446 +
      "dev": true,
447 +
      "license": "MIT",
448 +
      "optional": true,
449 +
      "os": [
450 +
        "linux"
451 +
      ],
452 +
      "engines": {
453 +
        "node": ">=18"
454 +
      }
455 +
    },
456 +
    "node_modules/@esbuild/linux-s390x": {
457 +
      "version": "0.28.2",
458 +
      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
459 +
      "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
460 +
      "cpu": [
461 +
        "s390x"
462 +
      ],
463 +
      "dev": true,
464 +
      "license": "MIT",
465 +
      "optional": true,
466 +
      "os": [
467 +
        "linux"
468 +
      ],
469 +
      "engines": {
470 +
        "node": ">=18"
471 +
      }
472 +
    },
473 +
    "node_modules/@esbuild/linux-x64": {
474 +
      "version": "0.28.2",
475 +
      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
476 +
      "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
477 +
      "cpu": [
478 +
        "x64"
479 +
      ],
480 +
      "dev": true,
481 +
      "license": "MIT",
482 +
      "optional": true,
483 +
      "os": [
484 +
        "linux"
485 +
      ],
486 +
      "engines": {
487 +
        "node": ">=18"
488 +
      }
489 +
    },
490 +
    "node_modules/@esbuild/netbsd-arm64": {
491 +
      "version": "0.28.2",
492 +
      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
493 +
      "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
494 +
      "cpu": [
495 +
        "arm64"
496 +
      ],
497 +
      "dev": true,
498 +
      "license": "MIT",
499 +
      "optional": true,
500 +
      "os": [
501 +
        "netbsd"
502 +
      ],
503 +
      "engines": {
504 +
        "node": ">=18"
505 +
      }
506 +
    },
507 +
    "node_modules/@esbuild/netbsd-x64": {
508 +
      "version": "0.28.2",
509 +
      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
510 +
      "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
511 +
      "cpu": [
512 +
        "x64"
513 +
      ],
514 +
      "dev": true,
515 +
      "license": "MIT",
516 +
      "optional": true,
517 +
      "os": [
518 +
        "netbsd"
519 +
      ],
520 +
      "engines": {
521 +
        "node": ">=18"
522 +
      }
523 +
    },
524 +
    "node_modules/@esbuild/openbsd-arm64": {
525 +
      "version": "0.28.2",
526 +
      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
527 +
      "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
528 +
      "cpu": [
529 +
        "arm64"
530 +
      ],
531 +
      "dev": true,
532 +
      "license": "MIT",
533 +
      "optional": true,
534 +
      "os": [
535 +
        "openbsd"
536 +
      ],
537 +
      "engines": {
538 +
        "node": ">=18"
539 +
      }
540 +
    },
541 +
    "node_modules/@esbuild/openbsd-x64": {
542 +
      "version": "0.28.2",
543 +
      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
544 +
      "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
545 +
      "cpu": [
546 +
        "x64"
547 +
      ],
548 +
      "dev": true,
549 +
      "license": "MIT",
550 +
      "optional": true,
551 +
      "os": [
552 +
        "openbsd"
553 +
      ],
554 +
      "engines": {
555 +
        "node": ">=18"
556 +
      }
557 +
    },
558 +
    "node_modules/@esbuild/openharmony-arm64": {
559 +
      "version": "0.28.2",
560 +
      "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
561 +
      "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
562 +
      "cpu": [
563 +
        "arm64"
564 +
      ],
565 +
      "dev": true,
566 +
      "license": "MIT",
567 +
      "optional": true,
568 +
      "os": [
569 +
        "openharmony"
570 +
      ],
571 +
      "engines": {
572 +
        "node": ">=18"
573 +
      }
574 +
    },
575 +
    "node_modules/@esbuild/sunos-x64": {
576 +
      "version": "0.28.2",
577 +
      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
578 +
      "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
579 +
      "cpu": [
580 +
        "x64"
581 +
      ],
582 +
      "dev": true,
583 +
      "license": "MIT",
584 +
      "optional": true,
585 +
      "os": [
586 +
        "sunos"
587 +
      ],
588 +
      "engines": {
589 +
        "node": ">=18"
590 +
      }
591 +
    },
592 +
    "node_modules/@esbuild/win32-arm64": {
593 +
      "version": "0.28.2",
594 +
      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
595 +
      "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
596 +
      "cpu": [
597 +
        "arm64"
598 +
      ],
599 +
      "dev": true,
600 +
      "license": "MIT",
601 +
      "optional": true,
602 +
      "os": [
603 +
        "win32"
604 +
      ],
605 +
      "engines": {
606 +
        "node": ">=18"
607 +
      }
608 +
    },
609 +
    "node_modules/@esbuild/win32-ia32": {
610 +
      "version": "0.28.2",
611 +
      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
612 +
      "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
613 +
      "cpu": [
614 +
        "ia32"
615 +
      ],
616 +
      "dev": true,
617 +
      "license": "MIT",
618 +
      "optional": true,
619 +
      "os": [
620 +
        "win32"
621 +
      ],
622 +
      "engines": {
623 +
        "node": ">=18"
624 +
      }
625 +
    },
626 +
    "node_modules/@esbuild/win32-x64": {
627 +
      "version": "0.28.2",
628 +
      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
629 +
      "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
630 +
      "cpu": [
631 +
        "x64"
632 +
      ],
633 +
      "dev": true,
634 +
      "license": "MIT",
635 +
      "optional": true,
636 +
      "os": [
637 +
        "win32"
638 +
      ],
639 +
      "engines": {
640 +
        "node": ">=18"
641 +
      }
642 +
    },
643 +
    "node_modules/@napi-rs/keyring": {
644 +
      "version": "1.3.0",
645 +
      "resolved": "https://registry.npmjs.org/@napi-rs/keyring/-/keyring-1.3.0.tgz",
646 +
      "integrity": "sha512-WrOw/bcXm0f9qHkumlT1QlArXSTWqaY9sunsDpOk+yCCorCKMxvWT/a3xko4EYHVdeZoh00yI2TydXn6eyICDA==",
647 +
      "dev": true,
648 +
      "license": "MIT",
649 +
      "engines": {
650 +
        "node": ">= 10"
651 +
      },
652 +
      "funding": {
653 +
        "type": "github",
654 +
        "url": "https://github.com/sponsors/Brooooooklyn"
655 +
      },
656 +
      "optionalDependencies": {
657 +
        "@napi-rs/keyring-darwin-arm64": "1.3.0",
658 +
        "@napi-rs/keyring-darwin-x64": "1.3.0",
659 +
        "@napi-rs/keyring-freebsd-x64": "1.3.0",
660 +
        "@napi-rs/keyring-linux-arm-gnueabihf": "1.3.0",
661 +
        "@napi-rs/keyring-linux-arm64-gnu": "1.3.0",
662 +
        "@napi-rs/keyring-linux-arm64-musl": "1.3.0",
663 +
        "@napi-rs/keyring-linux-riscv64-gnu": "1.3.0",
664 +
        "@napi-rs/keyring-linux-x64-gnu": "1.3.0",
665 +
        "@napi-rs/keyring-linux-x64-musl": "1.3.0",
666 +
        "@napi-rs/keyring-win32-arm64-msvc": "1.3.0",
667 +
        "@napi-rs/keyring-win32-ia32-msvc": "1.3.0",
668 +
        "@napi-rs/keyring-win32-x64-msvc": "1.3.0"
669 +
      }
670 +
    },
671 +
    "node_modules/@napi-rs/keyring-darwin-arm64": {
672 +
      "version": "1.3.0",
673 +
      "resolved": "https://registry.npmjs.org/@napi-rs/keyring-darwin-arm64/-/keyring-darwin-arm64-1.3.0.tgz",
674 +
      "integrity": "sha512-pl76hJvdYUBn6I24bXiOBMA9nbDapo3I5B+f3OorjDU4dUMSypXeKbOVehJe8fhgTiH24flMyTS3aAIy43xegQ==",
675 +
      "cpu": [
676 +
        "arm64"
677 +
      ],
678 +
      "dev": true,
679 +
      "license": "MIT",
680 +
      "optional": true,
681 +
      "os": [
682 +
        "darwin"
683 +
      ],
684 +
      "engines": {
685 +
        "node": ">= 10"
686 +
      }
687 +
    },
688 +
    "node_modules/@napi-rs/keyring-darwin-x64": {
689 +
      "version": "1.3.0",
690 +
      "resolved": "https://registry.npmjs.org/@napi-rs/keyring-darwin-x64/-/keyring-darwin-x64-1.3.0.tgz",
691 +
      "integrity": "sha512-YcJtEV5LA3cvA4z3BurgxH5IhTsW1JfIvcAAcqcecwk06Si9F9NqkxbZVIfDwQ8oRHgaBmT3zZJnLAotCrVahw==",
692 +
      "cpu": [
693 +
        "x64"
694 +
      ],
695 +
      "dev": true,
696 +
      "license": "MIT",
697 +
      "optional": true,
698 +
      "os": [
699 +
        "darwin"
700 +
      ],
701 +
      "engines": {
702 +
        "node": ">= 10"
703 +
      }
704 +
    },
705 +
    "node_modules/@napi-rs/keyring-freebsd-x64": {
706 +
      "version": "1.3.0",
707 +
      "resolved": "https://registry.npmjs.org/@napi-rs/keyring-freebsd-x64/-/keyring-freebsd-x64-1.3.0.tgz",
708 +
      "integrity": "sha512-vlLf31TGhfRAaxLDBhg8b89ss0HHD/lyNmL5F3UjSaz5CUXElsJmKYq9fqA/B+cZKUEUcLHHGhF0I/CqcFdaVw==",
709 +
      "cpu": [
710 +
        "x64"
711 +
      ],
712 +
      "dev": true,
713 +
      "license": "MIT",
714 +
      "optional": true,
715 +
      "os": [
716 +
        "freebsd"
717 +
      ],
718 +
      "engines": {
719 +
        "node": ">= 10"
720 +
      }
721 +
    },
722 +
    "node_modules/@napi-rs/keyring-linux-arm-gnueabihf": {
723 +
      "version": "1.3.0",
724 +
      "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-arm-gnueabihf/-/keyring-linux-arm-gnueabihf-1.3.0.tgz",
725 +
      "integrity": "sha512-KiWdMMu/Inz/bHHIAGrnF7r54FZDYXuHO6UFF/rhIrshUsxbMG1Rl9lEymNtqqsVo927G0VYcb02FzWQ3iBQRQ==",
726 +
      "cpu": [
727 +
        "arm"
728 +
      ],
729 +
      "dev": true,
730 +
      "license": "MIT",
731 +
      "optional": true,
732 +
      "os": [
733 +
        "linux"
734 +
      ],
735 +
      "engines": {
736 +
        "node": ">= 10"
737 +
      }
738 +
    },
739 +
    "node_modules/@napi-rs/keyring-linux-arm64-gnu": {
740 +
      "version": "1.3.0",
741 +
      "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-arm64-gnu/-/keyring-linux-arm64-gnu-1.3.0.tgz",
742 +
      "integrity": "sha512-eyKGpY40lm9Jvs1aD294XRH4y7+TlJM0YVAryZeXA6TX0mb4gMkxVXwSQv7MCwgah7raeUd0dKUb4BPAYIgcMg==",
743 +
      "cpu": [
744 +
        "arm64"
745 +
      ],
746 +
      "dev": true,
747 +
      "libc": [
748 +
        "glibc"
749 +
      ],
750 +
      "license": "MIT",
751 +
      "optional": true,
752 +
      "os": [
753 +
        "linux"
754 +
      ],
755 +
      "engines": {
756 +
        "node": ">= 10"
757 +
      }
758 +
    },
759 +
    "node_modules/@napi-rs/keyring-linux-arm64-musl": {
760 +
      "version": "1.3.0",
761 +
      "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-arm64-musl/-/keyring-linux-arm64-musl-1.3.0.tgz",
762 +
      "integrity": "sha512-iIK6JWHXAJqDrEyLY3TmswwloVyt2vj+04TZnew+uSJ9gnDO8EwRbp3/iw3LpWaXiDO7VomGO6y8I0Id8uBZSw==",
763 +
      "cpu": [
764 +
        "arm64"
765 +
      ],
766 +
      "dev": true,
767 +
      "libc": [
768 +
        "musl"
769 +
      ],
770 +
      "license": "MIT",
771 +
      "optional": true,
772 +
      "os": [
773 +
        "linux"
774 +
      ],
775 +
      "engines": {
776 +
        "node": ">= 10"
777 +
      }
778 +
    },
779 +
    "node_modules/@napi-rs/keyring-linux-riscv64-gnu": {
780 +
      "version": "1.3.0",
781 +
      "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-riscv64-gnu/-/keyring-linux-riscv64-gnu-1.3.0.tgz",
782 +
      "integrity": "sha512-/PGqrwn6EwgtK6vccASSXJRfOSP4vN1F4ASsIQ+7MdrK6hNvAJ1FZPrIuD5gGGdxezo3F++To2Wq7DbuGIeuNQ==",
783 +
      "cpu": [
784 +
        "riscv64"
785 +
      ],
786 +
      "dev": true,
787 +
      "libc": [
788 +
        "glibc"
789 +
      ],
790 +
      "license": "MIT",
791 +
      "optional": true,
792 +
      "os": [
793 +
        "linux"
794 +
      ],
795 +
      "engines": {
796 +
        "node": ">= 10"
797 +
      }
798 +
    },
799 +
    "node_modules/@napi-rs/keyring-linux-x64-gnu": {
800 +
      "version": "1.3.0",
801 +
      "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-x64-gnu/-/keyring-linux-x64-gnu-1.3.0.tgz",
802 +
      "integrity": "sha512-2PDK1WKWTu9lBGq9VvNEkSlQD3O7YwVpmnyN2M3cy4v7NJ/8gDMd9GXv3G+FVXN13uhp4gnnPBS+ScefmEeD2A==",
803 +
      "cpu": [
804 +
        "x64"
805 +
      ],
806 +
      "dev": true,
807 +
      "libc": [
808 +
        "glibc"
809 +
      ],
810 +
      "license": "MIT",
811 +
      "optional": true,
812 +
      "os": [
813 +
        "linux"
814 +
      ],
815 +
      "engines": {
816 +
        "node": ">= 10"
817 +
      }
818 +
    },
819 +
    "node_modules/@napi-rs/keyring-linux-x64-musl": {
820 +
      "version": "1.3.0",
821 +
      "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-x64-musl/-/keyring-linux-x64-musl-1.3.0.tgz",
822 +
      "integrity": "sha512-oJ2HkX8YUo46QBkn0pG+HuIKQNqr523q6vBobCn+P95s4C4K6/kLBqHY/1bg5J4ap31DzsznhnFKcfBNBsjCnw==",
823 +
      "cpu": [
824 +
        "x64"
825 +
      ],
826 +
      "dev": true,
827 +
      "libc": [
828 +
        "musl"
829 +
      ],
830 +
      "license": "MIT",
831 +
      "optional": true,
832 +
      "os": [
833 +
        "linux"
834 +
      ],
835 +
      "engines": {
836 +
        "node": ">= 10"
837 +
      }
838 +
    },
839 +
    "node_modules/@napi-rs/keyring-win32-arm64-msvc": {
840 +
      "version": "1.3.0",
841 +
      "resolved": "https://registry.npmjs.org/@napi-rs/keyring-win32-arm64-msvc/-/keyring-win32-arm64-msvc-1.3.0.tgz",
842 +
      "integrity": "sha512-tOd3c/uAaeoE4ycVlmAdSvygz0Zt3zdca6Y7gokBeIbaRDWpjDIUOpU3MvML59XAaqyuKGsVVu0F/DZb1lHPmw==",
843 +
      "cpu": [
844 +
        "arm64"
845 +
      ],
846 +
      "dev": true,
847 +
      "license": "MIT",
848 +
      "optional": true,
849 +
      "os": [
850 +
        "win32"
851 +
      ],
852 +
      "engines": {
853 +
        "node": ">= 10"
854 +
      }
855 +
    },
856 +
    "node_modules/@napi-rs/keyring-win32-ia32-msvc": {
857 +
      "version": "1.3.0",
858 +
      "resolved": "https://registry.npmjs.org/@napi-rs/keyring-win32-ia32-msvc/-/keyring-win32-ia32-msvc-1.3.0.tgz",
859 +
      "integrity": "sha512-sPSqeAFZMGqP1R++M2JTza7GQJJ/TpCo6JU6Vcd4jnebvOaEDs9b7eipakU1PJdSvhpC2yXMCNRk9gXfrhuwHQ==",
860 +
      "cpu": [
861 +
        "ia32"
862 +
      ],
863 +
      "dev": true,
864 +
      "license": "MIT",
865 +
      "optional": true,
866 +
      "os": [
867 +
        "win32"
868 +
      ],
869 +
      "engines": {
870 +
        "node": ">= 10"
871 +
      }
872 +
    },
873 +
    "node_modules/@napi-rs/keyring-win32-x64-msvc": {
874 +
      "version": "1.3.0",
875 +
      "resolved": "https://registry.npmjs.org/@napi-rs/keyring-win32-x64-msvc/-/keyring-win32-x64-msvc-1.3.0.tgz",
876 +
      "integrity": "sha512-4DnCWXwDc0HRKwyRlG5y0VhKZW2tNRQfKKfyj6IX/KWfDNyq9hn4n+GL1auyDcOO/v8PwnhmYo2+rOOqCkvvOg==",
877 +
      "cpu": [
878 +
        "x64"
879 +
      ],
880 +
      "dev": true,
881 +
      "license": "MIT",
882 +
      "optional": true,
883 +
      "os": [
884 +
        "win32"
885 +
      ],
886 +
      "engines": {
887 +
        "node": ">= 10"
888 +
      }
889 +
    },
890 +
    "node_modules/@secretlint/core": {
891 +
      "version": "10.2.2",
892 +
      "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.2.2.tgz",
893 +
      "integrity": "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw==",
894 +
      "dev": true,
895 +
      "license": "MIT",
896 +
      "dependencies": {
897 +
        "@secretlint/profiler": "^10.2.2",
898 +
        "@secretlint/types": "^10.2.2",
899 +
        "debug": "^4.4.1",
900 +
        "structured-source": "^4.0.0"
901 +
      },
902 +
      "engines": {
903 +
        "node": ">=20.0.0"
904 +
      }
905 +
    },
906 +
    "node_modules/@secretlint/profiler": {
907 +
      "version": "10.2.2",
908 +
      "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.2.2.tgz",
909 +
      "integrity": "sha512-qm9rWfkh/o8OvzMIfY8a5bCmgIniSpltbVlUVl983zDG1bUuQNd1/5lUEeWx5o/WJ99bXxS7yNI4/KIXfHexig==",
910 +
      "dev": true,
911 +
      "license": "MIT"
912 +
    },
913 +
    "node_modules/@secretlint/secretlint-rule-no-dotenv": {
914 +
      "version": "10.2.2",
915 +
      "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.2.2.tgz",
916 +
      "integrity": "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg==",
917 +
      "dev": true,
918 +
      "license": "MIT",
919 +
      "dependencies": {
920 +
        "@secretlint/types": "^10.2.2"
921 +
      },
922 +
      "engines": {
923 +
        "node": ">=20.0.0"
924 +
      }
925 +
    },
926 +
    "node_modules/@secretlint/secretlint-rule-preset-recommend": {
927 +
      "version": "10.2.2",
928 +
      "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.2.2.tgz",
929 +
      "integrity": "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA==",
930 +
      "dev": true,
931 +
      "license": "MIT",
932 +
      "engines": {
933 +
        "node": ">=20.0.0"
934 +
      }
935 +
    },
936 +
    "node_modules/@secretlint/source-creator": {
937 +
      "version": "10.2.2",
938 +
      "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.2.2.tgz",
939 +
      "integrity": "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw==",
940 +
      "dev": true,
941 +
      "license": "MIT",
942 +
      "dependencies": {
943 +
        "@secretlint/types": "^10.2.2",
944 +
        "istextorbinary": "^9.5.0"
945 +
      },
946 +
      "engines": {
947 +
        "node": ">=20.0.0"
948 +
      }
949 +
    },
950 +
    "node_modules/@secretlint/types": {
951 +
      "version": "10.2.2",
952 +
      "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.2.2.tgz",
953 +
      "integrity": "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg==",
954 +
      "dev": true,
955 +
      "license": "MIT",
956 +
      "engines": {
957 +
        "node": ">=20.0.0"
958 +
      }
959 +
    },
960 +
    "node_modules/@types/node": {
961 +
      "version": "24.19.0",
962 +
      "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.0.tgz",
963 +
      "integrity": "sha512-zY+5tKxXdhGh1PYI0ac+7juvEu4OI6vWtVVoj5i2m42jxAY1U+zHGt6QCyOFwykdP62sM3MJ9stoYYUw5aCWew==",
964 +
      "dev": true,
965 +
      "license": "MIT",
966 +
      "dependencies": {
967 +
        "undici-types": ">=7.24.0 <7.24.7"
968 +
      }
969 +
    },
970 +
    "node_modules/@types/vscode": {
971 +
      "version": "1.138.0",
972 +
      "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.138.0.tgz",
973 +
      "integrity": "sha512-DhlvrucJa8n6EHst7qQcXOcjLKs/VRTQy55plLO+RN74jonYsr/t9dd66zBnAyz1tpDig0okQAxuoPHpVNje2g==",
974 +
      "dev": true,
975 +
      "license": "MIT"
976 +
    },
977 +
    "node_modules/@typespec/ts-http-runtime": {
978 +
      "version": "0.3.9",
979 +
      "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.9.tgz",
980 +
      "integrity": "sha512-edSdeAqkdxBVzA1yL1LrLCml1YjyCVvPMtMqJpbF+6K609tHe8V6sQUzFQSGcYNhcuhOceZtjvN32+mpIth30A==",
981 +
      "dev": true,
982 +
      "license": "MIT",
983 +
      "dependencies": {
984 +
        "http-proxy-agent": "^7.0.0",
985 +
        "https-proxy-agent": "^7.0.0",
986 +
        "tslib": "^2.6.2"
987 +
      },
988 +
      "engines": {
989 +
        "node": ">=22.0.0"
990 +
      }
991 +
    },
992 +
    "node_modules/@vscode/vsce": {
993 +
      "version": "4.0.0",
994 +
      "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-4.0.0.tgz",
995 +
      "integrity": "sha512-NImwuLaenMmb5D5Jer9/lzi/F9ZQUBOp8Azhj/BVYcTFgixv8KehFXqEUDjQlD2tAiw2E6dDGyjTuAB//di60A==",
996 +
      "dev": true,
997 +
      "license": "MIT",
998 +
      "dependencies": {
999 +
        "@azure/identity": "^4.13.2",
1000 +
        "@napi-rs/keyring": "^1.3.0",
1001 +
        "@secretlint/core": "^10.2.2",
1002 +
        "@secretlint/secretlint-rule-no-dotenv": "^10.2.2",
1003 +
        "@secretlint/secretlint-rule-preset-recommend": "^10.2.2",
1004 +
        "@secretlint/source-creator": "^10.2.2",
1005 +
        "@secretlint/types": "^10.2.2",
1006 +
        "@vscode/vsce-sign": "^2.1.0",
1007 +
        "azure-devops-node-api": "^12.5.0",
1008 +
        "cockatiel": "^3.2.1",
1009 +
        "commander": "^12.1.0",
1010 +
        "hosted-git-info": "^4.1.0",
1011 +
        "jsonc-parser": "^3.3.1",
1012 +
        "marked": "^18.0.11",
1013 +
        "mime": "^1.6.0",
1014 +
        "minimatch": "^10.2.6",
1015 +
        "parse5": "^8.0.1",
1016 +
        "proper-lockfile": "^4.1.2",
1017 +
        "read": "^1.0.7",
1018 +
        "semver": "^7.8.5",
1019 +
        "tinyglobby": "^0.2.17",
1020 +
        "typed-rest-client": "^1.8.11",
1021 +
        "url-join": "^4.0.1",
1022 +
        "xml2js": "^0.5.0",
1023 +
        "yauzl": "^3.4.0",
1024 +
        "yazl": "^2.5.1"
1025 +
      },
1026 +
      "bin": {
1027 +
        "vsce": "vsce"
1028 +
      },
1029 +
      "engines": {
1030 +
        "node": ">= 22"
1031 +
      }
1032 +
    },
1033 +
    "node_modules/@vscode/vsce-sign": {
1034 +
      "version": "2.1.0",
1035 +
      "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.1.0.tgz",
1036 +
      "integrity": "sha512-9AQrqazrBgTgRSuwleLVXUrIUphY02/SFCh2TKYoLV/xifJAdblhdmEmw5gUrYSPQ3sRwNs9iyCMD14sATEE6g==",
1037 +
      "dev": true,
1038 +
      "hasInstallScript": true,
1039 +
      "license": "SEE LICENSE IN LICENSE.txt",
1040 +
      "optionalDependencies": {
1041 +
        "@vscode/vsce-sign-alpine-arm64": "2.0.6",
1042 +
        "@vscode/vsce-sign-alpine-x64": "2.0.6",
1043 +
        "@vscode/vsce-sign-darwin-arm64": "2.0.6",
1044 +
        "@vscode/vsce-sign-darwin-x64": "2.0.6",
1045 +
        "@vscode/vsce-sign-linux-arm": "2.0.6",
1046 +
        "@vscode/vsce-sign-linux-arm64": "2.0.6",
1047 +
        "@vscode/vsce-sign-linux-x64": "2.0.6",
1048 +
        "@vscode/vsce-sign-win32-arm64": "2.0.6",
1049 +
        "@vscode/vsce-sign-win32-x64": "2.0.6"
1050 +
      }
1051 +
    },
1052 +
    "node_modules/@vscode/vsce-sign-alpine-arm64": {
1053 +
      "version": "2.0.6",
1054 +
      "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.6.tgz",
1055 +
      "integrity": "sha512-wKkJBsvKF+f0GfsUuGT0tSW0kZL87QggEiqNqK6/8hvqsXvpx8OsTEc3mnE1kejkh5r+qUyQ7PtF8jZYN0mo8Q==",
1056 +
      "cpu": [
1057 +
        "arm64"
1058 +
      ],
1059 +
      "dev": true,
1060 +
      "license": "SEE LICENSE IN LICENSE.txt",
1061 +
      "optional": true,
1062 +
      "os": [
1063 +
        "alpine"
1064 +
      ]
1065 +
    },
1066 +
    "node_modules/@vscode/vsce-sign-alpine-x64": {
1067 +
      "version": "2.0.6",
1068 +
      "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.6.tgz",
1069 +
      "integrity": "sha512-YoAGlmdK39vKi9jA18i4ufBbd95OqGJxRvF3n6ZbCyziwy3O+JgOpIUPxv5tjeO6gQfx29qBivQ8ZZTUF2Ba0w==",
1070 +
      "cpu": [
1071 +
        "x64"
1072 +
      ],
1073 +
      "dev": true,
1074 +
      "license": "SEE LICENSE IN LICENSE.txt",
1075 +
      "optional": true,
1076 +
      "os": [
1077 +
        "alpine"
1078 +
      ]
1079 +
    },
1080 +
    "node_modules/@vscode/vsce-sign-darwin-arm64": {
1081 +
      "version": "2.0.6",
1082 +
      "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.6.tgz",
1083 +
      "integrity": "sha512-5HMHaJRIQuozm/XQIiJiA0W9uhdblwwl2ZNDSSAeXGO9YhB9MH5C4KIHOmvyjUnKy4UCuiP43VKpIxW1VWP4tQ==",
1084 +
      "cpu": [
1085 +
        "arm64"
1086 +
      ],
1087 +
      "dev": true,
1088 +
      "license": "SEE LICENSE IN LICENSE.txt",
1089 +
      "optional": true,
1090 +
      "os": [
1091 +
        "darwin"
1092 +
      ]
1093 +
    },
1094 +
    "node_modules/@vscode/vsce-sign-darwin-x64": {
1095 +
      "version": "2.0.6",
1096 +
      "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.6.tgz",
1097 +
      "integrity": "sha512-25GsUbTAiNfHSuRItoQafXOIpxlYj+IXb4/qarrXu7kmbH94jlm5sdWSCKrrREs8+GsXF1b+l3OB7VJy5jsykw==",
1098 +
      "cpu": [
1099 +
        "x64"
1100 +
      ],
1101 +
      "dev": true,
1102 +
      "license": "SEE LICENSE IN LICENSE.txt",
1103 +
      "optional": true,
1104 +
      "os": [
1105 +
        "darwin"
1106 +
      ]
1107 +
    },
1108 +
    "node_modules/@vscode/vsce-sign-linux-arm": {
1109 +
      "version": "2.0.6",
1110 +
      "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.6.tgz",
1111 +
      "integrity": "sha512-UndEc2Xlq4HsuMPnwu7420uqceXjs4yb5W8E2/UkaHBB9OWCwMd3/bRe/1eLe3D8kPpxzcaeTyXiK3RdzS/1CA==",
1112 +
      "cpu": [
1113 +
        "arm"
1114 +
      ],
1115 +
      "dev": true,
1116 +
      "license": "SEE LICENSE IN LICENSE.txt",
1117 +
      "optional": true,
1118 +
      "os": [
1119 +
        "linux"
1120 +
      ]
1121 +
    },
1122 +
    "node_modules/@vscode/vsce-sign-linux-arm64": {
1123 +
      "version": "2.0.6",
1124 +
      "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.6.tgz",
1125 +
      "integrity": "sha512-cfb1qK7lygtMa4NUl2582nP7aliLYuDEVpAbXJMkDq1qE+olIw/es+C8j1LJwvcRq1I2yWGtSn3EkDp9Dq5FdA==",
1126 +
      "cpu": [
1127 +
        "arm64"
1128 +
      ],
1129 +
      "dev": true,
1130 +
      "license": "SEE LICENSE IN LICENSE.txt",
1131 +
      "optional": true,
1132 +
      "os": [
1133 +
        "linux"
1134 +
      ]
1135 +
    },
1136 +
    "node_modules/@vscode/vsce-sign-linux-x64": {
1137 +
      "version": "2.0.6",
1138 +
      "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.6.tgz",
1139 +
      "integrity": "sha512-/olerl1A4sOqdP+hjvJ1sbQjKN07Y3DVnxO4gnbn/ahtQvFrdhUi0G1VsZXDNjfqmXw57DmPi5ASnj/8PGZhAA==",
1140 +
      "cpu": [
1141 +
        "x64"
1142 +
      ],
1143 +
      "dev": true,
1144 +
      "license": "SEE LICENSE IN LICENSE.txt",
1145 +
      "optional": true,
1146 +
      "os": [
1147 +
        "linux"
1148 +
      ]
1149 +
    },
1150 +
    "node_modules/@vscode/vsce-sign-win32-arm64": {
1151 +
      "version": "2.0.6",
1152 +
      "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.6.tgz",
1153 +
      "integrity": "sha512-ivM/MiGIY0PJNZBoGtlRBM/xDpwbdlCWomUWuLmIxbi1Cxe/1nooYrEQoaHD8ojVRgzdQEUzMsRbyF5cJJgYOg==",
1154 +
      "cpu": [
1155 +
        "arm64"
1156 +
      ],
1157 +
      "dev": true,
1158 +
      "license": "SEE LICENSE IN LICENSE.txt",
1159 +
      "optional": true,
1160 +
      "os": [
1161 +
        "win32"
1162 +
      ]
1163 +
    },
1164 +
    "node_modules/@vscode/vsce-sign-win32-x64": {
1165 +
      "version": "2.0.6",
1166 +
      "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.6.tgz",
1167 +
      "integrity": "sha512-mgth9Kvze+u8CruYMmhHw6Zgy3GRX2S+Ed5oSokDEK5vPEwGGKnmuXua9tmFhomeAnhgJnL4DCna3TiNuGrBTQ==",
1168 +
      "cpu": [
1169 +
        "x64"
1170 +
      ],
1171 +
      "dev": true,
1172 +
      "license": "SEE LICENSE IN LICENSE.txt",
1173 +
      "optional": true,
1174 +
      "os": [
1175 +
        "win32"
1176 +
      ]
1177 +
    },
1178 +
    "node_modules/agent-base": {
1179 +
      "version": "7.1.4",
1180 +
      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
1181 +
      "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
1182 +
      "dev": true,
1183 +
      "license": "MIT",
1184 +
      "engines": {
1185 +
        "node": ">= 14"
1186 +
      }
1187 +
    },
1188 +
    "node_modules/azure-devops-node-api": {
1189 +
      "version": "12.5.0",
1190 +
      "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz",
1191 +
      "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==",
1192 +
      "dev": true,
1193 +
      "license": "MIT",
1194 +
      "dependencies": {
1195 +
        "tunnel": "0.0.6",
1196 +
        "typed-rest-client": "^1.8.4"
1197 +
      }
1198 +
    },
1199 +
    "node_modules/balanced-match": {
1200 +
      "version": "4.0.4",
1201 +
      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
1202 +
      "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
1203 +
      "dev": true,
1204 +
      "license": "MIT",
1205 +
      "engines": {
1206 +
        "node": "18 || 20 || >=22"
1207 +
      }
1208 +
    },
1209 +
    "node_modules/binaryextensions": {
1210 +
      "version": "6.11.0",
1211 +
      "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz",
1212 +
      "integrity": "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==",
1213 +
      "dev": true,
1214 +
      "license": "Artistic-2.0",
1215 +
      "dependencies": {
1216 +
        "editions": "^6.21.0"
1217 +
      },
1218 +
      "engines": {
1219 +
        "node": ">=4"
1220 +
      },
1221 +
      "funding": {
1222 +
        "url": "https://bevry.me/fund"
1223 +
      }
1224 +
    },
1225 +
    "node_modules/boundary": {
1226 +
      "version": "2.0.0",
1227 +
      "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz",
1228 +
      "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==",
1229 +
      "dev": true,
1230 +
      "license": "BSD-2-Clause"
1231 +
    },
1232 +
    "node_modules/brace-expansion": {
1233 +
      "version": "5.0.12",
1234 +
      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
1235 +
      "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
1236 +
      "dev": true,
1237 +
      "license": "MIT",
1238 +
      "dependencies": {
1239 +
        "balanced-match": "^4.0.2"
1240 +
      },
1241 +
      "engines": {
1242 +
        "node": "20 || >=22"
1243 +
      }
1244 +
    },
1245 +
    "node_modules/buffer-crc32": {
1246 +
      "version": "0.2.13",
1247 +
      "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
1248 +
      "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
1249 +
      "dev": true,
1250 +
      "license": "MIT",
1251 +
      "engines": {
1252 +
        "node": "*"
1253 +
      }
1254 +
    },
1255 +
    "node_modules/buffer-equal-constant-time": {
1256 +
      "version": "1.0.1",
1257 +
      "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
1258 +
      "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
1259 +
      "dev": true,
1260 +
      "license": "BSD-3-Clause"
1261 +
    },
1262 +
    "node_modules/bundle-name": {
1263 +
      "version": "4.1.1",
1264 +
      "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.1.tgz",
1265 +
      "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==",
1266 +
      "dev": true,
1267 +
      "license": "MIT",
1268 +
      "dependencies": {
1269 +
        "run-applescript": "^7.0.0"
1270 +
      },
1271 +
      "engines": {
1272 +
        "node": ">=18"
1273 +
      },
1274 +
      "funding": {
1275 +
        "url": "https://github.com/sponsors/sindresorhus"
1276 +
      }
1277 +
    },
1278 +
    "node_modules/call-bind-apply-helpers": {
1279 +
      "version": "1.0.2",
1280 +
      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
1281 +
      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
1282 +
      "dev": true,
1283 +
      "license": "MIT",
1284 +
      "dependencies": {
1285 +
        "es-errors": "^1.3.0",
1286 +
        "function-bind": "^1.1.2"
1287 +
      },
1288 +
      "engines": {
1289 +
        "node": ">= 0.4"
1290 +
      }
1291 +
    },
1292 +
    "node_modules/call-bound": {
1293 +
      "version": "1.0.4",
1294 +
      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
1295 +
      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
1296 +
      "dev": true,
1297 +
      "license": "MIT",
1298 +
      "dependencies": {
1299 +
        "call-bind-apply-helpers": "^1.0.2",
1300 +
        "get-intrinsic": "^1.3.0"
1301 +
      },
1302 +
      "engines": {
1303 +
        "node": ">= 0.4"
1304 +
      },
1305 +
      "funding": {
1306 +
        "url": "https://github.com/sponsors/ljharb"
1307 +
      }
1308 +
    },
1309 +
    "node_modules/cockatiel": {
1310 +
      "version": "3.2.1",
1311 +
      "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz",
1312 +
      "integrity": "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==",
1313 +
      "dev": true,
1314 +
      "license": "MIT",
1315 +
      "engines": {
1316 +
        "node": ">=16"
1317 +
      }
1318 +
    },
1319 +
    "node_modules/commander": {
1320 +
      "version": "12.1.0",
1321 +
      "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
1322 +
      "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
1323 +
      "dev": true,
1324 +
      "license": "MIT",
1325 +
      "engines": {
1326 +
        "node": ">=18"
1327 +
      }
1328 +
    },
1329 +
    "node_modules/debug": {
1330 +
      "version": "4.4.3",
1331 +
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
1332 +
      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
1333 +
      "dev": true,
1334 +
      "license": "MIT",
1335 +
      "dependencies": {
1336 +
        "ms": "^2.1.3"
1337 +
      },
1338 +
      "engines": {
1339 +
        "node": ">=6.0"
1340 +
      },
1341 +
      "peerDependenciesMeta": {
1342 +
        "supports-color": {
1343 +
          "optional": true
1344 +
        }
1345 +
      }
1346 +
    },
1347 +
    "node_modules/default-browser": {
1348 +
      "version": "5.5.1",
1349 +
      "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz",
1350 +
      "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==",
1351 +
      "dev": true,
1352 +
      "license": "MIT",
1353 +
      "dependencies": {
1354 +
        "bundle-name": "^4.1.0",
1355 +
        "default-browser-id": "^5.0.0"
1356 +
      },
1357 +
      "engines": {
1358 +
        "node": ">=18"
1359 +
      },
1360 +
      "funding": {
1361 +
        "url": "https://github.com/sponsors/sindresorhus"
1362 +
      }
1363 +
    },
1364 +
    "node_modules/default-browser-id": {
1365 +
      "version": "5.0.1",
1366 +
      "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz",
1367 +
      "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==",
1368 +
      "dev": true,
1369 +
      "license": "MIT",
1370 +
      "engines": {
1371 +
        "node": ">=18"
1372 +
      },
1373 +
      "funding": {
1374 +
        "url": "https://github.com/sponsors/sindresorhus"
1375 +
      }
1376 +
    },
1377 +
    "node_modules/define-lazy-prop": {
1378 +
      "version": "3.0.0",
1379 +
      "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
1380 +
      "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
1381 +
      "dev": true,
1382 +
      "license": "MIT",
1383 +
      "engines": {
1384 +
        "node": ">=12"
1385 +
      },
1386 +
      "funding": {
1387 +
        "url": "https://github.com/sponsors/sindresorhus"
1388 +
      }
1389 +
    },
1390 +
    "node_modules/dunder-proto": {
1391 +
      "version": "1.0.1",
1392 +
      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
1393 +
      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
1394 +
      "dev": true,
1395 +
      "license": "MIT",
1396 +
      "dependencies": {
1397 +
        "call-bind-apply-helpers": "^1.0.1",
1398 +
        "es-errors": "^1.3.0",
1399 +
        "gopd": "^1.2.0"
1400 +
      },
1401 +
      "engines": {
1402 +
        "node": ">= 0.4"
1403 +
      }
1404 +
    },
1405 +
    "node_modules/ecdsa-sig-formatter": {
1406 +
      "version": "1.0.11",
1407 +
      "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
1408 +
      "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
1409 +
      "dev": true,
1410 +
      "license": "Apache-2.0",
1411 +
      "dependencies": {
1412 +
        "safe-buffer": "^5.0.1"
1413 +
      }
1414 +
    },
1415 +
    "node_modules/editions": {
1416 +
      "version": "6.22.0",
1417 +
      "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz",
1418 +
      "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==",
1419 +
      "dev": true,
1420 +
      "license": "Artistic-2.0",
1421 +
      "dependencies": {
1422 +
        "version-range": "^4.15.0"
1423 +
      },
1424 +
      "engines": {
1425 +
        "ecmascript": ">= es5",
1426 +
        "node": ">=4"
1427 +
      },
1428 +
      "funding": {
1429 +
        "url": "https://bevry.me/fund"
1430 +
      }
1431 +
    },
1432 +
    "node_modules/entities": {
1433 +
      "version": "8.1.0",
1434 +
      "resolved": "https://registry.npmjs.org/entities/-/entities-8.1.0.tgz",
1435 +
      "integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==",
1436 +
      "dev": true,
1437 +
      "license": "BSD-2-Clause",
1438 +
      "engines": {
1439 +
        "node": ">=20.19.0"
1440 +
      },
1441 +
      "funding": {
1442 +
        "url": "https://github.com/fb55/entities?sponsor=1"
1443 +
      }
1444 +
    },
1445 +
    "node_modules/es-define-property": {
1446 +
      "version": "1.0.1",
1447 +
      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
1448 +
      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
1449 +
      "dev": true,
1450 +
      "license": "MIT",
1451 +
      "engines": {
1452 +
        "node": ">= 0.4"
1453 +
      }
1454 +
    },
1455 +
    "node_modules/es-errors": {
1456 +
      "version": "1.3.0",
1457 +
      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
1458 +
      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
1459 +
      "dev": true,
1460 +
      "license": "MIT",
1461 +
      "engines": {
1462 +
        "node": ">= 0.4"
1463 +
      }
1464 +
    },
1465 +
    "node_modules/es-object-atoms": {
1466 +
      "version": "1.1.2",
1467 +
      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
1468 +
      "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
1469 +
      "dev": true,
1470 +
      "license": "MIT",
1471 +
      "dependencies": {
1472 +
        "es-errors": "^1.3.0"
1473 +
      },
1474 +
      "engines": {
1475 +
        "node": ">= 0.4"
1476 +
      }
1477 +
    },
1478 +
    "node_modules/esbuild": {
1479 +
      "version": "0.28.2",
1480 +
      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
1481 +
      "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
1482 +
      "dev": true,
1483 +
      "hasInstallScript": true,
1484 +
      "license": "MIT",
1485 +
      "bin": {
1486 +
        "esbuild": "bin/esbuild"
1487 +
      },
1488 +
      "engines": {
1489 +
        "node": ">=18"
1490 +
      },
1491 +
      "optionalDependencies": {
1492 +
        "@esbuild/aix-ppc64": "0.28.2",
1493 +
        "@esbuild/android-arm": "0.28.2",
1494 +
        "@esbuild/android-arm64": "0.28.2",
1495 +
        "@esbuild/android-x64": "0.28.2",
1496 +
        "@esbuild/darwin-arm64": "0.28.2",
1497 +
        "@esbuild/darwin-x64": "0.28.2",
1498 +
        "@esbuild/freebsd-arm64": "0.28.2",
1499 +
        "@esbuild/freebsd-x64": "0.28.2",
1500 +
        "@esbuild/linux-arm": "0.28.2",
1501 +
        "@esbuild/linux-arm64": "0.28.2",
1502 +
        "@esbuild/linux-ia32": "0.28.2",
1503 +
        "@esbuild/linux-loong64": "0.28.2",
1504 +
        "@esbuild/linux-mips64el": "0.28.2",
1505 +
        "@esbuild/linux-ppc64": "0.28.2",
1506 +
        "@esbuild/linux-riscv64": "0.28.2",
1507 +
        "@esbuild/linux-s390x": "0.28.2",
1508 +
        "@esbuild/linux-x64": "0.28.2",
1509 +
        "@esbuild/netbsd-arm64": "0.28.2",
1510 +
        "@esbuild/netbsd-x64": "0.28.2",
1511 +
        "@esbuild/openbsd-arm64": "0.28.2",
1512 +
        "@esbuild/openbsd-x64": "0.28.2",
1513 +
        "@esbuild/openharmony-arm64": "0.28.2",
1514 +
        "@esbuild/sunos-x64": "0.28.2",
1515 +
        "@esbuild/win32-arm64": "0.28.2",
1516 +
        "@esbuild/win32-ia32": "0.28.2",
1517 +
        "@esbuild/win32-x64": "0.28.2"
1518 +
      }
1519 +
    },
1520 +
    "node_modules/fdir": {
1521 +
      "version": "6.5.0",
1522 +
      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
1523 +
      "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
1524 +
      "dev": true,
1525 +
      "license": "MIT",
1526 +
      "engines": {
1527 +
        "node": ">=12.0.0"
1528 +
      },
1529 +
      "peerDependencies": {
1530 +
        "picomatch": "^3 || ^4"
1531 +
      },
1532 +
      "peerDependenciesMeta": {
1533 +
        "picomatch": {
1534 +
          "optional": true
1535 +
        }
1536 +
      }
1537 +
    },
1538 +
    "node_modules/function-bind": {
1539 +
      "version": "1.1.2",
1540 +
      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
1541 +
      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
1542 +
      "dev": true,
1543 +
      "license": "MIT",
1544 +
      "funding": {
1545 +
        "url": "https://github.com/sponsors/ljharb"
1546 +
      }
1547 +
    },
1548 +
    "node_modules/get-intrinsic": {
1549 +
      "version": "1.3.0",
1550 +
      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
1551 +
      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
1552 +
      "dev": true,
1553 +
      "license": "MIT",
1554 +
      "dependencies": {
1555 +
        "call-bind-apply-helpers": "^1.0.2",
1556 +
        "es-define-property": "^1.0.1",
1557 +
        "es-errors": "^1.3.0",
1558 +
        "es-object-atoms": "^1.1.1",
1559 +
        "function-bind": "^1.1.2",
1560 +
        "get-proto": "^1.0.1",
1561 +
        "gopd": "^1.2.0",
1562 +
        "has-symbols": "^1.1.0",
1563 +
        "hasown": "^2.0.2",
1564 +
        "math-intrinsics": "^1.1.0"
1565 +
      },
1566 +
      "engines": {
1567 +
        "node": ">= 0.4"
1568 +
      },
1569 +
      "funding": {
1570 +
        "url": "https://github.com/sponsors/ljharb"
1571 +
      }
1572 +
    },
1573 +
    "node_modules/get-proto": {
1574 +
      "version": "1.0.1",
1575 +
      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
1576 +
      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
1577 +
      "dev": true,
1578 +
      "license": "MIT",
1579 +
      "dependencies": {
1580 +
        "dunder-proto": "^1.0.1",
1581 +
        "es-object-atoms": "^1.0.0"
1582 +
      },
1583 +
      "engines": {
1584 +
        "node": ">= 0.4"
1585 +
      }
1586 +
    },
1587 +
    "node_modules/gopd": {
1588 +
      "version": "1.2.0",
1589 +
      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
1590 +
      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
1591 +
      "dev": true,
1592 +
      "license": "MIT",
1593 +
      "engines": {
1594 +
        "node": ">= 0.4"
1595 +
      },
1596 +
      "funding": {
1597 +
        "url": "https://github.com/sponsors/ljharb"
1598 +
      }
1599 +
    },
1600 +
    "node_modules/graceful-fs": {
1601 +
      "version": "4.2.11",
1602 +
      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
1603 +
      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
1604 +
      "dev": true,
1605 +
      "license": "ISC"
1606 +
    },
1607 +
    "node_modules/has-symbols": {
1608 +
      "version": "1.1.0",
1609 +
      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
1610 +
      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
1611 +
      "dev": true,
1612 +
      "license": "MIT",
1613 +
      "engines": {
1614 +
        "node": ">= 0.4"
1615 +
      },
1616 +
      "funding": {
1617 +
        "url": "https://github.com/sponsors/ljharb"
1618 +
      }
1619 +
    },
1620 +
    "node_modules/hasown": {
1621 +
      "version": "2.0.4",
1622 +
      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
1623 +
      "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
1624 +
      "dev": true,
1625 +
      "license": "MIT",
1626 +
      "dependencies": {
1627 +
        "function-bind": "^1.1.2"
1628 +
      },
1629 +
      "engines": {
1630 +
        "node": ">= 0.4"
1631 +
      }
1632 +
    },
1633 +
    "node_modules/hosted-git-info": {
1634 +
      "version": "4.1.0",
1635 +
      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
1636 +
      "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==",
1637 +
      "dev": true,
1638 +
      "license": "ISC",
1639 +
      "dependencies": {
1640 +
        "lru-cache": "^6.0.0"
1641 +
      },
1642 +
      "engines": {
1643 +
        "node": ">=10"
1644 +
      }
1645 +
    },
1646 +
    "node_modules/http-proxy-agent": {
1647 +
      "version": "7.0.2",
1648 +
      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
1649 +
      "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
1650 +
      "dev": true,
1651 +
      "license": "MIT",
1652 +
      "dependencies": {
1653 +
        "agent-base": "^7.1.0",
1654 +
        "debug": "^4.3.4"
1655 +
      },
1656 +
      "engines": {
1657 +
        "node": ">= 14"
1658 +
      }
1659 +
    },
1660 +
    "node_modules/https-proxy-agent": {
1661 +
      "version": "7.0.6",
1662 +
      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
1663 +
      "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
1664 +
      "dev": true,
1665 +
      "license": "MIT",
1666 +
      "dependencies": {
1667 +
        "agent-base": "^7.1.2",
1668 +
        "debug": "4"
1669 +
      },
1670 +
      "engines": {
1671 +
        "node": ">= 14"
1672 +
      }
1673 +
    },
1674 +
    "node_modules/is-docker": {
1675 +
      "version": "3.0.0",
1676 +
      "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
1677 +
      "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
1678 +
      "dev": true,
1679 +
      "license": "MIT",
1680 +
      "bin": {
1681 +
        "is-docker": "cli.js"
1682 +
      },
1683 +
      "engines": {
1684 +
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
1685 +
      },
1686 +
      "funding": {
1687 +
        "url": "https://github.com/sponsors/sindresorhus"
1688 +
      }
1689 +
    },
1690 +
    "node_modules/is-inside-container": {
1691 +
      "version": "1.0.0",
1692 +
      "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
1693 +
      "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
1694 +
      "dev": true,
1695 +
      "license": "MIT",
1696 +
      "dependencies": {
1697 +
        "is-docker": "^3.0.0"
1698 +
      },
1699 +
      "bin": {
1700 +
        "is-inside-container": "cli.js"
1701 +
      },
1702 +
      "engines": {
1703 +
        "node": ">=14.16"
1704 +
      },
1705 +
      "funding": {
1706 +
        "url": "https://github.com/sponsors/sindresorhus"
1707 +
      }
1708 +
    },
1709 +
    "node_modules/is-wsl": {
1710 +
      "version": "3.1.1",
1711 +
      "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz",
1712 +
      "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==",
1713 +
      "dev": true,
1714 +
      "license": "MIT",
1715 +
      "dependencies": {
1716 +
        "is-inside-container": "^1.0.0"
1717 +
      },
1718 +
      "engines": {
1719 +
        "node": ">=16"
1720 +
      },
1721 +
      "funding": {
1722 +
        "url": "https://github.com/sponsors/sindresorhus"
1723 +
      }
1724 +
    },
1725 +
    "node_modules/istextorbinary": {
1726 +
      "version": "9.5.0",
1727 +
      "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz",
1728 +
      "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==",
1729 +
      "dev": true,
1730 +
      "license": "Artistic-2.0",
1731 +
      "dependencies": {
1732 +
        "binaryextensions": "^6.11.0",
1733 +
        "editions": "^6.21.0",
1734 +
        "textextensions": "^6.11.0"
1735 +
      },
1736 +
      "engines": {
1737 +
        "node": ">=4"
1738 +
      },
1739 +
      "funding": {
1740 +
        "url": "https://bevry.me/fund"
1741 +
      }
1742 +
    },
1743 +
    "node_modules/jsonc-parser": {
1744 +
      "version": "3.3.1",
1745 +
      "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz",
1746 +
      "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==",
1747 +
      "dev": true,
1748 +
      "license": "MIT"
1749 +
    },
1750 +
    "node_modules/jsonwebtoken": {
1751 +
      "version": "9.0.3",
1752 +
      "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
1753 +
      "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
1754 +
      "dev": true,
1755 +
      "license": "MIT",
1756 +
      "dependencies": {
1757 +
        "jws": "^4.0.1",
1758 +
        "lodash.includes": "^4.3.0",
1759 +
        "lodash.isboolean": "^3.0.3",
1760 +
        "lodash.isinteger": "^4.0.4",
1761 +
        "lodash.isnumber": "^3.0.3",
1762 +
        "lodash.isplainobject": "^4.0.6",
1763 +
        "lodash.isstring": "^4.0.1",
1764 +
        "lodash.once": "^4.0.0",
1765 +
        "ms": "^2.1.1",
1766 +
        "semver": "^7.5.4"
1767 +
      },
1768 +
      "engines": {
1769 +
        "node": ">=12",
1770 +
        "npm": ">=6"
1771 +
      }
1772 +
    },
1773 +
    "node_modules/jwa": {
1774 +
      "version": "2.0.1",
1775 +
      "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
1776 +
      "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
1777 +
      "dev": true,
1778 +
      "license": "MIT",
1779 +
      "dependencies": {
1780 +
        "buffer-equal-constant-time": "^1.0.1",
1781 +
        "ecdsa-sig-formatter": "1.0.11",
1782 +
        "safe-buffer": "^5.0.1"
1783 +
      }
1784 +
    },
1785 +
    "node_modules/jws": {
1786 +
      "version": "4.0.1",
1787 +
      "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
1788 +
      "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
1789 +
      "dev": true,
1790 +
      "license": "MIT",
1791 +
      "dependencies": {
1792 +
        "jwa": "^2.0.1",
1793 +
        "safe-buffer": "^5.0.1"
1794 +
      }
1795 +
    },
1796 +
    "node_modules/lodash.includes": {
1797 +
      "version": "4.3.0",
1798 +
      "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
1799 +
      "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
1800 +
      "dev": true,
1801 +
      "license": "MIT"
1802 +
    },
1803 +
    "node_modules/lodash.isboolean": {
1804 +
      "version": "3.0.3",
1805 +
      "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
1806 +
      "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
1807 +
      "dev": true,
1808 +
      "license": "MIT"
1809 +
    },
1810 +
    "node_modules/lodash.isinteger": {
1811 +
      "version": "4.0.4",
1812 +
      "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
1813 +
      "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
1814 +
      "dev": true,
1815 +
      "license": "MIT"
1816 +
    },
1817 +
    "node_modules/lodash.isnumber": {
1818 +
      "version": "3.0.3",
1819 +
      "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
1820 +
      "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
1821 +
      "dev": true,
1822 +
      "license": "MIT"
1823 +
    },
1824 +
    "node_modules/lodash.isplainobject": {
1825 +
      "version": "4.0.6",
1826 +
      "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
1827 +
      "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
1828 +
      "dev": true,
1829 +
      "license": "MIT"
1830 +
    },
1831 +
    "node_modules/lodash.isstring": {
1832 +
      "version": "4.0.1",
1833 +
      "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
1834 +
      "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
1835 +
      "dev": true,
1836 +
      "license": "MIT"
1837 +
    },
1838 +
    "node_modules/lodash.once": {
1839 +
      "version": "4.1.1",
1840 +
      "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
1841 +
      "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
1842 +
      "dev": true,
1843 +
      "license": "MIT"
1844 +
    },
1845 +
    "node_modules/lru-cache": {
1846 +
      "version": "6.0.0",
1847 +
      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
1848 +
      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
1849 +
      "dev": true,
1850 +
      "license": "ISC",
1851 +
      "dependencies": {
1852 +
        "yallist": "^4.0.0"
1853 +
      },
1854 +
      "engines": {
1855 +
        "node": ">=10"
1856 +
      }
1857 +
    },
1858 +
    "node_modules/marked": {
1859 +
      "version": "18.0.14",
1860 +
      "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.14.tgz",
1861 +
      "integrity": "sha512-mBHK6FBHuBAlhgRe88w9F0O1AbwwXJUcQibUbC/QcdTbVGAD7aWza+xt3N6oT/jCZx3/OMeS+8rnuiHZcQ9s7A==",
1862 +
      "dev": true,
1863 +
      "license": "MIT",
1864 +
      "bin": {
1865 +
        "marked": "bin/marked.js"
1866 +
      },
1867 +
      "engines": {
1868 +
        "node": ">= 20"
1869 +
      }
1870 +
    },
1871 +
    "node_modules/math-intrinsics": {
1872 +
      "version": "1.1.0",
1873 +
      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
1874 +
      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
1875 +
      "dev": true,
1876 +
      "license": "MIT",
1877 +
      "engines": {
1878 +
        "node": ">= 0.4"
1879 +
      }
1880 +
    },
1881 +
    "node_modules/mime": {
1882 +
      "version": "1.6.0",
1883 +
      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
1884 +
      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
1885 +
      "dev": true,
1886 +
      "license": "MIT",
1887 +
      "bin": {
1888 +
        "mime": "cli.js"
1889 +
      },
1890 +
      "engines": {
1891 +
        "node": ">=4"
1892 +
      }
1893 +
    },
1894 +
    "node_modules/minimatch": {
1895 +
      "version": "10.2.6",
1896 +
      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
1897 +
      "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
1898 +
      "dev": true,
1899 +
      "license": "BlueOak-1.0.0",
1900 +
      "dependencies": {
1901 +
        "brace-expansion": "^5.0.8"
1902 +
      },
1903 +
      "engines": {
1904 +
        "node": "18 || 20 || >=22"
1905 +
      },
1906 +
      "funding": {
1907 +
        "url": "https://github.com/sponsors/isaacs"
1908 +
      }
1909 +
    },
1910 +
    "node_modules/ms": {
1911 +
      "version": "2.1.3",
1912 +
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1913 +
      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1914 +
      "dev": true,
1915 +
      "license": "MIT"
1916 +
    },
1917 +
    "node_modules/mute-stream": {
1918 +
      "version": "0.0.8",
1919 +
      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
1920 +
      "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
1921 +
      "dev": true,
1922 +
      "license": "ISC"
1923 +
    },
1924 +
    "node_modules/object-inspect": {
1925 +
      "version": "1.13.4",
1926 +
      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
1927 +
      "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
1928 +
      "dev": true,
1929 +
      "license": "MIT",
1930 +
      "engines": {
1931 +
        "node": ">= 0.4"
1932 +
      },
1933 +
      "funding": {
1934 +
        "url": "https://github.com/sponsors/ljharb"
1935 +
      }
1936 +
    },
1937 +
    "node_modules/open": {
1938 +
      "version": "10.2.0",
1939 +
      "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz",
1940 +
      "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==",
1941 +
      "dev": true,
1942 +
      "license": "MIT",
1943 +
      "dependencies": {
1944 +
        "default-browser": "^5.2.1",
1945 +
        "define-lazy-prop": "^3.0.0",
1946 +
        "is-inside-container": "^1.0.0",
1947 +
        "wsl-utils": "^0.1.0"
1948 +
      },
1949 +
      "engines": {
1950 +
        "node": ">=18"
1951 +
      },
1952 +
      "funding": {
1953 +
        "url": "https://github.com/sponsors/sindresorhus"
1954 +
      }
1955 +
    },
1956 +
    "node_modules/parse5": {
1957 +
      "version": "8.0.1",
1958 +
      "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
1959 +
      "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==",
1960 +
      "dev": true,
1961 +
      "license": "MIT",
1962 +
      "dependencies": {
1963 +
        "entities": "^8.0.0"
1964 +
      },
1965 +
      "funding": {
1966 +
        "url": "https://github.com/inikulin/parse5?sponsor=1"
1967 +
      }
1968 +
    },
1969 +
    "node_modules/pend": {
1970 +
      "version": "1.2.0",
1971 +
      "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
1972 +
      "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
1973 +
      "dev": true,
1974 +
      "license": "MIT"
1975 +
    },
1976 +
    "node_modules/picomatch": {
1977 +
      "version": "4.0.7",
1978 +
      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
1979 +
      "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
1980 +
      "dev": true,
1981 +
      "license": "MIT",
1982 +
      "engines": {
1983 +
        "node": ">=12"
1984 +
      },
1985 +
      "funding": {
1986 +
        "url": "https://github.com/sponsors/jonschlinkert"
1987 +
      }
1988 +
    },
1989 +
    "node_modules/proper-lockfile": {
1990 +
      "version": "4.1.2",
1991 +
      "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
1992 +
      "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
1993 +
      "dev": true,
1994 +
      "license": "MIT",
1995 +
      "dependencies": {
1996 +
        "graceful-fs": "^4.2.4",
1997 +
        "retry": "^0.12.0",
1998 +
        "signal-exit": "^3.0.2"
1999 +
      }
2000 +
    },
2001 +
    "node_modules/qs": {
2002 +
      "version": "6.16.0",
2003 +
      "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
2004 +
      "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
2005 +
      "dev": true,
2006 +
      "license": "BSD-3-Clause",
2007 +
      "dependencies": {
2008 +
        "es-define-property": "^1.0.1",
2009 +
        "side-channel": "^1.1.1"
2010 +
      },
2011 +
      "engines": {
2012 +
        "node": ">=0.6"
2013 +
      },
2014 +
      "funding": {
2015 +
        "url": "https://github.com/sponsors/ljharb"
2016 +
      }
2017 +
    },
2018 +
    "node_modules/read": {
2019 +
      "version": "1.0.7",
2020 +
      "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz",
2021 +
      "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==",
2022 +
      "dev": true,
2023 +
      "license": "ISC",
2024 +
      "dependencies": {
2025 +
        "mute-stream": "~0.0.4"
2026 +
      },
2027 +
      "engines": {
2028 +
        "node": ">=0.8"
2029 +
      }
2030 +
    },
2031 +
    "node_modules/retry": {
2032 +
      "version": "0.12.0",
2033 +
      "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
2034 +
      "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
2035 +
      "dev": true,
2036 +
      "license": "MIT",
2037 +
      "engines": {
2038 +
        "node": ">= 4"
2039 +
      }
2040 +
    },
2041 +
    "node_modules/run-applescript": {
2042 +
      "version": "7.1.0",
2043 +
      "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
2044 +
      "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==",
2045 +
      "dev": true,
2046 +
      "license": "MIT",
2047 +
      "engines": {
2048 +
        "node": ">=18"
2049 +
      },
2050 +
      "funding": {
2051 +
        "url": "https://github.com/sponsors/sindresorhus"
2052 +
      }
2053 +
    },
2054 +
    "node_modules/safe-buffer": {
2055 +
      "version": "5.2.1",
2056 +
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
2057 +
      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
2058 +
      "dev": true,
2059 +
      "funding": [
2060 +
        {
2061 +
          "type": "github",
2062 +
          "url": "https://github.com/sponsors/feross"
2063 +
        },
2064 +
        {
2065 +
          "type": "patreon",
2066 +
          "url": "https://www.patreon.com/feross"
2067 +
        },
2068 +
        {
2069 +
          "type": "consulting",
2070 +
          "url": "https://feross.org/support"
2071 +
        }
2072 +
      ],
2073 +
      "license": "MIT"
2074 +
    },
2075 +
    "node_modules/sax": {
2076 +
      "version": "1.6.1",
2077 +
      "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz",
2078 +
      "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==",
2079 +
      "dev": true,
2080 +
      "license": "BlueOak-1.0.0",
2081 +
      "engines": {
2082 +
        "node": ">=11.0.0"
2083 +
      }
2084 +
    },
2085 +
    "node_modules/semver": {
2086 +
      "version": "7.8.5",
2087 +
      "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
2088 +
      "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
2089 +
      "dev": true,
2090 +
      "license": "ISC",
2091 +
      "bin": {
2092 +
        "semver": "bin/semver.js"
2093 +
      },
2094 +
      "engines": {
2095 +
        "node": ">=10"
2096 +
      }
2097 +
    },
2098 +
    "node_modules/side-channel": {
2099 +
      "version": "1.1.1",
2100 +
      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
2101 +
      "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
2102 +
      "dev": true,
2103 +
      "license": "MIT",
2104 +
      "dependencies": {
2105 +
        "es-errors": "^1.3.0",
2106 +
        "object-inspect": "^1.13.4",
2107 +
        "side-channel-list": "^1.0.1",
2108 +
        "side-channel-map": "^1.0.1",
2109 +
        "side-channel-weakmap": "^1.0.2"
2110 +
      },
2111 +
      "engines": {
2112 +
        "node": ">= 0.4"
2113 +
      },
2114 +
      "funding": {
2115 +
        "url": "https://github.com/sponsors/ljharb"
2116 +
      }
2117 +
    },
2118 +
    "node_modules/side-channel-list": {
2119 +
      "version": "1.0.1",
2120 +
      "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
2121 +
      "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
2122 +
      "dev": true,
2123 +
      "license": "MIT",
2124 +
      "dependencies": {
2125 +
        "es-errors": "^1.3.0",
2126 +
        "object-inspect": "^1.13.4"
2127 +
      },
2128 +
      "engines": {
2129 +
        "node": ">= 0.4"
2130 +
      },
2131 +
      "funding": {
2132 +
        "url": "https://github.com/sponsors/ljharb"
2133 +
      }
2134 +
    },
2135 +
    "node_modules/side-channel-map": {
2136 +
      "version": "1.0.1",
2137 +
      "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
2138 +
      "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
2139 +
      "dev": true,
2140 +
      "license": "MIT",
2141 +
      "dependencies": {
2142 +
        "call-bound": "^1.0.2",
2143 +
        "es-errors": "^1.3.0",
2144 +
        "get-intrinsic": "^1.2.5",
2145 +
        "object-inspect": "^1.13.3"
2146 +
      },
2147 +
      "engines": {
2148 +
        "node": ">= 0.4"
2149 +
      },
2150 +
      "funding": {
2151 +
        "url": "https://github.com/sponsors/ljharb"
2152 +
      }
2153 +
    },
2154 +
    "node_modules/side-channel-weakmap": {
2155 +
      "version": "1.0.2",
2156 +
      "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
2157 +
      "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
2158 +
      "dev": true,
2159 +
      "license": "MIT",
2160 +
      "dependencies": {
2161 +
        "call-bound": "^1.0.2",
2162 +
        "es-errors": "^1.3.0",
2163 +
        "get-intrinsic": "^1.2.5",
2164 +
        "object-inspect": "^1.13.3",
2165 +
        "side-channel-map": "^1.0.1"
2166 +
      },
2167 +
      "engines": {
2168 +
        "node": ">= 0.4"
2169 +
      },
2170 +
      "funding": {
2171 +
        "url": "https://github.com/sponsors/ljharb"
2172 +
      }
2173 +
    },
2174 +
    "node_modules/signal-exit": {
2175 +
      "version": "3.0.7",
2176 +
      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
2177 +
      "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
2178 +
      "dev": true,
2179 +
      "license": "ISC"
2180 +
    },
2181 +
    "node_modules/structured-source": {
2182 +
      "version": "4.0.0",
2183 +
      "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-4.0.0.tgz",
2184 +
      "integrity": "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==",
2185 +
      "dev": true,
2186 +
      "license": "BSD-2-Clause",
2187 +
      "dependencies": {
2188 +
        "boundary": "^2.0.0"
2189 +
      }
2190 +
    },
2191 +
    "node_modules/textextensions": {
2192 +
      "version": "6.11.0",
2193 +
      "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-6.11.0.tgz",
2194 +
      "integrity": "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==",
2195 +
      "dev": true,
2196 +
      "license": "Artistic-2.0",
2197 +
      "dependencies": {
2198 +
        "editions": "^6.21.0"
2199 +
      },
2200 +
      "engines": {
2201 +
        "node": ">=4"
2202 +
      },
2203 +
      "funding": {
2204 +
        "url": "https://bevry.me/fund"
2205 +
      }
2206 +
    },
2207 +
    "node_modules/tinyglobby": {
2208 +
      "version": "0.2.17",
2209 +
      "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
2210 +
      "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
2211 +
      "dev": true,
2212 +
      "license": "MIT",
2213 +
      "dependencies": {
2214 +
        "fdir": "^6.5.0",
2215 +
        "picomatch": "^4.0.4"
2216 +
      },
2217 +
      "engines": {
2218 +
        "node": ">=12.0.0"
2219 +
      },
2220 +
      "funding": {
2221 +
        "url": "https://github.com/sponsors/SuperchupuDev"
2222 +
      }
2223 +
    },
2224 +
    "node_modules/tslib": {
2225 +
      "version": "2.8.1",
2226 +
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
2227 +
      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
2228 +
      "dev": true,
2229 +
      "license": "0BSD"
2230 +
    },
2231 +
    "node_modules/tunnel": {
2232 +
      "version": "0.0.6",
2233 +
      "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
2234 +
      "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
2235 +
      "dev": true,
2236 +
      "license": "MIT",
2237 +
      "engines": {
2238 +
        "node": ">=0.6.11 <=0.7.0 || >=0.7.3"
2239 +
      }
2240 +
    },
2241 +
    "node_modules/typed-rest-client": {
2242 +
      "version": "1.8.11",
2243 +
      "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz",
2244 +
      "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==",
2245 +
      "dev": true,
2246 +
      "license": "MIT",
2247 +
      "dependencies": {
2248 +
        "qs": "^6.9.1",
2249 +
        "tunnel": "0.0.6",
2250 +
        "underscore": "^1.12.1"
2251 +
      }
2252 +
    },
2253 +
    "node_modules/typescript": {
2254 +
      "version": "5.9.3",
2255 +
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
2256 +
      "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
2257 +
      "dev": true,
2258 +
      "license": "Apache-2.0",
2259 +
      "bin": {
2260 +
        "tsc": "bin/tsc",
2261 +
        "tsserver": "bin/tsserver"
2262 +
      },
2263 +
      "engines": {
2264 +
        "node": ">=14.17"
2265 +
      }
2266 +
    },
2267 +
    "node_modules/underscore": {
2268 +
      "version": "1.13.8",
2269 +
      "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz",
2270 +
      "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==",
2271 +
      "dev": true,
2272 +
      "license": "MIT"
2273 +
    },
2274 +
    "node_modules/undici-types": {
2275 +
      "version": "7.24.6",
2276 +
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
2277 +
      "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
2278 +
      "dev": true,
2279 +
      "license": "MIT"
2280 +
    },
2281 +
    "node_modules/url-join": {
2282 +
      "version": "4.0.1",
2283 +
      "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
2284 +
      "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
2285 +
      "dev": true,
2286 +
      "license": "MIT"
2287 +
    },
2288 +
    "node_modules/version-range": {
2289 +
      "version": "4.15.0",
2290 +
      "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz",
2291 +
      "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==",
2292 +
      "dev": true,
2293 +
      "license": "Artistic-2.0",
2294 +
      "engines": {
2295 +
        "node": ">=4"
2296 +
      },
2297 +
      "funding": {
2298 +
        "url": "https://bevry.me/fund"
2299 +
      }
2300 +
    },
2301 +
    "node_modules/wsl-utils": {
2302 +
      "version": "0.1.0",
2303 +
      "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz",
2304 +
      "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==",
2305 +
      "dev": true,
2306 +
      "license": "MIT",
2307 +
      "dependencies": {
2308 +
        "is-wsl": "^3.1.0"
2309 +
      },
2310 +
      "engines": {
2311 +
        "node": ">=18"
2312 +
      },
2313 +
      "funding": {
2314 +
        "url": "https://github.com/sponsors/sindresorhus"
2315 +
      }
2316 +
    },
2317 +
    "node_modules/xml2js": {
2318 +
      "version": "0.5.0",
2319 +
      "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
2320 +
      "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
2321 +
      "dev": true,
2322 +
      "license": "MIT",
2323 +
      "dependencies": {
2324 +
        "sax": ">=0.6.0",
2325 +
        "xmlbuilder": "~11.0.0"
2326 +
      },
2327 +
      "engines": {
2328 +
        "node": ">=4.0.0"
2329 +
      }
2330 +
    },
2331 +
    "node_modules/xmlbuilder": {
2332 +
      "version": "11.0.1",
2333 +
      "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
2334 +
      "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
2335 +
      "dev": true,
2336 +
      "license": "MIT",
2337 +
      "engines": {
2338 +
        "node": ">=4.0"
2339 +
      }
2340 +
    },
2341 +
    "node_modules/yallist": {
2342 +
      "version": "4.0.0",
2343 +
      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
2344 +
      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
2345 +
      "dev": true,
2346 +
      "license": "ISC"
2347 +
    },
2348 +
    "node_modules/yauzl": {
2349 +
      "version": "3.4.0",
2350 +
      "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.4.0.tgz",
2351 +
      "integrity": "sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==",
2352 +
      "dev": true,
2353 +
      "license": "MIT",
2354 +
      "dependencies": {
2355 +
        "pend": "~1.2.0"
2356 +
      },
2357 +
      "engines": {
2358 +
        "node": ">=12"
2359 +
      }
2360 +
    },
2361 +
    "node_modules/yazl": {
2362 +
      "version": "2.5.1",
2363 +
      "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz",
2364 +
      "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==",
2365 +
      "dev": true,
2366 +
      "license": "MIT",
2367 +
      "dependencies": {
2368 +
        "buffer-crc32": "~0.2.3"
2369 +
      }
2370 +
    }
2371 +
  }
2372 +
}
added package.json +106 −0
1 +
{
2 +
  "name": "oxygenhub-vscode",
3 +
  "displayName": "Oxygenhub",
4 +
  "description": "Proposals, reviews, issues and checks from an Oxygenhub, in the editor. Builds on the Oxygen extension.",
5 +
  "version": "0.1.0",
6 +
  "publisher": "andymccall",
7 +
  "license": "MIT OR Apache-2.0",
8 +
  "icon": "assets/images/oxygenhub.png",
9 +
  "engines": {
10 +
    "vscode": "^1.137.0"
11 +
  },
12 +
  "categories": ["SCM Providers", "Other"],
13 +
  "keywords": ["oxygen", "oxygenhub", "review", "proposals", "issues"],
14 +
  "extensionDependencies": ["andymccall.oxygen-vscode"],
15 +
  "activationEvents": ["workspaceContains:.oxy/format", "onAuthenticationRequest:oxygenhub", "onStartupFinished"],
16 +
  "main": "./dist/extension.js",
17 +
  "contributes": {
18 +
    "authentication": [{ "id": "oxygenhub", "label": "Oxygenhub" }],
19 +
    "configuration": {
20 +
      "title": "Oxygenhub",
21 +
      "properties": {
22 +
        "oxygenhub.hubs": { "type": "array", "items": { "type": "string" }, "default": [], "description": "Base URLs of hubs you use (learnt from sign in too)." },
23 +
        "oxygenhub.proposalBookmarkPrefix": { "type": "string", "default": "proposal/", "description": "Prefix of the local bookmark created when checking out a proposal." },
24 +
        "oxygenhub.autoRefresh": { "type": "number", "default": 60, "description": "Seconds between view refreshes; 0 disables." }
25 +
      }
26 +
    },
27 +
    "viewsContainers": {
28 +
      "activitybar": [{ "id": "oxygenhub", "title": "Oxygenhub", "icon": "assets/images/oxygenhub.svg" }]
29 +
    },
30 +
    "views": {
31 +
      "oxygenhub": [
32 +
        { "id": "oxygenhub.proposals", "name": "Proposals" },
33 +
        { "id": "oxygenhub.issues", "name": "Issues" }
34 +
      ]
35 +
    },
36 +
    "viewsWelcome": [
37 +
      { "view": "oxygenhub.proposals", "contents": "Sign in to an Oxygenhub to see proposals.\n[Sign in](command:oxygenhub.signIn)" }
38 +
    ],
39 +
    "commands": [
40 +
      { "command": "oxygenhub.signIn", "title": "Sign in", "category": "Oxygenhub" },
41 +
      { "command": "oxygenhub.signOut", "title": "Sign out", "category": "Oxygenhub" },
42 +
      { "command": "oxygenhub.refresh", "title": "Refresh", "category": "Oxygenhub", "icon": "$(refresh)" },
43 +
      { "command": "oxygenhub.openProposal", "title": "Open Proposal", "category": "Oxygenhub" },
44 +
      { "command": "oxygenhub.openOnHub", "title": "Open on Hub", "category": "Oxygenhub", "icon": "$(link-external)" },
45 +
      { "command": "oxygenhub.checkoutProposal", "title": "Check Out Proposal", "category": "Oxygenhub", "icon": "$(git-branch)" },
46 +
      { "command": "oxygenhub.review", "title": "Review…", "category": "Oxygenhub", "icon": "$(comment-discussion)" },
47 +
      { "command": "oxygenhub.sealApproval", "title": "Seal My Approval (oxy seal --proposal)", "category": "Oxygenhub" },
48 +
      { "command": "oxygenhub.land", "title": "Land Proposal", "category": "Oxygenhub" },
49 +
      { "command": "oxygenhub.closeProposal", "title": "Close Proposal", "category": "Oxygenhub" },
50 +
      { "command": "oxygenhub.createProposal", "title": "Propose Current Bookmark…", "category": "Oxygenhub" },
51 +
      { "command": "oxygenhub.newIssue", "title": "New Issue…", "category": "Oxygenhub", "icon": "$(add)" },
52 +
      { "command": "oxygenhub.openIssue", "title": "Open Issue", "category": "Oxygenhub" },
53 +
      { "command": "oxygenhub.commentIssue", "title": "Comment on Issue…", "category": "Oxygenhub" },
54 +
      { "command": "oxygenhub.closeIssue", "title": "Close Issue", "category": "Oxygenhub" },
55 +
      { "command": "oxygenhub.reopenIssue", "title": "Reopen Issue", "category": "Oxygenhub" },
56 +
      { "command": "oxygenhub.assignMe", "title": "Assign to Me", "category": "Oxygenhub" },
57 +
      { "command": "oxygenhub.newThread", "title": "Start Review Thread", "category": "Oxygenhub" },
58 +
      { "command": "oxygenhub.reply", "title": "Reply", "category": "Oxygenhub" },
59 +
      { "command": "oxygenhub.resolveThread", "title": "Resolve", "category": "Oxygenhub" }
60 +
    ],
61 +
    "menus": {
62 +
      "view/title": [
63 +
        { "command": "oxygenhub.refresh", "when": "view == oxygenhub.proposals || view == oxygenhub.issues", "group": "navigation" },
64 +
        { "command": "oxygenhub.createProposal", "when": "view == oxygenhub.proposals", "group": "navigation" },
65 +
        { "command": "oxygenhub.newIssue", "when": "view == oxygenhub.issues", "group": "navigation" }
66 +
      ],
67 +
      "view/item/context": [
68 +
        { "command": "oxygenhub.openOnHub", "when": "view == oxygenhub.proposals && viewItem == proposal", "group": "inline" },
69 +
        { "command": "oxygenhub.checkoutProposal", "when": "view == oxygenhub.proposals && viewItem == proposal", "group": "inline" },
70 +
        { "command": "oxygenhub.review", "when": "view == oxygenhub.proposals && viewItem == proposal", "group": "inline" },
71 +
        { "command": "oxygenhub.sealApproval", "when": "view == oxygenhub.proposals && viewItem == proposal", "group": "actions@1" },
72 +
        { "command": "oxygenhub.land", "when": "view == oxygenhub.proposals && viewItem == proposal", "group": "actions@2" },
73 +
        { "command": "oxygenhub.closeProposal", "when": "view == oxygenhub.proposals && viewItem == proposal", "group": "actions@3" },
74 +
        { "command": "oxygenhub.openOnHub", "when": "view == oxygenhub.issues && viewItem == issue", "group": "inline" },
75 +
        { "command": "oxygenhub.commentIssue", "when": "view == oxygenhub.issues && viewItem == issue", "group": "actions@1" },
76 +
        { "command": "oxygenhub.assignMe", "when": "view == oxygenhub.issues && viewItem == issue", "group": "actions@2" },
77 +
        { "command": "oxygenhub.closeIssue", "when": "view == oxygenhub.issues && viewItem == issue", "group": "actions@3" },
78 +
        { "command": "oxygenhub.reopenIssue", "when": "view == oxygenhub.issues && viewItem == issue", "group": "actions@3" }
79 +
      ],
80 +
      "comments/commentThread/context": [
81 +
        { "command": "oxygenhub.reply", "when": "commentController == oxygenhub", "group": "inline" },
82 +
        { "command": "oxygenhub.newThread", "when": "commentController == oxygenhub && commentThreadIsEmpty", "group": "inline" }
83 +
      ],
84 +
      "comments/commentThread/title": [
85 +
        { "command": "oxygenhub.resolveThread", "when": "commentController == oxygenhub", "group": "navigation" }
86 +
      ]
87 +
    }
88 +
  },
89 +
  "scripts": {
90 +
    "types": "cp ../oxygenhub/crates/hub-api/ts/index.d.ts src/hubapi.d.ts",
91 +
    "build": "esbuild src/extension.ts --bundle --platform=node --format=cjs --external:vscode --outfile=dist/extension.js --sourcemap",
92 +
    "watch": "npm run build -- --watch",
93 +
    "typecheck": "tsc --noEmit -p .",
94 +
    "pretest": "esbuild src/hub.ts --bundle --platform=node --format=esm --outfile=out/hub.mjs && esbuild src/provider.ts --bundle --platform=node --format=esm --outfile=out/provider.mjs",
95 +
    "test": "node --test \"test/**/*.test.mjs\"",
96 +
    "check": "npm run typecheck && npm run build && npm test",
97 +
    "package": "vsce package --no-dependencies"
98 +
  },
99 +
  "devDependencies": {
100 +
    "@types/node": "^24.0.0",
101 +
    "@types/vscode": "^1.137.0",
102 +
    "@vscode/vsce": "^4.0.0",
103 +
    "esbuild": "^0.28.0",
104 +
    "typescript": "^5.9.0"
105 +
  }
106 +
}
added src/auth.ts +105 −0
1 +
// The `oxygenhub` authentication provider (spec §4): a session is a hub
2 +
// base URL and a personal access token, kept in SecretStorage.
3 +
4 +
import * as vscode from "vscode";
5 +
import { HubClient, Session } from "./hub";
6 +
7 +
const KEY = "oxygenhub.sessions";
8 +
9 +
export class HubAuth implements vscode.AuthenticationProvider, vscode.Disposable {
10 +
  private readonly emitter = new vscode.EventEmitter<vscode.AuthenticationProviderAuthenticationSessionsChangeEvent>();
11 +
  readonly onDidChangeSessions = this.emitter.event;
12 +
  private readonly onDidChangeEmitter = new vscode.EventEmitter<void>();
13 +
  /** Fires when the set of hubs changes. */
14 +
  readonly onDidChange = this.onDidChangeEmitter.event;
15 +
  private sessions: Session[] = [];
16 +
  private readonly disposables: vscode.Disposable[] = [];
17 +
18 +
  constructor(private readonly secrets: vscode.SecretStorage) {
19 +
    this.disposables.push(vscode.authentication.registerAuthenticationProvider("oxygenhub", "Oxygenhub", this, { supportsMultipleAccounts: true }));
20 +
  }
21 +
22 +
  async load(): Promise<void> {
23 +
    const raw = await this.secrets.get(KEY);
24 +
    this.sessions = raw ? (JSON.parse(raw) as Session[]) : [];
25 +
  }
26 +
27 +
  private async save(): Promise<void> {
28 +
    await this.secrets.store(KEY, JSON.stringify(this.sessions));
29 +
    this.onDidChangeEmitter.fire();
30 +
  }
31 +
32 +
  all(): Session[] {
33 +
    return [...this.sessions];
34 +
  }
35 +
36 +
  bases(): string[] {
37 +
    const configured = vscode.workspace.getConfiguration("oxygenhub").get<string[]>("hubs") ?? [];
38 +
    return [...new Set([...this.sessions.map((s) => s.base), ...configured.map((b) => b.replace(/\/$/, ""))])];
39 +
  }
40 +
41 +
  /** The session for a hub base URL, if signed in. */
42 +
  sessionFor(base: string): Session | undefined {
43 +
    return this.sessions.find((s) => s.base === base.replace(/\/$/, ""));
44 +
  }
45 +
46 +
  clientFor(base: string): HubClient {
47 +
    return new HubClient(base.replace(/\/$/, ""), this.sessionFor(base)?.token);
48 +
  }
49 +
50 +
  private toVs(s: Session): vscode.AuthenticationSession {
51 +
    return { id: s.base, accessToken: s.token, account: { id: `${s.username ?? "?"}@${s.base}`, label: `${s.username ?? "?"} on ${s.base}` }, scopes: [] };
52 +
  }
53 +
54 +
  async getSessions(): Promise<vscode.AuthenticationSession[]> {
55 +
    return this.sessions.map((s) => this.toVs(s));
56 +
  }
57 +
58 +
  async createSession(): Promise<vscode.AuthenticationSession> {
59 +
    const s = await this.signIn();
60 +
    if (!s) {
61 +
      throw new Error("Sign in cancelled");
62 +
    }
63 +
    return this.toVs(s);
64 +
  }
65 +
66 +
  async removeSession(id: string): Promise<void> {
67 +
    const before = this.sessions.find((s) => s.base === id);
68 +
    this.sessions = this.sessions.filter((s) => s.base !== id);
69 +
    await this.save();
70 +
    if (before) {
71 +
      this.emitter.fire({ added: [], removed: [this.toVs(before)], changed: [] });
72 +
    }
73 +
  }
74 +
75 +
  /** Ask for a hub and a token, verify them, and keep the session. */
76 +
  async signIn(defaultBase?: string): Promise<Session | undefined> {
77 +
    const base = await vscode.window.showInputBox({ prompt: "Hub URL", value: defaultBase ?? this.bases()[0] ?? "https://", validateInput: (v) => (/^https?:\/\/[^/]+/.test(v) ? undefined : "A URL such as https://hub.example:7343") });
78 +
    if (!base) {
79 +
      return undefined;
80 +
    }
81 +
    const token = await vscode.window.showInputBox({ prompt: `Personal access token for ${base} (Settings › Tokens on the hub; scopes repo:read repo:write proposals:write issues:write)`, password: true });
82 +
    if (!token) {
83 +
      return undefined;
84 +
    }
85 +
    const client = new HubClient(base.replace(/\/$/, ""), token);
86 +
    let username: string | undefined;
87 +
    try {
88 +
      username = (await client.me()).username;
89 +
    } catch (e) {
90 +
      const pick = await vscode.window.showWarningMessage(`Oxygenhub: could not read your account (${(e as Error).message}); the token may lack user:read. Keep it anyway?`, "Keep", "Cancel");
91 +
      if (pick !== "Keep") {
92 +
        return undefined;
93 +
      }
94 +
    }
95 +
    const session: Session = { base: base.replace(/\/$/, ""), token, username };
96 +
    this.sessions = [...this.sessions.filter((s) => s.base !== session.base), session];
97 +
    await this.save();
98 +
    this.emitter.fire({ added: [this.toVs(session)], removed: [], changed: [] });
99 +
    return session;
100 +
  }
101 +
102 +
  dispose(): void {
103 +
    this.disposables.forEach((d) => d.dispose());
104 +
  }
105 +
}
added src/checks.ts +66 −0
1 +
// The status bar (spec §9): the proposal whose source is the current
2 +
// bookmark, with its checks, refreshed on the hub's event stream.
3 +
4 +
import * as vscode from "vscode";
5 +
import { HubAuth } from "./auth";
6 +
import type { HubRepo } from "./proposals";
7 +
8 +
export class ChecksBar implements vscode.Disposable {
9 +
  private readonly item = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 40);
10 +
  private timer: NodeJS.Timeout | undefined;
11 +
12 +
  constructor(
13 +
    private readonly auth: HubAuth,
14 +
    private readonly repos: () => Promise<HubRepo[]>,
15 +
  ) {
16 +
    this.item.command = "oxygenhub.openProposal";
17 +
  }
18 +
19 +
  async refresh(): Promise<void> {
20 +
    const repos = await this.repos();
21 +
    for (const repo of repos) {
22 +
      const bookmark = repo.handle.currentBookmark;
23 +
      if (!bookmark) {
24 +
        continue;
25 +
      }
26 +
      try {
27 +
        const list = await this.auth.clientFor(repo.base).proposals(repo.owner, repo.name);
28 +
        const p = list.find((x) => x.source.kind === "bookmark" && x.source.bookmark === bookmark && x.state === "open");
29 +
        if (p) {
30 +
          const checks = await this.auth.clientFor(repo.base).proposalChecks(repo.owner, repo.name, p.number);
31 +
          const passed = checks.filter((c) => c.status === "passed").length;
32 +
          const failed = checks.filter((c) => c.status === "failed").length;
33 +
          const running = checks.filter((c) => c.status === "running" || c.status === "queued").length;
34 +
          const glyph = failed ? "$(error)" : running ? "$(sync~spin)" : checks.length ? "$(pass)" : "$(git-pull-request)";
35 +
          this.item.text = `${glyph} #${p.number}${checks.length ? ` ${passed}/${checks.length}` : ""}`;
36 +
          this.item.tooltip = `${p.title}\n${p.landable ? "ready to land" : p.blockers.join("\n")}`;
37 +
          this.item.command = { title: "Open", command: "oxygenhub.openProposal", arguments: [{ kind: "proposal", repo, p }] };
38 +
          this.item.show();
39 +
          return;
40 +
        }
41 +
      } catch {
42 +
        // Not signed in or unreachable: nothing to show.
43 +
      }
44 +
    }
45 +
    this.item.hide();
46 +
  }
47 +
48 +
  /** Poll on the configured interval; the hub's SSE stream needs a browser-grade EventSource, so the bar polls instead. */
49 +
  start(): void {
50 +
    const seconds = vscode.workspace.getConfiguration("oxygenhub").get<number>("autoRefresh") ?? 60;
51 +
    if (this.timer) {
52 +
      clearInterval(this.timer);
53 +
    }
54 +
    if (seconds > 0) {
55 +
      this.timer = setInterval(() => void this.refresh(), seconds * 1000);
56 +
    }
57 +
    void this.refresh();
58 +
  }
59 +
60 +
  dispose(): void {
61 +
    if (this.timer) {
62 +
      clearInterval(this.timer);
63 +
    }
64 +
    this.item.dispose();
65 +
  }
66 +
}
added src/extension.ts +278 −0
1 +
// Activation: sign in, find the Oxygen extension's API, register the
2 +
// remote provider, wire the views, the review controller and the commands.
3 +
4 +
import * as vscode from "vscode";
5 +
import { HubAuth } from "./auth";
6 +
import { ChecksBar } from "./checks";
7 +
import { hubUrl } from "./hub";
8 +
import { IssueNode, IssuesProvider, showIssue } from "./issues";
9 +
import type { OxygenAPI } from "./oxygenapi";
10 +
import { HubRepo, ProposalNode, ProposalsProvider, showDetail } from "./proposals";
11 +
import { makeProvider } from "./provider";
12 +
import { ReviewController } from "./review";
13 +
14 +
let auth: HubAuth;
15 +
let oxygen: OxygenAPI | undefined;
16 +
let proposals: ProposalsProvider;
17 +
let issues: IssuesProvider;
18 +
let review: ReviewController;
19 +
let bar: ChecksBar;
20 +
21 +
export async function activate(context: vscode.ExtensionContext): Promise<void> {
22 +
  auth = new HubAuth(context.secrets);
23 +
  await auth.load();
24 +
  context.subscriptions.push(auth);
25 +
26 +
  const ext = vscode.extensions.getExtension<OxygenAPI>("andymccall.oxygen-vscode");
27 +
  oxygen = ext ? ((await ext.activate()) as OxygenAPI | undefined) : undefined;
28 +
  if (!oxygen) {
29 +
    void vscode.window.showWarningMessage("Oxygenhub: the Oxygen extension is not available; install andymccall.oxygen-vscode 0.7.0 or later.");
30 +
  } else {
31 +
    context.subscriptions.push(oxygen.registerRemoteProvider(makeProvider({ bases: () => auth.bases() })));
32 +
    context.subscriptions.push(oxygen.onDidChangeRepositories(() => refreshAll()));
33 +
  }
34 +
35 +
  proposals = new ProposalsProvider(() => oxygen, auth);
36 +
  issues = new IssuesProvider(() => proposals.repos(), auth);
37 +
  review = new ReviewController(auth);
38 +
  bar = new ChecksBar(auth, () => proposals.repos());
39 +
  context.subscriptions.push(vscode.window.registerTreeDataProvider("oxygenhub.proposals", proposals), vscode.window.registerTreeDataProvider("oxygenhub.issues", issues), review, bar);
40 +
  context.subscriptions.push(auth.onDidChange(() => refreshAll()));
41 +
42 +
  context.subscriptions.push(
43 +
    vscode.commands.registerCommand("oxygenhub.signIn", async () => {
44 +
      const repos = oxygen ? await guessHubs() : [];
45 +
      await auth.signIn(repos[0]);
46 +
      refreshAll();
47 +
    }),
48 +
    vscode.commands.registerCommand("oxygenhub.signOut", async () => {
49 +
      const pick = await vscode.window.showQuickPick(auth.all().map((s) => s.base), { placeHolder: "Sign out of" });
50 +
      if (pick) {
51 +
        await auth.removeSession(pick);
52 +
        refreshAll();
53 +
      }
54 +
    }),
55 +
    vscode.commands.registerCommand("oxygenhub.refresh", () => refreshAll()),
56 +
    vscode.commands.registerCommand("oxygenhub.openProposal", async (node?: ProposalNode) => {
57 +
      const n = node ?? (await pickProposal());
58 +
      if (!n) {
59 +
        return;
60 +
      }
61 +
      const checks = await auth.clientFor(n.repo.base).proposalChecks(n.repo.owner, n.repo.name, n.p.number).catch(() => []);
62 +
      showDetail(n.repo, n.p, checks);
63 +
    }),
64 +
    vscode.commands.registerCommand("oxygenhub.openOnHub", (node?: ProposalNode | IssueNode) => {
65 +
      if (!node) {
66 +
        return;
67 +
      }
68 +
      const target = node.kind === "proposal" ? { kind: "proposal", number: node.p.number } : { kind: "issue", number: node.i.number };
69 +
      void vscode.env.openExternal(vscode.Uri.parse(hubUrl(node.repo.base, node.repo.owner, node.repo.name, target)));
70 +
    }),
71 +
    vscode.commands.registerCommand("oxygenhub.checkoutProposal", (...args: unknown[]) => withProposal(args, checkout)),
72 +
    vscode.commands.registerCommand("oxygenhub.review", (...args: unknown[]) => withProposal(args, reviewProposal)),
73 +
    vscode.commands.registerCommand("oxygenhub.sealApproval", (...args: unknown[]) => withProposal(args, (repo, number) => runInTerminal(repo, `oxy seal --proposal ${hubUrl(repo.base, repo.owner, repo.name, { kind: "proposal", number })}`))),
74 +
    vscode.commands.registerCommand("oxygenhub.land", (...args: unknown[]) => withProposal(args, (repo, number) => runInTerminal(repo, `oxy proposal land ${hubUrl(repo.base, repo.owner, repo.name, { kind: "proposal", number })}`))),
75 +
    vscode.commands.registerCommand("oxygenhub.closeProposal", (...args: unknown[]) =>
76 +
      withProposal(args, async (repo, number) => {
77 +
        void vscode.env.openExternal(vscode.Uri.parse(hubUrl(repo.base, repo.owner, repo.name, { kind: "proposal", number })));
78 +
      }),
79 +
    ),
80 +
    vscode.commands.registerCommand("oxygenhub.createProposal", () => createProposal()),
81 +
    vscode.commands.registerCommand("oxygenhub.newIssue", () => newIssue()),
82 +
    vscode.commands.registerCommand("oxygenhub.openIssue", async (node?: IssueNode) => node && showIssue(auth, node.repo, node.i)),
83 +
    vscode.commands.registerCommand("oxygenhub.commentIssue", (...args: unknown[]) =>
84 +
      withIssue(args, async (repo, number) => {
85 +
        const body = await vscode.window.showInputBox({ prompt: `Comment on #${number}` });
86 +
        if (body) {
87 +
          await auth.clientFor(repo.base).commentIssue(repo.owner, repo.name, number, body);
88 +
          issues.refresh();
89 +
        }
90 +
      }),
91 +
    ),
92 +
    vscode.commands.registerCommand("oxygenhub.closeIssue", (...args: unknown[]) => withIssue(args, async (repo, number) => (await auth.clientFor(repo.base).setIssueState(repo.owner, repo.name, number, "closed"), issues.refresh()))),
93 +
    vscode.commands.registerCommand("oxygenhub.reopenIssue", (...args: unknown[]) => withIssue(args, async (repo, number) => (await auth.clientFor(repo.base).setIssueState(repo.owner, repo.name, number, "open"), issues.refresh()))),
94 +
    vscode.commands.registerCommand("oxygenhub.assignMe", (...args: unknown[]) =>
95 +
      withIssue(args, async (repo, number) => {
96 +
        void vscode.window.showInformationMessage(`Oxygenhub: assigning is on the hub for now: ${hubUrl(repo.base, repo.owner, repo.name, { kind: "issue", number })}`);
97 +
      }),
98 +
    ),
99 +
    vscode.commands.registerCommand("oxygenhub.reply", (reply: vscode.CommentReply) => review.reply(reply)),
100 +
    vscode.commands.registerCommand("oxygenhub.newThread", (reply: vscode.CommentReply) => review.reply(reply)),
101 +
    vscode.commands.registerCommand("oxygenhub.resolveThread", (thread: vscode.CommentThread) => {
102 +
      thread.state = thread.state === vscode.CommentThreadState.Resolved ? vscode.CommentThreadState.Unresolved : vscode.CommentThreadState.Resolved;
103 +
    }),
104 +
  );
105 +
  bar.start();
106 +
}
107 +
108 +
function refreshAll(): void {
109 +
  proposals.refresh();
110 +
  issues.refresh();
111 +
  void bar.refresh();
112 +
}
113 +
114 +
async function guessHubs(): Promise<string[]> {
115 +
  const out: string[] = [];
116 +
  for (const r of oxygen?.repositories ?? []) {
117 +
    for (const remote of await r.remotes().catch(() => [])) {
118 +
      const m = /^(https?:\/\/[^/]+)\/[^/]+\/[^/]+\/?$/.exec(remote.url);
119 +
      if (m && !out.includes(m[1])) {
120 +
        out.push(m[1]);
121 +
      }
122 +
    }
123 +
  }
124 +
  return out;
125 +
}
126 +
127 +
async function pickProposal(): Promise<ProposalNode | undefined> {
128 +
  const items: { label: string; description: string; node: ProposalNode }[] = [];
129 +
  for (const repo of await proposals.repos()) {
130 +
    for (const p of await proposals.proposalsOf(repo).catch(() => [])) {
131 +
      items.push({ label: `#${p.number} ${p.title}`, description: `${repo.owner}/${repo.name} · ${p.author} → ${p.target}`, node: { kind: "proposal", repo, p } });
132 +
    }
133 +
  }
134 +
  return (await vscode.window.showQuickPick(items, { placeHolder: "Proposal" }))?.node;
135 +
}
136 +
137 +
/** Commands take a tree node, or the webview's `[base, owner, name, number]`. */
138 +
async function withProposal(args: unknown[], f: (repo: HubRepo, number: number) => Promise<void>): Promise<void> {
139 +
  const a = args[0];
140 +
  if (a && typeof a === "object" && (a as ProposalNode).kind === "proposal") {
141 +
    const n = a as ProposalNode;
142 +
    return f(n.repo, n.p.number);
143 +
  }
144 +
  if (Array.isArray(a) && a.length === 4) {
145 +
    const [base, owner, name, number] = a as [string, string, string, number];
146 +
    const repo = (await proposals.repos()).find((r) => r.base === base && r.owner === owner && r.name === name);
147 +
    if (repo) {
148 +
      return f(repo, number);
149 +
    }
150 +
  }
151 +
  const n = await pickProposal();
152 +
  if (n) {
153 +
    return f(n.repo, n.p.number);
154 +
  }
155 +
}
156 +
157 +
async function withIssue(args: unknown[], f: (repo: HubRepo, number: number) => Promise<void>): Promise<void> {
158 +
  const a = args[0];
159 +
  if (a && typeof a === "object" && (a as IssueNode).kind === "issue") {
160 +
    const n = a as IssueNode;
161 +
    return f(n.repo, n.i.number);
162 +
  }
163 +
  if (Array.isArray(a) && a.length === 4) {
164 +
    const [base, owner, name, number] = a as [string, string, string, number];
165 +
    const repo = (await proposals.repos()).find((r) => r.base === base && r.owner === owner && r.name === name);
166 +
    if (repo) {
167 +
      return f(repo, number);
168 +
    }
169 +
  }
170 +
}
171 +
172 +
function runInTerminal(repo: HubRepo, command: string): Promise<void> {
173 +
  const t = vscode.window.createTerminal({ name: "Oxygenhub", cwd: repo.handle.root });
174 +
  t.show();
175 +
  t.sendText(command);
176 +
  return Promise.resolve();
177 +
}
178 +
179 +
/** Fetch the proposal's changes and switch to a local bookmark at its projected heads (spec §5). */
180 +
async function checkout(repo: HubRepo, number: number): Promise<void> {
181 +
  const p = await auth.clientFor(repo.base).proposal(repo.owner, repo.name, number);
182 +
  const prefix = vscode.workspace.getConfiguration("oxygenhub").get<string>("proposalBookmarkPrefix") ?? "proposal/";
183 +
  const bookmark = `${prefix}${number}`;
184 +
  try {
185 +
    await repo.handle.json(["fetch", repo.remote, "--json"]);
186 +
    const existing = repo.handle.bookmarks.some((b) => b.name === bookmark);
187 +
    if (!existing) {
188 +
      await repo.handle.json(["bookmark", "create", bookmark, ...p.projected_heads, "--json"]).catch(() => repo.handle.json(["bookmark", "create", bookmark, "--json"]));
189 +
    }
190 +
    await repo.handle.json(["bookmark", "set", bookmark, ...p.projected_heads, "--json"]).catch(() => undefined);
191 +
    await repo.handle.json(["bookmark", "switch", bookmark, "--json"]);
192 +
    await repo.handle.refresh();
193 +
    await review.load(repo, number);
194 +
    void vscode.window.showInformationMessage(`Oxygenhub: checked out #${number} as ${bookmark}; review threads are in place.`);
195 +
  } catch (e) {
196 +
    void vscode.window.showErrorMessage(`Oxygenhub: could not check out #${number}: ${(e as Error).message}`);
197 +
  }
198 +
}
199 +
200 +
async function reviewProposal(repo: HubRepo, number: number): Promise<void> {
201 +
  const verdict = await vscode.window.showQuickPick(
202 +
    [
203 +
      { label: "Comment", value: "comment" as const },
204 +
      { label: "Approve", value: "approve" as const, description: "then seal it with oxy so it counts" },
205 +
      { label: "Request changes", value: "changes" as const },
206 +
    ],
207 +
    { placeHolder: `Review #${number}` },
208 +
  );
209 +
  if (!verdict) {
210 +
    return;
211 +
  }
212 +
  const body = (await vscode.window.showInputBox({ prompt: "Summary (optional)" })) ?? "";
213 +
  try {
214 +
    await auth.clientFor(repo.base).review(repo.owner, repo.name, number, verdict.value, body);
215 +
  } catch (e) {
216 +
    void vscode.window.showErrorMessage(`Oxygenhub: ${(e as Error).message}`);
217 +
    return;
218 +
  }
219 +
  proposals.refresh();
220 +
  if (verdict.value === "approve") {
221 +
    const pick = await vscode.window.showInformationMessage("Approval recorded. Seal it with your key so it counts?", "Seal now");
222 +
    if (pick === "Seal now") {
223 +
      await runInTerminal(repo, `oxy seal --proposal ${hubUrl(repo.base, repo.owner, repo.name, { kind: "proposal", number })}`);
224 +
    }
225 +
  }
226 +
}
227 +
228 +
async function createProposal(): Promise<void> {
229 +
  const repos = await proposals.repos();
230 +
  const repo = repos.length === 1 ? repos[0] : (await vscode.window.showQuickPick(repos.map((r) => ({ label: `${r.owner}/${r.name}`, r })), { placeHolder: "Repository" }))?.r;
231 +
  if (!repo) {
232 +
    return;
233 +
  }
234 +
  const source = repo.handle.currentBookmark;
235 +
  if (!source) {
236 +
    void vscode.window.showWarningMessage("Oxygenhub: no current bookmark.");
237 +
    return;
238 +
  }
239 +
  const remoteBookmarks = await auth.clientFor(repo.base).bookmarks(repo.owner, repo.name).catch(() => []);
240 +
  const target = await vscode.window.showQuickPick(remoteBookmarks.filter((b) => b.name !== source).map((b) => b.name), { placeHolder: `Propose ${source} into…` });
241 +
  if (!target) {
242 +
    return;
243 +
  }
244 +
  const title = await vscode.window.showInputBox({ prompt: "Title", value: source });
245 +
  if (!title) {
246 +
    return;
247 +
  }
248 +
  try {
249 +
    await repo.handle.json(["push", repo.remote, "--bookmark", source, "--json"]).catch(() => undefined);
250 +
    const p = await auth.clientFor(repo.base).createProposal(repo.owner, repo.name, { title, body: "", target, source, draft: false });
251 +
    proposals.refresh();
252 +
    void vscode.env.openExternal(vscode.Uri.parse(p.url));
253 +
  } catch (e) {
254 +
    void vscode.window.showErrorMessage(`Oxygenhub: ${(e as Error).message}`);
255 +
  }
256 +
}
257 +
258 +
async function newIssue(): Promise<void> {
259 +
  const repos = await proposals.repos();
260 +
  const repo = repos.length === 1 ? repos[0] : (await vscode.window.showQuickPick(repos.map((r) => ({ label: `${r.owner}/${r.name}`, r })), { placeHolder: "Repository" }))?.r;
261 +
  if (!repo) {
262 +
    return;
263 +
  }
264 +
  const title = await vscode.window.showInputBox({ prompt: "Issue title" });
265 +
  if (!title) {
266 +
    return;
267 +
  }
268 +
  const body = (await vscode.window.showInputBox({ prompt: "Description (optional)" })) ?? "";
269 +
  try {
270 +
    const i = await auth.clientFor(repo.base).createIssue(repo.owner, repo.name, title, body);
271 +
    issues.refresh();
272 +
    void vscode.window.showInformationMessage(`Oxygenhub: opened #${i.number}.`);
273 +
  } catch (e) {
274 +
    void vscode.window.showErrorMessage(`Oxygenhub: ${(e as Error).message}`);
275 +
  }
276 +
}
277 +
278 +
export function deactivate(): void {}
added src/hub.ts +184 −0
1 +
// The hub client: typed fetch over /api/v1 (hub spec §12.1) using the
2 +
// generated types in hubapi.d.ts, plus URL helpers. No vscode imports,
3 +
// so it is tested with plain Node against a real hub.
4 +
5 +
import type * as api from "./hubapi";
6 +
7 +
export class HubError extends Error {
8 +
  constructor(
9 +
    readonly status: number,
10 +
    readonly title: string,
11 +
    readonly detail: string,
12 +
  ) {
13 +
    super(`${status} ${title}: ${detail}`);
14 +
  }
15 +
}
16 +
17 +
/** `https://hub/owner/name` → { base, owner, name }; undefined for anything else. */
18 +
export function parseRepoUrl(url: string): { base: string; owner: string; name: string } | undefined {
19 +
  const m = /^(https?:\/\/[^/]+)\/([A-Za-z0-9][A-Za-z0-9._-]*)\/([A-Za-z0-9][A-Za-z0-9._-]*)\/?$/.exec(url.trim());
20 +
  if (!m || m[2] === "api") {
21 +
    return undefined;
22 +
  }
23 +
  return { base: m[1], owner: m[2], name: m[3] };
24 +
}
25 +
26 +
export interface Session {
27 +
  base: string;
28 +
  token: string;
29 +
  username?: string;
30 +
}
31 +
32 +
export class HubClient {
33 +
  constructor(
34 +
    readonly base: string,
35 +
    private readonly token: string | undefined,
36 +
    private readonly fetchImpl: typeof fetch = fetch,
37 +
  ) {}
38 +
39 +
  private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
40 +
    const headers: Record<string, string> = { accept: "application/json" };
41 +
    if (this.token) {
42 +
      headers.authorization = `Bearer ${this.token}`;
43 +
    }
44 +
    if (body !== undefined) {
45 +
      headers["content-type"] = "application/json";
46 +
    }
47 +
    const res = await this.fetchImpl(`${this.base}/api/v1${path}`, { method, headers, body: body === undefined ? undefined : JSON.stringify(body) });
48 +
    if (res.status === 204) {
49 +
      return undefined as T;
50 +
    }
51 +
    const text = await res.text();
52 +
    if (!res.ok) {
53 +
      try {
54 +
        const p = JSON.parse(text) as api.Problem;
55 +
        throw new HubError(p.status, p.title, p.detail);
56 +
      } catch (e) {
57 +
        if (e instanceof HubError) {
58 +
          throw e;
59 +
        }
60 +
        throw new HubError(res.status, res.statusText, text);
61 +
      }
62 +
    }
63 +
    return (text ? JSON.parse(text) : undefined) as T;
64 +
  }
65 +
66 +
  me(): Promise<api.User> {
67 +
    return this.request("GET", "/user");
68 +
  }
69 +
70 +
  repositories(): Promise<api.Repository[]> {
71 +
    return this.request("GET", "/repos");
72 +
  }
73 +
74 +
  repository(owner: string, name: string): Promise<api.Repository> {
75 +
    return this.request("GET", `/repos/${owner}/${name}`);
76 +
  }
77 +
78 +
  proposals(owner: string, name: string): Promise<api.Proposal[]> {
79 +
    return this.request("GET", `/repos/${owner}/${name}/proposals`);
80 +
  }
81 +
82 +
  proposal(owner: string, name: string, number: number): Promise<api.Proposal> {
83 +
    return this.request("GET", `/repos/${owner}/${name}/proposals/${number}`);
84 +
  }
85 +
86 +
  createProposal(owner: string, name: string, p: api.NewProposal): Promise<api.Proposal> {
87 +
    return this.request("POST", `/repos/${owner}/${name}/proposals`, p);
88 +
  }
89 +
90 +
  proposalComments(owner: string, name: string, number: number): Promise<api.Comment[]> {
91 +
    return this.request("GET", `/repos/${owner}/${name}/proposals/${number}/comments`);
92 +
  }
93 +
94 +
  commentProposal(owner: string, name: string, number: number, body: string): Promise<api.Comment> {
95 +
    return this.request("POST", `/repos/${owner}/${name}/proposals/${number}/comments`, { body });
96 +
  }
97 +
98 +
  reviews(owner: string, name: string, number: number): Promise<api.Review[]> {
99 +
    return this.request("GET", `/repos/${owner}/${name}/proposals/${number}/reviews`);
100 +
  }
101 +
102 +
  review(owner: string, name: string, number: number, verdict: "comment" | "changes" | "approve", body = ""): Promise<api.Review> {
103 +
    return this.request("POST", `/repos/${owner}/${name}/proposals/${number}/reviews`, { verdict, body });
104 +
  }
105 +
106 +
  proposalChecks(owner: string, name: string, number: number): Promise<api.Check[]> {
107 +
    return this.request("GET", `/repos/${owner}/${name}/proposals/${number}/checks`);
108 +
  }
109 +
110 +
  issues(owner: string, name: string, state: "open" | "closed" | "all" = "open"): Promise<api.Issue[]> {
111 +
    return this.request("GET", `/repos/${owner}/${name}/issues?state=${state}`);
112 +
  }
113 +
114 +
  issue(owner: string, name: string, number: number): Promise<api.Issue> {
115 +
    return this.request("GET", `/repos/${owner}/${name}/issues/${number}`);
116 +
  }
117 +
118 +
  createIssue(owner: string, name: string, title: string, body = ""): Promise<api.Issue> {
119 +
    return this.request("POST", `/repos/${owner}/${name}/issues`, { title, body });
120 +
  }
121 +
122 +
  issueComments(owner: string, name: string, number: number): Promise<api.Comment[]> {
123 +
    return this.request("GET", `/repos/${owner}/${name}/issues/${number}/comments`);
124 +
  }
125 +
126 +
  commentIssue(owner: string, name: string, number: number, body: string): Promise<api.Comment> {
127 +
    return this.request("POST", `/repos/${owner}/${name}/issues/${number}/comments`, { body });
128 +
  }
129 +
130 +
  setIssueState(owner: string, name: string, number: number, state: "open" | "closed", reason = ""): Promise<api.Issue> {
131 +
    return this.request("POST", `/repos/${owner}/${name}/issues/${number}`, { state, reason });
132 +
  }
133 +
134 +
  history(owner: string, name: string, reference: string, limit = 50): Promise<api.History> {
135 +
    return this.request("GET", `/repos/${owner}/${name}/history/${encodeURIComponent(reference)}?limit=${limit}`);
136 +
  }
137 +
138 +
  bookmarks(owner: string, name: string): Promise<api.Bookmark[]> {
139 +
    return this.request("GET", `/repos/${owner}/${name}/bookmarks`);
140 +
  }
141 +
142 +
  search(q: string): Promise<{ kind: string; repository: string; number: number; title: string }[]> {
143 +
    return this.request("GET", `/search?q=${encodeURIComponent(q)}`);
144 +
  }
145 +
}
146 +
147 +
/** Web URLs on the hub for things the remote provider is asked about. */
148 +
export function hubUrl(base: string, owner: string, name: string, target: { kind: string; [k: string]: unknown }): string {
149 +
  const repo = `${base}/${owner}/${name}`;
150 +
  switch (target.kind) {
151 +
    case "repository":
152 +
      return repo;
153 +
    case "bookmark":
154 +
      return `${repo}/tree/${encodeURIComponent(String(target.name))}`;
155 +
    case "change":
156 +
      return `${repo}/change/${target.hash}`;
157 +
    case "file": {
158 +
      const line = typeof target.line === "number" ? `#L${target.line}` : "";
159 +
      return `${repo}/file/${encodeURIComponent(String(target.reference))}/${String(target.path)}${line}`;
160 +
    }
161 +
    case "proposals":
162 +
      return `${repo}/proposals`;
163 +
    case "issues":
164 +
      return `${repo}/issues`;
165 +
    case "proposal":
166 +
      return `${repo}/proposal/${target.number}`;
167 +
    case "issue":
168 +
      return `${repo}/issue/${target.number}`;
169 +
    default:
170 +
      return repo;
171 +
  }
172 +
}
173 +
174 +
/** Which of the proposals need the signed-in user: not theirs, and not yet sealed by them. */
175 +
export function needsMe(p: import("./hubapi").Proposal, username: string | undefined): boolean {
176 +
  if (!username || p.author === username) {
177 +
    return false;
178 +
  }
179 +
  if (p.state !== "open") {
180 +
    return false;
181 +
  }
182 +
  const mine = p.approvals.find((a) => a.account === username);
183 +
  return !mine || !mine.sealed;
184 +
}
added src/hubapi.d.ts +354 −0
1 +
// Generated by `cargo run -p hub-api --bin hub-api-typescript`; do not edit.
2 +
// Types of the Oxygenhub JSON API (hub spec §12.1).
3 +
4 +
export type Visibility = "public" | "internal" | "private";
5 +
6 +
export type Repository = {
7 +
  clone_url: string;
8 +
  created_ms: number;
9 +
  default_bookmark: string;
10 +
  description: string;
11 +
  name: string;
12 +
  owner: string;
13 +
  updated_ms: number;
14 +
  visibility: Visibility;
15 +
};
16 +
17 +
export type Bookmark = {
18 +
  changes: number;
19 +
  default: boolean;
20 +
  heads: string[];
21 +
  name: string;
22 +
};
23 +
24 +
export type Tag = {
25 +
  heads: string[];
26 +
  message: string;
27 +
  name: string;
28 +
  signer?: string | null;
29 +
};
30 +
31 +
export type Author = {
32 +
  account?: string | null;
33 +
  email: string;
34 +
  key: string;
35 +
  name: string;
36 +
};
37 +
38 +
export type Signature = {
39 +
  status: "unsigned";
40 +
} | {
41 +
  key: string;
42 +
  revoked: boolean;
43 +
  sealed: boolean;
44 +
  status: "valid";
45 +
} | {
46 +
  reason: string;
47 +
  status: "invalid";
48 +
};
49 +
50 +
export type ChangeSummary = {
51 +
  author: Author;
52 +
  deps: string[];
53 +
  hash: string;
54 +
  head: boolean;
55 +
  message: string;
56 +
  signature: Signature;
57 +
  when_ms: number;
58 +
};
59 +
60 +
export type Problem = {
61 +
  detail: string;
62 +
  status: number;
63 +
  title: string;
64 +
};
65 +
66 +
export type Approval = {
67 +
  account: string;
68 +
  key?: string | null;
69 +
  sealed: boolean;
70 +
};
71 +
72 +
export type ProposalSource = {
73 +
  bookmark: string;
74 +
  kind: "bookmark";
75 +
} | {
76 +
  bookmark: string;
77 +
  kind: "fork";
78 +
  name: string;
79 +
  owner: string;
80 +
} | {
81 +
  hashes: string[];
82 +
  kind: "changes";
83 +
};
84 +
85 +
export type ProposalState = "draft" | "open" | "landed" | "closed";
86 +
87 +
export type Proposal = {
88 +
  approvals: Approval[];
89 +
  author: string;
90 +
  blockers: string[];
91 +
  body: string;
92 +
  changes: ChangeSummary[];
93 +
  conflicted: boolean;
94 +
  created_ms: number;
95 +
  landable: boolean;
96 +
  number: number;
97 +
  projected_heads: string[];
98 +
  projected_state?: string | null;
99 +
  repository_url: string;
100 +
  source: ProposalSource;
101 +
  source_url?: string | null;
102 +
  state: ProposalState;
103 +
  target: string;
104 +
  title: string;
105 +
  updated_ms: number;
106 +
  url: string;
107 +
};
108 +
109 +
export type Label = {
110 +
  colour: string;
111 +
  description: string;
112 +
  name: string;
113 +
};
114 +
115 +
export type Issue = {
116 +
  assignees: string[];
117 +
  author: string;
118 +
  body: string;
119 +
  closed_ms?: number | null;
120 +
  comments: number;
121 +
  created_ms: number;
122 +
  labels: Label[];
123 +
  locked: boolean;
124 +
  milestone?: string | null;
125 +
  number: number;
126 +
  pinned: boolean;
127 +
  reason: string;
128 +
  state: string;
129 +
  title: string;
130 +
  updated_ms: number;
131 +
  url: string;
132 +
};
133 +
134 +
export type Milestone = {
135 +
  closed: number;
136 +
  description: string;
137 +
  due_ms?: number | null;
138 +
  open: number;
139 +
  state: string;
140 +
  title: string;
141 +
};
142 +
143 +
export type Atom = {
144 +
  change: string;
145 +
  index: number;
146 +
};
147 +
148 +
export type Comment = {
149 +
  anchor?: Atom | null;
150 +
  author: string;
151 +
  body: string;
152 +
  created_ms: number;
153 +
  id: number;
154 +
  path?: string | null;
155 +
  resolved: boolean;
156 +
};
157 +
158 +
export type Review = {
159 +
  author: string;
160 +
  body: string;
161 +
  created_ms: number;
162 +
  id: number;
163 +
  sealed: boolean;
164 +
  verdict: string;
165 +
};
166 +
167 +
export type TreeEntry = {
168 +
  conflicted: boolean;
169 +
  exec: boolean;
170 +
  kind: string;
171 +
  name: string;
172 +
  path: string;
173 +
  size?: number | null;
174 +
};
175 +
176 +
export type BlameLine = {
177 +
  author: string;
178 +
  change: string;
179 +
  index: number;
180 +
  line: number;
181 +
  text: string;
182 +
  when_ms: number;
183 +
};
184 +
185 +
export type FileChange = {
186 +
  added: number;
187 +
  from?: string | null;
188 +
  kind: string;
189 +
  path: string;
190 +
  removed: number;
191 +
};
192 +
193 +
export type ChangeDetail = {
194 +
  body: string;
195 +
  change: ChangeSummary;
196 +
  dependents: string[];
197 +
  files: FileChange[];
198 +
  in_bookmarks: string[];
199 +
  in_tags: string[];
200 +
};
201 +
202 +
export type History = {
203 +
  changes: ChangeSummary[];
204 +
  heads: string[];
205 +
  offset: number;
206 +
  reference: string;
207 +
  total: number;
208 +
};
209 +
210 +
export type Conflict = {
211 +
  bookmark: string;
212 +
  contributors: string[];
213 +
  kind: string;
214 +
  path: string;
215 +
};
216 +
217 +
export type Push = {
218 +
  at_ms: number;
219 +
  changes: number;
220 +
  heads: number;
221 +
  new_state: string;
222 +
  ok: boolean;
223 +
  old_state: string;
224 +
  pusher: string;
225 +
  reason: string;
226 +
  reference: string;
227 +
};
228 +
229 +
export type Policy = {
230 +
  expires_ms: number;
231 +
  root_threshold: number;
232 +
  roots: string[];
233 +
  rules: Rule[];
234 +
  serial: number;
235 +
};
236 +
237 +
export type Rule = {
238 +
  allow_conflicts: boolean;
239 +
  keys: string[];
240 +
  pattern: string;
241 +
  require: string;
242 +
  threshold: number;
243 +
};
244 +
245 +
export type Trust = {
246 +
  hub_key: string;
247 +
  invalid: string[];
248 +
  policy?: Policy | null;
249 +
  sealed: number;
250 +
  signed: number;
251 +
  unsigned: number;
252 +
};
253 +
254 +
export type Hook = {
255 +
  active: boolean;
256 +
  created_ms: number;
257 +
  events: string;
258 +
  id: number;
259 +
  url: string;
260 +
};
261 +
262 +
export type NewHook = {
263 +
  events?: string;
264 +
  secret: string;
265 +
  url: string;
266 +
};
267 +
268 +
export type CheckStatus = "queued" | "running" | "passed" | "failed" | "cancelled" | "skipped";
269 +
270 +
export type Check = {
271 +
  finished_ms?: number | null;
272 +
  name: string;
273 +
  reporter: string;
274 +
  seal?: string | null;
275 +
  started_ms: number;
276 +
  state: string;
277 +
  status: CheckStatus;
278 +
  summary: string;
279 +
  url: string;
280 +
};
281 +
282 +
export type NewCheck = {
283 +
  name: string;
284 +
  seal?: string | null;
285 +
  state: string;
286 +
  status: CheckStatus;
287 +
  summary?: string;
288 +
  url?: string;
289 +
};
290 +
291 +
export type User = {
292 +
  created_ms: number;
293 +
  display_name: string;
294 +
  is_admin: boolean;
295 +
  keys: string[];
296 +
  username: string;
297 +
};
298 +
299 +
export type Organisation = {
300 +
  description: string;
301 +
  display_name: string;
302 +
  members: string[];
303 +
  name: string;
304 +
};
305 +
306 +
export type NewRepository = {
307 +
  description?: string;
308 +
  name: string;
309 +
  owner: string;
310 +
  visibility?: Visibility;
311 +
};
312 +
313 +
export type NewIssue = {
314 +
  body?: string;
315 +
  title: string;
316 +
};
317 +
318 +
export type NewComment = {
319 +
  body: string;
320 +
};
321 +
322 +
export type NewProposal = {
323 +
  body?: string;
324 +
  draft?: boolean;
325 +
  source: string;
326 +
  target: string;
327 +
  title: string;
328 +
};
329 +
330 +
export type NewReview = {
331 +
  body?: string;
332 +
  verdict: string;
333 +
};
334 +
335 +
export type IssueStateChange = {
336 +
  reason?: string;
337 +
  state: string;
338 +
};
339 +
340 +
export type WebhookEnvelope = {
341 +
  actor: string;
342 +
  at_ms: number;
343 +
  data: unknown;
344 +
  delivery: string;
345 +
  event: string;
346 +
  repository?: Repository | null;
347 +
};
348 +
349 +
export type ProposalEvent = {
350 +
  check?: Check | null;
351 +
  kind: string;
352 +
  number: number;
353 +
};
354 +
added src/issues.ts +74 −0
1 +
// The Issues view (spec §8).
2 +
3 +
import * as vscode from "vscode";
4 +
import type * as api from "./hubapi";
5 +
import { HubAuth } from "./auth";
6 +
import { hubUrl } from "./hub";
7 +
import type { HubRepo } from "./proposals";
8 +
9 +
type Node = { kind: "repo"; repo: HubRepo } | { kind: "issue"; repo: HubRepo; i: api.Issue };
10 +
export type IssueNode = Extract<Node, { kind: "issue" }>;
11 +
12 +
export class IssuesProvider implements vscode.TreeDataProvider<Node> {
13 +
  private readonly emitter = new vscode.EventEmitter<void>();
14 +
  readonly onDidChangeTreeData = this.emitter.event;
15 +
16 +
  constructor(
17 +
    private readonly repos: () => Promise<HubRepo[]>,
18 +
    private readonly auth: HubAuth,
19 +
  ) {}
20 +
21 +
  refresh(): void {
22 +
    this.emitter.fire();
23 +
  }
24 +
25 +
  getTreeItem(node: Node): vscode.TreeItem {
26 +
    if (node.kind === "repo") {
27 +
      const item = new vscode.TreeItem(`${node.repo.owner}/${node.repo.name}`, vscode.TreeItemCollapsibleState.Expanded);
28 +
      item.iconPath = new vscode.ThemeIcon("repo");
29 +
      return item;
30 +
    }
31 +
    const i = node.i;
32 +
    const item = new vscode.TreeItem(`#${i.number} ${i.title}`, vscode.TreeItemCollapsibleState.None);
33 +
    item.description = [i.labels.map((l) => l.name).join(", "), i.assignees.length ? "→ " + i.assignees.join(", ") : "", i.milestone ?? ""].filter(Boolean).join(" · ");
34 +
    item.iconPath = new vscode.ThemeIcon(i.state === "open" ? "issue-opened" : "issue-closed");
35 +
    item.contextValue = "issue";
36 +
    item.tooltip = new vscode.MarkdownString(`**${i.title}**\n\nopened by ${i.author} · ${i.comments} comment(s)\n\n${i.body.slice(0, 400)}`);
37 +
    item.command = { title: "Open", command: "oxygenhub.openIssue", arguments: [node] };
38 +
    return item;
39 +
  }
40 +
41 +
  async getChildren(node?: Node): Promise<Node[]> {
42 +
    if (!node) {
43 +
      return (await this.repos()).map((repo) => ({ kind: "repo", repo }));
44 +
    }
45 +
    if (node.kind === "repo") {
46 +
      try {
47 +
        const me = this.auth.sessionFor(node.repo.base)?.username;
48 +
        const list = await this.auth.clientFor(node.repo.base).issues(node.repo.owner, node.repo.name, "open");
49 +
        list.sort((a, b) => Number(b.assignees.includes(me ?? "")) - Number(a.assignees.includes(me ?? "")) || b.updated_ms - a.updated_ms);
50 +
        return list.map((i) => ({ kind: "issue", repo: node.repo, i }));
51 +
      } catch (e) {
52 +
        void vscode.window.showWarningMessage(`Oxygenhub: ${(e as Error).message}`);
53 +
        return [];
54 +
      }
55 +
    }
56 +
    return [];
57 +
  }
58 +
}
59 +
60 +
export async function showIssue(auth: HubAuth, repo: HubRepo, i: api.Issue): Promise<void> {
61 +
  const comments = await auth.clientFor(repo.base).issueComments(repo.owner, repo.name, i.number);
62 +
  const panel = vscode.window.createWebviewPanel("oxygenhub.issue", `#${i.number} ${i.title}`, vscode.ViewColumn.Beside, { enableCommandUris: true });
63 +
  const esc = (s: string) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
64 +
  const arg = encodeURIComponent(JSON.stringify([repo.base, repo.owner, repo.name, i.number]));
65 +
  const thread = comments.map((c) => `<div class=c><b>${esc(c.author)}</b> <span class=mute>${new Date(c.created_ms).toLocaleString()}</span><pre>${esc(c.body)}</pre></div>`).join("");
66 +
  panel.webview.html = `<!doctype html><html><body style="font-family: var(--vscode-font-family); padding: 12px">
67 +
  <h2>#${i.number} ${esc(i.title)} <small>${i.state}${i.reason ? ": " + i.reason : ""}</small></h2>
68 +
  <p class=mute>opened by ${esc(i.author)} · ${i.labels.map((l) => esc(l.name)).join(", ") || "no labels"} · ${i.assignees.length ? "assigned to " + i.assignees.map(esc).join(", ") : "unassigned"}${i.milestone ? " · " + esc(i.milestone) : ""}</p>
69 +
  <p><a href="${hubUrl(repo.base, repo.owner, repo.name, { kind: "issue", number: i.number })}">Open on hub</a> · <a href="command:oxygenhub.commentIssue?${arg}">Comment</a> · <a href="command:oxygenhub.${i.state === "open" ? "closeIssue" : "reopenIssue"}?${arg}">${i.state === "open" ? "Close" : "Reopen"}</a> · <a href="command:oxygenhub.assignMe?${arg}">Assign to me</a></p>
70 +
  <pre style="white-space: pre-wrap">${esc(i.body) || "<i>No description.</i>"}</pre>
71 +
  ${thread}
72 +
  <style>.mute{opacity:.7} .c{border-top:1px solid var(--vscode-widget-border);padding:8px 0} pre{white-space:pre-wrap} small{font-weight:normal;opacity:.7}</style>
73 +
  </body></html>`;
74 +
}
added src/oxygenapi.ts +37 −0
1 +
// The Oxygen extension's exported API, as its src/api.ts declares it.
2 +
// Kept as a copy of the interface so this extension compiles alone.
3 +
4 +
export interface RemoteProvider {
5 +
  readonly id: string;
6 +
  readonly label: string;
7 +
  matches(remoteUrl: string): boolean;
8 +
  urlFor(remoteUrl: string, target: RemoteTarget): string | undefined;
9 +
}
10 +
11 +
export type RemoteTarget =
12 +
  | { kind: "repository" }
13 +
  | { kind: "bookmark"; name: string }
14 +
  | { kind: "change"; hash: string }
15 +
  | { kind: "file"; reference: string; path: string; line?: number }
16 +
  | { kind: "proposals" }
17 +
  | { kind: "issues" };
18 +
19 +
export interface RepositoryHandle {
20 +
  readonly root: string;
21 +
  readonly status: unknown;
22 +
  readonly bookmarks: { name: string; heads: string[]; current?: boolean }[];
23 +
  readonly currentBookmark: string | undefined;
24 +
  remotes(): Promise<{ name: string; url: string }[]>;
25 +
  json<T>(args: string[]): Promise<T>;
26 +
  refresh(): Promise<void>;
27 +
}
28 +
29 +
export interface OxygenAPI {
30 +
  readonly version: 1;
31 +
  readonly repositories: readonly RepositoryHandle[];
32 +
  readonly onDidChangeStatus: (listener: (repo: RepositoryHandle) => void) => { dispose(): void };
33 +
  readonly onDidChangeRepositories: (listener: () => void) => { dispose(): void };
34 +
  getRepository(fsPath: string): RepositoryHandle | undefined;
35 +
  registerRemoteProvider(provider: RemoteProvider): { dispose(): void };
36 +
  remoteProviders(): readonly RemoteProvider[];
37 +
}
added src/proposals.ts +155 −0
1 +
// The Proposals view (spec §5): per repository with a hub remote, three
2 +
// buckets, a detail webview, and the actions.
3 +
4 +
import * as vscode from "vscode";
5 +
import type * as api from "./hubapi";
6 +
import { HubAuth } from "./auth";
7 +
import { hubUrl, needsMe, parseRepoUrl } from "./hub";
8 +
import type { OxygenAPI, RepositoryHandle } from "./oxygenapi";
9 +
10 +
/** A repository in the workspace with a hub remote. */
11 +
export interface HubRepo {
12 +
  handle: RepositoryHandle;
13 +
  base: string;
14 +
  owner: string;
15 +
  name: string;
16 +
  remote: string;
17 +
}
18 +
19 +
/** Find the hub remotes of every repository the Oxygen extension knows. */
20 +
export async function hubRepos(oxygen: OxygenAPI, auth: HubAuth): Promise<HubRepo[]> {
21 +
  const out: HubRepo[] = [];
22 +
  const bases = auth.bases();
23 +
  for (const handle of oxygen.repositories) {
24 +
    let remotes: { name: string; url: string }[] = [];
25 +
    try {
26 +
      remotes = await handle.remotes();
27 +
    } catch {
28 +
      continue;
29 +
    }
30 +
    for (const r of remotes) {
31 +
      const p = parseRepoUrl(r.url);
32 +
      if (p && bases.includes(p.base)) {
33 +
        out.push({ handle, base: p.base, owner: p.owner, name: p.name, remote: r.name });
34 +
        break;
35 +
      }
36 +
    }
37 +
  }
38 +
  return out;
39 +
}
40 +
41 +
type Node = { kind: "repo"; repo: HubRepo } | { kind: "bucket"; repo: HubRepo; label: string; items: api.Proposal[] } | { kind: "proposal"; repo: HubRepo; p: api.Proposal };
42 +
43 +
export class ProposalsProvider implements vscode.TreeDataProvider<Node> {
44 +
  private readonly emitter = new vscode.EventEmitter<void>();
45 +
  readonly onDidChangeTreeData = this.emitter.event;
46 +
  private cache = new Map<string, api.Proposal[]>();
47 +
48 +
  constructor(
49 +
    private readonly oxygen: () => OxygenAPI | undefined,
50 +
    private readonly auth: HubAuth,
51 +
  ) {}
52 +
53 +
  refresh(): void {
54 +
    this.cache.clear();
55 +
    this.emitter.fire();
56 +
  }
57 +
58 +
  async repos(): Promise<HubRepo[]> {
59 +
    const o = this.oxygen();
60 +
    return o ? hubRepos(o, this.auth) : [];
61 +
  }
62 +
63 +
  async proposalsOf(repo: HubRepo): Promise<api.Proposal[]> {
64 +
    const key = `${repo.base}/${repo.owner}/${repo.name}`;
65 +
    const hit = this.cache.get(key);
66 +
    if (hit) {
67 +
      return hit;
68 +
    }
69 +
    const list = await this.auth.clientFor(repo.base).proposals(repo.owner, repo.name);
70 +
    this.cache.set(key, list);
71 +
    return list;
72 +
  }
73 +
74 +
  getTreeItem(node: Node): vscode.TreeItem {
75 +
    switch (node.kind) {
76 +
      case "repo": {
77 +
        const item = new vscode.TreeItem(`${node.repo.owner}/${node.repo.name}`, vscode.TreeItemCollapsibleState.Expanded);
78 +
        item.iconPath = new vscode.ThemeIcon("repo");
79 +
        item.contextValue = "repo";
80 +
        return item;
81 +
      }
82 +
      case "bucket": {
83 +
        const item = new vscode.TreeItem(`${node.label} (${node.items.length})`, node.items.length ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.Collapsed);
84 +
        item.contextValue = "bucket";
85 +
        return item;
86 +
      }
87 +
      case "proposal": {
88 +
        const p = node.p;
89 +
        const item = new vscode.TreeItem(`#${p.number} ${p.title}`, vscode.TreeItemCollapsibleState.None);
90 +
        const sealed = p.approvals.filter((a) => a.sealed).length;
91 +
        const chips = [p.state === "draft" ? "draft" : "", p.conflicted ? "conflicts" : "", p.landable ? "landable" : "", sealed ? `${sealed} sealed` : ""].filter(Boolean).join(" · ");
92 +
        item.description = `${p.author} → ${p.target}${chips ? " · " + chips : ""}`;
93 +
        item.tooltip = new vscode.MarkdownString(`**${p.title}** by ${p.author}\n\n${p.changes.length} change(s) into ${p.target}\n\n${p.blockers.length ? "Blocked: " + p.blockers.join("; ") : "Ready to land"}`);
94 +
        item.iconPath = new vscode.ThemeIcon(p.conflicted ? "warning" : p.landable ? "pass" : p.state === "draft" ? "circle-outline" : "git-pull-request");
95 +
        item.contextValue = "proposal";
96 +
        item.command = { title: "Open", command: "oxygenhub.openProposal", arguments: [node] };
97 +
        return item;
98 +
      }
99 +
    }
100 +
  }
101 +
102 +
  async getChildren(node?: Node): Promise<Node[]> {
103 +
    if (!node) {
104 +
      const repos = await this.repos();
105 +
      return repos.map((repo) => ({ kind: "repo", repo }));
106 +
    }
107 +
    if (node.kind === "repo") {
108 +
      let list: api.Proposal[] = [];
109 +
      try {
110 +
        list = await this.proposalsOf(node.repo);
111 +
      } catch (e) {
112 +
        void vscode.window.showWarningMessage(`Oxygenhub: ${(e as Error).message}`);
113 +
        return [];
114 +
      }
115 +
      const me = this.auth.sessionFor(node.repo.base)?.username;
116 +
      const mine = list.filter((p) => p.author === me);
117 +
      const needing = list.filter((p) => needsMe(p, me));
118 +
      const rest = list.filter((p) => !mine.includes(p) && !needing.includes(p));
119 +
      return [
120 +
        { kind: "bucket", repo: node.repo, label: "Needing me", items: needing },
121 +
        { kind: "bucket", repo: node.repo, label: "Mine", items: mine },
122 +
        { kind: "bucket", repo: node.repo, label: "Open", items: rest },
123 +
      ];
124 +
    }
125 +
    if (node.kind === "bucket") {
126 +
      return node.items.map((p) => ({ kind: "proposal", repo: node.repo, p }));
127 +
    }
128 +
    return [];
129 +
  }
130 +
}
131 +
132 +
export type ProposalNode = Extract<Node, { kind: "proposal" }>;
133 +
134 +
/** The detail webview. */
135 +
export function showDetail(repo: HubRepo, p: api.Proposal, checks: api.Check[]): void {
136 +
  const panel = vscode.window.createWebviewPanel("oxygenhub.proposal", `#${p.number} ${p.title}`, vscode.ViewColumn.Beside, { enableCommandUris: true });
137 +
  const esc = (s: string) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
138 +
  const changes = p.changes.map((c) => `<li><code>${c.hash.slice(0, 12)}</code> ${esc(c.message.split("\n")[0])} <span class="mute">${esc(c.author.name)} · ${c.signature.status}</span></li>`).join("");
139 +
  const approvals = p.approvals.map((a) => `<li>${a.sealed ? "◈" : "○"} ${esc(a.account)}${a.sealed ? "" : " (seal pending)"}</li>`).join("") || "<li class=mute>none</li>";
140 +
  const blockers = p.blockers.map((b) => `<li>✘ ${esc(b)}</li>`).join("") || "<li>✔ ready to land</li>";
141 +
  const checkRows = checks.map((c) => `<li>${c.status === "passed" ? "✔" : c.status === "failed" ? "✘" : "…"} ${esc(c.name)} <span class=mute>${c.status}${c.summary ? " · " + esc(c.summary) : ""}</span></li>`).join("") || "<li class=mute>none</li>";
142 +
  const url = hubUrl(repo.base, repo.owner, repo.name, { kind: "proposal", number: p.number });
143 +
  const arg = encodeURIComponent(JSON.stringify([repo.base, repo.owner, repo.name, p.number]));
144 +
  panel.webview.html = `<!doctype html><html><body style="font-family: var(--vscode-font-family); padding: 12px">
145 +
  <h2>#${p.number} ${esc(p.title)} <small>${p.state}</small></h2>
146 +
  <p>${esc(p.author)} wants <b>${esc(JSON.stringify(p.source))}</b> into <b>${esc(p.target)}</b> · ${p.changes.length} change(s)${p.conflicted ? " · <b>conflicts</b>" : ""}</p>
147 +
  <p><a href="${url}">Open on hub</a> · <a href="command:oxygenhub.checkoutProposal?${arg}">Check out</a> · <a href="command:oxygenhub.review?${arg}">Review</a> · <a href="command:oxygenhub.sealApproval?${arg}">Seal approval</a> · <a href="command:oxygenhub.land?${arg}">Land</a></p>
148 +
  <div>${p.body ? `<pre style="white-space: pre-wrap">${esc(p.body)}</pre>` : ""}</div>
149 +
  <h3>Landing</h3><ul>${blockers}</ul>
150 +
  <h3>Approvals</h3><ul>${approvals}</ul>
151 +
  <h3>Checks</h3><ul>${checkRows}</ul>
152 +
  <h3>Changes</h3><ul>${changes}</ul>
153 +
  <style>.mute{opacity:.7} small{font-weight:normal;opacity:.7}</style>
154 +
  </body></html>`;
155 +
}
added src/provider.ts +24 −0
1 +
// The remote provider registered with the Oxygen extension: a hub is any
2 +
// base URL we know (signed in, or configured), and a remote URL is a
3 +
// repository on one of them.
4 +
5 +
import { hubUrl, parseRepoUrl } from "./hub";
6 +
7 +
export interface KnownHubs {
8 +
  bases(): string[];
9 +
}
10 +
11 +
export function makeProvider(hubs: KnownHubs) {
12 +
  return {
13 +
    id: "oxygenhub",
14 +
    label: "Oxygenhub",
15 +
    matches(remoteUrl: string): boolean {
16 +
      const p = parseRepoUrl(remoteUrl);
17 +
      return !!p && hubs.bases().some((b) => b.replace(/\/$/, "") === p.base);
18 +
    },
19 +
    urlFor(remoteUrl: string, target: { kind: string; [k: string]: unknown }): string | undefined {
20 +
      const p = parseRepoUrl(remoteUrl);
21 +
      return p ? hubUrl(p.base, p.owner, p.name, target) : undefined;
22 +
    },
23 +
  };
24 +
}
added src/review.ts +100 −0
1 +
// Review threads on the checked-out proposal (spec §6): a comment
2 +
// controller whose threads are the hub's anchored comments, placed by
3 +
// asking oxy for each line's atom.
4 +
5 +
import * as vscode from "vscode";
6 +
import type * as api from "./hubapi";
7 +
import { HubAuth } from "./auth";
8 +
import type { HubRepo } from "./proposals";
9 +
10 +
interface BlameLine {
11 +
  line: number;
12 +
  hash: string;
13 +
  index: number;
14 +
}
15 +
16 +
/** The hub's comments as threads on files under the repository root. */
17 +
export class ReviewController implements vscode.Disposable {
18 +
  private readonly controller = vscode.comments.createCommentController("oxygenhub", "Oxygenhub review");
19 +
  private threads: vscode.CommentThread[] = [];
20 +
  private active: { repo: HubRepo; number: number } | undefined;
21 +
22 +
  constructor(private readonly auth: HubAuth) {
23 +
    this.controller.commentingRangeProvider = {
24 +
      provideCommentingRanges: (document) => (this.active && document.uri.fsPath.startsWith(this.active.repo.handle.root) ? [new vscode.Range(0, 0, Math.max(0, document.lineCount - 1), 0)] : []),
25 +
    };
26 +
  }
27 +
28 +
  /** Show the threads of a proposal; call after checking it out. */
29 +
  async load(repo: HubRepo, number: number): Promise<void> {
30 +
    this.clear();
31 +
    this.active = { repo, number };
32 +
    const comments = await this.auth.clientFor(repo.base).proposalComments(repo.owner, repo.name, number);
33 +
    const byPath = new Map<string, api.Comment[]>();
34 +
    for (const c of comments) {
35 +
      if (c.path && c.anchor) {
36 +
        byPath.set(c.path, [...(byPath.get(c.path) ?? []), c]);
37 +
      }
38 +
    }
39 +
    for (const [path, list] of byPath) {
40 +
      let blame: BlameLine[] = [];
41 +
      try {
42 +
        blame = await repo.handle.json<BlameLine[]>(["blame", path, "--json"]);
43 +
      } catch {
44 +
        continue;
45 +
      }
46 +
      const uri = vscode.Uri.file(`${repo.handle.root}/${path}`);
47 +
      // One thread per atom.
48 +
      const byAtom = new Map<string, api.Comment[]>();
49 +
      for (const c of list) {
50 +
        const key = `${c.anchor!.change}:${c.anchor!.index}`;
51 +
        byAtom.set(key, [...(byAtom.get(key) ?? []), c]);
52 +
      }
53 +
      for (const [key, cs] of byAtom) {
54 +
        const [hash, index] = key.split(":");
55 +
        const line = blame.find((b) => b.hash === hash && String(b.index) === index)?.line;
56 +
        if (!line) {
57 +
          continue;
58 +
        }
59 +
        const thread = this.controller.createCommentThread(uri, new vscode.Range(line - 1, 0, line - 1, 0), cs.map(toComment));
60 +
        thread.label = cs[0].resolved ? "resolved" : "review";
61 +
        thread.state = cs[0].resolved ? vscode.CommentThreadState.Resolved : vscode.CommentThreadState.Unresolved;
62 +
        thread.collapsibleState = vscode.CommentThreadCollapsibleState.Expanded;
63 +
        this.threads.push(thread);
64 +
      }
65 +
    }
66 +
  }
67 +
68 +
  /** Post a reply to a thread, anchored to the line it sits on. */
69 +
  async reply(reply: vscode.CommentReply): Promise<void> {
70 +
    const a = this.active;
71 +
    if (!a) {
72 +
      return;
73 +
    }
74 +
    const rel = reply.thread.uri.fsPath.slice(a.repo.handle.root.length + 1);
75 +
    const line = reply.thread.range?.start.line ?? 0;
76 +
    // The hub anchors by line number of the projected state; send the path and line.
77 +
    await postLineComment(this.auth, a.repo, a.number, rel, line + 1, reply.text);
78 +
    await this.load(a.repo, a.number);
79 +
  }
80 +
81 +
  clear(): void {
82 +
    this.threads.forEach((t) => t.dispose());
83 +
    this.threads = [];
84 +
    this.active = undefined;
85 +
  }
86 +
87 +
  dispose(): void {
88 +
    this.clear();
89 +
    this.controller.dispose();
90 +
  }
91 +
}
92 +
93 +
function toComment(c: api.Comment): vscode.Comment {
94 +
  return { body: new vscode.MarkdownString(c.body), mode: vscode.CommentMode.Preview, author: { name: c.author }, label: c.resolved ? "resolved" : undefined, timestamp: new Date(c.created_ms) };
95 +
}
96 +
97 +
/** The hub's form endpoint accepts a path and line; the API accepts general comments only, so line comments go through the form with the API token as a bearer is not enough: use the API's general comment with a path marker until the API grows an anchored variant. */
98 +
async function postLineComment(auth: HubAuth, repo: HubRepo, number: number, path: string, line: number, body: string): Promise<void> {
99 +
  await auth.clientFor(repo.base).commentProposal(repo.owner, repo.name, number, `${body}\n\n_on ${path}:${line}_`);
100 +
}
added test/hub.test.mjs +67 −0
1 +
// Against a real hub when the binary exists: create a repository, an
2 +
// issue and a proposal through the client.
3 +
import { test } from "node:test";
4 +
import assert from "node:assert/strict";
5 +
import { spawn, execFileSync } from "node:child_process";
6 +
import { mkdtempSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
7 +
import { tmpdir } from "node:os";
8 +
import { join } from "node:path";
9 +
import { HubClient, HubError } from "../out/hub.mjs";
10 +
11 +
const HUB = join(import.meta.dirname, "../../oxygenhub/target/release/oxygenhub");
12 +
const OXY = join(import.meta.dirname, "../../oxygen/target/release/oxy");
13 +
14 +
test("hub client end to end", { skip: !existsSync(HUB) || !existsSync(OXY) }, async () => {
15 +
  const data = mkdtempSync(join(tmpdir(), "hubvs-"));
16 +
  const port = 17300 + Math.floor(Math.random() * 1000);
17 +
  const base = `http://127.0.0.1:${port}`;
18 +
  writeFileSync(join(data, "hub.toml"), `[server]\nbind = "127.0.0.1:${port}"\nbase_url = "${base}"\n[auth]\npush_tokens = ["site"]\n[registration]\nmode = "open"\n`);
19 +
  execFileSync(HUB, ["--data", data, "init"], { stdio: "ignore" });
20 +
  execFileSync(HUB, ["--data", data, "admin", "user", "create", "andy", "--admin"], { stdio: "ignore" });
21 +
  const proc = spawn(HUB, ["--data", data, "serve"], { stdio: "ignore" });
22 +
  try {
23 +
    for (let i = 0; i < 50; i++) {
24 +
      try {
25 +
        await fetch(`${base}/health`);
26 +
        break;
27 +
      } catch {
28 +
        await new Promise((r) => setTimeout(r, 100));
29 +
      }
30 +
    }
31 +
    const client = new HubClient(base, "site");
32 +
    // The client has no createRepository method by design; use fetch directly.
33 +
    const res = await fetch(`${base}/api/v1/repos`, { method: "POST", headers: { authorization: "Bearer site", "content-type": "application/json" }, body: JSON.stringify({ owner: "andy", name: "demo" }) });
34 +
    assert.equal(res.status, 201);
35 +
    // Push a bookmark with oxy so a proposal has something to add.
36 +
    const work = mkdtempSync(join(tmpdir(), "hubvs-work-"));
37 +
    const env = { ...process.env, OXY_HOME: mkdtempSync(join(tmpdir(), "hubvs-home-")) };
38 +
    const oxy = (...args) => execFileSync(OXY, ["--plain", ...args], { cwd: work, env, stdio: ["ignore", "pipe", "pipe"] }).toString();
39 +
    oxy("init");
40 +
    oxy("remote", "add", "origin", `${base}/andy/demo`);
41 +
    oxy("config", "remote.origin.token", "site");
42 +
    mkdirSync(join(work, "src"), { recursive: true });
43 +
    writeFileSync(join(work, "src/a.txt"), "a\n");
44 +
    oxy("commit", "-m", "base");
45 +
    oxy("push", "origin");
46 +
    oxy("bookmark", "create", "feat");
47 +
    oxy("bookmark", "switch", "feat");
48 +
    writeFileSync(join(work, "src/b.txt"), "b\n");
49 +
    oxy("commit", "-m", "feat");
50 +
    oxy("push", "origin", "--bookmark", "feat");
51 +
    const p = await client.createProposal("andy", "demo", { title: "Feat", target: "main", source: "feat", body: "", draft: false });
52 +
    assert.equal(p.number, 1);
53 +
    assert.equal(p.changes.length, 1);
54 +
    const list = await client.proposals("andy", "demo");
55 +
    assert.equal(list[0].title, "Feat");
56 +
    const issue = await client.createIssue("andy", "demo", "Bug", "text");
57 +
    assert.equal(issue.number, 2);
58 +
    await client.commentIssue("andy", "demo", 2, "more");
59 +
    const closed = await client.setIssueState("andy", "demo", 2, "closed");
60 +
    assert.equal(closed.state, "closed");
61 +
    const me = await client.me();
62 +
    assert.equal(me.username, "andy");
63 +
    await assert.rejects(() => new HubClient(base, "nope").me(), (e) => e instanceof HubError && e.status === 401);
64 +
  } finally {
65 +
    proc.kill();
66 +
  }
67 +
});
added test/provider.test.mjs +34 −0
1 +
import { test } from "node:test";
2 +
import assert from "node:assert/strict";
3 +
import { makeProvider } from "../out/provider.mjs";
4 +
import { parseRepoUrl, hubUrl, needsMe } from "../out/hub.mjs";
5 +
6 +
test("parses repository URLs on a hub", () => {
7 +
  assert.deepEqual(parseRepoUrl("https://earth.example:7343/andy/oxygen"), { base: "https://earth.example:7343", owner: "andy", name: "oxygen" });
8 +
  assert.equal(parseRepoUrl("https://earth.example/api/v1/repos"), undefined);
9 +
  assert.equal(parseRepoUrl("file:///tmp/x"), undefined);
10 +
});
11 +
12 +
test("builds hub URLs for every target", () => {
13 +
  const b = "https://h";
14 +
  assert.equal(hubUrl(b, "a", "r", { kind: "repository" }), "https://h/a/r");
15 +
  assert.equal(hubUrl(b, "a", "r", { kind: "file", reference: "main", path: "src/x.rs", line: 4 }), "https://h/a/r/file/main/src/x.rs#L4");
16 +
  assert.equal(hubUrl(b, "a", "r", { kind: "change", hash: "abc" }), "https://h/a/r/change/abc");
17 +
  assert.equal(hubUrl(b, "a", "r", { kind: "proposal", number: 3 }), "https://h/a/r/proposal/3");
18 +
});
19 +
20 +
test("provider matches only known hubs", () => {
21 +
  const p = makeProvider({ bases: () => ["https://h/"] });
22 +
  assert.equal(p.matches("https://h/a/r"), true);
23 +
  assert.equal(p.matches("https://other/a/r"), false);
24 +
  assert.equal(p.urlFor("https://h/a/r", { kind: "issues" }), "https://h/a/r/issues");
25 +
});
26 +
27 +
test("needsMe: open, not mine, not yet sealed by me", () => {
28 +
  const base = { state: "open", author: "bob", approvals: [] };
29 +
  assert.equal(needsMe(base, "andy"), true);
30 +
  assert.equal(needsMe({ ...base, author: "andy" }, "andy"), false);
31 +
  assert.equal(needsMe({ ...base, approvals: [{ account: "andy", sealed: true, key: "k" }] }, "andy"), false);
32 +
  assert.equal(needsMe({ ...base, approvals: [{ account: "andy", sealed: false, key: null }] }, "andy"), true);
33 +
  assert.equal(needsMe({ ...base, state: "landed" }, "andy"), false);
34 +
});
added tsconfig.json +18 −0
1 +
{
2 +
  "compilerOptions": {
3 +
    "target": "ES2022",
4 +
    "module": "Node16",
5 +
    "moduleResolution": "Node16",
6 +
    "lib": ["ES2022"],
7 +
    "strict": true,
8 +
    "noImplicitReturns": true,
9 +
    "noUnusedLocals": true,
10 +
    "noFallthroughCasesInSwitch": true,
11 +
    "esModuleInterop": true,
12 +
    "skipLibCheck": true,
13 +
    "sourceMap": true,
14 +
    "outDir": "out",
15 +
    "rootDir": "src"
16 +
  },
17 +
  "include": ["src"]
18 +
}