@mainwp/control 1.0.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/LICENSE +674 -0
- package/README.md +583 -0
- package/bin/_exit.js +12 -0
- package/bin/dev.js +7 -0
- package/bin/run.js +7 -0
- package/dist/chat/chat-engine.d.ts +213 -0
- package/dist/chat/chat-engine.d.ts.map +1 -0
- package/dist/chat/chat-engine.js +636 -0
- package/dist/chat/chat-engine.js.map +1 -0
- package/dist/chat/index.d.ts +10 -0
- package/dist/chat/index.d.ts.map +1 -0
- package/dist/chat/index.js +14 -0
- package/dist/chat/index.js.map +1 -0
- package/dist/chat/providers/anthropic.d.ts +52 -0
- package/dist/chat/providers/anthropic.d.ts.map +1 -0
- package/dist/chat/providers/anthropic.js +292 -0
- package/dist/chat/providers/anthropic.js.map +1 -0
- package/dist/chat/providers/gemini.d.ts +52 -0
- package/dist/chat/providers/gemini.d.ts.map +1 -0
- package/dist/chat/providers/gemini.js +284 -0
- package/dist/chat/providers/gemini.js.map +1 -0
- package/dist/chat/providers/index.d.ts +19 -0
- package/dist/chat/providers/index.d.ts.map +1 -0
- package/dist/chat/providers/index.js +23 -0
- package/dist/chat/providers/index.js.map +1 -0
- package/dist/chat/providers/local.d.ts +37 -0
- package/dist/chat/providers/local.d.ts.map +1 -0
- package/dist/chat/providers/local.js +130 -0
- package/dist/chat/providers/local.js.map +1 -0
- package/dist/chat/providers/openai-compatible.d.ts +155 -0
- package/dist/chat/providers/openai-compatible.d.ts.map +1 -0
- package/dist/chat/providers/openai-compatible.js +264 -0
- package/dist/chat/providers/openai-compatible.js.map +1 -0
- package/dist/chat/providers/openai.d.ts +24 -0
- package/dist/chat/providers/openai.d.ts.map +1 -0
- package/dist/chat/providers/openai.js +62 -0
- package/dist/chat/providers/openai.js.map +1 -0
- package/dist/chat/providers/openrouter.d.ts +26 -0
- package/dist/chat/providers/openrouter.d.ts.map +1 -0
- package/dist/chat/providers/openrouter.js +65 -0
- package/dist/chat/providers/openrouter.js.map +1 -0
- package/dist/chat/providers/provider-fetch.d.ts +15 -0
- package/dist/chat/providers/provider-fetch.d.ts.map +1 -0
- package/dist/chat/providers/provider-fetch.js +35 -0
- package/dist/chat/providers/provider-fetch.js.map +1 -0
- package/dist/chat/providers/provider.d.ts +214 -0
- package/dist/chat/providers/provider.d.ts.map +1 -0
- package/dist/chat/providers/provider.js +166 -0
- package/dist/chat/providers/provider.js.map +1 -0
- package/dist/chat/providers/sse-reader.d.ts +21 -0
- package/dist/chat/providers/sse-reader.d.ts.map +1 -0
- package/dist/chat/providers/sse-reader.js +48 -0
- package/dist/chat/providers/sse-reader.js.map +1 -0
- package/dist/chat/system-prompt.d.ts +33 -0
- package/dist/chat/system-prompt.d.ts.map +1 -0
- package/dist/chat/system-prompt.js +166 -0
- package/dist/chat/system-prompt.js.map +1 -0
- package/dist/chat/tool-envelope.d.ts +72 -0
- package/dist/chat/tool-envelope.d.ts.map +1 -0
- package/dist/chat/tool-envelope.js +263 -0
- package/dist/chat/tool-envelope.js.map +1 -0
- package/dist/commands/abilities/info.d.ts +21 -0
- package/dist/commands/abilities/info.d.ts.map +1 -0
- package/dist/commands/abilities/info.js +80 -0
- package/dist/commands/abilities/info.js.map +1 -0
- package/dist/commands/abilities/list.d.ts +19 -0
- package/dist/commands/abilities/list.d.ts.map +1 -0
- package/dist/commands/abilities/list.js +98 -0
- package/dist/commands/abilities/list.js.map +1 -0
- package/dist/commands/abilities/run.d.ts +75 -0
- package/dist/commands/abilities/run.d.ts.map +1 -0
- package/dist/commands/abilities/run.js +468 -0
- package/dist/commands/abilities/run.js.map +1 -0
- package/dist/commands/chat.d.ts +54 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +384 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/config/show.d.ts +54 -0
- package/dist/commands/config/show.d.ts.map +1 -0
- package/dist/commands/config/show.js +324 -0
- package/dist/commands/config/show.js.map +1 -0
- package/dist/commands/doctor.d.ts +77 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +412 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/jobs/watch.d.ts +50 -0
- package/dist/commands/jobs/watch.d.ts.map +1 -0
- package/dist/commands/jobs/watch.js +269 -0
- package/dist/commands/jobs/watch.js.map +1 -0
- package/dist/commands/login.d.ts +25 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +165 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/profile/delete.d.ts +22 -0
- package/dist/commands/profile/delete.d.ts.map +1 -0
- package/dist/commands/profile/delete.js +57 -0
- package/dist/commands/profile/delete.js.map +1 -0
- package/dist/commands/profile/list.d.ts +19 -0
- package/dist/commands/profile/list.d.ts.map +1 -0
- package/dist/commands/profile/list.js +53 -0
- package/dist/commands/profile/list.js.map +1 -0
- package/dist/commands/profile/use.d.ts +22 -0
- package/dist/commands/profile/use.d.ts.map +1 -0
- package/dist/commands/profile/use.js +46 -0
- package/dist/commands/profile/use.js.map +1 -0
- package/dist/config/fs-utils.d.ts +14 -0
- package/dist/config/fs-utils.d.ts.map +1 -0
- package/dist/config/fs-utils.js +31 -0
- package/dist/config/fs-utils.js.map +1 -0
- package/dist/config/keychain.d.ts +53 -0
- package/dist/config/keychain.d.ts.map +1 -0
- package/dist/config/keychain.js +175 -0
- package/dist/config/keychain.js.map +1 -0
- package/dist/config/profile-store.d.ts +85 -0
- package/dist/config/profile-store.d.ts.map +1 -0
- package/dist/config/profile-store.js +228 -0
- package/dist/config/profile-store.js.map +1 -0
- package/dist/config/settings.d.ts +71 -0
- package/dist/config/settings.d.ts.map +1 -0
- package/dist/config/settings.js +151 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/core/abilities-executor.d.ts +126 -0
- package/dist/core/abilities-executor.d.ts.map +1 -0
- package/dist/core/abilities-executor.js +264 -0
- package/dist/core/abilities-executor.js.map +1 -0
- package/dist/core/batch-manager.d.ts +113 -0
- package/dist/core/batch-manager.d.ts.map +1 -0
- package/dist/core/batch-manager.js +244 -0
- package/dist/core/batch-manager.js.map +1 -0
- package/dist/core/http-client.d.ts +111 -0
- package/dist/core/http-client.d.ts.map +1 -0
- package/dist/core/http-client.js +329 -0
- package/dist/core/http-client.js.map +1 -0
- package/dist/core/safety-controller.d.ts +114 -0
- package/dist/core/safety-controller.d.ts.map +1 -0
- package/dist/core/safety-controller.js +229 -0
- package/dist/core/safety-controller.js.map +1 -0
- package/dist/hooks/command-not-found.d.ts +12 -0
- package/dist/hooks/command-not-found.d.ts.map +1 -0
- package/dist/hooks/command-not-found.js +58 -0
- package/dist/hooks/command-not-found.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/base-command.d.ts +123 -0
- package/dist/lib/base-command.d.ts.map +1 -0
- package/dist/lib/base-command.js +285 -0
- package/dist/lib/base-command.js.map +1 -0
- package/dist/output/formatter.d.ts +48 -0
- package/dist/output/formatter.d.ts.map +1 -0
- package/dist/output/formatter.js +138 -0
- package/dist/output/formatter.js.map +1 -0
- package/dist/output/json-envelope.d.ts +43 -0
- package/dist/output/json-envelope.d.ts.map +1 -0
- package/dist/output/json-envelope.js +73 -0
- package/dist/output/json-envelope.js.map +1 -0
- package/dist/utils/audit-logger.d.ts +97 -0
- package/dist/utils/audit-logger.d.ts.map +1 -0
- package/dist/utils/audit-logger.js +169 -0
- package/dist/utils/audit-logger.js.map +1 -0
- package/dist/utils/colors.d.ts +29 -0
- package/dist/utils/colors.d.ts.map +1 -0
- package/dist/utils/colors.js +36 -0
- package/dist/utils/colors.js.map +1 -0
- package/dist/utils/errors.d.ts +107 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +149 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/exit-codes.d.ts +21 -0
- package/dist/utils/exit-codes.d.ts.map +1 -0
- package/dist/utils/exit-codes.js +20 -0
- package/dist/utils/exit-codes.js.map +1 -0
- package/dist/utils/format.d.ts +64 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +69 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/prompt.d.ts +34 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +132 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/utils/retry.d.ts +59 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +96 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/terminal-sanitizer.d.ts +60 -0
- package/dist/utils/terminal-sanitizer.d.ts.map +1 -0
- package/dist/utils/terminal-sanitizer.js +166 -0
- package/dist/utils/terminal-sanitizer.js.map +1 -0
- package/dist/validation/input-sanitizer.d.ts +76 -0
- package/dist/validation/input-sanitizer.d.ts.map +1 -0
- package/dist/validation/input-sanitizer.js +199 -0
- package/dist/validation/input-sanitizer.js.map +1 -0
- package/dist/validation/schema-validator.d.ts +75 -0
- package/dist/validation/schema-validator.d.ts.map +1 -0
- package/dist/validation/schema-validator.js +147 -0
- package/dist/validation/schema-validator.js.map +1 -0
- package/oclif.manifest.json +857 -0
- package/package.json +101 -0
- package/scripts/completions/README.md +221 -0
- package/scripts/completions/mainwpcontrol.bash +193 -0
- package/scripts/completions/mainwpcontrol.zsh +267 -0
- package/scripts/completions/profile-completer.sh +35 -0
- package/scripts/completions/regenerate.sh +78 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Client for mainwpcontrol
|
|
3
|
+
*
|
|
4
|
+
* Single abstraction for all API traffic.
|
|
5
|
+
* INVARIANT: All HTTP requests MUST go through this module.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Request options
|
|
9
|
+
*/
|
|
10
|
+
export interface RequestOptions {
|
|
11
|
+
headers?: Record<string, string> | undefined;
|
|
12
|
+
timeout?: number | undefined;
|
|
13
|
+
signal?: AbortSignal | undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* HTTP client configuration
|
|
17
|
+
*/
|
|
18
|
+
export interface HttpClientConfig {
|
|
19
|
+
baseUrl: string;
|
|
20
|
+
username: string;
|
|
21
|
+
appPassword: string;
|
|
22
|
+
skipSSLVerification?: boolean | undefined;
|
|
23
|
+
timeout?: number | undefined;
|
|
24
|
+
maxResponseSize?: number | undefined;
|
|
25
|
+
allowInsecureHttp?: boolean | undefined;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Response wrapper with typed JSON parsing
|
|
29
|
+
*/
|
|
30
|
+
export interface HttpResponse<T = unknown> {
|
|
31
|
+
status: number;
|
|
32
|
+
statusText: string;
|
|
33
|
+
headers: Headers;
|
|
34
|
+
data: T;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* HTTP client class
|
|
38
|
+
*/
|
|
39
|
+
export declare class HttpClient {
|
|
40
|
+
private readonly baseUrl;
|
|
41
|
+
private readonly baseOrigin;
|
|
42
|
+
private readonly authHeader;
|
|
43
|
+
private readonly timeout;
|
|
44
|
+
private readonly maxResponseSize;
|
|
45
|
+
private readonly skipSSLVerification;
|
|
46
|
+
private readonly dispatcher;
|
|
47
|
+
/** Maximum redirects to follow (same-origin only) */
|
|
48
|
+
private static readonly MAX_REDIRECTS;
|
|
49
|
+
constructor(config: HttpClientConfig);
|
|
50
|
+
/**
|
|
51
|
+
* Make a GET request
|
|
52
|
+
*/
|
|
53
|
+
get<T = unknown>(path: string, options?: RequestOptions): Promise<HttpResponse<T>>;
|
|
54
|
+
/**
|
|
55
|
+
* Make a POST request
|
|
56
|
+
*/
|
|
57
|
+
post<T = unknown>(path: string, body?: unknown, options?: RequestOptions): Promise<HttpResponse<T>>;
|
|
58
|
+
/**
|
|
59
|
+
* Make a DELETE request
|
|
60
|
+
*/
|
|
61
|
+
delete<T = unknown>(path: string, options?: RequestOptions): Promise<HttpResponse<T>>;
|
|
62
|
+
/**
|
|
63
|
+
* Core request method
|
|
64
|
+
*/
|
|
65
|
+
private request;
|
|
66
|
+
/**
|
|
67
|
+
* Check if status code is a redirect
|
|
68
|
+
*/
|
|
69
|
+
private isRedirect;
|
|
70
|
+
/**
|
|
71
|
+
* Handle HTTP redirects securely
|
|
72
|
+
*
|
|
73
|
+
* SECURITY: Only follows same-origin redirects to prevent credential leakage.
|
|
74
|
+
* Cross-origin redirects are rejected with an error.
|
|
75
|
+
*/
|
|
76
|
+
private handleRedirect;
|
|
77
|
+
/**
|
|
78
|
+
* Build full URL from path
|
|
79
|
+
*/
|
|
80
|
+
private buildUrl;
|
|
81
|
+
/**
|
|
82
|
+
* Build request headers
|
|
83
|
+
*/
|
|
84
|
+
private buildHeaders;
|
|
85
|
+
/**
|
|
86
|
+
* Handle HTTP error status codes
|
|
87
|
+
*/
|
|
88
|
+
private handleHttpError;
|
|
89
|
+
/** Error code → typed error mappings */
|
|
90
|
+
private static readonly ERROR_CODE_MAP;
|
|
91
|
+
/** Known MainWPCTL error names that should pass through unchanged */
|
|
92
|
+
private static readonly KNOWN_ERROR_NAMES;
|
|
93
|
+
/**
|
|
94
|
+
* Normalize errors to MainWPCTLError types
|
|
95
|
+
*/
|
|
96
|
+
private normalizeError;
|
|
97
|
+
/**
|
|
98
|
+
* Extract error code from an error or its cause chain
|
|
99
|
+
* (e.g., fetch wraps ECONNREFUSED in TypeError.cause)
|
|
100
|
+
*/
|
|
101
|
+
private extractErrorCode;
|
|
102
|
+
/**
|
|
103
|
+
* Sanitize error data to prevent credential leaks
|
|
104
|
+
*/
|
|
105
|
+
private sanitizeErrorData;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Create an HTTP client from configuration
|
|
109
|
+
*/
|
|
110
|
+
export declare function createHttpClient(config: HttpClientConfig): HttpClient;
|
|
111
|
+
//# sourceMappingURL=http-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../../src/core/http-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1C,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,iBAAiB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,CAAC,CAAC;CACT;AAUD;;GAEG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAU;IAC9C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAE/C,qDAAqD;IACrD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAM;gBAE/B,MAAM,EAAE,gBAAgB;IA8CpC;;OAEG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EACnB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAI3B;;OAEG;IACG,IAAI,CAAC,CAAC,GAAG,OAAO,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAI3B;;OAEG;IACG,MAAM,CAAC,CAAC,GAAG,OAAO,EACtB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAI3B;;OAEG;YACW,OAAO;IAyGrB;;OAEG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;;OAKG;YACW,cAAc;IAiD5B;;OAEG;IACH,OAAO,CAAC,QAAQ;IAehB;;OAEG;IACH,OAAO,CAAC,YAAY;IAUpB;;OAEG;IACH,OAAO,CAAC,eAAe;IA6CvB,wCAAwC;IACxC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAqBpC;IAEF,qEAAqE;IACrE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAEtC;IAEH;;OAEG;IACH,OAAO,CAAC,cAAc;IAwBtB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAaxB;;OAEG;IACH,OAAO,CAAC,iBAAiB;CAuB1B;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CAErE"}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Client for mainwpcontrol
|
|
3
|
+
*
|
|
4
|
+
* Single abstraction for all API traffic.
|
|
5
|
+
* INVARIANT: All HTTP requests MUST go through this module.
|
|
6
|
+
*/
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import { Agent } from 'undici';
|
|
9
|
+
import { NetworkError, TLSError, APIError, AuthError } from '../utils/errors.js';
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const { version: PKG_VERSION } = require('../../package.json');
|
|
12
|
+
/**
|
|
13
|
+
* Default configuration values
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULTS = {
|
|
16
|
+
timeout: 30000, // 30 seconds
|
|
17
|
+
maxResponseSize: 10 * 1024 * 1024, // 10 MB
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* HTTP client class
|
|
21
|
+
*/
|
|
22
|
+
export class HttpClient {
|
|
23
|
+
baseUrl;
|
|
24
|
+
baseOrigin;
|
|
25
|
+
authHeader;
|
|
26
|
+
timeout;
|
|
27
|
+
maxResponseSize;
|
|
28
|
+
skipSSLVerification;
|
|
29
|
+
dispatcher;
|
|
30
|
+
/** Maximum redirects to follow (same-origin only) */
|
|
31
|
+
static MAX_REDIRECTS = 10;
|
|
32
|
+
constructor(config) {
|
|
33
|
+
// Normalize base URL (remove trailing slash)
|
|
34
|
+
this.baseUrl = config.baseUrl.replace(/\/+$/, '');
|
|
35
|
+
// Extract origin for redirect validation
|
|
36
|
+
const parsedUrl = new URL(this.baseUrl);
|
|
37
|
+
this.baseOrigin = parsedUrl.origin;
|
|
38
|
+
// Create Basic auth header
|
|
39
|
+
const credentials = Buffer.from(`${config.username}:${config.appPassword}`).toString('base64');
|
|
40
|
+
this.authHeader = `Basic ${credentials}`;
|
|
41
|
+
this.timeout = config.timeout ?? DEFAULTS.timeout;
|
|
42
|
+
this.maxResponseSize = config.maxResponseSize ?? DEFAULTS.maxResponseSize;
|
|
43
|
+
this.skipSSLVerification = config.skipSSLVerification ?? false;
|
|
44
|
+
// SECURITY: Enforce HTTPS by default
|
|
45
|
+
if (this.baseUrl.startsWith('http://')) {
|
|
46
|
+
const allowHttp = config.allowInsecureHttp || process.env['MAINWP_ALLOW_HTTP'] === '1';
|
|
47
|
+
if (!allowHttp) {
|
|
48
|
+
throw new TLSError('Dashboard URL uses insecure HTTP. HTTPS is required by default.', undefined, 'Switch to HTTPS, or set allowInsecureHttp in settings / MAINWP_ALLOW_HTTP=1 env var to override');
|
|
49
|
+
}
|
|
50
|
+
console.error('Warning: Dashboard URL uses HTTP. Credentials are sent unencrypted. ' +
|
|
51
|
+
'Consider switching to HTTPS.');
|
|
52
|
+
}
|
|
53
|
+
// Configure undici Agent for SSL verification control
|
|
54
|
+
if (this.skipSSLVerification) {
|
|
55
|
+
console.error('Warning: SSL verification is disabled for this profile. ' +
|
|
56
|
+
'Connection is vulnerable to interception.');
|
|
57
|
+
this.dispatcher = new Agent({
|
|
58
|
+
connect: {
|
|
59
|
+
rejectUnauthorized: false,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Make a GET request
|
|
66
|
+
*/
|
|
67
|
+
async get(path, options) {
|
|
68
|
+
return this.request('GET', path, undefined, options);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Make a POST request
|
|
72
|
+
*/
|
|
73
|
+
async post(path, body, options) {
|
|
74
|
+
return this.request('POST', path, body, options);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Make a DELETE request
|
|
78
|
+
*/
|
|
79
|
+
async delete(path, options) {
|
|
80
|
+
return this.request('DELETE', path, undefined, options);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Core request method
|
|
84
|
+
*/
|
|
85
|
+
async request(method, path, body, options, redirectCount = 0) {
|
|
86
|
+
const url = this.buildUrl(path);
|
|
87
|
+
const headers = this.buildHeaders(options?.headers);
|
|
88
|
+
// Create abort controller for timeout
|
|
89
|
+
const controller = new AbortController();
|
|
90
|
+
const timeoutId = setTimeout(() => controller.abort(), options?.timeout ?? this.timeout);
|
|
91
|
+
// Combine user signal with timeout signal
|
|
92
|
+
const effectiveSignal = options?.signal
|
|
93
|
+
? AbortSignal.any([options.signal, controller.signal])
|
|
94
|
+
: controller.signal;
|
|
95
|
+
try {
|
|
96
|
+
const fetchOptions = {
|
|
97
|
+
method,
|
|
98
|
+
headers,
|
|
99
|
+
signal: effectiveSignal,
|
|
100
|
+
// SECURITY: Disable auto-redirect to prevent auth header leakage to cross-origin
|
|
101
|
+
redirect: 'manual',
|
|
102
|
+
};
|
|
103
|
+
if (body !== undefined) {
|
|
104
|
+
fetchOptions.body = JSON.stringify(body);
|
|
105
|
+
}
|
|
106
|
+
// Use undici dispatcher for SSL verification control
|
|
107
|
+
if (this.dispatcher) {
|
|
108
|
+
// Cast needed due to type mismatch between undici and Node.js fetch types
|
|
109
|
+
fetchOptions.dispatcher = this.dispatcher;
|
|
110
|
+
}
|
|
111
|
+
const response = await fetch(url, fetchOptions);
|
|
112
|
+
clearTimeout(timeoutId);
|
|
113
|
+
// SECURITY: Handle redirects manually - only follow same-origin
|
|
114
|
+
if (this.isRedirect(response.status)) {
|
|
115
|
+
return this.handleRedirect(response, method, body, options, redirectCount);
|
|
116
|
+
}
|
|
117
|
+
// Check response size via Content-Length header (pre-read guard)
|
|
118
|
+
const contentLength = response.headers.get('content-length');
|
|
119
|
+
const parsedContentLength = contentLength ? parseInt(contentLength, 10) : NaN;
|
|
120
|
+
if (!isNaN(parsedContentLength) && parsedContentLength > this.maxResponseSize) {
|
|
121
|
+
throw new NetworkError(`Response too large: ${parsedContentLength} bytes`, undefined, 'Response is too large. Check the Dashboard logs or try a simpler query');
|
|
122
|
+
}
|
|
123
|
+
// Parse response
|
|
124
|
+
const text = await response.text();
|
|
125
|
+
// Post-read body length check (only when Content-Length was absent or unparseable)
|
|
126
|
+
if (isNaN(parsedContentLength) && text.length > this.maxResponseSize) {
|
|
127
|
+
throw new NetworkError(`Response too large: ${text.length} bytes`, undefined, 'Response is too large. Check the Dashboard logs or try a simpler query');
|
|
128
|
+
}
|
|
129
|
+
let data;
|
|
130
|
+
try {
|
|
131
|
+
// SECURITY: Strip __proto__ and constructor keys to prevent prototype
|
|
132
|
+
// pollution from untrusted API responses
|
|
133
|
+
data = text ? JSON.parse(text, (key, value) => {
|
|
134
|
+
if (key === '__proto__' || key === 'constructor') {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
return value;
|
|
138
|
+
}) : {};
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// If not JSON, wrap as string
|
|
142
|
+
data = text;
|
|
143
|
+
}
|
|
144
|
+
// Handle HTTP errors
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
this.handleHttpError(response.status, data);
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
status: response.status,
|
|
150
|
+
statusText: response.statusText,
|
|
151
|
+
headers: response.headers,
|
|
152
|
+
data,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
clearTimeout(timeoutId);
|
|
157
|
+
throw this.normalizeError(error);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Check if status code is a redirect
|
|
162
|
+
*/
|
|
163
|
+
isRedirect(status) {
|
|
164
|
+
return status >= 300 && status < 400;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Handle HTTP redirects securely
|
|
168
|
+
*
|
|
169
|
+
* SECURITY: Only follows same-origin redirects to prevent credential leakage.
|
|
170
|
+
* Cross-origin redirects are rejected with an error.
|
|
171
|
+
*/
|
|
172
|
+
async handleRedirect(response, method, body, options, redirectCount) {
|
|
173
|
+
if (redirectCount >= HttpClient.MAX_REDIRECTS) {
|
|
174
|
+
throw new NetworkError('Too many redirects', undefined, 'The Dashboard is redirecting too many times. Check the server configuration.');
|
|
175
|
+
}
|
|
176
|
+
const location = response.headers.get('location');
|
|
177
|
+
if (!location) {
|
|
178
|
+
throw new NetworkError(`Redirect response (${response.status}) missing Location header`, undefined, 'The server sent an invalid redirect response');
|
|
179
|
+
}
|
|
180
|
+
// Resolve relative URLs against the current base
|
|
181
|
+
let redirectUrl;
|
|
182
|
+
try {
|
|
183
|
+
redirectUrl = new URL(location, this.baseUrl);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
throw new NetworkError(`Invalid redirect URL: ${location}`, undefined, 'The server sent an invalid redirect location');
|
|
187
|
+
}
|
|
188
|
+
// SECURITY: Only follow same-origin redirects
|
|
189
|
+
if (redirectUrl.origin !== this.baseOrigin) {
|
|
190
|
+
throw new NetworkError(`Cross-origin redirect blocked: ${this.baseOrigin} → ${redirectUrl.origin}`, undefined, 'The Dashboard is redirecting to a different domain. This may indicate a security issue or misconfiguration.');
|
|
191
|
+
}
|
|
192
|
+
// Follow same-origin redirect (pass full URL to preserve path)
|
|
193
|
+
return this.request(method, redirectUrl.href, body, options, redirectCount + 1);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Build full URL from path
|
|
197
|
+
*/
|
|
198
|
+
buildUrl(path) {
|
|
199
|
+
// If path is already a full URL, validate same-origin
|
|
200
|
+
if (path.startsWith('http://') || path.startsWith('https://')) {
|
|
201
|
+
const parsedUrl = new URL(path);
|
|
202
|
+
if (parsedUrl.origin !== this.baseOrigin) {
|
|
203
|
+
throw new NetworkError(`Request URL origin mismatch: ${parsedUrl.origin} !== ${this.baseOrigin}`);
|
|
204
|
+
}
|
|
205
|
+
return path;
|
|
206
|
+
}
|
|
207
|
+
// Ensure path starts with /
|
|
208
|
+
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
209
|
+
return `${this.baseUrl}${normalizedPath}`;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Build request headers
|
|
213
|
+
*/
|
|
214
|
+
buildHeaders(custom) {
|
|
215
|
+
return {
|
|
216
|
+
Authorization: this.authHeader,
|
|
217
|
+
'Content-Type': 'application/json',
|
|
218
|
+
Accept: 'application/json',
|
|
219
|
+
'User-Agent': `mainwpcontrol/${PKG_VERSION}`,
|
|
220
|
+
...custom,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Handle HTTP error status codes
|
|
225
|
+
*/
|
|
226
|
+
handleHttpError(status, data) {
|
|
227
|
+
// Sanitize error data (remove any potential credential leaks)
|
|
228
|
+
const sanitizedData = this.sanitizeErrorData(data);
|
|
229
|
+
switch (status) {
|
|
230
|
+
case 401:
|
|
231
|
+
throw new AuthError('Authentication failed. Check your credentials.', sanitizedData, 'Run `mainwpcontrol login` to update your credentials');
|
|
232
|
+
case 403:
|
|
233
|
+
throw new AuthError('Access denied. Insufficient permissions.', sanitizedData, 'Verify your user has the required permissions in WordPress');
|
|
234
|
+
case 404:
|
|
235
|
+
throw new APIError('NOT_FOUND', 'Resource not found', status, sanitizedData);
|
|
236
|
+
case 422:
|
|
237
|
+
throw new APIError('VALIDATION_ERROR', 'Validation failed', status, sanitizedData);
|
|
238
|
+
case 429:
|
|
239
|
+
throw new APIError('RATE_LIMITED', 'Too many requests', status, sanitizedData, 'Wait a few minutes before retrying');
|
|
240
|
+
case 500:
|
|
241
|
+
case 502:
|
|
242
|
+
case 503:
|
|
243
|
+
case 504:
|
|
244
|
+
throw new APIError('SERVER_ERROR', 'Server error', status, sanitizedData, 'Check the Dashboard logs or try again later');
|
|
245
|
+
default:
|
|
246
|
+
throw new APIError('HTTP_ERROR', `HTTP ${status}`, status, sanitizedData);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/** Error code → typed error mappings */
|
|
250
|
+
static ERROR_CODE_MAP = {
|
|
251
|
+
ECONNREFUSED: () => new NetworkError('Connection refused. Is the Dashboard running?', undefined, 'Verify the Dashboard is running and the URL is correct'),
|
|
252
|
+
ENOTFOUND: () => new NetworkError('Host not found. Check the Dashboard URL.', undefined, 'Check the Dashboard URL in your profile with `mainwpcontrol config show`'),
|
|
253
|
+
CERT_HAS_EXPIRED: () => new TLSError('SSL certificate error. Use --skip-ssl-verify if needed.', undefined, 'Use --skip-ssl-verify flag if using self-signed certificates (not recommended for production)'),
|
|
254
|
+
UNABLE_TO_VERIFY_LEAF_SIGNATURE: () => new TLSError('SSL certificate error. Use --skip-ssl-verify if needed.', undefined, 'Use --skip-ssl-verify flag if using self-signed certificates (not recommended for production)'),
|
|
255
|
+
};
|
|
256
|
+
/** Known MainWPCTL error names that should pass through unchanged */
|
|
257
|
+
static KNOWN_ERROR_NAMES = new Set([
|
|
258
|
+
'NetworkError', 'TLSError', 'APIError', 'AuthError',
|
|
259
|
+
]);
|
|
260
|
+
/**
|
|
261
|
+
* Normalize errors to MainWPCTLError types
|
|
262
|
+
*/
|
|
263
|
+
normalizeError(error) {
|
|
264
|
+
if (!(error instanceof Error)) {
|
|
265
|
+
return new NetworkError(String(error));
|
|
266
|
+
}
|
|
267
|
+
if (error.name === 'AbortError') {
|
|
268
|
+
return new NetworkError('Request timed out', undefined, 'Increase timeout with --timeout flag or check network connection');
|
|
269
|
+
}
|
|
270
|
+
const errorCode = this.extractErrorCode(error);
|
|
271
|
+
const mapped = errorCode ? HttpClient.ERROR_CODE_MAP[errorCode] : undefined;
|
|
272
|
+
if (mapped)
|
|
273
|
+
return mapped();
|
|
274
|
+
if (HttpClient.KNOWN_ERROR_NAMES.has(error.name)) {
|
|
275
|
+
return error;
|
|
276
|
+
}
|
|
277
|
+
return new NetworkError(String(error));
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Extract error code from an error or its cause chain
|
|
281
|
+
* (e.g., fetch wraps ECONNREFUSED in TypeError.cause)
|
|
282
|
+
*/
|
|
283
|
+
extractErrorCode(error) {
|
|
284
|
+
// Check the error itself
|
|
285
|
+
if ('code' in error && typeof error.code === 'string') {
|
|
286
|
+
return error.code;
|
|
287
|
+
}
|
|
288
|
+
// Check the cause chain (Node.js fetch wraps errors in TypeError)
|
|
289
|
+
const cause = error.cause;
|
|
290
|
+
if (cause instanceof Error) {
|
|
291
|
+
return this.extractErrorCode(cause);
|
|
292
|
+
}
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Sanitize error data to prevent credential leaks
|
|
297
|
+
*/
|
|
298
|
+
sanitizeErrorData(data) {
|
|
299
|
+
if (typeof data !== 'object' || data === null)
|
|
300
|
+
return data;
|
|
301
|
+
if (Array.isArray(data))
|
|
302
|
+
return data.map(item => this.sanitizeErrorData(item));
|
|
303
|
+
const sanitized = {};
|
|
304
|
+
// Substring matching catches camelCase, snake_case, and header variants
|
|
305
|
+
// (e.g., accessToken, private_key, set-cookie, refreshToken)
|
|
306
|
+
const sensitiveSubstrings = [
|
|
307
|
+
'password', 'token', 'secret', 'authorization', 'cookie',
|
|
308
|
+
'apikey', 'api_key', 'bearer', 'credential', 'private_key',
|
|
309
|
+
'signing_key',
|
|
310
|
+
];
|
|
311
|
+
for (const [key, value] of Object.entries(data)) {
|
|
312
|
+
const keyLower = key.toLowerCase();
|
|
313
|
+
if (sensitiveSubstrings.some(s => keyLower.includes(s))) {
|
|
314
|
+
sanitized[key] = '[REDACTED]';
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
sanitized[key] = this.sanitizeErrorData(value);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return sanitized;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Create an HTTP client from configuration
|
|
325
|
+
*/
|
|
326
|
+
export function createHttpClient(config) {
|
|
327
|
+
return new HttpClient(config);
|
|
328
|
+
}
|
|
329
|
+
//# sourceMappingURL=http-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-client.js","sourceRoot":"","sources":["../../src/core/http-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safety Controller for mainwpcontrol
|
|
3
|
+
*
|
|
4
|
+
* Enforces the destructive action protocol defined in PLAN.md:
|
|
5
|
+
* - Safety check happens BEFORE any network call
|
|
6
|
+
* - dry_run and confirm are mutually exclusive
|
|
7
|
+
* - Destructive abilities require preview first
|
|
8
|
+
*
|
|
9
|
+
* INVARIANT: AI is the primary interaction surface, NOT the execution authority.
|
|
10
|
+
*/
|
|
11
|
+
import type { Ability, ExecutionResult } from './abilities-executor.js';
|
|
12
|
+
/**
|
|
13
|
+
* Preview result from dry_run execution
|
|
14
|
+
*/
|
|
15
|
+
export interface PreviewResult {
|
|
16
|
+
/** Items that would be affected */
|
|
17
|
+
affected: unknown[];
|
|
18
|
+
/** Summary of changes */
|
|
19
|
+
summary: string;
|
|
20
|
+
/** Whether approval is required to execute */
|
|
21
|
+
requiresApproval: boolean;
|
|
22
|
+
/** The ability being previewed */
|
|
23
|
+
abilityName: string;
|
|
24
|
+
/** Original input parameters */
|
|
25
|
+
input: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Safety classification for an ability
|
|
29
|
+
*/
|
|
30
|
+
export interface SafetyClassification {
|
|
31
|
+
/** Whether this ability is destructive */
|
|
32
|
+
isDestructive: boolean;
|
|
33
|
+
/** Whether this ability is read-only */
|
|
34
|
+
isReadOnly: boolean;
|
|
35
|
+
/** Whether this ability is idempotent */
|
|
36
|
+
isIdempotent: boolean;
|
|
37
|
+
/** Whether safety flow is required */
|
|
38
|
+
requiresSafetyFlow: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Execution intent from user
|
|
42
|
+
*/
|
|
43
|
+
export type ExecutionIntent = {
|
|
44
|
+
mode: 'preview';
|
|
45
|
+
} | {
|
|
46
|
+
mode: 'execute';
|
|
47
|
+
} | {
|
|
48
|
+
mode: 'auto';
|
|
49
|
+
};
|
|
50
|
+
export declare class SafetyController {
|
|
51
|
+
/**
|
|
52
|
+
* Classify an ability's safety requirements
|
|
53
|
+
*
|
|
54
|
+
* Safety classification derives ONLY from ability annotations.
|
|
55
|
+
* No heuristics are permitted.
|
|
56
|
+
*/
|
|
57
|
+
classify(ability: Ability): SafetyClassification;
|
|
58
|
+
/**
|
|
59
|
+
* Known-destructive ability name patterns.
|
|
60
|
+
* These abilities require the safety flow regardless of API-reported annotations.
|
|
61
|
+
*/
|
|
62
|
+
private static readonly DESTRUCTIVE_PATTERNS;
|
|
63
|
+
private isKnownDestructivePattern;
|
|
64
|
+
/**
|
|
65
|
+
* Validate annotation fields and resolve contradictions.
|
|
66
|
+
*
|
|
67
|
+
* - Non-boolean values fall back to safe defaults.
|
|
68
|
+
* - Contradictory annotations (destructive + readonly) → warn and treat as destructive.
|
|
69
|
+
*/
|
|
70
|
+
private validateAnnotations;
|
|
71
|
+
/**
|
|
72
|
+
* Check if an ability requires the safety flow
|
|
73
|
+
*/
|
|
74
|
+
requiresSafetyFlow(ability: Ability): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Validate execution flags BEFORE any network call
|
|
77
|
+
*
|
|
78
|
+
* @throws MutualExclusionError if dry_run and confirm are both true
|
|
79
|
+
* @throws InputError if destructive ability has neither flag
|
|
80
|
+
*/
|
|
81
|
+
validateExecutionFlags(ability: Ability, dryRun?: boolean, confirm?: boolean): void;
|
|
82
|
+
/**
|
|
83
|
+
* Determine execution intent from flags
|
|
84
|
+
*/
|
|
85
|
+
determineIntent(dryRun?: boolean, confirm?: boolean): ExecutionIntent;
|
|
86
|
+
/**
|
|
87
|
+
* Check if execution should proceed directly (without preview)
|
|
88
|
+
*
|
|
89
|
+
* Returns true for:
|
|
90
|
+
* - Read-only abilities
|
|
91
|
+
* - Explicit confirm flag
|
|
92
|
+
* - Non-destructive abilities
|
|
93
|
+
*/
|
|
94
|
+
shouldExecuteDirectly(ability: Ability, dryRun?: boolean, confirm?: boolean): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Format a preview result from API response
|
|
97
|
+
*/
|
|
98
|
+
formatPreviewResult(ability: Ability, input: Record<string, unknown>, apiResult: ExecutionResult): PreviewResult;
|
|
99
|
+
/**
|
|
100
|
+
* Extract affected items from API preview response
|
|
101
|
+
*/
|
|
102
|
+
private extractAffectedItems;
|
|
103
|
+
/** Ability name keywords → past-tense action verbs */
|
|
104
|
+
private static readonly ACTION_VERBS;
|
|
105
|
+
/**
|
|
106
|
+
* Generate human-readable preview summary
|
|
107
|
+
*/
|
|
108
|
+
private generatePreviewSummary;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get the safety controller singleton
|
|
112
|
+
*/
|
|
113
|
+
export declare function getSafetyController(): SafetyController;
|
|
114
|
+
//# sourceMappingURL=safety-controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safety-controller.d.ts","sourceRoot":"","sources":["../../src/core/safety-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAsB,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG5F;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,mCAAmC;IACnC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kCAAkC;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,0CAA0C;IAC1C,aAAa,EAAE,OAAO,CAAC;IACvB,wCAAwC;IACxC,UAAU,EAAE,OAAO,CAAC;IACpB,yCAAyC;IACzC,YAAY,EAAE,OAAO,CAAC;IACtB,sCAAsC;IACtC,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAoBrB,qBAAa,gBAAgB;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,oBAAoB;IAoBhD;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAQ1C;IAEF,OAAO,CAAC,yBAAyB;IAIjC;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAsB3B;;OAEG;IACH,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO;IAI7C;;;;;OAKG;IACH,sBAAsB,CACpB,OAAO,EAAE,OAAO,EAChB,MAAM,CAAC,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,OAAO,GAChB,IAAI;IAuBP;;OAEG;IACH,eAAe,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,eAAe;IAUrE;;;;;;;OAOG;IACH,qBAAqB,CACnB,OAAO,EAAE,OAAO,EAChB,MAAM,CAAC,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,OAAO,GAChB,OAAO;IA2BV;;OAEG;IACH,mBAAmB,CACjB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,eAAe,GACzB,aAAa;IAchB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IA2B5B,sDAAsD;IACtD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAIlC;IAEF;;OAEG;IACH,OAAO,CAAC,sBAAsB;CAU/B;AAOD;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,gBAAgB,CAKtD"}
|