@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.
Files changed (187) hide show
  1. package/README.md +18 -6
  2. package/bin/yt-builder-mcp.js +6 -0
  3. package/dist/auth.d.ts +86 -6
  4. package/dist/auth.d.ts.map +1 -1
  5. package/dist/auth.js +141 -8
  6. package/dist/auth.js.map +1 -1
  7. package/dist/errors/sanitize.d.ts.map +1 -1
  8. package/dist/errors/sanitize.js +10 -0
  9. package/dist/errors/sanitize.js.map +1 -1
  10. package/dist/gateway/essentials.d.ts +1 -1
  11. package/dist/gateway/essentials.d.ts.map +1 -1
  12. package/dist/gateway/essentials.js +15 -2
  13. package/dist/gateway/essentials.js.map +1 -1
  14. package/dist/index.d.ts +5 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +52 -16
  17. package/dist/index.js.map +1 -1
  18. package/dist/install-skill.d.ts +10 -2
  19. package/dist/install-skill.d.ts.map +1 -1
  20. package/dist/install-skill.js +18 -7
  21. package/dist/install-skill.js.map +1 -1
  22. package/dist/platform/detect.d.ts +64 -0
  23. package/dist/platform/detect.d.ts.map +1 -0
  24. package/dist/platform/detect.js +98 -0
  25. package/dist/platform/detect.js.map +1 -0
  26. package/dist/platform/index.d.ts +72 -0
  27. package/dist/platform/index.d.ts.map +1 -1
  28. package/dist/platform/index.js +138 -0
  29. package/dist/platform/index.js.map +1 -1
  30. package/dist/platform/joomla.d.ts +44 -0
  31. package/dist/platform/joomla.d.ts.map +1 -0
  32. package/dist/platform/joomla.js +46 -0
  33. package/dist/platform/joomla.js.map +1 -0
  34. package/dist/server.d.ts +61 -2
  35. package/dist/server.d.ts.map +1 -1
  36. package/dist/server.js +86 -2
  37. package/dist/server.js.map +1 -1
  38. package/dist/setup-cli-confirm.d.ts +36 -0
  39. package/dist/setup-cli-confirm.d.ts.map +1 -0
  40. package/dist/setup-cli-confirm.js +59 -0
  41. package/dist/setup-cli-confirm.js.map +1 -0
  42. package/dist/setup-cli.d.ts +53 -1
  43. package/dist/setup-cli.d.ts.map +1 -1
  44. package/dist/setup-cli.js +525 -10
  45. package/dist/setup-cli.js.map +1 -1
  46. package/dist/setup-prompts.d.ts.map +1 -1
  47. package/dist/setup-prompts.js +12 -8
  48. package/dist/setup-prompts.js.map +1 -1
  49. package/dist/setup-wizard-types.d.ts +19 -3
  50. package/dist/setup-wizard-types.d.ts.map +1 -1
  51. package/dist/setup-wizard.d.ts.map +1 -1
  52. package/dist/setup-wizard.js +34 -1
  53. package/dist/setup-wizard.js.map +1 -1
  54. package/dist/sites/cli/add-site.d.ts +106 -0
  55. package/dist/sites/cli/add-site.d.ts.map +1 -0
  56. package/dist/sites/cli/add-site.js +185 -0
  57. package/dist/sites/cli/add-site.js.map +1 -0
  58. package/dist/sites/cli/list-sites.d.ts +40 -0
  59. package/dist/sites/cli/list-sites.d.ts.map +1 -0
  60. package/dist/sites/cli/list-sites.js +117 -0
  61. package/dist/sites/cli/list-sites.js.map +1 -0
  62. package/dist/sites/cli/remove-site.d.ts +60 -0
  63. package/dist/sites/cli/remove-site.d.ts.map +1 -0
  64. package/dist/sites/cli/remove-site.js +120 -0
  65. package/dist/sites/cli/remove-site.js.map +1 -0
  66. package/dist/sites/cli/set-default.d.ts +47 -0
  67. package/dist/sites/cli/set-default.d.ts.map +1 -0
  68. package/dist/sites/cli/set-default.js +69 -0
  69. package/dist/sites/cli/set-default.js.map +1 -0
  70. package/dist/sites/cli/test-site.d.ts +77 -0
  71. package/dist/sites/cli/test-site.d.ts.map +1 -0
  72. package/dist/sites/cli/test-site.js +146 -0
  73. package/dist/sites/cli/test-site.js.map +1 -0
  74. package/dist/sites/client-pool.d.ts +124 -0
  75. package/dist/sites/client-pool.d.ts.map +1 -0
  76. package/dist/sites/client-pool.js +172 -0
  77. package/dist/sites/client-pool.js.map +1 -0
  78. package/dist/sites/env-bridge.d.ts +55 -0
  79. package/dist/sites/env-bridge.d.ts.map +1 -0
  80. package/dist/sites/env-bridge.js +94 -0
  81. package/dist/sites/env-bridge.js.map +1 -0
  82. package/dist/sites/paths.d.ts +21 -0
  83. package/dist/sites/paths.d.ts.map +1 -0
  84. package/dist/sites/paths.js +37 -0
  85. package/dist/sites/paths.js.map +1 -0
  86. package/dist/sites/probe.d.ts +60 -0
  87. package/dist/sites/probe.d.ts.map +1 -0
  88. package/dist/sites/probe.js +112 -0
  89. package/dist/sites/probe.js.map +1 -0
  90. package/dist/sites/registry.d.ts +142 -0
  91. package/dist/sites/registry.d.ts.map +1 -0
  92. package/dist/sites/registry.js +163 -0
  93. package/dist/sites/registry.js.map +1 -0
  94. package/dist/sites/schema.d.ts +90 -0
  95. package/dist/sites/schema.d.ts.map +1 -0
  96. package/dist/sites/schema.js +71 -0
  97. package/dist/sites/schema.js.map +1 -0
  98. package/dist/sites/secret-resolver.d.ts +109 -0
  99. package/dist/sites/secret-resolver.d.ts.map +1 -0
  100. package/dist/sites/secret-resolver.js +189 -0
  101. package/dist/sites/secret-resolver.js.map +1 -0
  102. package/dist/sites/store.d.ts +82 -0
  103. package/dist/sites/store.d.ts.map +1 -0
  104. package/dist/sites/store.js +198 -0
  105. package/dist/sites/store.js.map +1 -0
  106. package/dist/sites/tools/index.d.ts +28 -0
  107. package/dist/sites/tools/index.d.ts.map +1 -0
  108. package/dist/sites/tools/index.js +30 -0
  109. package/dist/sites/tools/index.js.map +1 -0
  110. package/dist/sites/tools/sites-list.d.ts +48 -0
  111. package/dist/sites/tools/sites-list.d.ts.map +1 -0
  112. package/dist/sites/tools/sites-list.js +135 -0
  113. package/dist/sites/tools/sites-list.js.map +1 -0
  114. package/dist/sites/tools/sites-test.d.ts +47 -0
  115. package/dist/sites/tools/sites-test.d.ts.map +1 -0
  116. package/dist/sites/tools/sites-test.js +148 -0
  117. package/dist/sites/tools/sites-test.js.map +1 -0
  118. package/dist/skill-loader.d.ts +63 -3
  119. package/dist/skill-loader.d.ts.map +1 -1
  120. package/dist/skill-loader.js +123 -10
  121. package/dist/skill-loader.js.map +1 -1
  122. package/dist/tools/elements/builders.d.ts +2 -2
  123. package/dist/tools/elements/builders.d.ts.map +1 -1
  124. package/dist/tools/elements/builders.js +45 -23
  125. package/dist/tools/elements/builders.js.map +1 -1
  126. package/dist/tools/elements/handlers.d.ts +8 -1
  127. package/dist/tools/elements/handlers.d.ts.map +1 -1
  128. package/dist/tools/elements/handlers.js +37 -1
  129. package/dist/tools/elements/handlers.js.map +1 -1
  130. package/dist/tools/format/health-format.d.ts +12 -6
  131. package/dist/tools/format/health-format.d.ts.map +1 -1
  132. package/dist/tools/format/health-format.js +14 -11
  133. package/dist/tools/format/health-format.js.map +1 -1
  134. package/dist/tools/format/pages-format.d.ts +7 -1
  135. package/dist/tools/format/pages-format.d.ts.map +1 -1
  136. package/dist/tools/format/pages-format.js +25 -1
  137. package/dist/tools/format/pages-format.js.map +1 -1
  138. package/dist/tools/health.d.ts +2 -2
  139. package/dist/tools/health.d.ts.map +1 -1
  140. package/dist/tools/health.js +120 -47
  141. package/dist/tools/health.js.map +1 -1
  142. package/dist/tools/index.d.ts +9 -2
  143. package/dist/tools/index.d.ts.map +1 -1
  144. package/dist/tools/index.js +21 -7
  145. package/dist/tools/index.js.map +1 -1
  146. package/dist/tools/inspection.d.ts +2 -2
  147. package/dist/tools/inspection.d.ts.map +1 -1
  148. package/dist/tools/inspection.js +67 -22
  149. package/dist/tools/inspection.js.map +1 -1
  150. package/dist/tools/multi-items/builders.d.ts +2 -2
  151. package/dist/tools/multi-items/builders.d.ts.map +1 -1
  152. package/dist/tools/multi-items/builders.js +21 -8
  153. package/dist/tools/multi-items/builders.js.map +1 -1
  154. package/dist/tools/pages/builders.d.ts +2 -2
  155. package/dist/tools/pages/builders.d.ts.map +1 -1
  156. package/dist/tools/pages/builders.js +36 -24
  157. package/dist/tools/pages/builders.js.map +1 -1
  158. package/dist/tools/pages/handlers-read.d.ts.map +1 -1
  159. package/dist/tools/pages/handlers-read.js +10 -0
  160. package/dist/tools/pages/handlers-read.js.map +1 -1
  161. package/dist/tools/pages/schemas.d.ts.map +1 -1
  162. package/dist/tools/pages/schemas.js +18 -0
  163. package/dist/tools/pages/schemas.js.map +1 -1
  164. package/dist/tools/pool-resolve-helper.d.ts +83 -0
  165. package/dist/tools/pool-resolve-helper.d.ts.map +1 -0
  166. package/dist/tools/pool-resolve-helper.js +148 -0
  167. package/dist/tools/pool-resolve-helper.js.map +1 -0
  168. package/dist/tools/shared-schemas.d.ts +10 -0
  169. package/dist/tools/shared-schemas.d.ts.map +1 -1
  170. package/dist/tools/shared-schemas.js +20 -2
  171. package/dist/tools/shared-schemas.js.map +1 -1
  172. package/dist/tools/sources/builders.d.ts +2 -2
  173. package/dist/tools/sources/builders.d.ts.map +1 -1
  174. package/dist/tools/sources/builders.js +27 -15
  175. package/dist/tools/sources/builders.js.map +1 -1
  176. package/dist/tools/tool-builder/index.d.ts +1 -1
  177. package/dist/tools/tool-builder/index.d.ts.map +1 -1
  178. package/dist/tools/tool-builder/index.js +1 -1
  179. package/dist/tools/tool-builder/index.js.map +1 -1
  180. package/dist/tools/tool-builder/results.d.ts +34 -0
  181. package/dist/tools/tool-builder/results.d.ts.map +1 -1
  182. package/dist/tools/tool-builder/results.js +86 -0
  183. package/dist/tools/tool-builder/results.js.map +1 -1
  184. package/manifest.json +27 -10
  185. package/package.json +3 -1
  186. package/skills/yt-builder-mcp/SKILL.md +846 -0
  187. package/skills/yootheme-builder/SKILL.md +0 -587
@@ -0,0 +1,71 @@
1
+ /**
2
+ * W1 — Sites-Storage Foundation: Zod schemas for the on-disk sites file.
3
+ *
4
+ * Schema spec frozen by `2026-05-25-04-yt-builder-mcp-multi-site-implementation.md`
5
+ * §W1. Do NOT rename fields without bumping `schema_version` and adding a
6
+ * migration in W3 (registry).
7
+ *
8
+ * The on-disk format is JSON. Each entry represents one connected
9
+ * YOOtheme-Pro-equipped site. A site MUST carry either an inline `bearer`
10
+ * (plain text, for local-dev) OR an `op://` 1Password Secret Reference
11
+ * (`bearer_ref`), never both — enforced via {@link SiteEntry}'s refine().
12
+ *
13
+ * @license MIT
14
+ */
15
+ import { z } from 'zod';
16
+ /**
17
+ * Allowed character set for a `site_id`. Letters, digits, underscore,
18
+ * dash — no spaces, no path separators, no shell metacharacters.
19
+ *
20
+ * Surface: filename-safe, URL-path-safe, log-safe.
21
+ */
22
+ export const SITE_ID_REGEX = /^[a-zA-Z0-9_-]+$/;
23
+ /**
24
+ * 1Password Secret Reference shape (`op://<vault>/<item>/<field>` and
25
+ * deeper). Letters, digits, underscore, slash, dot, dash only — same
26
+ * defence-in-depth set the W2 SecretResolver will re-validate before
27
+ * shelling out to `op`.
28
+ *
29
+ * W12-R1.3 (A2-L1): the previous pattern allowed `..` path segments
30
+ * because `.` was in the character class. The `op` CLI does not honour
31
+ * filesystem-style traversal but the input was reaching shell argv
32
+ * verbatim, so a ref like `op://vault/../something` (or worse, encoded
33
+ * in a hand-edited sites.json) muddied the trust boundary. The
34
+ * negative lookahead `(?!.*\/\.\.\/)` rejects any ref containing a
35
+ * `/../` segment without disallowing legitimate `.` in vault/item
36
+ * names (e.g. `op://team.acme/...`).
37
+ */
38
+ export const OP_REF_REGEX = /^op:\/\/(?!.*\/\.\.\/)[A-Za-z0-9_/.-]+$/;
39
+ /**
40
+ * Bearer-token shape produced by `KeyService.php`:
41
+ * `ytb_(live|test)_<payloadB64Url>.<sigB64Url>`. Re-validated here so a
42
+ * hand-edited sites.json can never inject a token shape the server would
43
+ * reject anyway.
44
+ */
45
+ export const BEARER_REGEX = /^ytb_(live|test)_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
46
+ /**
47
+ * One site entry. Carries identity (`site_id`), reachability (`url` +
48
+ * `platform`), credential (exactly one of `bearer`/`bearer_ref`), and
49
+ * presentation metadata (`is_default`, `label`, `added_at`).
50
+ */
51
+ export const SiteEntry = z.object({
52
+ site_id: z.string().min(1).max(64).regex(SITE_ID_REGEX),
53
+ url: z.string().url(),
54
+ platform: z.enum(['wordpress', 'joomla', 'auto']).default('auto'),
55
+ bearer: z.string().regex(BEARER_REGEX).optional(),
56
+ bearer_ref: z.string().regex(OP_REF_REGEX).optional(),
57
+ is_default: z.boolean().default(false),
58
+ label: z.string().max(120).optional(),
59
+ added_at: z.string().datetime().optional(),
60
+ }).refine((s) => (s.bearer !== undefined) !== (s.bearer_ref !== undefined), { message: 'Exactly one of bearer or bearer_ref must be set' });
61
+ /**
62
+ * The on-disk sites-file root. `schema_version` is a literal `1` for now;
63
+ * future schema migrations bump it AND add a migration step in the W3
64
+ * registry loader.
65
+ */
66
+ export const SitesFile = z.object({
67
+ schema_version: z.literal(1),
68
+ default_site_id: z.string().regex(SITE_ID_REGEX).nullable(),
69
+ sites: z.array(SiteEntry),
70
+ }).refine((f) => f.sites.filter((s) => s.is_default).length <= 1, { message: 'At most one site can have is_default:true' });
71
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/sites/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,kBAAkB,CAAC;AAEhD;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,yCAAyC,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,kDAAkD,CAAC;AAE/E;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9B,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;CAC7C,CAAC,CAAC,MAAM,CACL,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,KAAK,SAAS,CAAC,EAChE,EAAE,OAAO,EAAE,iDAAiD,EAAE,CACjE,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9B,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;CAC5B,CAAC,CAAC,MAAM,CACL,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,MAAM,IAAI,CAAC,EACtD,EAAE,OAAO,EAAE,2CAA2C,EAAE,CAC3D,CAAC"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * W2 — Secret-Resolver: produce a bearer-token for a `SiteEntryT` at call
3
+ * time, supporting either a plain in-file token (`bearer`) or a 1Password
4
+ * Secret Reference (`bearer_ref`) resolved through the `op` CLI.
5
+ *
6
+ * Design choices (per plan §W2):
7
+ * - **`execFile`, never `exec`/`spawn` with `shell:true`.** `op read <ref>`
8
+ * must run as a direct `execve` with an argv array — no shell, no
9
+ * metacharacter expansion. The W1 schema already enforces
10
+ * {@link OP_REF_REGEX}; this module re-enforces it at the call site as
11
+ * defence-in-depth (a hand-edited sites.json could otherwise sneak a
12
+ * payload past validation).
13
+ * - **5-second hard timeout.** `op` is interactive in some failure modes
14
+ * (Touch-ID, biometric retry). We cap with a real timer + `kill()` to
15
+ * avoid agent-loop wedging on a stalled subprocess.
16
+ * - **Pluggable for testing.** The OpSecretResolver accepts an injected
17
+ * `execFile` callback so tests can mock subprocess invocation without
18
+ * monkey-patching the global `node:child_process` module.
19
+ *
20
+ * @license MIT
21
+ */
22
+ import { type SiteEntryT } from './schema.js';
23
+ /**
24
+ * Shape returned by every resolver. W12-R1.2 (A5-S3): the planned TTL
25
+ * cache that was the original justification for `resolvedAt` never
26
+ * shipped — the W4 ClientPool caches the `RestClient` itself, not the
27
+ * bearer — so the field was dead weight. Dropped to keep the contract
28
+ * minimal. Re-add ONLY when a real consumer appears.
29
+ */
30
+ export interface ResolvedBearer {
31
+ readonly token: string;
32
+ readonly source: 'plain' | 'op';
33
+ }
34
+ /**
35
+ * Pluggable resolver contract. Implementations MUST NOT log the token,
36
+ * MUST NOT mutate the input site, and MUST throw {@link SecretResolverError}
37
+ * on any failure (never return an empty/placeholder bearer).
38
+ */
39
+ export interface SecretResolver {
40
+ resolve(site: SiteEntryT): Promise<ResolvedBearer>;
41
+ }
42
+ /** Domain-tagged error so callers (auth, client) can branch on `code`. */
43
+ export declare class SecretResolverError extends Error {
44
+ readonly code: string;
45
+ constructor(code: string, message: string);
46
+ }
47
+ /**
48
+ * Signature of `node:child_process.execFile` reduced to the callback we
49
+ * actually rely on. Injecting this lets tests provide a deterministic
50
+ * substitute without `vi.mock('node:child_process')` side-effects.
51
+ *
52
+ * The real `execFile` overload returns a `ChildProcess` and accepts
53
+ * many option shapes; we accept the broadest superset and only use the
54
+ * pieces the resolver needs (`timeout`, `signal`, callback args).
55
+ */
56
+ export type ExecFileLike = (file: string, args: readonly string[], options: {
57
+ timeout?: number;
58
+ maxBuffer?: number;
59
+ }, callback: (error: (Error & {
60
+ code?: string | number;
61
+ signal?: string;
62
+ }) | null, stdout: string, stderr: string) => void) => void;
63
+ /**
64
+ * Plain in-file bearer — used when `site.bearer` is populated. Returns the
65
+ * stored token verbatim, with `source:'plain'`.
66
+ */
67
+ export declare class PlainSecretResolver implements SecretResolver {
68
+ resolve(site: SiteEntryT): Promise<ResolvedBearer>;
69
+ }
70
+ /**
71
+ * Resolves `bearer_ref` (`op://vault/item/field`) through the 1Password CLI.
72
+ *
73
+ * Failure surface:
74
+ * - Missing `bearer_ref` → `OP_REF_MISSING`
75
+ * - Malformed ref at call site → `OP_REF_INVALID`
76
+ * - `op` binary not on PATH (ENOENT) → `OP_CLI_MISSING`
77
+ * - `op` exit-code 6 (not signed in) → `OP_NOT_SIGNED_IN`
78
+ * - 5-second timeout exceeded → `OP_TIMEOUT`
79
+ * - Empty/whitespace stdout → `OP_EMPTY_OUTPUT`
80
+ * - Any other non-zero exit → `OP_EXEC_FAILED`
81
+ */
82
+ export declare class OpSecretResolver implements SecretResolver {
83
+ private readonly opBinary;
84
+ private readonly execFile;
85
+ private readonly timeoutMs;
86
+ constructor(opBinary?: string, execFile?: ExecFileLike, timeoutMs?: number);
87
+ resolve(site: SiteEntryT): Promise<ResolvedBearer>;
88
+ /**
89
+ * Invokes `op read <ref>` with a hard timeout. The promise is wrapped
90
+ * so a stalled subprocess (e.g. waiting on biometric prompt) cannot
91
+ * outlive {@link timeoutMs}.
92
+ */
93
+ private runOpRead;
94
+ private mapExecError;
95
+ }
96
+ /**
97
+ * Composite resolver — chooses Plain/Op per site at resolve-time. This is
98
+ * the resolver wired into the registry in W4; W2 ships it standalone so
99
+ * unit tests cover the routing logic in isolation from any caching.
100
+ */
101
+ export declare class CompositeSecretResolver implements SecretResolver {
102
+ private readonly plain;
103
+ private readonly op;
104
+ constructor(plain?: SecretResolver, op?: SecretResolver);
105
+ resolve(site: SiteEntryT): Promise<ResolvedBearer>;
106
+ }
107
+ /** Default factory consumed by the W4 registry / W5 auth pipeline. */
108
+ export declare function defaultSecretResolver(): CompositeSecretResolver;
109
+ //# sourceMappingURL=secret-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-resolver.d.ts","sourceRoot":"","sources":["../../src/sites/secret-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAW5D;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;CACnC;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC3B,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CACtD;AAED,0EAA0E;AAC1E,qBAAa,mBAAoB,SAAQ,KAAK;aACd,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAI5D;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,YAAY,GAAG,CACvB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EACjD,QAAQ,EAAE,CACN,KAAK,EAAE,CAAC,KAAK,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,EACnE,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,KACb,IAAI,KACR,IAAI,CAAC;AAqBV;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,cAAc;IAChD,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,cAAc,CAAC;CAa3D;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,gBAAiB,YAAW,cAAc;IAE/C,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAFT,QAAQ,GAAE,MAAa,EACvB,QAAQ,GAAE,YAA8B,EACxC,SAAS,GAAE,MAAsB;IAGhD,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,cAAc,CAAC;IAmCxD;;;;OAIG;IACH,OAAO,CAAC,SAAS;IAoCjB,OAAO,CAAC,YAAY;CAoDvB;AAED;;;;GAIG;AACH,qBAAa,uBAAwB,YAAW,cAAc;IAEtD,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,EAAE;gBADF,KAAK,GAAE,cAA0C,EACjD,EAAE,GAAE,cAAuC;IAG1D,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,cAAc,CAAC;CAM3D;AAED,sEAAsE;AACtE,wBAAgB,qBAAqB,IAAI,uBAAuB,CAE/D"}
@@ -0,0 +1,189 @@
1
+ /**
2
+ * W2 — Secret-Resolver: produce a bearer-token for a `SiteEntryT` at call
3
+ * time, supporting either a plain in-file token (`bearer`) or a 1Password
4
+ * Secret Reference (`bearer_ref`) resolved through the `op` CLI.
5
+ *
6
+ * Design choices (per plan §W2):
7
+ * - **`execFile`, never `exec`/`spawn` with `shell:true`.** `op read <ref>`
8
+ * must run as a direct `execve` with an argv array — no shell, no
9
+ * metacharacter expansion. The W1 schema already enforces
10
+ * {@link OP_REF_REGEX}; this module re-enforces it at the call site as
11
+ * defence-in-depth (a hand-edited sites.json could otherwise sneak a
12
+ * payload past validation).
13
+ * - **5-second hard timeout.** `op` is interactive in some failure modes
14
+ * (Touch-ID, biometric retry). We cap with a real timer + `kill()` to
15
+ * avoid agent-loop wedging on a stalled subprocess.
16
+ * - **Pluggable for testing.** The OpSecretResolver accepts an injected
17
+ * `execFile` callback so tests can mock subprocess invocation without
18
+ * monkey-patching the global `node:child_process` module.
19
+ *
20
+ * @license MIT
21
+ */
22
+ import { execFile as nodeExecFile } from 'node:child_process';
23
+ import { OP_REF_REGEX } from './schema.js';
24
+ /** Hard cap for any single `op read` invocation. */
25
+ const OP_TIMEOUT_MS = 5_000;
26
+ /** `op` CLI exit-code → friendly hint mapping. */
27
+ const OP_EXIT_CODE_HINTS = {
28
+ 6: 'Not signed in to 1Password CLI. Run `op signin` and retry.',
29
+ 1: 'Generic op CLI error — verify the secret reference exists and the vault is accessible.',
30
+ };
31
+ /** Domain-tagged error so callers (auth, client) can branch on `code`. */
32
+ export class SecretResolverError extends Error {
33
+ code;
34
+ constructor(code, message) {
35
+ super(message);
36
+ this.code = code;
37
+ this.name = 'SecretResolverError';
38
+ }
39
+ }
40
+ /** Default factory wrapping Node's real `execFile`. Adapter for type-shape. */
41
+ const defaultExecFile = (file, args, options, callback) => {
42
+ // Pass `encoding: 'utf8'` explicitly so stdout/stderr are guaranteed
43
+ // strings, not Buffers. The execFile overload resolution returns `string`
44
+ // for both when `encoding` is set.
45
+ nodeExecFile(file, args, { ...options, encoding: 'utf8' }, (err, stdout, stderr) => {
46
+ callback(err, stdout, stderr);
47
+ });
48
+ };
49
+ /**
50
+ * Plain in-file bearer — used when `site.bearer` is populated. Returns the
51
+ * stored token verbatim, with `source:'plain'`.
52
+ */
53
+ export class PlainSecretResolver {
54
+ async resolve(site) {
55
+ const token = site.bearer;
56
+ if (token === undefined || token.length === 0) {
57
+ throw new SecretResolverError('PLAIN_BEARER_MISSING', `Site '${site.site_id}' has no inline 'bearer' — set one or configure 'bearer_ref'.`);
58
+ }
59
+ return {
60
+ token,
61
+ source: 'plain',
62
+ };
63
+ }
64
+ }
65
+ /**
66
+ * Resolves `bearer_ref` (`op://vault/item/field`) through the 1Password CLI.
67
+ *
68
+ * Failure surface:
69
+ * - Missing `bearer_ref` → `OP_REF_MISSING`
70
+ * - Malformed ref at call site → `OP_REF_INVALID`
71
+ * - `op` binary not on PATH (ENOENT) → `OP_CLI_MISSING`
72
+ * - `op` exit-code 6 (not signed in) → `OP_NOT_SIGNED_IN`
73
+ * - 5-second timeout exceeded → `OP_TIMEOUT`
74
+ * - Empty/whitespace stdout → `OP_EMPTY_OUTPUT`
75
+ * - Any other non-zero exit → `OP_EXEC_FAILED`
76
+ */
77
+ export class OpSecretResolver {
78
+ opBinary;
79
+ execFile;
80
+ timeoutMs;
81
+ constructor(opBinary = 'op', execFile = defaultExecFile, timeoutMs = OP_TIMEOUT_MS) {
82
+ this.opBinary = opBinary;
83
+ this.execFile = execFile;
84
+ this.timeoutMs = timeoutMs;
85
+ }
86
+ async resolve(site) {
87
+ const ref = site.bearer_ref;
88
+ if (ref === undefined || ref.length === 0) {
89
+ throw new SecretResolverError('OP_REF_MISSING', `Site '${site.site_id}' has no 'bearer_ref' — OpSecretResolver requires one.`);
90
+ }
91
+ // Defence-in-depth: re-validate the ref shape at the call site,
92
+ // BEFORE we hand the string to a subprocess. The W1 schema also
93
+ // enforces this on load/save, but a hand-edited or hot-loaded
94
+ // sites.json could in theory bypass that.
95
+ if (!OP_REF_REGEX.test(ref)) {
96
+ throw new SecretResolverError('OP_REF_INVALID', `Site '${site.site_id}' has an invalid 'bearer_ref' shape — must match op://<vault>/<item>/<field>.`);
97
+ }
98
+ const stdout = await this.runOpRead(ref);
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 {
104
+ token,
105
+ source: 'op',
106
+ };
107
+ }
108
+ /**
109
+ * Invokes `op read <ref>` with a hard timeout. The promise is wrapped
110
+ * so a stalled subprocess (e.g. waiting on biometric prompt) cannot
111
+ * outlive {@link timeoutMs}.
112
+ */
113
+ runOpRead(ref) {
114
+ return new Promise((resolve, reject) => {
115
+ let settled = false;
116
+ const timer = setTimeout(() => {
117
+ if (settled)
118
+ return;
119
+ settled = true;
120
+ reject(new SecretResolverError('OP_TIMEOUT', `op read '${ref}' exceeded ${this.timeoutMs}ms — is 1Password CLI hung on a biometric prompt?`));
121
+ }, this.timeoutMs);
122
+ // Allow the test process to exit even if `op` is still pending.
123
+ if (typeof timer.unref === 'function')
124
+ timer.unref();
125
+ this.execFile(this.opBinary, ['read', ref], { timeout: this.timeoutMs, maxBuffer: 1024 * 64 }, (err, stdout, stderr) => {
126
+ if (settled)
127
+ return;
128
+ settled = true;
129
+ clearTimeout(timer);
130
+ if (err) {
131
+ reject(this.mapExecError(err, stderr, ref));
132
+ return;
133
+ }
134
+ resolve(stdout);
135
+ });
136
+ });
137
+ }
138
+ mapExecError(err, stderr, ref) {
139
+ const codeStr = typeof err.code === 'string' ? err.code : '';
140
+ const codeNum = typeof err.code === 'number' ? err.code : NaN;
141
+ if (codeStr === 'ENOENT') {
142
+ return new SecretResolverError('OP_CLI_MISSING', `op CLI not found in PATH — install 1Password CLI or use plain bearer field instead of bearer_ref.`);
143
+ }
144
+ // W12-R1.2 (A5-S4): the `op` CLI returns "isn't an item" / "item
145
+ // not found" on stderr when the ref points to a nonexistent item.
146
+ // Surfaced as generic OP_EXEC_FAILED pre-W12, which left the
147
+ // customer guessing whether their ref shape was wrong vs whether
148
+ // the item was deleted. Brand it explicitly so the recovery hint
149
+ // can name the right knob.
150
+ const stderrLower = (stderr || '').toLowerCase();
151
+ if (stderrLower.includes(`isn't an item`)
152
+ || stderrLower.includes('item not found')) {
153
+ return new SecretResolverError('OP_ITEM_NOT_FOUND', `1Password item not found at '${ref}'. `
154
+ + 'Verify the ref points to an existing item, or update sites.json bearer_ref.');
155
+ }
156
+ if (Number.isFinite(codeNum)) {
157
+ const hint = OP_EXIT_CODE_HINTS[codeNum];
158
+ if (codeNum === 6) {
159
+ return new SecretResolverError('OP_NOT_SIGNED_IN', hint ?? `op exited 6 — not signed in.`);
160
+ }
161
+ return new SecretResolverError('OP_EXEC_FAILED', `op read '${ref}' failed with exit ${codeNum}: ${(stderr || '').trim() || (hint ?? 'no stderr')}`);
162
+ }
163
+ return new SecretResolverError('OP_EXEC_FAILED', `op read '${ref}' failed: ${err.message}`);
164
+ }
165
+ }
166
+ /**
167
+ * Composite resolver — chooses Plain/Op per site at resolve-time. This is
168
+ * the resolver wired into the registry in W4; W2 ships it standalone so
169
+ * unit tests cover the routing logic in isolation from any caching.
170
+ */
171
+ export class CompositeSecretResolver {
172
+ plain;
173
+ op;
174
+ constructor(plain = new PlainSecretResolver(), op = new OpSecretResolver()) {
175
+ this.plain = plain;
176
+ this.op = op;
177
+ }
178
+ async resolve(site) {
179
+ if (site.bearer_ref !== undefined) {
180
+ return this.op.resolve(site);
181
+ }
182
+ return this.plain.resolve(site);
183
+ }
184
+ }
185
+ /** Default factory consumed by the W4 registry / W5 auth pipeline. */
186
+ export function defaultSecretResolver() {
187
+ return new CompositeSecretResolver();
188
+ }
189
+ //# sourceMappingURL=secret-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-resolver.js","sourceRoot":"","sources":["../../src/sites/secret-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * W1 — Sites-Storage Foundation: load/save the sites file.
3
+ *
4
+ * Guarantees:
5
+ * - **Atomic replace.** Writes go to a sibling `*.tmp.<rand>` file, are
6
+ * fsynced, chmod'd to 0600, and then `rename(2)`d over the target.
7
+ * A SIGINT/SIGKILL between any two of these steps leaves either the
8
+ * old file intact or the new file fully written — never a torn write.
9
+ * - **Lock for concurrent writers.** `proper-lockfile` acquires an
10
+ * advisory lockfile (`<file>.lock`) before any read/modify/write
11
+ * cycle. Default `stale: 10000` ms; second concurrent save retries
12
+ * with exponential back-off (`retries: 10, minTimeout: 50`).
13
+ * - **0600 mode on write.** The temp file is opened/chmod'd to 0600
14
+ * BEFORE the rename so the new inode never exists with looser perms,
15
+ * even for one fsync window.
16
+ * - **Schema-version gate.** Refuses to load a file with a version we
17
+ * don't understand — explicit error, never silent migration.
18
+ *
19
+ * @license MIT
20
+ */
21
+ import { type SitesFileT } from './schema.js';
22
+ /** Base class for any sites-file IO/format failure. */
23
+ export declare class SitesFileError extends Error {
24
+ readonly cause?: unknown | undefined;
25
+ constructor(message: string, cause?: unknown | undefined);
26
+ }
27
+ /** Thrown when the on-disk file declares a `schema_version` we don't know. */
28
+ export declare class SchemaVersionError extends SitesFileError {
29
+ readonly found: unknown;
30
+ constructor(found: unknown);
31
+ }
32
+ /**
33
+ * Discriminated lock-acquisition failure. W12-R1.2 (A4-F1): the
34
+ * pre-W12 `SitesFileError` collapsed three distinct failure modes into
35
+ * one opaque "lock not acquired" string. Operators triaging a stuck
36
+ * setup wizard need to distinguish them to choose the right recovery:
37
+ *
38
+ * - LOCK_TIMEOUT → another setup process is currently writing.
39
+ * Wait + retry; do NOT touch the lockfile.
40
+ * - LOCK_ORPHAN → stale lock from a crashed prior process.
41
+ * Manual `rm <lockfilePath>` is the only fix.
42
+ * - LOCK_PERMISSION → the lock dir itself is not writable. Fix
43
+ * file-system permissions.
44
+ *
45
+ * The `lockCode` discriminant + `lockfilePath` field let CLI surfaces
46
+ * (and pre-flight automation) emit precise hints.
47
+ */
48
+ export type SitesFileLockCode = 'LOCK_TIMEOUT' | 'LOCK_ORPHAN' | 'LOCK_PERMISSION';
49
+ export declare class SitesFileLockError extends SitesFileError {
50
+ readonly lockCode: SitesFileLockCode;
51
+ readonly lockfilePath: string;
52
+ constructor(lockCode: SitesFileLockCode, lockfilePath: string, message: string, cause?: unknown);
53
+ }
54
+ /**
55
+ * Returns the empty seed used when a sites file does not yet exist.
56
+ * Callers (W3 registry) detect "no file → run env-bridge fallback".
57
+ */
58
+ export declare function emptySitesFile(): SitesFileT;
59
+ /**
60
+ * Load + validate a sites file.
61
+ *
62
+ * Returns {@link emptySitesFile} when the path does not exist (no error).
63
+ * Throws {@link SchemaVersionError} for a known-unknown version and
64
+ * {@link SitesFileError} for JSON-parse or Zod-validation failures.
65
+ */
66
+ export declare function loadSitesFile(path: string): Promise<SitesFileT>;
67
+ /**
68
+ * Persist + validate a sites file atomically with chmod 0600.
69
+ *
70
+ * Pipeline:
71
+ * 1. Acquire `<path>.lock` via proper-lockfile (with retries).
72
+ * 2. Validate `data` through {@link SitesFile} (rejects bad input
73
+ * before touching the filesystem).
74
+ * 3. Ensure parent directory exists.
75
+ * 4. Write JSON to `<path>.tmp.<pid>.<rand>`, fsync, chmod 0600.
76
+ * 5. `renameSync` over `<path>`.
77
+ * 6. Release lock.
78
+ *
79
+ * Tmp-file cleanup on any failure path.
80
+ */
81
+ export declare function saveSitesFile(path: string, data: SitesFileT): Promise<void>;
82
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/sites/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAkBH,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzD,uDAAuD;AACvD,qBAAa,cAAe,SAAQ,KAAK;aACQ,KAAK,CAAC,EAAE,OAAO;gBAAhD,OAAO,EAAE,MAAM,EAAkB,KAAK,CAAC,EAAE,OAAO,YAAA;CAI/D;AAED,8EAA8E;AAC9E,qBAAa,kBAAmB,SAAQ,cAAc;aACtB,KAAK,EAAE,OAAO;gBAAd,KAAK,EAAE,OAAO;CAQ7C;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG,aAAa,GAAG,iBAAiB,CAAC;AAEnF,qBAAa,kBAAmB,SAAQ,cAAc;aAE9B,QAAQ,EAAE,iBAAiB;aAC3B,YAAY,EAAE,MAAM;gBADpB,QAAQ,EAAE,iBAAiB,EAC3B,YAAY,EAAE,MAAM,EACpC,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,OAAO;CAKtB;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,UAAU,CAE3C;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAiCrE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAoFjF"}