@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.
Files changed (204) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +583 -0
  3. package/bin/_exit.js +12 -0
  4. package/bin/dev.js +7 -0
  5. package/bin/run.js +7 -0
  6. package/dist/chat/chat-engine.d.ts +213 -0
  7. package/dist/chat/chat-engine.d.ts.map +1 -0
  8. package/dist/chat/chat-engine.js +636 -0
  9. package/dist/chat/chat-engine.js.map +1 -0
  10. package/dist/chat/index.d.ts +10 -0
  11. package/dist/chat/index.d.ts.map +1 -0
  12. package/dist/chat/index.js +14 -0
  13. package/dist/chat/index.js.map +1 -0
  14. package/dist/chat/providers/anthropic.d.ts +52 -0
  15. package/dist/chat/providers/anthropic.d.ts.map +1 -0
  16. package/dist/chat/providers/anthropic.js +292 -0
  17. package/dist/chat/providers/anthropic.js.map +1 -0
  18. package/dist/chat/providers/gemini.d.ts +52 -0
  19. package/dist/chat/providers/gemini.d.ts.map +1 -0
  20. package/dist/chat/providers/gemini.js +284 -0
  21. package/dist/chat/providers/gemini.js.map +1 -0
  22. package/dist/chat/providers/index.d.ts +19 -0
  23. package/dist/chat/providers/index.d.ts.map +1 -0
  24. package/dist/chat/providers/index.js +23 -0
  25. package/dist/chat/providers/index.js.map +1 -0
  26. package/dist/chat/providers/local.d.ts +37 -0
  27. package/dist/chat/providers/local.d.ts.map +1 -0
  28. package/dist/chat/providers/local.js +130 -0
  29. package/dist/chat/providers/local.js.map +1 -0
  30. package/dist/chat/providers/openai-compatible.d.ts +155 -0
  31. package/dist/chat/providers/openai-compatible.d.ts.map +1 -0
  32. package/dist/chat/providers/openai-compatible.js +264 -0
  33. package/dist/chat/providers/openai-compatible.js.map +1 -0
  34. package/dist/chat/providers/openai.d.ts +24 -0
  35. package/dist/chat/providers/openai.d.ts.map +1 -0
  36. package/dist/chat/providers/openai.js +62 -0
  37. package/dist/chat/providers/openai.js.map +1 -0
  38. package/dist/chat/providers/openrouter.d.ts +26 -0
  39. package/dist/chat/providers/openrouter.d.ts.map +1 -0
  40. package/dist/chat/providers/openrouter.js +65 -0
  41. package/dist/chat/providers/openrouter.js.map +1 -0
  42. package/dist/chat/providers/provider-fetch.d.ts +15 -0
  43. package/dist/chat/providers/provider-fetch.d.ts.map +1 -0
  44. package/dist/chat/providers/provider-fetch.js +35 -0
  45. package/dist/chat/providers/provider-fetch.js.map +1 -0
  46. package/dist/chat/providers/provider.d.ts +214 -0
  47. package/dist/chat/providers/provider.d.ts.map +1 -0
  48. package/dist/chat/providers/provider.js +166 -0
  49. package/dist/chat/providers/provider.js.map +1 -0
  50. package/dist/chat/providers/sse-reader.d.ts +21 -0
  51. package/dist/chat/providers/sse-reader.d.ts.map +1 -0
  52. package/dist/chat/providers/sse-reader.js +48 -0
  53. package/dist/chat/providers/sse-reader.js.map +1 -0
  54. package/dist/chat/system-prompt.d.ts +33 -0
  55. package/dist/chat/system-prompt.d.ts.map +1 -0
  56. package/dist/chat/system-prompt.js +166 -0
  57. package/dist/chat/system-prompt.js.map +1 -0
  58. package/dist/chat/tool-envelope.d.ts +72 -0
  59. package/dist/chat/tool-envelope.d.ts.map +1 -0
  60. package/dist/chat/tool-envelope.js +263 -0
  61. package/dist/chat/tool-envelope.js.map +1 -0
  62. package/dist/commands/abilities/info.d.ts +21 -0
  63. package/dist/commands/abilities/info.d.ts.map +1 -0
  64. package/dist/commands/abilities/info.js +80 -0
  65. package/dist/commands/abilities/info.js.map +1 -0
  66. package/dist/commands/abilities/list.d.ts +19 -0
  67. package/dist/commands/abilities/list.d.ts.map +1 -0
  68. package/dist/commands/abilities/list.js +98 -0
  69. package/dist/commands/abilities/list.js.map +1 -0
  70. package/dist/commands/abilities/run.d.ts +75 -0
  71. package/dist/commands/abilities/run.d.ts.map +1 -0
  72. package/dist/commands/abilities/run.js +468 -0
  73. package/dist/commands/abilities/run.js.map +1 -0
  74. package/dist/commands/chat.d.ts +54 -0
  75. package/dist/commands/chat.d.ts.map +1 -0
  76. package/dist/commands/chat.js +384 -0
  77. package/dist/commands/chat.js.map +1 -0
  78. package/dist/commands/config/show.d.ts +54 -0
  79. package/dist/commands/config/show.d.ts.map +1 -0
  80. package/dist/commands/config/show.js +324 -0
  81. package/dist/commands/config/show.js.map +1 -0
  82. package/dist/commands/doctor.d.ts +77 -0
  83. package/dist/commands/doctor.d.ts.map +1 -0
  84. package/dist/commands/doctor.js +412 -0
  85. package/dist/commands/doctor.js.map +1 -0
  86. package/dist/commands/jobs/watch.d.ts +50 -0
  87. package/dist/commands/jobs/watch.d.ts.map +1 -0
  88. package/dist/commands/jobs/watch.js +269 -0
  89. package/dist/commands/jobs/watch.js.map +1 -0
  90. package/dist/commands/login.d.ts +25 -0
  91. package/dist/commands/login.d.ts.map +1 -0
  92. package/dist/commands/login.js +165 -0
  93. package/dist/commands/login.js.map +1 -0
  94. package/dist/commands/profile/delete.d.ts +22 -0
  95. package/dist/commands/profile/delete.d.ts.map +1 -0
  96. package/dist/commands/profile/delete.js +57 -0
  97. package/dist/commands/profile/delete.js.map +1 -0
  98. package/dist/commands/profile/list.d.ts +19 -0
  99. package/dist/commands/profile/list.d.ts.map +1 -0
  100. package/dist/commands/profile/list.js +53 -0
  101. package/dist/commands/profile/list.js.map +1 -0
  102. package/dist/commands/profile/use.d.ts +22 -0
  103. package/dist/commands/profile/use.d.ts.map +1 -0
  104. package/dist/commands/profile/use.js +46 -0
  105. package/dist/commands/profile/use.js.map +1 -0
  106. package/dist/config/fs-utils.d.ts +14 -0
  107. package/dist/config/fs-utils.d.ts.map +1 -0
  108. package/dist/config/fs-utils.js +31 -0
  109. package/dist/config/fs-utils.js.map +1 -0
  110. package/dist/config/keychain.d.ts +53 -0
  111. package/dist/config/keychain.d.ts.map +1 -0
  112. package/dist/config/keychain.js +175 -0
  113. package/dist/config/keychain.js.map +1 -0
  114. package/dist/config/profile-store.d.ts +85 -0
  115. package/dist/config/profile-store.d.ts.map +1 -0
  116. package/dist/config/profile-store.js +228 -0
  117. package/dist/config/profile-store.js.map +1 -0
  118. package/dist/config/settings.d.ts +71 -0
  119. package/dist/config/settings.d.ts.map +1 -0
  120. package/dist/config/settings.js +151 -0
  121. package/dist/config/settings.js.map +1 -0
  122. package/dist/core/abilities-executor.d.ts +126 -0
  123. package/dist/core/abilities-executor.d.ts.map +1 -0
  124. package/dist/core/abilities-executor.js +264 -0
  125. package/dist/core/abilities-executor.js.map +1 -0
  126. package/dist/core/batch-manager.d.ts +113 -0
  127. package/dist/core/batch-manager.d.ts.map +1 -0
  128. package/dist/core/batch-manager.js +244 -0
  129. package/dist/core/batch-manager.js.map +1 -0
  130. package/dist/core/http-client.d.ts +111 -0
  131. package/dist/core/http-client.d.ts.map +1 -0
  132. package/dist/core/http-client.js +329 -0
  133. package/dist/core/http-client.js.map +1 -0
  134. package/dist/core/safety-controller.d.ts +114 -0
  135. package/dist/core/safety-controller.d.ts.map +1 -0
  136. package/dist/core/safety-controller.js +229 -0
  137. package/dist/core/safety-controller.js.map +1 -0
  138. package/dist/hooks/command-not-found.d.ts +12 -0
  139. package/dist/hooks/command-not-found.d.ts.map +1 -0
  140. package/dist/hooks/command-not-found.js +58 -0
  141. package/dist/hooks/command-not-found.js.map +1 -0
  142. package/dist/index.d.ts +7 -0
  143. package/dist/index.d.ts.map +1 -0
  144. package/dist/index.js +7 -0
  145. package/dist/index.js.map +1 -0
  146. package/dist/lib/base-command.d.ts +123 -0
  147. package/dist/lib/base-command.d.ts.map +1 -0
  148. package/dist/lib/base-command.js +285 -0
  149. package/dist/lib/base-command.js.map +1 -0
  150. package/dist/output/formatter.d.ts +48 -0
  151. package/dist/output/formatter.d.ts.map +1 -0
  152. package/dist/output/formatter.js +138 -0
  153. package/dist/output/formatter.js.map +1 -0
  154. package/dist/output/json-envelope.d.ts +43 -0
  155. package/dist/output/json-envelope.d.ts.map +1 -0
  156. package/dist/output/json-envelope.js +73 -0
  157. package/dist/output/json-envelope.js.map +1 -0
  158. package/dist/utils/audit-logger.d.ts +97 -0
  159. package/dist/utils/audit-logger.d.ts.map +1 -0
  160. package/dist/utils/audit-logger.js +169 -0
  161. package/dist/utils/audit-logger.js.map +1 -0
  162. package/dist/utils/colors.d.ts +29 -0
  163. package/dist/utils/colors.d.ts.map +1 -0
  164. package/dist/utils/colors.js +36 -0
  165. package/dist/utils/colors.js.map +1 -0
  166. package/dist/utils/errors.d.ts +107 -0
  167. package/dist/utils/errors.d.ts.map +1 -0
  168. package/dist/utils/errors.js +149 -0
  169. package/dist/utils/errors.js.map +1 -0
  170. package/dist/utils/exit-codes.d.ts +21 -0
  171. package/dist/utils/exit-codes.d.ts.map +1 -0
  172. package/dist/utils/exit-codes.js +20 -0
  173. package/dist/utils/exit-codes.js.map +1 -0
  174. package/dist/utils/format.d.ts +64 -0
  175. package/dist/utils/format.d.ts.map +1 -0
  176. package/dist/utils/format.js +69 -0
  177. package/dist/utils/format.js.map +1 -0
  178. package/dist/utils/prompt.d.ts +34 -0
  179. package/dist/utils/prompt.d.ts.map +1 -0
  180. package/dist/utils/prompt.js +132 -0
  181. package/dist/utils/prompt.js.map +1 -0
  182. package/dist/utils/retry.d.ts +59 -0
  183. package/dist/utils/retry.d.ts.map +1 -0
  184. package/dist/utils/retry.js +96 -0
  185. package/dist/utils/retry.js.map +1 -0
  186. package/dist/utils/terminal-sanitizer.d.ts +60 -0
  187. package/dist/utils/terminal-sanitizer.d.ts.map +1 -0
  188. package/dist/utils/terminal-sanitizer.js +166 -0
  189. package/dist/utils/terminal-sanitizer.js.map +1 -0
  190. package/dist/validation/input-sanitizer.d.ts +76 -0
  191. package/dist/validation/input-sanitizer.d.ts.map +1 -0
  192. package/dist/validation/input-sanitizer.js +199 -0
  193. package/dist/validation/input-sanitizer.js.map +1 -0
  194. package/dist/validation/schema-validator.d.ts +75 -0
  195. package/dist/validation/schema-validator.d.ts.map +1 -0
  196. package/dist/validation/schema-validator.js +147 -0
  197. package/dist/validation/schema-validator.js.map +1 -0
  198. package/oclif.manifest.json +857 -0
  199. package/package.json +101 -0
  200. package/scripts/completions/README.md +221 -0
  201. package/scripts/completions/mainwpcontrol.bash +193 -0
  202. package/scripts/completions/mainwpcontrol.zsh +267 -0
  203. package/scripts/completions/profile-completer.sh +35 -0
  204. 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"}