@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,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
|