@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.
- package/CHANGELOG.md +1145 -0
- package/README.md +422 -227
- package/assets/doxbrix-preview.css +77 -15
- package/contracts/agent-events-v1.schema.json +1 -0
- package/contracts/coverage-v1.schema.json +59 -0
- package/contracts/drift-v1.schema.json +1 -0
- package/contracts/evaluation-v1.schema.json +62 -0
- package/contracts/quality-codes-v1.json +1 -0
- package/contracts/quality-report-v1.schema.json +1 -0
- package/contracts/validation-v1.schema.json +1 -0
- package/dist/agent-failure.d.ts +36 -0
- package/dist/agent-failure.js +98 -0
- package/dist/agent-isolation.d.ts +17 -0
- package/dist/agent-isolation.js +94 -0
- package/dist/agent-log.d.ts +197 -0
- package/dist/agent-log.js +1094 -0
- package/dist/agent-process.d.ts +63 -0
- package/dist/agent-process.js +128 -0
- package/dist/agent-reply.d.ts +47 -0
- package/dist/agent-reply.js +315 -0
- package/dist/agents.d.ts +24 -5
- package/dist/agents.js +60 -9
- package/dist/api-coverage.d.ts +11 -0
- package/dist/api-coverage.js +72 -0
- package/dist/application-probe.d.ts +28 -0
- package/dist/application-probe.js +93 -0
- package/dist/application-url.d.ts +14 -0
- package/dist/application-url.js +26 -0
- package/dist/args.js +15 -0
- package/dist/artifact-deploy.d.ts +9 -0
- package/dist/artifact-deploy.js +28 -8
- package/dist/assets.d.ts +72 -0
- package/dist/assets.js +382 -0
- package/dist/auth.d.ts +10 -0
- package/dist/auth.js +38 -9
- package/dist/author.d.ts +206 -4
- package/dist/author.js +1650 -52
- package/dist/authoring-batches.d.ts +190 -0
- package/dist/authoring-batches.js +489 -0
- package/dist/authoring-postpass.d.ts +79 -0
- package/dist/authoring-postpass.js +1289 -0
- package/dist/authoring-progress.d.ts +95 -0
- package/dist/authoring-progress.js +269 -0
- package/dist/autosync.d.ts +56 -0
- package/dist/autosync.js +442 -0
- package/dist/batch-artifacts.d.ts +81 -0
- package/dist/batch-artifacts.js +412 -0
- package/dist/batch-limits.d.ts +37 -0
- package/dist/batch-limits.js +80 -0
- package/dist/branding.d.ts +41 -0
- package/dist/branding.js +229 -0
- package/dist/bundle-upload.d.ts +26 -0
- package/dist/bundle-upload.js +99 -0
- package/dist/capture-auth.d.ts +95 -0
- package/dist/capture-auth.js +204 -0
- package/dist/capture.d.ts +48 -0
- package/dist/capture.js +20 -1
- package/dist/cli.js +839 -32
- package/dist/content-links.d.ts +10 -0
- package/dist/content-links.js +49 -0
- package/dist/contract-validation.d.ts +4 -0
- package/dist/contract-validation.js +25 -0
- package/dist/coverage-actions.d.ts +8 -0
- package/dist/coverage-actions.js +71 -0
- package/dist/coverage-resolutions.d.ts +16 -0
- package/dist/coverage-resolutions.js +58 -0
- package/dist/db.d.ts +37 -0
- package/dist/db.js +288 -0
- package/dist/demo.d.ts +12 -0
- package/dist/demo.js +122 -0
- package/dist/deploy-credentials.d.ts +7 -0
- package/dist/deploy-credentials.js +76 -0
- package/dist/deploy-targets/github-pages.d.ts +3 -0
- package/dist/deploy-targets/github-pages.js +100 -0
- package/dist/deploy-targets/index.d.ts +8 -0
- package/dist/deploy-targets/index.js +78 -0
- package/dist/deploy-targets/netlify.d.ts +3 -0
- package/dist/deploy-targets/netlify.js +49 -0
- package/dist/deploy-targets/types.d.ts +32 -0
- package/dist/deploy-targets/types.js +2 -0
- package/dist/deploy-targets/vercel.d.ts +3 -0
- package/dist/deploy-targets/vercel.js +95 -0
- package/dist/deploy.d.ts +9 -0
- package/dist/deploy.js +87 -13
- package/dist/deterministic-capture.d.ts +90 -0
- package/dist/deterministic-capture.js +435 -0
- package/dist/direct-edit.d.ts +40 -0
- package/dist/direct-edit.js +159 -0
- package/dist/docs-crawl.d.ts +86 -0
- package/dist/docs-crawl.js +536 -0
- package/dist/docs-site.d.ts +49 -0
- package/dist/docs-site.js +233 -0
- package/dist/doctor.js +8 -0
- package/dist/documentation-collections.d.ts +23 -0
- package/dist/documentation-collections.js +191 -0
- package/dist/documentation-plan.d.ts +253 -0
- package/dist/documentation-plan.js +2563 -0
- package/dist/doxbrix-build.d.ts +19 -0
- package/dist/doxbrix-build.js +167 -0
- package/dist/doxbrix-markdown.d.ts +9 -0
- package/dist/doxbrix-markdown.js +115 -14
- package/dist/drift.d.ts +10 -0
- package/dist/drift.js +164 -0
- package/dist/evaluation.d.ts +46 -0
- package/dist/evaluation.js +113 -0
- package/dist/evidence-pack.d.ts +47 -0
- package/dist/evidence-pack.js +358 -0
- package/dist/evidence.d.ts +25 -0
- package/dist/evidence.js +175 -0
- package/dist/fs.d.ts +8 -2
- package/dist/fs.js +41 -11
- package/dist/generator-api.d.ts +89 -0
- package/dist/generator-preflight.d.ts +27 -0
- package/dist/generator-preflight.js +105 -0
- package/dist/generator-runtime.d.ts +7 -0
- package/dist/generator-runtime.js +17 -1
- package/dist/generators.d.ts +24 -2
- package/dist/generators.js +54 -1
- package/dist/git-delivery.d.ts +17 -0
- package/dist/git-delivery.js +123 -0
- package/dist/globs.d.ts +16 -0
- package/dist/globs.js +65 -0
- package/dist/glossary.d.ts +26 -0
- package/dist/glossary.js +179 -0
- package/dist/history.d.ts +106 -0
- package/dist/history.js +600 -0
- package/dist/html-markdown.d.ts +46 -0
- package/dist/html-markdown.js +423 -0
- package/dist/interactive.js +3 -0
- package/dist/job-events.d.ts +74 -0
- package/dist/job-events.js +377 -0
- package/dist/keep-awake.d.ts +50 -0
- package/dist/keep-awake.js +123 -0
- package/dist/local-source-snapshot.d.ts +20 -0
- package/dist/local-source-snapshot.js +61 -0
- package/dist/mintlify-detect.d.ts +3 -0
- package/dist/mintlify-detect.js +18 -0
- package/dist/mintlify-import.d.ts +75 -0
- package/dist/mintlify-import.js +190 -0
- package/dist/navigation.d.ts +98 -0
- package/dist/navigation.js +310 -0
- package/dist/openapi.d.ts +60 -0
- package/dist/openapi.js +439 -0
- package/dist/page-editor-bridge.d.ts +3 -0
- package/dist/page-editor-bridge.js +109 -0
- package/dist/page-editor-preview.d.ts +10 -0
- package/dist/page-editor-preview.js +55 -0
- package/dist/page-extension.d.ts +9 -0
- package/dist/page-extension.js +15 -0
- package/dist/page-metadata.d.ts +28 -0
- package/dist/page-metadata.js +166 -0
- package/dist/page-operations.d.ts +34 -0
- package/dist/page-operations.js +215 -0
- package/dist/page-routes.d.ts +4 -0
- package/dist/page-routes.js +61 -0
- package/dist/pages.d.ts +20 -0
- package/dist/pages.js +184 -0
- package/dist/plan-generator.d.ts +3 -0
- package/dist/plan-generator.js +21 -0
- package/dist/plan-navigation.d.ts +11 -0
- package/dist/plan-navigation.js +30 -0
- package/dist/planning-captures.d.ts +20 -0
- package/dist/planning-captures.js +143 -0
- package/dist/planning-research.d.ts +135 -0
- package/dist/planning-research.js +472 -0
- package/dist/planning-triage.d.ts +23 -0
- package/dist/planning-triage.js +131 -0
- package/dist/preview.d.ts +24 -0
- package/dist/preview.js +280 -29
- package/dist/project-detect.d.ts +36 -0
- package/dist/project-detect.js +251 -0
- package/dist/project-import.d.ts +54 -0
- package/dist/project-import.js +157 -0
- package/dist/project-lock.d.ts +6 -0
- package/dist/project-lock.js +96 -0
- package/dist/project-registry.d.ts +25 -0
- package/dist/project-registry.js +79 -0
- package/dist/project.d.ts +35 -3
- package/dist/project.js +388 -38
- package/dist/prompts.d.ts +9 -0
- package/dist/prompts.js +25 -0
- package/dist/proposal-replay.d.ts +32 -0
- package/dist/proposal-replay.js +99 -0
- package/dist/quality-claims.d.ts +8 -0
- package/dist/quality-claims.js +168 -0
- package/dist/quality-config.d.ts +5 -0
- package/dist/quality-config.js +84 -0
- package/dist/quality-contract.d.ts +37 -0
- package/dist/quality-contract.js +46 -0
- package/dist/quality-examples.d.ts +4 -0
- package/dist/quality-examples.js +233 -0
- package/dist/quality-gates.d.ts +16 -0
- package/dist/quality-gates.js +192 -0
- package/dist/quality-links.d.ts +7 -0
- package/dist/quality-links.js +149 -0
- package/dist/quality-lint.d.ts +6 -0
- package/dist/quality-lint.js +124 -0
- package/dist/quality-rendered.d.ts +11 -0
- package/dist/quality-rendered.js +222 -0
- package/dist/quality-schema.d.ts +3 -0
- package/dist/quality-schema.js +71 -0
- package/dist/release-notes.d.ts +44 -0
- package/dist/release-notes.js +183 -0
- package/dist/remote-monitor.d.ts +16 -0
- package/dist/remote-monitor.js +74 -0
- package/dist/remote-source.d.ts +34 -0
- package/dist/remote-source.js +426 -0
- package/dist/review-diff.d.ts +82 -0
- package/dist/review-diff.js +400 -0
- package/dist/review-learning.d.ts +11 -0
- package/dist/review-learning.js +60 -0
- package/dist/review-render.d.ts +38 -0
- package/dist/review-render.js +224 -0
- package/dist/review-report.d.ts +9 -0
- package/dist/review-report.js +89 -0
- package/dist/review-ui.d.ts +14 -0
- package/dist/review-ui.js +1248 -0
- package/dist/schedule.d.ts +78 -0
- package/dist/schedule.js +480 -0
- package/dist/screen-capture-provider.d.ts +52 -0
- package/dist/screen-capture-provider.js +218 -0
- package/dist/screenshot-workflow.d.ts +167 -0
- package/dist/screenshot-workflow.js +1237 -0
- package/dist/settings.d.ts +1 -1
- package/dist/settings.js +95 -7
- package/dist/site-export.d.ts +18 -0
- package/dist/site-export.js +87 -0
- package/dist/source-connectors.d.ts +33 -0
- package/dist/source-connectors.js +268 -0
- package/dist/source-discovery.d.ts +132 -0
- package/dist/source-discovery.js +823 -0
- package/dist/source-intelligence.d.ts +9 -0
- package/dist/source-intelligence.js +306 -0
- package/dist/sync-review.d.ts +28 -0
- package/dist/sync-review.js +264 -0
- package/dist/sync-runs.d.ts +192 -0
- package/dist/sync-runs.js +2244 -0
- package/dist/sync.d.ts +35 -0
- package/dist/sync.js +298 -32
- package/dist/text-diff.d.ts +9 -0
- package/dist/text-diff.js +59 -0
- package/dist/types.d.ts +946 -1
- package/dist/ui/assets/doxloop-logo-light-De7Nx7j7.png +0 -0
- package/dist/ui/assets/index-BHBYU2aG.css +1 -0
- package/dist/ui/assets/index-Cq3RPQiC.js +33 -0
- package/dist/ui/index.html +18 -0
- package/dist/ui-server.d.ts +83 -0
- package/dist/ui-server.js +3532 -0
- package/dist/usage-budget.d.ts +28 -0
- package/dist/usage-budget.js +90 -0
- package/dist/validation.d.ts +25 -1
- package/dist/validation.js +312 -26
- package/dist/workspace-tools.d.ts +54 -0
- package/dist/workspace-tools.js +123 -0
- package/docs/agent-compatibility.md +49 -32
- package/docs/ci-and-automation.md +105 -66
- package/docs/doxbrix-http-api.md +8 -1
- package/docs/existing-documentation.md +80 -0
- package/docs/generation-performance.md +108 -0
- package/docs/generator-authoring.md +68 -5
- package/docs/generator-selection.md +50 -13
- package/docs/mintlify-import.md +71 -0
- package/docs/openapi-security.md +25 -0
- package/docs/project-format.md +331 -36
- package/docs/release-quality.md +158 -0
- package/docs/releasing.md +72 -0
- package/docs/review-workflows.md +51 -0
- package/docs/security-model.md +102 -39
- package/docs/troubleshooting.md +226 -93
- package/package.json +46 -15
- package/scripts/test-auto-screenshot.mjs +172 -0
- package/skills/doxloop-authoring/SKILL.md +242 -361
- package/skills/doxloop-authoring/references/existing-documentation.md +94 -0
- package/skills/doxloop-authoring/references/navigation-architecture.md +18 -7
- package/skills/doxloop-authoring/references/page-depth.md +169 -0
- package/skills/doxloop-authoring/references/project-format.md +106 -7
- package/skills/doxloop-authoring/references/quality.md +10 -0
- package/skills/doxloop-authoring/references/screenshot-manifest.md +113 -0
- package/skills/doxloop-authoring/references/screenshots.md +149 -235
- package/skills/doxloop-authoring/references/workflows.md +76 -0
- package/skills/doxloop-doxbrix/SKILL.md +38 -22
- package/skills/doxloop-doxbrix/references/api-endpoints.md +15 -14
- package/skills/doxloop-doxbrix/references/components.md +30 -3
- package/skills/doxloop-doxbrix/references/manifest.md +3 -2
- package/vendor/doxbrix-import/LICENSE +202 -0
- package/vendor/doxbrix-import/README.md +17 -0
- package/vendor/doxbrix-import/UPSTREAM.json +47 -0
- package/vendor/doxbrix-import/dist/docs/frontmatter.d.ts +13 -0
- package/vendor/doxbrix-import/dist/docs/frontmatter.js +83 -0
- package/vendor/doxbrix-import/dist/docs/import.d.ts +21 -0
- package/vendor/doxbrix-import/dist/docs/import.js +147 -0
- package/vendor/doxbrix-import/dist/docs/manifest.d.ts +163 -0
- package/vendor/doxbrix-import/dist/docs/manifest.js +64 -0
- package/vendor/doxbrix-import/dist/docs/project.d.ts +25 -0
- package/vendor/doxbrix-import/dist/docs/project.js +77 -0
- package/vendor/doxbrix-import/dist/docs/starter.d.ts +4 -0
- package/vendor/doxbrix-import/dist/docs/starter.js +11 -0
- package/vendor/doxbrix-import/dist/importer.d.ts +227 -0
- package/vendor/doxbrix-import/dist/importer.js +1567 -0
- package/vendor/doxbrix-import/dist/mintlify-openapi.d.ts +37 -0
- package/vendor/doxbrix-import/dist/mintlify-openapi.js +305 -0
- package/vendor/doxbrix-import/dist/safe-path.d.ts +9 -0
- package/vendor/doxbrix-import/dist/safe-path.js +47 -0
- package/dist/agents.d.ts.map +0 -1
- package/dist/agents.js.map +0 -1
- package/dist/args.d.ts.map +0 -1
- package/dist/args.js.map +0 -1
- package/dist/artifact-deploy.d.ts.map +0 -1
- package/dist/artifact-deploy.js.map +0 -1
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/author.d.ts.map +0 -1
- package/dist/author.js.map +0 -1
- package/dist/capture.d.ts.map +0 -1
- package/dist/capture.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/deploy.d.ts.map +0 -1
- package/dist/deploy.js.map +0 -1
- package/dist/deployment-visibility.d.ts.map +0 -1
- package/dist/deployment-visibility.js.map +0 -1
- package/dist/doctor.d.ts.map +0 -1
- package/dist/doctor.js.map +0 -1
- package/dist/doxbrix-markdown.d.ts.map +0 -1
- package/dist/doxbrix-markdown.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/fs.d.ts.map +0 -1
- package/dist/fs.js.map +0 -1
- package/dist/generator-api.d.ts.map +0 -1
- package/dist/generator-api.js.map +0 -1
- package/dist/generator-manager.d.ts.map +0 -1
- package/dist/generator-manager.js.map +0 -1
- package/dist/generator-runtime.d.ts.map +0 -1
- package/dist/generator-runtime.js.map +0 -1
- package/dist/generators.d.ts.map +0 -1
- package/dist/generators.js.map +0 -1
- package/dist/interactive.d.ts.map +0 -1
- package/dist/interactive.js.map +0 -1
- package/dist/preview.d.ts.map +0 -1
- package/dist/preview.js.map +0 -1
- package/dist/progress.d.ts.map +0 -1
- package/dist/progress.js.map +0 -1
- package/dist/project.d.ts.map +0 -1
- package/dist/project.js.map +0 -1
- package/dist/prompts.d.ts.map +0 -1
- package/dist/prompts.js.map +0 -1
- package/dist/settings.d.ts.map +0 -1
- package/dist/settings.js.map +0 -1
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
266
|
-
|
|
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
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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 (![
|
|
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
|
|
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
|
|
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
|