@m-kopa/launchpad-cli 0.26.1 → 0.27.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 CHANGED
@@ -6,6 +6,51 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
  This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html);
7
7
  pre-1.0 minor bumps may carry breaking changes per ADR 0005.
8
8
 
9
+ ## 0.27.0 — 2026-06-12
10
+
11
+ `launchpad login` moves onto the platform's auth gateway (sp-cli7kq
12
+ Task 3, ADR 0026). Dual-path: gateway first, legacy Cloudflare Access
13
+ as a deprecated fallback for the duration of the dual-auth window.
14
+
15
+ ### Added
16
+
17
+ - **Gateway login is the default.** `launchpad login` now authenticates
18
+ against the auth gateway's `cli-session` grant
19
+ (`auth.launchpad.m-kopa.us`): loopback Authorization-Code + PKCE into
20
+ the Entra SSO, yielding a short-lived (15-minute) RS256 access token
21
+ plus an opaque refresh token that **rotates on every refresh**. No
22
+ discovery, no dynamic client registration — the endpoints are fixed.
23
+ Sessions persist as a new `version: 2` shape in
24
+ `~/.launchpad/session.json` (same location, same `0600` mode); the
25
+ rotated refresh token is persisted atomically (write-rename) **before**
26
+ the new access token is used, so a crash can never strand the session
27
+ on a rotated-away token.
28
+ - **Server-side logout.** `launchpad logout` now revokes gateway
29
+ sessions at the gateway (`POST /__cli_logout`) before clearing the
30
+ local file: refresh dies immediately; in-flight access tokens expire
31
+ within 15 minutes. Offline-safe — if the gateway is unreachable the
32
+ local session is still cleared with a warning, exit code `0`.
33
+ - **Kill-switch.** `LAUNCHPAD_AUTH_LEGACY=1` forces the legacy
34
+ Cloudflare Access login outright (no gateway attempt).
35
+ `LAUNCHPAD_AUTH_GATEWAY_URL` overrides the gateway base URL for
36
+ tests/previews.
37
+ - Gateway `429` rate-limit responses are honoured: short `Retry-After`
38
+ waits are absorbed with a single retry; longer ones surface a clear
39
+ "session intact, retry shortly" error without burning the refresh
40
+ token.
41
+
42
+ ### Changed
43
+
44
+ - A revoked / idle-expired (7 days) / capped (30 days) gateway session
45
+ now clears itself locally and prompts ``run `launchpad login` ``;
46
+ authenticated verbs keep the exact exit-`3` contract for auth
47
+ failures.
48
+ - The legacy Cloudflare Access login path is **deprecated but intact**
49
+ — it runs automatically (with a notice) when the gateway flow fails,
50
+ and will be deleted after the dual-auth window closes (AC-DECOM).
51
+ - `launchpad update`'s installer-bearer channel auth (ADR 0023) is
52
+ untouched by all of the above.
53
+
9
54
  ## 0.26.1 — 2026-06-11
10
55
 
11
56
  Two `launchpad status` UX faults found live by the owner (fast-track,
@@ -1,10 +1,14 @@
1
- import { type CliSession } from "./session.js";
1
+ import { type AnyCliSession, type CliSession } from "./session.js";
2
2
  export declare class LoginRequiredError extends Error {
3
3
  readonly code: "login_required";
4
4
  }
5
5
  /** Refresh-window buffer: refresh `refreshSkewMs` before expiry
6
6
  * so a request fired right at the edge doesn't hit a 401. */
7
7
  export declare const REFRESH_SKEW_MS = 30000;
8
+ /** A gateway 429 with Retry-After at or under this is absorbed
9
+ * in-process (wait, then retry the refresh ONCE). Anything longer is
10
+ * surfaced to the user instead of silently stalling a verb. */
11
+ export declare const MAX_ABSORBED_RETRY_AFTER_SEC = 10;
8
12
  export interface LoginOptions {
9
13
  readonly botUrl: string;
10
14
  readonly sessionPath: string;
@@ -32,8 +36,8 @@ export declare function login(opts: LoginOptions): Promise<CliSession>;
32
36
  * `now` is injected for tests so we can simulate expiry without
33
37
  * mocking the system clock.
34
38
  */
35
- export declare function getValidAccessToken(sessionPath: string, fetcher?: typeof fetch, now?: () => number): Promise<{
39
+ export declare function getValidAccessToken(sessionPath: string, fetcher?: typeof fetch, now?: () => number, sleep?: (ms: number) => Promise<void>): Promise<{
36
40
  accessToken: string;
37
- session: CliSession;
41
+ session: AnyCliSession;
38
42
  }>;
39
43
  //# sourceMappingURL=flow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../../src/auth/flow.ts"],"names":[],"mappings":"AAwCA,OAAO,EAGL,KAAK,UAAU,EAEhB,MAAM,cAAc,CAAC;AAGtB,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAG,gBAAgB,CAAU;CAC3C;AAED;8DAC8D;AAC9D,eAAO,MAAM,eAAe,QAAS,CAAC;AAEtC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;sDAEkD;IAClD,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,kCAAkC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IAChC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED;;;;GAIG;AACH,wBAAsB,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CA8DnE;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,OAAO,KAAa,EAC7B,GAAG,GAAE,MAAM,MAAiB,GAC3B,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,UAAU,CAAA;CAAE,CAAC,CAiDvD"}
1
+ {"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../../src/auth/flow.ts"],"names":[],"mappings":"AAwCA,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,UAAU,EAGhB,MAAM,cAAc,CAAC;AAQtB,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAG,gBAAgB,CAAU;CAC3C;AAED;8DAC8D;AAC9D,eAAO,MAAM,eAAe,QAAS,CAAC;AAEtC;;gEAEgE;AAChE,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAE/C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;sDAEkD;IAClD,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,kCAAkC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IAChC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED;;;;GAIG;AACH,wBAAsB,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CA8DnE;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,OAAO,KAAa,EAC7B,GAAG,GAAE,MAAM,MAAiB,EAC5B,KAAK,GAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CACI,GACtC,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,aAAa,CAAA;CAAE,CAAC,CAuD1D"}
@@ -0,0 +1,76 @@
1
+ import { type GatewayCliSession } from "./session.js";
2
+ export declare const CLI_SESSION_AUTH_PATH = "/__cli_session_auth";
3
+ export declare const CLI_SESSION_TOKEN_PATH = "/__cli_session_token";
4
+ export declare const CLI_LOGOUT_PATH = "/__cli_logout";
5
+ /** The gateway is unreachable or not serving the cli-session grant
6
+ * (404 wrong host / 503 config missing / network error). The login
7
+ * command treats this as "fall back to the legacy flow". */
8
+ export declare class GatewayUnavailableError extends Error {
9
+ readonly code: "gateway_unavailable";
10
+ }
11
+ /** Non-2xx from the session-token or logout endpoint. `httpStatus`
12
+ * lets flow.ts distinguish "session dead — re-login" (400/401) from
13
+ * transient server trouble (5xx). */
14
+ export declare class GatewayTokenError extends Error {
15
+ readonly code: "gateway_token_error";
16
+ readonly httpStatus?: number;
17
+ constructor(message: string, httpStatus?: number);
18
+ }
19
+ /** 429 from the gateway. The request was rejected BEFORE the body was
20
+ * read (the gateway rate-limits pre-parse), so a rate-limited refresh
21
+ * has NOT consumed the refresh token — the session is intact and the
22
+ * caller must NOT clear it. */
23
+ export declare class GatewayRateLimitError extends Error {
24
+ readonly code: "gateway_rate_limited";
25
+ readonly retryAfterSec: number;
26
+ constructor(retryAfterSec: number);
27
+ }
28
+ export interface GatewayTokenPair {
29
+ readonly accessToken: string;
30
+ readonly refreshToken: string;
31
+ readonly expiresInSec: number;
32
+ }
33
+ export interface GatewayLoginOptions {
34
+ readonly gatewayUrl: string;
35
+ readonly sessionPath: string;
36
+ /** Receives the auth URL once the localhost server is bound — the
37
+ * CLI verb prints it so the user can copy-paste if the browser
38
+ * doesn't open. */
39
+ readonly onAuthUrl?: (url: string) => void;
40
+ /** Injection points for tests. */
41
+ readonly fetcher?: typeof fetch;
42
+ readonly browserOpener?: (url: string) => Promise<void>;
43
+ }
44
+ /**
45
+ * Run the gateway cli-session login and persist the v2 session.
46
+ *
47
+ * Probes the gateway FIRST (one cheap GET) so an unreachable gateway
48
+ * or a host without the grant fails fast with `GatewayUnavailableError`
49
+ * — before any browser opens — and the verb can fall back to the
50
+ * legacy flow instead of hanging on a callback that will never come.
51
+ */
52
+ export declare function gatewayLogin(opts: GatewayLoginOptions): Promise<GatewayCliSession>;
53
+ /**
54
+ * Refresh-token grant. Returns the new pair — with a ROTATED refresh
55
+ * token the caller MUST persist before first use of the access token
56
+ * (flow.ts owns that ordering). Throws:
57
+ * * GatewayRateLimitError on 429 (token NOT consumed),
58
+ * * GatewayTokenError with httpStatus on any other non-2xx
59
+ * (400/401 = session dead; 5xx = transient),
60
+ * * GatewayTokenError without httpStatus on network failure.
61
+ */
62
+ export declare function refreshGatewayTokens(params: {
63
+ readonly gatewayUrl: string;
64
+ readonly refreshToken: string;
65
+ }, fetcher?: typeof fetch): Promise<GatewayTokenPair>;
66
+ /**
67
+ * `POST /__cli_logout` — server-side revocation. 204 = revoked (or
68
+ * idempotently already-gone). Throws on anything else; `launchpad
69
+ * logout` catches, warns, and clears the local session anyway (logout
70
+ * must work offline).
71
+ */
72
+ export declare function revokeGatewaySession(params: {
73
+ readonly gatewayUrl: string;
74
+ readonly refreshToken: string;
75
+ }, fetcher?: typeof fetch): Promise<void>;
76
+ //# sourceMappingURL=gateway-flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-flow.d.ts","sourceRoot":"","sources":["../../src/auth/gateway-flow.ts"],"names":[],"mappings":"AA0CA,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAC;AAEtB,eAAO,MAAM,qBAAqB,wBAAwB,CAAC;AAC3D,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAC7D,eAAO,MAAM,eAAe,kBAAkB,CAAC;AAE/C;;6DAE6D;AAC7D,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,IAAI,EAAG,qBAAqB,CAAU;CAChD;AAED;;sCAEsC;AACtC,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAG,qBAAqB,CAAU;IAC/C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;gBACjB,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM;CAKjD;AAED;;;gCAGgC;AAChC,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,EAAG,sBAAsB,CAAU;IAChD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;gBACnB,aAAa,EAAE,MAAM;CAOlC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;wBAEoB;IACpB,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,kCAAkC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IAChC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,iBAAiB,CAAC,CAkD5B;AAED;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAAE,EACtE,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,gBAAgB,CAAC,CAS3B;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAAE,EACtE,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,IAAI,CAAC,CAsBf"}
@@ -1,4 +1,5 @@
1
1
  export declare const SESSION_VERSION = 1;
2
+ export declare const GATEWAY_SESSION_VERSION = 2;
2
3
  export interface CliSession {
3
4
  readonly version: typeof SESSION_VERSION;
4
5
  /** OAuth `access_token`. Sent to the bot as `Authorization: Bearer <accessToken>`. */
@@ -27,6 +28,38 @@ export interface CliSession {
27
28
  * by `whoami` (slice 3) for diagnostic display only. */
28
29
  readonly issuedAt: string;
29
30
  }
31
+ /** A `version: 2` session minted by the auth gateway's cli-session
32
+ * grant (ADR 0026, sp-cli7kq). Differences from the legacy shape:
33
+ * no `clientId`/`tokenEndpoint`/`resource` (the gateway endpoints
34
+ * are fixed — no discovery, no RFC 7591 registration), and the
35
+ * refresh token is OPAQUE and ROTATES on every refresh — a stale
36
+ * copy is not just useless, presenting it after rotation revokes
37
+ * the whole session server-side (the replay tripwire). That makes
38
+ * the write-before-use persistence ordering in `flow.ts`
39
+ * load-bearing, not cosmetic. */
40
+ export interface GatewayCliSession {
41
+ readonly version: typeof GATEWAY_SESSION_VERSION;
42
+ /** Discriminator for future non-gateway v2 shapes; always "gateway". */
43
+ readonly kind: "gateway";
44
+ /** RS256 `typ: cli-session` JWT. Sent to the bot as
45
+ * `Authorization: Bearer <accessToken>`. */
46
+ readonly accessToken: string;
47
+ /** Opaque rotating refresh token (`<jti>.<random>`). NEVER reuse a
48
+ * rotated-away value — the gateway treats reuse as theft. */
49
+ readonly refreshToken: string;
50
+ /** Epoch milliseconds at which `accessToken` ceases to be valid. */
51
+ readonly accessTokenExpiresAt: number;
52
+ /** Base URL of the auth gateway that minted this session — refresh
53
+ * and logout go back to the same issuer. */
54
+ readonly gatewayUrl: string;
55
+ /** ISO-8601 UTC timestamp of when this session was written. */
56
+ readonly issuedAt: string;
57
+ }
58
+ /** Either on-disk session shape. Readers that only need the common
59
+ * fields (accessToken / accessTokenExpiresAt / issuedAt) can stay
60
+ * agnostic; refresh + logout must dispatch on `isGatewaySession`. */
61
+ export type AnyCliSession = CliSession | GatewayCliSession;
62
+ export declare function isGatewaySession(s: AnyCliSession): s is GatewayCliSession;
30
63
  export declare class SessionParseError extends Error {
31
64
  readonly code: "session_parse_error";
32
65
  }
@@ -37,13 +70,13 @@ export declare class SessionParseError extends Error {
37
70
  * than to silently re-prompt for login when the storage is
38
71
  * actually broken.
39
72
  */
40
- export declare function readSession(sessionPath: string): Promise<CliSession | null>;
73
+ export declare function readSession(sessionPath: string): Promise<AnyCliSession | null>;
41
74
  /**
42
75
  * Atomic-ish write: writes to `<path>.tmp` then renames into
43
76
  * place. The whole-directory chmod is best-effort (Windows ignores
44
77
  * mode bits) — the per-file mode is the load-bearing one.
45
78
  */
46
- export declare function writeSession(sessionPath: string, session: CliSession): Promise<void>;
79
+ export declare function writeSession(sessionPath: string, session: AnyCliSession): Promise<void>;
47
80
  /**
48
81
  * `launchpad logout`: zero out the session file. Idempotent —
49
82
  * already-absent file is a no-op success. Returns whether a
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/auth/session.ts"],"names":[],"mappings":"AA0BA,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,eAAe,CAAC;IACzC,sFAAsF;IACtF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;qDACiD;IACjD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;wEACoE;IACpE,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC;uCACmC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;6CACyC;IACzC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;;;;;2BAMuB;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;6DACyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAG,qBAAqB,CAAU;CAChD;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAmD5B;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,IAAI,CAAC,CA4Bf;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQxE"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/auth/session.ts"],"names":[],"mappings":"AA2BA,eAAO,MAAM,eAAe,IAAI,CAAC;AACjC,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,eAAe,CAAC;IACzC,sFAAsF;IACtF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;qDACiD;IACjD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;wEACoE;IACpE,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC;uCACmC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;6CACyC;IACzC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;;;;;2BAMuB;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;6DACyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;kCAQkC;AAClC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,OAAO,uBAAuB,CAAC;IACjD,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB;iDAC6C;IAC7C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;kEAC8D;IAC9D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,oEAAoE;IACpE,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC;iDAC6C;IAC7C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;sEAEsE;AACtE,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,iBAAiB,CAAC;AAE3D,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,aAAa,GAAG,CAAC,IAAI,iBAAiB,CAEzE;AAED,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAG,qBAAqB,CAAU;CAChD;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAsD/B;AA0BD;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,IAAI,CAAC,CA4Bf;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQxE"}