@wootsup/mcp 0.1.0 → 0.4.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/CHANGELOG.md +157 -83
- package/README.md +31 -27
- package/SECURITY.md +15 -6
- package/dist/auth/keychain.d.ts +27 -1
- package/dist/auth/keychain.js +48 -2
- package/dist/auth/keychain.js.map +1 -1
- package/dist/catalog/build-catalog.d.ts +31 -0
- package/dist/catalog/build-catalog.js +68 -0
- package/dist/catalog/build-catalog.js.map +1 -0
- package/dist/cli-hint.d.ts +22 -0
- package/dist/cli-hint.js +55 -0
- package/dist/cli-hint.js.map +1 -0
- package/dist/index.js +129 -22
- package/dist/index.js.map +1 -1
- package/dist/install-skill.js +1 -1
- package/dist/modules/apimapper/auto-layout.d.ts +21 -0
- package/dist/modules/apimapper/auto-layout.js +54 -0
- package/dist/modules/apimapper/auto-layout.js.map +1 -0
- package/dist/modules/apimapper/cache.js +25 -17
- package/dist/modules/apimapper/cache.js.map +1 -1
- package/dist/modules/apimapper/client.d.ts +115 -4
- package/dist/modules/apimapper/client.js +699 -304
- package/dist/modules/apimapper/client.js.map +1 -1
- package/dist/modules/apimapper/connections-format.d.ts +31 -1
- package/dist/modules/apimapper/connections-format.js +97 -5
- package/dist/modules/apimapper/connections-format.js.map +1 -1
- package/dist/modules/apimapper/connections.d.ts +9 -7
- package/dist/modules/apimapper/connections.js +449 -127
- package/dist/modules/apimapper/connections.js.map +1 -1
- package/dist/modules/apimapper/credential-sanitizer.d.ts +5 -0
- package/dist/modules/apimapper/credential-sanitizer.js +60 -1
- package/dist/modules/apimapper/credential-sanitizer.js.map +1 -1
- package/dist/modules/apimapper/credentials.js +105 -61
- package/dist/modules/apimapper/credentials.js.map +1 -1
- package/dist/modules/apimapper/diagnose.js +21 -2
- package/dist/modules/apimapper/diagnose.js.map +1 -1
- package/dist/modules/apimapper/elicitation.d.ts +29 -0
- package/dist/modules/apimapper/elicitation.js +62 -0
- package/dist/modules/apimapper/elicitation.js.map +1 -1
- package/dist/modules/apimapper/example-extract.d.ts +13 -0
- package/dist/modules/apimapper/example-extract.js +111 -0
- package/dist/modules/apimapper/example-extract.js.map +1 -0
- package/dist/modules/apimapper/filter-operators.d.ts +24 -0
- package/dist/modules/apimapper/filter-operators.js +103 -0
- package/dist/modules/apimapper/filter-operators.js.map +1 -0
- package/dist/modules/apimapper/flows-format.js +92 -22
- package/dist/modules/apimapper/flows-format.js.map +1 -1
- package/dist/modules/apimapper/flows.d.ts +8 -7
- package/dist/modules/apimapper/flows.js +275 -120
- package/dist/modules/apimapper/flows.js.map +1 -1
- package/dist/modules/apimapper/gateway/advanced-read-tool.d.ts +9 -0
- package/dist/modules/apimapper/gateway/advanced-read-tool.js +172 -0
- package/dist/modules/apimapper/gateway/advanced-read-tool.js.map +1 -0
- package/dist/modules/apimapper/gateway/advanced-tool.js +66 -106
- package/dist/modules/apimapper/gateway/advanced-tool.js.map +1 -1
- package/dist/modules/apimapper/gateway/collect-module-tools.d.ts +17 -0
- package/dist/modules/apimapper/gateway/collect-module-tools.js +44 -0
- package/dist/modules/apimapper/gateway/collect-module-tools.js.map +1 -0
- package/dist/modules/apimapper/gateway/essentials.d.ts +1 -1
- package/dist/modules/apimapper/gateway/essentials.js +21 -2
- package/dist/modules/apimapper/gateway/essentials.js.map +1 -1
- package/dist/modules/apimapper/gateway/gateway-shared.d.ts +21 -0
- package/dist/modules/apimapper/gateway/gateway-shared.js +124 -0
- package/dist/modules/apimapper/gateway/gateway-shared.js.map +1 -0
- package/dist/modules/apimapper/gateway/test-support.d.ts +1 -17
- package/dist/modules/apimapper/gateway/test-support.js +4 -33
- package/dist/modules/apimapper/gateway/test-support.js.map +1 -1
- package/dist/modules/apimapper/get-skill-cores.d.ts +4 -0
- package/dist/modules/apimapper/get-skill-cores.js +220 -0
- package/dist/modules/apimapper/get-skill-cores.js.map +1 -0
- package/dist/modules/apimapper/get-skill.d.ts +1 -1
- package/dist/modules/apimapper/get-skill.js +74 -9
- package/dist/modules/apimapper/get-skill.js.map +1 -1
- package/dist/modules/apimapper/graph-builder.d.ts +85 -2
- package/dist/modules/apimapper/graph-builder.js +151 -15
- package/dist/modules/apimapper/graph-builder.js.map +1 -1
- package/dist/modules/apimapper/graph.js +152 -48
- package/dist/modules/apimapper/graph.js.map +1 -1
- package/dist/modules/apimapper/index.js +27 -13
- package/dist/modules/apimapper/index.js.map +1 -1
- package/dist/modules/apimapper/jmespath-test.d.ts +4 -0
- package/dist/modules/apimapper/jmespath-test.js +152 -0
- package/dist/modules/apimapper/jmespath-test.js.map +1 -0
- package/dist/modules/apimapper/library.js +553 -88
- package/dist/modules/apimapper/library.js.map +1 -1
- package/dist/modules/apimapper/license.js +12 -36
- package/dist/modules/apimapper/license.js.map +1 -1
- package/dist/modules/apimapper/list-footer.d.ts +27 -0
- package/dist/modules/apimapper/list-footer.js +57 -0
- package/dist/modules/apimapper/list-footer.js.map +1 -0
- package/dist/modules/apimapper/local-sources.js +100 -57
- package/dist/modules/apimapper/local-sources.js.map +1 -1
- package/dist/modules/apimapper/mcp-client-identity.d.ts +32 -0
- package/dist/modules/apimapper/mcp-client-identity.js +70 -0
- package/dist/modules/apimapper/mcp-client-identity.js.map +1 -0
- package/dist/modules/apimapper/merge-constants.d.ts +6 -0
- package/dist/modules/apimapper/merge-constants.js +26 -0
- package/dist/modules/apimapper/merge-constants.js.map +1 -0
- package/dist/modules/apimapper/misc.js +13 -27
- package/dist/modules/apimapper/misc.js.map +1 -1
- package/dist/modules/apimapper/node-schema.d.ts +52 -2
- package/dist/modules/apimapper/node-schema.js +95 -4
- package/dist/modules/apimapper/node-schema.js.map +1 -1
- package/dist/modules/apimapper/onboarding.d.ts +59 -1
- package/dist/modules/apimapper/onboarding.js +231 -28
- package/dist/modules/apimapper/onboarding.js.map +1 -1
- package/dist/modules/apimapper/read-cache.d.ts +16 -3
- package/dist/modules/apimapper/read-cache.js +59 -4
- package/dist/modules/apimapper/read-cache.js.map +1 -1
- package/dist/modules/apimapper/render/index.js +26 -5
- package/dist/modules/apimapper/render/index.js.map +1 -1
- package/dist/modules/apimapper/resource-id.d.ts +13 -0
- package/dist/modules/apimapper/resource-id.js +69 -0
- package/dist/modules/apimapper/resource-id.js.map +1 -0
- package/dist/modules/apimapper/schema.js +9 -18
- package/dist/modules/apimapper/schema.js.map +1 -1
- package/dist/modules/apimapper/settings.js +49 -52
- package/dist/modules/apimapper/settings.js.map +1 -1
- package/dist/modules/apimapper/sites-tools.d.ts +29 -0
- package/dist/modules/apimapper/sites-tools.js +165 -0
- package/dist/modules/apimapper/sites-tools.js.map +1 -0
- package/dist/modules/apimapper/tool-result.d.ts +66 -0
- package/dist/modules/apimapper/tool-result.js +125 -0
- package/dist/modules/apimapper/tool-result.js.map +1 -0
- package/dist/modules/apimapper/toolslist-size.d.ts +12 -11
- package/dist/modules/apimapper/toolslist-size.js +34 -21
- package/dist/modules/apimapper/toolslist-size.js.map +1 -1
- package/dist/modules/apimapper/types.d.ts +34 -0
- package/dist/modules/apimapper/types.js +1 -1
- package/dist/modules/apimapper/types.js.map +1 -1
- package/dist/modules/apimapper/whitelist-drift.d.ts +85 -0
- package/dist/modules/apimapper/whitelist-drift.js +375 -0
- package/dist/modules/apimapper/whitelist-drift.js.map +1 -0
- package/dist/modules/apimapper/workflows.js +302 -58
- package/dist/modules/apimapper/workflows.js.map +1 -1
- package/dist/modules/apimapper/yootheme-binding.d.ts +35 -0
- package/dist/modules/apimapper/yootheme-binding.js +267 -0
- package/dist/modules/apimapper/yootheme-binding.js.map +1 -0
- package/dist/platform/index.d.ts +56 -0
- package/dist/platform/index.js +158 -2
- package/dist/platform/index.js.map +1 -1
- package/dist/proxy/bridge.d.ts +35 -0
- package/dist/proxy/bridge.js +129 -0
- package/dist/proxy/bridge.js.map +1 -0
- package/dist/proxy/mode.d.ts +9 -0
- package/dist/proxy/mode.js +20 -0
- package/dist/proxy/mode.js.map +1 -0
- package/dist/setup/detect-clients.d.ts +40 -1
- package/dist/setup/detect-clients.js +148 -1
- package/dist/setup/detect-clients.js.map +1 -1
- package/dist/setup/probe-auth.d.ts +51 -0
- package/dist/setup/probe-auth.js +141 -0
- package/dist/setup/probe-auth.js.map +1 -0
- package/dist/setup/probe-handshake.js +40 -7
- package/dist/setup/probe-handshake.js.map +1 -1
- package/dist/setup/remove-config.d.ts +8 -0
- package/dist/setup/remove-config.js +145 -0
- package/dist/setup/remove-config.js.map +1 -0
- package/dist/setup/uninstall.d.ts +34 -0
- package/dist/setup/uninstall.js +147 -0
- package/dist/setup/uninstall.js.map +1 -0
- package/dist/setup-cli.d.ts +16 -0
- package/dist/setup-cli.js +63 -1
- package/dist/setup-cli.js.map +1 -1
- package/dist/sites/loader.d.ts +48 -0
- package/dist/sites/loader.js +134 -0
- package/dist/sites/loader.js.map +1 -0
- package/dist/sites/schema.d.ts +69 -0
- package/dist/sites/schema.js +71 -0
- package/dist/sites/schema.js.map +1 -0
- package/dist/sites/secret-resolver.d.ts +47 -0
- package/dist/sites/secret-resolver.js +150 -0
- package/dist/sites/secret-resolver.js.map +1 -0
- package/dist/skill-instructions.d.ts +14 -1
- package/dist/skill-instructions.js +35 -6
- package/dist/skill-instructions.js.map +1 -1
- package/dist/transports/stdio.js +4 -4
- package/dist/transports/stdio.js.map +1 -1
- package/dist/uninstall-skill.d.ts +27 -0
- package/dist/uninstall-skill.js +89 -0
- package/dist/uninstall-skill.js.map +1 -0
- package/docs/architecture.md +21 -21
- package/docs/customgraph-internal-migration.md +4 -4
- package/docs/security.md +2 -21
- package/docs/tools.md +40 -12
- package/manifest.json +77 -79
- package/package.json +69 -65
- package/skills/apimapper/SKILL.md +128 -7
- package/skills/apimapper/reference/conditional-style-multi-items.md +114 -0
- package/skills/apimapper/reference/dynamize-existing-layout.md +158 -0
- package/skills/apimapper/reference/jmespath-cookbook.md +241 -0
- package/skills/apimapper/reference/jmespath-pitfalls.md +189 -0
- package/skills/apimapper/reference/joomla.md +1 -1
- package/skills/apimapper/reference/library-template-discovery.md +65 -0
- package/skills/apimapper/reference/merge-two-sources-on-key.md +204 -0
- package/skills/apimapper/reference/oauth.md +143 -52
- package/skills/apimapper/reference/troubleshooting.md +22 -2
- package/skills/apimapper/reference/yootheme-source-to-builder-handoff.md +348 -0
- package/skills/apimapper/reference/yootheme.md +75 -44
- package/dist/auth/oauth-provider.d.ts +0 -68
- package/dist/auth/oauth-provider.js +0 -232
- package/dist/auth/oauth-provider.js.map +0 -1
- package/dist/server-http.d.ts +0 -22
- package/dist/server-http.js +0 -159
- package/dist/server-http.js.map +0 -1
- package/dist/transports/http.d.ts +0 -29
- package/dist/transports/http.js +0 -267
- package/dist/transports/http.js.map +0 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// src/sites/loader.ts — Phase 3.
|
|
2
|
+
//
|
|
3
|
+
// Load + validate the on-disk sites file pointed at by APIMAPPER_SITES_FILE and
|
|
4
|
+
// wrap it in a small read-only registry. This is the multi-site "agency / .dxt
|
|
5
|
+
// one-click" path: a plaintext JSON file listing several WordPress/Joomla sites,
|
|
6
|
+
// each with its own URL + token + platform. The keychain ProfileStore path
|
|
7
|
+
// (src/auth/profiles.ts) remains the secure wizard default for single-machine
|
|
8
|
+
// setups — the two mechanisms coexist; the sites-file takes priority in
|
|
9
|
+
// client.ts ONLY when APIMAPPER_SITES_FILE is set and the file is non-empty.
|
|
10
|
+
//
|
|
11
|
+
// Read-only by design: this loader never writes the sites file. Editing is an
|
|
12
|
+
// operator / wizard concern, kept out of the hot request path.
|
|
13
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { SitesFile } from "./schema.js";
|
|
17
|
+
/** Base class for any sites-file IO / parse / validation failure. */
|
|
18
|
+
export class SitesFileError extends Error {
|
|
19
|
+
cause;
|
|
20
|
+
constructor(message, cause) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.cause = cause;
|
|
23
|
+
this.name = "SitesFileError";
|
|
24
|
+
Object.setPrototypeOf(this, SitesFileError.prototype);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Read-only registry around a parsed, non-empty sites file. Provides O(1)-ish
|
|
29
|
+
* lookups for the active-site resolution in client.ts. Never mutates the input.
|
|
30
|
+
*/
|
|
31
|
+
export class SitesRegistry {
|
|
32
|
+
file;
|
|
33
|
+
constructor(file) {
|
|
34
|
+
this.file = file;
|
|
35
|
+
}
|
|
36
|
+
/** Entries in insertion order (stable for list-tool UX). */
|
|
37
|
+
list() {
|
|
38
|
+
return this.file.sites;
|
|
39
|
+
}
|
|
40
|
+
/** Site_id list in insertion order. */
|
|
41
|
+
listIds() {
|
|
42
|
+
return this.file.sites.map((s) => s.site_id);
|
|
43
|
+
}
|
|
44
|
+
/** Raw entry for a site_id, or null when unknown. */
|
|
45
|
+
get(siteId) {
|
|
46
|
+
return this.file.sites.find((s) => s.site_id === siteId) ?? null;
|
|
47
|
+
}
|
|
48
|
+
/** True when the registry contains the given site_id. */
|
|
49
|
+
has(siteId) {
|
|
50
|
+
return this.file.sites.some((s) => s.site_id === siteId);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The default site entry. Resolution order:
|
|
54
|
+
* 1. `default_site_id` (when it points at an existing entry).
|
|
55
|
+
* 2. The lone `is_default:true` entry (schema guarantees at most one).
|
|
56
|
+
* 3. The first entry (the file is guaranteed non-empty by the loader).
|
|
57
|
+
*/
|
|
58
|
+
getDefault() {
|
|
59
|
+
const explicit = this.file.default_site_id;
|
|
60
|
+
if (explicit !== null) {
|
|
61
|
+
const hit = this.get(explicit);
|
|
62
|
+
if (hit)
|
|
63
|
+
return hit;
|
|
64
|
+
}
|
|
65
|
+
const flagged = this.file.sites.find((s) => s.is_default);
|
|
66
|
+
if (flagged)
|
|
67
|
+
return flagged;
|
|
68
|
+
// Loader guarantees sites.length > 0 (empty → null, never a registry).
|
|
69
|
+
return this.file.sites[0];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Load the sites file at `path` and return a {@link SitesRegistry}, or `null`
|
|
74
|
+
* when there is no usable multi-site config (so the caller falls through to the
|
|
75
|
+
* env / keychain single-site path).
|
|
76
|
+
*
|
|
77
|
+
* - `path` unset / empty / whitespace → null
|
|
78
|
+
* - file does not exist → null
|
|
79
|
+
* - file parses but has zero sites → null
|
|
80
|
+
* - malformed JSON / schema-invalid /
|
|
81
|
+
* unknown schema_version → throws {@link SitesFileError}
|
|
82
|
+
*/
|
|
83
|
+
/**
|
|
84
|
+
* F190 (2026-06-12): expand a leading `~` to the user's home directory.
|
|
85
|
+
* Node's fs does NOT do this — a `~/...` path typed into the Claude Desktop
|
|
86
|
+
* DXT settings silently failed (existsSync false → no sites). Only `~` and
|
|
87
|
+
* `~/...` are expanded; `~user/...` is left untouched (not supported).
|
|
88
|
+
*/
|
|
89
|
+
export function expandTilde(p) {
|
|
90
|
+
if (p === "~")
|
|
91
|
+
return homedir();
|
|
92
|
+
if (p.startsWith("~/"))
|
|
93
|
+
return join(homedir(), p.slice(2));
|
|
94
|
+
return p;
|
|
95
|
+
}
|
|
96
|
+
export function loadSitesRegistry(path) {
|
|
97
|
+
const trimmed = expandTilde((path ?? "").trim());
|
|
98
|
+
if (trimmed.length === 0)
|
|
99
|
+
return null;
|
|
100
|
+
if (!existsSync(trimmed))
|
|
101
|
+
return null;
|
|
102
|
+
let raw;
|
|
103
|
+
try {
|
|
104
|
+
raw = readFileSync(trimmed, "utf8");
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
throw new SitesFileError(`Failed to read sites file at ${trimmed}`, e);
|
|
108
|
+
}
|
|
109
|
+
let parsed;
|
|
110
|
+
try {
|
|
111
|
+
parsed = JSON.parse(raw);
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
throw new SitesFileError(`Sites file at ${trimmed} is not valid JSON`, e);
|
|
115
|
+
}
|
|
116
|
+
// Explicit schema-version gate: reject a known-unknown version with a clear
|
|
117
|
+
// message rather than letting the Zod literal check surface a cryptic issue.
|
|
118
|
+
if (typeof parsed === "object" &&
|
|
119
|
+
parsed !== null &&
|
|
120
|
+
"schema_version" in parsed &&
|
|
121
|
+
parsed.schema_version !== 1) {
|
|
122
|
+
throw new SitesFileError(`Unsupported sites-file schema_version: ${String(parsed.schema_version)}. This build supports schema_version: 1.`);
|
|
123
|
+
}
|
|
124
|
+
const result = SitesFile.safeParse(parsed);
|
|
125
|
+
if (!result.success) {
|
|
126
|
+
throw new SitesFileError(`Sites file at ${trimmed} failed schema validation: ` +
|
|
127
|
+
result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; "));
|
|
128
|
+
}
|
|
129
|
+
// Empty sites list → no usable multi-site config; fall through to env.
|
|
130
|
+
if (result.data.sites.length === 0)
|
|
131
|
+
return null;
|
|
132
|
+
return new SitesRegistry(result.data);
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/sites/loader.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,iFAAiF;AACjF,2EAA2E;AAC3E,8EAA8E;AAC9E,wEAAwE;AACxE,6EAA6E;AAC7E,EAAE;AACF,8EAA8E;AAC9E,+DAA+D;AAE/D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,SAAS,EAAoC,MAAM,aAAa,CAAC;AAE1E,qEAAqE;AACrE,MAAM,OAAO,cAAe,SAAQ,KAAK;IAGrB;IAFlB,YACE,OAAe,EACC,KAAe;QAE/B,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,UAAK,GAAL,KAAK,CAAU;QAG/B,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IACxD,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,aAAa;IACK;IAA7B,YAA6B,IAAgB;QAAhB,SAAI,GAAJ,IAAI,CAAY;IAAG,CAAC;IAEjD,4DAA4D;IAC5D,IAAI;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;IACzB,CAAC;IAED,uCAAuC;IACvC,OAAO;QACL,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC/C,CAAC;IAED,qDAAqD;IACrD,GAAG,CAAC,MAAc;QAChB,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC;IACnE,CAAC;IAED,yDAAyD;IACzD,GAAG,CAAC,MAAc;QAChB,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC;IAC3D,CAAC;IAED;;;;;OAKG;IACH,UAAU;QACR,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC;QAC3C,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC/B,IAAI,GAAG;gBAAE,OAAO,GAAG,CAAC;QACtB,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QAC1D,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC;QAC5B,uEAAuE;QACvE,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5B,CAAC;CACF;AAED;;;;;;;;;;GAUG;AACH;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,CAAS;IACnC,IAAI,CAAC,KAAK,GAAG;QAAE,OAAO,OAAO,EAAE,CAAC;IAChC,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAwB;IACxD,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACjD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAEtC,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,cAAc,CAAC,gCAAgC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IACzE,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,cAAc,CAAC,iBAAiB,OAAO,oBAAoB,EAAE,CAAC,CAAC,CAAC;IAC5E,CAAC;IAED,4EAA4E;IAC5E,6EAA6E;IAC7E,IACE,OAAO,MAAM,KAAK,QAAQ;QAC1B,MAAM,KAAK,IAAI;QACf,gBAAgB,IAAI,MAAM;QACzB,MAAsC,CAAC,cAAc,KAAK,CAAC,EAC5D,CAAC;QACD,MAAM,IAAI,cAAc,CACtB,0CAA0C,MAAM,CAC7C,MAAsC,CAAC,cAAc,CACvD,0CAA0C,CAC5C,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC3C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,cAAc,CACtB,iBAAiB,OAAO,6BAA6B;YACnD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CACjF,CAAC;IACJ,CAAC;IAED,uEAAuE;IACvE,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEhD,OAAO,IAAI,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Allowed character set for a `site_id`. Letters, digits, underscore, dash —
|
|
4
|
+
* no spaces, no path separators, no shell metacharacters. Filename-safe,
|
|
5
|
+
* URL-path-safe, log-safe.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SITE_ID_REGEX: RegExp;
|
|
8
|
+
/**
|
|
9
|
+
* 1Password Secret Reference shape (`op://<vault>/<item>/<field>` and deeper).
|
|
10
|
+
* Letters, digits, underscore, slash, dot, dash only. The negative lookahead
|
|
11
|
+
* `(?!.*\/\.\.\/)` rejects any ref containing a `/../` traversal segment while
|
|
12
|
+
* still allowing legitimate `.` in vault/item names (e.g. `op://team.acme/...`).
|
|
13
|
+
* Re-validated at the secret-resolver call site as defence-in-depth before the
|
|
14
|
+
* ref ever reaches a subprocess argv.
|
|
15
|
+
*/
|
|
16
|
+
export declare const OP_REF_REGEX: RegExp;
|
|
17
|
+
/**
|
|
18
|
+
* API Mapper MCP-key shape (canonical — mirrors core `KeyService.php`):
|
|
19
|
+
* `amk_(live|test)_<payloadB64url>.<signatureB64url>` — the payload and the
|
|
20
|
+
* signature are base64url (`A-Za-z0-9_-`) joined by exactly ONE dot. The earlier
|
|
21
|
+
* pattern dropped the `.<sig>` arm, so it rejected every REAL token (which always
|
|
22
|
+
* carries the dot) — a sites.json with a valid key failed schema validation.
|
|
23
|
+
* Re-validated here so a hand-edited sites.json can never inject a token shape the
|
|
24
|
+
* server would reject anyway.
|
|
25
|
+
*/
|
|
26
|
+
export declare const BEARER_REGEX: RegExp;
|
|
27
|
+
/**
|
|
28
|
+
* One site entry. Carries identity (`site_id`), reachability (`url` +
|
|
29
|
+
* `platform`), credential (exactly one of `bearer`/`bearer_ref`), and
|
|
30
|
+
* presentation metadata (`is_default`, `label`, `added_at`).
|
|
31
|
+
*/
|
|
32
|
+
export declare const SiteEntry: z.ZodObject<{
|
|
33
|
+
site_id: z.ZodString;
|
|
34
|
+
url: z.ZodString;
|
|
35
|
+
platform: z.ZodDefault<z.ZodEnum<{
|
|
36
|
+
wordpress: "wordpress";
|
|
37
|
+
joomla: "joomla";
|
|
38
|
+
auto: "auto";
|
|
39
|
+
}>>;
|
|
40
|
+
bearer: z.ZodOptional<z.ZodString>;
|
|
41
|
+
bearer_ref: z.ZodOptional<z.ZodString>;
|
|
42
|
+
is_default: z.ZodDefault<z.ZodBoolean>;
|
|
43
|
+
label: z.ZodOptional<z.ZodString>;
|
|
44
|
+
added_at: z.ZodOptional<z.ZodString>;
|
|
45
|
+
}, z.core.$strip>;
|
|
46
|
+
/**
|
|
47
|
+
* The on-disk sites-file root. `schema_version` is a literal `1` for now;
|
|
48
|
+
* future schema migrations bump it AND add a migration step in the loader.
|
|
49
|
+
*/
|
|
50
|
+
export declare const SitesFile: z.ZodObject<{
|
|
51
|
+
schema_version: z.ZodLiteral<1>;
|
|
52
|
+
default_site_id: z.ZodNullable<z.ZodString>;
|
|
53
|
+
sites: z.ZodArray<z.ZodObject<{
|
|
54
|
+
site_id: z.ZodString;
|
|
55
|
+
url: z.ZodString;
|
|
56
|
+
platform: z.ZodDefault<z.ZodEnum<{
|
|
57
|
+
wordpress: "wordpress";
|
|
58
|
+
joomla: "joomla";
|
|
59
|
+
auto: "auto";
|
|
60
|
+
}>>;
|
|
61
|
+
bearer: z.ZodOptional<z.ZodString>;
|
|
62
|
+
bearer_ref: z.ZodOptional<z.ZodString>;
|
|
63
|
+
is_default: z.ZodDefault<z.ZodBoolean>;
|
|
64
|
+
label: z.ZodOptional<z.ZodString>;
|
|
65
|
+
added_at: z.ZodOptional<z.ZodString>;
|
|
66
|
+
}, z.core.$strip>>;
|
|
67
|
+
}, z.core.$strip>;
|
|
68
|
+
export type SiteEntryT = z.infer<typeof SiteEntry>;
|
|
69
|
+
export type SitesFileT = z.infer<typeof SitesFile>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/sites/schema.ts — Zod schemas for the on-disk API Mapper sites file.
|
|
2
|
+
//
|
|
3
|
+
// Phase 3 (2026-06-03): the multi-site sites-file mechanism. Mirrors the YT
|
|
4
|
+
// Builder reference shape (yt-builder-mcp/packages/mcp/src/sites/schema.ts) so
|
|
5
|
+
// both MCP servers expose the SAME multi-site concept, adapted for API Mapper:
|
|
6
|
+
// - token shape is `amk_(live|test)_…` (API Mapper MCP key), not `ytb_…`.
|
|
7
|
+
// - per-site `platform` is 'wordpress' | 'joomla' | 'auto' (default 'auto').
|
|
8
|
+
//
|
|
9
|
+
// The on-disk format is JSON. Each entry is one connected WordPress/Joomla site
|
|
10
|
+
// running API Mapper. A site MUST carry either an inline `bearer` (plain text)
|
|
11
|
+
// OR an `op://` 1Password Secret Reference (`bearer_ref`), never both — enforced
|
|
12
|
+
// via the SiteEntry refine().
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
/**
|
|
15
|
+
* Allowed character set for a `site_id`. Letters, digits, underscore, dash —
|
|
16
|
+
* no spaces, no path separators, no shell metacharacters. Filename-safe,
|
|
17
|
+
* URL-path-safe, log-safe.
|
|
18
|
+
*/
|
|
19
|
+
export const SITE_ID_REGEX = /^[a-zA-Z0-9_-]+$/;
|
|
20
|
+
/**
|
|
21
|
+
* 1Password Secret Reference shape (`op://<vault>/<item>/<field>` and deeper).
|
|
22
|
+
* Letters, digits, underscore, slash, dot, dash only. The negative lookahead
|
|
23
|
+
* `(?!.*\/\.\.\/)` rejects any ref containing a `/../` traversal segment while
|
|
24
|
+
* still allowing legitimate `.` in vault/item names (e.g. `op://team.acme/...`).
|
|
25
|
+
* Re-validated at the secret-resolver call site as defence-in-depth before the
|
|
26
|
+
* ref ever reaches a subprocess argv.
|
|
27
|
+
*/
|
|
28
|
+
export const OP_REF_REGEX = /^op:\/\/(?!.*\/\.\.\/)[A-Za-z0-9_/.-]+$/;
|
|
29
|
+
/**
|
|
30
|
+
* API Mapper MCP-key shape (canonical — mirrors core `KeyService.php`):
|
|
31
|
+
* `amk_(live|test)_<payloadB64url>.<signatureB64url>` — the payload and the
|
|
32
|
+
* signature are base64url (`A-Za-z0-9_-`) joined by exactly ONE dot. The earlier
|
|
33
|
+
* pattern dropped the `.<sig>` arm, so it rejected every REAL token (which always
|
|
34
|
+
* carries the dot) — a sites.json with a valid key failed schema validation.
|
|
35
|
+
* Re-validated here so a hand-edited sites.json can never inject a token shape the
|
|
36
|
+
* server would reject anyway.
|
|
37
|
+
*/
|
|
38
|
+
export const BEARER_REGEX = /^amk_(live|test)_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
|
|
39
|
+
/**
|
|
40
|
+
* One site entry. Carries identity (`site_id`), reachability (`url` +
|
|
41
|
+
* `platform`), credential (exactly one of `bearer`/`bearer_ref`), and
|
|
42
|
+
* presentation metadata (`is_default`, `label`, `added_at`).
|
|
43
|
+
*/
|
|
44
|
+
export const SiteEntry = z
|
|
45
|
+
.object({
|
|
46
|
+
site_id: z.string().min(1).max(64).regex(SITE_ID_REGEX),
|
|
47
|
+
url: z.string().url(),
|
|
48
|
+
platform: z.enum(["wordpress", "joomla", "auto"]).default("auto"),
|
|
49
|
+
bearer: z.string().regex(BEARER_REGEX).optional(),
|
|
50
|
+
bearer_ref: z.string().regex(OP_REF_REGEX).optional(),
|
|
51
|
+
is_default: z.boolean().default(false),
|
|
52
|
+
label: z.string().max(120).optional(),
|
|
53
|
+
added_at: z.string().datetime().optional(),
|
|
54
|
+
})
|
|
55
|
+
.refine((s) => (s.bearer !== undefined) !== (s.bearer_ref !== undefined), {
|
|
56
|
+
message: "Exactly one of bearer or bearer_ref must be set",
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* The on-disk sites-file root. `schema_version` is a literal `1` for now;
|
|
60
|
+
* future schema migrations bump it AND add a migration step in the loader.
|
|
61
|
+
*/
|
|
62
|
+
export const SitesFile = z
|
|
63
|
+
.object({
|
|
64
|
+
schema_version: z.literal(1),
|
|
65
|
+
default_site_id: z.string().regex(SITE_ID_REGEX).nullable(),
|
|
66
|
+
sites: z.array(SiteEntry),
|
|
67
|
+
})
|
|
68
|
+
.refine((f) => f.sites.filter((s) => s.is_default).length <= 1, {
|
|
69
|
+
message: "At most one site can have is_default:true",
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/sites/schema.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,+EAA+E;AAC/E,4EAA4E;AAC5E,+EAA+E;AAC/E,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,iFAAiF;AACjF,8BAA8B;AAE9B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,kBAAkB,CAAC;AAEhD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,yCAAyC,CAAC;AAEtE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,YAAY,GACvB,kDAAkD,CAAC;AAErD;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC;KACvB,MAAM,CAAC;IACN,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC;IACvD,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IACrB,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IACjE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE;IACjD,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE;IACrD,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACtC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IACrC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CAC3C,CAAC;KACD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,KAAK,SAAS,CAAC,EAAE;IACxE,OAAO,EAAE,iDAAiD;CAC3D,CAAC,CAAC;AAEL;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC;KACvB,MAAM,CAAC;IACN,cAAc,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5B,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,QAAQ,EAAE;IAC3D,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC;CAC1B,CAAC;KACD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,MAAM,IAAI,CAAC,EAAE;IAC9D,OAAO,EAAE,2CAA2C;CACrD,CAAC,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type SiteEntryT } from "./schema.js";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the `op` binary to an absolute path so it works under a stripped-down
|
|
4
|
+
* GUI PATH. Precedence: explicit `APIMAPPER_OP_BINARY` env override → first
|
|
5
|
+
* existing well-known absolute path → bare `op` (PATH).
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveOpBinary(env?: NodeJS.ProcessEnv): string;
|
|
8
|
+
/** Resolved bearer + its provenance for downstream logs (never the token). */
|
|
9
|
+
export interface ResolvedBearer {
|
|
10
|
+
readonly token: string;
|
|
11
|
+
readonly source: "plain" | "op";
|
|
12
|
+
}
|
|
13
|
+
/** Domain-tagged error so callers can branch on `code`. */
|
|
14
|
+
export declare class SecretResolverError extends Error {
|
|
15
|
+
readonly code: string;
|
|
16
|
+
constructor(code: string, message: string);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Signature of `node:child_process.execFile` reduced to the callback we rely
|
|
20
|
+
* on. Injecting this lets tests provide a deterministic substitute without a
|
|
21
|
+
* `vi.mock('node:child_process')` side-effect.
|
|
22
|
+
*/
|
|
23
|
+
export type ExecFileLike = (file: string, args: readonly string[], options: {
|
|
24
|
+
timeout?: number;
|
|
25
|
+
maxBuffer?: number;
|
|
26
|
+
}, callback: (error: (Error & {
|
|
27
|
+
code?: string | number;
|
|
28
|
+
signal?: string;
|
|
29
|
+
}) | null, stdout: string, stderr: string) => void) => void;
|
|
30
|
+
export interface ResolveSiteBearerOptions {
|
|
31
|
+
/** Injected execFile for tests. Defaults to Node's real execFile. */
|
|
32
|
+
execFile?: ExecFileLike;
|
|
33
|
+
/** Resolved `op` binary path. Defaults to {@link resolveOpBinary}. */
|
|
34
|
+
opBinary?: string;
|
|
35
|
+
/** Per-call timeout override. Defaults to {@link OP_TIMEOUT_MS}. */
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the bearer token for a site entry.
|
|
40
|
+
*
|
|
41
|
+
* - `bearer` present → returned verbatim (`source:'plain'`).
|
|
42
|
+
* - `bearer_ref` set → `op read <ref>` via execFile (`source:'op'`).
|
|
43
|
+
* - neither → throws SecretResolverError('BEARER_MISSING').
|
|
44
|
+
*
|
|
45
|
+
* Never logs the token; never mutates the input site.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveSiteBearer(site: SiteEntryT, opts?: ResolveSiteBearerOptions): Promise<ResolvedBearer>;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// src/sites/secret-resolver.ts — Phase 3.
|
|
2
|
+
//
|
|
3
|
+
// Produce a bearer token for a SiteEntryT at call time, supporting either a
|
|
4
|
+
// plain in-file token (`bearer`) or a 1Password Secret Reference (`bearer_ref`)
|
|
5
|
+
// resolved through the `op` CLI. Mirrors the YT Builder defence model
|
|
6
|
+
// (yt-builder-mcp/packages/mcp/src/sites/secret-resolver.ts), kept simple:
|
|
7
|
+
//
|
|
8
|
+
// - `execFile`, NEVER `exec`/`spawn` with `shell:true`. `op read <ref>` runs
|
|
9
|
+
// as a direct argv invocation — no shell, no metacharacter expansion. The
|
|
10
|
+
// schema enforces OP_REF_REGEX on load; we re-enforce it here as
|
|
11
|
+
// defence-in-depth (a hand-edited sites.json could otherwise sneak a
|
|
12
|
+
// payload past validation).
|
|
13
|
+
// - Hard timeout so a biometric-stalled `op` can't wedge the agent loop.
|
|
14
|
+
// - Pluggable `execFile` for testing — no global child_process monkey-patch.
|
|
15
|
+
import { execFile as nodeExecFile } from "node:child_process";
|
|
16
|
+
import { existsSync } from "node:fs";
|
|
17
|
+
import { OP_REF_REGEX } from "./schema.js";
|
|
18
|
+
/** Hard cap for any single `op read` invocation. */
|
|
19
|
+
const OP_TIMEOUT_MS = 5_000;
|
|
20
|
+
/**
|
|
21
|
+
* Common absolute install locations for the 1Password CLI. GUI-spawned MCP
|
|
22
|
+
* hosts (Claude Desktop, etc.) launch the server subprocess with a minimal
|
|
23
|
+
* PATH that usually omits `/opt/homebrew/bin` and `/usr/local/bin`, so a bare
|
|
24
|
+
* `op` resolves to ENOENT even when `op` works in the user's shell. Probe these
|
|
25
|
+
* absolute paths before falling back to PATH.
|
|
26
|
+
*/
|
|
27
|
+
const OP_BINARY_CANDIDATES = [
|
|
28
|
+
"/opt/homebrew/bin/op",
|
|
29
|
+
"/usr/local/bin/op",
|
|
30
|
+
"/usr/bin/op",
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Resolve the `op` binary to an absolute path so it works under a stripped-down
|
|
34
|
+
* GUI PATH. Precedence: explicit `APIMAPPER_OP_BINARY` env override → first
|
|
35
|
+
* existing well-known absolute path → bare `op` (PATH).
|
|
36
|
+
*/
|
|
37
|
+
export function resolveOpBinary(env = process.env) {
|
|
38
|
+
const override = (env.APIMAPPER_OP_BINARY ?? "").trim();
|
|
39
|
+
if (override.length > 0)
|
|
40
|
+
return override;
|
|
41
|
+
for (const candidate of OP_BINARY_CANDIDATES) {
|
|
42
|
+
try {
|
|
43
|
+
if (existsSync(candidate))
|
|
44
|
+
return candidate;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// ignore stat errors and keep probing
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return "op";
|
|
51
|
+
}
|
|
52
|
+
/** Domain-tagged error so callers can branch on `code`. */
|
|
53
|
+
export class SecretResolverError extends Error {
|
|
54
|
+
code;
|
|
55
|
+
constructor(code, message) {
|
|
56
|
+
super(message);
|
|
57
|
+
this.code = code;
|
|
58
|
+
this.name = "SecretResolverError";
|
|
59
|
+
Object.setPrototypeOf(this, SecretResolverError.prototype);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Default adapter wrapping Node's real `execFile` with utf8 encoding. */
|
|
63
|
+
const defaultExecFile = (file, args, options, callback) => {
|
|
64
|
+
nodeExecFile(file, args, { ...options, encoding: "utf8" }, (err, stdout, stderr) => {
|
|
65
|
+
callback(err, stdout, stderr);
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
const OP_EXIT_CODE_HINTS = {
|
|
69
|
+
6: "Not signed in to 1Password CLI. Run `op signin` and retry.",
|
|
70
|
+
1: "Generic op CLI error — verify the secret reference exists and the vault is accessible.",
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Resolve the bearer token for a site entry.
|
|
74
|
+
*
|
|
75
|
+
* - `bearer` present → returned verbatim (`source:'plain'`).
|
|
76
|
+
* - `bearer_ref` set → `op read <ref>` via execFile (`source:'op'`).
|
|
77
|
+
* - neither → throws SecretResolverError('BEARER_MISSING').
|
|
78
|
+
*
|
|
79
|
+
* Never logs the token; never mutates the input site.
|
|
80
|
+
*/
|
|
81
|
+
export async function resolveSiteBearer(site, opts = {}) {
|
|
82
|
+
if (site.bearer !== undefined && site.bearer.length > 0) {
|
|
83
|
+
return { token: site.bearer, source: "plain" };
|
|
84
|
+
}
|
|
85
|
+
const ref = site.bearer_ref;
|
|
86
|
+
if (ref === undefined || ref.length === 0) {
|
|
87
|
+
throw new SecretResolverError("BEARER_MISSING", `Site '${site.site_id}' has neither an inline 'bearer' nor a 'bearer_ref'.`);
|
|
88
|
+
}
|
|
89
|
+
// Defence-in-depth: re-validate the ref shape BEFORE it reaches a subprocess
|
|
90
|
+
// argv. The schema enforces this on load; a hand-edited sites.json could
|
|
91
|
+
// bypass that.
|
|
92
|
+
if (!OP_REF_REGEX.test(ref)) {
|
|
93
|
+
throw new SecretResolverError("OP_REF_INVALID", `Site '${site.site_id}' has an invalid 'bearer_ref' shape — must match op://<vault>/<item>/<field>.`);
|
|
94
|
+
}
|
|
95
|
+
const execFile = opts.execFile ?? defaultExecFile;
|
|
96
|
+
const opBinary = opts.opBinary ?? resolveOpBinary();
|
|
97
|
+
const timeoutMs = opts.timeoutMs ?? OP_TIMEOUT_MS;
|
|
98
|
+
const stdout = await runOpRead(execFile, opBinary, ref, timeoutMs, site.site_id);
|
|
99
|
+
const token = stdout.trim();
|
|
100
|
+
if (token.length === 0) {
|
|
101
|
+
throw new SecretResolverError("OP_EMPTY_OUTPUT", `op returned empty output for '${ref}' — verify the field exists and is non-empty.`);
|
|
102
|
+
}
|
|
103
|
+
return { token, source: "op" };
|
|
104
|
+
}
|
|
105
|
+
/** Invoke `op read <ref>` with a hard timeout that survives a stalled child. */
|
|
106
|
+
function runOpRead(execFile, opBinary, ref, timeoutMs, siteId) {
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
let settled = false;
|
|
109
|
+
const timer = setTimeout(() => {
|
|
110
|
+
if (settled)
|
|
111
|
+
return;
|
|
112
|
+
settled = true;
|
|
113
|
+
reject(new SecretResolverError("OP_TIMEOUT", `op read '${ref}' exceeded ${timeoutMs}ms — is 1Password CLI hung on a biometric prompt?`));
|
|
114
|
+
}, timeoutMs);
|
|
115
|
+
if (typeof timer.unref === "function")
|
|
116
|
+
timer.unref();
|
|
117
|
+
execFile(opBinary, ["read", ref], { timeout: timeoutMs, maxBuffer: 1024 * 64 }, (err, stdout, stderr) => {
|
|
118
|
+
if (settled)
|
|
119
|
+
return;
|
|
120
|
+
settled = true;
|
|
121
|
+
clearTimeout(timer);
|
|
122
|
+
if (err) {
|
|
123
|
+
reject(mapExecError(err, stderr, ref, siteId));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
resolve(stdout);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function mapExecError(err, stderr, ref, siteId) {
|
|
131
|
+
const codeStr = typeof err.code === "string" ? err.code : "";
|
|
132
|
+
const codeNum = typeof err.code === "number" ? err.code : NaN;
|
|
133
|
+
if (codeStr === "ENOENT") {
|
|
134
|
+
return new SecretResolverError("OP_CLI_MISSING", `op CLI not found for site '${siteId}' — install 1Password CLI (or set APIMAPPER_OP_BINARY), ` +
|
|
135
|
+
"or use an inline 'bearer' field instead of 'bearer_ref'.");
|
|
136
|
+
}
|
|
137
|
+
const stderrLower = (stderr || "").toLowerCase();
|
|
138
|
+
if (stderrLower.includes("isn't an item") || stderrLower.includes("item not found")) {
|
|
139
|
+
return new SecretResolverError("OP_ITEM_NOT_FOUND", `1Password item not found at '${ref}'. Verify the ref points to an existing item.`);
|
|
140
|
+
}
|
|
141
|
+
if (Number.isFinite(codeNum)) {
|
|
142
|
+
const hint = OP_EXIT_CODE_HINTS[codeNum];
|
|
143
|
+
if (codeNum === 6) {
|
|
144
|
+
return new SecretResolverError("OP_NOT_SIGNED_IN", hint ?? "op exited 6 — not signed in.");
|
|
145
|
+
}
|
|
146
|
+
return new SecretResolverError("OP_EXEC_FAILED", `op read '${ref}' failed with exit ${codeNum}: ${(stderr || "").trim() || (hint ?? "no stderr")}`);
|
|
147
|
+
}
|
|
148
|
+
return new SecretResolverError("OP_EXEC_FAILED", `op read '${ref}' failed: ${err.message}`);
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=secret-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-resolver.js","sourceRoot":"","sources":["../../src/sites/secret-resolver.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,EAAE;AACF,4EAA4E;AAC5E,gFAAgF;AAChF,sEAAsE;AACtE,2EAA2E;AAC3E,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,qEAAqE;AACrE,yEAAyE;AACzE,gCAAgC;AAChC,2EAA2E;AAC3E,+EAA+E;AAE/E,OAAO,EAAE,QAAQ,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,OAAO,EAAE,YAAY,EAAmB,MAAM,aAAa,CAAC;AAE5D,oDAAoD;AACpD,MAAM,aAAa,GAAG,KAAK,CAAC;AAE5B;;;;;;GAMG;AACH,MAAM,oBAAoB,GAAsB;IAC9C,sBAAsB;IACtB,mBAAmB;IACnB,aAAa;CACd,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,MAAyB,OAAO,CAAC,GAAG;IAClE,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACxD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IACzC,KAAK,MAAM,SAAS,IAAI,oBAAoB,EAAE,CAAC;QAC7C,IAAI,CAAC;YACH,IAAI,UAAU,CAAC,SAAS,CAAC;gBAAE,OAAO,SAAS,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACP,sCAAsC;QACxC,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAQD,2DAA2D;AAC3D,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAE1B;IADlB,YACkB,IAAY,EAC5B,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,SAAI,GAAJ,IAAI,CAAQ;QAI5B,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,mBAAmB,CAAC,SAAS,CAAC,CAAC;IAC7D,CAAC;CACF;AAkBD,0EAA0E;AAC1E,MAAM,eAAe,GAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;IACtE,YAAY,CACV,IAAI,EACJ,IAAgB,EAChB,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAChC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;QACtB,QAAQ,CACN,GAAmE,EACnE,MAAM,EACN,MAAM,CACP,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAA2B;IACjD,CAAC,EAAE,4DAA4D;IAC/D,CAAC,EAAE,wFAAwF;CAC5F,CAAC;AAWF;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAgB,EAChB,OAAiC,EAAE;IAEnC,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxD,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IACjD,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC;IAC5B,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,mBAAmB,CAC3B,gBAAgB,EAChB,SAAS,IAAI,CAAC,OAAO,sDAAsD,CAC5E,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,yEAAyE;IACzE,eAAe;IACf,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,mBAAmB,CAC3B,gBAAgB,EAChB,SAAS,IAAI,CAAC,OAAO,+EAA+E,CACrG,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,eAAe,CAAC;IAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,eAAe,EAAE,CAAC;IACpD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,aAAa,CAAC;IAElD,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACjF,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,mBAAmB,CAC3B,iBAAiB,EACjB,iCAAiC,GAAG,+CAA+C,CACpF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AACjC,CAAC;AAED,gFAAgF;AAChF,SAAS,SAAS,CAChB,QAAsB,EACtB,QAAgB,EAChB,GAAW,EACX,SAAiB,EACjB,MAAc;IAEd,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC7C,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,MAAM,CACJ,IAAI,mBAAmB,CACrB,YAAY,EACZ,YAAY,GAAG,cAAc,SAAS,mDAAmD,CAC1F,CACF,CAAC;QACJ,CAAC,EAAE,SAAS,CAAC,CAAC;QACd,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,UAAU;YAAE,KAAK,CAAC,KAAK,EAAE,CAAC;QAErD,QAAQ,CACN,QAAQ,EACR,CAAC,MAAM,EAAE,GAAG,CAAC,EACb,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,GAAG,EAAE,EAAE,EAC5C,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACtB,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,GAAG,EAAE,CAAC;gBACR,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;gBAC/C,OAAO;YACT,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,YAAY,CACnB,GAAwD,EACxD,MAAc,EACd,GAAW,EACX,MAAc;IAEd,MAAM,OAAO,GAAG,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7D,MAAM,OAAO,GAAG,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;IAE9D,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,OAAO,IAAI,mBAAmB,CAC5B,gBAAgB,EAChB,8BAA8B,MAAM,0DAA0D;YAC5F,0DAA0D,CAC7D,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACjD,IAAI,WAAW,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACpF,OAAO,IAAI,mBAAmB,CAC5B,mBAAmB,EACnB,gCAAgC,GAAG,+CAA+C,CACnF,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;QACzC,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;YAClB,OAAO,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,IAAI,IAAI,8BAA8B,CAAC,CAAC;QAC7F,CAAC;QACD,OAAO,IAAI,mBAAmB,CAC5B,gBAAgB,EAChB,YAAY,GAAG,sBAAsB,OAAO,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,WAAW,CAAC,EAAE,CAClG,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,mBAAmB,CAAC,gBAAgB,EAAE,YAAY,GAAG,aAAa,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;AAC9F,CAAC"}
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
export declare const FALLBACK_INSTRUCTIONS = "Use this MCP to manage API Mapper flows, connections, and sources across WordPress + Joomla.";
|
|
2
|
+
/**
|
|
3
|
+
* F134 / F118 — load-bearing operational rules that MUST survive into the
|
|
4
|
+
* server `instructions` field regardless of SKILL.md body length. The body is
|
|
5
|
+
* truncated to MAX_LENGTH chars, and its opening prose (intro + `npx setup`)
|
|
6
|
+
* pushes the rules a cold agent actually needs PAST that window. We front-load
|
|
7
|
+
* this compact preamble so the rules are always present, then append a bounded
|
|
8
|
+
* slice of the body and the topic pointer.
|
|
9
|
+
*
|
|
10
|
+
* Keep this TERSE — every char competes with the body for the instructions
|
|
11
|
+
* budget. Each line encodes one P0-prevention rule found by the cold-agent
|
|
12
|
+
* friction audit.
|
|
13
|
+
*/
|
|
14
|
+
export declare const CRITICAL_RULES = "## Critical rules (read first)\n\n- Most tools are reachable via the gateway: apimapper_advanced({ tool: \"<name>\", arguments: {...} }). The arg key is `arguments` (NOT `input`). Omit `arguments` to discover a tool's schema.\n- If a call returns \"Unknown tool\", it is gateway-only \u2014 wrap it as apimapper_advanced({ tool, arguments }).\n- Verify connectivity with apimapper_health({}) \u2014 it is first-class in tools/list (call it directly, NOT via the gateway). Recover auth/config errors via apimapper_diagnose; deeper guidance via apimapper_get_skill({ topic }).\n";
|
|
2
15
|
/**
|
|
3
16
|
* Agent-discoverable skill-topic pointer appended to every loaded instructions
|
|
4
17
|
* string. Documents the canonical entrypoint for deeper guidance via the
|
|
@@ -8,7 +21,7 @@ export declare const FALLBACK_INSTRUCTIONS = "Use this MCP to manage API Mapper
|
|
|
8
21
|
*
|
|
9
22
|
* Topics MUST stay in sync with SKILL_TOPICS in modules/apimapper/get-skill.ts.
|
|
10
23
|
*/
|
|
11
|
-
export declare const SKILL_TOPIC_POINTER = "\n\n## Available skill topics\n\nFor deeper guidance on specific aspects of API Mapper, call:\n apimapper_get_skill({ topic })\n\nAvailable topics:\n- getting-started \u2014 Quickstart, common tools, common pitfalls\n- oauth \u2014 Wiring OAuth-protected sources (Google, Pexels, Instagram, Notion, ...)\n- yootheme \u2014 Publish flows as YOOtheme sources; Save-vs-Publish\n- joomla \u2014 Joomla 3-tier auth, com_ajax envelope, system plugin layout\n- troubleshooting \u2014 HTTP status triage, flow issues, credential states\n";
|
|
24
|
+
export declare const SKILL_TOPIC_POINTER = "\n\n## Available skill topics\n\nFor deeper guidance on specific aspects of API Mapper, call:\n apimapper_get_skill({ topic })\n\nAvailable topics:\n- getting-started \u2014 Quickstart, common tools, common pitfalls\n- oauth \u2014 Wiring OAuth-protected sources (Google, Pexels, Instagram, Notion, ...)\n- yootheme \u2014 Publish flows as YOOtheme sources; Save-vs-Publish\n- joomla \u2014 Joomla 3-tier auth, com_ajax envelope, system plugin layout\n- troubleshooting \u2014 HTTP status triage, flow issues, credential states\n- render \u2014 Render a flow as a diagram (viz-type, flow-diagram output)\n- jmespath-pitfalls \u2014 The 9 traps + date-primitive functions (date_weekday, date_iso_to_time, date_iso_to_date, time_in_window) + depth-cap warning\n- jmespath-cookbook \u2014 Copy-paste JMESPath recipes (ternary, join, conditions, sort_by, coordinates, format_number, dates, two-transform split) + a GIBT-ES-NICHT list (arithmetic/split/regex/if-else) with workarounds; iterate via apimapper_jmespath_test\n- merge-two-sources-on-key \u2014 Joining two sources on a shared key, including date-primitive bridging for weekday-name \u2194 ISO-datetime joins\n- conditional-style-multi-items \u2014 Per-row YOOtheme styles driven by a row field (text_color, panel_style)\n- library-template-discovery \u2014 Inspect a library template's contract before activating\n- yootheme-source-to-builder-handoff \u2014 Bind a published flow into a Builder element. Two shapes: field_mappings (fresh element; binding_for_flow \u2192 element_bind_source; map_item, bindingLevel) AND the theme-canonical #parent repeater form to dynamize an EXISTING designed layout (swap source.query.name to apiMapperFlow<Id>List; _condition '!'/'!!'; named-section discovery; article-context width)\n- dynamize-existing-layout \u2014 READ FIRST when the customer says \"keep the design exactly as it is\" / \"bind onto my existing section/page\": ONE call does it \u2014 template_summary (find the named section) \u2192 binding_for_flow (source name + flat fields) \u2192 yootheme_builder_page_dynamize({ template_id, section_path, source_name, leaf_map, publish: true }) \u2192 visual row check. The tool copies the subtree byte-identically and swaps only leaf_map names by construction (insert nothing, rebuild nothing)\n";
|
|
12
25
|
/**
|
|
13
26
|
* Loads MCP server instructions from skills/apimapper/SKILL.md.
|
|
14
27
|
*
|
|
@@ -7,6 +7,24 @@ import { readFileSync, existsSync } from "node:fs";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { dirname, join, resolve } from "node:path";
|
|
9
9
|
export const FALLBACK_INSTRUCTIONS = "Use this MCP to manage API Mapper flows, connections, and sources across WordPress + Joomla.";
|
|
10
|
+
/**
|
|
11
|
+
* F134 / F118 — load-bearing operational rules that MUST survive into the
|
|
12
|
+
* server `instructions` field regardless of SKILL.md body length. The body is
|
|
13
|
+
* truncated to MAX_LENGTH chars, and its opening prose (intro + `npx setup`)
|
|
14
|
+
* pushes the rules a cold agent actually needs PAST that window. We front-load
|
|
15
|
+
* this compact preamble so the rules are always present, then append a bounded
|
|
16
|
+
* slice of the body and the topic pointer.
|
|
17
|
+
*
|
|
18
|
+
* Keep this TERSE — every char competes with the body for the instructions
|
|
19
|
+
* budget. Each line encodes one P0-prevention rule found by the cold-agent
|
|
20
|
+
* friction audit.
|
|
21
|
+
*/
|
|
22
|
+
export const CRITICAL_RULES = `## Critical rules (read first)
|
|
23
|
+
|
|
24
|
+
- Most tools are reachable via the gateway: apimapper_advanced({ tool: "<name>", arguments: {...} }). The arg key is \`arguments\` (NOT \`input\`). Omit \`arguments\` to discover a tool's schema.
|
|
25
|
+
- If a call returns "Unknown tool", it is gateway-only — wrap it as apimapper_advanced({ tool, arguments }).
|
|
26
|
+
- Verify connectivity with apimapper_health({}) — it is first-class in tools/list (call it directly, NOT via the gateway). Recover auth/config errors via apimapper_diagnose; deeper guidance via apimapper_get_skill({ topic }).
|
|
27
|
+
`;
|
|
10
28
|
const MAX_LENGTH = 500;
|
|
11
29
|
/**
|
|
12
30
|
* Agent-discoverable skill-topic pointer appended to every loaded instructions
|
|
@@ -30,6 +48,14 @@ Available topics:
|
|
|
30
48
|
- yootheme — Publish flows as YOOtheme sources; Save-vs-Publish
|
|
31
49
|
- joomla — Joomla 3-tier auth, com_ajax envelope, system plugin layout
|
|
32
50
|
- troubleshooting — HTTP status triage, flow issues, credential states
|
|
51
|
+
- render — Render a flow as a diagram (viz-type, flow-diagram output)
|
|
52
|
+
- jmespath-pitfalls — The 9 traps + date-primitive functions (date_weekday, date_iso_to_time, date_iso_to_date, time_in_window) + depth-cap warning
|
|
53
|
+
- jmespath-cookbook — Copy-paste JMESPath recipes (ternary, join, conditions, sort_by, coordinates, format_number, dates, two-transform split) + a GIBT-ES-NICHT list (arithmetic/split/regex/if-else) with workarounds; iterate via apimapper_jmespath_test
|
|
54
|
+
- merge-two-sources-on-key — Joining two sources on a shared key, including date-primitive bridging for weekday-name ↔ ISO-datetime joins
|
|
55
|
+
- conditional-style-multi-items — Per-row YOOtheme styles driven by a row field (text_color, panel_style)
|
|
56
|
+
- library-template-discovery — Inspect a library template's contract before activating
|
|
57
|
+
- yootheme-source-to-builder-handoff — Bind a published flow into a Builder element. Two shapes: field_mappings (fresh element; binding_for_flow → element_bind_source; map_item, bindingLevel) AND the theme-canonical #parent repeater form to dynamize an EXISTING designed layout (swap source.query.name to apiMapperFlow<Id>List; _condition '!'/'!!'; named-section discovery; article-context width)
|
|
58
|
+
- dynamize-existing-layout — READ FIRST when the customer says "keep the design exactly as it is" / "bind onto my existing section/page": ONE call does it — template_summary (find the named section) → binding_for_flow (source name + flat fields) → yootheme_builder_page_dynamize({ template_id, section_path, source_name, leaf_map, publish: true }) → visual row check. The tool copies the subtree byte-identically and swaps only leaf_map names by construction (insert nothing, rebuild nothing)
|
|
33
59
|
`;
|
|
34
60
|
/**
|
|
35
61
|
* Loads MCP server instructions from skills/apimapper/SKILL.md.
|
|
@@ -43,22 +69,25 @@ export function loadSkillInstructions(rootDir) {
|
|
|
43
69
|
const baseDir = rootDir ?? defaultRoot();
|
|
44
70
|
const skillPath = join(baseDir, "skills", "apimapper", "SKILL.md");
|
|
45
71
|
if (!existsSync(skillPath)) {
|
|
46
|
-
return FALLBACK_INSTRUCTIONS + SKILL_TOPIC_POINTER;
|
|
72
|
+
return CRITICAL_RULES + FALLBACK_INSTRUCTIONS + SKILL_TOPIC_POINTER;
|
|
47
73
|
}
|
|
48
74
|
let raw;
|
|
49
75
|
try {
|
|
50
76
|
raw = readFileSync(skillPath, "utf8");
|
|
51
77
|
}
|
|
52
78
|
catch {
|
|
53
|
-
return FALLBACK_INSTRUCTIONS + SKILL_TOPIC_POINTER;
|
|
79
|
+
return CRITICAL_RULES + FALLBACK_INSTRUCTIONS + SKILL_TOPIC_POINTER;
|
|
54
80
|
}
|
|
55
81
|
const body = stripFrontmatter(raw).trim();
|
|
56
82
|
if (body.length === 0) {
|
|
57
|
-
return FALLBACK_INSTRUCTIONS + SKILL_TOPIC_POINTER;
|
|
83
|
+
return CRITICAL_RULES + FALLBACK_INSTRUCTIONS + SKILL_TOPIC_POINTER;
|
|
58
84
|
}
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
|
|
85
|
+
// Front-load the load-bearing rules (F134/F118) so they always survive
|
|
86
|
+
// truncation, then a bounded slice of the SKILL.md body, then the topic
|
|
87
|
+
// pointer. The body keeps its own MAX_LENGTH window; the rules + pointer are
|
|
88
|
+
// never truncated. (Pre-F134 the body opened the instructions and its intro
|
|
89
|
+
// prose pushed these rules past the window — a cold agent never saw them.)
|
|
90
|
+
return CRITICAL_RULES + body.slice(0, MAX_LENGTH) + SKILL_TOPIC_POINTER;
|
|
62
91
|
}
|
|
63
92
|
/**
|
|
64
93
|
* Strips a YAML frontmatter block (`---\n...\n---\n`) from the start of the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill-instructions.js","sourceRoot":"","sources":["../src/skill-instructions.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,0EAA0E;AAC1E,yCAAyC;AACzC,EAAE;AACF,yEAAyE;AAEzE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEnD,MAAM,CAAC,MAAM,qBAAqB,GAChC,8FAA8F,CAAC;AAEjG,MAAM,UAAU,GAAG,GAAG,CAAC;AAEvB;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG
|
|
1
|
+
{"version":3,"file":"skill-instructions.js","sourceRoot":"","sources":["../src/skill-instructions.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,0EAA0E;AAC1E,yCAAyC;AACzC,EAAE;AACF,yEAAyE;AAEzE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEnD,MAAM,CAAC,MAAM,qBAAqB,GAChC,8FAA8F,CAAC;AAEjG;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;CAK7B,CAAC;AAEF,MAAM,UAAU,GAAG,GAAG,CAAC;AAEvB;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;CAqBlC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,MAAM,OAAO,GAAG,OAAO,IAAI,WAAW,EAAE,CAAC;IACzC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;IAEnE,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,OAAO,cAAc,GAAG,qBAAqB,GAAG,mBAAmB,CAAC;IACtE,CAAC;IAED,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,cAAc,GAAG,qBAAqB,GAAG,mBAAmB,CAAC;IACtE,CAAC;IAED,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,cAAc,GAAG,qBAAqB,GAAG,mBAAmB,CAAC;IACtE,CAAC;IACD,uEAAuE;IACvE,wEAAwE;IACxE,6EAA6E;IAC7E,4EAA4E;IAC5E,2EAA2E;IAC3E,OAAO,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,GAAG,mBAAmB,CAAC;AAC1E,CAAC;AAED;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,KAAa;IACrC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,iDAAiD;IACjD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC1C,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,kEAAkE;IAClE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;IAC/C,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACd,qDAAqD;QACrD,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,WAAW;IAClB,8DAA8D;IAC9D,qEAAqE;IACrE,wCAAwC;IACxC,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,OAAO,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC7B,CAAC"}
|
package/dist/transports/stdio.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// src/transports/stdio.ts
|
|
1
|
+
// src/transports/stdio.ts
|
|
2
2
|
//
|
|
3
3
|
// Thin factory that connects an McpServer to a StdioServerTransport so the
|
|
4
|
-
// process speaks JSON-RPC over stdin/stdout.
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// process speaks JSON-RPC over stdin/stdout. stdio is the only transport the
|
|
5
|
+
// MCP server ships — it runs over stdio for both the npm/npx path and the
|
|
6
|
+
// Claude Desktop DXT bundle.
|
|
7
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
8
|
/**
|
|
9
9
|
* Connect the given McpServer to a fresh `StdioServerTransport`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/transports/stdio.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/transports/stdio.ts"],"names":[],"mappings":"AAAA,0BAA0B;AAC1B,EAAE;AACF,2EAA2E;AAC3E,6EAA6E;AAC7E,0EAA0E;AAC1E,6BAA6B;AAG7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAiB;IAClD,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC"}
|