@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,98 @@
1
+ /**
2
+ * Abilities list command for mainwpcontrol
3
+ *
4
+ * Lists all available abilities from the Dashboard.
5
+ */
6
+ import { Flags } from '@oclif/core';
7
+ import { BaseCommand, commonFlags } from '../../lib/base-command.js';
8
+ import { formatTable, formatHeading } from '../../output/formatter.js';
9
+ import { color, colors } from '../../utils/colors.js';
10
+ import { stripControlChars } from '../../utils/terminal-sanitizer.js';
11
+ export default class AbilitiesList extends BaseCommand {
12
+ static description = 'List available abilities';
13
+ static examples = [
14
+ '<%= config.bin %> abilities list',
15
+ '<%= config.bin %> abilities list --category sites',
16
+ '<%= config.bin %> abilities list --json',
17
+ ];
18
+ static flags = {
19
+ ...commonFlags,
20
+ category: Flags.string({
21
+ char: 'c',
22
+ description: 'Filter by category',
23
+ }),
24
+ };
25
+ async run() {
26
+ const { flags } = await this.parse(AbilitiesList);
27
+ await this.initCommon(flags);
28
+ const executor = await this.getExecutor();
29
+ let abilities = await executor.listAbilities();
30
+ // Filter by category if specified
31
+ if (flags.category) {
32
+ abilities = abilities.filter((a) => a.category.toLowerCase() === flags.category?.toLowerCase());
33
+ }
34
+ // Get unique categories for display
35
+ const categories = [...new Set(abilities.map((a) => a.category))].sort();
36
+ this.output({
37
+ abilities: abilities.map((a) => ({
38
+ name: a.name,
39
+ label: a.label,
40
+ category: a.category,
41
+ readonly: a.meta?.annotations?.readonly ?? false,
42
+ destructive: a.meta?.annotations?.destructive ?? false,
43
+ })),
44
+ total: abilities.length,
45
+ categories,
46
+ }, () => {
47
+ if (abilities.length === 0) {
48
+ return 'No abilities found.';
49
+ }
50
+ const lines = [
51
+ formatHeading(`Abilities (${abilities.length} total)`),
52
+ '',
53
+ ];
54
+ // Group by category
55
+ const grouped = new Map();
56
+ for (const ability of abilities) {
57
+ const cat = ability.category;
58
+ if (!grouped.has(cat)) {
59
+ grouped.set(cat, []);
60
+ }
61
+ grouped.get(cat)?.push(ability);
62
+ }
63
+ // Sort categories
64
+ const sortedCategories = [...grouped.keys()].sort();
65
+ for (const category of sortedCategories) {
66
+ const catAbilities = grouped.get(category) ?? [];
67
+ lines.push(formatHeading(` ${category}`));
68
+ const headers = ['Name', 'Description', 'Type'];
69
+ const rows = catAbilities.map((a) => {
70
+ let type = '📖 read';
71
+ if (a.meta?.annotations?.destructive) {
72
+ type = '⚠️ destructive';
73
+ }
74
+ else if (!a.meta?.annotations?.readonly) {
75
+ type = '✏️ write';
76
+ }
77
+ return [a.name, a.label || a.description.slice(0, 50), type];
78
+ });
79
+ // Render table with usage sub-rows under each ability
80
+ const tableStr = formatTable(headers, rows);
81
+ const tableLines = tableStr.split('\n');
82
+ // Header and separator
83
+ lines.push(tableLines[0] ?? '', tableLines[1] ?? '');
84
+ // Data rows with copy-pasteable usage hints
85
+ for (let j = 0; j < catAbilities.length; j++) {
86
+ lines.push(tableLines[j + 2] ?? '');
87
+ const ability = catAbilities[j];
88
+ const safeName = stripControlChars(ability.name);
89
+ const shortName = safeName.split('/').pop() ?? safeName;
90
+ lines.push(color(` mainwpcontrol abilities run ${shortName}`, colors.dim));
91
+ }
92
+ lines.push('');
93
+ }
94
+ return lines.join('\n');
95
+ });
96
+ }
97
+ }
98
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../../src/commands/abilities/list.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Abilities run command for mainwpcontrol
3
+ *
4
+ * Execute abilities with safety enforcement.
5
+ * Routes through SafetyController for destructive actions.
6
+ *
7
+ * INVARIANT: Commands and chat share the same execution path.
8
+ */
9
+ import { BaseCommand } from '../../lib/base-command.js';
10
+ export default class AbilitiesRun extends BaseCommand {
11
+ static description: string;
12
+ static examples: string[];
13
+ static flags: {
14
+ input: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
15
+ 'input-file': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
+ confirm: import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
+ force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
19
+ wait: import("@oclif/core/interfaces").BooleanFlag<boolean>;
20
+ 'wait-timeout': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
21
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
22
+ quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
23
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
24
+ debug: import("@oclif/core/interfaces").BooleanFlag<boolean>;
25
+ };
26
+ static args: {
27
+ name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
28
+ };
29
+ run(): Promise<void>;
30
+ /**
31
+ * Execute preview (dry_run mode)
32
+ */
33
+ private executePreview;
34
+ /**
35
+ * Execute destructive ability with confirmation
36
+ */
37
+ private executeDestructive;
38
+ /**
39
+ * Execute directly (read-only or non-destructive)
40
+ */
41
+ private executeDirect;
42
+ /**
43
+ * Wait for a batch job to complete using BatchManager
44
+ */
45
+ private waitForBatchJob;
46
+ /**
47
+ * Format watch result output for human display
48
+ */
49
+ private formatWatchResultOutput;
50
+ /**
51
+ * Resolve input from --input, --input-file, or stdin (--input -)
52
+ */
53
+ private resolveInput;
54
+ /**
55
+ * Read all data from stdin
56
+ */
57
+ private readStdin;
58
+ /**
59
+ * Format preview output for human display
60
+ */
61
+ private formatPreviewOutput;
62
+ /**
63
+ * Format execution output for human display
64
+ */
65
+ private formatExecutionOutput;
66
+ /**
67
+ * Format batch job output
68
+ */
69
+ private formatBatchOutput;
70
+ /**
71
+ * Output guidance for destructive abilities
72
+ */
73
+ private outputDestructiveGuidance;
74
+ }
75
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/commands/abilities/run.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,EAAE,WAAW,EAAe,MAAM,2BAA2B,CAAC;AAiBrE,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,WAAW;IACnD,MAAM,CAAC,WAAW,SAAwB;IAE1C,MAAM,CAAC,QAAQ,WAuBb;IAEF,MAAM,CAAC,KAAK;;;;;;;;;;;;MAkCV;IAEF,MAAM,CAAC,IAAI;;MAKT;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IA4F1B;;OAEG;YACW,cAAc;IA4B5B;;OAEG;YACW,kBAAkB;IAqHhC;;OAEG;YACW,aAAa;IAiD3B;;OAEG;YACW,eAAe;IA6C7B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAwC/B;;OAEG;YACW,YAAY;IAqC1B;;OAEG;YACW,SAAS;IAavB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAU3B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAc7B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAUzB;;OAEG;IACH,OAAO,CAAC,yBAAyB;CAalC"}
@@ -0,0 +1,468 @@
1
+ /**
2
+ * Abilities run command for mainwpcontrol
3
+ *
4
+ * Execute abilities with safety enforcement.
5
+ * Routes through SafetyController for destructive actions.
6
+ *
7
+ * INVARIANT: Commands and chat share the same execution path.
8
+ */
9
+ import { Args, Flags } from '@oclif/core';
10
+ import { readFile } from 'node:fs/promises';
11
+ import { resolve } from 'node:path';
12
+ import { BaseCommand, commonFlags } from '../../lib/base-command.js';
13
+ import { formatSuccess, formatWarning, formatPreview, formatHeading, formatKeyValue, } from '../../output/formatter.js';
14
+ import { InputError, MutualExclusionError } from '../../utils/errors.js';
15
+ import { getSafetyController } from '../../core/safety-controller.js';
16
+ import { getSchemaValidator } from '../../validation/schema-validator.js';
17
+ import { getInputSanitizer } from '../../validation/input-sanitizer.js';
18
+ import { promptForConfirmation, isInteractive } from '../../utils/prompt.js';
19
+ import { logDestructiveActionSafe } from '../../utils/audit-logger.js';
20
+ import { APIError } from '../../utils/errors.js';
21
+ export default class AbilitiesRun extends BaseCommand {
22
+ static description = 'Execute an ability';
23
+ static examples = [
24
+ // Read-only abilities
25
+ '<%= config.bin %> abilities run list-sites-v1',
26
+ '<%= config.bin %> abilities run list-sites-v1 --input \'{"status": "connected"}\'',
27
+ // Input from file or stdin
28
+ '<%= config.bin %> abilities run update-site-plugins-v1 --input-file params.json --confirm',
29
+ 'echo \'{"site_id": 5}\' | <%= config.bin %> abilities run list-sites-v1 --input -',
30
+ // Destructive abilities - preview first
31
+ '<%= config.bin %> abilities run delete-site-v1 --input \'{"site_id": 1}\' --dry-run',
32
+ // Destructive abilities - execute after preview
33
+ '<%= config.bin %> abilities run delete-site-v1 --input \'{"site_id": 1}\' --confirm',
34
+ // Wait for batch job completion
35
+ '<%= config.bin %> abilities run sync-sites-v1 --wait --json',
36
+ // JSON output for scripting
37
+ '<%= config.bin %> abilities run list-sites-v1 --json',
38
+ // Quiet mode (exit code only)
39
+ '<%= config.bin %> abilities run check-site-v1 --input \'{"site_id": 1}\' --quiet',
40
+ ];
41
+ static flags = {
42
+ ...commonFlags,
43
+ input: Flags.string({
44
+ char: 'i',
45
+ description: 'Input parameters as JSON (use "-" to read from stdin)',
46
+ default: '{}',
47
+ exclusive: ['input-file'],
48
+ }),
49
+ 'input-file': Flags.string({
50
+ description: 'Read input parameters from a JSON file',
51
+ exclusive: ['input'],
52
+ }),
53
+ 'dry-run': Flags.boolean({
54
+ description: 'Preview changes without executing (required for destructive abilities)',
55
+ default: false,
56
+ exclusive: ['confirm'],
57
+ }),
58
+ confirm: Flags.boolean({
59
+ description: 'Execute destructive ability (after preview)',
60
+ default: false,
61
+ exclusive: ['dry-run'],
62
+ }),
63
+ force: Flags.boolean({
64
+ description: 'Skip confirmation prompt (use with --confirm)',
65
+ default: false,
66
+ }),
67
+ wait: Flags.boolean({
68
+ description: 'Wait for batch job to complete (blocks until done)',
69
+ default: false,
70
+ }),
71
+ 'wait-timeout': Flags.integer({
72
+ description: 'Maximum seconds to wait for batch job (default: 300)',
73
+ default: 300,
74
+ }),
75
+ };
76
+ static args = {
77
+ name: Args.string({
78
+ description: 'Ability name (e.g., list-sites-v1, delete-site-v1)',
79
+ required: true,
80
+ }),
81
+ };
82
+ async run() {
83
+ const { args, flags } = await this.parse(AbilitiesRun);
84
+ await this.initCommon(flags);
85
+ const executor = await this.getExecutor();
86
+ const safetyController = getSafetyController();
87
+ const schemaValidator = getSchemaValidator();
88
+ const inputSanitizer = getInputSanitizer();
89
+ // Get ability metadata
90
+ const ability = await executor.getAbility(args.name);
91
+ if (!ability) {
92
+ throw new InputError(`Ability not found: ${args.name}. Run \`mainwpcontrol abilities list\` to see available abilities.`);
93
+ }
94
+ // Resolve input from --input, --input-file, or stdin
95
+ const rawInput = await this.resolveInput(flags.input, flags['input-file']);
96
+ // Parse input JSON
97
+ let input;
98
+ try {
99
+ input = JSON.parse(rawInput);
100
+ }
101
+ catch {
102
+ throw new InputError(`Invalid JSON input: ${rawInput}`);
103
+ }
104
+ // Sanitize input
105
+ input = inputSanitizer.sanitize(input);
106
+ // Validate input against schema if available
107
+ if (ability.input_schema) {
108
+ const validated = schemaValidator.validateOrThrow(input, ability.input_schema, ability.name);
109
+ // Use coerced values (type coercion, defaults applied) from validated clone
110
+ input = validated.coerced ?? input;
111
+ }
112
+ // Safety validation BEFORE any network call
113
+ const dryRun = flags['dry-run'];
114
+ const confirm = flags.confirm;
115
+ const requiresSafetyFlow = safetyController.requiresSafetyFlow(ability);
116
+ this.debugLog('Resolved ability execution', {
117
+ abilityName: ability.name,
118
+ dryRun,
119
+ confirm,
120
+ wait: flags.wait,
121
+ waitTimeoutSeconds: flags['wait-timeout'],
122
+ requiresSafetyFlow,
123
+ });
124
+ try {
125
+ safetyController.validateExecutionFlags(ability, dryRun, confirm);
126
+ }
127
+ catch (error) {
128
+ if (error instanceof MutualExclusionError) {
129
+ throw error;
130
+ }
131
+ // For destructive abilities without flags, provide guidance
132
+ if (requiresSafetyFlow) {
133
+ this.outputDestructiveGuidance(ability.name);
134
+ throw error;
135
+ }
136
+ throw error;
137
+ }
138
+ // Determine execution path
139
+ const shouldExecute = safetyController.shouldExecuteDirectly(ability, dryRun, confirm);
140
+ if (dryRun || !shouldExecute) {
141
+ // Preview mode — --dry-run always previews, regardless of ability classification
142
+ await this.executePreview(ability.name, input, dryRun);
143
+ }
144
+ else if (confirm && safetyController.requiresSafetyFlow(ability)) {
145
+ // Destructive execution with confirmation
146
+ await this.executeDestructive({
147
+ abilityName: ability.name,
148
+ input,
149
+ force: flags.force,
150
+ wait: flags.wait,
151
+ waitTimeout: flags['wait-timeout'],
152
+ });
153
+ }
154
+ else {
155
+ // Direct execution (read-only or non-destructive)
156
+ await this.executeDirect({
157
+ abilityName: ability.name,
158
+ input,
159
+ wait: flags.wait,
160
+ waitTimeout: flags['wait-timeout'],
161
+ });
162
+ }
163
+ }
164
+ /**
165
+ * Execute preview (dry_run mode)
166
+ */
167
+ async executePreview(abilityName, input, _dryRun) {
168
+ const executor = await this.getExecutor();
169
+ const result = await executor.execute(abilityName, input, { dryRun: true });
170
+ if (!result.success) {
171
+ throw new InputError(result.error?.message ?? 'Preview failed', result.error);
172
+ }
173
+ const safetyController = getSafetyController();
174
+ const ability = await executor.getAbility(abilityName);
175
+ const preview = safetyController.formatPreviewResult(ability, input, result);
176
+ this.output({
177
+ mode: 'preview',
178
+ ability: abilityName,
179
+ ...result,
180
+ preview,
181
+ }, () => this.formatPreviewOutput(preview));
182
+ }
183
+ /**
184
+ * Execute destructive ability with confirmation
185
+ */
186
+ async executeDestructive(opts) {
187
+ const { abilityName, input, force, wait, waitTimeout } = opts;
188
+ const executor = await this.getExecutor();
189
+ const safetyController = getSafetyController();
190
+ // Get preview data first for audit logging (gracefully handle failures)
191
+ let preview;
192
+ try {
193
+ const ability = await executor.getAbility(abilityName);
194
+ const previewResult = await executor.execute(abilityName, input, { dryRun: true });
195
+ if (previewResult.success && ability) {
196
+ preview = safetyController.formatPreviewResult(ability, input, previewResult);
197
+ }
198
+ }
199
+ catch {
200
+ // Preview failure is non-fatal - continue without preview data in audit
201
+ }
202
+ // Helper to build preview metadata for audit entries (spread-friendly)
203
+ const previewMeta = preview
204
+ ? { preview: { summary: preview.summary, affectedCount: preview.affected.length } }
205
+ : {};
206
+ // In non-interactive mode, require --force or fail
207
+ if (!isInteractive() && !force) {
208
+ await logDestructiveActionSafe({
209
+ abilityName,
210
+ ...previewMeta,
211
+ userDecision: 'declined',
212
+ input,
213
+ });
214
+ throw new InputError('Destructive operations require interactive confirmation or --force flag in non-interactive mode.');
215
+ }
216
+ // Interactive confirmation (unless --force)
217
+ if (!force) {
218
+ const confirmed = await promptForConfirmation(`Execute destructive ability "${abilityName}"?`);
219
+ if (!confirmed) {
220
+ await logDestructiveActionSafe({
221
+ abilityName,
222
+ ...previewMeta,
223
+ userDecision: 'declined',
224
+ input,
225
+ });
226
+ this.log(formatWarning('Operation cancelled by user.'));
227
+ return;
228
+ }
229
+ }
230
+ // Execute with confirm
231
+ const result = await executor.execute(abilityName, input, { confirm: true });
232
+ // Build execution result for audit
233
+ const executionResult = {
234
+ success: result.success,
235
+ };
236
+ if (result.error?.message) {
237
+ executionResult.error = getInputSanitizer().sanitizeErrorMessage(result.error.message);
238
+ }
239
+ // Log audit entry (fire-and-forget, covers both success and failure)
240
+ await logDestructiveActionSafe({
241
+ abilityName,
242
+ ...previewMeta,
243
+ userDecision: 'approved',
244
+ execution: executionResult,
245
+ input,
246
+ });
247
+ // Throw after audit logging if execution failed
248
+ if (!result.success) {
249
+ throw new APIError(result.error?.code ?? 'ABILITY_EXECUTION_ERROR', result.error?.message ?? 'Execution failed', undefined, result.error);
250
+ }
251
+ // Check for batch job
252
+ if (result.jobId) {
253
+ if (wait) {
254
+ await this.waitForBatchJob(abilityName, result.jobId, waitTimeout ?? 300);
255
+ return;
256
+ }
257
+ this.output({
258
+ mode: 'batch',
259
+ ability: abilityName,
260
+ jobId: result.jobId,
261
+ ...result,
262
+ }, () => this.formatBatchOutput(abilityName, result.jobId));
263
+ return;
264
+ }
265
+ this.output({
266
+ mode: 'execute',
267
+ ability: abilityName,
268
+ ...result,
269
+ }, () => this.formatExecutionOutput(abilityName, result.data));
270
+ }
271
+ /**
272
+ * Execute directly (read-only or non-destructive)
273
+ */
274
+ async executeDirect(opts) {
275
+ const { abilityName, input, wait, waitTimeout } = opts;
276
+ const executor = await this.getExecutor();
277
+ const result = await executor.execute(abilityName, input);
278
+ if (!result.success) {
279
+ throw new APIError(result.error?.code ?? 'ABILITY_EXECUTION_ERROR', result.error?.message ?? 'Execution failed', undefined, result.error);
280
+ }
281
+ // Check for batch job
282
+ if (result.jobId) {
283
+ if (wait) {
284
+ // --wait: poll until job completes
285
+ await this.waitForBatchJob(abilityName, result.jobId, waitTimeout ?? 300);
286
+ return;
287
+ }
288
+ this.output({
289
+ mode: 'batch',
290
+ ability: abilityName,
291
+ jobId: result.jobId,
292
+ ...result,
293
+ }, () => this.formatBatchOutput(abilityName, result.jobId));
294
+ return;
295
+ }
296
+ this.output({
297
+ mode: 'execute',
298
+ ability: abilityName,
299
+ ...result,
300
+ }, () => this.formatExecutionOutput(abilityName, result.data));
301
+ }
302
+ /**
303
+ * Wait for a batch job to complete using BatchManager
304
+ */
305
+ async waitForBatchJob(abilityName, jobId, timeoutSeconds) {
306
+ const batchManager = await this.getBatchManager();
307
+ const watchResult = await batchManager.resumeJob(jobId, {
308
+ maxWait: timeoutSeconds * 1000,
309
+ });
310
+ if (watchResult.timedOut) {
311
+ // Output partial results and throw API error for exit code 4
312
+ this.output({
313
+ mode: 'batch',
314
+ ability: abilityName,
315
+ jobId,
316
+ timedOut: true,
317
+ ...watchResult.status,
318
+ elapsed_ms: watchResult.elapsed,
319
+ }, () => formatWarning(`Batch job ${jobId} timed out after ${timeoutSeconds}s (partial results returned)`));
320
+ throw new APIError('BATCH_TIMEOUT', `Batch job timed out after ${timeoutSeconds}s`, undefined, { jobId, partialStatus: watchResult.status });
321
+ }
322
+ // Job completed (or failed)
323
+ const data = {
324
+ mode: 'batch',
325
+ ability: abilityName,
326
+ jobId,
327
+ timedOut: false,
328
+ ...watchResult.status,
329
+ elapsed_ms: watchResult.elapsed,
330
+ };
331
+ this.output(data, () => this.formatWatchResultOutput(abilityName, jobId, watchResult));
332
+ }
333
+ /**
334
+ * Format watch result output for human display
335
+ */
336
+ formatWatchResultOutput(abilityName, jobId, result) {
337
+ const { status, elapsed } = result;
338
+ const lines = [];
339
+ if (status.status === 'completed') {
340
+ lines.push(formatSuccess(`Batch job completed: ${abilityName}`));
341
+ }
342
+ else if (status.status === 'failed') {
343
+ lines.push(formatWarning(`Batch job failed: ${abilityName}`));
344
+ }
345
+ else {
346
+ lines.push(formatWarning(`Batch job ${status.status}: ${abilityName}`));
347
+ }
348
+ lines.push('');
349
+ lines.push(formatKeyValue('Job ID', jobId));
350
+ lines.push(formatKeyValue('Status', status.status));
351
+ lines.push(formatKeyValue('Elapsed', `${Math.round(elapsed / 1000)}s`));
352
+ if (status.processed !== undefined && status.total !== undefined) {
353
+ lines.push(formatKeyValue('Processed', `${status.processed}/${status.total}`));
354
+ }
355
+ if (status.results && status.results.length > 0) {
356
+ lines.push('');
357
+ lines.push(`${status.results.length} items processed`);
358
+ }
359
+ if (status.errors && status.errors.length > 0) {
360
+ lines.push('');
361
+ for (const error of status.errors) {
362
+ lines.push(formatWarning(` ${error.message}`));
363
+ }
364
+ }
365
+ return lines.join('\n');
366
+ }
367
+ /**
368
+ * Resolve input from --input, --input-file, or stdin (--input -)
369
+ */
370
+ async resolveInput(inputFlag, inputFilePath) {
371
+ // --input-file takes priority when provided
372
+ if (inputFilePath) {
373
+ // SECURITY: Reject null bytes before any path processing
374
+ if (inputFilePath.includes('\0')) {
375
+ throw new InputError('Invalid file path: contains null bytes');
376
+ }
377
+ const resolvedPath = resolve(inputFilePath);
378
+ try {
379
+ return await readFile(resolvedPath, 'utf-8');
380
+ }
381
+ catch (error) {
382
+ if (error.code === 'ENOENT') {
383
+ throw new InputError(`Input file not found: ${inputFilePath}`);
384
+ }
385
+ throw new InputError(`Failed to read input file: ${inputFilePath}: ${error instanceof Error ? error.message : String(error)}`);
386
+ }
387
+ }
388
+ // --input - means read from stdin
389
+ if (inputFlag === '-') {
390
+ const data = await this.readStdin();
391
+ if (!data) {
392
+ throw new InputError('No input received from stdin');
393
+ }
394
+ return data;
395
+ }
396
+ // Default: use --input value directly
397
+ return inputFlag;
398
+ }
399
+ /**
400
+ * Read all data from stdin
401
+ */
402
+ async readStdin() {
403
+ if (process.stdin.isTTY) {
404
+ return '';
405
+ }
406
+ return new Promise((resolve, reject) => {
407
+ const chunks = [];
408
+ process.stdin.setEncoding('utf-8');
409
+ process.stdin.on('data', (chunk) => chunks.push(chunk));
410
+ process.stdin.on('end', () => resolve(chunks.join('')));
411
+ process.stdin.on('error', reject);
412
+ });
413
+ }
414
+ /**
415
+ * Format preview output for human display
416
+ */
417
+ formatPreviewOutput(preview) {
418
+ const lines = [
419
+ formatHeading(`Preview: ${preview.abilityName}`),
420
+ '',
421
+ formatPreview(preview.summary, preview.affected),
422
+ ];
423
+ return lines.join('\n');
424
+ }
425
+ /**
426
+ * Format execution output for human display
427
+ */
428
+ formatExecutionOutput(abilityName, data) {
429
+ const lines = [
430
+ formatSuccess(`Executed: ${abilityName}`),
431
+ '',
432
+ ];
433
+ if (data !== undefined) {
434
+ lines.push(formatHeading('Result:'));
435
+ lines.push(JSON.stringify(data, null, 2));
436
+ }
437
+ return lines.join('\n');
438
+ }
439
+ /**
440
+ * Format batch job output
441
+ */
442
+ formatBatchOutput(abilityName, jobId) {
443
+ return [
444
+ formatSuccess(`Batch job started: ${abilityName}`),
445
+ '',
446
+ formatKeyValue('Job ID', jobId),
447
+ '',
448
+ `Monitor progress with: mainwpcontrol jobs watch ${jobId}`,
449
+ ].join('\n');
450
+ }
451
+ /**
452
+ * Output guidance for destructive abilities
453
+ */
454
+ outputDestructiveGuidance(abilityName) {
455
+ if (!this.jsonOutput) {
456
+ this.logToStderr('');
457
+ this.logToStderr(formatWarning(`"${abilityName}" is a destructive ability.`));
458
+ this.logToStderr('');
459
+ this.logToStderr('To preview changes:');
460
+ this.logToStderr(` mainwpcontrol abilities run ${abilityName} --dry-run --input '{...}'`);
461
+ this.logToStderr('');
462
+ this.logToStderr('To execute after preview:');
463
+ this.logToStderr(` mainwpcontrol abilities run ${abilityName} --confirm --input '{...}'`);
464
+ this.logToStderr('');
465
+ }
466
+ }
467
+ }
468
+ //# sourceMappingURL=run.js.map