@doxbrix/doxloop 0.1.4 → 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 +1153 -0
  2. package/README.md +422 -226
  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 +40 -4
  22. package/dist/agents.js +115 -11
  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 +16 -15
  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 +33 -4
  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 +50 -28
  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/cli.js CHANGED
@@ -1,22 +1,40 @@
1
1
  #!/usr/bin/env node
2
+ import { replayRun } from './proposal-replay.js';
3
+ import { auditDocumentation, backfillEvidence } from './workspace-tools.js';
2
4
  import { mkdir, readFile } from 'node:fs/promises';
3
5
  import { join, relative, resolve } from 'node:path';
4
6
  import { assertAllowedFlags, booleanFlag, flag, flags, numberFlag, parseArgs, } from './args.js';
5
7
  import { installSkill, parseAgent, skillStatus } from './agents.js';
6
- import { loadUserConfig, login, logout, whoami } from './auth.js';
7
- import { parseReasoning, resolveScreenshotIntent, runAuthor, } from './author.js';
8
+ import { loadUserConfig, tokenFor, login, logout, whoami } from './auth.js';
9
+ import { parseClaudeEffort, parseReasoning, resolveScreenshotIntent, runAuthor, } from './author.js';
8
10
  import { capture } from './capture.js';
11
+ import { applySyncConfig, computeConfiguredDrift, disableSync, formatSyncStatus, parseSyncMode, parseTriggerList, replaySyncSetupCommand, runSyncNow, runSyncSetupWizard, } from './autosync.js';
9
12
  import { deploy } from './deploy.js';
13
+ import { createDemoWorkspace } from './demo.js';
14
+ import { continueDocumentationPlanGeneration, generateApprovedDocumentationPlan, planAuthoringRecord, proposeDocumentationPlan, readDocumentationPlan, reviseDocumentationPlan, } from './documentation-plan.js';
15
+ import { historyAvailable } from './db.js';
16
+ import { formatDrift } from './drift.js';
17
+ import { backfillHistory, formatRequestHistory, listDeployments, listRequests, pageHistory, } from './history.js';
10
18
  import { formatDoctorReport, runDoctor } from './doctor.js';
11
19
  import { DoxloopError, UsageError } from './errors.js';
20
+ import { exportStaticSite } from './site-export.js';
21
+ import { approveEvaluationBaseline, evaluateWorkspace, formatEvaluation } from './evaluation.js';
12
22
  import { addGenerator, diagnoseGenerator, formatGeneratorInfo, formatGeneratorList, removeGenerator, } from './generator-manager.js';
13
23
  import { GENERATOR_CATALOG, generatorCatalogEntry, parseGenerator, resolveGeneratorPackage, } from './generators.js';
14
24
  import { formatInitPlan, promptForRequest, replayInitCommand, runCreateRescueWizard, runInitWizard, selectAgentInteractive, } from './interactive.js';
15
- import { addDesignReferences, assertNewProjectDirectory, findProjectRoot, isSpecUrl, loadProject, parseDesignReference, parseSource, parseSpec, resolveSeparateProjectLayout, saveDefaultAgent, scaffoldProject, validateProjectSourceBoundaries, } from './project.js';
25
+ import { addDesignReferences, assertNewProjectDirectory, findProjectRoot, isSpecUrl, isUnmaterializedDocsSite, parseDocsSite, loadProject, parseDesignReference, parseSource, parseSpec, projectDefaultModel, resolveSeparateProjectLayout, saveDefaultAgent, scaffoldProject, validateProjectSourceBoundaries, } from './project.js';
26
+ import { crawlDocumentationSite } from './docs-crawl.js';
27
+ import { describeDocsSite, docsSiteBinding, materializeDocsSiteSnapshot } from './docs-site.js';
28
+ import { importExistingDocumentation } from './project-import.js';
16
29
  import { isInteractive, promptConfirm } from './prompts.js';
30
+ import { listPages as listDocumentationPages } from './pages.js';
17
31
  import { startPreview } from './preview.js';
32
+ import { startUiServer } from './ui-server.js';
33
+ import { createSyncRun, formatSyncRunHistory, listSyncRuns, readSyncRun, recoverSyncRun, resumeSyncRun, reviseSyncRun } from './sync-runs.js';
18
34
  import { effectiveDeployment, formatProjectSettings, runSettingsWizard, } from './settings.js';
19
35
  import { collectSourceChanges, formatSourceChanges } from './sync.js';
36
+ import { buildSourceIntelligence, formatSourceIntelligence } from './source-intelligence.js';
37
+ import { formatQualityReport, runQuality } from './quality-gates.js';
20
38
  import { formatValidation, validateProject } from './validation.js';
21
39
  import { VERSION } from './version.js';
22
40
  async function main() {
@@ -38,6 +56,17 @@ async function main() {
38
56
  return agentCommand(args, cwd);
39
57
  case 'generator':
40
58
  return generatorCommand(args, cwd);
59
+ case 'audit': {
60
+ const root = await findProjectRoot(cwd);
61
+ if (booleanFlag(args, 'backfill-evidence'))
62
+ await backfillEvidence(root);
63
+ const result = await auditDocumentation(root);
64
+ if (flag(args, 'format') === 'json')
65
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
66
+ else
67
+ process.stdout.write(`${result.generator}: ${result.pages.length} pages, ${result.drift.pages.length} stale, ${result.unverified.length} unverified, ${result.unmapped.length} unmapped.\n${result.message}\n`);
68
+ return 0;
69
+ }
41
70
  case 'doctor': {
42
71
  const source = flag(args, 'source');
43
72
  const output = flag(args, 'output');
@@ -51,10 +80,37 @@ async function main() {
51
80
  process.stdout.write(`${formatDoctorReport(report)}\n`);
52
81
  return report.ready ? 0 : 1;
53
82
  }
83
+ case 'demo': {
84
+ const demo = await createDemoWorkspace();
85
+ const keep = booleanFlag(args, 'keep');
86
+ process.stdout.write(`Doxloop demo is ready.\n\n Workspace: ${demo.root}\n Plan: generated (${demo.plan.pages.length} pages)\n Evidence: ${demo.validation.pages.length} verified pages\n Validation: ${demo.validation.errors} errors, ${demo.validation.warnings} warnings\n Review: ${demo.review.score}/100 (${demo.review.hardGates})\n\n`);
87
+ if (booleanFlag(args, 'no-preview')) {
88
+ process.stdout.write(`${keep ? `The demo was kept at ${demo.root}.` : 'The isolated demo has been cleaned up.'}\n`);
89
+ if (!keep)
90
+ await demo.cleanup();
91
+ return 0;
92
+ }
93
+ if (!keep) {
94
+ const cleanup = () => void demo.cleanup();
95
+ process.once('SIGINT', cleanup);
96
+ process.once('SIGTERM', cleanup);
97
+ }
98
+ process.stdout.write('Opening the finished documentation preview. Press Ctrl+C to stop and clean up.\n');
99
+ try {
100
+ await startPreview({ root: demo.root, host: '127.0.0.1', port: numberFlag(args, 'port', 4321), open: !booleanFlag(args, 'no-open') });
101
+ }
102
+ catch (error) {
103
+ if (!keep)
104
+ await demo.cleanup();
105
+ throw error;
106
+ }
107
+ return 0;
108
+ }
54
109
  case 'create':
55
110
  if (flag(args, 'source') !== undefined ||
56
111
  flag(args, 'output') !== undefined ||
57
- flags(args, 'spec').length > 0) {
112
+ flags(args, 'spec').length > 0 ||
113
+ flags(args, 'docs').length > 0) {
58
114
  return createProjectCommand(args, cwd);
59
115
  }
60
116
  return authorCommand(args, cwd, 'create');
@@ -63,6 +119,14 @@ async function main() {
63
119
  case 'review': {
64
120
  return authorCommand(args, cwd, 'review');
65
121
  }
122
+ case 'plan':
123
+ return documentationPlanCommand(args, cwd);
124
+ case 'proposal':
125
+ return proposalCommand(args, cwd);
126
+ case 'pages':
127
+ return pagesCommand(args, cwd);
128
+ case 'replay':
129
+ return replayCommand(args, cwd);
66
130
  case 'capture': {
67
131
  const root = await findProjectRoot(cwd);
68
132
  await capture({ root, urls: args.positionals });
@@ -77,8 +141,62 @@ async function main() {
77
141
  : `${formatValidation(result)}\n`);
78
142
  return result.errors > 0 ? 1 : 0;
79
143
  }
144
+ case 'check':
145
+ return checkCommand(cwd, outputFormat(flag(args, 'format')), booleanFlag(args, 'quiet'));
146
+ case 'coverage': {
147
+ const root = await findProjectRoot(cwd);
148
+ const report = await buildSourceIntelligence(root);
149
+ process.stdout.write(outputFormat(flag(args, 'format')) === 'json' ? `${JSON.stringify(report, null, 2)}\n` : `${formatSourceIntelligence(report)}\n`);
150
+ return report.evidenceDiagnostics.some((item) => item.severity === 'error') || report.health.some((item) => item.status === 'error') ? 1 : 0;
151
+ }
152
+ case 'quality': {
153
+ const root = await findProjectRoot(cwd);
154
+ const report = await runQuality(root, {
155
+ ...(args.flags.has('offline') ? { offline: booleanFlag(args, 'offline') } : {}),
156
+ ...(args.flags.has('rendered') ? { rendered: booleanFlag(args, 'rendered') } : {}),
157
+ ...(args.flags.has('examples') ? { examples: booleanFlag(args, 'examples') } : {}),
158
+ fix: booleanFlag(args, 'fix'),
159
+ updateVisuals: booleanFlag(args, 'update-visuals'),
160
+ approveBaseline: booleanFlag(args, 'approve-quality-baseline'),
161
+ });
162
+ process.stdout.write(outputFormat(flag(args, 'format')) === 'json' ? `${JSON.stringify(report, null, 2)}\n` : `${formatQualityReport(report)}\n`);
163
+ return report.status === 'fail' || (booleanFlag(args, 'warnings-as-errors') && report.status === 'warning') ? 1 : 0;
164
+ }
165
+ case 'evaluate': {
166
+ const root = await findProjectRoot(cwd);
167
+ const mode = flag(args, 'mode') ?? 'generation';
168
+ if (mode !== 'generation' && mode !== 'update')
169
+ throw new UsageError('--mode must be generation or update');
170
+ const report = await evaluateWorkspace(root, {
171
+ mode,
172
+ ...(flag(args, 'before') ? { before: resolve(cwd, flag(args, 'before')) } : {}),
173
+ expectedChangedPages: flags(args, 'expected-change'),
174
+ ...(flag(args, 'max-pages') ? { maximumPages: numberFlag(args, 'max-pages', 1) } : {}),
175
+ ...(flag(args, 'regression-threshold') ? { regressionThreshold: numberFlag(args, 'regression-threshold', 3) } : {}),
176
+ });
177
+ if (booleanFlag(args, 'approve-baseline'))
178
+ await approveEvaluationBaseline(root, report);
179
+ process.stdout.write(outputFormat(flag(args, 'format')) === 'json' ? `${JSON.stringify(report, null, 2)}\n` : `${formatEvaluation(report)}\n`);
180
+ return report.regression?.blocked ? 1 : 0;
181
+ }
182
+ case 'sync':
183
+ return syncCommand(args, cwd);
184
+ case 'ui': {
185
+ const page = flag(args, 'page');
186
+ const project = flag(args, 'project');
187
+ await startUiServer({
188
+ cwd,
189
+ port: numberFlag(args, 'port', 4317),
190
+ open: !booleanFlag(args, 'no-open'),
191
+ ...(page ? { page } : {}),
192
+ ...(project ? { project } : {}),
193
+ });
194
+ return 0;
195
+ }
80
196
  case 'status':
81
197
  return statusCommand(cwd, outputFormat(flag(args, 'format')));
198
+ case 'history':
199
+ return historyCommand(args, cwd);
82
200
  case 'settings': {
83
201
  const root = await findProjectRoot(cwd);
84
202
  if (!isInteractive(args)) {
@@ -120,9 +238,19 @@ async function main() {
120
238
  const project = await loadProject(root);
121
239
  const userConfig = await loadUserConfig();
122
240
  const savedDeployment = effectiveDeployment(project, userConfig.apiUrl);
241
+ const rawTarget = flag(args, 'target') ?? savedDeployment.target;
242
+ if (!['doxbrix', 'github-pages', 'netlify', 'vercel'].includes(rawTarget))
243
+ throw new UsageError('--target must be doxbrix, github-pages, netlify, or vercel.');
244
+ const target = rawTarget;
123
245
  const name = flag(args, 'name') ?? savedDeployment.name;
124
246
  const slug = flag(args, 'slug') ?? savedDeployment.slug;
125
- const apiOverride = flag(args, 'api-url') ?? savedDeployment.apiUrl;
247
+ const defaultTargetApi = target === 'netlify' ? 'https://api.netlify.com' : target === 'vercel' ? 'https://api.vercel.com' : savedDeployment.apiUrl;
248
+ const apiOverride = flag(args, 'api-url') ?? (target === savedDeployment.target ? savedDeployment.apiUrl : defaultTargetApi);
249
+ const siteId = flag(args, 'site-id') ?? savedDeployment.siteId;
250
+ const projectId = flag(args, 'project-id') ?? savedDeployment.projectId;
251
+ const teamId = flag(args, 'team-id') ?? savedDeployment.teamId;
252
+ const branch = flag(args, 'branch') ?? savedDeployment.branch;
253
+ const basePath = flag(args, 'base-path') ?? savedDeployment.basePath;
126
254
  const dryRun = booleanFlag(args, 'dry-run');
127
255
  const publicSite = flag(args, 'public') !== undefined
128
256
  ? booleanFlag(args, 'public')
@@ -132,20 +260,20 @@ async function main() {
132
260
  if (validation.errors > 0) {
133
261
  throw new DoxloopError(`Deployment stopped because documentation has ${validation.errors} validation error${validation.errors === 1 ? '' : 's'}. Run \`doxloop test\`.`);
134
262
  }
135
- process.stdout.write(`\nDeployment summary\n\n Project: ${name}\n Slug: ${slug}\n Destination: ${apiOverride}\n Visibility: ${publicSite ? 'PUBLIC' : 'Private'}\n Pages: ${validation.pages.length}\n Warnings: ${validation.warnings}\n Product files: 0\n\n`);
136
- if (publicSite) {
263
+ process.stdout.write(`\nDeployment summary\n\n Project: ${name}\n Slug: ${slug}\n Target: ${target}\n Destination: ${apiOverride}\n Visibility: ${publicSite ? 'PUBLIC' : 'Private'}\n Pages: ${validation.pages.length}\n Warnings: ${validation.warnings}\n Product files: 0\n\n`);
264
+ if (target === 'doxbrix' && publicSite) {
137
265
  process.stdout.write('Anyone on the internet will be able to access this documentation.\n\n');
138
266
  }
139
267
  const proceed = await promptConfirm({
140
- message: publicSite ? 'Deploy publicly?' : 'Deploy now?',
141
- initial: !publicSite,
268
+ message: target === 'doxbrix' && publicSite ? 'Deploy publicly?' : 'Deploy now?',
269
+ initial: target !== 'doxbrix' || !publicSite,
142
270
  });
143
271
  if (!proceed) {
144
272
  process.stdout.write('Deployment canceled. No data was uploaded.\n');
145
273
  return 0;
146
274
  }
147
- const token = userConfig.token ?? process.env.DOXLOOP_TOKEN ?? process.env.DOXBRIX_TOKEN;
148
- if (!token) {
275
+ const token = target === 'doxbrix' ? tokenFor(userConfig, apiOverride) : undefined;
276
+ if (target === 'doxbrix' && !token) {
149
277
  process.stdout.write('You are not signed in to Doxbrix.\n');
150
278
  const signIn = await promptConfirm({
151
279
  message: 'Sign in now?',
@@ -160,12 +288,35 @@ async function main() {
160
288
  }
161
289
  await deploy({
162
290
  root,
291
+ target,
163
292
  name,
164
293
  slug,
165
294
  dryRun,
166
295
  public: publicSite,
167
296
  apiUrl: apiOverride,
297
+ ...(siteId ? { siteId } : {}),
298
+ ...(projectId ? { projectId } : {}),
299
+ ...(teamId ? { teamId } : {}),
300
+ ...(branch ? { branch } : {}),
301
+ ...(basePath ? { basePath } : {}),
302
+ });
303
+ return 0;
304
+ }
305
+ case 'export': {
306
+ const root = await findProjectRoot(cwd);
307
+ const rawOut = flag(args, 'out');
308
+ if (!rawOut)
309
+ throw new UsageError('doxloop export requires --out <directory>.');
310
+ const basePath = flag(args, 'base-path');
311
+ const siteUrl = flag(args, 'site-url');
312
+ const result = await exportStaticSite({
313
+ root,
314
+ out: resolve(cwd, rawOut),
315
+ zip: booleanFlag(args, 'zip'),
316
+ ...(basePath ? { basePath } : {}),
317
+ ...(siteUrl ? { siteUrl } : {}),
168
318
  });
319
+ process.stdout.write(`Exported ${result.files} files to ${result.outputDir}\n${result.zipPath ? `Archive: ${result.zipPath}\n` : ''}SHA-256: ${result.sha256}\n`);
169
320
  return 0;
170
321
  }
171
322
  default:
@@ -173,10 +324,13 @@ async function main() {
173
324
  }
174
325
  }
175
326
  async function initCommand(args, cwd) {
327
+ if (booleanFlag(args, 'existing'))
328
+ return importExistingCommand(args, cwd);
176
329
  const providedDirectory = args.positionals[0];
177
330
  let sources = [
178
331
  ...flags(args, 'source').map(parseSource),
179
332
  ...flags(args, 'spec').map(parseSpec),
333
+ ...flags(args, 'docs').map(parseDocsSite),
180
334
  ];
181
335
  let title = flag(args, 'title');
182
336
  let generator = parseGenerator(flag(args, 'generator'));
@@ -216,20 +370,52 @@ async function initCommand(args, cwd) {
216
370
  process.stdout.write(`\nNext:\n cd ${directory}\n doxloop create\n doxloop preview --open\n doxloop test\n`);
217
371
  return 0;
218
372
  }
373
+ /**
374
+ * Adopt a folder that already holds a documentation site. Nothing in the
375
+ * folder is converted or rewritten; Doxloop only adds its own project files.
376
+ */
377
+ async function importExistingCommand(args, cwd) {
378
+ if (args.positionals.length > 1)
379
+ throw new UsageError('The init command accepts one documentation directory.');
380
+ if (flags(args, 'source').length > 0 || flags(args, 'spec').length > 0 || flags(args, 'docs').length > 0 || flags(args, 'reference').length > 0) {
381
+ throw new UsageError('`doxloop init --existing` adopts the folder as it is. Connect sources afterwards from the control center.');
382
+ }
383
+ const generator = parseGenerator(flag(args, 'generator'));
384
+ const contentDir = flag(args, 'content-dir');
385
+ const title = flag(args, 'title');
386
+ const result = await importExistingDocumentation({
387
+ directory: resolve(cwd, args.positionals[0] ?? '.'),
388
+ ...(generator ? { generator } : {}),
389
+ ...(contentDir !== undefined ? { contentDir } : {}),
390
+ ...(title ? { title } : {}),
391
+ });
392
+ const entry = generatorCatalogEntry(result.generator);
393
+ process.stdout.write(`Imported existing documentation at ${result.root}\n` +
394
+ ` Generator: ${entry?.displayName ?? result.generator}\n` +
395
+ ` Content directory: ${result.contentDir || '.'}\n` +
396
+ ` Pages: ${result.pageCount}\n`);
397
+ for (const install of result.skills)
398
+ process.stdout.write(`${install.action}: ${install.path}\n`);
399
+ for (const warning of result.warnings)
400
+ process.stdout.write(`Warning: ${warning}\n`);
401
+ process.stdout.write(`\nNo page was changed. Open the control center with:\n doxloop ui --project ${result.root}\n`);
402
+ return 0;
403
+ }
219
404
  async function confirmInitPlan(cwd, plan) {
220
405
  process.stdout.write(`\n${formatInitPlan(cwd, plan)}\n\n`);
221
406
  return promptConfirm({ message: 'Create this project?', initial: true });
222
407
  }
223
408
  async function initializeProject(args, cwd, plan, designReferences = []) {
224
409
  const projectRoot = resolve(cwd, plan.directory);
225
- await validateProjectSourceBoundaries(projectRoot, plan.sources);
410
+ const sources = await materializeDocsSiteSources(projectRoot, plan.sources);
411
+ await validateProjectSourceBoundaries(projectRoot, sources);
226
412
  if (plan.generator !== 'doxbrix') {
227
413
  await ensureGeneratorAvailable(args, projectRoot, plan.generator);
228
414
  }
229
415
  const root = await scaffoldProject({
230
416
  directory: projectRoot,
231
417
  ...(plan.title ? { title: plan.title } : {}),
232
- sources: plan.sources,
418
+ sources,
233
419
  designReferences,
234
420
  generator: plan.generator,
235
421
  });
@@ -240,6 +426,36 @@ async function initializeProject(args, cwd, plan, designReferences = []) {
240
426
  }
241
427
  return root;
242
428
  }
429
+ /**
430
+ * Crawl every `--docs` site into its read-only snapshot before the project is
431
+ * validated, so the binding stored in project.json points at the snapshot
432
+ * folder rather than the URL.
433
+ */
434
+ async function materializeDocsSiteSources(projectRoot, sources) {
435
+ const output = [];
436
+ for (const source of sources) {
437
+ if (!isUnmaterializedDocsSite(source)) {
438
+ output.push(source);
439
+ continue;
440
+ }
441
+ process.stdout.write(`Crawling existing documentation at ${source.path}…\n`);
442
+ let lastReport = 0;
443
+ const snapshot = await crawlDocumentationSite(source.path, {
444
+ onProgress: ({ fetched, discovered }) => {
445
+ if (fetched - lastReport >= 10) {
446
+ lastReport = fetched;
447
+ process.stdout.write(` ${fetched} pages read, ${discovered} discovered\n`);
448
+ }
449
+ },
450
+ });
451
+ const materialized = await materializeDocsSiteSnapshot(projectRoot, source.name, snapshot);
452
+ for (const warning of snapshot.warnings)
453
+ process.stdout.write(` Warning: ${warning}\n`);
454
+ process.stdout.write(` ${describeDocsSite(materialized.site)} → ${materialized.path}\n`);
455
+ output.push(docsSiteBinding(projectRoot, source.name, materialized, source.scope));
456
+ }
457
+ return output;
458
+ }
243
459
  async function ensureGeneratorAvailable(args, projectRoot, generator) {
244
460
  const entry = generatorCatalogEntry(generator);
245
461
  if (!entry?.packageName)
@@ -262,8 +478,9 @@ async function createProjectCommand(args, cwd) {
262
478
  const source = flag(args, 'source');
263
479
  const output = flag(args, 'output');
264
480
  const specs = flags(args, 'spec').map(parseSpec);
265
- if (!output || (!source && specs.length === 0)) {
266
- throw new UsageError('Creating a new documentation project requires `--output <documentation-directory>` plus `--source <product-directory>`, `--spec <openapi-file-or-url>`, or both.');
481
+ const docsSites = flags(args, 'docs').map(parseDocsSite);
482
+ if (!output || (!source && specs.length === 0 && docsSites.length === 0)) {
483
+ throw new UsageError('Creating a new documentation project requires `--output <documentation-directory>` plus `--source <product-directory>`, `--spec <openapi-file-or-url>`, `--docs <existing-documentation-url>`, or a combination.');
267
484
  }
268
485
  if (flags(args, 'source').length > 1 || flags(args, 'output').length > 1) {
269
486
  throw new UsageError('The first-run create command accepts one source and one output directory.');
@@ -272,20 +489,24 @@ async function createProjectCommand(args, cwd) {
272
489
  ? await resolveSeparateProjectLayout({ cwd, source, output })
273
490
  : undefined;
274
491
  const projectRoot = layout?.projectRoot ?? resolve(cwd, output);
275
- const sources = [
276
- ...(layout ? [layout.sourceBinding] : []),
277
- ...specs.map((spec) => projectRelativeSpec(spec, cwd, projectRoot)),
278
- ];
279
492
  const print = booleanFlag(args, 'print');
280
493
  const designReferences = flags(args, 'reference').map(parseDesignReference);
281
494
  if (!layout)
282
495
  await assertNewProjectDirectory(projectRoot);
496
+ const sources = await materializeDocsSiteSources(projectRoot, [
497
+ ...(layout ? [layout.sourceBinding] : []),
498
+ ...specs.map((spec) => projectRelativeSpec(spec, cwd, projectRoot)),
499
+ ...docsSites,
500
+ ]);
283
501
  await validateProjectSourceBoundaries(projectRoot, sources);
284
- const sourceText = layout
502
+ const docsText = docsSites.length > 0
503
+ ? `Existing documentation:\n${docsSites.map((site) => ` ${site.path}`).join('\n')}\n Crawled into a read-only snapshot and rewritten; the site itself is never changed.\n\n`
504
+ : '';
505
+ const sourceText = (layout
285
506
  ? `Product source:\n ${layout.sourceRoot}\n Read-only — product files will not be changed or deployed.\n\n`
286
507
  : specs.length > 0
287
508
  ? `API specification${specs.length === 1 ? '' : 's'}:\n${specs.map((spec) => ` ${spec.path}`).join('\n')}\n Read-only API evidence.\n\n`
288
- : '';
509
+ : '') + docsText;
289
510
  process.stdout.write(`Welcome to Doxloop\n\n${sourceText}Documentation project:\n ${projectRoot}\n Only this project can be previewed or deployed.\n\n`);
290
511
  const root = await scaffoldProject({
291
512
  directory: projectRoot,
@@ -408,16 +629,19 @@ async function authorCommand(args, cwd, mode) {
408
629
  const designReferences = flags(args, 'reference').map(parseDesignReference);
409
630
  if (mode !== 'review')
410
631
  await addDesignReferences(root, designReferences);
411
- const model = flag(args, 'model');
632
+ const model = flag(args, 'model') ?? projectDefaultModel(project, selectedAgent);
412
633
  const reasoning = parseReasoning(flag(args, 'reasoning'));
634
+ const effort = parseClaudeEffort(flag(args, 'effort'));
413
635
  const screenshots = screenshotIntent(args);
414
636
  const result = await runAuthor({
415
637
  root,
416
638
  mode,
639
+ nonInteractive: !interactive,
417
640
  ...(changeSummary !== undefined ? { changeSummary } : {}),
418
641
  ...(selectedAgent ? { agent: selectedAgent } : {}),
419
642
  ...(model ? { model } : {}),
420
643
  ...(reasoning ? { reasoning } : {}),
644
+ ...(effort ? { effort } : {}),
421
645
  screenshots,
422
646
  print: booleanFlag(args, 'print'),
423
647
  ...(request || (mode === 'review' && designReferences.length > 0)
@@ -439,6 +663,149 @@ async function authorCommand(args, cwd, mode) {
439
663
  }
440
664
  return result;
441
665
  }
666
+ /** Internal UI workflow command. The UI owns plan state and approval. */
667
+ async function documentationPlanCommand(args, cwd) {
668
+ const action = args.positionals[0];
669
+ const id = flag(args, 'id');
670
+ if (!id || !action || !['propose', 'revise', 'generate', 'continue'].includes(action)) {
671
+ throw new UsageError('Usage: doxloop plan <propose|revise|generate|continue> --id <plan-id> [--strategy resume|ignore-errors]');
672
+ }
673
+ if (args.positionals.length > 1) {
674
+ throw new UsageError('The plan command accepts one action.');
675
+ }
676
+ const strategy = flag(args, 'strategy');
677
+ if (action === 'continue' && strategy !== 'resume' && strategy !== 'ignore-errors') {
678
+ throw new UsageError('doxloop plan continue needs --strategy resume or --strategy ignore-errors.');
679
+ }
680
+ const root = await findProjectRoot(cwd);
681
+ const plan = action === 'propose'
682
+ ? await proposeDocumentationPlan(root, id)
683
+ : action === 'revise'
684
+ ? await reviseDocumentationPlan(root, id, flag(args, 'feedback') ?? '')
685
+ : action === 'continue'
686
+ ? await continueDocumentationPlanGeneration(root, id, strategy)
687
+ : await generateApprovedDocumentationPlan(root, id);
688
+ process.stdout.write(`\nDocumentation plan ${plan.id} is ${plan.status}.\n`);
689
+ return 0;
690
+ }
691
+ /** Internal UI workflow command. Proposal mutations remain mediated by the local server. */
692
+ async function proposalCommand(args, cwd) {
693
+ const action = args.positionals[0];
694
+ const id = flag(args, 'id');
695
+ if (!id || (action !== 'revise' && action !== 'recover' && action !== 'resume')) {
696
+ throw new UsageError('Usage: doxloop proposal <revise|recover|resume> --id <run-id> [--change <change-id> --request <instruction>] [--ignore-screenshot-problems]');
697
+ }
698
+ if (action === 'recover') {
699
+ const run = await recoverSyncRun(await findProjectRoot(cwd), id, {
700
+ ignoreScreenshotProblems: booleanFlag(args, 'ignore-screenshot-problems'),
701
+ });
702
+ process.stdout.write(`\nDocumentation proposal ${run.id} is ${run.status}.\n`);
703
+ return 0;
704
+ }
705
+ if (action === 'resume') {
706
+ const root = await findProjectRoot(cwd);
707
+ const failed = await readSyncRun(root, id);
708
+ // A run started from an approved plan can rebuild its instructions from
709
+ // that plan when it predates the recorded authoring inputs.
710
+ const fallbackAuthoring = failed.planId
711
+ ? await planAuthoringRecord(root, await readDocumentationPlan(root, failed.planId)).catch(() => undefined)
712
+ : undefined;
713
+ const run = await resumeSyncRun(root, id, fallbackAuthoring ? { fallbackAuthoring } : {});
714
+ if (run.status === 'failed')
715
+ throw new DoxloopError(run.error ?? 'The resumed proposal failed.');
716
+ process.stdout.write(`\nDocumentation proposal ${run.id} is ${run.status}.\n`);
717
+ return 0;
718
+ }
719
+ const changes = flags(args, 'change');
720
+ const request = flag(args, 'request') ?? '';
721
+ const root = await findProjectRoot(cwd);
722
+ const run = await reviseSyncRun(root, id, {
723
+ instruction: request,
724
+ changeIds: changes,
725
+ hunkIds: flags(args, 'hunk'),
726
+ });
727
+ if (run.status === 'failed')
728
+ throw new DoxloopError(run.error ?? 'The proposal revision failed.');
729
+ process.stdout.write(`\nDocumentation proposal ${run.id} is ${run.status}.\n`);
730
+ return 0;
731
+ }
732
+ async function replayCommand(args, cwd) {
733
+ const target = args.positionals[0];
734
+ if (!target || args.positionals.length > 1)
735
+ throw new UsageError('Usage: doxloop replay <run-directory> [--keep] [--format text|json]');
736
+ const result = await replayRun(resolve(cwd, target), {
737
+ keep: booleanFlag(args, 'keep'),
738
+ log: (line) => { if (outputFormat(flag(args, 'format')) !== 'json')
739
+ process.stdout.write(`${line}\n`); },
740
+ });
741
+ if (outputFormat(flag(args, 'format')) === 'json')
742
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
743
+ else if (booleanFlag(args, 'keep'))
744
+ process.stdout.write(`Working copy kept at ${result.workspace}\n`);
745
+ return result.after.errors > 0 ? 1 : 0;
746
+ }
747
+ async function pagesCommand(args, cwd) {
748
+ const action = args.positionals[0];
749
+ if (action !== 'list' && action !== 'edit') {
750
+ throw new UsageError('Usage: doxloop pages <list|edit> [--path <page> --request <instruction>]');
751
+ }
752
+ if (args.positionals.length > 1)
753
+ throw new UsageError(`The pages ${action} command does not accept positional arguments.`);
754
+ const root = await findProjectRoot(cwd);
755
+ if (action === 'list') {
756
+ const pages = await listDocumentationPages(root);
757
+ if (outputFormat(flag(args, 'format')) === 'json') {
758
+ process.stdout.write(`${JSON.stringify(pages, null, 2)}\n`);
759
+ }
760
+ else if (pages.length === 0) {
761
+ process.stdout.write('No documentation pages found.\n');
762
+ }
763
+ else {
764
+ process.stdout.write(`${formatPageList(pages)}\n`);
765
+ }
766
+ return 0;
767
+ }
768
+ const paths = [...new Set(flags(args, 'path').map((path) => path.trim()).filter(Boolean))];
769
+ const request = flag(args, 'request')?.trim();
770
+ if (paths.length === 0)
771
+ throw new UsageError('pages edit requires at least one --path <page>.');
772
+ if (!request)
773
+ throw new UsageError('pages edit requires --request <instruction>.');
774
+ if (request.length < 8)
775
+ throw new UsageError('pages edit requires an instruction of at least 8 characters.');
776
+ if (paths.length > 10)
777
+ throw new UsageError('pages edit accepts at most 10 --path values.');
778
+ const project = await loadProject(root);
779
+ const selectedAgent = parseAgent(flag(args, 'agent'));
780
+ const run = await createSyncRun({
781
+ ...(flag(args, 'run-id') ? { id: flag(args, 'run-id') } : {}),
782
+ root,
783
+ project,
784
+ drift: await computeConfiguredDrift(root, project),
785
+ sourceChanges: await collectSourceChanges(root, project.sources),
786
+ trigger: 'edit',
787
+ editRequest: { instruction: request, paths, allowRelated: booleanFlag(args, 'allow-related'), followUps: [] },
788
+ authoring: {
789
+ mode: 'update',
790
+ historyRequest: request,
791
+ ...(selectedAgent ? { agent: selectedAgent } : {}),
792
+ ...(flag(args, 'model') ? { model: flag(args, 'model') } : {}),
793
+ ...(flag(args, 'reasoning') ? { reasoning: parseReasoning(flag(args, 'reasoning')) } : {}),
794
+ ...(flag(args, 'effort') ? { effort: parseClaudeEffort(flag(args, 'effort')) } : {}),
795
+ screenshots: booleanFlag(args, 'screenshots') ? 'enabled' : 'disabled',
796
+ },
797
+ });
798
+ process.stdout.write(`Documentation edit ${run.id} is ${run.status}.\n`);
799
+ if (run.status === 'failed')
800
+ throw new DoxloopError(run.error ?? 'The documentation edit failed.');
801
+ return 0;
802
+ }
803
+ function formatPageList(pages) {
804
+ const headings = ['Path', 'Title', 'Section', 'Words', 'Evidence'];
805
+ const rows = pages.map((page) => [page.path, page.title, page.section ?? 'Not in navigation', String(page.wordCount), page.evidence]);
806
+ const widths = headings.map((heading, index) => Math.max(heading.length, ...rows.map((row) => row[index].length)));
807
+ return [headings, ...rows].map((row) => row.map((cell, index) => cell.padEnd(widths[index])).join(' ').trimEnd()).join('\n');
808
+ }
442
809
  async function agentCommand(args, cwd) {
443
810
  const action = args.positionals[0];
444
811
  if (!action || !['setup', 'status', 'update'].includes(action)) {
@@ -492,6 +859,104 @@ async function generatorCommand(args, cwd) {
492
859
  process.stdout.write(`${await formatGeneratorInfo(cwd, generator)}\n`);
493
860
  return 0;
494
861
  }
862
+ async function syncCommand(args, cwd) {
863
+ const action = args.positionals[0] ?? 'status';
864
+ if (!['setup', 'status', 'now', 'review', 'history', 'off'].includes(action)) {
865
+ throw new UsageError('Usage: doxloop sync <setup|status|now|review|history|off>');
866
+ }
867
+ if (args.positionals.length > 1) {
868
+ throw new UsageError('The sync command accepts one action.');
869
+ }
870
+ const root = await findProjectRoot(cwd);
871
+ const project = await loadProject(root);
872
+ if (action === 'history') {
873
+ process.stdout.write(`${formatSyncRunHistory(await listSyncRuns(root))}\n`);
874
+ return 0;
875
+ }
876
+ if (action === 'review') {
877
+ await startUiServer({
878
+ cwd: root,
879
+ host: flag(args, 'host') ?? '127.0.0.1',
880
+ port: numberFlag(args, 'port', 4317),
881
+ open: booleanFlag(args, 'open'),
882
+ page: 'proposals',
883
+ });
884
+ return 0;
885
+ }
886
+ if (action === 'status') {
887
+ process.stdout.write(`${await formatSyncStatus(root, project)}\n`);
888
+ return 0;
889
+ }
890
+ if (action === 'now') {
891
+ const trigger = flag(args, 'trigger');
892
+ const request = flag(args, 'request');
893
+ const agent = parseAgent(flag(args, 'agent'));
894
+ const model = flag(args, 'model') ?? projectDefaultModel(project, agent ?? project.defaultAgent);
895
+ const reasoning = parseReasoning(flag(args, 'reasoning'));
896
+ const effort = parseClaudeEffort(flag(args, 'effort'));
897
+ const hasManualAuthoring = Boolean(request || agent || model || reasoning || effort || booleanFlag(args, 'screenshots') || booleanFlag(args, 'no-screenshots'));
898
+ return runSyncNow({
899
+ root,
900
+ project,
901
+ quiet: booleanFlag(args, 'quiet'),
902
+ ...(trigger ? { trigger: parseSyncRunTrigger(trigger) } : {}),
903
+ ...(hasManualAuthoring ? {
904
+ authoring: {
905
+ ...(request ? { request } : {}),
906
+ ...(agent ? { agent } : {}),
907
+ ...(model ? { model } : {}),
908
+ ...(reasoning ? { reasoning } : {}),
909
+ ...(effort ? { effort } : {}),
910
+ screenshots: screenshotIntent(args),
911
+ },
912
+ } : {}),
913
+ });
914
+ }
915
+ if (action === 'off') {
916
+ const lines = await disableSync(root, project);
917
+ process.stdout.write(`\n${lines.join('\n')}\n\nAutomatic sync: OFF\n`);
918
+ return 0;
919
+ }
920
+ const modeFlag = flag(args, 'mode');
921
+ const onFlag = flag(args, 'on');
922
+ const branchFlag = flag(args, 'branch');
923
+ let sync;
924
+ if (isInteractive(args) && modeFlag === undefined && onFlag === undefined) {
925
+ sync = await runSyncSetupWizard({ root, project, io: promptIo() });
926
+ if (!sync) {
927
+ process.stdout.write('Setup canceled. Nothing was changed.\n');
928
+ return 0;
929
+ }
930
+ }
931
+ else {
932
+ if (modeFlag === undefined && onFlag === undefined) {
933
+ throw new UsageError('Guided setup requires an interactive terminal. For automation, use `doxloop sync setup --mode <check|propose|auto> --on <every@Nm|every@Nh|daily@HH:MM|manual>`.');
934
+ }
935
+ sync = {
936
+ ...project.sync,
937
+ ...(modeFlag ? { mode: parseSyncMode(modeFlag) } : {}),
938
+ ...(branchFlag ? { branch: branchFlag } : {}),
939
+ ...(onFlag ? { on: parseTriggerList(onFlag) } : {}),
940
+ };
941
+ }
942
+ const applied = await applySyncConfig(root, project, sync);
943
+ process.stdout.write(`\n${applied.join('\n')}\n`);
944
+ process.stdout.write(`\nRerun this setup non-interactively:\n ${replaySyncSetupCommand(sync)}\n`);
945
+ process.stdout.write('\nNext:\n doxloop sync status confirm everything is working\n doxloop check see the current drift\n doxloop sync review --open review generated proposals\n');
946
+ return 0;
947
+ }
948
+ async function checkCommand(cwd, format, quiet) {
949
+ const root = await findProjectRoot(cwd);
950
+ const project = await loadProject(root);
951
+ const result = await computeConfiguredDrift(root, project);
952
+ // Quiet mode keeps scheduled checks silent unless there is something to act on.
953
+ if (!quiet || result.status !== 'current') {
954
+ process.stdout.write(format === 'json'
955
+ ? `${JSON.stringify(result, null, 2)}\n`
956
+ : `${formatDrift(result)}\n`);
957
+ }
958
+ return result.status === 'current' ? 0 : 1;
959
+ }
495
960
  async function statusCommand(cwd, format) {
496
961
  const root = await findProjectRoot(cwd);
497
962
  const project = await loadProject(root);
@@ -519,24 +984,95 @@ async function statusCommand(cwd, format) {
519
984
  }
520
985
  return result.errors > 0 ? 1 : 0;
521
986
  }
987
+ /**
988
+ * Show what the project has been asked to do and what happened. History is
989
+ * derived data; supported Doxloop runtimes include built-in SQLite.
990
+ */
991
+ async function historyCommand(args, cwd) {
992
+ const root = await findProjectRoot(cwd);
993
+ if (!(await historyAvailable())) {
994
+ process.stdout.write(process.env.DOXLOOP_NO_HISTORY === '1'
995
+ ? 'Documentation history is explicitly disabled by DOXLOOP_NO_HISTORY=1. Remove that variable to restore request and deployment history.\n'
996
+ : `Documentation history requires Node.js 22.13 or newer. This runtime is ${process.version}; upgrade before authoring so request and deployment history remain available.\n`);
997
+ return 0;
998
+ }
999
+ await backfillHistory(root);
1000
+ const limit = numberFlag(args, 'limit', 20);
1001
+ const page = flag(args, 'page');
1002
+ const format = outputFormat(flag(args, 'format'));
1003
+ if (page) {
1004
+ const entries = await pageHistory(root, page, limit);
1005
+ if (format === 'json') {
1006
+ process.stdout.write(`${JSON.stringify(entries, null, 2)}\n`);
1007
+ return 0;
1008
+ }
1009
+ if (entries.length === 0) {
1010
+ process.stdout.write(`No recorded history for ${page}.\n`);
1011
+ return 0;
1012
+ }
1013
+ process.stdout.write(`History for ${page}\n`);
1014
+ for (const entry of entries) {
1015
+ const when = entry.requestedAt.replace('T', ' ').slice(0, 16);
1016
+ process.stdout.write(` ${when} ${entry.changeKind.padEnd(8)} ${entry.decision.padEnd(9)} +${entry.linesAdded}/-${entry.linesRemoved}${entry.agent ? ` · ${entry.agent}` : ''}\n`);
1017
+ if (entry.requestText) {
1018
+ process.stdout.write(` "${entry.requestText.replace(/\s+/g, ' ').trim()}"\n`);
1019
+ }
1020
+ }
1021
+ return 0;
1022
+ }
1023
+ if (booleanFlag(args, 'deployments')) {
1024
+ const deployments = await listDeployments(root, limit);
1025
+ if (format === 'json') {
1026
+ process.stdout.write(`${JSON.stringify(deployments, null, 2)}\n`);
1027
+ return 0;
1028
+ }
1029
+ if (deployments.length === 0) {
1030
+ process.stdout.write('No deployments recorded yet.\n');
1031
+ return 0;
1032
+ }
1033
+ for (const record of deployments) {
1034
+ const when = record.startedAt.replace('T', ' ').slice(0, 16);
1035
+ process.stdout.write(` ${record.status === 'succeeded' ? '✓' : '✗'} ${when} ${record.slug ?? record.target} ${record.pagesCount ?? 0} pages${record.error ? ` ${record.error}` : ''}\n`);
1036
+ }
1037
+ return 0;
1038
+ }
1039
+ const requests = await listRequests(root, limit);
1040
+ process.stdout.write(format === 'json'
1041
+ ? `${JSON.stringify(requests, null, 2)}\n`
1042
+ : `${formatRequestHistory(requests)}\n`);
1043
+ return 0;
1044
+ }
522
1045
  function validateCommandArguments(args) {
523
1046
  const allowed = {
524
- init: ['title', 'source', 'spec', 'reference', 'generator'],
1047
+ init: ['title', 'source', 'spec', 'docs', 'reference', 'generator', 'existing', 'content-dir'],
525
1048
  agent: ['agent'],
526
1049
  generator: [],
527
1050
  doctor: ['source', 'output', 'agent'],
528
- create: ['agent', 'model', 'reasoning', 'reference', 'print', 'screenshots', 'no-screenshots', 'source', 'spec', 'output'],
529
- update: ['agent', 'model', 'reasoning', 'reference', 'print', 'screenshots', 'no-screenshots'],
530
- review: ['agent', 'model', 'reasoning', 'reference', 'print'],
1051
+ audit: ['format', 'backfill-evidence'],
1052
+ demo: ['port', 'no-open', 'no-preview', 'keep'],
1053
+ create: ['agent', 'model', 'reasoning', 'effort', 'reference', 'print', 'screenshots', 'no-screenshots', 'source', 'spec', 'docs', 'output'],
1054
+ update: ['agent', 'model', 'reasoning', 'effort', 'reference', 'print', 'screenshots', 'no-screenshots'],
1055
+ review: ['agent', 'model', 'reasoning', 'effort', 'reference', 'print'],
1056
+ plan: ['id', 'feedback', 'strategy'],
1057
+ proposal: ['id', 'change', 'hunk', 'request', 'ignore-screenshot-problems'],
1058
+ pages: ['format', 'path', 'request', 'allow-related', 'screenshots', 'run-id', 'agent', 'model', 'reasoning', 'effort'],
531
1059
  capture: [],
532
1060
  test: ['format'],
1061
+ check: ['format', 'quiet'],
1062
+ coverage: ['format'],
1063
+ quality: ['format', 'offline', 'rendered', 'examples', 'fix', 'update-visuals', 'warnings-as-errors', 'approve-quality-baseline'],
1064
+ evaluate: ['format', 'mode', 'before', 'expected-change', 'max-pages', 'regression-threshold', 'approve-baseline'],
1065
+ sync: ['mode', 'on', 'branch', 'quiet', 'trigger', 'host', 'port', 'open', 'request', 'agent', 'model', 'reasoning', 'effort', 'screenshots', 'no-screenshots'],
1066
+ ui: ['port', 'page', 'no-open', 'project'],
533
1067
  status: ['format'],
1068
+ history: ['format', 'limit', 'page', 'deployments'],
534
1069
  settings: [],
535
1070
  preview: ['host', 'port', 'open'],
536
1071
  login: ['api-url', 'token'],
537
1072
  logout: [],
538
1073
  whoami: ['api-url'],
539
- deploy: ['dry-run', 'public', 'name', 'slug', 'api-url'],
1074
+ deploy: ['dry-run', 'public', 'name', 'slug', 'api-url', 'target', 'site-id', 'project-id', 'team-id', 'branch', 'base-path'],
1075
+ export: ['out', 'zip', 'base-path', 'site-url'],
540
1076
  };
541
1077
  if (args.command === undefined) {
542
1078
  assertAllowedFlags(args, new Set());
@@ -546,7 +1082,19 @@ function validateCommandArguments(args) {
546
1082
  if (commandFlags === undefined)
547
1083
  return;
548
1084
  assertAllowedFlags(args, new Set(commandFlags));
549
- if (!['init', 'agent', 'generator', 'create', 'update', 'review', 'capture'].includes(args.command) &&
1085
+ if (![
1086
+ 'init',
1087
+ 'agent',
1088
+ 'generator',
1089
+ 'create',
1090
+ 'update',
1091
+ 'review',
1092
+ 'plan',
1093
+ 'proposal',
1094
+ 'pages',
1095
+ 'capture',
1096
+ 'sync',
1097
+ ].includes(args.command) &&
550
1098
  args.positionals.length > 0) {
551
1099
  throw new UsageError(`The ${args.command} command does not accept arguments.`);
552
1100
  }
@@ -606,19 +1154,48 @@ function outputFormat(value) {
606
1154
  return 'json';
607
1155
  throw new UsageError('--format must be text or json');
608
1156
  }
1157
+ function parseSyncRunTrigger(value) {
1158
+ if (value === 'manual' ||
1159
+ value === 'schedule') {
1160
+ return value;
1161
+ }
1162
+ throw new UsageError('--trigger is reserved for the Doxloop scheduler.');
1163
+ }
609
1164
  function help(command) {
1165
+ if (command === 'demo') {
1166
+ return `Usage: doxloop demo [options]
1167
+
1168
+ Create a complete bundled documentation project in an isolated temporary
1169
+ directory, then open its local preview. No agent sign-in, repository, commit,
1170
+ or network source is required, and the current directory is never modified.
1171
+
1172
+ Options:
1173
+ --port <port> Preview port (default: 4321)
1174
+ --no-open Start the preview without opening a browser
1175
+ --no-preview Validate the showcase without starting a server
1176
+ --keep Keep the temporary workspace after the demo
1177
+
1178
+ Try without installing:
1179
+ npx @doxbrix/doxloop demo
1180
+ `;
1181
+ }
610
1182
  if (command === 'init') {
611
1183
  return `Usage: doxloop init [directory] [options]
612
1184
 
613
1185
  Create a local documentation project and install the authoring and format skills.
614
1186
  Run without arguments in a terminal to answer a short set of setup questions.
1187
+ With --existing, adopt a folder that already holds a documentation site: the
1188
+ generator is detected from its configuration files and no page is changed.
615
1189
 
616
1190
  Options:
617
1191
  --title <title> Documentation site title
618
1192
  --source <name=path> Add a local product source; may be repeated
619
1193
  --spec <name=file|url> Add an OpenAPI specification as API evidence; may be repeated
1194
+ --docs <name=url> Crawl an existing documentation site to rewrite; may be repeated
620
1195
  --reference <url> Add a documentation design reference; may be repeated
621
1196
  --generator <name> Generator: ${GENERATOR_CATALOG.map((entry) => entry.id).join(', ')}
1197
+ --existing Adopt the existing documentation in [directory] (default: current folder)
1198
+ --content-dir <path> With --existing, the folder that holds the pages when detection is wrong
622
1199
  --yes Never prompt; fail instead of asking
623
1200
  --cwd <directory> Resolve paths from this directory
624
1201
 
@@ -626,6 +1203,8 @@ Examples:
626
1203
  doxloop init
627
1204
  doxloop init my-docs --source product=../my-app
628
1205
  doxloop init api-docs --spec https://example.com/openapi.json
1206
+ doxloop init new-docs --source product=../my-app --docs https://docs.example.com
1207
+ doxloop init --existing ./website
629
1208
  `;
630
1209
  }
631
1210
  if (command === 'agent') {
@@ -676,10 +1255,10 @@ Options:
676
1255
  --no-screenshots Do not capture application screenshots
677
1256
  `;
678
1257
  const createUsage = command === 'create'
679
- ? `\nRun inside a Doxloop project to answer a short set of authoring questions.\nWhen run inside a detected product repository, Doxloop offers the complete setup\nwizard first. No flags are required for interactive use.\n\nOptional automation form:\n doxloop create --source <product-directory> --output <documentation-directory> [request]\n doxloop create --spec <openapi-file-or-url> --output <documentation-directory> [request]\n`
1258
+ ? `\nRun inside a Doxloop project to answer a short set of authoring questions.\nWhen run inside a detected product repository, Doxloop offers the complete setup\nwizard first. No flags are required for interactive use.\n\nOptional automation form:\n doxloop create --source <product-directory> --output <documentation-directory> [request]\n doxloop create --spec <openapi-file-or-url> --output <documentation-directory> [request]\n doxloop create --docs <existing-documentation-url> [--source <product-directory>] --output <documentation-directory> [request]\n`
680
1259
  : '';
681
1260
  const createOptions = command === 'create'
682
- ? ` --source <directory> Read-only product source for a new documentation project\n --spec <name=file|url> OpenAPI specification used as read-only API evidence\n --output <directory> New, separate documentation project directory\n`
1261
+ ? ` --source <directory> Read-only product source for a new documentation project\n --spec <name=file|url> OpenAPI specification used as read-only API evidence\n --docs <name=url> Existing documentation site to crawl and rewrite\n --output <directory> New, separate documentation project directory\n`
683
1262
  : '';
684
1263
  return `Usage: doxloop ${command} [request] [options]
685
1264
  ${createUsage}
@@ -689,7 +1268,8 @@ Start an authoring agent with the project-local skill.
689
1268
  Options:
690
1269
  ${createOptions} --agent <name> codex, claude, or gemini
691
1270
  --model <name> Model passed to the selected agent CLI
692
- --reasoning <level> Codex reasoning effort: minimal, low, medium, high, or xhigh
1271
+ --reasoning <level> Codex reasoning effort (supported levels depend on the model)
1272
+ --effort <level> Claude effort: low, medium, high, xhigh, or max
693
1273
  --reference <url> Use a documentation design reference; may be repeated
694
1274
  ${screenshotOptions} --print Print the prepared prompt instead of starting an agent
695
1275
  --cwd <directory> Run from this project directory
@@ -706,6 +1286,27 @@ pages are captured.
706
1286
 
707
1287
  Options:
708
1288
  --cwd <directory> Run from this project directory
1289
+ `;
1290
+ }
1291
+ if (command === 'pages') {
1292
+ return `Usage:
1293
+ doxloop pages list [--format text|json]
1294
+ doxloop pages edit --path <page> [--path <page> ...] --request <instruction> [options]
1295
+
1296
+ List documentation pages or ask the agent for an isolated, reviewable edit.
1297
+
1298
+ Options:
1299
+ --path <page> Existing page to edit; may be repeated
1300
+ --request <text> Describe what should change
1301
+ --allow-related Allow navigation and page-related image changes
1302
+ --screenshots Capture application screenshots when configured
1303
+ --run-id <id> Use a caller-supplied proposal id
1304
+ --agent <name> codex, claude, or gemini
1305
+ --model <name> Model passed to the selected agent CLI
1306
+ --reasoning <level> Codex reasoning effort
1307
+ --effort <level> Claude effort
1308
+ --format <text|json> Page-list output format (default: text)
1309
+ --cwd <directory> Run from this project directory
709
1310
  `;
710
1311
  }
711
1312
  if (command === 'preview') {
@@ -718,6 +1319,135 @@ Options:
718
1319
  --port <port> Listening port (default: 4321)
719
1320
  --open Open the preview in a browser
720
1321
  --cwd <directory> Run from this project directory
1322
+ `;
1323
+ }
1324
+ if (command === 'sync') {
1325
+ return `Usage: doxloop sync <setup|status|now|review|history|off> [options]
1326
+
1327
+ Keep documentation current automatically.
1328
+
1329
+ Detection, proposal generation, review, and application are separate. Generated
1330
+ changes stay in an isolated workspace until the user accepts them. Git is not
1331
+ required for the documentation directory.
1332
+
1333
+ Actions:
1334
+ setup Answer a few questions, then install the triggers
1335
+ status Verify the triggers, agent sign-in, and current drift
1336
+ now Generate one isolated proposal when pages are stale
1337
+ review Open the local run history and visual change review
1338
+ history List every proposal and its decision status
1339
+ off Remove the schedule, keeping settings
1340
+
1341
+ Options:
1342
+ --mode <name> check (report only), propose, or auto
1343
+ --on <frequency> daily, weekdays, weekly, monthly, interval, or manual syntax
1344
+ --branch <name> Product branch documentation follows
1345
+ --quiet Print nothing when documentation is current
1346
+ --host <host> Review server host (default: 127.0.0.1)
1347
+ --port <port> Review server port (default: 4317)
1348
+ --open Open the visual review in a browser
1349
+ --cwd <directory> Run from this project directory
1350
+
1351
+ Examples:
1352
+ doxloop sync setup
1353
+ doxloop sync setup --mode propose --on every@15m --branch main
1354
+ doxloop sync status
1355
+ doxloop sync history
1356
+ doxloop sync review --open
1357
+ `;
1358
+ }
1359
+ if (command === 'check') {
1360
+ return `Usage: doxloop check [options]
1361
+
1362
+ Report which documentation pages no longer match the configured sources.
1363
+
1364
+ No authoring agent is started and no model is used: the answer comes from the
1365
+ provider API, the recorded sync baseline, and the evidence map written by the
1366
+ last authoring run. Safe to run on a schedule or in continuous integration.
1367
+
1368
+ Exit status:
1369
+ 0 documentation is current
1370
+ 1 pages are stale, or drift could not be determined
1371
+
1372
+ Options:
1373
+ --format <text|json> Output format (default: text)
1374
+ --quiet Print nothing when documentation is current
1375
+ --cwd <directory> Run from this project directory
1376
+ `;
1377
+ }
1378
+ if (command === 'replay') {
1379
+ return `Usage: doxloop replay <run-directory> [options]
1380
+
1381
+ Re-run the end-of-generation checks over a copy of a recorded run's workspace:
1382
+ capture status from the images on disk, the tolerant screenshot check, the
1383
+ deterministic post-pass, starter cleanup, and the validation that "Accept all"
1384
+ performs. The run folder itself is never modified; no agent is started.
1385
+
1386
+ Use it after changing any of those steps, and to turn a run that ended blocked
1387
+ into a regression check. Run folders live under .doxloop/runs/<run-id>.
1388
+
1389
+ Exit status:
1390
+ 0 the accept check would pass
1391
+ 1 errors remain after every repair
1392
+
1393
+ Options:
1394
+ --keep Leave the working copy in place and print its path
1395
+ --format <text|json> Output format (default: text)
1396
+ --cwd <directory> Resolve the run directory from here
1397
+ `;
1398
+ }
1399
+ if (command === 'coverage') {
1400
+ return `Usage: doxloop coverage [options]
1401
+
1402
+ Report source health, documented public-surface coverage, and evidence precision.
1403
+ Coverage is a traceability measure and does not claim that documentation is correct.
1404
+
1405
+ Options:
1406
+ --format <text|json> Output format (default: text)
1407
+ --cwd <directory> Run from this project directory
1408
+ `;
1409
+ }
1410
+ if (command === 'quality') {
1411
+ return `Usage: doxloop quality [options]
1412
+
1413
+ Run the versioned release-quality contract: deterministic validation, the
1414
+ selected generator's strict build, external links, executable examples,
1415
+ OpenAPI schemas, documentation linting, claim reverification, and optional
1416
+ rendered accessibility and visual regression checks.
1417
+
1418
+ Options:
1419
+ --format <text|json> Stable human or CI output (default: text)
1420
+ --offline Use cached external-link results without network
1421
+ --examples Run opt-in .doxloop/examples.json checks
1422
+ --rendered Run rendered accessibility and visual checks
1423
+ --update-visuals Approve current screenshots as visual baselines
1424
+ --approve-quality-baseline Approve current issue codes/files for ratcheting
1425
+ --fix Apply only deterministic formatting fixes first
1426
+ --warnings-as-errors Return exit code 1 for warnings as well as failures
1427
+ --cwd <directory> Run from this project directory
1428
+
1429
+ Exit status:
1430
+ 0 no failing gates (and no warnings with --warnings-as-errors)
1431
+ 1 one or more release gates failed
1432
+ `;
1433
+ }
1434
+ if (command === 'evaluate') {
1435
+ return `Usage: doxloop evaluate [options]
1436
+
1437
+ Score a generated or updated documentation workspace using the stable
1438
+ evaluation contract. Reports include factual grounding, coverage, examples,
1439
+ information architecture, evidence precision, page economy, update locality,
1440
+ accessibility, duration/usage when supplied, and reviewer outcomes.
1441
+
1442
+ Options:
1443
+ --mode <generation|update> Evaluation mode (default: generation)
1444
+ --before <directory> Original workspace for update-locality scoring
1445
+ --expected-change <page> Expected changed page; may be repeated
1446
+ --max-pages <count> Expected page ceiling for page-economy scoring
1447
+ --regression-threshold <n> Allowed score drop from the approved baseline
1448
+ --approve-baseline Save this report as the approved project baseline
1449
+ --format <text|json> Output format (default: text)
1450
+ --cwd <directory> Run from this project directory
721
1451
  `;
722
1452
  }
723
1453
  if (command === 'test' || command === 'status') {
@@ -728,6 +1458,26 @@ ${command === 'test' ? 'Validate documentation structure and content.' : 'Summar
728
1458
  Options:
729
1459
  --format <text|json> Output format (default: text)
730
1460
  --cwd <directory> Run from this project directory
1461
+ `;
1462
+ }
1463
+ if (command === 'history') {
1464
+ return `Usage: doxloop history [options]
1465
+
1466
+ Show what this project was asked to document and what happened. Records the
1467
+ request, its outcome, and the pages it touched. Agent transcripts stay in
1468
+ .doxloop/ui-job-logs and are never stored.
1469
+
1470
+ Options:
1471
+ --page <path> History for one page, such as docs/quickstart.mdx
1472
+ --deployments List publishing history instead of documentation runs
1473
+ --limit <count> Entries to show (default: 20)
1474
+ --format <text|json> Output format (default: text)
1475
+ --cwd <directory> Run from this project directory
1476
+
1477
+ Examples:
1478
+ doxloop history
1479
+ doxloop history --page docs/quickstart.mdx
1480
+ doxloop history --deployments --limit 5
731
1481
  `;
732
1482
  }
733
1483
  if (command === 'settings') {
@@ -764,21 +1514,61 @@ Show the Doxbrix account for the active token.
764
1514
 
765
1515
  Options:
766
1516
  --api-url <url> HTTPS API base URL; HTTP is allowed only for localhost
1517
+ `;
1518
+ }
1519
+ if (command === 'ui') {
1520
+ return `Usage: doxloop ui [options]
1521
+
1522
+ Open the local Doxloop project control center. Outside a Doxloop project, the UI
1523
+ starts with the new-project setup wizard. Project files and credentials stay on
1524
+ this computer.
1525
+
1526
+ Options:
1527
+ --page <name> Open overview, sources, update, pages, review, deploy, or settings
1528
+ --project <directory> Open this documentation project instead of the current folder
1529
+ --port <port> Local UI port (default: 4317)
1530
+ --no-open Start the server without opening a browser
1531
+ --cwd <directory> Run from this directory
1532
+
1533
+ Examples:
1534
+ doxloop ui
1535
+ doxloop ui --page review
1536
+ doxloop ui --project ~/work/product-docs
1537
+ doxloop ui --no-open --port 4400
767
1538
  `;
768
1539
  }
769
1540
  if (command === 'deploy') {
770
1541
  return `Usage: doxloop deploy [options]
771
1542
 
772
- Validate and publish documentation through the public Doxbrix HTTP API.
1543
+ Validate and publish documentation to Doxbrix or a configured static host.
773
1544
 
774
1545
  Options:
775
1546
  --dry-run Validate and summarize without uploading
1547
+ --target <target> doxbrix, github-pages, netlify, or vercel
776
1548
  --public Deploy publicly after an explicit confirmation
777
1549
  --name <name> Hosted project name
778
1550
  --slug <slug> Hosted project slug
779
1551
  --api-url <url> Override the Doxbrix API base URL
1552
+ --site-id <id> Netlify site ID
1553
+ --project-id <id> Vercel project ID or name
1554
+ --team-id <id> Optional Vercel team ID
1555
+ --branch <name> GitHub Pages branch (default: gh-pages)
1556
+ --base-path <path> Static site mount path, such as /repository
780
1557
  --yes Use saved settings without prompting
781
1558
  --cwd <directory> Run from this project directory
1559
+ `;
1560
+ }
1561
+ if (command === 'export') {
1562
+ return `Usage: doxloop export --out <directory> [options]
1563
+
1564
+ Build a portable static site for any configured generator.
1565
+
1566
+ Options:
1567
+ --out <directory> Write the static site to this directory
1568
+ --zip Also write <directory>.zip
1569
+ --base-path <path> Host below an origin path, such as /repository
1570
+ --site-url <url> Public URL used by the sitemap and canonical metadata
1571
+ --cwd <directory> Run from this project directory
782
1572
  `;
783
1573
  }
784
1574
  return `Doxloop ${VERSION}
@@ -789,6 +1579,7 @@ Usage:
789
1579
  doxloop <command> [options]
790
1580
 
791
1581
  Author:
1582
+ demo Try a safe, complete example in a temporary workspace
792
1583
  init Create a documentation project
793
1584
  create Ask an agent to create documentation
794
1585
  update Maintain docs after product changes
@@ -796,10 +1587,24 @@ Author:
796
1587
  agent Set up project-local agent skills
797
1588
  generator Install and inspect generator packages
798
1589
  capture Capture rendered design-reference evidence
1590
+ pages List pages or ask the agent for a scoped page edit
1591
+
1592
+ Maintain:
1593
+ check Report documentation stale since the last source change
1594
+ coverage Report source health, coverage, and evidence precision
1595
+ replay Re-run the end-of-generation checks over a recorded run
1596
+ sync Set up and run automatic documentation maintenance
1597
+
1598
+ Visual:
1599
+ ui Open the local Doxloop project control center
799
1600
 
800
1601
  Verify:
801
1602
  doctor Check runtime, source, agent, generator, skills, and documentation
1603
+ audit Inspect existing docs without an agent; optionally backfill unverified evidence
1604
+ quality Run the versioned release-quality contract
1605
+ evaluate Score generation/update quality and regressions
802
1606
  status Summarize the documentation project
1607
+ history Show past documentation requests, page changes, and deployments
803
1608
  settings View or change project settings
804
1609
  test Validate pages, navigation, links, and code fences
805
1610
  preview Run a beautiful local preview
@@ -809,6 +1614,7 @@ Publish:
809
1614
  logout Remove the local token
810
1615
  whoami Show the current Doxbrix account
811
1616
  deploy Publish through the public Doxbrix HTTP API
1617
+ export Build a self-hostable static folder or zip archive
812
1618
 
813
1619
  Global options:
814
1620
  --cwd <directory> Run as if started in this directory
@@ -817,6 +1623,7 @@ Global options:
817
1623
  -v, --version Show version
818
1624
 
819
1625
  Get started:
1626
+ npx @doxbrix/doxloop demo Preview a complete example safely
820
1627
  doxloop init Answer a few questions interactively
821
1628
  doxloop create Create docs from saved project settings
822
1629
  doxloop settings View or change project settings