@rockhopper-co/mcp-server 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +134 -1
- package/README.md +34 -10
- package/dist/api-client.d.ts +56 -5
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +86 -11
- package/dist/api-client.js.map +1 -1
- package/dist/auth/device-grant-client.d.ts +80 -0
- package/dist/auth/device-grant-client.d.ts.map +1 -0
- package/dist/auth/device-grant-client.js +138 -0
- package/dist/auth/device-grant-client.js.map +1 -0
- package/dist/auth/resolve-auth.d.ts +52 -0
- package/dist/auth/resolve-auth.d.ts.map +1 -0
- package/dist/auth/resolve-auth.js +100 -0
- package/dist/auth/resolve-auth.js.map +1 -0
- package/dist/auth/token-store.d.ts +51 -0
- package/dist/auth/token-store.d.ts.map +1 -0
- package/dist/auth/token-store.js +98 -0
- package/dist/auth/token-store.js.map +1 -0
- package/dist/cli.js +37 -11
- package/dist/cli.js.map +1 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +20 -9
- package/dist/prompts/index.js.map +1 -1
- package/dist/resources/changes.d.ts.map +1 -1
- package/dist/resources/changes.js +8 -2
- package/dist/resources/changes.js.map +1 -1
- package/dist/resources/orchestration-guide.md +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +113 -20
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/write-files.js +4 -4
- package/dist/tools/write-files.js.map +1 -1
- package/dist/tools/write-reviews.d.ts.map +1 -1
- package/dist/tools/write-reviews.js +3 -1
- package/dist/tools/write-reviews.js.map +1 -1
- package/dist/types.d.ts +23 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/zod-schemas.d.ts +111 -0
- package/dist/zod-schemas.d.ts.map +1 -0
- package/dist/zod-schemas.js +62 -0
- package/dist/zod-schemas.js.map +1 -0
- package/package.json +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,140 @@ All notable changes to this project are documented here. Follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
-
## [
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **`get_cell_history`, `resolve_comment`, and `rename_file` no longer
|
|
11
|
+
render `undefined` for every field (KI-096).** Diagnosis revealed the
|
|
12
|
+
formatters were correct — the backend was returning the wrong shape
|
|
13
|
+
on all three endpoints. Backend PR
|
|
14
|
+
[#478](https://github.com/Rockhopper-Co/backend/pull/478) fixed the
|
|
15
|
+
shapes; this PR adopts them:
|
|
16
|
+
- `getCellHistory` passes `?format=mcp` to opt into the backend's
|
|
17
|
+
normalized projection (`{versionId, value, changedBy, changedAt}`).
|
|
18
|
+
Default `format` preserves the raw-CTE shape the frontend cell-
|
|
19
|
+
history popover consumes — we never call that path.
|
|
20
|
+
- `resolveComment` + `updateEnrolledFile` continue to call the same
|
|
21
|
+
URLs but now receive the updated `FileChat` / `EnrolledFile` entity
|
|
22
|
+
(was TypeORM `UpdateResult`).
|
|
23
|
+
- `CellHistoryEntry.versionId` retyped `number` → `string` (was a
|
|
24
|
+
contributing root cause of the `Version undefined` symptom — the
|
|
25
|
+
backend's semver string never coerced to the declared numeric type).
|
|
26
|
+
- **`api-client.ts`: zod-parse opt-in for response validation
|
|
27
|
+
(KI-096).** `request<T>(path, init?, responseSchema?)` now accepts an
|
|
28
|
+
optional zod schema; when supplied, the response is parsed with
|
|
29
|
+
`safeParse` and any drift throws a useful diagnostic
|
|
30
|
+
(`Rockhopper API response failed schema check at <path>: <field> —
|
|
31
|
+
<message>`) instead of silently rendering `undefined` in formatters.
|
|
32
|
+
Three call sites opt in: `getCellHistory`, `resolveComment`,
|
|
33
|
+
`updateEnrolledFile`. Other methods stay unchanged; a sweep ticket
|
|
34
|
+
can migrate them later. New `src/zod-schemas.ts` module holds the
|
|
35
|
+
per-entity schemas.
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
- **`search_files` `matchIn` parameter.** Optional enum (`name` |
|
|
39
|
+
`comments` | `versions` | `all`); defaults to `name` for back-compat.
|
|
40
|
+
Broadens search past file-name substring into comment text
|
|
41
|
+
(`FileChat.message`) and version descriptions (`FileVersion.description`).
|
|
42
|
+
Backed by [backend PR #472](https://github.com/Rockhopper-Co/backend/pull/472)
|
|
43
|
+
/ ENG-1383; behavior available once that merges. Closes KI-080.
|
|
44
|
+
- **OAuth device-grant flow (RFC 8628) as default auth.** First launch
|
|
45
|
+
with no `ROCKHOPPER_TOKEN` env var now prints a verification code +
|
|
46
|
+
URL to stderr, polls the backend's `/auth/device/{code,token}`
|
|
47
|
+
endpoints, and persists the resulting bearer token in the OS keychain
|
|
48
|
+
(Keychain on macOS, Credential Manager on Windows, libsecret on
|
|
49
|
+
Linux). Subsequent launches reuse the stored token silently. Tokens
|
|
50
|
+
default to a 60-minute lifetime; on expiry the next launch silently
|
|
51
|
+
re-runs the flow. `ROCKHOPPER_TOKEN` still takes precedence when set
|
|
52
|
+
— PAT path preserved for headless / CI scenarios. Backed by
|
|
53
|
+
[backend PR #473](https://github.com/Rockhopper-Co/backend/pull/473)
|
|
54
|
+
/ ENG-1384. Closes KI-081 / ENG-1444.
|
|
55
|
+
- **`get_unattributed_changes` cursor pagination + cap/summary (KI-097).**
|
|
56
|
+
File-wide mode now uses the dedicated paginated backend route
|
|
57
|
+
(`GET /unattributed-changes/paginated/:fileMsId`, added by
|
|
58
|
+
[backend PR #475](https://github.com/Rockhopper-Co/backend/pull/475) /
|
|
59
|
+
KI-102) instead of the legacy unpaginated route. The MCP tool gains an
|
|
60
|
+
optional `cursor` input for round-tripping the backend's cursor.
|
|
61
|
+
Responses are now capped at 200 displayed rows with a summary line
|
|
62
|
+
("Showing X of Y change(s) on this page (Z total across the file)" +
|
|
63
|
+
top-sheets breakdown) and a pagination hint when more pages or hidden
|
|
64
|
+
rows are available. Audit measured one file at 12.5 MB / 28k rows on
|
|
65
|
+
the old route — context-blowing for AI clients; now bounded under
|
|
66
|
+
the 25k-token MCP limit. Sheet-filter mode (`sheetName` set) is
|
|
67
|
+
unchanged — it stays unpaginated since sheet size inherently bounds
|
|
68
|
+
it. Closes KI-097.
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
- `ROCKHOPPER_TOKEN` is now **optional** (was required). Unset → OAuth.
|
|
72
|
+
Set → PAT auth path.
|
|
73
|
+
- **`ApiClient.getUnattributedChanges` refactored into two methods**
|
|
74
|
+
(KI-097): `getUnattributedChangesBySheet(fileMsId, sheetName)` for
|
|
75
|
+
the sheet-filtered legacy route, and
|
|
76
|
+
`getUnattributedChangesPaginated(fileMsId, cursor?)` for the new
|
|
77
|
+
cursor-paginated route. The old combined method is removed. External
|
|
78
|
+
consumers of `ApiClient` (e.g. `mcp-gateway`) only use `createServer`
|
|
79
|
+
+ `ApiClient` as types, not these methods directly, so the rename has
|
|
80
|
+
zero blast radius outside this repo.
|
|
81
|
+
|
|
82
|
+
### Dependencies
|
|
83
|
+
- **`keytar`** (new runtime dep) — OS-native keychain wrapper. Linux
|
|
84
|
+
requires `libsecret` installed; otherwise the OAuth path errors with
|
|
85
|
+
a clear remediation message and you must fall back to PAT.
|
|
86
|
+
|
|
87
|
+
### Changed (breaking)
|
|
88
|
+
- **`update_file_description` renamed to `rename_file`.** The tool always
|
|
89
|
+
performed a rename (its `name` input wires to backend
|
|
90
|
+
`PATCH /enrolled-files/:fileMsId`); the old name described non-existent
|
|
91
|
+
"description" semantics. Customers using the npm-installed local server
|
|
92
|
+
should update any tool-name allowlists or prompts referencing
|
|
93
|
+
`update_file_description`. Closes KI-100 / ENG-1439.
|
|
94
|
+
|
|
95
|
+
### Fixed
|
|
96
|
+
- **`cancel_review` now actually cancels PENDING reviews.** The
|
|
97
|
+
pre-flight status check at `tools/write-reviews.ts` compared against
|
|
98
|
+
the lowercase string `'pending'`, but the backend's
|
|
99
|
+
`ReviewRequestStatus` enum is uppercase (`PENDING`/`APPROVED`/
|
|
100
|
+
`CANCELLED`). The check always failed → `api.cancelReview()` was never
|
|
101
|
+
invoked → every cancel returned "cannot be cancelled — status is
|
|
102
|
+
'PENDING'". Now uses a defensive `.toUpperCase()` comparison so the
|
|
103
|
+
fix survives future backend casing flips. Closes KI-099 / ENG-1438.
|
|
104
|
+
|
|
105
|
+
### Internal
|
|
106
|
+
- Test fixtures in `src/__tests__/unit/test-helpers.ts` and
|
|
107
|
+
`src/__tests__/e2e/fixtures/rockhopper-api-fixtures.ts` updated to use
|
|
108
|
+
the real backend's uppercase `ReviewRequestStatus` enum values. Prior
|
|
109
|
+
fixtures used lowercase, which masked KI-099 by being bug-symmetric
|
|
110
|
+
with the broken code.
|
|
111
|
+
|
|
112
|
+
### Fixed (bundled — same casing-bug class as KI-099)
|
|
113
|
+
- **`file-overview` prompt no longer mis-classifies APPROVED + CANCELLED
|
|
114
|
+
reviews as pending.** `prompts/index.ts:180` filtered with lowercase
|
|
115
|
+
`r.status !== 'approved' && r.status !== 'rejected'`, but
|
|
116
|
+
`ReviewRequestStatus` is uppercase and contains no `'rejected'` value
|
|
117
|
+
(`PENDING`/`APPROVED`/`CANCELLED` only). Result: all non-pending
|
|
118
|
+
reviews were silently counted as pending in the prompt output. Now
|
|
119
|
+
filters with positive intent — `r.status?.toUpperCase() === 'PENDING'`.
|
|
120
|
+
Sibling fix to KI-099; same casing-bug class but in a different
|
|
121
|
+
surface (prompt vs. tool).
|
|
122
|
+
|
|
123
|
+
### Tooling
|
|
124
|
+
- **`npm run lint` now works.** The `lint` script referenced
|
|
125
|
+
`eslint src/` but `eslint` was missing from `devDependencies`, so any
|
|
126
|
+
fresh install silently produced `sh: eslint: command not found`. Added
|
|
127
|
+
`@eslint/js`, `eslint`, `globals`, and `typescript-eslint` as devDeps
|
|
128
|
+
and shipped a flat-config `eslint.config.js` that mirrors the
|
|
129
|
+
`mcp-gateway` repo's setup. Test files relax
|
|
130
|
+
`@typescript-eslint/no-explicit-any` (mock-stub casts) while production
|
|
131
|
+
code keeps the rule on. Lint is clean across `src/`.
|
|
132
|
+
|
|
133
|
+
## [0.6.0] — 2026-05-13
|
|
134
|
+
|
|
135
|
+
> Released to npm as `0.6.0`. The release branch was prepared with
|
|
136
|
+
> `package.json` at `0.5.0` (set in [PR #37](https://github.com/Rockhopper-Co/mcp-server/pull/37)),
|
|
137
|
+
> but the `release:minor` script ran on top of that and produced `0.6.0`
|
|
138
|
+
> via `npm version minor`. No code differs between the `0.5.0` manifest
|
|
139
|
+
> and the `0.6.0` published artifact — the bump is purely a version-string
|
|
140
|
+
> change. Content below is what shipped.
|
|
8
141
|
|
|
9
142
|
### Changed
|
|
10
143
|
- **`resources/list` no longer enumerates per-file instances.** The 4
|
package/README.md
CHANGED
|
@@ -6,17 +6,41 @@ MCP (Model Context Protocol) server for Rockhopper. Lets AI tools like Claude, C
|
|
|
6
6
|
|
|
7
7
|
- Node.js 18+
|
|
8
8
|
- A Rockhopper account with at least one enrolled file
|
|
9
|
-
- A Personal Access Token
|
|
9
|
+
- (Optional) A Personal Access Token — only required for headless / CI / scripted setups
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Authentication
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
The server supports two auth modes. **OAuth (recommended)** is the default — no token to copy and paste. **PAT** stays available for headless scenarios.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
### OAuth (recommended)
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
On first launch, the server prints a short verification code to stderr and a URL to visit. Sign in once in your browser — the resulting bearer token is stored in your OS keychain (Keychain on macOS, Credential Manager on Windows, libsecret on Linux). Subsequent launches pick the token up silently.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Nothing to configure — just launch the server with no `ROCKHOPPER_TOKEN` set:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx @rockhopper-co/mcp-server
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
You'll see (on stderr):
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
Rockhopper — sign in to authorize this MCP client.
|
|
29
|
+
Open: https://app.rockhopper.co/device?user_code=ABCD2345
|
|
30
|
+
(or visit https://app.rockhopper.co/device and enter code: ABCD2345)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Tokens default to a 60-minute lifetime. When yours expires, the next launch silently re-runs the device flow.
|
|
34
|
+
|
|
35
|
+
> **Linux**: requires `libsecret` to be installed (`apt-get install libsecret-1-dev` on Debian/Ubuntu, `dnf install libsecret` on Fedora). If unavailable, fall back to the PAT path below.
|
|
36
|
+
|
|
37
|
+
### Personal Access Token (headless / CI)
|
|
38
|
+
|
|
39
|
+
For non-interactive setups, generate a PAT in the Rockhopper web app under **Settings > Personal Access Tokens** (`read-only` or `read-write` scope) and set it as `ROCKHOPPER_TOKEN`. PATs take precedence over OAuth — if `ROCKHOPPER_TOKEN` is set, the device-grant flow is skipped.
|
|
40
|
+
|
|
41
|
+
## Setup
|
|
42
|
+
|
|
43
|
+
### 1. Install
|
|
20
44
|
|
|
21
45
|
```bash
|
|
22
46
|
npm install -g @rockhopper-co/mcp-server
|
|
@@ -28,7 +52,7 @@ Or run directly with npx:
|
|
|
28
52
|
npx @rockhopper-co/mcp-server
|
|
29
53
|
```
|
|
30
54
|
|
|
31
|
-
###
|
|
55
|
+
### 2. Configure your AI tool
|
|
32
56
|
|
|
33
57
|
#### Claude Desktop / Claude Code
|
|
34
58
|
|
|
@@ -41,7 +65,6 @@ Add to your MCP config (`~/.claude/mcp.json` or Claude Desktop settings):
|
|
|
41
65
|
"command": "npx",
|
|
42
66
|
"args": ["-y", "@rockhopper-co/mcp-server"],
|
|
43
67
|
"env": {
|
|
44
|
-
"ROCKHOPPER_TOKEN": "rh_pat_your_token_here",
|
|
45
68
|
"ROCKHOPPER_API_URL": "https://api.rockhopper.co"
|
|
46
69
|
}
|
|
47
70
|
}
|
|
@@ -49,6 +72,8 @@ Add to your MCP config (`~/.claude/mcp.json` or Claude Desktop settings):
|
|
|
49
72
|
}
|
|
50
73
|
```
|
|
51
74
|
|
|
75
|
+
Leave `ROCKHOPPER_TOKEN` out to use OAuth (recommended). Set it if you want PAT auth instead.
|
|
76
|
+
|
|
52
77
|
#### Cursor
|
|
53
78
|
|
|
54
79
|
Add to `.cursor/mcp.json` in your project:
|
|
@@ -60,7 +85,6 @@ Add to `.cursor/mcp.json` in your project:
|
|
|
60
85
|
"command": "npx",
|
|
61
86
|
"args": ["-y", "@rockhopper-co/mcp-server"],
|
|
62
87
|
"env": {
|
|
63
|
-
"ROCKHOPPER_TOKEN": "rh_pat_your_token_here",
|
|
64
88
|
"ROCKHOPPER_API_URL": "https://api.rockhopper.co"
|
|
65
89
|
}
|
|
66
90
|
}
|
|
@@ -72,7 +96,7 @@ Add to `.cursor/mcp.json` in your project:
|
|
|
72
96
|
|
|
73
97
|
| Variable | Required | Default | Description |
|
|
74
98
|
|----------|----------|---------|-------------|
|
|
75
|
-
| `ROCKHOPPER_TOKEN` |
|
|
99
|
+
| `ROCKHOPPER_TOKEN` | No | — | Personal Access Token (starts with `rh_pat_`). When unset, OAuth device-grant flow runs on first launch. |
|
|
76
100
|
| `ROCKHOPPER_API_URL` | No | `https://api.rockhopper.co` | Rockhopper API base URL |
|
|
77
101
|
|
|
78
102
|
## Postman
|
package/dist/api-client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CellHistoryEntry, EnrolledFile, FileChat, FileVersion, ReviewActivity, ReviewRequest, Team, UnattributedChange, UserSummary } from './types.js';
|
|
1
|
+
import type { CellHistoryEntry, EnrolledFile, FileChat, FileVersion, PaginatedUnattributedResponse, ReviewActivity, ReviewRequest, Team, UnattributedChange, UserSummary } from './types.js';
|
|
2
2
|
export interface ApiClientConfig {
|
|
3
3
|
baseUrl: string;
|
|
4
4
|
token: string;
|
|
@@ -7,15 +7,32 @@ export declare class ApiClient {
|
|
|
7
7
|
private readonly baseUrl;
|
|
8
8
|
private readonly token;
|
|
9
9
|
constructor(config: ApiClientConfig);
|
|
10
|
+
/**
|
|
11
|
+
* KI-096: optional `responseSchema` validates the response shape with
|
|
12
|
+
* zod. When supplied, drift between backend's actual response and the
|
|
13
|
+
* mcp-server's declared type fails LOUDLY with a `ZodError` (wrapped
|
|
14
|
+
* here in an Error with the path that drifted) instead of silently
|
|
15
|
+
* rendering `undefined` in tool formatters. Opt-in per call site so
|
|
16
|
+
* existing methods stay untouched until a sweep migrates them.
|
|
17
|
+
*/
|
|
10
18
|
private request;
|
|
11
19
|
getMe(): Promise<UserSummary>;
|
|
12
20
|
getTeam(teamId: number): Promise<Team>;
|
|
13
21
|
listEnrolledFiles(params?: {
|
|
14
22
|
search?: string;
|
|
23
|
+
matchIn?: 'name' | 'comments' | 'versions' | 'all';
|
|
15
24
|
}): Promise<EnrolledFile[]>;
|
|
16
25
|
getEnrolledFile(fileMsId: string): Promise<EnrolledFile>;
|
|
17
26
|
getFileVersions(fileMsId: string): Promise<FileVersion[]>;
|
|
18
27
|
getFileVersion(versionInternalId: number): Promise<FileVersion>;
|
|
28
|
+
/**
|
|
29
|
+
* KI-096: passes `?format=mcp` to opt into the backend's normalized
|
|
30
|
+
* projection `{versionId, value, changedBy, changedAt}` (added by
|
|
31
|
+
* backend PR #478). Default `format` (omitted) returns the raw CTE
|
|
32
|
+
* row shape the frontend cell-history popover consumes — we never
|
|
33
|
+
* call that path. Zod-parses the response so future drift between
|
|
34
|
+
* backend and mcp-server contracts fails loudly.
|
|
35
|
+
*/
|
|
19
36
|
getCellHistory(fileMsId: string, sheetName: string, cellAddress: string): Promise<CellHistoryEntry[]>;
|
|
20
37
|
getFileComments(fileMsId: string): Promise<FileChat[]>;
|
|
21
38
|
getComment(chatId: number): Promise<FileChat>;
|
|
@@ -29,6 +46,14 @@ export declare class ApiClient {
|
|
|
29
46
|
message: string;
|
|
30
47
|
versionInternalId: number;
|
|
31
48
|
}): Promise<FileChat>;
|
|
49
|
+
/**
|
|
50
|
+
* KI-096: zod-parses the response. Backend PR #478 fixed
|
|
51
|
+
* `PATCH /file-chat/:chatId` to return the updated entity (was
|
|
52
|
+
* UpdateResult) AND to persist `resolved` (was silently dropped).
|
|
53
|
+
* The schema check pins both fixes — if either regresses, the parse
|
|
54
|
+
* fails with a clear message instead of the formatter rendering
|
|
55
|
+
* `Comment undefined marked as resolved.`
|
|
56
|
+
*/
|
|
32
57
|
resolveComment(chatId: number): Promise<FileChat>;
|
|
33
58
|
getReviewsForVersion(versionId: number): Promise<ReviewRequest[]>;
|
|
34
59
|
getReviewsForLatestVersion(fileMsId: string): Promise<ReviewRequest[]>;
|
|
@@ -43,10 +68,28 @@ export declare class ApiClient {
|
|
|
43
68
|
approveReview(reviewId: number, body: {
|
|
44
69
|
notes?: string;
|
|
45
70
|
}): Promise<ReviewRequest>;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Sheet-filtered unattributed changes. Returns ALL rows for the given
|
|
73
|
+
* sheet on the file (no pagination — sheet filter inherently bounds
|
|
74
|
+
* result size). Use this when the caller already knows which sheet to
|
|
75
|
+
* inspect; use {@link getUnattributedChangesPaginated} for the file-wide
|
|
76
|
+
* view.
|
|
77
|
+
*/
|
|
78
|
+
getUnattributedChangesBySheet(fileMsId: string, sheetName: string): Promise<UnattributedChange[]>;
|
|
79
|
+
/**
|
|
80
|
+
* Cursor-paginated file-wide unattributed changes (KI-097).
|
|
81
|
+
*
|
|
82
|
+
* Hits the non-shadowable `GET /unattributed-changes/paginated/:fileMsId`
|
|
83
|
+
* route added by backend PR #475 (KI-102). The legacy `:fileMsId/v2`
|
|
84
|
+
* route is shadowed by `:fileMsId/:sheetName` route ordering and returns
|
|
85
|
+
* an empty array; do not call it from here.
|
|
86
|
+
*
|
|
87
|
+
* Pass `cursor` returned by a previous call to fetch the next page.
|
|
88
|
+
* Snapshot TTL is 30 minutes — older cursors cause the backend to return
|
|
89
|
+
* HTTP 410 GONE with `{ resyncRequired: { code: 'SNAPSHOT_EXPIRED' } }`,
|
|
90
|
+
* which surfaces here as a thrown RockhopperApiError.
|
|
91
|
+
*/
|
|
92
|
+
getUnattributedChangesPaginated(fileMsId: string, cursor?: string): Promise<PaginatedUnattributedResponse>;
|
|
50
93
|
createVersion(body: {
|
|
51
94
|
enrolledFileMsId: string;
|
|
52
95
|
version: {
|
|
@@ -60,6 +103,14 @@ export declare class ApiClient {
|
|
|
60
103
|
description: string;
|
|
61
104
|
}): Promise<FileVersion>;
|
|
62
105
|
cancelReview(reviewId: number): Promise<ReviewRequest>;
|
|
106
|
+
/**
|
|
107
|
+
* KI-096: zod-parses the response. Backend PR #478 fixed
|
|
108
|
+
* `PATCH /enrolled-files/:fileMsId` to return the updated entity
|
|
109
|
+
* (was UpdateResult typed as Promise<any>). The schema check pins
|
|
110
|
+
* the entity contract — if it regresses, the parse fails with a
|
|
111
|
+
* clear message instead of `rename_file` rendering
|
|
112
|
+
* `File renamed to 'undefined' (id: undefined).`
|
|
113
|
+
*/
|
|
63
114
|
updateEnrolledFile(fileMsId: string, body: {
|
|
64
115
|
name?: string;
|
|
65
116
|
}): Promise<EnrolledFile>;
|
package/dist/api-client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,6BAA6B,EAC7B,cAAc,EACd,aAAa,EACb,IAAI,EACJ,kBAAkB,EAClB,WAAW,EACZ,MAAM,YAAY,CAAC;AAOpB,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,MAAM,EAAE,eAAe;IAKnC;;;;;;;OAOG;YACW,OAAO;IA2Cf,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC;IAM7B,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMtC,iBAAiB,CAAC,MAAM,CAAC,EAAE;QAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,KAAK,CAAC;KACpD,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAUrB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAMxD,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAIzD,cAAc,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAMrE;;;;;;;OAOG;IACG,cAAc,CAClB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAexB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAItD,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAI7C,aAAa,CAAC,IAAI,EAAE;QACxB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,iBAAiB,EAAE,MAAM,CAAC;KAC3B,GAAG,OAAO,CAAC,QAAQ,CAAC;IAOf,cAAc,CAClB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE,GACnD,OAAO,CAAC,QAAQ,CAAC;IAOpB;;;;;;;OAOG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAajD,oBAAoB,CACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,EAAE,CAAC;IAMrB,0BAA0B,CAC9B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,EAAE,CAAC;IAMrB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAInD,mBAAmB,CACvB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,EAAE,CAAC;IAMtB,mBAAmB,CAAC,IAAI,EAAE;QAC9B,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,EAAE,CAAC;KACvB,GAAG,OAAO,CAAC,aAAa,CAAC;IAOpB,aAAa,CACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GACvB,OAAO,CAAC,aAAa,CAAC;IAYzB;;;;;;OAMG;IACG,6BAA6B,CACjC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAOhC;;;;;;;;;;;;OAYG;IACG,+BAA+B,CACnC,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,6BAA6B,CAAC;IAQnC,aAAa,CAAC,IAAI,EAAE;QACxB,gBAAgB,EAAE,MAAM,CAAC;QACzB,OAAO,EAAE;YACP,YAAY,EAAE,MAAM,CAAC;YACrB,YAAY,EAAE,MAAM,CAAC;YACrB,YAAY,EAAE,MAAM,CAAC;YACrB,WAAW,EAAE,MAAM,CAAC;SACrB,CAAC;KACH,GAAG,OAAO,CAAC,WAAW,CAAC;IAOlB,cAAc,CAClB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,GAC5B,OAAO,CAAC,WAAW,CAAC;IAYjB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAS5D;;;;;;;OAOG;IACG,kBAAkB,CACtB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GACtB,OAAO,CAAC,YAAY,CAAC;CAUzB"}
|
package/dist/api-client.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CellHistoryEntryArraySchema, EnrolledFileSchema, FileChatSchema, } from './zod-schemas.js';
|
|
1
2
|
export class ApiClient {
|
|
2
3
|
baseUrl;
|
|
3
4
|
token;
|
|
@@ -5,7 +6,15 @@ export class ApiClient {
|
|
|
5
6
|
this.baseUrl = config.baseUrl.replace(/\/+$/, '');
|
|
6
7
|
this.token = config.token;
|
|
7
8
|
}
|
|
8
|
-
|
|
9
|
+
/**
|
|
10
|
+
* KI-096: optional `responseSchema` validates the response shape with
|
|
11
|
+
* zod. When supplied, drift between backend's actual response and the
|
|
12
|
+
* mcp-server's declared type fails LOUDLY with a `ZodError` (wrapped
|
|
13
|
+
* here in an Error with the path that drifted) instead of silently
|
|
14
|
+
* rendering `undefined` in tool formatters. Opt-in per call site so
|
|
15
|
+
* existing methods stay untouched until a sweep migrates them.
|
|
16
|
+
*/
|
|
17
|
+
async request(path, init, responseSchema) {
|
|
9
18
|
const url = `${this.baseUrl}${path}`;
|
|
10
19
|
const response = await fetch(url, {
|
|
11
20
|
...init,
|
|
@@ -19,7 +28,21 @@ export class ApiClient {
|
|
|
19
28
|
const body = await response.text().catch(() => '');
|
|
20
29
|
throw new Error(`Rockhopper API ${response.status}: ${response.statusText} — ${body}`);
|
|
21
30
|
}
|
|
22
|
-
|
|
31
|
+
const json = await response.json();
|
|
32
|
+
if (responseSchema) {
|
|
33
|
+
const parsed = responseSchema.safeParse(json);
|
|
34
|
+
if (!parsed.success) {
|
|
35
|
+
// Surface a useful diagnostic — the formatter would otherwise show
|
|
36
|
+
// `undefined`, hiding the contract break. Include the path of the
|
|
37
|
+
// first issue so the cause is obvious from the error message.
|
|
38
|
+
const first = parsed.error.issues[0];
|
|
39
|
+
throw new Error(`Rockhopper API response failed schema check at ${path}: ` +
|
|
40
|
+
`${first?.path.join('.') || '<root>'} — ${first?.message} ` +
|
|
41
|
+
`(${parsed.error.issues.length} issue(s) total)`);
|
|
42
|
+
}
|
|
43
|
+
return parsed.data;
|
|
44
|
+
}
|
|
45
|
+
return json;
|
|
23
46
|
}
|
|
24
47
|
// --- Users ---
|
|
25
48
|
async getMe() {
|
|
@@ -34,6 +57,8 @@ export class ApiClient {
|
|
|
34
57
|
const query = new URLSearchParams();
|
|
35
58
|
if (params?.search)
|
|
36
59
|
query.set('search', params.search);
|
|
60
|
+
if (params?.matchIn)
|
|
61
|
+
query.set('matchIn', params.matchIn);
|
|
37
62
|
const qs = query.toString();
|
|
38
63
|
return this.request(`/enrolled-files${qs ? `?${qs}` : ''}`);
|
|
39
64
|
}
|
|
@@ -47,9 +72,21 @@ export class ApiClient {
|
|
|
47
72
|
async getFileVersion(versionInternalId) {
|
|
48
73
|
return this.request(`/file-versions/file/version/${versionInternalId}`);
|
|
49
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* KI-096: passes `?format=mcp` to opt into the backend's normalized
|
|
77
|
+
* projection `{versionId, value, changedBy, changedAt}` (added by
|
|
78
|
+
* backend PR #478). Default `format` (omitted) returns the raw CTE
|
|
79
|
+
* row shape the frontend cell-history popover consumes — we never
|
|
80
|
+
* call that path. Zod-parses the response so future drift between
|
|
81
|
+
* backend and mcp-server contracts fails loudly.
|
|
82
|
+
*/
|
|
50
83
|
async getCellHistory(fileMsId, sheetName, cellAddress) {
|
|
51
|
-
const query = new URLSearchParams({
|
|
52
|
-
|
|
84
|
+
const query = new URLSearchParams({
|
|
85
|
+
cell: cellAddress,
|
|
86
|
+
sheetName,
|
|
87
|
+
format: 'mcp',
|
|
88
|
+
});
|
|
89
|
+
return this.request(`/file-versions/file/${fileMsId}/cell-history?${query}`, undefined, CellHistoryEntryArraySchema);
|
|
53
90
|
}
|
|
54
91
|
// --- File Chat (Comments) ---
|
|
55
92
|
async getFileComments(fileMsId) {
|
|
@@ -70,11 +107,19 @@ export class ApiClient {
|
|
|
70
107
|
body: JSON.stringify(body),
|
|
71
108
|
});
|
|
72
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* KI-096: zod-parses the response. Backend PR #478 fixed
|
|
112
|
+
* `PATCH /file-chat/:chatId` to return the updated entity (was
|
|
113
|
+
* UpdateResult) AND to persist `resolved` (was silently dropped).
|
|
114
|
+
* The schema check pins both fixes — if either regresses, the parse
|
|
115
|
+
* fails with a clear message instead of the formatter rendering
|
|
116
|
+
* `Comment undefined marked as resolved.`
|
|
117
|
+
*/
|
|
73
118
|
async resolveComment(chatId) {
|
|
74
119
|
return this.request(`/file-chat/${chatId}`, {
|
|
75
120
|
method: 'PATCH',
|
|
76
121
|
body: JSON.stringify({ resolved: true }),
|
|
77
|
-
});
|
|
122
|
+
}, FileChatSchema);
|
|
78
123
|
}
|
|
79
124
|
// --- Reviews ---
|
|
80
125
|
async getReviewsForVersion(versionId) {
|
|
@@ -102,11 +147,33 @@ export class ApiClient {
|
|
|
102
147
|
});
|
|
103
148
|
}
|
|
104
149
|
// --- Unattributed Changes ---
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Sheet-filtered unattributed changes. Returns ALL rows for the given
|
|
152
|
+
* sheet on the file (no pagination — sheet filter inherently bounds
|
|
153
|
+
* result size). Use this when the caller already knows which sheet to
|
|
154
|
+
* inspect; use {@link getUnattributedChangesPaginated} for the file-wide
|
|
155
|
+
* view.
|
|
156
|
+
*/
|
|
157
|
+
async getUnattributedChangesBySheet(fileMsId, sheetName) {
|
|
158
|
+
const path = `/unattributed-changes/${fileMsId}/${encodeURIComponent(sheetName)}`;
|
|
159
|
+
return this.request(path);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Cursor-paginated file-wide unattributed changes (KI-097).
|
|
163
|
+
*
|
|
164
|
+
* Hits the non-shadowable `GET /unattributed-changes/paginated/:fileMsId`
|
|
165
|
+
* route added by backend PR #475 (KI-102). The legacy `:fileMsId/v2`
|
|
166
|
+
* route is shadowed by `:fileMsId/:sheetName` route ordering and returns
|
|
167
|
+
* an empty array; do not call it from here.
|
|
168
|
+
*
|
|
169
|
+
* Pass `cursor` returned by a previous call to fetch the next page.
|
|
170
|
+
* Snapshot TTL is 30 minutes — older cursors cause the backend to return
|
|
171
|
+
* HTTP 410 GONE with `{ resyncRequired: { code: 'SNAPSHOT_EXPIRED' } }`,
|
|
172
|
+
* which surfaces here as a thrown RockhopperApiError.
|
|
173
|
+
*/
|
|
174
|
+
async getUnattributedChangesPaginated(fileMsId, cursor) {
|
|
175
|
+
const qs = cursor ? `?cursor=${encodeURIComponent(cursor)}` : '';
|
|
176
|
+
const path = `/unattributed-changes/paginated/${fileMsId}${qs}`;
|
|
110
177
|
return this.request(path);
|
|
111
178
|
}
|
|
112
179
|
// --- Version lifecycle ---
|
|
@@ -130,11 +197,19 @@ export class ApiClient {
|
|
|
130
197
|
});
|
|
131
198
|
}
|
|
132
199
|
// --- File metadata update ---
|
|
200
|
+
/**
|
|
201
|
+
* KI-096: zod-parses the response. Backend PR #478 fixed
|
|
202
|
+
* `PATCH /enrolled-files/:fileMsId` to return the updated entity
|
|
203
|
+
* (was UpdateResult typed as Promise<any>). The schema check pins
|
|
204
|
+
* the entity contract — if it regresses, the parse fails with a
|
|
205
|
+
* clear message instead of `rename_file` rendering
|
|
206
|
+
* `File renamed to 'undefined' (id: undefined).`
|
|
207
|
+
*/
|
|
133
208
|
async updateEnrolledFile(fileMsId, body) {
|
|
134
209
|
return this.request(`/enrolled-files/${fileMsId}`, {
|
|
135
210
|
method: 'PATCH',
|
|
136
211
|
body: JSON.stringify(body),
|
|
137
|
-
});
|
|
212
|
+
}, EnrolledFileSchema);
|
|
138
213
|
}
|
|
139
214
|
}
|
|
140
215
|
//# sourceMappingURL=api-client.js.map
|
package/dist/api-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ENG-1444 / KI-081 — RFC 8628 device-grant client.
|
|
3
|
+
*
|
|
4
|
+
* Talks to the backend's `POST /auth/device/{code,token}` endpoints
|
|
5
|
+
* (introduced in ENG-1384 PR 1, `Rockhopper-Co/backend#473`). Used by
|
|
6
|
+
* the mcp-server CLI when no PAT env var is set and no OAuth bundle
|
|
7
|
+
* is stored in the OS keychain.
|
|
8
|
+
*
|
|
9
|
+
* Surfaces a single entrypoint, `runDeviceGrantFlow`, that:
|
|
10
|
+
*
|
|
11
|
+
* 1. Calls `/auth/device/code` to get a (deviceCode, userCode) pair.
|
|
12
|
+
* 2. Emits the user-facing `userCode` + verification URI to stderr
|
|
13
|
+
* (LLM clients pick this up via stdout's stderr passthrough).
|
|
14
|
+
* 3. Polls `/auth/device/token` at the server-specified interval,
|
|
15
|
+
* respecting RFC 8628 § 3.5 `slow_down` (bumps interval +5s) and
|
|
16
|
+
* `authorization_pending` (continues polling).
|
|
17
|
+
* 4. Resolves with the access-token bundle on success, rejects on
|
|
18
|
+
* `access_denied` / `expired_token` / network failure.
|
|
19
|
+
*
|
|
20
|
+
* No external HTTP dependency — uses globalThis.fetch (Node 18+).
|
|
21
|
+
* `fetchImpl` + `sleep` + `onUserCode` are injectable for tests.
|
|
22
|
+
*/
|
|
23
|
+
export interface DeviceCodeResponse {
|
|
24
|
+
deviceCode: string;
|
|
25
|
+
userCode: string;
|
|
26
|
+
verificationUri: string;
|
|
27
|
+
verificationUriComplete: string;
|
|
28
|
+
expiresIn: number;
|
|
29
|
+
interval: number;
|
|
30
|
+
}
|
|
31
|
+
export interface DeviceTokenSuccess {
|
|
32
|
+
accessToken: string;
|
|
33
|
+
tokenType: 'Bearer';
|
|
34
|
+
expiresIn: number;
|
|
35
|
+
refreshToken?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface DeviceGrantFlowOptions {
|
|
38
|
+
baseUrl: string;
|
|
39
|
+
clientId: string;
|
|
40
|
+
/** Override for tests. Defaults to globalThis.fetch. */
|
|
41
|
+
fetchImpl?: typeof fetch;
|
|
42
|
+
/** Override for tests. Defaults to setTimeout-based sleep. */
|
|
43
|
+
sleep?: (ms: number) => Promise<void>;
|
|
44
|
+
/** Called once with the user code + verification URI. Defaults to stderr. */
|
|
45
|
+
onUserCode?: (info: {
|
|
46
|
+
userCode: string;
|
|
47
|
+
verificationUri: string;
|
|
48
|
+
verificationUriComplete: string;
|
|
49
|
+
}) => void;
|
|
50
|
+
}
|
|
51
|
+
export declare class DeviceGrantError extends Error {
|
|
52
|
+
readonly code: 'access_denied' | 'expired_token' | 'network_error' | 'unknown';
|
|
53
|
+
constructor(code: 'access_denied' | 'expired_token' | 'network_error' | 'unknown', message: string);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Issue a fresh device code + user code pair.
|
|
57
|
+
*/
|
|
58
|
+
export declare function requestDeviceCode(opts: Pick<DeviceGrantFlowOptions, 'baseUrl' | 'clientId' | 'fetchImpl'>): Promise<DeviceCodeResponse>;
|
|
59
|
+
/**
|
|
60
|
+
* Internal: one polling round-trip. Returns the token bundle on
|
|
61
|
+
* success, or a sentinel describing why the server isn't ready yet.
|
|
62
|
+
*/
|
|
63
|
+
export declare function pollOnce(opts: Pick<DeviceGrantFlowOptions, 'baseUrl' | 'clientId' | 'fetchImpl'>, deviceCode: string): Promise<{
|
|
64
|
+
kind: 'success';
|
|
65
|
+
bundle: DeviceTokenSuccess;
|
|
66
|
+
} | {
|
|
67
|
+
kind: 'pending';
|
|
68
|
+
} | {
|
|
69
|
+
kind: 'slow_down';
|
|
70
|
+
}>;
|
|
71
|
+
/**
|
|
72
|
+
* Full flow — request code, print to stderr, poll until success or
|
|
73
|
+
* fatal error. Returns the access-token bundle on success.
|
|
74
|
+
*
|
|
75
|
+
* `interval` (seconds, server-supplied) becomes the poll cadence.
|
|
76
|
+
* `slow_down` responses bump the interval by RFC's recommended +5s.
|
|
77
|
+
* Total wall time is bounded by the server-supplied `expiresIn`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function runDeviceGrantFlow(opts: DeviceGrantFlowOptions): Promise<DeviceTokenSuccess>;
|
|
80
|
+
//# sourceMappingURL=device-grant-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-grant-client.d.ts","sourceRoot":"","sources":["../../src/auth/device-grant-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,uBAAuB,EAAE,MAAM,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,QAAQ,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,wDAAwD;IACxD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,6EAA6E;IAC7E,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,eAAe,EAAE,MAAM,CAAC;QACxB,uBAAuB,EAAE,MAAM,CAAC;KACjC,KAAK,IAAI,CAAC;CACZ;AAED,qBAAa,gBAAiB,SAAQ,KAAK;aAEvB,IAAI,EAChB,eAAe,GACf,eAAe,GACf,eAAe,GACf,SAAS;gBAJG,IAAI,EAChB,eAAe,GACf,eAAe,GACf,eAAe,GACf,SAAS,EACb,OAAO,EAAE,MAAM;CAKlB;AAkBD;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,IAAI,CAAC,sBAAsB,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC,GACvE,OAAO,CAAC,kBAAkB,CAAC,CAuB7B;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,IAAI,CAAC,sBAAsB,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC,EACxE,UAAU,EAAE,MAAM,GACjB,OAAO,CACN;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CACxB,CAsDA;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CAgC7B"}
|