videobook 0.0.0-stage → 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 +54 -0
- package/LICENSE +21 -0
- package/README.md +195 -2
- package/bin/videobook.js +7 -0
- package/dist/acestep/client.js +203 -0
- package/dist/acestep/config.js +62 -0
- package/dist/acestep/errors.js +8 -0
- package/dist/acestep/process.js +175 -0
- package/dist/alive-captions/analyze.js +117 -0
- package/dist/alive-captions/audio.js +179 -0
- package/dist/alive-captions/frames.js +59 -0
- package/dist/alive-captions/model.js +37 -0
- package/dist/alive-captions/schema.js +49 -0
- package/dist/alive-captions/track.js +9 -0
- package/dist/api-key.js +33 -0
- package/dist/app-state.js +56 -0
- package/dist/app.js +257 -0
- package/dist/archive-b2.js +332 -0
- package/dist/archive-store.js +169 -0
- package/dist/artifact-label.js +36 -0
- package/dist/artifact-refs.js +49 -0
- package/dist/asset-tag-extraction.js +169 -0
- package/dist/asset-tags.js +228 -0
- package/dist/asset-usage.js +23 -0
- package/dist/audio-cue.js +71 -0
- package/dist/audio-encoding.js +32 -0
- package/dist/audio-sync-request.js +1 -0
- package/dist/audio-sync.js +223 -0
- package/dist/audio-trim-range.js +31 -0
- package/dist/book-context.js +31 -0
- package/dist/book-manager.js +374 -0
- package/dist/catalog-gc.js +26 -0
- package/dist/chain-executor.js +815 -0
- package/dist/chain-generation.js +115 -0
- package/dist/chain-plan.js +746 -0
- package/dist/chain-step-review.js +60 -0
- package/dist/character-name.js +25 -0
- package/dist/chat-generation-routing.js +73 -0
- package/dist/chat-generation-settings.js +78 -0
- package/dist/chat-models.js +44 -0
- package/dist/chat-prompt-rewrite.js +67 -0
- package/dist/chat-router.js +628 -0
- package/dist/chat-view-actions.js +49 -0
- package/dist/cli/args.js +242 -0
- package/dist/cli/config-command.js +103 -0
- package/dist/cli/doctor-command.js +289 -0
- package/dist/cli/entry.js +2 -0
- package/dist/cli/key-report.js +16 -0
- package/dist/cli/main.js +104 -0
- package/dist/cli/mcp-command.js +88 -0
- package/dist/cli/mcp-relay.js +51 -0
- package/dist/cli/open-browser.js +14 -0
- package/dist/cli/run-command.js +231 -0
- package/dist/cli/serve-command.js +62 -0
- package/dist/cli/setup-command.js +60 -0
- package/dist/cli/stdio-guard.js +8 -0
- package/dist/cold-open-model.js +7 -0
- package/dist/cold-open.js +79 -0
- package/dist/command-reference.js +95 -0
- package/dist/content-store.js +74 -0
- package/dist/cost.js +8 -0
- package/dist/crop-geometry.js +23 -0
- package/dist/dancing-workflow.js +447 -0
- package/dist/data-home.js +44 -0
- package/dist/diagnostics.js +109 -0
- package/dist/dialog-quality.js +34 -0
- package/dist/downloads-dest.js +56 -0
- package/dist/editing-moments.js +194 -0
- package/dist/elevenlabs-audio-extension.js +66 -0
- package/dist/engine.js +54 -0
- package/dist/env-file.js +91 -0
- package/dist/env-secrets.js +148 -0
- package/dist/ffmpeg-binary.js +12 -0
- package/dist/ffmpeg.js +425 -0
- package/dist/ffprobe-binary.js +77 -0
- package/dist/forget-asset.js +216 -0
- package/dist/frame-utils.js +27 -0
- package/dist/generation-credit-error.js +11 -0
- package/dist/generation-input-mentions.js +117 -0
- package/dist/generation-input-policy.js +68 -0
- package/dist/generation-reference-mentions.js +97 -0
- package/dist/goal-characters.js +49 -0
- package/dist/goal-plan-v3.js +273 -0
- package/dist/goal-plan.js +220 -0
- package/dist/goal-planner-prompt.js +28 -0
- package/dist/goal-prompt-budget.js +4 -0
- package/dist/goal-reference-images.js +25 -0
- package/dist/goal-state.js +261 -0
- package/dist/goal-stills.js +153 -0
- package/dist/goal-store.js +144 -0
- package/dist/goal-styles.js +144 -0
- package/dist/goal-v3-runtime.js +157 -0
- package/dist/guides/character_schema_guide.md +70 -0
- package/dist/guides/gpt_image_2_5_prompting_guide.md +21 -0
- package/dist/guides/grok_imagine_prompting_guide.md +72 -0
- package/dist/guides/image_reproduction_guide.md +84 -0
- package/dist/guides/kling_3_0_prompting_guide.md +136 -0
- package/dist/guides/nano_banana_pro_prompting_guide.md +28 -0
- package/dist/guides/seedance_2_prompting_guide.md +172 -0
- package/dist/http-access.js +49 -0
- package/dist/http-byte-range.js +31 -0
- package/dist/hydration.js +56 -0
- package/dist/image-compress.js +77 -0
- package/dist/image-edit-default.js +1 -0
- package/dist/image-edit-models.js +41 -0
- package/dist/image-edit-settings.js +11 -0
- package/dist/image-region-edit.js +32 -0
- package/dist/image-subject-map.js +523 -0
- package/dist/image.js +46 -0
- package/dist/index.js +12 -0
- package/dist/job-deadline.js +60 -0
- package/dist/jobs/_alibaba-upload.js +30 -0
- package/dist/jobs/_audio-chunks.js +31 -0
- package/dist/jobs/_download-into-asset.js +15 -0
- package/dist/jobs/_fal-shared.js +22 -0
- package/dist/jobs/_fal-upload.js +35 -0
- package/dist/jobs/_image-edit-adapter.js +213 -0
- package/dist/jobs/_kie-poll.js +119 -0
- package/dist/jobs/_kie-upload.js +32 -0
- package/dist/jobs/_lease.js +163 -0
- package/dist/jobs/_persist-output.js +19 -0
- package/dist/jobs/_storage-retry.js +21 -0
- package/dist/jobs/_transcribe.js +245 -0
- package/dist/jobs/_translate-transcript.js +270 -0
- package/dist/jobs/analyze-alive-captions.js +10 -0
- package/dist/jobs/analyze-editing-moments.js +104 -0
- package/dist/jobs/analyze-jokes.js +143 -0
- package/dist/jobs/analyze-keyword-placement.js +155 -0
- package/dist/jobs/analyze-music.js +14 -0
- package/dist/jobs/analyze-video.js +282 -0
- package/dist/jobs/apply-cuts.js +83 -0
- package/dist/jobs/apply-sfx.js +107 -0
- package/dist/jobs/archive-clip.js +458 -0
- package/dist/jobs/assemble-goal.js +98 -0
- package/dist/jobs/audio-boost.js +71 -0
- package/dist/jobs/audio-mix.js +161 -0
- package/dist/jobs/backfill-asset-tags.js +144 -0
- package/dist/jobs/burn-watermark.js +316 -0
- package/dist/jobs/change-video-speed.js +176 -0
- package/dist/jobs/clean-video-audio.js +207 -0
- package/dist/jobs/cold-open.js +166 -0
- package/dist/jobs/combine-goal.js +108 -0
- package/dist/jobs/complete-provider-task.js +185 -0
- package/dist/jobs/copy-library-asset.js +33 -0
- package/dist/jobs/cost-estimate-assessment.js +113 -0
- package/dist/jobs/crop-image.js +47 -0
- package/dist/jobs/crop-video.js +71 -0
- package/dist/jobs/describe-image.js +181 -0
- package/dist/jobs/detect-scenes.js +33 -0
- package/dist/jobs/doctor-fix.js +100 -0
- package/dist/jobs/download-video.js +427 -0
- package/dist/jobs/enqueue-search-index.js +24 -0
- package/dist/jobs/even-split.js +122 -0
- package/dist/jobs/export-sequence.js +365 -0
- package/dist/jobs/extend-audio.js +229 -0
- package/dist/jobs/extract-audio.js +72 -0
- package/dist/jobs/extract-frame.js +58 -0
- package/dist/jobs/extract-notebook-audio.js +82 -0
- package/dist/jobs/extract-notebook-frame.js +93 -0
- package/dist/jobs/extract-video-clip.js +8 -0
- package/dist/jobs/extract-video-poster.js +55 -0
- package/dist/jobs/extract-waveform.js +25 -0
- package/dist/jobs/finish-goal.js +255 -0
- package/dist/jobs/forget-asset.js +11 -0
- package/dist/jobs/generate-acestep-cover.js +219 -0
- package/dist/jobs/generate-media.js +70 -0
- package/dist/jobs/image-subjects.js +1059 -0
- package/dist/jobs/index.js +18 -0
- package/dist/jobs/inject-first-frame.js +175 -0
- package/dist/jobs/isolate-vocals.js +69 -0
- package/dist/jobs/kie-paygate.js +177 -0
- package/dist/jobs/label-music-sections.js +5 -0
- package/dist/jobs/osg-placement.js +135 -0
- package/dist/jobs/pad-video.js +72 -0
- package/dist/jobs/plan-goal.js +97 -0
- package/dist/jobs/post-record.js +21 -0
- package/dist/jobs/post-to-x.js +21 -0
- package/dist/jobs/prepare-search-reference.js +190 -0
- package/dist/jobs/prepare-temporal-search.js +16 -0
- package/dist/jobs/process-audio.js +67 -0
- package/dist/jobs/process-image.js +58 -0
- package/dist/jobs/process-upload.js +166 -0
- package/dist/jobs/process-video.js +438 -0
- package/dist/jobs/register-all.js +161 -0
- package/dist/jobs/relayout-notebook.js +78 -0
- package/dist/jobs/remove-sound.js +299 -0
- package/dist/jobs/render-project.js +380 -0
- package/dist/jobs/render-qr-outro.js +138 -0
- package/dist/jobs/render-timeline.js +283 -0
- package/dist/jobs/repair-search-indexes.js +42 -0
- package/dist/jobs/replace-video-audio.js +64 -0
- package/dist/jobs/restore-archive-clip.js +53 -0
- package/dist/jobs/reverse-video.js +53 -0
- package/dist/jobs/rewrite-script.js +173 -0
- package/dist/jobs/run-notebook.js +3335 -0
- package/dist/jobs/semantic-index.js +797 -0
- package/dist/jobs/smooth-scene-concat.js +257 -0
- package/dist/jobs/splice-video.js +280 -0
- package/dist/jobs/split-video.js +127 -0
- package/dist/jobs/suggest-cuts.js +149 -0
- package/dist/jobs/suggest-sfx.js +135 -0
- package/dist/jobs/sync-video-to-audio.js +329 -0
- package/dist/jobs/tag-asset.js +266 -0
- package/dist/jobs/task-failure.js +16 -0
- package/dist/jobs/timewarp-video.js +152 -0
- package/dist/jobs/transcribe-video-audio.js +117 -0
- package/dist/jobs/transcribe.js +35 -0
- package/dist/jobs/translate-transcript.js +55 -0
- package/dist/jobs/trim-audio.js +133 -0
- package/dist/jobs/trim-video.js +291 -0
- package/dist/jobs/xai-paygate.js +26 -0
- package/dist/keyword-placement-cache.js +74 -0
- package/dist/lazy-render.js +142 -0
- package/dist/library-browse.js +78 -0
- package/dist/lightning-defaults.js +206 -0
- package/dist/load-env.js +12 -0
- package/dist/local-search-models.js +206 -0
- package/dist/local-upload.js +318 -0
- package/dist/logger.js +69 -0
- package/dist/media/adapters/alibaba.js +20 -0
- package/dist/media/adapters/elevenlabs.js +58 -0
- package/dist/media/adapters/fal.js +45 -0
- package/dist/media/adapters/index.js +26 -0
- package/dist/media/adapters/kie.js +120 -0
- package/dist/media/adapters/pending.js +21 -0
- package/dist/media/adapters/types.js +5 -0
- package/dist/media/adapters/xai.js +18 -0
- package/dist/media/catalog.js +236 -0
- package/dist/media/complete.js +191 -0
- package/dist/media/cost.js +45 -0
- package/dist/media/descriptor.js +157 -0
- package/dist/media/endpoints.js +11 -0
- package/dist/media/frame-options.js +18 -0
- package/dist/media/insert-video-timing.js +17 -0
- package/dist/media/json-schema.js +75 -0
- package/dist/media/legacy-task-types.js +129 -0
- package/dist/media/listing.js +24 -0
- package/dist/media/model-id.js +14 -0
- package/dist/media/preview.js +86 -0
- package/dist/media/prompt-dialects.js +65 -0
- package/dist/media/ref-fields.js +106 -0
- package/dist/media/refs.js +131 -0
- package/dist/media/request.js +188 -0
- package/dist/media/types.js +6 -0
- package/dist/media/wire.js +161 -0
- package/dist/media-library.js +535 -0
- package/dist/media-output-path.js +33 -0
- package/dist/multyculty-workflow.js +757 -0
- package/dist/notebook-action-catalog.js +608 -0
- package/dist/notebook-cell-outputs.js +726 -0
- package/dist/notebook-credit-retry.js +45 -0
- package/dist/notebook-fixtures.js +8 -0
- package/dist/notebook-generation-contract.js +77 -0
- package/dist/notebook-generation-controls.js +145 -0
- package/dist/notebook-goal-layout.js +40 -0
- package/dist/notebook-graph.js +37 -0
- package/dist/notebook-image-edit.js +84 -0
- package/dist/notebook-initial-generation-prompt.js +222 -0
- package/dist/notebook-live-analysis.js +941 -0
- package/dist/notebook-media-ops.js +579 -0
- package/dist/notebook-mutations.js +20 -0
- package/dist/notebook-placement.js +41 -0
- package/dist/notebook-provider-failure.js +87 -0
- package/dist/notebook-published-post.js +26 -0
- package/dist/notebook-reference-resolver.js +227 -0
- package/dist/notebook-relayout.js +270 -0
- package/dist/notebook-run-plan.js +519 -0
- package/dist/notebook-shots.js +162 -0
- package/dist/notebook-workflow.js +856 -0
- package/dist/one-password.js +200 -0
- package/dist/openai-intelligence.js +99 -0
- package/dist/original-media-file.js +12 -0
- package/dist/osg-assets.js +36 -0
- package/dist/osg-placement-cache.js +64 -0
- package/dist/osg-settings.js +27 -0
- package/dist/paid-tools.js +22 -0
- package/dist/platform-guidance.js +26 -0
- package/dist/posts/dropbox-auth.js +91 -0
- package/dist/posts/dropbox-upload.js +132 -0
- package/dist/posts/icloud-drive.js +134 -0
- package/dist/posts/icloud-status.js +64 -0
- package/dist/posts/post-contract.js +2 -0
- package/dist/posts/post-copy-format.js +43 -0
- package/dist/posts/post-copy.js +193 -0
- package/dist/posts/post-folder.js +18 -0
- package/dist/posts/post-metrics.js +119 -0
- package/dist/posts/post-store.js +340 -0
- package/dist/posts/x-post-target.js +43 -0
- package/dist/preview-payload.js +90 -0
- package/dist/prompt-surfaces.js +22 -0
- package/dist/provider-prompt-transform.js +77 -0
- package/dist/provider-status-link.js +21 -0
- package/dist/provider-task-record.js +78 -0
- package/dist/provider-task-status.js +47 -0
- package/dist/provider-usage.js +294 -0
- package/dist/queue.js +810 -0
- package/dist/reference-video-duration.js +17 -0
- package/dist/remotion/BlurFillVideo.js +51 -0
- package/dist/remotion/KeywordGraphicsOverlay.js +68 -0
- package/dist/remotion/OsgOverlay.js +30 -0
- package/dist/remotion/QrOutroComposition.js +47 -0
- package/dist/remotion/RenderComposition.js +11 -0
- package/dist/remotion/browser.js +59 -0
- package/dist/remotion/constants.js +35 -0
- package/dist/remotion/index.js +66 -0
- package/dist/remotion/keyword-graphics.js +151 -0
- package/dist/remotion/keyword-placement.js +182 -0
- package/dist/remotion/osg-spec.js +198 -0
- package/dist/remotion/public/qr-outro-impact.wav +0 -0
- package/dist/remotion/qr-outro-audio.js +1 -0
- package/dist/remotion/qr-outro-spec.js +211 -0
- package/dist/remotion/shared-bundle.js +37 -0
- package/dist/remotion/subtitles/AliveCaptions.js +83 -0
- package/dist/remotion/subtitles/Highlight.js +58 -0
- package/dist/remotion/subtitles/Impact.js +67 -0
- package/dist/remotion/subtitles/SubtitleOverlay.js +9 -0
- package/dist/remotion/subtitles/alive-layout.js +321 -0
- package/dist/remotion/subtitles/alive-types.js +17 -0
- package/dist/remotion/subtitles/chunker.js +68 -0
- package/dist/remotion/subtitles/segments.js +42 -0
- package/dist/remotion/subtitles/styles.js +4 -0
- package/dist/remotion/subtitles/types.js +1 -0
- package/dist/remotion/util.js +18 -0
- package/dist/remotion-cancellation.js +15 -0
- package/dist/rename-helpers.js +204 -0
- package/dist/rhythm/analyze.js +128 -0
- package/dist/rhythm/contract.js +14 -0
- package/dist/rhythm/draft-model.js +19 -0
- package/dist/rhythm/energy.js +133 -0
- package/dist/rhythm/fft.js +44 -0
- package/dist/rhythm/model.js +71 -0
- package/dist/rhythm/music-analysis.js +124 -0
- package/dist/rhythm/music-draft.js +261 -0
- package/dist/rhythm/onset.js +101 -0
- package/dist/rhythm/run-worker.js +51 -0
- package/dist/rhythm/score.js +47 -0
- package/dist/rhythm/section-labels.js +168 -0
- package/dist/rhythm/sequence-rhythm.js +67 -0
- package/dist/rhythm/synthesize.js +103 -0
- package/dist/rhythm/timing.js +98 -0
- package/dist/rhythm/track.js +320 -0
- package/dist/rhythm/visual-fit.js +4 -0
- package/dist/rhythm/worker.js +25 -0
- package/dist/scene-detect.js +200 -0
- package/dist/scene-transition.js +12 -0
- package/dist/scheduled-posts.js +200 -0
- package/dist/search-index-coverage.js +13 -0
- package/dist/search-index-media.js +71 -0
- package/dist/search-reference-store.js +60 -0
- package/dist/search-scoring.js +17 -0
- package/dist/serve.js +125 -0
- package/dist/server-lock.js +143 -0
- package/dist/server.js +42 -0
- package/dist/srt.js +16 -0
- package/dist/tag-backfill-state.js +121 -0
- package/dist/tag-settings.js +30 -0
- package/dist/tag-vocabulary.js +237 -0
- package/dist/test-queue-cleanup.js +1 -0
- package/dist/time-parse.js +50 -0
- package/dist/timeline-helpers.js +554 -0
- package/dist/timewarp.js +87 -0
- package/dist/tools/_image-inputs.js +9 -0
- package/dist/tools/_media-refs.js +84 -0
- package/dist/tools/_shared.js +59 -0
- package/dist/tools/_suggest-shared.js +279 -0
- package/dist/tools/add-timeline-audio.js +73 -0
- package/dist/tools/alive-captions.js +157 -0
- package/dist/tools/analyze-jokes.js +56 -0
- package/dist/tools/analyze-video.js +66 -0
- package/dist/tools/animate-keywords.js +217 -0
- package/dist/tools/api-status.js +42 -0
- package/dist/tools/application-commands.js +34 -0
- package/dist/tools/apply-cuts.js +112 -0
- package/dist/tools/apply-sfx.js +152 -0
- package/dist/tools/apply-watermark.js +670 -0
- package/dist/tools/archive-clip.js +74 -0
- package/dist/tools/asset-history.js +61 -0
- package/dist/tools/asset-manifest.js +24 -0
- package/dist/tools/asset-tags.js +151 -0
- package/dist/tools/assign-slot.js +97 -0
- package/dist/tools/audio-boost.js +107 -0
- package/dist/tools/audio-mix.js +121 -0
- package/dist/tools/book-usage.js +16 -0
- package/dist/tools/chain.js +136 -0
- package/dist/tools/change-video-speed.js +105 -0
- package/dist/tools/chat-history.js +5 -0
- package/dist/tools/chat.js +848 -0
- package/dist/tools/clean-video-audio.js +97 -0
- package/dist/tools/clear-failed-tasks.js +66 -0
- package/dist/tools/cold-open.js +114 -0
- package/dist/tools/command-plan.js +1680 -0
- package/dist/tools/commands.js +1666 -0
- package/dist/tools/concat-images.js +236 -0
- package/dist/tools/connect-service.js +55 -0
- package/dist/tools/copy-file-to-clipboard.js +58 -0
- package/dist/tools/create-character.js +114 -0
- package/dist/tools/create-qart.js +253 -0
- package/dist/tools/create-qr-outro.js +149 -0
- package/dist/tools/create-qrbtf.js +215 -0
- package/dist/tools/create-watermark.js +97 -0
- package/dist/tools/crop-asset.js +18 -0
- package/dist/tools/crop-image.js +120 -0
- package/dist/tools/crop-video.js +97 -0
- package/dist/tools/delete-asset.js +104 -0
- package/dist/tools/describe-image.js +69 -0
- package/dist/tools/detect-scenes.js +44 -0
- package/dist/tools/doctor.js +126 -0
- package/dist/tools/download-video.js +114 -0
- package/dist/tools/duplicate-asset.js +184 -0
- package/dist/tools/edit-timeline.js +59 -0
- package/dist/tools/editing-moments.js +105 -0
- package/dist/tools/estimate-cost.js +171 -0
- package/dist/tools/even-split.js +295 -0
- package/dist/tools/export-asset-file.js +22 -0
- package/dist/tools/export-asset.js +140 -0
- package/dist/tools/export-sequence.js +250 -0
- package/dist/tools/export-to-downloads.js +54 -0
- package/dist/tools/export-transcript-srt.js +79 -0
- package/dist/tools/extend-audio.js +100 -0
- package/dist/tools/extract-audio.js +174 -0
- package/dist/tools/extract-frame.js +108 -0
- package/dist/tools/extract-notebook-audio.js +173 -0
- package/dist/tools/extract-notebook-frame.js +160 -0
- package/dist/tools/extract-video-clip.js +101 -0
- package/dist/tools/generate-media.js +157 -0
- package/dist/tools/generation-payload.js +48 -0
- package/dist/tools/get-timeline.js +12 -0
- package/dist/tools/get-watermark-config.js +38 -0
- package/dist/tools/goal.js +644 -0
- package/dist/tools/image-subjects.js +711 -0
- package/dist/tools/index.js +323 -0
- package/dist/tools/inject-first-frame.js +154 -0
- package/dist/tools/invert-image.js +70 -0
- package/dist/tools/isolate-vocals.js +66 -0
- package/dist/tools/list-assets.js +98 -0
- package/dist/tools/list-cell-generations.js +225 -0
- package/dist/tools/list-elevenlabs-voices.js +163 -0
- package/dist/tools/log-wrapper.js +29 -0
- package/dist/tools/make-cover-song.js +95 -0
- package/dist/tools/mark-asset-seen.js +23 -0
- package/dist/tools/media-library.js +245 -0
- package/dist/tools/move-timeline-audio.js +35 -0
- package/dist/tools/music-analysis.js +60 -0
- package/dist/tools/music-draft.js +29 -0
- package/dist/tools/pad-video.js +87 -0
- package/dist/tools/post-record.js +83 -0
- package/dist/tools/post.js +168 -0
- package/dist/tools/prompt-history.js +164 -0
- package/dist/tools/provider-key-status.js +248 -0
- package/dist/tools/qrbtf-render.js +849 -0
- package/dist/tools/read-asset-metadata.js +26 -0
- package/dist/tools/read-file-at-commit.js +29 -0
- package/dist/tools/read-transcript.js +41 -0
- package/dist/tools/reconcile-project-work.js +1452 -0
- package/dist/tools/recover-asset.js +28 -0
- package/dist/tools/remove-sound.js +72 -0
- package/dist/tools/remove-timeline-audio.js +34 -0
- package/dist/tools/remove-timeline-slot.js +40 -0
- package/dist/tools/rename-all.js +434 -0
- package/dist/tools/rename-asset.js +34 -0
- package/dist/tools/render-settings.js +47 -0
- package/dist/tools/render-timeline.js +120 -0
- package/dist/tools/render.js +384 -0
- package/dist/tools/reorder-character-images.js +45 -0
- package/dist/tools/replace-video-audio.js +133 -0
- package/dist/tools/restore-archive-clips.js +209 -0
- package/dist/tools/restore-asset-version.js +37 -0
- package/dist/tools/retry-generation.js +54 -0
- package/dist/tools/reverse-video.js +74 -0
- package/dist/tools/rewrite-prompt.js +388 -0
- package/dist/tools/rewrite-script.js +67 -0
- package/dist/tools/schema-from-apicity.js +66 -0
- package/dist/tools/search-archive.js +150 -0
- package/dist/tools/semantic-search.js +1214 -0
- package/dist/tools/sequence-rhythm.js +43 -0
- package/dist/tools/set-character-metadata.js +84 -0
- package/dist/tools/set-env-secret.js +40 -0
- package/dist/tools/set-osg.js +143 -0
- package/dist/tools/show-folder.js +37 -0
- package/dist/tools/smooth-scene-concat.js +122 -0
- package/dist/tools/splice-video.js +149 -0
- package/dist/tools/split-video.js +202 -0
- package/dist/tools/suggest-cuts.js +45 -0
- package/dist/tools/suggest-sfx.js +43 -0
- package/dist/tools/sync-video-to-audio.js +115 -0
- package/dist/tools/tag-backfill.js +177 -0
- package/dist/tools/timewarp-video.js +85 -0
- package/dist/tools/tool-def.js +1 -0
- package/dist/tools/tool-json-schema.js +116 -0
- package/dist/tools/transcribe.js +90 -0
- package/dist/tools/translate-subtitles.js +59 -0
- package/dist/tools/trim-audio.js +139 -0
- package/dist/tools/trim-video.js +168 -0
- package/dist/tools/update-script.js +25 -0
- package/dist/tools/update-timeline-audio.js +40 -0
- package/dist/tools/upload-file.js +41 -0
- package/dist/tools/verify-x-mentions.js +141 -0
- package/dist/tools/video-editing-command-contract.js +291 -0
- package/dist/tools/video-source-reference.js +30 -0
- package/dist/tools/video-time-input.js +18 -0
- package/dist/tools/videobook-domain.js +2021 -0
- package/dist/tools/wait-for-asset-ready.js +143 -0
- package/dist/tools/workspace-actions.js +54 -0
- package/dist/transcript-projection.js +202 -0
- package/dist/transcript-srt.js +33 -0
- package/dist/upload.js +76 -0
- package/dist/usage-providers.js +25 -0
- package/dist/video-analysis-projection.js +46 -0
- package/dist/video-poster.js +33 -0
- package/dist/video-proxy.js +99 -0
- package/dist/vision-regions.js +111 -0
- package/dist/voice-actions.js +90 -0
- package/dist/voice-audio.js +55 -0
- package/dist/voice-config.js +122 -0
- package/dist/voice-continuation.js +54 -0
- package/dist/voice-contract.js +1 -0
- package/dist/voice-http.js +145 -0
- package/dist/voice-reply-progress.js +55 -0
- package/dist/voice-session.js +992 -0
- package/dist/watermark-badge.js +259 -0
- package/dist/watermark-config.js +111 -0
- package/dist/watermark-handle-position.js +55 -0
- package/dist/watermark-platform.js +15 -0
- package/dist/watermark-timing.js +16 -0
- package/dist/workspace-action-catalog.js +315 -0
- package/dist/workspace-action-contract.js +21 -0
- package/dist/workspace-action-groups.js +98 -0
- package/dist/workspace-action-surfaces.js +7 -0
- package/dist/x/account.js +88 -0
- package/dist/x/authenticated-handle.js +30 -0
- package/dist/x/oauth-flow.js +191 -0
- package/dist/x/post-contract.js +1 -0
- package/dist/x/post-metadata.js +44 -0
- package/dist/x/post-to-x.js +456 -0
- package/dist/x/published-post-cell.js +90 -0
- package/dist/x/x-tokens.js +69 -0
- package/dist/ytdlp-binary.js +65 -0
- package/dist/zod-slots.js +334 -0
- package/package.json +84 -4
- package/ui/assets/alive-captions-BWb5nLKj.css +1 -0
- package/ui/assets/alive-captions-DPEr1nKM.js +1 -0
- package/ui/assets/confirm-bodies-D8ouHXKy.js +1 -0
- package/ui/assets/confirm-bodies-DrMhaXC6.css +1 -0
- package/ui/assets/fullscreen-B7nw4YbE.js +1 -0
- package/ui/assets/goal-ui-DK-xTCK0.js +2 -0
- package/ui/assets/goal-ui-DXdUM2KF.css +1 -0
- package/ui/assets/index-DzPAnCUj.css +1 -0
- package/ui/assets/index-kS1905s5.js +37 -0
- package/ui/assets/inspector-MlzQbTVo.js +1 -0
- package/ui/assets/jobs-ui-CoinOWx0.js +1 -0
- package/ui/assets/music-video-Cbe80kmQ.js +1 -0
- package/ui/assets/music-video-DPaiKKEq.css +1 -0
- package/ui/assets/ui-Oirr9VOf.css +1 -0
- package/ui/assets/ui-xo_Y1yQg.js +2 -0
- package/ui/assets/vendor-D2rfiZ5j.css +1 -0
- package/ui/assets/vendor-DfTCWgKm.js +19 -0
- package/ui/assets/vendor-flow-BXbeWxW7.js +7 -0
- package/ui/assets/vendor-flow-BnuhLJ6X.css +1 -0
- package/ui/assets/vendor-icons-CGqhCIXV.js +1 -0
- package/ui/assets/vendor-mcp-BTlEJcHV.js +67 -0
- package/ui/assets/vendor-react-Dj7D1fD4.js +9 -0
- package/ui/assets/vendor-remotion-CH1HXrlc.js +95 -0
- package/ui/assets/videobook-Qlp9HQFu.js +20 -0
- package/ui/favicon.svg +8 -0
- package/ui/index.html +126 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { resolveNotebookCellArtifactId } from "videobook-engine";
|
|
2
|
+
import { notebookGridTag } from "videobook-engine/notebook-grid";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { resolveNotebookArtifactReference } from "./notebook-reference-resolver.js";
|
|
5
|
+
const SHOW_MEDIA_FULLSCREEN = "show_media_fullscreen";
|
|
6
|
+
const FULLSCREEN_MEDIA_KINDS = ["image", "video", "final"];
|
|
7
|
+
const CHAT_VIEW_ACTIONS = [
|
|
8
|
+
{
|
|
9
|
+
name: SHOW_MEDIA_FULLSCREEN,
|
|
10
|
+
description: "Show, open, view or enlarge one notebook cell's image or video in the fullscreen viewer. Changes nothing in the book",
|
|
11
|
+
schema: {
|
|
12
|
+
asset_id: z.string().min(1).describe("Artifact ID or grid address of the cell's image or video"),
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
function resolveMediaFullscreen(engine, notebook, reference) {
|
|
17
|
+
if (!notebook)
|
|
18
|
+
return { ok: false, message: "Open a notebook before showing media fullscreen" };
|
|
19
|
+
const resolved = resolveNotebookArtifactReference({ engine, documents: [notebook], reference });
|
|
20
|
+
if ("code" in resolved)
|
|
21
|
+
return { ok: false, message: resolved.message };
|
|
22
|
+
const cell = notebook.cells.find((candidate) => resolved.cellId
|
|
23
|
+
? candidate.id === resolved.cellId
|
|
24
|
+
: resolveNotebookCellArtifactId(notebook, candidate) === resolved.artifactId);
|
|
25
|
+
if (!cell)
|
|
26
|
+
return { ok: false, message: `No cell in this notebook holds ${resolved.mention}` };
|
|
27
|
+
const artifact = engine.artifacts.get(resolved.artifactId);
|
|
28
|
+
if (!artifact.ok)
|
|
29
|
+
return { ok: false, message: artifact.error.message };
|
|
30
|
+
if (!FULLSCREEN_MEDIA_KINDS.includes(artifact.value.kind)) {
|
|
31
|
+
return {
|
|
32
|
+
ok: false,
|
|
33
|
+
message: `${notebookGridTag(cell.slot)} holds ${artifact.value.kind}; fullscreen shows images and video`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return { ok: true, params: { asset_id: resolved.artifactId, cell_id: cell.id } };
|
|
37
|
+
}
|
|
38
|
+
/** Validate a routed view action and pin it to the cell the app should open. */
|
|
39
|
+
function resolveChatViewAction(engine, notebook, tool, params) {
|
|
40
|
+
const action = CHAT_VIEW_ACTIONS.find((candidate) => candidate.name === tool);
|
|
41
|
+
if (!action)
|
|
42
|
+
return { ok: false, message: `Unknown view action: ${tool}` };
|
|
43
|
+
const parsed = z.object(action.schema).safeParse(params);
|
|
44
|
+
if (!parsed.success) {
|
|
45
|
+
return { ok: false, message: `Invalid parameters for ${tool}: ${z.prettifyError(parsed.error)}` };
|
|
46
|
+
}
|
|
47
|
+
return resolveMediaFullscreen(engine, notebook, String(parsed.data.asset_id));
|
|
48
|
+
}
|
|
49
|
+
export { CHAT_VIEW_ACTIONS, resolveChatViewAction };
|
package/dist/cli/args.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
export class CliUsageError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "CliUsageError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const LOG_LEVELS = new Set(["silent", "error", "warn", "info", "debug"]);
|
|
10
|
+
const COMMANDS = new Set(["serve", "mcp", "run", "tools", "gc-books", "doctor", "setup", "config", "version", "help"]);
|
|
11
|
+
const CONFIG_ACTIONS = new Set(["list", "get", "set", "unset"]);
|
|
12
|
+
const POSITIONAL_COMMANDS = new Set(["run", "setup", "config"]);
|
|
13
|
+
const DEFAULT_RUN_TIMEOUT_SECONDS = 600;
|
|
14
|
+
const GLOBAL_OPTIONS = {
|
|
15
|
+
port: { type: "string" },
|
|
16
|
+
library: { type: "string" },
|
|
17
|
+
home: { type: "string" },
|
|
18
|
+
"log-level": { type: "string" },
|
|
19
|
+
help: { type: "boolean", short: "h" },
|
|
20
|
+
version: { type: "boolean", short: "v" },
|
|
21
|
+
};
|
|
22
|
+
const COMMAND_OPTIONS = {
|
|
23
|
+
serve: { headless: { type: "boolean" }, "no-ui": { type: "boolean" }, open: { type: "boolean" } },
|
|
24
|
+
mcp: {},
|
|
25
|
+
run: { wait: { type: "boolean" }, "no-wait": { type: "boolean" }, timeout: { type: "string" } },
|
|
26
|
+
tools: {},
|
|
27
|
+
"gc-books": {},
|
|
28
|
+
doctor: { json: { type: "boolean" } },
|
|
29
|
+
setup: { "op-vault": { type: "string" }, "op-token": { type: "string" }, "no-verify": { type: "boolean" }, remove: { type: "boolean" } },
|
|
30
|
+
config: { json: { type: "boolean" } },
|
|
31
|
+
};
|
|
32
|
+
function splitCommand(argv) {
|
|
33
|
+
const [first, ...rest] = argv;
|
|
34
|
+
if (first === undefined || first.startsWith("-"))
|
|
35
|
+
return { command: "serve", rest: argv };
|
|
36
|
+
if (!COMMANDS.has(first))
|
|
37
|
+
throw new CliUsageError(`Unknown command "${first}".`);
|
|
38
|
+
return { command: first, rest };
|
|
39
|
+
}
|
|
40
|
+
function parsePort(raw) {
|
|
41
|
+
if (typeof raw !== "string")
|
|
42
|
+
return undefined;
|
|
43
|
+
const port = Number(raw);
|
|
44
|
+
if (!/^\d+$/u.test(raw) || port > 65535)
|
|
45
|
+
throw new CliUsageError(`--port must be an integer from 0 to 65535, got "${raw}".`);
|
|
46
|
+
return port;
|
|
47
|
+
}
|
|
48
|
+
function parseLogLevel(raw) {
|
|
49
|
+
if (typeof raw !== "string")
|
|
50
|
+
return undefined;
|
|
51
|
+
if (!LOG_LEVELS.has(raw))
|
|
52
|
+
throw new CliUsageError(`--log-level must be one of silent, error, warn, info, debug; got "${raw}".`);
|
|
53
|
+
return raw;
|
|
54
|
+
}
|
|
55
|
+
function parseTimeout(raw) {
|
|
56
|
+
if (typeof raw !== "string")
|
|
57
|
+
return DEFAULT_RUN_TIMEOUT_SECONDS;
|
|
58
|
+
const seconds = Number(raw);
|
|
59
|
+
if (!Number.isFinite(seconds) || seconds <= 0)
|
|
60
|
+
throw new CliUsageError(`--timeout must be a positive number of seconds, got "${raw}".`);
|
|
61
|
+
return seconds;
|
|
62
|
+
}
|
|
63
|
+
function stringValue(raw) {
|
|
64
|
+
return typeof raw === "string" && raw.trim() !== "" ? raw : undefined;
|
|
65
|
+
}
|
|
66
|
+
function globalFlags(values) {
|
|
67
|
+
const flags = {};
|
|
68
|
+
const port = parsePort(values.port);
|
|
69
|
+
const library = stringValue(values.library);
|
|
70
|
+
const home = stringValue(values.home);
|
|
71
|
+
const logLevel = parseLogLevel(values["log-level"]);
|
|
72
|
+
if (port !== undefined)
|
|
73
|
+
flags.port = port;
|
|
74
|
+
if (library !== undefined)
|
|
75
|
+
flags.library = library;
|
|
76
|
+
if (home !== undefined)
|
|
77
|
+
flags.home = home;
|
|
78
|
+
if (logLevel !== undefined)
|
|
79
|
+
flags.logLevel = logLevel;
|
|
80
|
+
return flags;
|
|
81
|
+
}
|
|
82
|
+
function parseValue(raw) {
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(raw);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return raw;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export function parseToolArgs(pairs) {
|
|
91
|
+
const args = {};
|
|
92
|
+
for (const pair of pairs) {
|
|
93
|
+
const eq = pair.indexOf("=");
|
|
94
|
+
if (eq <= 0)
|
|
95
|
+
throw new CliUsageError(`Invalid tool argument "${pair}". Use key=value.`);
|
|
96
|
+
args[pair.slice(0, eq)] = parseValue(pair.slice(eq + 1));
|
|
97
|
+
}
|
|
98
|
+
return args;
|
|
99
|
+
}
|
|
100
|
+
function parseCommandArgs(command, rest) {
|
|
101
|
+
try {
|
|
102
|
+
const parsed = parseArgs({
|
|
103
|
+
args: rest,
|
|
104
|
+
options: { ...GLOBAL_OPTIONS, ...COMMAND_OPTIONS[command] },
|
|
105
|
+
allowPositionals: POSITIONAL_COMMANDS.has(command),
|
|
106
|
+
strict: true,
|
|
107
|
+
});
|
|
108
|
+
return { values: parsed.values, positionals: parsed.positionals };
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
throw new CliUsageError(error instanceof Error ? error.message : String(error));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function buildSetup(values, positionals, flags) {
|
|
115
|
+
const [target, ...extra] = positionals;
|
|
116
|
+
if (target !== "1password" || extra.length > 0)
|
|
117
|
+
throw new CliUsageError("setup needs a target: videobook setup 1password --op-vault <vault> [--op-token env:OP_SERVICE_ACCOUNT_TOKEN]");
|
|
118
|
+
const vault = stringValue(values["op-vault"]);
|
|
119
|
+
const token = stringValue(values["op-token"]);
|
|
120
|
+
return {
|
|
121
|
+
command: "setup",
|
|
122
|
+
target,
|
|
123
|
+
flags: {
|
|
124
|
+
...flags,
|
|
125
|
+
...(vault === undefined ? {} : { vault }),
|
|
126
|
+
...(token === undefined ? {} : { token }),
|
|
127
|
+
verify: values["no-verify"] !== true,
|
|
128
|
+
remove: values.remove === true,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function buildConfig(values, positionals, flags) {
|
|
133
|
+
const [action = "list", key, value, ...extra] = positionals;
|
|
134
|
+
if (!CONFIG_ACTIONS.has(action))
|
|
135
|
+
throw new CliUsageError(`Unknown config action "${action}". Use list, get, set or unset.`);
|
|
136
|
+
const needsKey = action !== "list";
|
|
137
|
+
const needsValue = action === "set";
|
|
138
|
+
const expected = 1 + (needsKey ? 1 : 0) + (needsValue ? 1 : 0);
|
|
139
|
+
if (positionals.length > Math.max(expected, 1) || extra.length > 0 || (needsKey && !key) || (needsValue && value === undefined)) {
|
|
140
|
+
throw new CliUsageError("Usage: videobook config list | get <KEY> | set <KEY> <VALUE> | unset <KEY>");
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
command: "config",
|
|
144
|
+
action: action,
|
|
145
|
+
key: key ?? null,
|
|
146
|
+
value: value ?? null,
|
|
147
|
+
flags: { ...flags, json: values.json === true },
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function buildCommand(command, values, positionals) {
|
|
151
|
+
const flags = globalFlags(values);
|
|
152
|
+
switch (command) {
|
|
153
|
+
case "serve":
|
|
154
|
+
return { command, flags: { ...flags, ui: values.headless !== true && values["no-ui"] !== true, open: values.open === true } };
|
|
155
|
+
case "run": {
|
|
156
|
+
const [tool, ...pairs] = positionals;
|
|
157
|
+
if (!tool)
|
|
158
|
+
throw new CliUsageError("run needs a tool name, for example: videobook run list_books");
|
|
159
|
+
return { command, tool, toolArgs: parseToolArgs(pairs), flags: { ...flags, wait: values["no-wait"] !== true, timeoutSeconds: parseTimeout(values.timeout) } };
|
|
160
|
+
}
|
|
161
|
+
case "doctor":
|
|
162
|
+
return { command, flags: { ...flags, json: values.json === true } };
|
|
163
|
+
case "setup":
|
|
164
|
+
return buildSetup(values, positionals, flags);
|
|
165
|
+
case "config":
|
|
166
|
+
return buildConfig(values, positionals, flags);
|
|
167
|
+
default:
|
|
168
|
+
return { command, flags };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
export function parseCli(argv) {
|
|
172
|
+
const { command, rest } = splitCommand(argv);
|
|
173
|
+
if (command === "help")
|
|
174
|
+
return { command: "help" };
|
|
175
|
+
if (command === "version")
|
|
176
|
+
return { command: "version" };
|
|
177
|
+
const name = command;
|
|
178
|
+
const { values, positionals } = parseCommandArgs(name, rest);
|
|
179
|
+
if (values.help === true)
|
|
180
|
+
return { command: "help" };
|
|
181
|
+
if (values.version === true)
|
|
182
|
+
return { command: "version" };
|
|
183
|
+
return buildCommand(name, values, positionals);
|
|
184
|
+
}
|
|
185
|
+
const HOME_DERIVED_ENV = ["VIDEOBOOK_LIBRARY_DIR", "VIDEOBOOK_APP_STATE_PATH", "VIDEOBOOK_ENV_PATH"];
|
|
186
|
+
function applyHome(home, env, cwd) {
|
|
187
|
+
env.VIDEOBOOK_HOME = resolve(cwd, home);
|
|
188
|
+
for (const name of HOME_DERIVED_ENV)
|
|
189
|
+
delete env[name];
|
|
190
|
+
}
|
|
191
|
+
export function applyFlagsToEnv(flags, env, cwd) {
|
|
192
|
+
if (flags.home !== undefined)
|
|
193
|
+
applyHome(flags.home, env, cwd);
|
|
194
|
+
if (flags.library !== undefined)
|
|
195
|
+
env.VIDEOBOOK_LIBRARY_DIR = resolve(cwd, flags.library);
|
|
196
|
+
if (flags.port !== undefined)
|
|
197
|
+
env.PORT = String(flags.port);
|
|
198
|
+
if (flags.logLevel !== undefined)
|
|
199
|
+
env.VIDEOBOOK_LOG_LEVEL = flags.logLevel;
|
|
200
|
+
}
|
|
201
|
+
export function resolvePort(env) {
|
|
202
|
+
const raw = env.PORT?.trim();
|
|
203
|
+
const port = raw ? Number(raw) : 3001;
|
|
204
|
+
return Number.isInteger(port) && port >= 0 && port <= 65535 ? port : 3001;
|
|
205
|
+
}
|
|
206
|
+
export function helpText() {
|
|
207
|
+
return [
|
|
208
|
+
"Usage: videobook [command] [options]",
|
|
209
|
+
"",
|
|
210
|
+
"Commands:",
|
|
211
|
+
" serve Run the MCP server with the web UI (default)",
|
|
212
|
+
" mcp Serve MCP over stdio for agent hosts such as Claude Code",
|
|
213
|
+
" run <tool> [k=v] Call one MCP tool and print its result",
|
|
214
|
+
" tools List the MCP tools and their parameters",
|
|
215
|
+
" gc-books Reclaim catalog storage in every book",
|
|
216
|
+
" doctor Check Node, media binaries, native modules and keys",
|
|
217
|
+
" config [action] list | get <KEY> | set <KEY> <VALUE> | unset <KEY> in the data-home .env",
|
|
218
|
+
" setup 1password Read keys from a 1Password vault (--op-vault, --op-token)",
|
|
219
|
+
" version Print versions",
|
|
220
|
+
" help Print this help",
|
|
221
|
+
"",
|
|
222
|
+
"Options:",
|
|
223
|
+
" --port <n> HTTP port on 127.0.0.1 (default 3001, or PORT)",
|
|
224
|
+
" --library <dir> Book library directory (default <data home>/books)",
|
|
225
|
+
" --home <dir> Data home (default ~/.videobook, or VIDEOBOOK_HOME)",
|
|
226
|
+
" --log-level <l> silent, error, warn, info or debug",
|
|
227
|
+
" --headless serve: MCP and API only, no web UI (alias --no-ui)",
|
|
228
|
+
" --open serve: open the web UI in a browser",
|
|
229
|
+
" --no-wait run: do not wait for queued jobs",
|
|
230
|
+
" --timeout <s> run: seconds to wait for jobs (default 600)",
|
|
231
|
+
" --json doctor, config: print machine-readable results",
|
|
232
|
+
" --op-vault <name> setup 1password: vault whose items are named after the keys",
|
|
233
|
+
" --op-token <tok> setup 1password: service-account token, best as env:VAR",
|
|
234
|
+
" --no-verify setup 1password: skip the op item list check",
|
|
235
|
+
" --remove setup 1password: remove the saved 1Password settings",
|
|
236
|
+
" -v, --version Print versions",
|
|
237
|
+
" -h, --help Print this help",
|
|
238
|
+
"",
|
|
239
|
+
"Agent hosts: claude mcp add videobook -- videobook mcp",
|
|
240
|
+
"",
|
|
241
|
+
].join("\n");
|
|
242
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { hasControlCharacters, readEnvValues, updateEnvFile } from "../env-file.js";
|
|
2
|
+
import { MANAGED_ENV_KEYS, getDefaultSecretStore, isSecretEnvKey, maskEnvValue, } from "../env-secrets.js";
|
|
3
|
+
import { OP_TOKEN_SETTING, OP_VAULT_SETTING, isOpReference, vaultReference } from "../one-password.js";
|
|
4
|
+
const SETTING_KEYS = [OP_VAULT_SETTING, OP_TOKEN_SETTING];
|
|
5
|
+
const CONFIG_KEYS = [...MANAGED_ENV_KEYS, ...SETTING_KEYS];
|
|
6
|
+
const TOKEN_VARIABLE_FORM = /^(env:|\$)?[A-Z_][A-Z0-9_]*$/u;
|
|
7
|
+
function isSecret(key) {
|
|
8
|
+
if (key === OP_VAULT_SETTING)
|
|
9
|
+
return false;
|
|
10
|
+
if (key === OP_TOKEN_SETTING)
|
|
11
|
+
return true;
|
|
12
|
+
return isSecretEnvKey(key);
|
|
13
|
+
}
|
|
14
|
+
function displayValue(key, value) {
|
|
15
|
+
if (isOpReference(value))
|
|
16
|
+
return value.trim();
|
|
17
|
+
if (key === OP_TOKEN_SETTING && TOKEN_VARIABLE_FORM.test(value))
|
|
18
|
+
return value;
|
|
19
|
+
return isSecret(key) ? maskEnvValue(value) : value;
|
|
20
|
+
}
|
|
21
|
+
function configuredVault(file) {
|
|
22
|
+
return process.env[OP_VAULT_SETTING]?.trim() || file[OP_VAULT_SETTING]?.trim() || null;
|
|
23
|
+
}
|
|
24
|
+
function configRow(key, file) {
|
|
25
|
+
const shell = process.env[key]?.trim();
|
|
26
|
+
if (shell)
|
|
27
|
+
return { key, source: "environment", value: displayValue(key, shell) };
|
|
28
|
+
const stored = file[key]?.trim();
|
|
29
|
+
if (stored)
|
|
30
|
+
return { key, source: isOpReference(stored) ? "1password reference" : ".env", value: displayValue(key, stored) };
|
|
31
|
+
const vault = configuredVault(file);
|
|
32
|
+
if (vault && !SETTING_KEYS.includes(key))
|
|
33
|
+
return { key, source: "1password vault", value: vaultReference(vault, key) };
|
|
34
|
+
return { key, source: "missing", value: null };
|
|
35
|
+
}
|
|
36
|
+
function vaultNote(rows, vault) {
|
|
37
|
+
if (!vault || !rows.some((row) => row.source === "1password vault"))
|
|
38
|
+
return "";
|
|
39
|
+
return `\nKeys marked "1password vault" come from ${vault} when it has an item with that name; videobook doctor shows which do.\n`;
|
|
40
|
+
}
|
|
41
|
+
function printRows(rows, file, json, envPath) {
|
|
42
|
+
const vault = configuredVault(file);
|
|
43
|
+
if (json) {
|
|
44
|
+
process.stdout.write(`${JSON.stringify({ envPath, vault, keys: rows }, null, 2)}\n`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const lines = rows.map((row) => `${row.key.padEnd(28)} ${row.source.padEnd(20)} ${row.value ?? ""}`.trimEnd());
|
|
48
|
+
process.stdout.write(`${lines.join("\n")}\n${vaultNote(rows, vault)}`);
|
|
49
|
+
}
|
|
50
|
+
function knownKey(key) {
|
|
51
|
+
if (CONFIG_KEYS.includes(key))
|
|
52
|
+
return true;
|
|
53
|
+
process.stderr.write(`${key} is not a videobook setting. Known keys: ${CONFIG_KEYS.join(", ")}\n`);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
function invalidValue(key, value) {
|
|
57
|
+
if (!value || hasControlCharacters(value))
|
|
58
|
+
return `${key} needs a non-empty value without control characters`;
|
|
59
|
+
if (value.includes("'") && value.includes("\""))
|
|
60
|
+
return `${key} cannot contain both single and double quotes`;
|
|
61
|
+
if (process.env[key]?.trim())
|
|
62
|
+
return `${key} is set in the environment, which overrides the .env file; change it where it is exported instead`;
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
async function setKey(key, raw, envPath) {
|
|
66
|
+
const value = raw.trim();
|
|
67
|
+
const problem = invalidValue(key, value);
|
|
68
|
+
if (problem) {
|
|
69
|
+
process.stderr.write(`${problem}\n`);
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
await updateEnvFile(envPath, { [key]: value });
|
|
73
|
+
process.stdout.write(`Set ${key}=${displayValue(key, value)} in ${envPath}\n`);
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
async function unsetKey(key, envPath) {
|
|
77
|
+
const present = key in readEnvValues(envPath);
|
|
78
|
+
if (present)
|
|
79
|
+
await updateEnvFile(envPath, { [key]: null });
|
|
80
|
+
process.stdout.write(present ? `Removed ${key} from ${envPath}\n` : `${key} was not set in ${envPath}\n`);
|
|
81
|
+
if (process.env[key]?.trim())
|
|
82
|
+
process.stderr.write(`Note: ${key} is still set in the environment.\n`);
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
export async function runConfigCommand(command) {
|
|
86
|
+
const envPath = getDefaultSecretStore().envPath();
|
|
87
|
+
if (command.action === "list") {
|
|
88
|
+
const file = readEnvValues(envPath);
|
|
89
|
+
printRows(CONFIG_KEYS.map((key) => configRow(key, file)), file, command.flags.json, envPath);
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
const key = command.key ?? "";
|
|
93
|
+
if (!knownKey(key))
|
|
94
|
+
return 1;
|
|
95
|
+
if (command.action === "get") {
|
|
96
|
+
const file = readEnvValues(envPath);
|
|
97
|
+
printRows([configRow(key, file)], file, command.flags.json, envPath);
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
if (command.action === "set")
|
|
101
|
+
return setKey(key, command.value ?? "", envPath);
|
|
102
|
+
return unsetKey(key, envPath);
|
|
103
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { access, constants, readdir } from "node:fs/promises";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { SCHEMA_VERSION } from "videobook-engine/catalog-metadata";
|
|
7
|
+
import { resolvePort } from "./args.js";
|
|
8
|
+
import { dataLayout } from "../data-home.js";
|
|
9
|
+
import { bookManager } from "../engine.js";
|
|
10
|
+
import { MANAGED_ENV_KEYS, getDefaultSecretStore } from "../env-secrets.js";
|
|
11
|
+
import { FFMPEG } from "../ffmpeg-binary.js";
|
|
12
|
+
import { FFPROBE, bundledFfprobe } from "../ffprobe-binary.js";
|
|
13
|
+
import { inspectServerOwner } from "../server-lock.js";
|
|
14
|
+
import { readOnePasswordSettings } from "../one-password.js";
|
|
15
|
+
import { remotionBrowserDir, remotionCacheRoot } from "../remotion/browser.js";
|
|
16
|
+
import { resolveYtdlp } from "../ytdlp-binary.js";
|
|
17
|
+
const execFileAsync = promisify(execFile);
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
const UUID_V7 = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
|
|
20
|
+
const ALLOW_SCRIPTS = "ffmpeg-static,ytdlp-nodejs,@dolthub/doltlite,usearch,onnxruntime-node,esbuild";
|
|
21
|
+
const INSTALL_REMEDY = `Reinstall with install scripts allowed: npm install -g videobook --allow-scripts=${ALLOW_SCRIPTS}`;
|
|
22
|
+
function check(fields) {
|
|
23
|
+
return fields;
|
|
24
|
+
}
|
|
25
|
+
function message(error) {
|
|
26
|
+
return error instanceof Error ? error.message.split("\n")[0] ?? error.message : String(error);
|
|
27
|
+
}
|
|
28
|
+
function nodeCheck() {
|
|
29
|
+
const engines = require("../../package.json").engines?.node ?? ">=22";
|
|
30
|
+
const minimum = Number(/(\d+)/u.exec(engines)?.[1] ?? 22);
|
|
31
|
+
const major = Number(process.versions.node.split(".")[0]);
|
|
32
|
+
return check({
|
|
33
|
+
id: "node",
|
|
34
|
+
label: "Node.js",
|
|
35
|
+
status: major >= minimum ? "ok" : "fail",
|
|
36
|
+
detail: `${process.version} (needs ${engines})`,
|
|
37
|
+
...(major >= minimum ? {} : { remedy: `Install Node.js ${minimum} or newer.` }),
|
|
38
|
+
required: true,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async function nearestExisting(path) {
|
|
42
|
+
let current = path;
|
|
43
|
+
for (;;) {
|
|
44
|
+
try {
|
|
45
|
+
await access(current);
|
|
46
|
+
return current;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
const parent = dirname(current);
|
|
50
|
+
if (parent === current)
|
|
51
|
+
return current;
|
|
52
|
+
current = parent;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function dataHomeCheck() {
|
|
57
|
+
const layout = dataLayout();
|
|
58
|
+
const kind = layout.kind === "checkout" ? "source checkout" : process.env.VIDEOBOOK_HOME?.trim() ? "VIDEOBOOK_HOME" : "default";
|
|
59
|
+
const target = await nearestExisting(layout.root);
|
|
60
|
+
try {
|
|
61
|
+
await access(target, constants.W_OK);
|
|
62
|
+
return check({ id: "data-home", label: "Data home", status: "ok", detail: `${layout.root} (${kind}); .env ${layout.envPath}`, required: true });
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return check({
|
|
66
|
+
id: "data-home",
|
|
67
|
+
label: "Data home",
|
|
68
|
+
status: "fail",
|
|
69
|
+
detail: `${layout.root} is not writable (${kind})`,
|
|
70
|
+
remedy: "Set VIDEOBOOK_HOME or pass --home to a writable directory.",
|
|
71
|
+
required: true,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function libraryCheck() {
|
|
76
|
+
const libraryDir = bookManager.rootDir;
|
|
77
|
+
try {
|
|
78
|
+
const books = (await readdir(libraryDir, { withFileTypes: true })).filter((entry) => entry.isDirectory() && UUID_V7.test(entry.name));
|
|
79
|
+
return check({ id: "library", label: "Library", status: "ok", detail: `${libraryDir}: ${books.length} book(s), schema ${SCHEMA_VERSION}`, required: false });
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return check({ id: "library", label: "Library", status: "ok", detail: `${libraryDir} is created on first start (schema ${SCHEMA_VERSION})`, required: false });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function bundledPath(moduleName) {
|
|
86
|
+
try {
|
|
87
|
+
const value = require(moduleName);
|
|
88
|
+
return typeof value === "string" ? value : value?.path ?? null;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function binaryCheck(id, label, binary, bundled, envName) {
|
|
95
|
+
const origin = binary === bundled ? "bundled" : binary.includes("/") ? envName : "from PATH";
|
|
96
|
+
try {
|
|
97
|
+
const { stdout } = await execFileAsync(binary, ["-version"], { timeout: 15_000 });
|
|
98
|
+
return check({ id, label, status: "ok", detail: `${stdout.split("\n")[0]} (${origin}: ${binary})`, required: true });
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
return check({
|
|
102
|
+
id,
|
|
103
|
+
label,
|
|
104
|
+
status: "fail",
|
|
105
|
+
detail: `${binary} did not run: ${message(error)}`,
|
|
106
|
+
remedy: `${INSTALL_REMEDY}, install ${label} on PATH, or set ${envName}.`,
|
|
107
|
+
required: true,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function ytdlpCheck() {
|
|
112
|
+
const binary = resolveYtdlp();
|
|
113
|
+
if (binary) {
|
|
114
|
+
const origin = binary.source === "PATH" ? "from PATH" : binary.source;
|
|
115
|
+
return check({ id: "yt-dlp", label: "yt-dlp", status: "ok", detail: `${binary.path} (${origin})`, required: false });
|
|
116
|
+
}
|
|
117
|
+
return check({
|
|
118
|
+
id: "yt-dlp",
|
|
119
|
+
label: "yt-dlp",
|
|
120
|
+
status: "warn",
|
|
121
|
+
detail: "not found; download_video and archive_clip from URLs will fail",
|
|
122
|
+
remedy: `${INSTALL_REMEDY}, install yt-dlp on PATH, or set VIDEOBOOK_YTDLP_PATH.`,
|
|
123
|
+
required: false,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function sharedObjects(pattern) {
|
|
127
|
+
const report = process.report.getReport();
|
|
128
|
+
return (report.sharedObjects ?? []).filter((name) => pattern.test(name));
|
|
129
|
+
}
|
|
130
|
+
function engineRequire() {
|
|
131
|
+
return createRequire(require.resolve("videobook-engine/package.json"));
|
|
132
|
+
}
|
|
133
|
+
function nativeCoreCheck() {
|
|
134
|
+
try {
|
|
135
|
+
const { DatabaseSync } = engineRequire()("@dolthub/doltlite");
|
|
136
|
+
const database = new DatabaseSync(":memory:");
|
|
137
|
+
const dolt = database.doltVersion();
|
|
138
|
+
database.close();
|
|
139
|
+
const sharp = require("sharp");
|
|
140
|
+
const copies = sharedObjects(/sharp[^/\\]*\.node$/u).length;
|
|
141
|
+
return check({
|
|
142
|
+
id: "native-runtime",
|
|
143
|
+
label: "Native modules",
|
|
144
|
+
status: copies > 1 ? "fail" : "ok",
|
|
145
|
+
detail: `DoltLite ${dolt}; Sharp ${sharp.versions.sharp ?? "?"} / libvips ${sharp.versions.vips ?? "?"}${copies > 1 ? `; ${copies} Sharp copies loaded` : ""}`,
|
|
146
|
+
...(copies > 1 ? { remedy: "Reinstall videobook so npm dedupes Sharp to one copy." } : {}),
|
|
147
|
+
required: true,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
return check({ id: "native-runtime", label: "Native modules", status: "fail", detail: message(error), remedy: INSTALL_REMEDY, required: true });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function searchRuntimeCheck() {
|
|
155
|
+
const fromEngine = engineRequire();
|
|
156
|
+
try {
|
|
157
|
+
fromEngine("onnxruntime-node");
|
|
158
|
+
fromEngine("usearch");
|
|
159
|
+
return check({ id: "search-runtime", label: "Semantic search", status: "ok", detail: "ONNX Runtime and USearch load", required: false });
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
return check({
|
|
163
|
+
id: "search-runtime",
|
|
164
|
+
label: "Semantic search",
|
|
165
|
+
status: "warn",
|
|
166
|
+
detail: `unavailable on ${process.platform}-${process.arch}: ${message(error)}`,
|
|
167
|
+
required: false,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function renderBrowserCheck() {
|
|
172
|
+
const root = remotionCacheRoot();
|
|
173
|
+
if (!root) {
|
|
174
|
+
return check({ id: "render-browser", label: "Render browser", status: "ok", detail: "managed by Remotion in this source checkout", required: false });
|
|
175
|
+
}
|
|
176
|
+
const directory = remotionBrowserDir(root);
|
|
177
|
+
try {
|
|
178
|
+
await access(join(directory, "chrome-headless-shell"));
|
|
179
|
+
return check({ id: "render-browser", label: "Render browser", status: "ok", detail: `Chrome Headless Shell in ${directory}`, required: false });
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return check({
|
|
183
|
+
id: "render-browser",
|
|
184
|
+
label: "Render browser",
|
|
185
|
+
status: "warn",
|
|
186
|
+
detail: `not downloaded yet; the first render downloads Chrome Headless Shell into ${directory}`,
|
|
187
|
+
required: false,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async function opVersion() {
|
|
192
|
+
try {
|
|
193
|
+
return (await execFileAsync("op", ["--version"], { timeout: 10_000 })).stdout.trim();
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function describeResolution(resolution) {
|
|
200
|
+
const filled = resolution.fromReferences.length + resolution.fromVault.length;
|
|
201
|
+
const vault = resolution.vault ? `vault ${resolution.vault}` : "op:// references";
|
|
202
|
+
const token = resolution.tokenSource ? `token ${resolution.tokenSource}` : "op's own sign-in";
|
|
203
|
+
return `${vault}, ${token}: ${filled} key(s) from 1Password`;
|
|
204
|
+
}
|
|
205
|
+
function unresolvedDetail(resolution) {
|
|
206
|
+
const failures = resolution.unresolved.map((item) => `${item.key} from ${item.reference} (${item.reason})`);
|
|
207
|
+
return [resolution.error, ...failures].filter((part) => Boolean(part)).join("; ");
|
|
208
|
+
}
|
|
209
|
+
async function onePasswordCheck() {
|
|
210
|
+
const resolution = getDefaultSecretStore().onePasswordResolution();
|
|
211
|
+
const version = await opVersion();
|
|
212
|
+
const cli = version ? `op ${version}` : "op not on PATH";
|
|
213
|
+
if (!resolution?.attempted) {
|
|
214
|
+
const tokenSource = readOnePasswordSettings(process.env).token.source;
|
|
215
|
+
const token = tokenSource ? `token ${tokenSource}, ` : "";
|
|
216
|
+
return check({ id: "one-password", label: "1Password", status: "ok", detail: `not used (${token}${cli}); to read keys from a vault: videobook setup 1password --op-vault <vault>`, required: false });
|
|
217
|
+
}
|
|
218
|
+
const problems = unresolvedDetail(resolution);
|
|
219
|
+
return check({
|
|
220
|
+
id: "one-password",
|
|
221
|
+
label: "1Password",
|
|
222
|
+
status: problems ? "warn" : "ok",
|
|
223
|
+
detail: `${describeResolution(resolution)}${problems ? `; unresolved: ${problems}` : ""} (${cli})`,
|
|
224
|
+
...(problems ? { remedy: "Check the references and the service-account token, or run videobook setup 1password again." } : {}),
|
|
225
|
+
required: false,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function providerKeysCheck() {
|
|
229
|
+
const store = getDefaultSecretStore();
|
|
230
|
+
const configured = MANAGED_ENV_KEYS.filter((key) => key.endsWith("_API_KEY") && store.getSource(key) !== "missing");
|
|
231
|
+
if (configured.length === 0) {
|
|
232
|
+
return check({
|
|
233
|
+
id: "provider-keys",
|
|
234
|
+
label: "Provider keys",
|
|
235
|
+
status: "warn",
|
|
236
|
+
detail: "none configured; generation and transcription tools will fail",
|
|
237
|
+
remedy: `Add keys such as OPENAI_API_KEY to ${dataLayout().envPath} or the environment.`,
|
|
238
|
+
required: false,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
const shown = configured.map((key) => `${key} (${store.getSource(key)})`);
|
|
242
|
+
return check({ id: "provider-keys", label: "Provider keys", status: "ok", detail: shown.join(", "), required: false });
|
|
243
|
+
}
|
|
244
|
+
async function ownerCheck(port) {
|
|
245
|
+
const owner = await inspectServerOwner(bookManager.rootDir);
|
|
246
|
+
if (owner.kind === "running") {
|
|
247
|
+
return check({ id: "owner", label: "Server", status: "ok", detail: `running at ${owner.record.url} (pid ${owner.record.pid})`, required: false });
|
|
248
|
+
}
|
|
249
|
+
if (owner.kind === "unresponsive") {
|
|
250
|
+
return check({ id: "owner", label: "Server", status: "warn", detail: `pid ${owner.record.pid} holds the library but does not answer at ${owner.record.url}`, required: false });
|
|
251
|
+
}
|
|
252
|
+
return check({ id: "owner", label: "Server", status: "ok", detail: `not running (videobook serve listens on 127.0.0.1:${port})`, required: false });
|
|
253
|
+
}
|
|
254
|
+
async function collectDoctorChecks() {
|
|
255
|
+
return [
|
|
256
|
+
nodeCheck(),
|
|
257
|
+
await dataHomeCheck(),
|
|
258
|
+
await libraryCheck(),
|
|
259
|
+
await binaryCheck("ffmpeg", "ffmpeg", FFMPEG, bundledPath("ffmpeg-static"), "VIDEOBOOK_FFMPEG_PATH"),
|
|
260
|
+
await binaryCheck("ffprobe", "ffprobe", FFPROBE, bundledFfprobe(), "VIDEOBOOK_FFPROBE_PATH"),
|
|
261
|
+
ytdlpCheck(),
|
|
262
|
+
nativeCoreCheck(),
|
|
263
|
+
searchRuntimeCheck(),
|
|
264
|
+
await renderBrowserCheck(),
|
|
265
|
+
await onePasswordCheck(),
|
|
266
|
+
providerKeysCheck(),
|
|
267
|
+
await ownerCheck(resolvePort(process.env)),
|
|
268
|
+
];
|
|
269
|
+
}
|
|
270
|
+
function statusLabel(status) {
|
|
271
|
+
return status === "ok" ? "ok " : status === "warn" ? "warn" : "FAIL";
|
|
272
|
+
}
|
|
273
|
+
function renderText(checks) {
|
|
274
|
+
const lines = checks.flatMap((item) => [
|
|
275
|
+
`${statusLabel(item.status)} ${item.label.padEnd(16)} ${item.detail}`,
|
|
276
|
+
...(item.remedy && item.status !== "ok" ? [` ${"".padEnd(16)} fix: ${item.remedy}`] : []),
|
|
277
|
+
]);
|
|
278
|
+
return `${lines.join("\n")}\n`;
|
|
279
|
+
}
|
|
280
|
+
function doctorExitCode(checks) {
|
|
281
|
+
return checks.some((item) => item.required && item.status === "fail") ? 1 : 0;
|
|
282
|
+
}
|
|
283
|
+
export async function runDoctorCommand(flags) {
|
|
284
|
+
getDefaultSecretStore().resolveOnePassword();
|
|
285
|
+
const checks = await collectDoctorChecks();
|
|
286
|
+
const code = doctorExitCode(checks);
|
|
287
|
+
process.stdout.write(flags.json ? `${JSON.stringify({ ok: code === 0, checks }, null, 2)}\n` : renderText(checks));
|
|
288
|
+
return code;
|
|
289
|
+
}
|