@gentbajko/slopify 3.25.9 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/account-limits.js +30 -0
- package/dist/adapters/alignment/audio.js +1 -1
- package/dist/adapters/alignment/cache.js +4 -6
- package/dist/adapters/alignment/ctc.js +1 -2
- package/dist/adapters/alignment/lock.js +4 -3
- package/dist/adapters/alignment/multilingual.js +2 -4
- package/dist/adapters/alignment/prefetch.js +2 -1
- package/dist/adapters/alignment/runner.js +1 -1
- package/dist/adapters/alignment/worker.js +3 -2
- package/dist/adapters/explain.js +10 -12
- package/dist/adapters/ffmpeg.js +19 -1
- package/dist/adapters/host-cli/index.js +21 -4
- package/dist/adapters/host-cli/transport.js +2 -1
- package/dist/adapters/http-failure.js +53 -0
- package/dist/adapters/image/bytes.js +2 -1
- package/dist/adapters/image/codex.js +42 -9
- package/dist/adapters/image/fal.js +21 -53
- package/dist/adapters/image/google.js +33 -56
- package/dist/adapters/image/models.js +2 -2
- package/dist/adapters/image/openai.js +22 -48
- package/dist/adapters/image/reference.js +36 -0
- package/dist/adapters/image/replicate.js +21 -49
- package/dist/adapters/image/video.js +2 -1
- package/dist/adapters/interest/search-interest.js +113 -0
- package/dist/adapters/key-probes.js +54 -3
- package/dist/adapters/llm/antigravity-models.js +1 -1
- package/dist/adapters/llm/antigravity.js +11 -6
- package/dist/adapters/llm/assistant-cli.js +388 -0
- package/dist/adapters/llm/child-session.js +83 -0
- package/dist/adapters/llm/claude-code-models.js +52 -99
- package/dist/adapters/llm/claude-code.js +16 -4
- package/dist/adapters/llm/codex-limits.js +27 -68
- package/dist/adapters/llm/codex-models.js +1 -1
- package/dist/adapters/llm/codex-server-models.js +61 -109
- package/dist/adapters/llm/codex.js +12 -1
- package/dist/adapters/llm/document-workspace.js +3 -2
- package/dist/adapters/llm/image-workspace.js +3 -2
- package/dist/adapters/llm/openrouter.js +12 -48
- package/dist/adapters/llm/run-cli.js +9 -24
- package/dist/adapters/llm/sse-lines.js +5 -1
- package/dist/adapters/model-access.js +37 -0
- package/dist/adapters/tts/cartesia.js +15 -46
- package/dist/adapters/tts/elevenlabs.js +20 -52
- package/dist/adapters/tts/gemini.js +21 -41
- package/dist/adapters/tts/inworld-async.js +6 -2
- package/dist/adapters/tts/inworld.js +6 -2
- package/dist/adapters/tts/openai.js +20 -50
- package/dist/adapters/tts/system.js +2 -1
- package/dist/assets/models-lifecycle.yaml +6 -0
- package/dist/assistant-wiring.js +60 -0
- package/dist/autopilot-timers.js +59 -0
- package/dist/catalog/lifecycle.js +52 -0
- package/dist/catalog/propose.js +204 -0
- package/dist/catalog/registry.js +23 -14
- package/dist/catalog/schema.js +4 -1
- package/dist/catalog/store.js +39 -0
- package/dist/catalog/validate.js +5 -2
- package/dist/edge/autostart/native.js +3 -5
- package/dist/edge/autostart/prompt.js +2 -1
- package/dist/edge/autostart/service.js +3 -4
- package/dist/edge/cli-args.js +3 -4
- package/dist/edge/cli.js +2 -1
- package/dist/edge/docker-install/apply.js +11 -11
- package/dist/edge/docker-install/run.js +2 -1
- package/dist/edge/docker-install/state.js +4 -3
- package/dist/edge/docker-install/tree.js +2 -4
- package/dist/edge/docker-install/volume.js +3 -2
- package/dist/edge/docker.js +5 -4
- package/dist/edge/events/hub.js +5 -5
- package/dist/edge/http/actions.js +11 -32
- package/dist/edge/http/actor.js +5 -3
- package/dist/edge/http/app.js +86 -11
- package/dist/edge/http/assistant.js +129 -0
- package/dist/edge/http/audio-preview.js +4 -11
- package/dist/edge/http/auditions.js +1 -1
- package/dist/edge/http/autostart.js +5 -5
- package/dist/edge/http/backups.js +2 -4
- package/dist/edge/http/channel-memory.js +9 -22
- package/dist/edge/http/channels.js +14 -27
- package/dist/edge/http/checkpoints.js +39 -21
- package/dist/edge/http/design-templates.js +167 -0
- package/dist/edge/http/designs.js +178 -0
- package/dist/edge/http/diagnostics.js +5 -1
- package/dist/edge/http/document-themes.js +5 -14
- package/dist/edge/http/draft-files.js +1 -3
- package/dist/edge/http/draft-problem.js +5 -10
- package/dist/edge/http/drafts.js +3 -3
- package/dist/edge/http/entries.js +6 -9
- package/dist/edge/http/extension-auth.js +27 -0
- package/dist/edge/http/external-reviews.js +12 -11
- package/dist/edge/http/files.js +5 -11
- package/dist/edge/http/folder-location.js +1 -4
- package/dist/edge/http/fonts.js +0 -7
- package/dist/edge/http/home.js +47 -14
- package/dist/edge/http/host-cli.js +18 -8
- package/dist/edge/http/ids.js +8 -0
- package/dist/edge/http/keywords.js +35 -0
- package/dist/edge/http/model-checks.js +132 -0
- package/dist/edge/http/narration-peaks.js +5 -13
- package/dist/edge/http/onboarding.js +30 -26
- package/dist/edge/http/open-folder.js +5 -10
- package/dist/edge/http/outputs.js +7 -14
- package/dist/edge/http/patch-notes.js +6 -12
- package/dist/edge/http/planning.js +3 -7
- package/dist/edge/http/problem.js +2 -4
- package/dist/edge/http/project-create.js +2 -1
- package/dist/edge/http/project-templates.js +31 -19
- package/dist/edge/http/projects.js +24 -16
- package/dist/edge/http/prompts.js +9 -16
- package/dist/edge/http/pronunciations.js +1 -2
- package/dist/edge/http/providers.js +48 -34
- package/dist/edge/http/refusal.js +29 -0
- package/dist/edge/http/remote-access.js +40 -0
- package/dist/edge/http/remote-guard.js +71 -0
- package/dist/edge/http/request-guard.js +91 -0
- package/dist/edge/http/reviews.js +12 -25
- package/dist/edge/http/revision-files.js +6 -11
- package/dist/edge/http/revisions.js +14 -19
- package/dist/edge/http/run-cost.js +1 -2
- package/dist/edge/http/same-page.js +80 -0
- package/dist/edge/http/schedules.js +69 -40
- package/dist/edge/http/settings.js +27 -14
- package/dist/edge/http/social.js +283 -0
- package/dist/edge/http/staging.js +3 -5
- package/dist/edge/http/storage-files.js +5 -7
- package/dist/edge/http/storage.js +33 -25
- package/dist/edge/http/studio.js +94 -69
- package/dist/edge/http/style-preview.js +3 -6
- package/dist/edge/http/subtitles.js +3 -17
- package/dist/edge/http/template-studio.js +436 -0
- package/dist/edge/http/trash.js +11 -12
- package/dist/edge/http/tutorial.js +1 -2
- package/dist/edge/http/tutorials.js +5 -11
- package/dist/edge/http/update.js +7 -13
- package/dist/edge/http/waveform.js +3 -6
- package/dist/edge/http/youtube-edits.js +3 -4
- package/dist/edge/mcp/assistant.js +279 -0
- package/dist/edge/mcp/attribution.js +8 -2
- package/dist/edge/mcp/call-route.js +11 -2
- package/dist/edge/mcp/excluded-routes.js +29 -3
- package/dist/edge/mcp/http.js +11 -2
- package/dist/edge/mcp/instructions.js +6 -4
- package/dist/edge/mcp/library-tools.js +9 -5
- package/dist/edge/mcp/remake-tools.js +7 -4
- package/dist/edge/mcp/route-tool.js +35 -15
- package/dist/edge/mcp/server.js +13 -8
- package/dist/edge/mcp/tools-channels.js +11 -9
- package/dist/edge/mcp/tools-designs.js +165 -0
- package/dist/edge/mcp/tools-library.js +19 -7
- package/dist/edge/mcp/tools-planning.js +40 -21
- package/dist/edge/mcp/tools-projects.js +38 -11
- package/dist/edge/mcp/tools-publishing.js +9 -9
- package/dist/edge/mcp/tools-runs.js +23 -10
- package/dist/edge/mcp/tools-schedules.js +32 -8
- package/dist/edge/mcp/tools-setup.js +75 -11
- package/dist/edge/mcp/tools-social.js +47 -0
- package/dist/edge/mcp/tools-storage.js +11 -3
- package/dist/edge/mcp/tools-template-studio.js +166 -0
- package/dist/edge/mcp/tools.js +6 -0
- package/dist/edge/remote-access/cli.js +71 -0
- package/dist/edge/remote-access/model.js +32 -0
- package/dist/edge/remote-access/rules.js +207 -0
- package/dist/edge/remote-access/service.js +203 -0
- package/dist/edge/remote-access/store.js +44 -0
- package/dist/edge/remote-access/views.js +43 -0
- package/dist/extension/slopify-studio-chrome.zip +0 -0
- package/dist/extension/slopify-studio-firefox.zip +0 -0
- package/dist/host-cli/install.js +2 -2
- package/dist/host-cli/open-folder.js +2 -2
- package/dist/host-cli/paths.js +2 -4
- package/dist/host-cli/server.js +4 -4
- package/dist/host-cli/service.js +3 -3
- package/dist/host-cli/status.js +7 -7
- package/dist/kernel/db/migrations/0063-model-checks.sql +20 -0
- package/dist/kernel/db/migrations/0064-template-studio.sql +46 -0
- package/dist/kernel/db/migrations/0065-promise-and-assistant-reviews.sql +58 -0
- package/dist/kernel/db/migrations/0066-assistant.sql +44 -0
- package/dist/kernel/db/migrations/0067-autopilot.sql +6 -0
- package/dist/kernel/db/migrations/0068-topic-scores.sql +17 -0
- package/dist/kernel/db/migrations/0070-designs.sql +53 -0
- package/dist/kernel/db/migrations/0071-design-usage.sql +33 -0
- package/dist/kernel/db/migrations/0072-design-templates.sql +33 -0
- package/dist/kernel/db/migrations/0073-schedule-design-kind.sql +5 -0
- package/dist/kernel/db/migrations/0074-template-bakeoffs.sql +44 -0
- package/dist/kernel/db/migrations/0075-social-posts.sql +25 -0
- package/dist/kernel/db/migrations/0076-channel-social.sql +5 -0
- package/dist/kernel/db/migrations/0077-post-prompts.sql +17 -0
- package/dist/kernel/db/migrations/0078-uploaded-from-links.sql +7 -0
- package/dist/kernel/db/tx.js +2 -4
- package/dist/kernel/errors.js +27 -1
- package/dist/kernel/json-answer.js +15 -0
- package/dist/kernel/lock.js +4 -9
- package/dist/kernel/paths.js +8 -0
- package/dist/kernel/pipeline.js +12 -0
- package/dist/kernel/ports/assistant-cli.js +4 -0
- package/dist/kernel/ports/host-cli.js +3 -0
- package/dist/kernel/ports/interest.js +4 -0
- package/dist/kernel/ports/languages.js +0 -3
- package/dist/kernel/ports/model.js +2 -0
- package/dist/kernel/ports/settings-places.js +31 -0
- package/dist/kernel/ports/system-speech.js +31 -3
- package/dist/kernel/runner/attempt.js +14 -4
- package/dist/kernel/runner/audition.js +3 -1
- package/dist/kernel/runner/checkpoint-authority.js +2 -1
- package/dist/kernel/runner/index.js +8 -10
- package/dist/kernel/runner/model-access.js +40 -0
- package/dist/kernel/runner/model-check.js +153 -0
- package/dist/kernel/runner/providers.js +18 -12
- package/dist/kernel/runner/standalone.js +11 -7
- package/dist/main.js +338 -21
- package/dist/model-catalog.js +20 -0
- package/dist/model-checks.js +166 -0
- package/dist/patch-notes/4.0.0.md +51 -0
- package/dist/patch-notes/4.1.0.md +42 -0
- package/dist/patch-notes/index.json +12 -0
- package/dist/slices/admission/heads.js +2 -1
- package/dist/slices/admission/listing-facts.js +95 -0
- package/dist/slices/admission/repo.js +11 -7
- package/dist/slices/admission/rules.js +50 -15
- package/dist/slices/admission/schema.js +8 -0
- package/dist/slices/admission/stage-writes.js +0 -3
- package/dist/slices/admission/start.js +48 -31
- package/dist/slices/admission/state-words.js +34 -5
- package/dist/slices/admission/substitute.js +7 -4
- package/dist/slices/agents/attention-posts.js +31 -0
- package/dist/slices/agents/attention-templates.js +33 -0
- package/dist/slices/agents/attention.js +10 -1
- package/dist/slices/agents/connection.js +2 -6
- package/dist/slices/agents/model.js +6 -3
- package/dist/slices/agents/repo.js +15 -7
- package/dist/slices/article/continuation.js +0 -63
- package/dist/slices/article/plain.js +3 -2
- package/dist/slices/article/segments.js +2 -62
- package/dist/slices/assistant/access.js +1 -0
- package/dist/slices/assistant/asks.js +80 -0
- package/dist/slices/assistant/cost.js +54 -0
- package/dist/slices/assistant/gate.js +222 -0
- package/dist/slices/assistant/model.js +86 -0
- package/dist/slices/assistant/names.js +23 -0
- package/dist/slices/assistant/prompt.js +74 -0
- package/dist/slices/assistant/repo.js +74 -0
- package/dist/slices/assistant/tokens.js +18 -0
- package/dist/slices/assistant/turn.js +344 -0
- package/dist/slices/autopilot/buffer.js +76 -0
- package/dist/slices/autopilot/model.js +14 -0
- package/dist/slices/autopilot/notify.js +27 -0
- package/dist/slices/autopilot/repo.js +34 -0
- package/dist/slices/autopilot/signals.js +194 -0
- package/dist/slices/autopilot/warnings.js +85 -0
- package/dist/slices/autopilot/weekly.js +100 -0
- package/dist/slices/backups/files.js +2 -6
- package/dist/slices/backups/repo.js +3 -15
- package/dist/slices/backups/service.js +8 -7
- package/dist/slices/channels/cast-images.js +3 -2
- package/dist/slices/channels/model.js +0 -1
- package/dist/slices/channels/rebrand.js +0 -3
- package/dist/slices/channels/repo.js +4 -0
- package/dist/slices/channels/schema.js +7 -1
- package/dist/slices/channels/service.js +10 -0
- package/dist/slices/channels/videos.js +3 -15
- package/dist/slices/checkpoints/index.js +0 -1
- package/dist/slices/control/providers.js +2 -28
- package/dist/slices/designs/artwork.js +66 -0
- package/dist/slices/designs/budget.js +23 -0
- package/dist/slices/designs/calls.js +124 -0
- package/dist/slices/designs/check-context.js +38 -0
- package/dist/slices/designs/check-text.js +43 -0
- package/dist/slices/designs/fields.js +88 -0
- package/dist/slices/designs/frames.js +34 -0
- package/dist/slices/designs/gate.js +130 -0
- package/dist/slices/designs/layout.js +293 -0
- package/dist/slices/designs/model.js +35 -0
- package/dist/slices/designs/pdf-job.js +23 -0
- package/dist/slices/designs/pdf-pages.js +30 -0
- package/dist/slices/designs/pdf-runner.js +68 -0
- package/dist/slices/designs/pdf-worker.js +24 -0
- package/dist/slices/designs/picture-size.js +79 -0
- package/dist/slices/designs/pictures.js +55 -0
- package/dist/slices/designs/quote.js +24 -0
- package/dist/slices/designs/render-args.js +36 -0
- package/dist/slices/designs/render-input.js +59 -0
- package/dist/slices/designs/render.js +215 -0
- package/dist/slices/designs/repo.js +101 -0
- package/dist/slices/designs/requests.js +80 -0
- package/dist/slices/designs/runner.js +239 -0
- package/dist/slices/designs/sample-repo.js +91 -0
- package/dist/slices/designs/sample.js +223 -0
- package/dist/slices/designs/schema.js +87 -0
- package/dist/slices/designs/sections.js +44 -0
- package/dist/slices/designs/service.js +233 -0
- package/dist/slices/designs/steps-repo.js +102 -0
- package/dist/slices/designs/studio-kind.js +39 -0
- package/dist/slices/designs/template-checks.js +44 -0
- package/dist/slices/designs/template-make.js +118 -0
- package/dist/slices/designs/templates.js +218 -0
- package/dist/slices/document/fonts.js +2 -1
- package/dist/slices/document/model.js +7 -4
- package/dist/slices/document/render.js +2 -1
- package/dist/slices/document/theme-schema.js +1 -1
- package/dist/slices/document/theme.js +49 -3
- package/dist/slices/episodes/summarize.js +6 -2
- package/dist/slices/estimate/index.js +72 -28
- package/dist/slices/estimate/requests.js +4 -4
- package/dist/slices/fixes/rules.js +3 -1
- package/dist/slices/fonts/catalog.js +5 -3
- package/dist/slices/fonts/coverage.js +2 -1
- package/dist/slices/fonts/discovery.js +3 -2
- package/dist/slices/fonts/files.js +2 -6
- package/dist/slices/fonts/index.js +0 -1
- package/dist/slices/fonts/sfnt.js +45 -10
- package/dist/slices/images/appearance.js +8 -16
- package/dist/slices/images/scenes.js +102 -28
- package/dist/slices/keywords/digest.js +85 -0
- package/dist/slices/keywords/facts.js +92 -0
- package/dist/slices/keywords/fill.js +80 -0
- package/dist/slices/keywords/model.js +75 -0
- package/dist/slices/keywords/rules.js +187 -0
- package/dist/slices/keywords/schema.js +28 -0
- package/dist/slices/keywords/specs.js +260 -0
- package/dist/slices/keywords/values.js +74 -0
- package/dist/slices/library/lint.js +29 -12
- package/dist/slices/library/model.js +2 -0
- package/dist/slices/library/photorealistic.js +4 -12
- package/dist/slices/library/rename.js +9 -7
- package/dist/slices/library/save.js +14 -10
- package/dist/slices/library/slots.js +37 -11
- package/dist/slices/library/transfer.js +5 -4
- package/dist/slices/library/used-by.js +25 -10
- package/dist/slices/loudness/loudnorm.js +53 -12
- package/dist/slices/loudness/model.js +3 -2
- package/dist/slices/loudness/sentence-level.js +2 -1
- package/dist/slices/model-checks/cost.js +61 -0
- package/dist/slices/model-checks/listing.js +124 -0
- package/dist/slices/model-checks/model.js +20 -0
- package/dist/slices/model-checks/record.js +32 -0
- package/dist/slices/model-checks/repo.js +120 -0
- package/dist/slices/model-checks/rules.js +125 -0
- package/dist/slices/model-checks/run.js +143 -0
- package/dist/slices/model-checks/service.js +198 -0
- package/dist/slices/model-upkeep/model.js +1 -1
- package/dist/slices/model-upkeep/switch.js +72 -27
- package/dist/slices/model-upkeep/usage.js +39 -13
- package/dist/slices/narration/breaths.js +3 -1
- package/dist/slices/narration/clean-pieces.js +3 -1
- package/dist/slices/narration/concat.js +2 -1
- package/dist/slices/narration/pauses-model.js +3 -2
- package/dist/slices/narration/peaks.js +3 -4
- package/dist/slices/narration/pronunciation-chunks.js +4 -3
- package/dist/slices/narration/pronunciation.js +6 -4
- package/dist/slices/narration/steering.js +4 -3
- package/dist/slices/notifications/notifier.js +4 -3
- package/dist/slices/notifications/rules.js +5 -2
- package/dist/slices/notifications/settings.js +4 -12
- package/dist/slices/notifications/subject.js +4 -3
- package/dist/slices/onboarding/copy.js +21 -3
- package/dist/slices/onboarding/first-run.js +16 -40
- package/dist/slices/onboarding/full-video.js +3 -2
- package/dist/slices/onboarding/install.js +6 -4
- package/dist/slices/onboarding/model.js +26 -37
- package/dist/slices/onboarding/packs.js +22 -22
- package/dist/slices/onboarding/preferred-model.js +14 -0
- package/dist/slices/onboarding/quick-short.js +105 -65
- package/dist/slices/onboarding/sample.js +4 -2
- package/dist/slices/onboarding/state.js +9 -11
- package/dist/slices/onboarding/template.js +8 -6
- package/dist/slices/outputs/add.js +3 -2
- package/dist/slices/outputs/service.js +2 -2
- package/dist/slices/patch-notes/library.js +4 -3
- package/dist/slices/play-drafts/convert.js +4 -0
- package/dist/slices/play-drafts/readiness.js +3 -3
- package/dist/slices/play-drafts/review-inputs.js +9 -3
- package/dist/slices/play-drafts/review.js +5 -4
- package/dist/slices/play-drafts/schema.js +4 -0
- package/dist/slices/play-drafts/service.js +17 -7
- package/dist/slices/project-templates/from-project.js +9 -2
- package/dist/slices/project-templates/next-chapter.js +2 -1
- package/dist/slices/project-templates/provider-defaults.js +22 -0
- package/dist/slices/project-templates/repo.js +6 -1
- package/dist/slices/project-templates/setup-sections.js +26 -0
- package/dist/slices/project-templates/setup.js +14 -1
- package/dist/slices/project-templates/stand-ins.js +22 -0
- package/dist/slices/rebuild/activity.js +2 -0
- package/dist/slices/rebuild/admission-repo.js +3 -2
- package/dist/slices/rebuild/narration-history.js +4 -5
- package/dist/slices/rebuild/narration-retry.js +2 -1
- package/dist/slices/rebuild/preview-details.js +32 -14
- package/dist/slices/rebuild/preview-plan.js +3 -8
- package/dist/slices/rebuild/recipe-build.js +8 -3
- package/dist/slices/rebuild/recipe-describe.js +3 -3
- package/dist/slices/rebuild/recipe-digest.js +58 -0
- package/dist/slices/rebuild/recipe-input-schema.js +10 -0
- package/dist/slices/rebuild/recipe-keywords.js +71 -0
- package/dist/slices/rebuild/recipe-legacy.js +5 -1
- package/dist/slices/rebuild/recipe-model.js +4 -0
- package/dist/slices/rebuild/recipe-narration-text.js +2 -1
- package/dist/slices/rebuild/recipe-preparation.js +4 -4
- package/dist/slices/rebuild/recipe-reference.js +8 -4
- package/dist/slices/rebuild/recipe-reviews.js +27 -7
- package/dist/slices/rebuild/recipe-save.js +8 -5
- package/dist/slices/rebuild/recipe-scenes.js +40 -11
- package/dist/slices/rebuild/recipe-shorts.js +8 -8
- package/dist/slices/rebuild/recipe-social.js +46 -0
- package/dist/slices/rebuild/recipe-text.js +64 -20
- package/dist/slices/rebuild/recipe-validation.js +102 -31
- package/dist/slices/rebuild/recipe-visual.js +55 -16
- package/dist/slices/rebuild/recipe-voices.js +3 -2
- package/dist/slices/rebuild/recipe-work.js +19 -7
- package/dist/slices/rebuild/recipe-youtube.js +4 -3
- package/dist/slices/rebuild/recovery-selection.js +3 -1
- package/dist/slices/rebuild/recovery.js +9 -8
- package/dist/slices/rebuild/remake-outdated.js +5 -5
- package/dist/slices/rebuild/repo.js +4 -3
- package/dist/slices/rebuild/review-promise.js +25 -0
- package/dist/slices/rebuild/review-redo.js +3 -2
- package/dist/slices/rebuild/runtime-admission.js +4 -3
- package/dist/slices/rebuild/runtime-agent-review.js +11 -5
- package/dist/slices/rebuild/runtime-animate.js +10 -9
- package/dist/slices/rebuild/runtime-article.js +7 -5
- package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
- package/dist/slices/rebuild/runtime-document.js +11 -13
- package/dist/slices/rebuild/runtime-export-bed.js +8 -12
- package/dist/slices/rebuild/runtime-export-edit.js +8 -16
- package/dist/slices/rebuild/runtime-export-inputs.js +12 -11
- package/dist/slices/rebuild/runtime-export-short.js +9 -5
- package/dist/slices/rebuild/runtime-export.js +38 -15
- package/dist/slices/rebuild/runtime-figure-card.js +2 -1
- package/dist/slices/rebuild/runtime-image.js +8 -12
- package/dist/slices/rebuild/runtime-instructions.js +2 -0
- package/dist/slices/rebuild/runtime-keywords.js +59 -0
- package/dist/slices/rebuild/runtime-lines.js +2 -11
- package/dist/slices/rebuild/runtime-local.js +17 -22
- package/dist/slices/rebuild/runtime-narration-publication.js +2 -5
- package/dist/slices/rebuild/runtime-narration-reuse.js +9 -13
- package/dist/slices/rebuild/runtime-narration-text.js +13 -17
- package/dist/slices/rebuild/runtime-pictures.js +2 -4
- package/dist/slices/rebuild/runtime-piece-label.js +10 -6
- package/dist/slices/rebuild/runtime-plan.js +31 -5
- package/dist/slices/rebuild/runtime-provider.js +67 -25
- package/dist/slices/rebuild/runtime-publication.js +10 -9
- package/dist/slices/rebuild/runtime-review.js +39 -33
- package/dist/slices/rebuild/runtime-run.js +4 -1
- package/dist/slices/rebuild/runtime-shorts.js +31 -50
- package/dist/slices/rebuild/runtime-store.js +20 -10
- package/dist/slices/rebuild/runtime-subtitles.js +28 -25
- package/dist/slices/rebuild/runtime-voices.js +10 -16
- package/dist/slices/rebuild/runtime-youtube.js +14 -31
- package/dist/slices/rebuild/service-readiness.js +11 -3
- package/dist/slices/rebuild/transition-repo.js +6 -5
- package/dist/slices/rebuild/work-records.js +6 -0
- package/dist/slices/reruns/cascade.js +1 -2
- package/dist/slices/reruns/index.js +6 -9
- package/dist/slices/research/synthesis.js +3 -2
- package/dist/slices/reviews/external-model.js +4 -1
- package/dist/slices/reviews/external-repo.js +2 -1
- package/dist/slices/reviews/external-rows.js +2 -3
- package/dist/slices/reviews/holds.js +5 -4
- package/dist/slices/reviews/model.js +32 -11
- package/dist/slices/reviews/repo.js +2 -1
- package/dist/slices/reviews/rules.js +4 -3
- package/dist/slices/reviews/verdict.js +2 -1
- package/dist/slices/reviews/view.js +5 -2
- package/dist/slices/revisions/downloads.js +4 -5
- package/dist/slices/revisions/image-plan.js +1 -1
- package/dist/slices/revisions/mutation-assets.js +9 -5
- package/dist/slices/revisions/mutation-cues.js +2 -5
- package/dist/slices/revisions/mutations.js +3 -3
- package/dist/slices/revisions/repo.js +3 -1
- package/dist/slices/revisions/schema.js +1 -0
- package/dist/slices/revisions/view.js +20 -0
- package/dist/slices/run-cost/limits.js +5 -2
- package/dist/slices/run-cost/meter.js +12 -5
- package/dist/slices/run-cost/panel.js +8 -74
- package/dist/slices/run-cost/plan-readings.js +127 -0
- package/dist/slices/run-cost/plan-use.js +169 -0
- package/dist/slices/run-cost/pricing.js +3 -3
- package/dist/slices/run-cost/week.js +32 -4
- package/dist/slices/schedules/agenda.js +18 -7
- package/dist/slices/schedules/by-date.js +42 -0
- package/dist/slices/schedules/calendar.js +5 -0
- package/dist/slices/schedules/design-agenda.js +36 -0
- package/dist/slices/schedules/design-check.js +41 -0
- package/dist/slices/schedules/design-run.js +126 -0
- package/dist/slices/schedules/next-run.js +62 -0
- package/dist/slices/schedules/prepare.js +18 -2
- package/dist/slices/schedules/repo.js +30 -21
- package/dist/slices/schedules/rescore.js +35 -0
- package/dist/slices/schedules/scheduler.js +13 -3
- package/dist/slices/schedules/schema.js +57 -3
- package/dist/slices/schedules/score.js +107 -0
- package/dist/slices/schedules/service.js +57 -24
- package/dist/slices/schedules/topic-list.js +15 -0
- package/dist/slices/schedules/topic-scores.js +104 -0
- package/dist/slices/schedules/topics.js +53 -41
- package/dist/slices/settings/cli-paths.js +4 -6
- package/dist/slices/settings/first-run.js +8 -43
- package/dist/slices/settings/health.js +30 -15
- package/dist/slices/settings/key-test.js +1 -1
- package/dist/slices/settings/model-reach.js +3 -17
- package/dist/slices/settings/model.js +14 -2
- package/dist/slices/settings/playback.js +34 -23
- package/dist/slices/settings/probe-read.js +16 -0
- package/dist/slices/settings/repo.js +33 -0
- package/dist/slices/settings/tutorial.js +2 -2
- package/dist/slices/settings/voices.js +4 -12
- package/dist/slices/shorts/keep.js +2 -1
- package/dist/slices/shorts/pick.js +6 -18
- package/dist/slices/shorts/prompts.js +4 -14
- package/dist/slices/shorts/render.js +6 -2
- package/dist/slices/social/channel.js +44 -0
- package/dist/slices/social/defaults.js +17 -0
- package/dist/slices/social/due.js +36 -0
- package/dist/slices/social/extension.js +18 -0
- package/dist/slices/social/fit.js +115 -0
- package/dist/slices/social/limits.js +186 -0
- package/dist/slices/social/links.js +21 -0
- package/dist/slices/social/media.js +132 -0
- package/dist/slices/social/model.js +75 -0
- package/dist/slices/social/notice.js +19 -0
- package/dist/slices/social/pack.js +176 -0
- package/dist/slices/social/post-prompt.js +56 -0
- package/dist/slices/social/queue.js +31 -0
- package/dist/slices/social/repo.js +133 -0
- package/dist/slices/social/reshape.js +97 -0
- package/dist/slices/social/rules.js +5 -0
- package/dist/slices/social/schema.js +70 -0
- package/dist/slices/social/serve.js +51 -0
- package/dist/slices/social/service.js +129 -0
- package/dist/slices/social/settings.js +21 -0
- package/dist/slices/social/sources.js +114 -0
- package/dist/slices/social/state.js +110 -0
- package/dist/slices/social/text.js +149 -0
- package/dist/slices/storage/arrange.js +3 -1
- package/dist/slices/storage/assets.js +8 -10
- package/dist/slices/storage/backup-designs.js +74 -0
- package/dist/slices/storage/backup-export.js +8 -3
- package/dist/slices/storage/backup-format.js +10 -0
- package/dist/slices/storage/backup-import.js +22 -4
- package/dist/slices/storage/delete-project.js +6 -5
- package/dist/slices/storage/files-location.js +6 -8
- package/dist/slices/storage/layout.js +13 -2
- package/dist/slices/storage/model.js +3 -0
- package/dist/slices/storage/portable.js +17 -12
- package/dist/slices/storage/schema.js +2 -0
- package/dist/slices/storage/staging-refs.js +2 -1
- package/dist/slices/storage/staging.js +6 -9
- package/dist/slices/storage/trim.js +26 -9
- package/dist/slices/studio/calendar.js +7 -2
- package/dist/slices/studio/disclosure.js +4 -4
- package/dist/slices/studio/model.js +1 -1
- package/dist/slices/studio/pack.js +9 -8
- package/dist/slices/studio/pick.js +2 -11
- package/dist/slices/studio/plan.js +7 -16
- package/dist/slices/studio/queue.js +3 -14
- package/dist/slices/studio/releases.js +26 -0
- package/dist/slices/studio/settings.js +20 -57
- package/dist/slices/studio/videos.js +35 -4
- package/dist/slices/style-preview/render.js +37 -16
- package/dist/slices/style-preview/schema.js +14 -1
- package/dist/slices/style-preview/service.js +2 -1
- package/dist/slices/style-preview/settings.js +33 -5
- package/dist/slices/subtitles/captions.js +1 -1
- package/dist/slices/telemetry/collector-client.js +2 -4
- package/dist/slices/telemetry/flush.js +2 -4
- package/dist/slices/telemetry/model.js +0 -7
- package/dist/slices/telemetry/record.js +10 -3
- package/dist/slices/template-studio/anatomy-view.js +161 -0
- package/dist/slices/template-studio/anatomy.js +73 -0
- package/dist/slices/template-studio/bakeoff-make.js +225 -0
- package/dist/slices/template-studio/bakeoff-repo.js +83 -0
- package/dist/slices/template-studio/bakeoff.js +333 -0
- package/dist/slices/template-studio/budget.js +106 -0
- package/dist/slices/template-studio/cards.js +78 -0
- package/dist/slices/template-studio/checks.js +322 -0
- package/dist/slices/template-studio/document-sample.js +56 -0
- package/dist/slices/template-studio/edit.js +15 -0
- package/dist/slices/template-studio/for-all.js +161 -0
- package/dist/slices/template-studio/free-video.js +31 -0
- package/dist/slices/template-studio/free.js +176 -0
- package/dist/slices/template-studio/gate.js +40 -0
- package/dist/slices/template-studio/hidden.js +20 -0
- package/dist/slices/template-studio/jobs.js +79 -0
- package/dist/slices/template-studio/loudness.js +111 -0
- package/dist/slices/template-studio/make.js +40 -0
- package/dist/slices/template-studio/model.js +58 -0
- package/dist/slices/template-studio/pack-import.js +253 -0
- package/dist/slices/template-studio/pack-samples.js +60 -0
- package/dist/slices/template-studio/pack.js +207 -0
- package/dist/slices/template-studio/paid-view.js +66 -0
- package/dist/slices/template-studio/paid.js +355 -0
- package/dist/slices/template-studio/quote.js +155 -0
- package/dist/slices/template-studio/ready.js +104 -0
- package/dist/slices/template-studio/repo.js +134 -0
- package/dist/slices/template-studio/reviews-path.js +58 -0
- package/dist/slices/template-studio/runner.js +84 -0
- package/dist/slices/template-studio/save.js +236 -0
- package/dist/slices/template-studio/say-it-as.js +248 -0
- package/dist/slices/template-studio/schedule-gate.js +34 -0
- package/dist/slices/template-studio/schedule-price.js +32 -0
- package/dist/slices/template-studio/schedule-sample.js +91 -0
- package/dist/slices/template-studio/schema.js +243 -0
- package/dist/slices/template-studio/scope.js +124 -0
- package/dist/slices/template-studio/sections.js +176 -0
- package/dist/slices/template-studio/service.js +175 -0
- package/dist/slices/template-studio/status.js +191 -0
- package/dist/slices/template-studio/topic.js +223 -0
- package/dist/slices/thumbnail/by-llm.js +1 -1
- package/dist/slices/trash/designs.js +76 -0
- package/dist/slices/trash/model.js +13 -4
- package/dist/slices/trash/service.js +52 -2
- package/dist/slices/tutorials/library.js +3 -2
- package/dist/slices/uploads/repo.js +37 -1
- package/dist/slices/uploads/rules.js +3 -2
- package/dist/slices/video/ass-text.js +17 -0
- package/dist/slices/video/audio-export.js +2 -64
- package/dist/slices/video/edit-list.js +2 -2
- package/dist/slices/video/edit-settings.js +1 -1
- package/dist/slices/video/encoding.js +169 -0
- package/dist/slices/video/ffmpeg.js +23 -21
- package/dist/slices/video/figure-card.js +1 -15
- package/dist/slices/video/plan.js +2 -1
- package/dist/slices/video/slideshow.js +43 -18
- package/dist/slices/voices/attribution.js +3 -2
- package/dist/slices/voices/model.js +9 -8
- package/dist/slices/voices/palette.js +0 -3
- package/dist/slices/voices/script.js +2 -1
- package/dist/slices/youtube/answer.js +4 -16
- package/dist/slices/youtube/edits-repo.js +3 -12
- package/dist/tutorials/AI-CLIs.md +25 -25
- package/dist/tutorials/Agents-MCP.md +34 -33
- package/dist/tutorials/Assistant.md +42 -0
- package/dist/tutorials/Backups.md +7 -7
- package/dist/tutorials/Calendar.md +17 -25
- package/dist/tutorials/Cast-Library.md +19 -19
- package/dist/tutorials/Channels.md +42 -35
- package/dist/tutorials/Costs-and-Run-Cost.md +38 -40
- package/dist/tutorials/Create-Article.md +114 -0
- package/dist/tutorials/Create-Extras.md +112 -0
- package/dist/tutorials/Create-Images.md +165 -0
- package/dist/tutorials/Create-Narration.md +178 -0
- package/dist/tutorials/Create-Overview.md +244 -0
- package/dist/tutorials/Create-Video-and-Style.md +144 -0
- package/dist/tutorials/Docker.md +3 -3
- package/dist/tutorials/Document-Themes.md +32 -32
- package/dist/tutorials/Editing-a-Project.md +94 -94
- package/dist/tutorials/FAQ.md +18 -18
- package/dist/tutorials/First-Launch-and-Welcome.md +50 -64
- package/dist/tutorials/Home-and-Projects.md +115 -71
- package/dist/tutorials/Home.md +33 -33
- package/dist/tutorials/Install.md +14 -13
- package/dist/tutorials/Intros-and-Outros.md +33 -34
- package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +62 -58
- package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
- package/dist/tutorials/Models.md +53 -21
- package/dist/tutorials/Multiple-Voices.md +41 -40
- package/dist/tutorials/Narration-Aliases-and-Glossary.md +14 -14
- package/dist/tutorials/Notifications.md +27 -17
- package/dist/tutorials/Other-Languages.md +15 -15
- package/dist/tutorials/PDF-Documents.md +38 -29
- package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
- package/dist/tutorials/Project-Page.md +118 -120
- package/dist/tutorials/Prompts-and-Templates.md +84 -0
- package/dist/tutorials/Prompts.md +48 -44
- package/dist/tutorials/Providers-and-Keys.md +105 -98
- package/dist/tutorials/Publishing-to-YouTube.md +31 -31
- package/dist/tutorials/Recovery-and-Retries.md +31 -31
- package/dist/tutorials/Remote-Access.md +63 -0
- package/dist/tutorials/Reviews-and-Checkpoints.md +98 -90
- package/dist/tutorials/Schedules.md +46 -27
- package/dist/tutorials/Settings-Reference.md +194 -174
- package/dist/tutorials/Shorts.md +34 -33
- package/dist/tutorials/Start-at-Login.md +3 -3
- package/dist/tutorials/Studio-Extension.md +22 -24
- package/dist/tutorials/Template-Studio.md +128 -0
- package/dist/tutorials/Templates.md +51 -77
- package/dist/tutorials/Trash.md +4 -4
- package/dist/tutorials/Troubleshooting.md +44 -44
- package/dist/tutorials/Uninstalling-and-Moving.md +5 -5
- package/dist/tutorials/Updating-and-Patch-Notes.md +18 -18
- package/dist/tutorials/Video-Editing.md +34 -34
- package/dist/tutorials/Where-Your-Files-Live.md +10 -10
- package/dist/tutorials/YouTube-Description.md +26 -26
- package/dist/tutorials/Your-First-Short.md +35 -35
- package/dist/tutorials/_Sidebar.md +19 -14
- package/dist/updater/work-in-progress.js +42 -12
- package/dist/web/assets/index-BNftKQrA.css +1 -0
- package/dist/web/assets/index-Cpa58woP.js +459 -0
- package/dist/web/assets/pdf-BKh8f7R6.js +56 -0
- package/dist/web/assets/pdf.worker.min-BmVo14Nb.mjs +28 -0
- package/dist/web/assets/pdf.worker.min-D7i8o3wR.js +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +6 -2
- package/dist/slices/article/provided-entries.js +0 -66
- package/dist/slices/article/run.js +0 -145
- package/dist/slices/images/run.js +0 -234
- package/dist/slices/narration/legacy-plan.js +0 -58
- package/dist/slices/narration/run.js +0 -270
- package/dist/slices/rebuild/runtime-actions.js +0 -90
- package/dist/slices/research/run.js +0 -224
- package/dist/slices/subtitles/prepare.js +0 -194
- package/dist/slices/subtitles/transcript.js +0 -34
- package/dist/slices/thumbnail/run.js +0 -219
- package/dist/slices/video/audio-inputs.js +0 -33
- package/dist/slices/video/reuse-audio.js +0 -62
- package/dist/slices/video/run.js +0 -78
- package/dist/slices/video/write-export.js +0 -158
- package/dist/slices/video/write-subtitles.js +0 -59
- package/dist/tutorials/Library-Overview.md +0 -76
- package/dist/tutorials/Play-Images.md +0 -161
- package/dist/tutorials/Play-Narration.md +0 -167
- package/dist/tutorials/Play-Outputs.md +0 -109
- package/dist/tutorials/Play-Overview.md +0 -227
- package/dist/tutorials/Play-Title-and-Article.md +0 -136
- package/dist/tutorials/Play-Video-and-Style.md +0 -143
- package/dist/web/assets/index-BadsoORk.css +0 -1
- package/dist/web/assets/index-D8LqNsCl.js +0 -326
- package/dist/web/assets/pdf-T7MCwjIv.js +0 -54
- package/dist/web/assets/pdf.worker.min-Dswkl-cV.mjs +0 -29
- package/dist/web/assets/pdf.worker.min-zC6nfbxG.js +0 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Public search interest without a key: Google's search suggestions (DuckDuckGo's when Google
|
|
3
|
+
// doesn't answer) and the Wikipedia article's views last month. Checked keyless on 2026-10-18;
|
|
4
|
+
// Google Trends answers 429/404 to such calls, so it isn't asked.
|
|
5
|
+
// ceiling: one service that hangs costs a topic this long at most.
|
|
6
|
+
export const interestRequestMs = 4000;
|
|
7
|
+
const suggestAnswer = z.tuple([z.string(), z.array(z.string())]).rest(z.unknown());
|
|
8
|
+
const openSearchAnswer = z.tuple([z.string(), z.array(z.string())]).rest(z.unknown());
|
|
9
|
+
const pageviewsAnswer = z.object({ items: z.array(z.object({ views: z.number() }).loose()) });
|
|
10
|
+
class Unanswered extends Error {
|
|
11
|
+
reason;
|
|
12
|
+
constructor(reason) {
|
|
13
|
+
super(reason);
|
|
14
|
+
this.reason = reason;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// "en" from "en-GB"; a language Wikipedia names by its two letters.
|
|
18
|
+
function languageOf(language) {
|
|
19
|
+
const code = language.trim().toLowerCase().slice(0, 2);
|
|
20
|
+
return /^[a-z]{2}$/.test(code) ? code : "en";
|
|
21
|
+
}
|
|
22
|
+
// The last whole month before `now`, as the pageviews API names its days.
|
|
23
|
+
export function lastMonth(now) {
|
|
24
|
+
const first = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - 1, 1));
|
|
25
|
+
const last = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 0));
|
|
26
|
+
const day = (date) => `${String(date.getUTCFullYear())}${String(date.getUTCMonth() + 1).padStart(2, "0")}${String(date.getUTCDate()).padStart(2, "0")}00`;
|
|
27
|
+
return { start: day(first), end: day(last) };
|
|
28
|
+
}
|
|
29
|
+
export function createSearchInterest(deps) {
|
|
30
|
+
const headers = {
|
|
31
|
+
"User-Agent": `Slopify/${deps.version} (https://github.com/gentbajko/slopify)`,
|
|
32
|
+
Accept: "application/json",
|
|
33
|
+
};
|
|
34
|
+
const json = async (url, signal) => {
|
|
35
|
+
const timeout = AbortSignal.timeout(interestRequestMs);
|
|
36
|
+
let response;
|
|
37
|
+
try {
|
|
38
|
+
response = await deps.fetch(url, { headers, signal: AbortSignal.any([signal, timeout]) });
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
throw new Unanswered(timeout.aborted ? "timeout" : "offline");
|
|
42
|
+
}
|
|
43
|
+
if (!response.ok)
|
|
44
|
+
throw new Unanswered("refused");
|
|
45
|
+
try {
|
|
46
|
+
return await response.json();
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
throw new Unanswered("refused");
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const reasonOf = (error) => error instanceof Unanswered ? error.reason : "offline";
|
|
53
|
+
const suggestions = async (topic, language, signal) => {
|
|
54
|
+
const q = encodeURIComponent(topic);
|
|
55
|
+
let failure = "offline";
|
|
56
|
+
for (const url of [
|
|
57
|
+
`https://suggestqueries.google.com/complete/search?client=firefox&hl=${language}&q=${q}`,
|
|
58
|
+
`https://duckduckgo.com/ac/?q=${q}&type=list&kl=wt-wt`,
|
|
59
|
+
]) {
|
|
60
|
+
try {
|
|
61
|
+
const parsed = suggestAnswer.safeParse(await json(url, signal));
|
|
62
|
+
if (parsed.success)
|
|
63
|
+
return parsed.data[1].slice(0, 10);
|
|
64
|
+
failure = "refused";
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
failure = reasonOf(error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return failure;
|
|
71
|
+
};
|
|
72
|
+
const wikiViews = async (topic, language, signal) => {
|
|
73
|
+
try {
|
|
74
|
+
const found = openSearchAnswer.safeParse(await json(`https://${language}.wikipedia.org/w/api.php?action=opensearch&format=json&namespace=0&limit=1&search=${encodeURIComponent(topic)}`, signal));
|
|
75
|
+
if (!found.success)
|
|
76
|
+
return "refused";
|
|
77
|
+
const title = found.data[1][0];
|
|
78
|
+
if (title === undefined)
|
|
79
|
+
return null;
|
|
80
|
+
const { start, end } = lastMonth(deps.now());
|
|
81
|
+
let answer;
|
|
82
|
+
try {
|
|
83
|
+
answer = await json(`https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/${language}.wikipedia/all-access/user/${encodeURIComponent(title.replaceAll(" ", "_"))}/monthly/${start}/${end}`, signal);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
// An article too new to have a month of views answers 404.
|
|
87
|
+
if (reasonOf(error) === "refused")
|
|
88
|
+
return null;
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
const views = pageviewsAnswer.safeParse(answer);
|
|
92
|
+
return views.success ? views.data.items.reduce((sum, item) => sum + item.views, 0) : null;
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
return reasonOf(error);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
return async (topic, rawLanguage, signal) => {
|
|
99
|
+
const language = languageOf(rawLanguage);
|
|
100
|
+
const [offered, views] = await Promise.all([
|
|
101
|
+
suggestions(topic, language, signal),
|
|
102
|
+
wikiViews(topic, language, signal),
|
|
103
|
+
]);
|
|
104
|
+
const failed = (value) => typeof value === "string";
|
|
105
|
+
if (failed(offered) && failed(views))
|
|
106
|
+
return { ok: false, reason: offered };
|
|
107
|
+
return {
|
|
108
|
+
ok: true,
|
|
109
|
+
suggestions: failed(offered) ? null : offered,
|
|
110
|
+
wikiViews: failed(views) ? null : views,
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { safeJson } from "./http-failure.js";
|
|
1
2
|
import { cartesiaVersion } from "./tts/cartesia.js";
|
|
2
|
-
// Settings →
|
|
3
|
+
// Settings → Keys & services' Test button, per provider. Some answer a key they do not recognise
|
|
3
4
|
// with something other than 401: Google with 400 API_KEY_INVALID, ElevenLabs with 400
|
|
4
5
|
// invalid_api_key, Inworld with 403 "does not exist". Observed on 2026-09-27; their
|
|
5
6
|
// documentation says 401.
|
|
@@ -21,6 +22,37 @@ function listed(ids) {
|
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
24
|
const field = (value, key) => typeof value === "object" && value !== null ? value[key] : undefined;
|
|
25
|
+
function idsOf(rows, key, strip) {
|
|
26
|
+
if (!Array.isArray(rows))
|
|
27
|
+
return undefined;
|
|
28
|
+
return rows.flatMap((row) => {
|
|
29
|
+
const id = field(row, key);
|
|
30
|
+
return typeof id === "string" && id !== "" ? [strip ? id.replace(strip, "") : id] : [];
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// `{ data: [{ id }] }`: OpenAI's and OpenRouter's lists.
|
|
34
|
+
function dataList(url) {
|
|
35
|
+
return {
|
|
36
|
+
url: () => url,
|
|
37
|
+
read: (body) => {
|
|
38
|
+
const ids = idsOf(field(safeJson(body), "data"), "id");
|
|
39
|
+
return ids === undefined ? undefined : { ids };
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const googleBase = "https://generativelanguage.googleapis.com/v1beta/models?pageSize=1000";
|
|
44
|
+
const googleList = {
|
|
45
|
+
url: (page) => page === undefined ? googleBase : `${googleBase}&pageToken=${encodeURIComponent(page)}`,
|
|
46
|
+
read: (body) => {
|
|
47
|
+
const value = safeJson(body);
|
|
48
|
+
const ids = idsOf(field(value, "models"), "name", /^models\//);
|
|
49
|
+
if (ids === undefined)
|
|
50
|
+
return undefined;
|
|
51
|
+
const next = field(value, "nextPageToken");
|
|
52
|
+
return typeof next === "string" && next !== "" ? { ids, next } : { ids };
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const openAiList = dataList("https://api.openai.com/v1/models");
|
|
24
56
|
const google = {
|
|
25
57
|
url: "https://generativelanguage.googleapis.com/v1beta/models?pageSize=1",
|
|
26
58
|
headers: (key) => ({ "x-goog-api-key": key }),
|
|
@@ -28,6 +60,7 @@ const google = {
|
|
|
28
60
|
model: {
|
|
29
61
|
url: (model) => `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model.replace(/^models\//, ""))}`,
|
|
30
62
|
},
|
|
63
|
+
list: googleList,
|
|
31
64
|
};
|
|
32
65
|
export const keyProbes = {
|
|
33
66
|
openrouter: {
|
|
@@ -40,9 +73,20 @@ export const keyProbes = {
|
|
|
40
73
|
return Array.isArray(data) ? data.map((one) => field(one, "id")) : [];
|
|
41
74
|
}),
|
|
42
75
|
},
|
|
76
|
+
list: dataList("https://openrouter.ai/api/v1/models"),
|
|
77
|
+
},
|
|
78
|
+
"openai-tts": {
|
|
79
|
+
url: "https://api.openai.com/v1/models",
|
|
80
|
+
headers: bearer,
|
|
81
|
+
model: openAiModel,
|
|
82
|
+
list: openAiList,
|
|
83
|
+
},
|
|
84
|
+
"openai-image": {
|
|
85
|
+
url: "https://api.openai.com/v1/models",
|
|
86
|
+
headers: bearer,
|
|
87
|
+
model: openAiModel,
|
|
88
|
+
list: openAiList,
|
|
43
89
|
},
|
|
44
|
-
"openai-tts": { url: "https://api.openai.com/v1/models", headers: bearer, model: openAiModel },
|
|
45
|
-
"openai-image": { url: "https://api.openai.com/v1/models", headers: bearer, model: openAiModel },
|
|
46
90
|
"google-image": google,
|
|
47
91
|
"google-tts": google,
|
|
48
92
|
// fal.ai's model list answers without a key; its price lookup requires one.
|
|
@@ -72,6 +116,13 @@ export const keyProbes = {
|
|
|
72
116
|
url: () => "https://api.elevenlabs.io/v1/models",
|
|
73
117
|
lists: listed((body) => Array.isArray(body) ? body.map((one) => field(one, "model_id")) : []),
|
|
74
118
|
},
|
|
119
|
+
list: {
|
|
120
|
+
url: () => "https://api.elevenlabs.io/v1/models",
|
|
121
|
+
read: (body) => {
|
|
122
|
+
const ids = idsOf(safeJson(body), "model_id");
|
|
123
|
+
return ids === undefined ? undefined : { ids };
|
|
124
|
+
},
|
|
125
|
+
},
|
|
75
126
|
},
|
|
76
127
|
cartesia: {
|
|
77
128
|
url: "https://api.cartesia.ai/voices?limit=1",
|
|
@@ -32,6 +32,6 @@ export async function nodeAntigravityModels(binary, read = nodeReadCliOutput) {
|
|
|
32
32
|
}
|
|
33
33
|
catch {
|
|
34
34
|
// Do not pass the CLI's raw output or home paths to the browser.
|
|
35
|
-
throw new Error('Slopify could not get the model list from the Antigravity CLI (it ran "agy models"). Check agy is installed and signed in (Settings →
|
|
35
|
+
throw new Error('Slopify could not get the model list from the Antigravity CLI (it ran "agy models"). Check agy is installed and signed in (Settings → Keys & services), then refresh the list, or type a model slug that "agy models" shows.');
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -5,7 +5,9 @@ import { z } from "zod";
|
|
|
5
5
|
import { redact } from "../../kernel/log.js";
|
|
6
6
|
import { documentMessages } from "../../kernel/ports/llm-documents.js";
|
|
7
7
|
import { providerError } from "../../kernel/ports/model.js";
|
|
8
|
+
import { editSettings } from "../../kernel/ports/settings-places.js";
|
|
8
9
|
import { cliCheck, cliName, cliReported, commandOf } from "../explain.js";
|
|
10
|
+
import { accessField } from "../model-access.js";
|
|
9
11
|
import { nodeAntigravityModels } from "./antigravity-models.js";
|
|
10
12
|
import { imageWorkspace, readFileName, unseenImages } from "./image-workspace.js";
|
|
11
13
|
import { cliInput, cliShaped, deliveredInput, endedWithout, promptOf, stopCliRun, stuckCli, } from "./run-cli.js";
|
|
@@ -76,7 +78,7 @@ export function antigravityLlm(deps) {
|
|
|
76
78
|
if (/^gemini$/i.test(commandOf(binary)))
|
|
77
79
|
throw providerError({
|
|
78
80
|
kind: "unsupported",
|
|
79
|
-
message: `The saved program path for the Antigravity CLI (${binary}) still points at the Gemini CLI, which Google has retired. In Settings →
|
|
81
|
+
message: `The saved program path for the Antigravity CLI (${binary}) still points at the Gemini CLI, which Google has retired. In Settings → Keys & services, clear the Antigravity CLI path or set it to the agy program, then use Try again.`,
|
|
80
82
|
});
|
|
81
83
|
// The documents travel inline, as with OpenRouter: headless agy refuses MCP tools it was
|
|
82
84
|
// not pre-approved for, so a research reader tool would be silently unavailable.
|
|
@@ -159,7 +161,7 @@ export function antigravityLlm(deps) {
|
|
|
159
161
|
}
|
|
160
162
|
req.signal.throwIfAborted();
|
|
161
163
|
if (run === undefined)
|
|
162
|
-
throw failure(binary, req.model, "", `The ${cliName(binary)} could not be started. Check it is installed and set up in Settings →
|
|
164
|
+
throw failure(binary, req.model, "", `The ${cliName(binary)} could not be started. Check it is installed and set up in Settings → Keys & services, then use Try again.`);
|
|
163
165
|
if (authRequired(run.stderr()))
|
|
164
166
|
throw authFailure();
|
|
165
167
|
throw failure(binary, req.model, run.stderr(), ended === undefined ? stuckCli(binary) : endedWithout(binary, ended, run.stderr()));
|
|
@@ -190,11 +192,11 @@ function emptyAnswer(binary, stderr) {
|
|
|
190
192
|
if (/no output produced|auto-denied|permission/i.test(stderr))
|
|
191
193
|
return providerError({
|
|
192
194
|
kind: "other",
|
|
193
|
-
message: `The ${cliName(binary)} tried to use a tool Slopify does not allow (it said: ${redact(stderr.trim()).slice(0, 300)}), so it gave no answer. Use Try again; if it keeps happening, choose another
|
|
195
|
+
message: `The ${cliName(binary)} tried to use a tool Slopify does not allow (it said: ${redact(stderr.trim()).slice(0, 300)}), so it gave no answer. Use Try again; if it keeps happening, choose another service or model in ${editSettings.writer}.`,
|
|
194
196
|
});
|
|
195
197
|
return providerError({
|
|
196
198
|
kind: "other",
|
|
197
|
-
message: `The ${cliName(binary)} answered with no text. Use Try again; if it keeps happening, run agy in a terminal to check it works, or choose another model in
|
|
199
|
+
message: `The ${cliName(binary)} answered with no text. Use Try again; if it keeps happening, run agy in a terminal to check it works, or choose another model in ${editSettings.writer}.`,
|
|
198
200
|
});
|
|
199
201
|
}
|
|
200
202
|
// `message` is what the CLI said; `sentence` replaces the default wording when Slopify
|
|
@@ -205,7 +207,9 @@ function failure(binary, model, message, sentence, now = Date.now) {
|
|
|
205
207
|
if (/invalid model selection/i.test(message))
|
|
206
208
|
return providerError({
|
|
207
209
|
kind: "unsupported",
|
|
208
|
-
|
|
210
|
+
// agy lists models a plan can't run and refuses them with this sentence.
|
|
211
|
+
access: "no-access",
|
|
212
|
+
message: `The ${cliName(binary)} does not offer the model "${model}". Run "agy models" in a terminal to see the ones it has, pick one in ${editSettings.writer}, then use Continue the run.`,
|
|
209
213
|
});
|
|
210
214
|
if (/quota|resource.?exhausted|capacity/i.test(message) &&
|
|
211
215
|
/exhaust|exceed|used up|reached|limit/i.test(message)) {
|
|
@@ -219,7 +223,7 @@ function failure(binary, model, message, sentence, now = Date.now) {
|
|
|
219
223
|
if (waitMs !== undefined && waitMs <= 5 * 60_000)
|
|
220
224
|
return providerError({
|
|
221
225
|
kind: "rate_limit",
|
|
222
|
-
message: `The ${cliName(binary)} is busy: Google's capacity for this model is used for the moment (it said: ${redact(message)}). Slopify tries again by itself shortly; if it keeps happening, run fewer projects at once or choose another model in
|
|
226
|
+
message: `The ${cliName(binary)} is busy: Google's capacity for this model is used for the moment (it said: ${redact(message)}). Slopify tries again by itself shortly; if it keeps happening, run fewer projects at once or choose another model in ${editSettings.writer}.`,
|
|
223
227
|
retryAfterMs: Math.max(1000, waitMs),
|
|
224
228
|
});
|
|
225
229
|
return providerError({
|
|
@@ -233,6 +237,7 @@ function failure(binary, model, message, sentence, now = Date.now) {
|
|
|
233
237
|
}
|
|
234
238
|
return providerError({
|
|
235
239
|
kind: "other",
|
|
240
|
+
...accessField(null, message),
|
|
236
241
|
message: sentence ?? cliReported(binary, redact(message), cliCheck(binary)),
|
|
237
242
|
});
|
|
238
243
|
}
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { redact } from "../../kernel/log.js";
|
|
6
|
+
import { cliName } from "../explain.js";
|
|
7
|
+
import { cliLoginError } from "./cli-login-error.js";
|
|
8
|
+
import { codexBinary, hardenedCodexBase } from "./codex.js";
|
|
9
|
+
import { cliInput, endedWithout, stopCliRun, stuckCli } from "./run-cli.js";
|
|
10
|
+
import { lines } from "./sse-lines.js";
|
|
11
|
+
// The in-app assistant's CLI turn: Claude Code or Codex with no tool of its own (no shell, no
|
|
12
|
+
// files, no web), none of the person's settings, hooks, plugins, skills, memory or MCP
|
|
13
|
+
// servers, and one MCP server: Slopify's `/mcp/assistant`, opened with the turn's token.
|
|
14
|
+
//
|
|
15
|
+
// The token is never an argument: argv is readable by every process on the computer. It is in
|
|
16
|
+
// the CLI's environment only, and the CLIs put it in the header themselves:
|
|
17
|
+
// - Claude Code 2.1.302 expands ${VAR} in an --mcp-config file's headers (checked on this
|
|
18
|
+
// machine: a stand-in server saw "Bearer <the token>" from `"Bearer
|
|
19
|
+
// ${SLOPIFY_ASSISTANT_TOKEN}"`). The file names the variable, not the token.
|
|
20
|
+
// - Codex 0.155.1 reads `mcp_servers.<name>.bearer_token_env_var` (codex-rs config
|
|
21
|
+
// `McpServerTransportConfig::StreamableHttp`; also checked: the server saw the bearer).
|
|
22
|
+
export const assistantServerName = "slopify";
|
|
23
|
+
export const assistantTokenVariable = "SLOPIFY_ASSISTANT_TOKEN";
|
|
24
|
+
const slopifyTool = `mcp__${assistantServerName}__`;
|
|
25
|
+
export function claudeAssistantArgs(options) {
|
|
26
|
+
return [
|
|
27
|
+
"-p",
|
|
28
|
+
"--output-format",
|
|
29
|
+
"stream-json",
|
|
30
|
+
"--verbose",
|
|
31
|
+
"--include-partial-messages",
|
|
32
|
+
// `claude --help` 2.1.302: --restricted drops the code-running tools and WebFetch and
|
|
33
|
+
// ignores the user, project and local settings files; --setting-sources "" loads none of
|
|
34
|
+
// them either. --bare would discard the OAuth sign-in the assistant runs on.
|
|
35
|
+
"--restricted",
|
|
36
|
+
"--setting-sources",
|
|
37
|
+
"",
|
|
38
|
+
"--disable-slash-commands",
|
|
39
|
+
"--no-chrome",
|
|
40
|
+
"--no-session-persistence",
|
|
41
|
+
"--settings",
|
|
42
|
+
JSON.stringify({
|
|
43
|
+
disableAllHooks: true,
|
|
44
|
+
autoMemoryEnabled: false,
|
|
45
|
+
claudeMdExcludes: ["**"],
|
|
46
|
+
enabledPlugins: {},
|
|
47
|
+
}),
|
|
48
|
+
"--system-prompt",
|
|
49
|
+
options.system,
|
|
50
|
+
"--strict-mcp-config",
|
|
51
|
+
"--mcp-config",
|
|
52
|
+
options.mcpConfig,
|
|
53
|
+
// No built-in tool at all; only Slopify's server is allowed, and the web tools are named
|
|
54
|
+
// as refused in case a later version brings them back by default.
|
|
55
|
+
"--tools",
|
|
56
|
+
"",
|
|
57
|
+
"--allowedTools",
|
|
58
|
+
`mcp__${assistantServerName}`,
|
|
59
|
+
"--disallowedTools",
|
|
60
|
+
"WebSearch,WebFetch,Bash",
|
|
61
|
+
"--permission-mode",
|
|
62
|
+
"dontAsk",
|
|
63
|
+
...(options.model === undefined || options.model === "" ? [] : ["--model", options.model]),
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
export function claudeAssistantConfig(url) {
|
|
67
|
+
return JSON.stringify({
|
|
68
|
+
mcpServers: {
|
|
69
|
+
[assistantServerName]: {
|
|
70
|
+
type: "http",
|
|
71
|
+
url,
|
|
72
|
+
headers: { Authorization: `Bearer \${${assistantTokenVariable}}` },
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export function codexAssistantArgs(options) {
|
|
78
|
+
const server = `mcp_servers.${assistantServerName}`;
|
|
79
|
+
return [
|
|
80
|
+
...hardenedCodexBase(options.directory),
|
|
81
|
+
"-c",
|
|
82
|
+
`instructions=${JSON.stringify(options.system)}`,
|
|
83
|
+
"-c",
|
|
84
|
+
'web_search="disabled"',
|
|
85
|
+
"-c",
|
|
86
|
+
"tool_output_token_limit=32768",
|
|
87
|
+
"-c",
|
|
88
|
+
`${server}.url=${JSON.stringify(options.url)}`,
|
|
89
|
+
"-c",
|
|
90
|
+
`${server}.bearer_token_env_var=${JSON.stringify(assistantTokenVariable)}`,
|
|
91
|
+
"-c",
|
|
92
|
+
`${server}.required=true`,
|
|
93
|
+
"-c",
|
|
94
|
+
`${server}.default_tools_approval_mode="auto"`,
|
|
95
|
+
...(options.model === undefined || options.model === "" ? [] : ["-m", options.model]),
|
|
96
|
+
"--",
|
|
97
|
+
"-",
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
// What the CLI is started with: enough to find itself, its sign-in and the network, and
|
|
101
|
+
// nothing else of Slopify's environment. A provider key there would make the CLI bill that key
|
|
102
|
+
// instead of the person's plan, and hand it a secret it never needs.
|
|
103
|
+
const passedNames = new Set([
|
|
104
|
+
"PATH",
|
|
105
|
+
"HOME",
|
|
106
|
+
"USER",
|
|
107
|
+
"LOGNAME",
|
|
108
|
+
"SHELL",
|
|
109
|
+
"LANG",
|
|
110
|
+
"LANGUAGE",
|
|
111
|
+
"TZ",
|
|
112
|
+
"TMPDIR",
|
|
113
|
+
"TEMP",
|
|
114
|
+
"TMP",
|
|
115
|
+
"TERM",
|
|
116
|
+
// Where each CLI keeps its sign-in and settings, when moved from the default.
|
|
117
|
+
"CLAUDE_CONFIG_DIR",
|
|
118
|
+
"CODEX_HOME",
|
|
119
|
+
// A network that needs a proxy or its own certificates.
|
|
120
|
+
"HTTP_PROXY",
|
|
121
|
+
"HTTPS_PROXY",
|
|
122
|
+
"NO_PROXY",
|
|
123
|
+
"http_proxy",
|
|
124
|
+
"https_proxy",
|
|
125
|
+
"no_proxy",
|
|
126
|
+
"NODE_EXTRA_CA_CERTS",
|
|
127
|
+
"SSL_CERT_FILE",
|
|
128
|
+
"SSL_CERT_DIR",
|
|
129
|
+
// Windows: the CLI can't start, or find its folders, without these.
|
|
130
|
+
"SYSTEMROOT",
|
|
131
|
+
"SystemRoot",
|
|
132
|
+
"WINDIR",
|
|
133
|
+
"COMSPEC",
|
|
134
|
+
"PATHEXT",
|
|
135
|
+
"USERPROFILE",
|
|
136
|
+
"APPDATA",
|
|
137
|
+
"LOCALAPPDATA",
|
|
138
|
+
"HOMEDRIVE",
|
|
139
|
+
"HOMEPATH",
|
|
140
|
+
"PROGRAMDATA",
|
|
141
|
+
"ProgramData",
|
|
142
|
+
"PROGRAMFILES",
|
|
143
|
+
"ProgramFiles",
|
|
144
|
+
"Path",
|
|
145
|
+
]);
|
|
146
|
+
export function assistantEnv(sourceEnv) {
|
|
147
|
+
const env = {};
|
|
148
|
+
for (const [name, value] of Object.entries(sourceEnv))
|
|
149
|
+
if (value !== undefined && (passedNames.has(name) || /^(LC|XDG)_[A-Z_]+$/.test(name)))
|
|
150
|
+
env[name] = value;
|
|
151
|
+
return env;
|
|
152
|
+
}
|
|
153
|
+
// A private folder (0700) that is the CLI's working directory, so nothing of the person's
|
|
154
|
+
// projects is its cwd, holding the 0600 config file; removed when the turn ends.
|
|
155
|
+
export function assistantWorkspace(url, token, sourceEnv) {
|
|
156
|
+
const directory = mkdtempSync(join(tmpdir(), "slopify-assistant-"));
|
|
157
|
+
const remove = () => rmSync(directory, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
|
|
158
|
+
try {
|
|
159
|
+
const config = join(directory, "mcp.json");
|
|
160
|
+
writeFileSync(config, claudeAssistantConfig(url), { mode: 0o600, flag: "wx" });
|
|
161
|
+
const env = assistantEnv(sourceEnv);
|
|
162
|
+
env.PWD = directory;
|
|
163
|
+
env[assistantTokenVariable] = token;
|
|
164
|
+
return { directory, config, env, remove };
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
remove();
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const typed = z.object({ type: z.string() }).loose();
|
|
172
|
+
const claudeInit = z
|
|
173
|
+
.object({
|
|
174
|
+
subtype: z.literal("init"),
|
|
175
|
+
tools: z.array(z.string()).optional(),
|
|
176
|
+
mcp_servers: z.array(z.object({ name: z.string(), status: z.string() }).loose()).optional(),
|
|
177
|
+
})
|
|
178
|
+
.loose();
|
|
179
|
+
const claudeAssistant = z
|
|
180
|
+
.object({
|
|
181
|
+
message: z.object({
|
|
182
|
+
content: z.array(z
|
|
183
|
+
.object({ type: z.string(), text: z.string().optional(), name: z.string().optional() })
|
|
184
|
+
.loose()),
|
|
185
|
+
}),
|
|
186
|
+
})
|
|
187
|
+
.loose();
|
|
188
|
+
const claudePartial = z
|
|
189
|
+
.object({
|
|
190
|
+
event: z.object({
|
|
191
|
+
type: z.literal("content_block_delta"),
|
|
192
|
+
delta: z.object({ type: z.literal("text_delta"), text: z.string() }),
|
|
193
|
+
}),
|
|
194
|
+
})
|
|
195
|
+
.loose();
|
|
196
|
+
const claudeResult = z
|
|
197
|
+
.object({ subtype: z.string(), is_error: z.boolean().optional(), result: z.string().optional() })
|
|
198
|
+
.loose();
|
|
199
|
+
const codexItem = z
|
|
200
|
+
.object({
|
|
201
|
+
item: z
|
|
202
|
+
.object({
|
|
203
|
+
id: z.string().optional(),
|
|
204
|
+
type: z.string(),
|
|
205
|
+
text: z.string().optional(),
|
|
206
|
+
server: z.string().optional(),
|
|
207
|
+
tool: z.string().optional(),
|
|
208
|
+
})
|
|
209
|
+
.loose(),
|
|
210
|
+
})
|
|
211
|
+
.loose();
|
|
212
|
+
const codexFailed = z.object({ error: z.object({ message: z.string() }) }).loose();
|
|
213
|
+
const codexError = z.object({ message: z.string() }).loose();
|
|
214
|
+
// Codex items that are no part of a Slopify chat: a shell command, a file edit, a web
|
|
215
|
+
// search, another agent.
|
|
216
|
+
const codexForeign = new Set([
|
|
217
|
+
"command_execution",
|
|
218
|
+
"file_change",
|
|
219
|
+
"web_search",
|
|
220
|
+
"collab_tool_call",
|
|
221
|
+
]);
|
|
222
|
+
function failed(cli, said) {
|
|
223
|
+
const login = cliLoginError(cli, said);
|
|
224
|
+
if (login !== undefined)
|
|
225
|
+
return { type: "failed", message: login.message };
|
|
226
|
+
return {
|
|
227
|
+
type: "failed",
|
|
228
|
+
message: `The ${cliName(cli === "claude-code" ? "claude" : "codex")} stopped with an error: ${redact(said).slice(0, 500)}. Send your message again; if it keeps happening, check the CLI in Settings → Keys & services.`,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
// One JSONL line of the CLI's stream as what the chat shows. A stateful reader: Codex reports
|
|
232
|
+
// a message's text so far, and only what is new is passed on.
|
|
233
|
+
export function assistantStream(cli) {
|
|
234
|
+
const typedSoFar = new Map();
|
|
235
|
+
return (line) => {
|
|
236
|
+
if (line.trim() === "")
|
|
237
|
+
return [];
|
|
238
|
+
let value;
|
|
239
|
+
try {
|
|
240
|
+
value = JSON.parse(line);
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
const event = typed.safeParse(value);
|
|
246
|
+
if (!event.success)
|
|
247
|
+
return [];
|
|
248
|
+
return cli === "claude-code"
|
|
249
|
+
? claudeEvents(event.data.type, value)
|
|
250
|
+
: codexEvents(event.data.type, value);
|
|
251
|
+
};
|
|
252
|
+
function claudeEvents(type, value) {
|
|
253
|
+
if (type === "system") {
|
|
254
|
+
const init = claudeInit.safeParse(value);
|
|
255
|
+
if (!init.success)
|
|
256
|
+
return [];
|
|
257
|
+
const foreign = (init.data.tools ?? []).filter((name) => !name.startsWith(slopifyTool));
|
|
258
|
+
if (foreign.length > 0)
|
|
259
|
+
return [{ type: "foreign", name: foreign.join(", ") }];
|
|
260
|
+
const server = init.data.mcp_servers?.find((one) => one.name === assistantServerName);
|
|
261
|
+
if (server !== undefined && (server.status === "failed" || server.status === "needs-auth"))
|
|
262
|
+
return [
|
|
263
|
+
{
|
|
264
|
+
type: "failed",
|
|
265
|
+
message: "Claude Code couldn't reach Slopify's assistant tools, so it answered nothing. Send your message again; if it keeps happening, restart Slopify.",
|
|
266
|
+
},
|
|
267
|
+
];
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
if (type === "stream_event") {
|
|
271
|
+
const partial = claudePartial.safeParse(value);
|
|
272
|
+
return partial.success && partial.data.event.delta.text !== ""
|
|
273
|
+
? [{ type: "partial", text: partial.data.event.delta.text }]
|
|
274
|
+
: [];
|
|
275
|
+
}
|
|
276
|
+
if (type === "assistant") {
|
|
277
|
+
const message = claudeAssistant.safeParse(value);
|
|
278
|
+
if (!message.success)
|
|
279
|
+
return [];
|
|
280
|
+
return message.data.message.content.flatMap((block) => {
|
|
281
|
+
if (block.type === "text" && block.text !== undefined && block.text !== "")
|
|
282
|
+
return [{ type: "text", text: block.text }];
|
|
283
|
+
if (block.type !== "tool_use" || block.name === undefined)
|
|
284
|
+
return [];
|
|
285
|
+
return block.name.startsWith(slopifyTool)
|
|
286
|
+
? [{ type: "tool", name: block.name.slice(slopifyTool.length) }]
|
|
287
|
+
: [{ type: "foreign", name: block.name }];
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
if (type === "result") {
|
|
291
|
+
const result = claudeResult.safeParse(value);
|
|
292
|
+
if (!result.success)
|
|
293
|
+
return [];
|
|
294
|
+
return result.data.is_error === true || result.data.subtype !== "success"
|
|
295
|
+
? [failed("claude-code", result.data.result ?? result.data.subtype)]
|
|
296
|
+
: [{ type: "done" }];
|
|
297
|
+
}
|
|
298
|
+
return [];
|
|
299
|
+
}
|
|
300
|
+
function codexEvents(type, value) {
|
|
301
|
+
if (type === "item.started" || type === "item.updated" || type === "item.completed") {
|
|
302
|
+
const parsed = codexItem.safeParse(value);
|
|
303
|
+
if (!parsed.success)
|
|
304
|
+
return [];
|
|
305
|
+
const { item } = parsed.data;
|
|
306
|
+
if (codexForeign.has(item.type))
|
|
307
|
+
return [{ type: "foreign", name: item.type }];
|
|
308
|
+
if (item.type === "mcp_tool_call") {
|
|
309
|
+
if (item.server !== assistantServerName)
|
|
310
|
+
return [{ type: "foreign", name: `${item.server ?? "?"}.${item.tool ?? "?"}` }];
|
|
311
|
+
return type === "item.started" && item.tool !== undefined
|
|
312
|
+
? [{ type: "tool", name: item.tool }]
|
|
313
|
+
: [];
|
|
314
|
+
}
|
|
315
|
+
if (item.type !== "agent_message")
|
|
316
|
+
return [];
|
|
317
|
+
const id = item.id ?? "";
|
|
318
|
+
const seen = typedSoFar.get(id) ?? "";
|
|
319
|
+
const text = item.text ?? "";
|
|
320
|
+
if (type === "item.completed") {
|
|
321
|
+
typedSoFar.delete(id);
|
|
322
|
+
return text === "" ? [] : [{ type: "text", text }];
|
|
323
|
+
}
|
|
324
|
+
if (text.length > seen.length && text.startsWith(seen)) {
|
|
325
|
+
typedSoFar.set(id, text);
|
|
326
|
+
return [{ type: "partial", text: text.slice(seen.length) }];
|
|
327
|
+
}
|
|
328
|
+
return [];
|
|
329
|
+
}
|
|
330
|
+
if (type === "turn.completed")
|
|
331
|
+
return [{ type: "done" }];
|
|
332
|
+
if (type === "turn.failed") {
|
|
333
|
+
const parsed = codexFailed.safeParse(value);
|
|
334
|
+
return [failed("codex", parsed.success ? parsed.data.error.message : "the turn failed")];
|
|
335
|
+
}
|
|
336
|
+
if (type === "error") {
|
|
337
|
+
const parsed = codexError.safeParse(value);
|
|
338
|
+
return [failed("codex", parsed.success ? parsed.data.message : "unknown error")];
|
|
339
|
+
}
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// The turn itself: a private workspace, the CLI started in it with the prompt on stdin, its
|
|
344
|
+
// stream read into the chat's events, and everything stopped and removed at the end.
|
|
345
|
+
export function assistantCliRunner(run, sourceEnv = process.env) {
|
|
346
|
+
return async function* turn(request) {
|
|
347
|
+
const space = assistantWorkspace(request.url, request.token, sourceEnv);
|
|
348
|
+
let child;
|
|
349
|
+
let ended;
|
|
350
|
+
try {
|
|
351
|
+
child = run(request.binary, request.cli === "claude-code"
|
|
352
|
+
? claudeAssistantArgs({ mcpConfig: space.config, system: request.system })
|
|
353
|
+
: codexAssistantArgs({
|
|
354
|
+
directory: space.directory,
|
|
355
|
+
url: request.url,
|
|
356
|
+
system: request.system,
|
|
357
|
+
}), request.signal, { cwd: space.directory, env: space.env, stdin: cliInput(request.prompt) });
|
|
358
|
+
const read = assistantStream(request.cli);
|
|
359
|
+
for await (const line of lines(child.stdout, request.signal))
|
|
360
|
+
for (const event of read(line)) {
|
|
361
|
+
yield event;
|
|
362
|
+
if (event.type === "done" || event.type === "failed" || event.type === "foreign")
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
finally {
|
|
367
|
+
// Also when the reader stops early (Stop, the time limit): a CLI left running would
|
|
368
|
+
// keep spending the person's plan.
|
|
369
|
+
try {
|
|
370
|
+
if (child !== undefined)
|
|
371
|
+
ended = await stopCliRun(child);
|
|
372
|
+
}
|
|
373
|
+
finally {
|
|
374
|
+
space.remove();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (request.signal.aborted || child === undefined)
|
|
378
|
+
return;
|
|
379
|
+
// The stream ended with neither an answer nor a failure.
|
|
380
|
+
const binary = request.cli === "claude-code" ? "claude" : codexBinary;
|
|
381
|
+
const said = child.stderr();
|
|
382
|
+
yield {
|
|
383
|
+
type: "failed",
|
|
384
|
+
message: cliLoginError(request.cli, said)?.message ??
|
|
385
|
+
(ended === undefined ? stuckCli(binary) : endedWithout(binary, ended, said)),
|
|
386
|
+
};
|
|
387
|
+
};
|
|
388
|
+
}
|