@doxbrix/doxloop 0.1.5 → 0.2.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 (357) hide show
  1. package/CHANGELOG.md +1145 -0
  2. package/README.md +422 -227
  3. package/assets/doxbrix-preview.css +77 -15
  4. package/contracts/agent-events-v1.schema.json +1 -0
  5. package/contracts/coverage-v1.schema.json +59 -0
  6. package/contracts/drift-v1.schema.json +1 -0
  7. package/contracts/evaluation-v1.schema.json +62 -0
  8. package/contracts/quality-codes-v1.json +1 -0
  9. package/contracts/quality-report-v1.schema.json +1 -0
  10. package/contracts/validation-v1.schema.json +1 -0
  11. package/dist/agent-failure.d.ts +36 -0
  12. package/dist/agent-failure.js +98 -0
  13. package/dist/agent-isolation.d.ts +17 -0
  14. package/dist/agent-isolation.js +94 -0
  15. package/dist/agent-log.d.ts +197 -0
  16. package/dist/agent-log.js +1094 -0
  17. package/dist/agent-process.d.ts +63 -0
  18. package/dist/agent-process.js +128 -0
  19. package/dist/agent-reply.d.ts +47 -0
  20. package/dist/agent-reply.js +315 -0
  21. package/dist/agents.d.ts +24 -5
  22. package/dist/agents.js +60 -9
  23. package/dist/api-coverage.d.ts +11 -0
  24. package/dist/api-coverage.js +72 -0
  25. package/dist/application-probe.d.ts +28 -0
  26. package/dist/application-probe.js +93 -0
  27. package/dist/application-url.d.ts +14 -0
  28. package/dist/application-url.js +26 -0
  29. package/dist/args.js +15 -0
  30. package/dist/artifact-deploy.d.ts +9 -0
  31. package/dist/artifact-deploy.js +28 -8
  32. package/dist/assets.d.ts +72 -0
  33. package/dist/assets.js +382 -0
  34. package/dist/auth.d.ts +10 -0
  35. package/dist/auth.js +38 -9
  36. package/dist/author.d.ts +206 -4
  37. package/dist/author.js +1650 -52
  38. package/dist/authoring-batches.d.ts +190 -0
  39. package/dist/authoring-batches.js +489 -0
  40. package/dist/authoring-postpass.d.ts +79 -0
  41. package/dist/authoring-postpass.js +1289 -0
  42. package/dist/authoring-progress.d.ts +95 -0
  43. package/dist/authoring-progress.js +269 -0
  44. package/dist/autosync.d.ts +56 -0
  45. package/dist/autosync.js +442 -0
  46. package/dist/batch-artifacts.d.ts +81 -0
  47. package/dist/batch-artifacts.js +412 -0
  48. package/dist/batch-limits.d.ts +37 -0
  49. package/dist/batch-limits.js +80 -0
  50. package/dist/branding.d.ts +41 -0
  51. package/dist/branding.js +229 -0
  52. package/dist/bundle-upload.d.ts +26 -0
  53. package/dist/bundle-upload.js +99 -0
  54. package/dist/capture-auth.d.ts +95 -0
  55. package/dist/capture-auth.js +204 -0
  56. package/dist/capture.d.ts +48 -0
  57. package/dist/capture.js +20 -1
  58. package/dist/cli.js +839 -32
  59. package/dist/content-links.d.ts +10 -0
  60. package/dist/content-links.js +49 -0
  61. package/dist/contract-validation.d.ts +4 -0
  62. package/dist/contract-validation.js +25 -0
  63. package/dist/coverage-actions.d.ts +8 -0
  64. package/dist/coverage-actions.js +71 -0
  65. package/dist/coverage-resolutions.d.ts +16 -0
  66. package/dist/coverage-resolutions.js +58 -0
  67. package/dist/db.d.ts +37 -0
  68. package/dist/db.js +288 -0
  69. package/dist/demo.d.ts +12 -0
  70. package/dist/demo.js +122 -0
  71. package/dist/deploy-credentials.d.ts +7 -0
  72. package/dist/deploy-credentials.js +76 -0
  73. package/dist/deploy-targets/github-pages.d.ts +3 -0
  74. package/dist/deploy-targets/github-pages.js +100 -0
  75. package/dist/deploy-targets/index.d.ts +8 -0
  76. package/dist/deploy-targets/index.js +78 -0
  77. package/dist/deploy-targets/netlify.d.ts +3 -0
  78. package/dist/deploy-targets/netlify.js +49 -0
  79. package/dist/deploy-targets/types.d.ts +32 -0
  80. package/dist/deploy-targets/types.js +2 -0
  81. package/dist/deploy-targets/vercel.d.ts +3 -0
  82. package/dist/deploy-targets/vercel.js +95 -0
  83. package/dist/deploy.d.ts +9 -0
  84. package/dist/deploy.js +87 -13
  85. package/dist/deterministic-capture.d.ts +90 -0
  86. package/dist/deterministic-capture.js +435 -0
  87. package/dist/direct-edit.d.ts +40 -0
  88. package/dist/direct-edit.js +159 -0
  89. package/dist/docs-crawl.d.ts +86 -0
  90. package/dist/docs-crawl.js +536 -0
  91. package/dist/docs-site.d.ts +49 -0
  92. package/dist/docs-site.js +233 -0
  93. package/dist/doctor.js +8 -0
  94. package/dist/documentation-collections.d.ts +23 -0
  95. package/dist/documentation-collections.js +191 -0
  96. package/dist/documentation-plan.d.ts +253 -0
  97. package/dist/documentation-plan.js +2563 -0
  98. package/dist/doxbrix-build.d.ts +19 -0
  99. package/dist/doxbrix-build.js +167 -0
  100. package/dist/doxbrix-markdown.d.ts +9 -0
  101. package/dist/doxbrix-markdown.js +115 -14
  102. package/dist/drift.d.ts +10 -0
  103. package/dist/drift.js +164 -0
  104. package/dist/evaluation.d.ts +46 -0
  105. package/dist/evaluation.js +113 -0
  106. package/dist/evidence-pack.d.ts +47 -0
  107. package/dist/evidence-pack.js +358 -0
  108. package/dist/evidence.d.ts +25 -0
  109. package/dist/evidence.js +175 -0
  110. package/dist/fs.d.ts +8 -2
  111. package/dist/fs.js +41 -11
  112. package/dist/generator-api.d.ts +89 -0
  113. package/dist/generator-preflight.d.ts +27 -0
  114. package/dist/generator-preflight.js +105 -0
  115. package/dist/generator-runtime.d.ts +7 -0
  116. package/dist/generator-runtime.js +17 -1
  117. package/dist/generators.d.ts +24 -2
  118. package/dist/generators.js +54 -1
  119. package/dist/git-delivery.d.ts +17 -0
  120. package/dist/git-delivery.js +123 -0
  121. package/dist/globs.d.ts +16 -0
  122. package/dist/globs.js +65 -0
  123. package/dist/glossary.d.ts +26 -0
  124. package/dist/glossary.js +179 -0
  125. package/dist/history.d.ts +106 -0
  126. package/dist/history.js +600 -0
  127. package/dist/html-markdown.d.ts +46 -0
  128. package/dist/html-markdown.js +423 -0
  129. package/dist/interactive.js +3 -0
  130. package/dist/job-events.d.ts +74 -0
  131. package/dist/job-events.js +377 -0
  132. package/dist/keep-awake.d.ts +50 -0
  133. package/dist/keep-awake.js +123 -0
  134. package/dist/local-source-snapshot.d.ts +20 -0
  135. package/dist/local-source-snapshot.js +61 -0
  136. package/dist/mintlify-detect.d.ts +3 -0
  137. package/dist/mintlify-detect.js +18 -0
  138. package/dist/mintlify-import.d.ts +75 -0
  139. package/dist/mintlify-import.js +190 -0
  140. package/dist/navigation.d.ts +98 -0
  141. package/dist/navigation.js +310 -0
  142. package/dist/openapi.d.ts +60 -0
  143. package/dist/openapi.js +439 -0
  144. package/dist/page-editor-bridge.d.ts +3 -0
  145. package/dist/page-editor-bridge.js +109 -0
  146. package/dist/page-editor-preview.d.ts +10 -0
  147. package/dist/page-editor-preview.js +55 -0
  148. package/dist/page-extension.d.ts +9 -0
  149. package/dist/page-extension.js +15 -0
  150. package/dist/page-metadata.d.ts +28 -0
  151. package/dist/page-metadata.js +166 -0
  152. package/dist/page-operations.d.ts +34 -0
  153. package/dist/page-operations.js +215 -0
  154. package/dist/page-routes.d.ts +4 -0
  155. package/dist/page-routes.js +61 -0
  156. package/dist/pages.d.ts +20 -0
  157. package/dist/pages.js +184 -0
  158. package/dist/plan-generator.d.ts +3 -0
  159. package/dist/plan-generator.js +21 -0
  160. package/dist/plan-navigation.d.ts +11 -0
  161. package/dist/plan-navigation.js +30 -0
  162. package/dist/planning-captures.d.ts +20 -0
  163. package/dist/planning-captures.js +143 -0
  164. package/dist/planning-research.d.ts +135 -0
  165. package/dist/planning-research.js +472 -0
  166. package/dist/planning-triage.d.ts +23 -0
  167. package/dist/planning-triage.js +131 -0
  168. package/dist/preview.d.ts +24 -0
  169. package/dist/preview.js +280 -29
  170. package/dist/project-detect.d.ts +36 -0
  171. package/dist/project-detect.js +251 -0
  172. package/dist/project-import.d.ts +54 -0
  173. package/dist/project-import.js +157 -0
  174. package/dist/project-lock.d.ts +6 -0
  175. package/dist/project-lock.js +96 -0
  176. package/dist/project-registry.d.ts +25 -0
  177. package/dist/project-registry.js +79 -0
  178. package/dist/project.d.ts +35 -3
  179. package/dist/project.js +388 -38
  180. package/dist/prompts.d.ts +9 -0
  181. package/dist/prompts.js +25 -0
  182. package/dist/proposal-replay.d.ts +32 -0
  183. package/dist/proposal-replay.js +99 -0
  184. package/dist/quality-claims.d.ts +8 -0
  185. package/dist/quality-claims.js +168 -0
  186. package/dist/quality-config.d.ts +5 -0
  187. package/dist/quality-config.js +84 -0
  188. package/dist/quality-contract.d.ts +37 -0
  189. package/dist/quality-contract.js +46 -0
  190. package/dist/quality-examples.d.ts +4 -0
  191. package/dist/quality-examples.js +233 -0
  192. package/dist/quality-gates.d.ts +16 -0
  193. package/dist/quality-gates.js +192 -0
  194. package/dist/quality-links.d.ts +7 -0
  195. package/dist/quality-links.js +149 -0
  196. package/dist/quality-lint.d.ts +6 -0
  197. package/dist/quality-lint.js +124 -0
  198. package/dist/quality-rendered.d.ts +11 -0
  199. package/dist/quality-rendered.js +222 -0
  200. package/dist/quality-schema.d.ts +3 -0
  201. package/dist/quality-schema.js +71 -0
  202. package/dist/release-notes.d.ts +44 -0
  203. package/dist/release-notes.js +183 -0
  204. package/dist/remote-monitor.d.ts +16 -0
  205. package/dist/remote-monitor.js +74 -0
  206. package/dist/remote-source.d.ts +34 -0
  207. package/dist/remote-source.js +426 -0
  208. package/dist/review-diff.d.ts +82 -0
  209. package/dist/review-diff.js +400 -0
  210. package/dist/review-learning.d.ts +11 -0
  211. package/dist/review-learning.js +60 -0
  212. package/dist/review-render.d.ts +38 -0
  213. package/dist/review-render.js +224 -0
  214. package/dist/review-report.d.ts +9 -0
  215. package/dist/review-report.js +89 -0
  216. package/dist/review-ui.d.ts +14 -0
  217. package/dist/review-ui.js +1248 -0
  218. package/dist/schedule.d.ts +78 -0
  219. package/dist/schedule.js +480 -0
  220. package/dist/screen-capture-provider.d.ts +52 -0
  221. package/dist/screen-capture-provider.js +218 -0
  222. package/dist/screenshot-workflow.d.ts +167 -0
  223. package/dist/screenshot-workflow.js +1237 -0
  224. package/dist/settings.d.ts +1 -1
  225. package/dist/settings.js +95 -7
  226. package/dist/site-export.d.ts +18 -0
  227. package/dist/site-export.js +87 -0
  228. package/dist/source-connectors.d.ts +33 -0
  229. package/dist/source-connectors.js +268 -0
  230. package/dist/source-discovery.d.ts +132 -0
  231. package/dist/source-discovery.js +823 -0
  232. package/dist/source-intelligence.d.ts +9 -0
  233. package/dist/source-intelligence.js +306 -0
  234. package/dist/sync-review.d.ts +28 -0
  235. package/dist/sync-review.js +264 -0
  236. package/dist/sync-runs.d.ts +192 -0
  237. package/dist/sync-runs.js +2244 -0
  238. package/dist/sync.d.ts +35 -0
  239. package/dist/sync.js +298 -32
  240. package/dist/text-diff.d.ts +9 -0
  241. package/dist/text-diff.js +59 -0
  242. package/dist/types.d.ts +946 -1
  243. package/dist/ui/assets/doxloop-logo-light-De7Nx7j7.png +0 -0
  244. package/dist/ui/assets/index-BHBYU2aG.css +1 -0
  245. package/dist/ui/assets/index-Cq3RPQiC.js +33 -0
  246. package/dist/ui/index.html +18 -0
  247. package/dist/ui-server.d.ts +83 -0
  248. package/dist/ui-server.js +3532 -0
  249. package/dist/usage-budget.d.ts +28 -0
  250. package/dist/usage-budget.js +90 -0
  251. package/dist/validation.d.ts +25 -1
  252. package/dist/validation.js +312 -26
  253. package/dist/workspace-tools.d.ts +54 -0
  254. package/dist/workspace-tools.js +123 -0
  255. package/docs/agent-compatibility.md +49 -32
  256. package/docs/ci-and-automation.md +105 -66
  257. package/docs/doxbrix-http-api.md +8 -1
  258. package/docs/existing-documentation.md +80 -0
  259. package/docs/generation-performance.md +108 -0
  260. package/docs/generator-authoring.md +68 -5
  261. package/docs/generator-selection.md +50 -13
  262. package/docs/mintlify-import.md +71 -0
  263. package/docs/openapi-security.md +25 -0
  264. package/docs/project-format.md +331 -36
  265. package/docs/release-quality.md +158 -0
  266. package/docs/releasing.md +72 -0
  267. package/docs/review-workflows.md +51 -0
  268. package/docs/security-model.md +102 -39
  269. package/docs/troubleshooting.md +226 -93
  270. package/package.json +46 -15
  271. package/scripts/test-auto-screenshot.mjs +172 -0
  272. package/skills/doxloop-authoring/SKILL.md +242 -361
  273. package/skills/doxloop-authoring/references/existing-documentation.md +94 -0
  274. package/skills/doxloop-authoring/references/navigation-architecture.md +18 -7
  275. package/skills/doxloop-authoring/references/page-depth.md +169 -0
  276. package/skills/doxloop-authoring/references/project-format.md +106 -7
  277. package/skills/doxloop-authoring/references/quality.md +10 -0
  278. package/skills/doxloop-authoring/references/screenshot-manifest.md +113 -0
  279. package/skills/doxloop-authoring/references/screenshots.md +149 -235
  280. package/skills/doxloop-authoring/references/workflows.md +76 -0
  281. package/skills/doxloop-doxbrix/SKILL.md +38 -22
  282. package/skills/doxloop-doxbrix/references/api-endpoints.md +15 -14
  283. package/skills/doxloop-doxbrix/references/components.md +30 -3
  284. package/skills/doxloop-doxbrix/references/manifest.md +3 -2
  285. package/vendor/doxbrix-import/LICENSE +202 -0
  286. package/vendor/doxbrix-import/README.md +17 -0
  287. package/vendor/doxbrix-import/UPSTREAM.json +47 -0
  288. package/vendor/doxbrix-import/dist/docs/frontmatter.d.ts +13 -0
  289. package/vendor/doxbrix-import/dist/docs/frontmatter.js +83 -0
  290. package/vendor/doxbrix-import/dist/docs/import.d.ts +21 -0
  291. package/vendor/doxbrix-import/dist/docs/import.js +147 -0
  292. package/vendor/doxbrix-import/dist/docs/manifest.d.ts +163 -0
  293. package/vendor/doxbrix-import/dist/docs/manifest.js +64 -0
  294. package/vendor/doxbrix-import/dist/docs/project.d.ts +25 -0
  295. package/vendor/doxbrix-import/dist/docs/project.js +77 -0
  296. package/vendor/doxbrix-import/dist/docs/starter.d.ts +4 -0
  297. package/vendor/doxbrix-import/dist/docs/starter.js +11 -0
  298. package/vendor/doxbrix-import/dist/importer.d.ts +227 -0
  299. package/vendor/doxbrix-import/dist/importer.js +1567 -0
  300. package/vendor/doxbrix-import/dist/mintlify-openapi.d.ts +37 -0
  301. package/vendor/doxbrix-import/dist/mintlify-openapi.js +305 -0
  302. package/vendor/doxbrix-import/dist/safe-path.d.ts +9 -0
  303. package/vendor/doxbrix-import/dist/safe-path.js +47 -0
  304. package/dist/agents.d.ts.map +0 -1
  305. package/dist/agents.js.map +0 -1
  306. package/dist/args.d.ts.map +0 -1
  307. package/dist/args.js.map +0 -1
  308. package/dist/artifact-deploy.d.ts.map +0 -1
  309. package/dist/artifact-deploy.js.map +0 -1
  310. package/dist/auth.d.ts.map +0 -1
  311. package/dist/auth.js.map +0 -1
  312. package/dist/author.d.ts.map +0 -1
  313. package/dist/author.js.map +0 -1
  314. package/dist/capture.d.ts.map +0 -1
  315. package/dist/capture.js.map +0 -1
  316. package/dist/cli.d.ts.map +0 -1
  317. package/dist/cli.js.map +0 -1
  318. package/dist/deploy.d.ts.map +0 -1
  319. package/dist/deploy.js.map +0 -1
  320. package/dist/deployment-visibility.d.ts.map +0 -1
  321. package/dist/deployment-visibility.js.map +0 -1
  322. package/dist/doctor.d.ts.map +0 -1
  323. package/dist/doctor.js.map +0 -1
  324. package/dist/doxbrix-markdown.d.ts.map +0 -1
  325. package/dist/doxbrix-markdown.js.map +0 -1
  326. package/dist/errors.d.ts.map +0 -1
  327. package/dist/errors.js.map +0 -1
  328. package/dist/fs.d.ts.map +0 -1
  329. package/dist/fs.js.map +0 -1
  330. package/dist/generator-api.d.ts.map +0 -1
  331. package/dist/generator-api.js.map +0 -1
  332. package/dist/generator-manager.d.ts.map +0 -1
  333. package/dist/generator-manager.js.map +0 -1
  334. package/dist/generator-runtime.d.ts.map +0 -1
  335. package/dist/generator-runtime.js.map +0 -1
  336. package/dist/generators.d.ts.map +0 -1
  337. package/dist/generators.js.map +0 -1
  338. package/dist/interactive.d.ts.map +0 -1
  339. package/dist/interactive.js.map +0 -1
  340. package/dist/preview.d.ts.map +0 -1
  341. package/dist/preview.js.map +0 -1
  342. package/dist/progress.d.ts.map +0 -1
  343. package/dist/progress.js.map +0 -1
  344. package/dist/project.d.ts.map +0 -1
  345. package/dist/project.js.map +0 -1
  346. package/dist/prompts.d.ts.map +0 -1
  347. package/dist/prompts.js.map +0 -1
  348. package/dist/settings.d.ts.map +0 -1
  349. package/dist/settings.js.map +0 -1
  350. package/dist/sync.d.ts.map +0 -1
  351. package/dist/sync.js.map +0 -1
  352. package/dist/types.d.ts.map +0 -1
  353. package/dist/types.js.map +0 -1
  354. package/dist/validation.d.ts.map +0 -1
  355. package/dist/validation.js.map +0 -1
  356. package/dist/version.d.ts.map +0 -1
  357. package/dist/version.js.map +0 -1
package/dist/agents.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { constants } from 'node:fs';
3
3
  import { access, cp, lstat, mkdir, readFile, readdir, rm, stat, } from 'node:fs/promises';
4
+ import { homedir } from 'node:os';
4
5
  import { delimiter, dirname, join, resolve } from 'node:path';
5
6
  import { fileURLToPath } from 'node:url';
6
7
  import spawn from 'cross-spawn';
@@ -126,7 +127,7 @@ export async function detectAgents() {
126
127
  }
127
128
  return found;
128
129
  }
129
- export async function installAgent(name) {
130
+ export async function installAgent(name, options = {}) {
130
131
  const agent = AGENT_CATALOG.find((candidate) => candidate.name === name);
131
132
  if (!agent)
132
133
  throw new DoxloopError(`Unsupported agent: ${name}`);
@@ -136,9 +137,14 @@ export async function installAgent(name) {
136
137
  }
137
138
  const exitCode = await new Promise((resolveInstall, rejectInstall) => {
138
139
  const child = spawn(npm, ['install', '--global', agent.packageName], {
139
- stdio: 'inherit',
140
+ stdio: options.onOutput ? ['ignore', 'pipe', 'pipe'] : 'inherit',
140
141
  env: process.env,
141
142
  });
143
+ options.onChild?.(child);
144
+ if (options.onOutput) {
145
+ child.stdout?.on('data', (chunk) => options.onOutput?.(chunk.toString()));
146
+ child.stderr?.on('data', (chunk) => options.onOutput?.(chunk.toString()));
147
+ }
142
148
  child.once('error', (error) => {
143
149
  rejectInstall(new DoxloopError(`Could not start npm to install ${agent.displayName}: ${error.message}`, 2));
144
150
  });
@@ -172,13 +178,9 @@ export async function chooseAgent(preferred) {
172
178
  const expected = preferred ?? 'Codex, Claude Code, or Gemini';
173
179
  throw new DoxloopError(`${expected} is not available on PATH. Install an agent or use --print to copy the prompt.`);
174
180
  }
175
- export async function agentAuthenticationStatus(agent) {
176
- if (agent.name === 'gemini') {
177
- return {
178
- status: 'unknown',
179
- detail: 'Gemini authentication cannot be verified automatically; it will be checked when Gemini starts.',
180
- };
181
- }
181
+ export async function agentAuthenticationStatus(agent, options = {}) {
182
+ if (agent.name === 'gemini')
183
+ return geminiAuthenticationStatus(options);
182
184
  const args = agent.name === 'codex'
183
185
  ? ['login', 'status']
184
186
  : ['auth', 'status', '--json'];
@@ -219,6 +221,37 @@ export async function agentAuthenticationStatus(agent) {
219
221
  : 'Claude Code is not signed in. Run `claude auth login`.',
220
222
  };
221
223
  }
224
+ /**
225
+ * Gemini CLI has no non-interactive status command, so its sign-in is read
226
+ * from the places it keeps credentials: an API key or Vertex AI project in
227
+ * the environment, or the OAuth token file a Google sign-in leaves behind.
228
+ * Only presence is checked; no credential value is read into memory for
229
+ * longer than the parse, and none is reported.
230
+ */
231
+ export async function geminiAuthenticationStatus(options = {}) {
232
+ const env = options.env ?? process.env;
233
+ if (env.GEMINI_API_KEY?.trim() || env.GOOGLE_API_KEY?.trim()) {
234
+ return { status: 'authenticated', detail: 'Gemini uses the API key from the environment.' };
235
+ }
236
+ const vertex = env.GOOGLE_GENAI_USE_VERTEXAI?.trim().toLowerCase();
237
+ if ((vertex === 'true' || vertex === '1') && (env.GOOGLE_CLOUD_PROJECT?.trim() || env.GOOGLE_APPLICATION_CREDENTIALS?.trim())) {
238
+ return { status: 'authenticated', detail: 'Gemini uses Vertex AI credentials from the environment.' };
239
+ }
240
+ const credentials = join(options.home ?? homedir(), '.gemini', 'oauth_creds.json');
241
+ try {
242
+ const parsed = JSON.parse(await readFile(credentials, 'utf8'));
243
+ if (typeof parsed.refresh_token === 'string' || typeof parsed.access_token === 'string') {
244
+ return { status: 'authenticated', detail: 'Gemini is signed in with a Google account.' };
245
+ }
246
+ }
247
+ catch {
248
+ // No token file, or one Gemini itself would not accept.
249
+ }
250
+ return {
251
+ status: 'unauthenticated',
252
+ detail: 'Gemini is not signed in. Run `gemini` once in a terminal and complete the sign-in, or set GEMINI_API_KEY.',
253
+ };
254
+ }
222
255
  async function runAgentStatus(executable, args) {
223
256
  return new Promise((resolveStatus) => {
224
257
  const child = spawn(executable, args, {
@@ -247,6 +280,24 @@ async function runAgentStatus(executable, args) {
247
280
  });
248
281
  }
249
282
  async function findExecutable(name) {
283
+ if (name === 'codex' || name === 'claude' || name === 'gemini') {
284
+ const override = process.env[`DOXLOOP_AGENT_EXECUTABLE_${name.toUpperCase()}`];
285
+ if (override) {
286
+ const candidate = resolve(override);
287
+ try {
288
+ const stats = await stat(candidate);
289
+ if (stats.isFile()) {
290
+ if (process.platform !== 'win32')
291
+ await access(candidate, constants.X_OK);
292
+ return candidate;
293
+ }
294
+ throw new Error('not a file');
295
+ }
296
+ catch {
297
+ throw new DoxloopError(`The test agent executable for ${name} is not runnable: ${candidate}`);
298
+ }
299
+ }
300
+ }
250
301
  const path = process.env.PATH;
251
302
  if (!path)
252
303
  return undefined;
@@ -0,0 +1,11 @@
1
+ import type { ValidationIssue } from './types.js';
2
+ /** `METHOD /path` for every `<ApiEndpoint>` block in a page. */
3
+ export declare function documentedOperations(content: string): string[];
4
+ /**
5
+ * One warning per contract operation that no page documents with an
6
+ * `<ApiEndpoint>` block, filed against the page that mentions it (the
7
+ * operation itself first, then its path). Operations no page mentions are
8
+ * returned without a file so the caller can report them without a fix session.
9
+ */
10
+ export declare function contractCoverageIssues(root: string, files: string[]): Promise<ValidationIssue[]>;
11
+ //# sourceMappingURL=api-coverage.d.ts.map
@@ -0,0 +1,72 @@
1
+ /**
2
+ * API contract coverage for Doxbrix sites.
3
+ *
4
+ * A RealWorld run documented 14 of the contract's 19 operations with an
5
+ * `<ApiEndpoint>` block; login, registration, article deletion, and the two
6
+ * favorite operations were written as curl walkthroughs on how-to pages, so
7
+ * the published reference had no "Try it" entry for them and nothing flagged
8
+ * the gap. The end-of-run check now names each contract operation without a
9
+ * reference block and hands it to the page that already talks about it.
10
+ */
11
+ import { readFile } from 'node:fs/promises';
12
+ import { join } from 'node:path';
13
+ import { discoverDocumentationSources } from './source-discovery.js';
14
+ const ENDPOINT_TAG = /<ApiEndpoint\b([^>]*)>/g;
15
+ /** `METHOD /path` for every `<ApiEndpoint>` block in a page. */
16
+ export function documentedOperations(content) {
17
+ const found = [];
18
+ for (const match of content.matchAll(ENDPOINT_TAG)) {
19
+ const method = /\bmethod=["']([A-Za-z]+)["']/.exec(match[1])?.[1];
20
+ const path = /\bpath=["']([^"']+)["']/.exec(match[1])?.[1];
21
+ if (method && path)
22
+ found.push(`${method.toUpperCase()} ${normalizePath(path)}`);
23
+ }
24
+ return found;
25
+ }
26
+ /**
27
+ * One warning per contract operation that no page documents with an
28
+ * `<ApiEndpoint>` block, filed against the page that mentions it (the
29
+ * operation itself first, then its path). Operations no page mentions are
30
+ * returned without a file so the caller can report them without a fix session.
31
+ */
32
+ export async function contractCoverageIssues(root, files) {
33
+ let operations;
34
+ try {
35
+ const { inventory } = await discoverDocumentationSources(root);
36
+ operations = [...new Set(inventory.sources.flatMap((source) => source.evidence.filter((row) => row.contract && row.kind === 'operation').map((row) => row.label)))];
37
+ }
38
+ catch {
39
+ return [];
40
+ }
41
+ if (operations.length === 0)
42
+ return [];
43
+ const pages = [];
44
+ for (const file of files) {
45
+ try {
46
+ pages.push({ file, content: await readFile(join(root, file), 'utf8') });
47
+ }
48
+ catch { /* A page the run did not write. */ }
49
+ }
50
+ const documented = new Set(pages.flatMap((page) => documentedOperations(page.content)));
51
+ const issues = [];
52
+ for (const operation of operations) {
53
+ const [method = '', ...rest] = operation.split(' ');
54
+ const path = normalizePath(rest.join(' '));
55
+ if (documented.has(`${method} ${path}`))
56
+ continue;
57
+ const mentions = (page) => page.content.includes(`${method} ${rest.join(' ')}`) || page.content.includes(`${method} ${path}`);
58
+ const home = pages.find(mentions) ?? pages.find((page) => page.content.includes(rest.join(' ')));
59
+ issues.push({
60
+ severity: 'warning',
61
+ code: 'api-endpoint-missing',
62
+ message: `The API contract's ${operation} has no <ApiEndpoint> reference block on any page. Add one on this page where it discusses the operation: method, path, baseUrl, a <Param> for each path, query, header, and body field, and a <Response> for the success and each documented error status, all taken from the contract.`,
63
+ ...(home ? { file: home.file } : {}),
64
+ });
65
+ }
66
+ return issues;
67
+ }
68
+ /** `{slug}` and `:slug` path parameters compare equal. */
69
+ function normalizePath(path) {
70
+ return path.trim().replace(/:([A-Za-z_][\w]*)/g, '{$1}').replace(/\/+$/, '') || '/';
71
+ }
72
+ //# sourceMappingURL=api-coverage.js.map
@@ -0,0 +1,28 @@
1
+ /** Paths (or hash routes) a product uses for its sign-in screen. */
2
+ export declare const SIGN_IN_ROUTE: RegExp;
3
+ export interface SignInProbeObservation {
4
+ /** Where the page ended up once client routing settled. */
5
+ finalUrl: string;
6
+ /** Whether a visible password field is on the page. */
7
+ hasPasswordField: boolean;
8
+ }
9
+ export interface SignInWall {
10
+ /** The sign-in route the visitor was sent to, such as `/auth` or `/_/#/login`. */
11
+ signInPath: string;
12
+ }
13
+ /**
14
+ * Decide from what the browser saw whether the page is a sign-in wall: the
15
+ * final route looks like a sign-in route, or a password field is visible.
16
+ */
17
+ export declare function signInWallFromObservation(observation: SignInProbeObservation): SignInWall | undefined;
18
+ export type SignInProbe = (url: string, options?: {
19
+ timeoutMs?: number;
20
+ }) => Promise<SignInWall | undefined>;
21
+ /**
22
+ * Load `url` headless and report a sign-in wall, or undefined when the page
23
+ * is not one or nothing could be learned (no browser, timeout, crash).
24
+ */
25
+ export declare function probeSignInWall(url: string, options?: {
26
+ timeoutMs?: number;
27
+ }): Promise<SignInWall | undefined>;
28
+ //# sourceMappingURL=application-probe.d.ts.map
@@ -0,0 +1,93 @@
1
+ /**
2
+ * A single-page application answers every route with HTTP 200 and decides in
3
+ * the browser whether the visitor may stay, so a plain fetch reports a sign-in
4
+ * wall as a reachable page. This probe loads the page in the managed headless
5
+ * browser, lets client routing settle, and reports where the visitor landed.
6
+ * It never throws: a missing browser or a slow page means "no evidence".
7
+ */
8
+ import { loadInstalledPlaywright } from './capture.js';
9
+ /** Paths (or hash routes) a product uses for its sign-in screen. */
10
+ export const SIGN_IN_ROUTE = /(?:^|[/#])(?:login|log-in|signin|sign-in|sign_in|auth|authenticate|sso|oauth)(?:[/?#]|$)/i;
11
+ /**
12
+ * Decide from what the browser saw whether the page is a sign-in wall: the
13
+ * final route looks like a sign-in route, or a password field is visible.
14
+ */
15
+ export function signInWallFromObservation(observation) {
16
+ let url;
17
+ try {
18
+ url = new URL(observation.finalUrl);
19
+ }
20
+ catch {
21
+ return observation.hasPasswordField ? { signInPath: observation.finalUrl } : undefined;
22
+ }
23
+ const hash = url.hash && url.hash !== '#' ? url.hash.replace(/\?.*$/, '') : '';
24
+ if (SIGN_IN_ROUTE.test(url.pathname))
25
+ return { signInPath: url.pathname };
26
+ // Hash-routed applications (PocketBase `/_/#/login`) keep the route after `#`.
27
+ if (hash && SIGN_IN_ROUTE.test(hash))
28
+ return { signInPath: `${url.pathname}${hash}` };
29
+ if (observation.hasPasswordField)
30
+ return { signInPath: `${url.pathname}${hash}` };
31
+ return undefined;
32
+ }
33
+ const DEFAULT_PROBE_TIMEOUT_MS = 8_000;
34
+ /** Client-side guards often redirect just after the first idle moment. */
35
+ const SETTLE_AFTER_IDLE_MS = 400;
36
+ /**
37
+ * Load `url` headless and report a sign-in wall, or undefined when the page
38
+ * is not one or nothing could be learned (no browser, timeout, crash).
39
+ */
40
+ export async function probeSignInWall(url, options = {}) {
41
+ const timeoutMs = options.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
42
+ let browser;
43
+ let timer;
44
+ let abandoned = false;
45
+ const run = async () => {
46
+ const playwright = await loadInstalledPlaywright();
47
+ if (!playwright || abandoned)
48
+ return undefined;
49
+ const launched = (await playwright.chromium.launch({ headless: true }));
50
+ // The overall deadline may have passed while the browser started.
51
+ if (abandoned) {
52
+ await launched.close().catch(() => { });
53
+ return undefined;
54
+ }
55
+ browser = launched;
56
+ const context = await browser.newContext({ viewport: { width: 1280, height: 800 } });
57
+ const page = (await context.newPage());
58
+ const deadline = Date.now() + timeoutMs;
59
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout: timeoutMs });
60
+ await page.waitForLoadState('networkidle', { timeout: Math.max(500, deadline - Date.now()) }).catch(() => { });
61
+ await page.waitForTimeout(Math.min(SETTLE_AFTER_IDLE_MS, Math.max(0, deadline - Date.now())));
62
+ let hasPasswordField = false;
63
+ try {
64
+ const field = page.locator('input[type="password"]');
65
+ hasPasswordField = (await field.count()) > 0 && (await field.first().isVisible());
66
+ }
67
+ catch {
68
+ hasPasswordField = false;
69
+ }
70
+ return signInWallFromObservation({ finalUrl: page.url(), hasPasswordField });
71
+ };
72
+ try {
73
+ return await Promise.race([
74
+ run().catch(() => undefined),
75
+ new Promise((resolve) => {
76
+ timer = setTimeout(() => resolve(undefined), timeoutMs + 4_000);
77
+ timer.unref?.();
78
+ }),
79
+ ]);
80
+ }
81
+ catch {
82
+ return undefined;
83
+ }
84
+ finally {
85
+ abandoned = true;
86
+ if (timer)
87
+ clearTimeout(timer);
88
+ const opened = browser;
89
+ if (opened)
90
+ await opened.close().catch(() => { });
91
+ }
92
+ }
93
+ //# sourceMappingURL=application-probe.js.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Resolve an application route (`/settings`) against the configured base URL.
3
+ *
4
+ * Plain `new URL(route, base)` is wrong for a hash-routed single-page
5
+ * application (`https://host/_/#`): the route lives in the fragment, so
6
+ * `/settings` must open `https://host/_/#/settings`, not `https://host/settings`
7
+ * (which the server answers with 404). Every other base URL keeps the standard
8
+ * resolution, because some projects configure a page such as `/login` as the
9
+ * base and still write routes from the origin root.
10
+ */
11
+ export declare function applicationUrl(baseUrl: string, route: string | undefined): URL;
12
+ /** A base URL ending in an empty or route-shaped fragment (`#`, `#/`) is hash-routed. */
13
+ export declare function hashRouted(base: URL | string): boolean;
14
+ //# sourceMappingURL=application-url.d.ts.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Resolve an application route (`/settings`) against the configured base URL.
3
+ *
4
+ * Plain `new URL(route, base)` is wrong for a hash-routed single-page
5
+ * application (`https://host/_/#`): the route lives in the fragment, so
6
+ * `/settings` must open `https://host/_/#/settings`, not `https://host/settings`
7
+ * (which the server answers with 404). Every other base URL keeps the standard
8
+ * resolution, because some projects configure a page such as `/login` as the
9
+ * base and still write routes from the origin root.
10
+ */
11
+ export function applicationUrl(baseUrl, route) {
12
+ const base = new URL(baseUrl);
13
+ const path = route?.trim() || '/';
14
+ if (!hashRouted(base) || /^[a-z][a-z0-9+.-]*:/i.test(path) || path.startsWith('//'))
15
+ return new URL(path, base);
16
+ const document = new URL(base.toString());
17
+ const routed = path.startsWith('#') ? path.slice(1) : path;
18
+ document.hash = routed.startsWith('/') ? routed : `/${routed}`;
19
+ return document;
20
+ }
21
+ /** A base URL ending in an empty or route-shaped fragment (`#`, `#/`) is hash-routed. */
22
+ export function hashRouted(base) {
23
+ const url = typeof base === 'string' ? new URL(base) : base;
24
+ return url.href.includes('#') && (url.hash === '' || url.hash.startsWith('#/'));
25
+ }
26
+ //# sourceMappingURL=application-url.js.map
package/dist/args.js CHANGED
@@ -1,16 +1,31 @@
1
1
  import { UsageError } from './errors.js';
2
2
  const BOOLEAN_FLAGS = new Set([
3
+ 'backfill-evidence',
4
+ 'ignore-screenshot-problems',
3
5
  'dry-run',
4
6
  'h',
5
7
  'help',
6
8
  'open',
7
9
  'print',
8
10
  'public',
11
+ 'quiet',
9
12
  'screenshots',
10
13
  'no-screenshots',
14
+ 'no-open',
11
15
  'v',
12
16
  'version',
13
17
  'yes',
18
+ 'offline',
19
+ 'rendered',
20
+ 'examples',
21
+ 'fix',
22
+ 'update-visuals',
23
+ 'warnings-as-errors',
24
+ 'approve-baseline',
25
+ 'approve-quality-baseline',
26
+ 'allow-related',
27
+ 'existing',
28
+ 'zip',
14
29
  ]);
15
30
  export function parseArgs(argv) {
16
31
  const flags = new Map();
@@ -6,4 +6,13 @@ export declare function deployGeneratedSite(options: {
6
6
  public?: boolean;
7
7
  apiUrl?: string;
8
8
  }): Promise<void>;
9
+ export declare function containedOutput(root: string, outputDir: string): string;
10
+ export declare function runLocalBuild(generator: string, command: string, root: string, siteUrl: string): Promise<void>;
11
+ export declare function packageStaticOutput(root: string): Promise<{
12
+ archive: Uint8Array;
13
+ files: number;
14
+ omittedSourceMaps: number;
15
+ sha256: string;
16
+ }>;
17
+ export declare function writeDryRunArchive(root: string, archive: Uint8Array): Promise<string>;
9
18
  //# sourceMappingURL=artifact-deploy.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { createHash } from 'node:crypto';
3
- import { lstat, readdir, readFile } from 'node:fs/promises';
4
- import { basename, extname, relative, resolve, sep } from 'node:path';
3
+ import { lstat, mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
4
+ import { basename, extname, join, relative, resolve, sep } from 'node:path';
5
5
  import { zipSync } from 'fflate';
6
6
  import { apiUrl, authenticatedRequest, authenticatedRequestOptional } from './auth.js';
7
7
  import { DoxloopError } from './errors.js';
@@ -65,7 +65,8 @@ export async function deployGeneratedSite(options) {
65
65
  const packaged = await packageStaticOutput(outputRoot);
66
66
  building.done(`Built ${adapter.displayName} artifact`, `${packaged.files} files, ${formatBytes(packaged.archive.byteLength)}${packaged.omittedSourceMaps ? `, omitted ${packaged.omittedSourceMaps} source maps` : ''}`);
67
67
  if (options.dryRun) {
68
- process.stdout.write(`\nDeployment is valid.\nGenerator: ${project.generator}\nVisibility: ${visibility}\nOutput: ${adapter.build.outputDir}\nFiles: ${packaged.files}\nArtifact: ${formatBytes(packaged.archive.byteLength)}\nSHA-256: ${packaged.sha256}\nNo data was uploaded.\n`);
68
+ const archivePath = await writeDryRunArchive(options.root, packaged.archive);
69
+ process.stdout.write(`\nDeployment is valid.\nGenerator: ${project.generator}\nVisibility: ${visibility}\nOutput: ${adapter.build.outputDir}\nFiles: ${packaged.files}\nArtifact: ${formatBytes(packaged.archive.byteLength)}\nArchive: ${archivePath}\nSHA-256: ${packaged.sha256}\nNo data was uploaded.\n`);
69
70
  return;
70
71
  }
71
72
  if (!target)
@@ -104,7 +105,7 @@ function assertArtifactProject(project, generator) {
104
105
  throw new DoxloopError(`Project "${project.slug}" expects generator "${project.generator ?? 'unknown'}", but this Doxloop project uses "${generator}". Choose another --slug.`);
105
106
  }
106
107
  }
107
- function containedOutput(root, outputDir) {
108
+ export function containedOutput(root, outputDir) {
108
109
  const base = resolve(root);
109
110
  const output = resolve(base, outputDir);
110
111
  if (output === base || !output.startsWith(`${base}${sep}`)) {
@@ -112,10 +113,14 @@ function containedOutput(root, outputDir) {
112
113
  }
113
114
  return output;
114
115
  }
115
- async function runLocalBuild(generator, command, root, siteUrl) {
116
+ export async function runLocalBuild(generator, command, root, siteUrl) {
116
117
  const environment = { ...process.env, DOXLOOP_SITE_URL: siteUrl, SITE_URL: siteUrl };
117
118
  delete environment.DOXLOOP_TOKEN;
118
119
  delete environment.DOXBRIX_TOKEN;
120
+ delete environment.DOXLOOP_NETLIFY_TOKEN;
121
+ delete environment.NETLIFY_AUTH_TOKEN;
122
+ delete environment.DOXLOOP_VERCEL_TOKEN;
123
+ delete environment.VERCEL_TOKEN;
119
124
  const effectiveCommand = generator === 'hugo'
120
125
  ? `${command} --baseURL ${JSON.stringify(`${siteUrl.replace(/\/$/, '')}/`)}`
121
126
  : generator === 'jekyll'
@@ -135,7 +140,7 @@ async function runLocalBuild(generator, command, root, siteUrl) {
135
140
  throw new DoxloopError(`Local generator build failed (exit ${code}). Doxloop does not install dependencies during deploy; install them and run \`${command}\` locally to diagnose.`);
136
141
  }
137
142
  }
138
- async function packageStaticOutput(root) {
143
+ export async function packageStaticOutput(root) {
139
144
  let rootStat;
140
145
  try {
141
146
  rootStat = await lstat(root);
@@ -181,9 +186,16 @@ async function packageStaticOutput(root) {
181
186
  if (/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/.test(text)) {
182
187
  throw new DoxloopError(`Build output appears to contain a private key: ${path}`);
183
188
  }
184
- const activeTokens = [process.env.DOXLOOP_TOKEN, process.env.DOXBRIX_TOKEN].filter((value) => Boolean(value && value.length >= 12));
189
+ const activeTokens = [
190
+ process.env.DOXLOOP_TOKEN,
191
+ process.env.DOXBRIX_TOKEN,
192
+ process.env.DOXLOOP_NETLIFY_TOKEN,
193
+ process.env.NETLIFY_AUTH_TOKEN,
194
+ process.env.DOXLOOP_VERCEL_TOKEN,
195
+ process.env.VERCEL_TOKEN,
196
+ ].filter((value) => Boolean(value && value.length >= 12));
185
197
  if (activeTokens.some((token) => text.includes(token)) || /\bdxb_[A-Za-z0-9_-]{20,}\b/.test(text)) {
186
- throw new DoxloopError(`Build output appears to contain a Doxbrix access token: ${path}`);
198
+ throw new DoxloopError(`Build output appears to contain a deployment access token: ${path}`);
187
199
  }
188
200
  entries[path] = new Uint8Array(data);
189
201
  if (path === 'index.html')
@@ -200,6 +212,14 @@ async function packageStaticOutput(root) {
200
212
  throw new DoxloopError(`Compressed artifact exceeds ${formatBytes(MAX_ZIP_BYTES)}.`);
201
213
  return { archive, files, omittedSourceMaps, sha256: createHash('sha256').update(archive).digest('hex') };
202
214
  }
215
+ export async function writeDryRunArchive(root, archive) {
216
+ const directory = join(root, '.doxloop', 'exports');
217
+ await mkdir(directory, { recursive: true, mode: 0o700 });
218
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
219
+ const path = join(directory, `${stamp}.zip`);
220
+ await writeFile(path, archive, { mode: 0o600 });
221
+ return path;
222
+ }
203
223
  function assertSafeArtifactName(path) {
204
224
  const name = basename(path).toLowerCase();
205
225
  if (name === '.env' || name.startsWith('.env.') || name === 'id_rsa' || name === 'id_ed25519' ||
@@ -0,0 +1,72 @@
1
+ import type { GeneratorAdapter } from './generator-api.js';
2
+ import type { DoxloopProject } from './types.js';
3
+ /**
4
+ * Images and downloadable files that documentation pages reference. Uploads
5
+ * land in the generator's conventional asset directory and are referenced by
6
+ * a public path pages can use directly; the library lists every asset with the
7
+ * pages that embed it so alt text can be edited and unused files deleted.
8
+ */
9
+ export declare const ASSET_IMAGE_EXTENSIONS: Set<string>;
10
+ export declare const ASSET_FILE_EXTENSIONS: Set<string>;
11
+ export declare const MAX_ASSET_BYTES: number;
12
+ export interface AssetReference {
13
+ page: string;
14
+ alt: string;
15
+ }
16
+ export interface AssetEntry {
17
+ /** Project-relative path. */
18
+ path: string;
19
+ name: string;
20
+ /** The address a page uses to embed the asset. */
21
+ publicPath: string;
22
+ kind: 'image' | 'file';
23
+ bytes: number;
24
+ modifiedAt: string;
25
+ references: AssetReference[];
26
+ }
27
+ export interface AssetLibrary {
28
+ /** Project-relative directory uploads go to. */
29
+ directory: string;
30
+ publicPrefix: string;
31
+ maxBytes: number;
32
+ assets: AssetEntry[];
33
+ }
34
+ export declare function assetLocation(project: DoxloopProject, adapter?: GeneratorAdapter): {
35
+ directory: string;
36
+ publicPrefix: string;
37
+ };
38
+ export declare function readAssetLibrary(root: string): Promise<AssetLibrary>;
39
+ export declare function listAssets(root: string, loaded?: DoxloopProject, loadedAdapter?: GeneratorAdapter): Promise<AssetEntry[]>;
40
+ export interface AssetUpload {
41
+ name: string;
42
+ /** Base64 file content, with or without a data-URL prefix. */
43
+ data: string;
44
+ /** Project-relative directory inside the content or asset folder; defaults to the generator's asset directory. */
45
+ directory?: string;
46
+ /** Overwrite an existing file with the same name. */
47
+ replace?: boolean;
48
+ }
49
+ export declare function uploadAsset(root: string, raw: unknown): Promise<AssetEntry>;
50
+ export declare function deleteAsset(root: string, raw: unknown): Promise<{
51
+ removed: string;
52
+ }>;
53
+ /** Rewrite the alt text of every reference to one asset across the pages that embed it. */
54
+ export declare function updateAssetAlt(root: string, raw: unknown): Promise<AssetEntry>;
55
+ /** Replace one screenshot in a proposal workspace before the proposal is accepted. */
56
+ export declare function replaceRunCapture(root: string, raw: unknown): Promise<{
57
+ run: string;
58
+ path: string;
59
+ bytes: number;
60
+ }>;
61
+ /**
62
+ * Markdown image references and HTML `img`/`source` tags with their alt text.
63
+ * Exposed so the page service and the library agree on what counts as a
64
+ * reference.
65
+ */
66
+ export declare function pageAssetReferences(raw: string): Array<{
67
+ reference: string;
68
+ alt: string;
69
+ }>;
70
+ export declare function safeAssetName(raw: string): string;
71
+ export declare function decodeUpload(data: string, name: string): Buffer;
72
+ //# sourceMappingURL=assets.d.ts.map