@wootsup/yt-builder-mcp 1.0.0 → 1.1.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/README.md +18 -6
- package/bin/yt-builder-mcp.js +6 -0
- package/dist/auth.d.ts +86 -6
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +141 -8
- package/dist/auth.js.map +1 -1
- package/dist/errors/sanitize.d.ts.map +1 -1
- package/dist/errors/sanitize.js +10 -0
- package/dist/errors/sanitize.js.map +1 -1
- package/dist/gateway/essentials.d.ts +1 -1
- package/dist/gateway/essentials.d.ts.map +1 -1
- package/dist/gateway/essentials.js +15 -2
- package/dist/gateway/essentials.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +52 -16
- package/dist/index.js.map +1 -1
- package/dist/install-skill.d.ts +10 -2
- package/dist/install-skill.d.ts.map +1 -1
- package/dist/install-skill.js +18 -7
- package/dist/install-skill.js.map +1 -1
- package/dist/platform/detect.d.ts +64 -0
- package/dist/platform/detect.d.ts.map +1 -0
- package/dist/platform/detect.js +98 -0
- package/dist/platform/detect.js.map +1 -0
- package/dist/platform/index.d.ts +72 -0
- package/dist/platform/index.d.ts.map +1 -1
- package/dist/platform/index.js +138 -0
- package/dist/platform/index.js.map +1 -1
- package/dist/platform/joomla.d.ts +44 -0
- package/dist/platform/joomla.d.ts.map +1 -0
- package/dist/platform/joomla.js +46 -0
- package/dist/platform/joomla.js.map +1 -0
- package/dist/server.d.ts +61 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +86 -2
- package/dist/server.js.map +1 -1
- package/dist/setup-cli-confirm.d.ts +36 -0
- package/dist/setup-cli-confirm.d.ts.map +1 -0
- package/dist/setup-cli-confirm.js +59 -0
- package/dist/setup-cli-confirm.js.map +1 -0
- package/dist/setup-cli.d.ts +53 -1
- package/dist/setup-cli.d.ts.map +1 -1
- package/dist/setup-cli.js +525 -10
- package/dist/setup-cli.js.map +1 -1
- package/dist/setup-prompts.d.ts.map +1 -1
- package/dist/setup-prompts.js +12 -8
- package/dist/setup-prompts.js.map +1 -1
- package/dist/setup-wizard-types.d.ts +19 -3
- package/dist/setup-wizard-types.d.ts.map +1 -1
- package/dist/setup-wizard.d.ts.map +1 -1
- package/dist/setup-wizard.js +34 -1
- package/dist/setup-wizard.js.map +1 -1
- package/dist/sites/cli/add-site.d.ts +106 -0
- package/dist/sites/cli/add-site.d.ts.map +1 -0
- package/dist/sites/cli/add-site.js +185 -0
- package/dist/sites/cli/add-site.js.map +1 -0
- package/dist/sites/cli/list-sites.d.ts +40 -0
- package/dist/sites/cli/list-sites.d.ts.map +1 -0
- package/dist/sites/cli/list-sites.js +117 -0
- package/dist/sites/cli/list-sites.js.map +1 -0
- package/dist/sites/cli/remove-site.d.ts +60 -0
- package/dist/sites/cli/remove-site.d.ts.map +1 -0
- package/dist/sites/cli/remove-site.js +120 -0
- package/dist/sites/cli/remove-site.js.map +1 -0
- package/dist/sites/cli/set-default.d.ts +47 -0
- package/dist/sites/cli/set-default.d.ts.map +1 -0
- package/dist/sites/cli/set-default.js +69 -0
- package/dist/sites/cli/set-default.js.map +1 -0
- package/dist/sites/cli/test-site.d.ts +77 -0
- package/dist/sites/cli/test-site.d.ts.map +1 -0
- package/dist/sites/cli/test-site.js +146 -0
- package/dist/sites/cli/test-site.js.map +1 -0
- package/dist/sites/client-pool.d.ts +124 -0
- package/dist/sites/client-pool.d.ts.map +1 -0
- package/dist/sites/client-pool.js +172 -0
- package/dist/sites/client-pool.js.map +1 -0
- package/dist/sites/env-bridge.d.ts +55 -0
- package/dist/sites/env-bridge.d.ts.map +1 -0
- package/dist/sites/env-bridge.js +94 -0
- package/dist/sites/env-bridge.js.map +1 -0
- package/dist/sites/paths.d.ts +21 -0
- package/dist/sites/paths.d.ts.map +1 -0
- package/dist/sites/paths.js +37 -0
- package/dist/sites/paths.js.map +1 -0
- package/dist/sites/probe.d.ts +60 -0
- package/dist/sites/probe.d.ts.map +1 -0
- package/dist/sites/probe.js +112 -0
- package/dist/sites/probe.js.map +1 -0
- package/dist/sites/registry.d.ts +142 -0
- package/dist/sites/registry.d.ts.map +1 -0
- package/dist/sites/registry.js +163 -0
- package/dist/sites/registry.js.map +1 -0
- package/dist/sites/schema.d.ts +90 -0
- package/dist/sites/schema.d.ts.map +1 -0
- package/dist/sites/schema.js +71 -0
- package/dist/sites/schema.js.map +1 -0
- package/dist/sites/secret-resolver.d.ts +109 -0
- package/dist/sites/secret-resolver.d.ts.map +1 -0
- package/dist/sites/secret-resolver.js +189 -0
- package/dist/sites/secret-resolver.js.map +1 -0
- package/dist/sites/store.d.ts +82 -0
- package/dist/sites/store.d.ts.map +1 -0
- package/dist/sites/store.js +198 -0
- package/dist/sites/store.js.map +1 -0
- package/dist/sites/tools/index.d.ts +28 -0
- package/dist/sites/tools/index.d.ts.map +1 -0
- package/dist/sites/tools/index.js +30 -0
- package/dist/sites/tools/index.js.map +1 -0
- package/dist/sites/tools/sites-list.d.ts +48 -0
- package/dist/sites/tools/sites-list.d.ts.map +1 -0
- package/dist/sites/tools/sites-list.js +135 -0
- package/dist/sites/tools/sites-list.js.map +1 -0
- package/dist/sites/tools/sites-test.d.ts +47 -0
- package/dist/sites/tools/sites-test.d.ts.map +1 -0
- package/dist/sites/tools/sites-test.js +148 -0
- package/dist/sites/tools/sites-test.js.map +1 -0
- package/dist/skill-loader.d.ts +63 -3
- package/dist/skill-loader.d.ts.map +1 -1
- package/dist/skill-loader.js +123 -10
- package/dist/skill-loader.js.map +1 -1
- package/dist/tools/elements/builders.d.ts +2 -2
- package/dist/tools/elements/builders.d.ts.map +1 -1
- package/dist/tools/elements/builders.js +45 -23
- package/dist/tools/elements/builders.js.map +1 -1
- package/dist/tools/elements/handlers.d.ts +8 -1
- package/dist/tools/elements/handlers.d.ts.map +1 -1
- package/dist/tools/elements/handlers.js +37 -1
- package/dist/tools/elements/handlers.js.map +1 -1
- package/dist/tools/format/health-format.d.ts +12 -6
- package/dist/tools/format/health-format.d.ts.map +1 -1
- package/dist/tools/format/health-format.js +14 -11
- package/dist/tools/format/health-format.js.map +1 -1
- package/dist/tools/format/pages-format.d.ts +7 -1
- package/dist/tools/format/pages-format.d.ts.map +1 -1
- package/dist/tools/format/pages-format.js +25 -1
- package/dist/tools/format/pages-format.js.map +1 -1
- package/dist/tools/health.d.ts +2 -2
- package/dist/tools/health.d.ts.map +1 -1
- package/dist/tools/health.js +120 -47
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/index.d.ts +9 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +21 -7
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/inspection.d.ts +2 -2
- package/dist/tools/inspection.d.ts.map +1 -1
- package/dist/tools/inspection.js +67 -22
- package/dist/tools/inspection.js.map +1 -1
- package/dist/tools/multi-items/builders.d.ts +2 -2
- package/dist/tools/multi-items/builders.d.ts.map +1 -1
- package/dist/tools/multi-items/builders.js +21 -8
- package/dist/tools/multi-items/builders.js.map +1 -1
- package/dist/tools/pages/builders.d.ts +2 -2
- package/dist/tools/pages/builders.d.ts.map +1 -1
- package/dist/tools/pages/builders.js +36 -24
- package/dist/tools/pages/builders.js.map +1 -1
- package/dist/tools/pages/handlers-read.d.ts.map +1 -1
- package/dist/tools/pages/handlers-read.js +10 -0
- package/dist/tools/pages/handlers-read.js.map +1 -1
- package/dist/tools/pages/schemas.d.ts.map +1 -1
- package/dist/tools/pages/schemas.js +18 -0
- package/dist/tools/pages/schemas.js.map +1 -1
- package/dist/tools/pool-resolve-helper.d.ts +83 -0
- package/dist/tools/pool-resolve-helper.d.ts.map +1 -0
- package/dist/tools/pool-resolve-helper.js +148 -0
- package/dist/tools/pool-resolve-helper.js.map +1 -0
- package/dist/tools/shared-schemas.d.ts +10 -0
- package/dist/tools/shared-schemas.d.ts.map +1 -1
- package/dist/tools/shared-schemas.js +20 -2
- package/dist/tools/shared-schemas.js.map +1 -1
- package/dist/tools/sources/builders.d.ts +2 -2
- package/dist/tools/sources/builders.d.ts.map +1 -1
- package/dist/tools/sources/builders.js +27 -15
- package/dist/tools/sources/builders.js.map +1 -1
- package/dist/tools/tool-builder/index.d.ts +1 -1
- package/dist/tools/tool-builder/index.d.ts.map +1 -1
- package/dist/tools/tool-builder/index.js +1 -1
- package/dist/tools/tool-builder/index.js.map +1 -1
- package/dist/tools/tool-builder/results.d.ts +34 -0
- package/dist/tools/tool-builder/results.d.ts.map +1 -1
- package/dist/tools/tool-builder/results.js +86 -0
- package/dist/tools/tool-builder/results.js.map +1 -1
- package/manifest.json +27 -10
- package/package.json +3 -1
- package/skills/yt-builder-mcp/SKILL.md +846 -0
- package/skills/yootheme-builder/SKILL.md +0 -587
package/dist/setup-cli.js
CHANGED
|
@@ -24,7 +24,14 @@
|
|
|
24
24
|
import { ALL_CLIENTS } from './clients/index.js';
|
|
25
25
|
import { installSkill as defaultInstallSkill } from './install-skill.js';
|
|
26
26
|
import { SERVER_VERSION } from './server.js';
|
|
27
|
+
import { confirmTTY as defaultConfirmTTY } from './setup-cli-confirm.js';
|
|
27
28
|
import { DEFAULT_WIZARD_DEPS, runWizard as runWizardImpl } from './setup-wizard.js';
|
|
29
|
+
import { addSiteCommand, AddSiteError, DEFAULT_ADD_SITE_DEPS, } from './sites/cli/add-site.js';
|
|
30
|
+
import { DEFAULT_LIST_SITES_DEPS, listSitesCommand, } from './sites/cli/list-sites.js';
|
|
31
|
+
import { DEFAULT_REMOVE_SITE_DEPS, removeSiteCommand, RemoveSiteError, } from './sites/cli/remove-site.js';
|
|
32
|
+
import { DEFAULT_SET_DEFAULT_DEPS, setDefaultCommand, SetDefaultError, } from './sites/cli/set-default.js';
|
|
33
|
+
import { DEFAULT_TEST_SITE_DEPS, renderTestSiteLines, testSiteCommand, TestSiteError, } from './sites/cli/test-site.js';
|
|
34
|
+
import { defaultSitesFilePath } from './sites/paths.js';
|
|
28
35
|
// Re-export the wizard surface so consumers that imported from
|
|
29
36
|
// `./setup-cli` before the I5 split keep working without changes.
|
|
30
37
|
export { DEFAULT_WIZARD_DEPS, majorMinor, runWizard, } from './setup-wizard.js';
|
|
@@ -36,12 +43,26 @@ Commands:
|
|
|
36
43
|
~/.claude/skills/ and register it in ~/AGENTS.md.
|
|
37
44
|
Note: ~/.claude/skills/ is the universal-marker path
|
|
38
45
|
recognised by Claude Desktop and other AI clients.
|
|
46
|
+
|
|
47
|
+
Multi-site commands (W9):
|
|
48
|
+
add-site Add a site to ~/.config/yt-builder-mcp/sites.json.
|
|
49
|
+
Flags: --url <url>, --token <bearer> OR --token-ref
|
|
50
|
+
<op://...>, --platform auto|wordpress|joomla,
|
|
51
|
+
--label "...", --site-id <slug>, --default, --yes.
|
|
52
|
+
list-sites Show the configured sites (no network calls).
|
|
53
|
+
remove-site <id> Remove a site; auto-promotes the next as default.
|
|
54
|
+
Flag: --yes to skip the confirmation prompt.
|
|
55
|
+
set-default <id> Flip is_default to the given site_id.
|
|
56
|
+
test-site <id> Probe /health (no auth) + /etag (auth) for one site.
|
|
57
|
+
|
|
39
58
|
--version, -v Print the package version.
|
|
40
59
|
--help, -h, help Show this message.
|
|
41
60
|
|
|
42
61
|
Setup flags (for non-interactive / CI usage):
|
|
43
62
|
--non-interactive Skip all prompts; fail-fast on missing input.
|
|
44
|
-
--url <
|
|
63
|
+
--url <site-url> Site base URL (required with --non-interactive).
|
|
64
|
+
WordPress or Joomla — the wizard auto-detects the
|
|
65
|
+
platform from the URL shape.
|
|
45
66
|
--token <bearer> Bearer key (required with --non-interactive).
|
|
46
67
|
--client <id> AI client id to configure; repeatable. Valid ids:
|
|
47
68
|
claude-desktop, claude-code, cursor, zed, continue,
|
|
@@ -54,9 +75,12 @@ Example (non-interactive):
|
|
|
54
75
|
--url https://example.com \\
|
|
55
76
|
--token "$YTB_TOKEN"
|
|
56
77
|
|
|
57
|
-
Pickup mode (one-click setup
|
|
58
|
-
--pickup <url> Pickup URL revealed by the
|
|
59
|
-
|
|
78
|
+
Pickup mode (one-click setup from the admin "Setup wizard" flow):
|
|
79
|
+
--pickup <url> Pickup URL revealed by the host CMS admin (WordPress:
|
|
80
|
+
wp-admin → Tools → "YT Builder MCP"; Joomla:
|
|
81
|
+
Administrator → Components → "YT Builder MCP").
|
|
82
|
+
WordPress: https://example.com/wp-json/yt-builder-mcp/v1/setup/pickup
|
|
83
|
+
Joomla: https://example.com/api/index.php/v1/yt-builder-mcp/setup/pickup
|
|
60
84
|
--nonce <code> One-shot nonce revealed alongside the pickup URL.
|
|
61
85
|
Pairs 1:1 with --pickup (both or neither).
|
|
62
86
|
|
|
@@ -66,16 +90,31 @@ Pickup mode (one-click setup via wp-admin):
|
|
|
66
90
|
expires after 5 minutes. If --url or --token are also passed they are
|
|
67
91
|
ignored (a warning is printed to stderr).
|
|
68
92
|
|
|
69
|
-
Example (pickup):
|
|
93
|
+
Example (pickup — WordPress):
|
|
70
94
|
npx -y @wootsup/yt-builder-mcp setup \\
|
|
71
95
|
--pickup https://example.com/wp-json/yt-builder-mcp/v1/setup/pickup \\
|
|
72
96
|
--nonce "$NONCE" \\
|
|
73
97
|
--client claude-desktop
|
|
74
98
|
|
|
99
|
+
Example (pickup — Joomla):
|
|
100
|
+
npx -y @wootsup/yt-builder-mcp setup \\
|
|
101
|
+
--pickup https://example.com/api/index.php/v1/yt-builder-mcp/setup/pickup \\
|
|
102
|
+
--nonce "$NONCE" \\
|
|
103
|
+
--client claude-desktop
|
|
104
|
+
|
|
75
105
|
Environment (when launched by an AI client):
|
|
76
|
-
|
|
77
|
-
|
|
106
|
+
YTB_MCP_SITE_URL Host CMS base URL (e.g. https://example.com).
|
|
107
|
+
Works for BOTH WordPress and Joomla.
|
|
108
|
+
YTB_MCP_WP_URL DEPRECATED alias for YTB_MCP_SITE_URL — still
|
|
109
|
+
honoured for pre-Wave-7 configurations.
|
|
110
|
+
YTB_MCP_BEARER_TOKEN Bearer key from:
|
|
111
|
+
- WordPress: wp-admin → Tools → "YT Builder MCP"
|
|
112
|
+
- Joomla: Administrator → Components → "YT Builder MCP"
|
|
78
113
|
Format: ytb_(live|test)_<payload>.<signature>
|
|
114
|
+
YTB_MCP_PLATFORM Optional explicit platform: 'wordpress' or 'joomla'.
|
|
115
|
+
Set this to 'joomla' when YTB_MCP_SITE_URL is an
|
|
116
|
+
origin-only Joomla URL (no /api/index.php/ in path).
|
|
117
|
+
Defaults to URL-shape auto-detection.
|
|
79
118
|
YTB_MCP_TEST_MODE=1 Skip stdio loop (used by smoke tests).
|
|
80
119
|
|
|
81
120
|
Documentation: https://github.com/wootsup/yt-builder-mcp
|
|
@@ -265,12 +304,269 @@ export function buildPickupDeps(parsed, base = DEFAULT_WIZARD_DEPS) {
|
|
|
265
304
|
confirmContinue: async () => false,
|
|
266
305
|
};
|
|
267
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* Parse `add-site` flags from the tail of argv. Accepts both
|
|
309
|
+
* `--key value` and `--key=value` styles. Unknown flags are reported
|
|
310
|
+
* as errors so typos never silently disable validation.
|
|
311
|
+
*/
|
|
312
|
+
export function parseAddSiteArgs(argv) {
|
|
313
|
+
let url = '';
|
|
314
|
+
let token = '';
|
|
315
|
+
let tokenRef = '';
|
|
316
|
+
let label = '';
|
|
317
|
+
let siteId = '';
|
|
318
|
+
let platform;
|
|
319
|
+
let isDefault = false;
|
|
320
|
+
let yes = false;
|
|
321
|
+
let sitesFile;
|
|
322
|
+
const errors = [];
|
|
323
|
+
function consume(name, inline, i) {
|
|
324
|
+
if (inline !== undefined)
|
|
325
|
+
return { value: inline, nextIndex: i };
|
|
326
|
+
const next = argv[i + 1];
|
|
327
|
+
if (next === undefined || next.startsWith('--')) {
|
|
328
|
+
errors.push(`Flag ${name} requires a value.`);
|
|
329
|
+
return { value: '', nextIndex: i };
|
|
330
|
+
}
|
|
331
|
+
return { value: next, nextIndex: i + 1 };
|
|
332
|
+
}
|
|
333
|
+
for (let i = 0; i < argv.length; i++) {
|
|
334
|
+
const raw = argv[i];
|
|
335
|
+
const eq = raw.indexOf('=');
|
|
336
|
+
const flag = eq >= 0 ? raw.slice(0, eq) : raw;
|
|
337
|
+
const inline = eq >= 0 ? raw.slice(eq + 1) : undefined;
|
|
338
|
+
switch (flag) {
|
|
339
|
+
case '--url': {
|
|
340
|
+
const r = consume('--url', inline, i);
|
|
341
|
+
url = r.value.trim().replace(/\/+$/, '');
|
|
342
|
+
i = r.nextIndex;
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
case '--token': {
|
|
346
|
+
const r = consume('--token', inline, i);
|
|
347
|
+
token = r.value.trim();
|
|
348
|
+
i = r.nextIndex;
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
case '--token-ref': {
|
|
352
|
+
const r = consume('--token-ref', inline, i);
|
|
353
|
+
tokenRef = r.value.trim();
|
|
354
|
+
i = r.nextIndex;
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
case '--platform': {
|
|
358
|
+
const r = consume('--platform', inline, i);
|
|
359
|
+
const v = r.value.trim();
|
|
360
|
+
if (v === 'auto' || v === 'wordpress' || v === 'joomla') {
|
|
361
|
+
platform = v;
|
|
362
|
+
}
|
|
363
|
+
else if (v.length > 0) {
|
|
364
|
+
errors.push(`--platform must be one of auto, wordpress, joomla (got "${v}").`);
|
|
365
|
+
}
|
|
366
|
+
i = r.nextIndex;
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
case '--label': {
|
|
370
|
+
const r = consume('--label', inline, i);
|
|
371
|
+
label = r.value;
|
|
372
|
+
i = r.nextIndex;
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
case '--site-id': {
|
|
376
|
+
const r = consume('--site-id', inline, i);
|
|
377
|
+
siteId = r.value.trim();
|
|
378
|
+
i = r.nextIndex;
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
case '--default': {
|
|
382
|
+
isDefault = true;
|
|
383
|
+
if (inline !== undefined) {
|
|
384
|
+
errors.push('Flag --default does not take a value.');
|
|
385
|
+
}
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
case '--yes':
|
|
389
|
+
case '-y': {
|
|
390
|
+
yes = true;
|
|
391
|
+
if (inline !== undefined) {
|
|
392
|
+
errors.push(`Flag ${flag} does not take a value.`);
|
|
393
|
+
}
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
case '--sites-file': {
|
|
397
|
+
const r = consume('--sites-file', inline, i);
|
|
398
|
+
sitesFile = r.value.trim();
|
|
399
|
+
i = r.nextIndex;
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
case '--help':
|
|
403
|
+
case '-h': {
|
|
404
|
+
errors.push('HELP_REQUESTED');
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
default: {
|
|
408
|
+
errors.push(`Unknown add-site flag: ${flag}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
const parsed = {
|
|
413
|
+
url,
|
|
414
|
+
token,
|
|
415
|
+
tokenRef,
|
|
416
|
+
label,
|
|
417
|
+
siteId: siteId.length > 0 ? siteId : 'default',
|
|
418
|
+
default: isDefault,
|
|
419
|
+
yes,
|
|
420
|
+
...(platform !== undefined ? { platform } : {}),
|
|
421
|
+
...(sitesFile !== undefined ? { sitesFile } : {}),
|
|
422
|
+
errors,
|
|
423
|
+
};
|
|
424
|
+
return parsed;
|
|
425
|
+
}
|
|
426
|
+
const ADD_SITE_HELP = `Usage: yt-builder-mcp add-site [flags]\n\n` +
|
|
427
|
+
`Flags:\n` +
|
|
428
|
+
` --url <url> (required) Site base URL (WordPress or Joomla origin).\n` +
|
|
429
|
+
` --token <bearer> Inline bearer (mutually exclusive with --token-ref).\n` +
|
|
430
|
+
` --token-ref <op://...> 1Password Secret Reference for the bearer.\n` +
|
|
431
|
+
` --platform <kind> auto|wordpress|joomla (default: auto).\n` +
|
|
432
|
+
` --label "<text>" Human-readable label (≤120 chars).\n` +
|
|
433
|
+
` --site-id <slug> Registry key (default: "default").\n` +
|
|
434
|
+
` --default Mark this site as the default.\n` +
|
|
435
|
+
` --yes, -y Overwrite an existing site_id without confirming.\n` +
|
|
436
|
+
` --sites-file <path> Override sites.json location.\n\n` +
|
|
437
|
+
`Notes:\n` +
|
|
438
|
+
` - The first site you add becomes the default automatically.\n` +
|
|
439
|
+
` - Use --token OR --token-ref, not both.\n` +
|
|
440
|
+
` - URL is normalised (trailing slashes stripped).\n\n` +
|
|
441
|
+
`Example:\n` +
|
|
442
|
+
` yt-builder-mcp add-site --url https://acme.example.com \\\n` +
|
|
443
|
+
` --token "$YTB_TOKEN" --site-id wp-acme --label "ACME WP"\n`;
|
|
444
|
+
const REMOVE_SITE_HELP = `Usage: yt-builder-mcp remove-site <site_id> [--yes] [--sites-file <path>]\n\n` +
|
|
445
|
+
`Removes the site from sites.json. If the default site is removed and at\n` +
|
|
446
|
+
`least one other site remains, the next site in insertion order becomes\n` +
|
|
447
|
+
`the new default. Removing the only site clears default_site_id and\n` +
|
|
448
|
+
`leaves an empty registry.\n\n` +
|
|
449
|
+
`Confirmation is required unless --yes is passed. In non-TTY contexts\n` +
|
|
450
|
+
`(CI, piped stdin) the subcommand fails with CONFIRM_REQUIRED rather\n` +
|
|
451
|
+
`than silently deleting.\n\n` +
|
|
452
|
+
`Example:\n` +
|
|
453
|
+
` yt-builder-mcp remove-site wp-acme --yes\n`;
|
|
454
|
+
const SET_DEFAULT_HELP = `Usage: yt-builder-mcp set-default <site_id> [--yes] [--sites-file <path>]\n\n` +
|
|
455
|
+
`Changes which site every subsequent tool call routes to by default\n` +
|
|
456
|
+
`when site_id is omitted. Confirmation is required unless --yes is\n` +
|
|
457
|
+
`passed — in non-TTY contexts the subcommand fails with\n` +
|
|
458
|
+
`SET_DEFAULT_CONFIRM_REQUIRED rather than silently flipping.\n\n` +
|
|
459
|
+
`Example:\n` +
|
|
460
|
+
` yt-builder-mcp set-default wp-beta --yes\n`;
|
|
461
|
+
const TEST_SITE_HELP = `Usage: yt-builder-mcp test-site <site_id> [--sites-file <path>]\n\n` +
|
|
462
|
+
`Probes /health (unauthenticated) and /etag (authenticated) in parallel\n` +
|
|
463
|
+
`against the configured Bearer. Returns plugin reachability + Bearer\n` +
|
|
464
|
+
`validity in one call. Does NOT mutate the registry or the site.\n\n` +
|
|
465
|
+
`Example:\n` +
|
|
466
|
+
` yt-builder-mcp test-site wp-acme\n`;
|
|
467
|
+
const LIST_SITES_HELP = `Usage: yt-builder-mcp list-sites [--sites-file <path>]\n\n` +
|
|
468
|
+
`Prints the configured sites table (site_id, url, platform, label,\n` +
|
|
469
|
+
`bearer_source, is_default). No network calls — pure registry read.\n\n` +
|
|
470
|
+
`Example:\n` +
|
|
471
|
+
` yt-builder-mcp list-sites\n`;
|
|
472
|
+
function parseIdArgs(argv, allowYes) {
|
|
473
|
+
let siteId = '';
|
|
474
|
+
let yes = false;
|
|
475
|
+
let sitesFile;
|
|
476
|
+
const errors = [];
|
|
477
|
+
let helpRequested = false;
|
|
478
|
+
for (let i = 0; i < argv.length; i++) {
|
|
479
|
+
const raw = argv[i];
|
|
480
|
+
const eq = raw.indexOf('=');
|
|
481
|
+
const flag = eq >= 0 ? raw.slice(0, eq) : raw;
|
|
482
|
+
const inline = eq >= 0 ? raw.slice(eq + 1) : undefined;
|
|
483
|
+
if (flag === '--help' || flag === '-h') {
|
|
484
|
+
helpRequested = true;
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
if (flag === '--yes' || flag === '-y') {
|
|
488
|
+
if (!allowYes) {
|
|
489
|
+
errors.push(`Unknown flag for this subcommand: ${flag}`);
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
yes = true;
|
|
493
|
+
}
|
|
494
|
+
if (inline !== undefined)
|
|
495
|
+
errors.push(`Flag ${flag} does not take a value.`);
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
if (flag === '--sites-file') {
|
|
499
|
+
const next = inline ?? argv[i + 1];
|
|
500
|
+
if (next === undefined || next.startsWith('--')) {
|
|
501
|
+
errors.push('Flag --sites-file requires a value.');
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
sitesFile = next.trim();
|
|
505
|
+
if (inline === undefined)
|
|
506
|
+
i += 1;
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (flag.startsWith('--') || flag.startsWith('-')) {
|
|
510
|
+
errors.push(`Unknown flag: ${flag}`);
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
if (siteId !== '') {
|
|
514
|
+
errors.push(`Too many positional arguments; got "${siteId}" and "${flag}".`);
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
siteId = flag.trim();
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
siteId,
|
|
521
|
+
yes,
|
|
522
|
+
...(sitesFile !== undefined ? { sitesFile } : {}),
|
|
523
|
+
errors,
|
|
524
|
+
helpRequested,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
268
527
|
export async function runCli(argv, deps) {
|
|
269
528
|
const out = deps?.log ?? ((s) => process.stdout.write(`${s}\n`));
|
|
270
529
|
const err = deps?.error ?? ((s) => process.stderr.write(`${s}\n`));
|
|
271
530
|
const wizard = deps?.runWizard ?? ((wizDeps) => runWizardImpl(wizDeps));
|
|
272
531
|
const install = deps?.installSkill ?? defaultInstallSkill;
|
|
532
|
+
const resolveSitesPath = deps?.resolveSitesPath ?? (() => defaultSitesFilePath());
|
|
533
|
+
const addSiteDeps = deps?.addSiteDeps ?? DEFAULT_ADD_SITE_DEPS;
|
|
534
|
+
const listSitesDeps = deps?.listSitesDeps ?? DEFAULT_LIST_SITES_DEPS;
|
|
535
|
+
// W12-R2A: wire a TTY confirm hook into the destructive-subcommand
|
|
536
|
+
// deps bags. Tests that supply `removeSiteDeps`/`setDefaultDeps`
|
|
537
|
+
// directly retain full control over the confirm channel (so the
|
|
538
|
+
// existing unit-tests keep working unchanged); only the default
|
|
539
|
+
// dispatcher path gets the `defaultConfirmTTY` injection.
|
|
540
|
+
const confirmHook = deps?.confirm ?? defaultConfirmTTY;
|
|
541
|
+
const removeSiteDeps = deps?.removeSiteDeps
|
|
542
|
+
?? { ...DEFAULT_REMOVE_SITE_DEPS, confirm: confirmHook };
|
|
543
|
+
const setDefaultDeps = deps?.setDefaultDeps
|
|
544
|
+
?? { ...DEFAULT_SET_DEFAULT_DEPS, confirm: confirmHook };
|
|
545
|
+
const testSiteDeps = deps?.testSiteDeps ?? DEFAULT_TEST_SITE_DEPS;
|
|
546
|
+
const addSiteFromWizard = deps?.addSiteFromWizard
|
|
547
|
+
?? ((args, path) => addSiteCommand(args, path, addSiteDeps));
|
|
273
548
|
const [subcommand] = argv;
|
|
549
|
+
// Build a `persistSite` callback for the wizard that translates
|
|
550
|
+
// the collected WizardAnswers into an `addSiteCommand` call so the
|
|
551
|
+
// sites.json registry stays in sync with the env-var path. This is
|
|
552
|
+
// the W9 "wizard internally invokes add-site" wiring.
|
|
553
|
+
const buildPersistSite = () => {
|
|
554
|
+
return async (answers) => {
|
|
555
|
+
const siteId = (answers.siteId !== undefined && answers.siteId.length > 0)
|
|
556
|
+
? answers.siteId
|
|
557
|
+
: 'default';
|
|
558
|
+
const path = resolveSitesPath();
|
|
559
|
+
const args = {
|
|
560
|
+
url: answers.wpUrl,
|
|
561
|
+
token: answers.bearer,
|
|
562
|
+
siteId,
|
|
563
|
+
default: true,
|
|
564
|
+
yes: true,
|
|
565
|
+
platform: 'auto',
|
|
566
|
+
};
|
|
567
|
+
await addSiteFromWizard(args, path);
|
|
568
|
+
};
|
|
569
|
+
};
|
|
274
570
|
if (subcommand === undefined || subcommand === 'setup') {
|
|
275
571
|
const setupArgs = argv[0] === 'setup' ? argv.slice(1) : argv;
|
|
276
572
|
const parsed = parseSetupArgs(setupArgs);
|
|
@@ -278,6 +574,10 @@ export async function runCli(argv, deps) {
|
|
|
278
574
|
for (const line of parsed.warnings)
|
|
279
575
|
err(`yt-builder-mcp: warning: ${line}`);
|
|
280
576
|
const pickupMode = (parsed.pickup ?? '') !== '' || (parsed.nonce ?? '') !== '';
|
|
577
|
+
const persistSite = buildPersistSite();
|
|
578
|
+
function withPersist(base) {
|
|
579
|
+
return { ...base, persistSite };
|
|
580
|
+
}
|
|
281
581
|
if (pickupMode) {
|
|
282
582
|
if (parsed.errors.length > 0) {
|
|
283
583
|
for (const line of parsed.errors)
|
|
@@ -285,7 +585,7 @@ export async function runCli(argv, deps) {
|
|
|
285
585
|
err(HELP_TEXT);
|
|
286
586
|
return 2;
|
|
287
587
|
}
|
|
288
|
-
return wizard(buildPickupDeps(parsed));
|
|
588
|
+
return wizard(withPersist(buildPickupDeps(parsed)));
|
|
289
589
|
}
|
|
290
590
|
if (parsed.nonInteractive) {
|
|
291
591
|
if (parsed.errors.length > 0) {
|
|
@@ -294,7 +594,7 @@ export async function runCli(argv, deps) {
|
|
|
294
594
|
err(HELP_TEXT);
|
|
295
595
|
return 2;
|
|
296
596
|
}
|
|
297
|
-
return wizard(buildNonInteractiveDeps(parsed));
|
|
597
|
+
return wizard(withPersist(buildNonInteractiveDeps(parsed)));
|
|
298
598
|
}
|
|
299
599
|
if (parsed.errors.length > 0) {
|
|
300
600
|
for (const line of parsed.errors)
|
|
@@ -302,7 +602,7 @@ export async function runCli(argv, deps) {
|
|
|
302
602
|
err(HELP_TEXT);
|
|
303
603
|
return 1;
|
|
304
604
|
}
|
|
305
|
-
return wizard();
|
|
605
|
+
return wizard(withPersist(DEFAULT_WIZARD_DEPS));
|
|
306
606
|
}
|
|
307
607
|
switch (subcommand) {
|
|
308
608
|
case 'install-skill':
|
|
@@ -323,6 +623,221 @@ export async function runCli(argv, deps) {
|
|
|
323
623
|
return 2;
|
|
324
624
|
}
|
|
325
625
|
}
|
|
626
|
+
case 'add-site': {
|
|
627
|
+
const tail = argv.slice(1);
|
|
628
|
+
const parsed = parseAddSiteArgs(tail);
|
|
629
|
+
if (parsed.errors.some((e) => e === 'HELP_REQUESTED')) {
|
|
630
|
+
out(ADD_SITE_HELP);
|
|
631
|
+
return 0;
|
|
632
|
+
}
|
|
633
|
+
if (parsed.errors.length > 0) {
|
|
634
|
+
for (const line of parsed.errors)
|
|
635
|
+
err(`yt-builder-mcp: ${line}`);
|
|
636
|
+
err(ADD_SITE_HELP);
|
|
637
|
+
return 2;
|
|
638
|
+
}
|
|
639
|
+
const path = parsed.sitesFile ?? resolveSitesPath();
|
|
640
|
+
const args = {
|
|
641
|
+
url: parsed.url,
|
|
642
|
+
...(parsed.token.length > 0 ? { token: parsed.token } : {}),
|
|
643
|
+
...(parsed.tokenRef.length > 0 ? { tokenRef: parsed.tokenRef } : {}),
|
|
644
|
+
...(parsed.platform !== undefined ? { platform: parsed.platform } : {}),
|
|
645
|
+
...(parsed.label.length > 0 ? { label: parsed.label } : {}),
|
|
646
|
+
siteId: parsed.siteId,
|
|
647
|
+
default: parsed.default,
|
|
648
|
+
yes: parsed.yes,
|
|
649
|
+
};
|
|
650
|
+
try {
|
|
651
|
+
const result = await addSiteCommand(args, path, addSiteDeps);
|
|
652
|
+
const stateBits = [];
|
|
653
|
+
if (result.becameDefault)
|
|
654
|
+
stateBits.push('default — first site');
|
|
655
|
+
else if (result.defaultRequested)
|
|
656
|
+
stateBits.push('default');
|
|
657
|
+
if (result.overwritten)
|
|
658
|
+
stateBits.push('overwritten');
|
|
659
|
+
const state = stateBits.length > 0 ? ` (${stateBits.join(', ')})` : '';
|
|
660
|
+
const verb = result.overwritten ? 'updated' : 'added';
|
|
661
|
+
out(`✓ Site "${result.siteId}" ${verb}${state}.`);
|
|
662
|
+
out(` Path: ${result.path}`);
|
|
663
|
+
out('Restart your AI client to pick up the new site list.');
|
|
664
|
+
return 0;
|
|
665
|
+
}
|
|
666
|
+
catch (e) {
|
|
667
|
+
if (e instanceof AddSiteError) {
|
|
668
|
+
err(`yt-builder-mcp: add-site failed [${e.code}]: ${e.message}`);
|
|
669
|
+
return 2;
|
|
670
|
+
}
|
|
671
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
672
|
+
err(`yt-builder-mcp: add-site failed: ${msg}`);
|
|
673
|
+
return 2;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
case 'list-sites': {
|
|
677
|
+
const tail = argv.slice(1);
|
|
678
|
+
const parsed = parseIdArgs(tail, false);
|
|
679
|
+
// list-sites takes no positional id; if one slipped in it's
|
|
680
|
+
// a usage error (parseIdArgs allows zero or one).
|
|
681
|
+
if (parsed.helpRequested) {
|
|
682
|
+
out(LIST_SITES_HELP);
|
|
683
|
+
return 0;
|
|
684
|
+
}
|
|
685
|
+
if (parsed.siteId !== '') {
|
|
686
|
+
err(`yt-builder-mcp: list-sites takes no positional argument (got "${parsed.siteId}").`);
|
|
687
|
+
err(LIST_SITES_HELP);
|
|
688
|
+
return 2;
|
|
689
|
+
}
|
|
690
|
+
if (parsed.errors.length > 0) {
|
|
691
|
+
for (const line of parsed.errors)
|
|
692
|
+
err(`yt-builder-mcp: ${line}`);
|
|
693
|
+
err(LIST_SITES_HELP);
|
|
694
|
+
return 2;
|
|
695
|
+
}
|
|
696
|
+
const path = parsed.sitesFile ?? resolveSitesPath();
|
|
697
|
+
try {
|
|
698
|
+
const lines = await listSitesCommand(path, listSitesDeps);
|
|
699
|
+
for (const line of lines)
|
|
700
|
+
out(line);
|
|
701
|
+
return 0;
|
|
702
|
+
}
|
|
703
|
+
catch (e) {
|
|
704
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
705
|
+
err(`yt-builder-mcp: list-sites failed: ${msg}`);
|
|
706
|
+
return 2;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
case 'remove-site': {
|
|
710
|
+
const tail = argv.slice(1);
|
|
711
|
+
const parsed = parseIdArgs(tail, true);
|
|
712
|
+
if (parsed.helpRequested) {
|
|
713
|
+
out(REMOVE_SITE_HELP);
|
|
714
|
+
return 0;
|
|
715
|
+
}
|
|
716
|
+
if (parsed.siteId === '') {
|
|
717
|
+
err('yt-builder-mcp: remove-site requires a <site_id> argument.');
|
|
718
|
+
err(REMOVE_SITE_HELP);
|
|
719
|
+
return 2;
|
|
720
|
+
}
|
|
721
|
+
if (parsed.errors.length > 0) {
|
|
722
|
+
for (const line of parsed.errors)
|
|
723
|
+
err(`yt-builder-mcp: ${line}`);
|
|
724
|
+
err(REMOVE_SITE_HELP);
|
|
725
|
+
return 2;
|
|
726
|
+
}
|
|
727
|
+
const path = parsed.sitesFile ?? resolveSitesPath();
|
|
728
|
+
try {
|
|
729
|
+
const result = await removeSiteCommand(parsed.siteId, path, { yes: parsed.yes }, removeSiteDeps);
|
|
730
|
+
if (result.cancelled) {
|
|
731
|
+
out(`Cancelled — site "${result.siteId}" not removed.`);
|
|
732
|
+
return 0;
|
|
733
|
+
}
|
|
734
|
+
out(`✓ Site "${result.siteId}" removed.`);
|
|
735
|
+
if (result.promoted !== undefined) {
|
|
736
|
+
out(` Default promoted: ${result.promoted}`);
|
|
737
|
+
}
|
|
738
|
+
if (result.nowEmpty) {
|
|
739
|
+
out(' (registry is now empty)');
|
|
740
|
+
}
|
|
741
|
+
out(` Path: ${result.path}`);
|
|
742
|
+
out('Restart your AI client to pick up the new site list.');
|
|
743
|
+
return 0;
|
|
744
|
+
}
|
|
745
|
+
catch (e) {
|
|
746
|
+
if (e instanceof RemoveSiteError) {
|
|
747
|
+
err(`yt-builder-mcp: remove-site failed [${e.code}]: ${e.message}`);
|
|
748
|
+
return 2;
|
|
749
|
+
}
|
|
750
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
751
|
+
err(`yt-builder-mcp: remove-site failed: ${msg}`);
|
|
752
|
+
return 2;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
case 'set-default': {
|
|
756
|
+
const tail = argv.slice(1);
|
|
757
|
+
// W12-R2A: set-default is a routing-destructive op (every
|
|
758
|
+
// subsequent default-routed tool call lands on a different
|
|
759
|
+
// site). Allow --yes so CI can flip without a prompt.
|
|
760
|
+
const parsed = parseIdArgs(tail, true);
|
|
761
|
+
if (parsed.helpRequested) {
|
|
762
|
+
out(SET_DEFAULT_HELP);
|
|
763
|
+
return 0;
|
|
764
|
+
}
|
|
765
|
+
if (parsed.siteId === '') {
|
|
766
|
+
err('yt-builder-mcp: set-default requires a <site_id> argument.');
|
|
767
|
+
err(SET_DEFAULT_HELP);
|
|
768
|
+
return 2;
|
|
769
|
+
}
|
|
770
|
+
if (parsed.errors.length > 0) {
|
|
771
|
+
for (const line of parsed.errors)
|
|
772
|
+
err(`yt-builder-mcp: ${line}`);
|
|
773
|
+
err(SET_DEFAULT_HELP);
|
|
774
|
+
return 2;
|
|
775
|
+
}
|
|
776
|
+
const path = parsed.sitesFile ?? resolveSitesPath();
|
|
777
|
+
try {
|
|
778
|
+
const result = await setDefaultCommand(parsed.siteId, path, { yes: parsed.yes }, setDefaultDeps);
|
|
779
|
+
if (result.cancelled) {
|
|
780
|
+
out(`Cancelled — default unchanged.`);
|
|
781
|
+
return 0;
|
|
782
|
+
}
|
|
783
|
+
const prior = result.previousDefault !== null
|
|
784
|
+
? `(was: ${result.previousDefault})`
|
|
785
|
+
: '(no previous default)';
|
|
786
|
+
out(`✓ Default set to "${result.siteId}" ${prior}.`);
|
|
787
|
+
out(` Path: ${result.path}`);
|
|
788
|
+
out('Restart your AI client to pick up the new site list.');
|
|
789
|
+
return 0;
|
|
790
|
+
}
|
|
791
|
+
catch (e) {
|
|
792
|
+
if (e instanceof SetDefaultError) {
|
|
793
|
+
err(`yt-builder-mcp: set-default failed [${e.code}]: ${e.message}`);
|
|
794
|
+
return 2;
|
|
795
|
+
}
|
|
796
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
797
|
+
err(`yt-builder-mcp: set-default failed: ${msg}`);
|
|
798
|
+
return 2;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
case 'test-site': {
|
|
802
|
+
const tail = argv.slice(1);
|
|
803
|
+
const parsed = parseIdArgs(tail, false);
|
|
804
|
+
if (parsed.helpRequested) {
|
|
805
|
+
out(TEST_SITE_HELP);
|
|
806
|
+
return 0;
|
|
807
|
+
}
|
|
808
|
+
if (parsed.siteId === '') {
|
|
809
|
+
err('yt-builder-mcp: test-site requires a <site_id> argument.');
|
|
810
|
+
err(TEST_SITE_HELP);
|
|
811
|
+
return 2;
|
|
812
|
+
}
|
|
813
|
+
if (parsed.errors.length > 0) {
|
|
814
|
+
for (const line of parsed.errors)
|
|
815
|
+
err(`yt-builder-mcp: ${line}`);
|
|
816
|
+
err(TEST_SITE_HELP);
|
|
817
|
+
return 2;
|
|
818
|
+
}
|
|
819
|
+
const path = parsed.sitesFile ?? resolveSitesPath();
|
|
820
|
+
try {
|
|
821
|
+
const result = await testSiteCommand(parsed.siteId, path, testSiteDeps);
|
|
822
|
+
for (const line of renderTestSiteLines(result))
|
|
823
|
+
out(line);
|
|
824
|
+
const exitCode = result.unknownSite
|
|
825
|
+
|| !result.pluginReachable
|
|
826
|
+
|| !result.bearerValid
|
|
827
|
+
? 2
|
|
828
|
+
: 0;
|
|
829
|
+
return exitCode;
|
|
830
|
+
}
|
|
831
|
+
catch (e) {
|
|
832
|
+
if (e instanceof TestSiteError) {
|
|
833
|
+
err(`yt-builder-mcp: test-site failed [${e.code}]: ${e.message}`);
|
|
834
|
+
return 2;
|
|
835
|
+
}
|
|
836
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
837
|
+
err(`yt-builder-mcp: test-site failed: ${msg}`);
|
|
838
|
+
return 2;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
326
841
|
case '--version':
|
|
327
842
|
case '-v': {
|
|
328
843
|
out(SERVER_VERSION);
|