@gentbajko/slopify 3.17.0 → 3.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/dist/edge/http/actions.js +2 -2
  2. package/dist/edge/http/actor.js +10 -0
  3. package/dist/edge/http/app.js +21 -0
  4. package/dist/edge/http/checkpoints.js +14 -6
  5. package/dist/edge/http/external-reviews.js +95 -0
  6. package/dist/edge/http/projects.js +36 -10
  7. package/dist/edge/http/reviews.js +53 -18
  8. package/dist/edge/http/revisions.js +5 -1
  9. package/dist/edge/http/settings.js +9 -1
  10. package/dist/edge/http/starts.js +41 -0
  11. package/dist/edge/mcp/attribution.js +38 -0
  12. package/dist/edge/mcp/call-route.js +203 -0
  13. package/dist/edge/mcp/excluded-routes.js +95 -0
  14. package/dist/edge/mcp/file-tools.js +141 -0
  15. package/dist/edge/mcp/http.js +79 -0
  16. package/dist/edge/mcp/instructions.js +17 -0
  17. package/dist/edge/mcp/library-tools.js +143 -0
  18. package/dist/edge/mcp/remake-tools.js +90 -0
  19. package/dist/edge/mcp/review-tools.js +64 -0
  20. package/dist/edge/mcp/route-tool.js +61 -0
  21. package/dist/edge/mcp/server.js +48 -0
  22. package/dist/edge/mcp/state-shapes.js +60 -0
  23. package/dist/edge/mcp/tools-channels.js +153 -0
  24. package/dist/edge/mcp/tools-library.js +151 -0
  25. package/dist/edge/mcp/tools-planning.js +137 -0
  26. package/dist/edge/mcp/tools-projects.js +167 -0
  27. package/dist/edge/mcp/tools-publishing.js +148 -0
  28. package/dist/edge/mcp/tools-runs.js +94 -0
  29. package/dist/edge/mcp/tools-schedules.js +143 -0
  30. package/dist/edge/mcp/tools-setup.js +234 -0
  31. package/dist/edge/mcp/tools-storage.js +99 -0
  32. package/dist/edge/mcp/tools.js +21 -0
  33. package/dist/extension/slopify-studio-chrome.zip +0 -0
  34. package/dist/extension/slopify-studio-firefox.zip +0 -0
  35. package/dist/kernel/db/migrations/0055-external-reviews.sql +56 -0
  36. package/dist/kernel/db/migrations/0056-agent-actions.sql +20 -0
  37. package/dist/kernel/db/migrations/0057-agent-action-once.sql +5 -0
  38. package/dist/kernel/db/migrations/0058-revision-work-admission.sql +3 -0
  39. package/dist/kernel/db/migrations/0059-external-review-canceled.sql +29 -0
  40. package/dist/kernel/db/migrations/0060-reservation-work.sql +4 -0
  41. package/dist/kernel/db/migrations/0061-project-control-counts.sql +4 -0
  42. package/dist/kernel/db/migrations/0062-told-failures.sql +8 -0
  43. package/dist/kernel/pipeline.js +6 -0
  44. package/dist/kernel/runner/graph.js +37 -2
  45. package/dist/kernel/runner/index.js +6 -3
  46. package/dist/main.js +29 -13
  47. package/dist/patch-notes/3.18.0.md +13 -0
  48. package/dist/patch-notes/index.json +6 -0
  49. package/dist/slices/admission/admitted-work.js +53 -0
  50. package/dist/slices/admission/heads.js +5 -0
  51. package/dist/slices/admission/repo.js +47 -29
  52. package/dist/slices/admission/schema.js +1 -0
  53. package/dist/slices/admission/stage-writes.js +21 -0
  54. package/dist/slices/admission/start.js +2 -1
  55. package/dist/slices/admission/state-words.js +110 -0
  56. package/dist/slices/agents/attention-work.js +233 -0
  57. package/dist/slices/agents/attention.js +168 -0
  58. package/dist/slices/agents/connection.js +15 -0
  59. package/dist/slices/agents/model.js +29 -0
  60. package/dist/slices/agents/repo.js +66 -0
  61. package/dist/slices/cancel/index.js +4 -1
  62. package/dist/slices/control/index.js +2 -1
  63. package/dist/slices/episodes/summarize.js +2 -1
  64. package/dist/slices/estimate/index.js +6 -2
  65. package/dist/slices/images/run.js +2 -1
  66. package/dist/slices/library/transfer.js +69 -0
  67. package/dist/slices/narration/run.js +2 -1
  68. package/dist/slices/notifications/address.js +40 -0
  69. package/dist/slices/notifications/notifier.js +43 -10
  70. package/dist/slices/notifications/rules.js +111 -45
  71. package/dist/slices/notifications/server.js +14 -0
  72. package/dist/slices/notifications/settings.js +1 -1
  73. package/dist/slices/notifications/subject.js +30 -0
  74. package/dist/slices/notifications/told.js +33 -0
  75. package/dist/slices/play-drafts/readiness.js +9 -2
  76. package/dist/slices/play-drafts/schema.js +2 -1
  77. package/dist/slices/rebuild/narration-words.js +76 -0
  78. package/dist/slices/rebuild/recipe-model.js +1 -0
  79. package/dist/slices/rebuild/recipe-reviews.js +44 -23
  80. package/dist/slices/rebuild/recovery-repo.js +0 -20
  81. package/dist/slices/rebuild/recovery-selection.js +14 -3
  82. package/dist/slices/rebuild/recovery.js +5 -2
  83. package/dist/slices/rebuild/remake-outdated.js +27 -0
  84. package/dist/slices/rebuild/repo.js +7 -2
  85. package/dist/slices/rebuild/review-redo.js +12 -0
  86. package/dist/slices/rebuild/runtime-agent-review.js +121 -0
  87. package/dist/slices/rebuild/runtime-export-inputs.js +26 -11
  88. package/dist/slices/rebuild/runtime-review.js +12 -2
  89. package/dist/slices/rebuild/runtime-run.js +11 -2
  90. package/dist/slices/rebuild/runtime-store.js +32 -5
  91. package/dist/slices/reruns/index.js +2 -1
  92. package/dist/slices/research/run.js +2 -1
  93. package/dist/slices/reviews/external-model.js +21 -0
  94. package/dist/slices/reviews/external-release.js +38 -0
  95. package/dist/slices/reviews/external-repo.js +109 -0
  96. package/dist/slices/reviews/external-rows.js +68 -0
  97. package/dist/slices/reviews/external-waiting.js +75 -0
  98. package/dist/slices/reviews/holds.js +144 -0
  99. package/dist/slices/reviews/model.js +14 -0
  100. package/dist/slices/reviews/repo.js +27 -10
  101. package/dist/slices/reviews/rules.js +5 -3
  102. package/dist/slices/reviews/verdict.js +18 -8
  103. package/dist/slices/revisions/repo.js +3 -0
  104. package/dist/slices/revisions/schema.js +1 -0
  105. package/dist/slices/schedules/agenda.js +47 -17
  106. package/dist/slices/schedules/prepare.js +6 -0
  107. package/dist/slices/schedules/schema.js +13 -1
  108. package/dist/slices/storage/consequences.js +27 -0
  109. package/dist/slices/storage/portable.js +3 -0
  110. package/dist/slices/storage/project-files.js +66 -0
  111. package/dist/slices/subtitles/prepare.js +2 -1
  112. package/dist/slices/uploads/repo.js +12 -6
  113. package/dist/slices/uploads/rules.js +5 -0
  114. package/dist/slices/video/write-export.js +1 -1
  115. package/dist/slices/video/write-subtitles.js +1 -1
  116. package/dist/tutorials/Agents-MCP.md +168 -0
  117. package/dist/tutorials/Calendar.md +2 -1
  118. package/dist/tutorials/First-Launch-and-Welcome.md +1 -0
  119. package/dist/tutorials/Home-and-Projects.md +16 -9
  120. package/dist/tutorials/Home.md +1 -0
  121. package/dist/tutorials/Notifications.md +25 -6
  122. package/dist/tutorials/Project-Page.md +8 -2
  123. package/dist/tutorials/Publishing-to-YouTube.md +1 -0
  124. package/dist/tutorials/Recovery-and-Retries.md +17 -3
  125. package/dist/tutorials/Reviews-and-Checkpoints.md +22 -5
  126. package/dist/tutorials/Schedules.md +1 -1
  127. package/dist/tutorials/Settings-Reference.md +15 -0
  128. package/dist/tutorials/Troubleshooting.md +3 -1
  129. package/dist/tutorials/Your-First-Short.md +1 -0
  130. package/dist/tutorials/_Sidebar.md +1 -0
  131. package/dist/web/assets/{index-DpdXRssU.css → index-BKbZIxAq.css} +1 -1
  132. package/dist/web/assets/index-Drth_MfQ.js +325 -0
  133. package/dist/web/assets/{pdf-B42WW32R.js → pdf-CQFUyijy.js} +1 -1
  134. package/dist/web/index.html +2 -2
  135. package/package.json +1 -1
  136. package/dist/web/assets/index-CbmBh8V-.js +0 -325
@@ -0,0 +1,94 @@
1
+ import { control } from "./route-tool.js";
2
+ const runs = [
3
+ {
4
+ name: "pause_project",
5
+ route: "POST /api/projects/:id/pause",
6
+ title: "Pause",
7
+ description: `Pause: the run stops after the steps already sent finish; nothing starts until resume_project. ${control}`,
8
+ },
9
+ {
10
+ name: "resume_project",
11
+ route: "POST /api/projects/:id/resume",
12
+ title: "Continue the run",
13
+ description: `Continue the run: starts the project's held and unfinished work, including work a saved edit left waiting. On a canceled run it resumes the whole run, the canceled steps too; once a step of it was taken up again (retry_stage), only the steps taken up again go on and the rest stay canceled. ${control}`,
14
+ },
15
+ {
16
+ name: "cancel_project",
17
+ route: "POST /api/projects/:id/cancel",
18
+ title: "Cancel run",
19
+ description: `Cancel run: stops every running stage of the project. ${control}`,
20
+ confirm: "Cancel this run? It stops every running stage; finished outputs are kept.",
21
+ },
22
+ {
23
+ name: "retry_stage",
24
+ route: "POST /api/projects/:id/stages/:kind/retry",
25
+ title: "Try again",
26
+ description: `Try again on a failed step (kind: research, article, audio, images, thumbnail, video or document), keeping what it already made. ${control}`,
27
+ },
28
+ {
29
+ name: "soften_stage",
30
+ route: "POST /api/projects/:id/stages/:kind/soften",
31
+ title: "Soften and retry",
32
+ description: `Soften and retry, for a picture a content filter refused (kind images or thumbnail, or video for a short's picture): rewords the refused prompts and draws them again. ${control}`,
33
+ confirm: "Your project's AI model rewrites each refused prompt without what a content filter could flag, keeping the scene and style. The image is then drawn again from the new wording, which it keeps as its prompt. The AI call and the new image are charged like any other.",
34
+ },
35
+ {
36
+ name: "rerun_stage",
37
+ route: "POST /api/projects/:id/stages/:kind/rerun",
38
+ title: "Re-run a stage",
39
+ description: `Re-run section: makes a stage again and everything built on it. A re-run that costs money is refused until it is reviewed: use preview_rebuild (Choose what to remake), then start_rebuild. ${control}`,
40
+ confirm: "Re-run this stage? It generates the stage and its dependent outputs again, which costs what the stage cost the first time; previous outputs stay in History.",
41
+ },
42
+ ];
43
+ const revisions = [
44
+ {
45
+ name: "prepare_revision",
46
+ route: "POST /api/projects/:id/revisions/prepare",
47
+ title: "Prepare an older project for editing",
48
+ description: "Saves the first version of a project made before versions existed; nothing is generated.",
49
+ },
50
+ {
51
+ name: "list_revisions",
52
+ route: "GET /api/projects/:id/revisions",
53
+ title: "Read project history",
54
+ description: "Every saved version of the project, newest first, as Project history lists them (byAgent marks one an agent saved), and agentActions: what agents did in the project.",
55
+ },
56
+ {
57
+ name: "get_revision",
58
+ route: "GET /api/projects/:id/revisions/:revisionId",
59
+ title: "Read a version",
60
+ description: "One version in full: revision.config and revision.content (send them back as save_revision's edit.config and edit.content), its outputs, pieces and text parts.",
61
+ },
62
+ {
63
+ name: "read_narration_chunks",
64
+ route: "GET /api/projects/:id/revisions/:revisionId/narration-chunks",
65
+ title: "Read narration chunk keys",
66
+ description: 'The narration chunk keys in spoken order, per segment: {chunks: {intro?, body?, outro?}}. "Narration chunk 5 of 10" in an error is chunks.body[4] (an intro or outro chunk is named after its segment); the key is what save_revision\'s edit.regenerate and content.narrationOverrides take.',
67
+ },
68
+ {
69
+ name: "save_revision",
70
+ route: "POST /api/projects/:id/revisions",
71
+ title: "Save an edit",
72
+ description: "Saves an edit as a new version, like Save in Edit project; it starts nothing. Body: {baseRevisionId, idempotencyKey: a new UUID, edit: {config, content, regenerate?: [work keys to make again, such as a narration chunk key], uploads?: [{stagedFileId, destination}]}}; send get_revision's revision.config and revision.content with your change. One chunk's wording or delivery note is content.narrationOverrides[chunk key] = {kind: \"text\", text, direction?}. Finished shorts kept through a new pick are content.shortsKeep: leave one out to pick a new moment for it, and lower config.shorts.count as well to drop it. Then preview_rebuild (Choose what to remake) says what it makes and costs, and start_rebuild starts it.",
73
+ },
74
+ {
75
+ name: "restore_revision",
76
+ route: "POST /api/projects/:id/revisions/restore",
77
+ title: "Restore a version",
78
+ description: "Makes an earlier version current again, like Restore this revision; it starts nothing. Body: {baseRevisionId, idempotencyKey: a new UUID, targetRevisionId}.",
79
+ },
80
+ {
81
+ name: "preview_rebuild",
82
+ route: "POST /api/projects/:id/rebuild/preview",
83
+ title: "Choose what to remake",
84
+ description: 'Choose what to remake: what a remake makes again, reuses or leaves blocked, each output\'s estimate and the total, like the project\'s Choose what to remake. Body: {baseRevisionId, request: {kind: "allAffected"} | {kind: "selected", workKeys: [...]}}. Nothing starts; start_rebuild does, with this preview\'s id.',
85
+ unrecorded: true,
86
+ },
87
+ {
88
+ name: "start_rebuild",
89
+ route: "POST /api/projects/:id/rebuild",
90
+ title: "Start a remake",
91
+ description: "Starts the remake you previewed: the acknowledgement Choose what to remake asks for. Body: {baseRevisionId, idempotencyKey: a new UUID, previewId: the preview's id, acknowledgeUnknownCosts: true only after reading the preview's unknown costs, confirmedProvidedWorkKeys: the preview's providedReuseRequired keys you accept replacing}.",
92
+ },
93
+ ];
94
+ export const runTools = [...runs, ...revisions];
@@ -0,0 +1,143 @@
1
+ import { formBody } from "./route-tool.js";
2
+ export const scheduleTools = [
3
+ {
4
+ name: "list_schedules",
5
+ route: "GET /api/schedules",
6
+ title: "List schedules",
7
+ description: "Schedules with their next runs, release lines and topic queues.",
8
+ },
9
+ {
10
+ name: "create_schedule",
11
+ route: "POST /api/schedules",
12
+ title: "Create a schedule",
13
+ description: `${formBody("the schedule editor")} Duplicate on a schedule is this with get_schedule's settings, a new id, a new name ending "(copy)" and items: [], then pause_schedule on the copy.`,
14
+ },
15
+ {
16
+ name: "get_schedule",
17
+ route: "GET /api/schedules/:id",
18
+ title: "Read a schedule",
19
+ description: "One schedule with its topic queue and release lines.",
20
+ },
21
+ {
22
+ name: "update_schedule",
23
+ route: "PUT /api/schedules/:id",
24
+ title: "Save a schedule",
25
+ description: `${formBody("the schedule editor")} Its release lines set when each run's videos are released.`,
26
+ },
27
+ {
28
+ name: "pause_schedule",
29
+ route: "POST /api/schedules/:id/pause",
30
+ title: "Pause a schedule",
31
+ description: "No run starts from it until resume_schedule.",
32
+ },
33
+ {
34
+ name: "resume_schedule",
35
+ route: "POST /api/schedules/:id/resume",
36
+ title: "Resume a schedule",
37
+ description: "Runs start from it again at its times.",
38
+ },
39
+ {
40
+ name: "cancel_schedule",
41
+ route: "POST /api/schedules/:id/cancel",
42
+ title: "Cancel a schedule",
43
+ description: "Cancel schedule, as the schedule's page does.",
44
+ confirm: "Stops future scheduled runs. Existing projects and run history are kept.",
45
+ },
46
+ {
47
+ name: "delete_schedule",
48
+ route: "DELETE /api/schedules/:id",
49
+ title: "Delete a schedule",
50
+ description: "Moves the schedule to the trash.",
51
+ confirm: "Moves this schedule to the trash for 30 days (Settings → Trash). Its run history is kept.",
52
+ },
53
+ {
54
+ name: "list_held_topics",
55
+ route: "GET /api/schedules/:id/topics/held",
56
+ title: "Read suggested topics",
57
+ description: "Topics the schedule generated and holds for approval, as Calendar → Suggested topics lists them.",
58
+ },
59
+ {
60
+ name: "list_prepared_topics",
61
+ route: "GET /api/schedules/:id/prepared",
62
+ title: "Read prepared topics",
63
+ description: "Which queued topics are Prepared ahead, and their projects.",
64
+ },
65
+ {
66
+ name: "prepare_topic",
67
+ route: "POST /api/schedules/:id/topics/prepare",
68
+ title: "Prepare a topic",
69
+ description: "Prepare, from the schedule's topics or Open on calendar: makes the topic's project now, exactly as the schedule would, and runs everything but the video, which its scheduled day renders. It spends what Play's review estimates and is refused over the schedule's spend limit. Body: {topic: the topic's title}.",
70
+ },
71
+ {
72
+ name: "generate_topics",
73
+ route: "POST /api/schedules/:id/topics/generate",
74
+ title: "Suggest topics",
75
+ description: "Asks the schedule's text model for topics, a paid call, like Suggest topics.",
76
+ },
77
+ {
78
+ name: "approve_all_topics",
79
+ route: "POST /api/schedules/:id/topics/held/approve-all",
80
+ title: "Approve every suggested topic",
81
+ description: "Moves every held topic into the queue.",
82
+ },
83
+ {
84
+ name: "approve_topics",
85
+ route: "POST /api/schedules/:id/topics/held/approve",
86
+ title: "Approve selected topics",
87
+ description: "Approve selected: moves these held topics into the queue. Body: {ids: [held topic ids]}.",
88
+ },
89
+ {
90
+ name: "reject_topics",
91
+ route: "POST /api/schedules/:id/topics/held/reject",
92
+ title: "Reject selected topics",
93
+ description: "Reject selected, or Reject all with every id shown: later generations won't suggest them again. Body: {ids: [held topic ids]}.",
94
+ },
95
+ {
96
+ name: "restore_topics",
97
+ route: "POST /api/schedules/:id/topics/held/restore",
98
+ title: "Bring rejected topics back",
99
+ description: `The Undo after a rejection: puts turned-down topics back. ${formBody("the Undo in the rejection's message")}`,
100
+ },
101
+ {
102
+ name: "approve_topic",
103
+ route: "POST /api/schedules/:id/topics/held/:topicId/approve",
104
+ title: "Approve a topic",
105
+ description: formBody("Approve in Calendar → Suggested topics"),
106
+ },
107
+ {
108
+ name: "reject_topic",
109
+ route: "POST /api/schedules/:id/topics/held/:topicId/reject",
110
+ title: "Reject a topic",
111
+ description: "Turns down one suggested topic.",
112
+ },
113
+ {
114
+ name: "edit_held_topic",
115
+ route: "PUT /api/schedules/:id/topics/held/:topicId",
116
+ title: "Edit a suggested topic",
117
+ description: formBody("the suggested topic's editor"),
118
+ },
119
+ {
120
+ name: "set_topic_queue",
121
+ route: "PUT /api/schedules/:id/topics",
122
+ title: "Set the topic queue",
123
+ description: `${formBody("the schedule's topic queue")} Bulk paste is this with the pasted topics added to the queue.`,
124
+ },
125
+ {
126
+ name: "move_topic",
127
+ route: "POST /api/schedules/:id/topics/move",
128
+ title: "Move a topic",
129
+ description: `Reorders the queue. ${formBody("the topic queue's move")}`,
130
+ },
131
+ {
132
+ name: "transfer_topic",
133
+ route: "POST /api/schedules/:id/topics/transfer",
134
+ title: "Move a topic to another schedule",
135
+ description: formBody("the topic queue's move to another schedule"),
136
+ },
137
+ {
138
+ name: "get_calendar",
139
+ route: "GET /api/calendar",
140
+ title: "Read the calendar",
141
+ description: "Upcoming scheduled runs (each with its prepared project, if any) and projects, each with needs (failed, paused, stopped, review) and readyToUpload. Query: {from, to: ISO times at most 92 days apart}.",
142
+ },
143
+ ];
@@ -0,0 +1,234 @@
1
+ import { z } from "zod";
2
+ import { readinessIsUsable } from "../../kernel/ports/model.js";
3
+ import { formBody } from "./route-tool.js";
4
+ const readinessShape = z.discriminatedUnion("kind", [
5
+ z.object({ kind: z.literal("keyed"), hasKey: z.boolean() }).loose(),
6
+ z
7
+ .object({ kind: z.literal("cli"), installed: z.boolean(), issue: z.string().optional() })
8
+ .loose(),
9
+ z
10
+ .object({ kind: z.literal("local"), available: z.boolean(), issue: z.string().optional() })
11
+ .loose(),
12
+ ]);
13
+ const providerList = z.object({
14
+ providers: z.array(z.object({
15
+ id: z.string(),
16
+ family: z.string(),
17
+ displayName: z.string(),
18
+ readiness: z.custom((value) => readinessShape.safeParse(value).success),
19
+ })),
20
+ });
21
+ // Providers as agents see them: set up or not, never whether a key exists or where a CLI is.
22
+ export function configuredOrMissing(value) {
23
+ const parsed = providerList.safeParse(value);
24
+ if (!parsed.success)
25
+ return { providers: [] };
26
+ return {
27
+ providers: parsed.data.providers.map((provider) => ({
28
+ id: provider.id,
29
+ family: provider.family,
30
+ displayName: provider.displayName,
31
+ state: readinessIsUsable(provider.readiness) ? "configured" : "missing",
32
+ })),
33
+ };
34
+ }
35
+ // The Studio settings without the extension's pairing token (a credential) and its unpacked
36
+ // folder (host setup).
37
+ export function withoutExtensionSecrets(value) {
38
+ const parsed = z.record(z.string(), z.unknown()).safeParse(value);
39
+ return parsed.success
40
+ ? Object.fromEntries(Object.entries(parsed.data).filter(([key]) => key !== "pairing" && key !== "unpacked"))
41
+ : {};
42
+ }
43
+ // The Notification URL can carry a secret (a webhook's token, a private ntfy topic): agents are
44
+ // told only whether one is set, and change it with set_notification_url.
45
+ export function notificationUrlState(value) {
46
+ const parsed = z.object({ url: z.string().nullable() }).loose().safeParse(value);
47
+ const url = parsed.success ? parsed.data.url : null;
48
+ return { notificationUrl: url === null || url.trim() === "" ? "missing" : "configured" };
49
+ }
50
+ const settings = [
51
+ {
52
+ name: "get_settings",
53
+ route: "GET /api/settings",
54
+ title: "Read production defaults",
55
+ description: "Settings → Production defaults: silence gap, appearance and loudness.",
56
+ },
57
+ {
58
+ name: "update_settings",
59
+ route: "PUT /api/settings",
60
+ title: "Save production defaults",
61
+ description: "Body: {silenceGapSeconds, appearance: system | light | dark, loudness?: {enabled, videoLufs, audioFilesLufs}}.",
62
+ },
63
+ {
64
+ name: "get_default_channel_links",
65
+ route: "GET /api/settings/channel-links",
66
+ title: "Read the default channel links",
67
+ description: "Settings → Channel links: the links a description's {{Name}} placeholders fill from.",
68
+ },
69
+ {
70
+ name: "set_default_channel_links",
71
+ route: "PUT /api/settings/channel-links",
72
+ title: "Save the default channel links",
73
+ description: "Body: {links: [{name, url}]}.",
74
+ },
75
+ {
76
+ name: "get_notification_url",
77
+ route: "GET /api/settings/notifications",
78
+ title: "Check the Notification URL",
79
+ description: "Whether run notifications have somewhere to go: configured or missing. The address itself is never shown to agents.",
80
+ shape: notificationUrlState,
81
+ },
82
+ {
83
+ name: "set_notification_url",
84
+ route: "PUT /api/settings/notifications",
85
+ title: "Save the Notification URL",
86
+ description: "Body: {url: an http(s) address, or empty to stop}. Answers configured or missing, never the address.",
87
+ shape: notificationUrlState,
88
+ },
89
+ {
90
+ name: "send_test_notification",
91
+ route: "POST /api/settings/notifications/test",
92
+ title: "Send a test notification",
93
+ description: "Body: {url}.",
94
+ unrecorded: true,
95
+ reachesOut: "sends",
96
+ },
97
+ {
98
+ name: "list_voices",
99
+ route: "GET /api/settings/voices",
100
+ title: "List voices",
101
+ description: "Settings → Voices.",
102
+ },
103
+ {
104
+ name: "add_voice",
105
+ route: "POST /api/settings/voices",
106
+ title: "Add a voice",
107
+ description: "Body: {provider, name, voiceId, languages?: [primary language codes]}.",
108
+ },
109
+ {
110
+ name: "set_voice_languages",
111
+ route: "PUT /api/settings/voices/:id/languages",
112
+ title: "Set a voice's languages",
113
+ description: formBody("Settings → Voices' languages"),
114
+ },
115
+ {
116
+ name: "set_voice_real_person",
117
+ route: "PUT /api/settings/voices/:id/real-person",
118
+ title: "Mark a voice as a real person's",
119
+ description: formBody("Settings → Voices' real-person box"),
120
+ },
121
+ {
122
+ name: "remove_voice",
123
+ route: "DELETE /api/settings/voices/:id",
124
+ title: "Remove a voice",
125
+ description: "Removes the voice from Settings → Voices.",
126
+ confirm: "Projects that used this voice keep the audio they made with it.",
127
+ },
128
+ {
129
+ name: "quote_voice_sample",
130
+ route: "POST /api/auditions/quote",
131
+ title: "Price a voice sample",
132
+ description: "Try a sample's price, as Play shows it before playing one. Body: {lines: [{speaker, provider, model, text}]}.",
133
+ unrecorded: true,
134
+ },
135
+ {
136
+ name: "get_usage",
137
+ route: "GET /api/usage",
138
+ title: "Read usage",
139
+ description: "Settings → Usage: this machine's usage counters.",
140
+ },
141
+ {
142
+ name: "get_diagnostics",
143
+ route: "GET /api/diagnostics",
144
+ title: "Download diagnostics",
145
+ description: "What Download diagnostics saves: versions, secret-free provider readiness, the project count and the model catalogue's state.",
146
+ },
147
+ {
148
+ name: "get_studio_settings",
149
+ route: "GET /api/studio/settings",
150
+ title: "Read YouTube Studio settings",
151
+ description: "Settings → YouTube Studio: the playlists upload packs name, and whether the comment is posted.",
152
+ shape: withoutExtensionSecrets,
153
+ },
154
+ {
155
+ name: "set_studio_playlists",
156
+ route: "PUT /api/studio/settings/playlists",
157
+ title: "Save playlist kinds",
158
+ description: "Playlist kinds: the playlists upload packs name (long videos, shorts or both), for every channel or one. Body: {playlists: [{name, byDefault, for?: long | shorts | both}], channelId?}.",
159
+ },
160
+ {
161
+ name: "get_agent_connection",
162
+ route: "GET /api/settings/agents",
163
+ title: "Read Settings → Connections → Agents (MCP)",
164
+ description: "When an agent last connected to Slopify's MCP endpoint, and the latest changes agents made, as Settings → Connections → Agents (MCP) lists them.",
165
+ },
166
+ ];
167
+ const providers = [
168
+ {
169
+ name: "list_providers",
170
+ route: "GET /api/providers",
171
+ title: "List providers",
172
+ description: "Each provider as configured or missing.",
173
+ shape: configuredOrMissing,
174
+ },
175
+ {
176
+ name: "list_models",
177
+ route: "GET /api/providers/:id/models",
178
+ title: "List a provider's models",
179
+ description: "The models Slopify offers for the provider, with prices where known.",
180
+ },
181
+ {
182
+ name: "list_system_voices",
183
+ route: "GET /api/providers/system-voice/voices",
184
+ title: "List the computer's voices",
185
+ description: "Voices of the computer's own speech program.",
186
+ },
187
+ {
188
+ name: "check_providers",
189
+ route: "POST /api/providers/health",
190
+ title: "Check providers",
191
+ description: "Settings → Providers' Check all: which providers answer and which CLIs are signed in.",
192
+ unrecorded: true,
193
+ reachesOut: "asks",
194
+ },
195
+ {
196
+ name: "get_catalogue",
197
+ route: "GET /api/providers/catalogue",
198
+ title: "Read the model catalogue",
199
+ description: "When the model catalogue was updated and any warning.",
200
+ },
201
+ {
202
+ name: "check_catalogue",
203
+ route: "POST /api/providers/catalogue/check",
204
+ title: "Check the model catalogue",
205
+ description: "Checks the published model catalogue and merges what changed into models.yaml (Settings → Models, Check now).",
206
+ reachesOut: "asks",
207
+ },
208
+ {
209
+ name: "refresh_catalogue",
210
+ route: "POST /api/providers/catalogue/refresh",
211
+ title: "Refresh the model catalogue",
212
+ description: "Downloads the newest model catalogue.",
213
+ reachesOut: "asks",
214
+ },
215
+ {
216
+ name: "list_retired_models",
217
+ route: "GET /api/providers/catalogue/retired",
218
+ title: "List retired models in use",
219
+ description: "Templates, drafts and schedules that use a retired model.",
220
+ },
221
+ {
222
+ name: "switch_retired_model",
223
+ route: "POST /api/providers/catalogue/retired/switch",
224
+ title: "Switch a retired model",
225
+ description: formBody("Settings → Models' Switch"),
226
+ },
227
+ {
228
+ name: "switch_all_retired_models",
229
+ route: "POST /api/providers/catalogue/retired/switch-all",
230
+ title: "Switch every retired model",
231
+ description: "Switches every use of a retired model to its suggested replacement.",
232
+ },
233
+ ];
234
+ export const setupTools = [...settings, ...providers];
@@ -0,0 +1,99 @@
1
+ import { formBody } from "./route-tool.js";
2
+ const storage = [
3
+ {
4
+ name: "get_files_folder",
5
+ route: "GET /api/storage/files",
6
+ title: "Read the files folder",
7
+ description: "Where projects, backups and exports are kept.",
8
+ },
9
+ {
10
+ name: "move_files_folder",
11
+ route: "POST /api/storage/files/move",
12
+ title: "Move the files folder",
13
+ description: "Body: {target: the new folder's full path; it must be empty or new}.",
14
+ confirm: "This moves every project and backup file to the new folder; Slopify refuses a folder that is not empty.",
15
+ },
16
+ {
17
+ name: "get_storage_usage",
18
+ route: "GET /api/storage",
19
+ title: "Read disk use",
20
+ description: "Settings → Backup & storage: disk used by projects (each with its outputs, working files and what can be freed), staged files and the trash.",
21
+ },
22
+ {
23
+ name: "list_trash",
24
+ route: "GET /api/trash",
25
+ title: "Read the trash",
26
+ description: "Deleted projects, prompts, intros and outros, templates and schedules, kept 30 days.",
27
+ },
28
+ {
29
+ name: "restore_from_trash",
30
+ route: "POST /api/trash/:kind/:id/restore",
31
+ title: "Restore from the trash",
32
+ description: "Puts the item back; a restored project picks up where it was.",
33
+ },
34
+ {
35
+ name: "restore_from_trash_many",
36
+ route: "POST /api/trash/bulk/restore",
37
+ title: "Restore several from the trash",
38
+ description: "Restore for the ticked rows. Body: {items: [{kind, id}]}.",
39
+ },
40
+ {
41
+ name: "delete_from_trash",
42
+ route: "DELETE /api/trash/:kind/:id",
43
+ title: "Delete for good",
44
+ description: "Deletes one trashed item for good, like Delete now in the trash.",
45
+ confirm: "The project and every file it produced are removed from disk (a deleted schedule keeps its run history on Schedules). Nothing removed here can be restored. This cannot be undone.",
46
+ },
47
+ {
48
+ name: "delete_from_trash_many",
49
+ route: "POST /api/trash/bulk/delete",
50
+ title: "Delete several for good",
51
+ description: "Delete now for the ticked rows. Body: {items: [{kind, id}]}.",
52
+ confirm: "The projects and every file they produced are removed from disk (deleted schedules keep their run history on Schedules). Nothing removed here can be restored. This cannot be undone.",
53
+ },
54
+ ];
55
+ const onboarding = [
56
+ {
57
+ name: "get_onboarding",
58
+ route: "GET /api/onboarding",
59
+ title: "Read the welcome state",
60
+ description: "Starter packs and what the welcome offers.",
61
+ },
62
+ {
63
+ name: "install_starter_pack",
64
+ route: "POST /api/onboarding/packs/:id",
65
+ title: "Install a starter pack",
66
+ description: "Adds a starter pack's prompts and templates.",
67
+ },
68
+ {
69
+ name: "make_quick_short",
70
+ route: "POST /api/onboarding/short",
71
+ title: "Make a quick short",
72
+ description: `${formBody("the welcome's Make a short")} It starts a paid run.`,
73
+ },
74
+ {
75
+ name: "make_full_video_draft",
76
+ route: "POST /api/onboarding/full-video",
77
+ title: "Draft a full video",
78
+ description: formBody("the welcome's Make a full video"),
79
+ },
80
+ {
81
+ name: "get_samples",
82
+ route: "GET /api/onboarding/sample",
83
+ title: "Read the sample projects",
84
+ description: "Which bundled sample projects are in the projects list.",
85
+ },
86
+ {
87
+ name: "restore_samples",
88
+ route: "POST /api/onboarding/sample/restore",
89
+ title: "Restore the samples",
90
+ description: "Puts the bundled samples back as they shipped.",
91
+ },
92
+ {
93
+ name: "copy_sample",
94
+ route: "POST /api/onboarding/sample/copy",
95
+ title: "Copy a sample",
96
+ description: "Makes an editable copy of a sample, like Make my own copy.",
97
+ },
98
+ ];
99
+ export const storageTools = [...storage, ...onboarding];
@@ -0,0 +1,21 @@
1
+ import { channelTools } from "./tools-channels.js";
2
+ import { libraryTools } from "./tools-library.js";
3
+ import { planningTools } from "./tools-planning.js";
4
+ import { projectTools } from "./tools-projects.js";
5
+ import { publishingTools } from "./tools-publishing.js";
6
+ import { runTools } from "./tools-runs.js";
7
+ import { scheduleTools } from "./tools-schedules.js";
8
+ import { setupTools } from "./tools-setup.js";
9
+ import { storageTools } from "./tools-storage.js";
10
+ // Every API route an agent may call, as a tool; `excluded-routes.ts` names the rest and why.
11
+ export const routeTools = [
12
+ ...projectTools,
13
+ ...publishingTools,
14
+ ...runTools,
15
+ ...planningTools,
16
+ ...scheduleTools,
17
+ ...channelTools,
18
+ ...libraryTools,
19
+ ...setupTools,
20
+ ...storageTools,
21
+ ];
@@ -0,0 +1,56 @@
1
+ -- A review an external agent answers through MCP: what the built-in reviewer would be shown,
2
+ -- kept while it waits (picture files by their stored path, found again where they are when
3
+ -- read); the agent's verdict, or its passing at the deadline. One row per review step
4
+ -- fingerprint, like review_verdicts.
5
+ CREATE TABLE external_reviews (
6
+ id TEXT PRIMARY KEY,
7
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
8
+ revision_id TEXT NOT NULL,
9
+ work_id TEXT NOT NULL,
10
+ piece_id TEXT NOT NULL,
11
+ work_key TEXT NOT NULL,
12
+ item_key TEXT NOT NULL,
13
+ stage TEXT NOT NULL CHECK(stage IN ('article','images','narration','thumbnail','shorts')),
14
+ item_fingerprint TEXT NOT NULL,
15
+ review_fingerprint TEXT NOT NULL,
16
+ material TEXT NOT NULL CHECK(json_valid(material)),
17
+ state TEXT NOT NULL CHECK(state IN ('waiting','answered','auto-passed','superseded')),
18
+ waiting_since TEXT NOT NULL,
19
+ deadline_at TEXT NOT NULL,
20
+ verdict TEXT CHECK(verdict IS NULL OR json_valid(verdict)),
21
+ answered_by TEXT CHECK(answered_by IN ('person','agent')),
22
+ answered_at TEXT,
23
+ UNIQUE(project_id,review_fingerprint)
24
+ );
25
+ CREATE INDEX external_reviews_waiting ON external_reviews(state, deadline_at);
26
+ -- Verdicts say who gave them and who acted on them, and a review nobody answered in time is
27
+ -- its own outcome. SQLite cannot change a CHECK in place, so the table is copied, as 0025 did.
28
+ CREATE TABLE review_verdicts_agent_upgrade (
29
+ id TEXT PRIMARY KEY,
30
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
31
+ revision_id TEXT NOT NULL,
32
+ item_key TEXT NOT NULL,
33
+ stage TEXT NOT NULL CHECK(stage IN ('article','images','narration','thumbnail','shorts')),
34
+ item_fingerprint TEXT NOT NULL,
35
+ review_fingerprint TEXT NOT NULL,
36
+ passed INTEGER NOT NULL CHECK(passed IN (0,1)),
37
+ reasons TEXT NOT NULL,
38
+ outcome TEXT NOT NULL CHECK(outcome IN ('passed','flagged','redo','auto-passed')),
39
+ attempt INTEGER NOT NULL CHECK(attempt >= 1),
40
+ action TEXT CHECK(action IN ('overruled','redone')),
41
+ action_at TEXT,
42
+ action_by TEXT CHECK(action_by IN ('person','agent')),
43
+ redo_state TEXT CHECK(redo_state IN ('pending','started','failed')),
44
+ redo_error TEXT,
45
+ answered_by TEXT NOT NULL DEFAULT 'model' CHECK(answered_by IN ('model','agent','person','nobody')),
46
+ created_at TEXT NOT NULL,
47
+ UNIQUE(project_id,review_fingerprint)
48
+ );
49
+ INSERT INTO review_verdicts_agent_upgrade (id,project_id,revision_id,item_key,stage,item_fingerprint,review_fingerprint,passed,reasons,outcome,attempt,action,action_at,action_by,redo_state,redo_error,answered_by,created_at)
50
+ SELECT id,project_id,revision_id,item_key,stage,item_fingerprint,review_fingerprint,passed,reasons,outcome,attempt,action,action_at,
51
+ CASE WHEN action IS NULL THEN NULL ELSE 'person' END,redo_state,redo_error,'model',created_at
52
+ FROM review_verdicts ORDER BY rowid;
53
+ DROP TABLE review_verdicts;
54
+ ALTER TABLE review_verdicts_agent_upgrade RENAME TO review_verdicts;
55
+ CREATE INDEX review_verdicts_item ON review_verdicts(project_id,item_key);
56
+ CREATE INDEX review_verdicts_redo ON review_verdicts(redo_state);