@tday-com/mcp 0.2.0 → 0.3.1
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/README.md +25 -6
- package/dist/api-client.js +1 -18
- package/dist/cli.js +0 -3
- package/dist/config.js +0 -4
- package/dist/download-safety.js +2 -5
- package/dist/installer-agents.js +132 -216
- package/dist/installer-callback.js +0 -7
- package/dist/installer-slash-command.js +94 -35
- package/dist/installer.js +34 -11
- package/dist/package-info.js +14 -8
- package/dist/prompts.js +36 -0
- package/dist/result-format.js +7 -31
- package/dist/server.js +12 -6
- package/dist/tool-schemas.js +1 -15
- package/dist/tools-account.js +0 -4
- package/dist/tools-features.js +39 -0
- package/dist/tools-launch.js +43 -50
- package/dist/tools.js +5 -26
- package/package.json +5 -15
- package/dist/api-client.d.ts +0 -23
- package/dist/cli.d.ts +0 -2
- package/dist/config.d.ts +0 -7
- package/dist/demo-import.d.ts +0 -6
- package/dist/demo-import.js +0 -101
- package/dist/download-safety.d.ts +0 -7
- package/dist/installer-agents.d.ts +0 -17
- package/dist/installer-callback.d.ts +0 -13
- package/dist/installer-slash-command.d.ts +0 -12
- package/dist/installer.d.ts +0 -2
- package/dist/observability.d.ts +0 -1
- package/dist/observability.js +0 -77
- package/dist/package-info.d.ts +0 -11
- package/dist/result-format.d.ts +0 -4
- package/dist/server.d.ts +0 -1
- package/dist/tool-schemas.d.ts +0 -460
- package/dist/tools-account.d.ts +0 -6
- package/dist/tools-demo.d.ts +0 -11
- package/dist/tools-demo.js +0 -57
- package/dist/tools-knowledge.d.ts +0 -4
- package/dist/tools-knowledge.js +0 -111
- package/dist/tools-launch.d.ts +0 -4
- package/dist/tools.d.ts +0 -15
package/dist/tools-launch.js
CHANGED
|
@@ -4,25 +4,17 @@ import { z } from "zod";
|
|
|
4
4
|
import { api } from "./api-client.js";
|
|
5
5
|
import { prepareOutputFilePath, validateImageUrl } from "./download-safety.js";
|
|
6
6
|
import { argsObject, readOnly, safeIdSchema, safePath, writeLocal, writeRemote, } from "./tool-schemas.js";
|
|
7
|
-
// The launch-video (launch film) tools. They drive the same routes tday's Launch page
|
|
8
|
-
// does, so tday's own pipeline writes the brief, storyboard, motion, score and narration:
|
|
9
|
-
// the caller supplies a subject in words and the creative axes, never HTML, a screenshot
|
|
10
|
-
// or a file. Split from tools.ts per the 500-line ceiling, like tools-knowledge.ts.
|
|
11
|
-
//
|
|
12
|
-
// The hosted connector has the same four tools (web/src/lib/mcp/remote-launch-tools.ts).
|
|
13
|
-
// The two cannot share code, so keep them in step. The option vocabularies below mirror
|
|
14
|
-
// web/src/lib/showcase/video-*.ts; an unknown voice id falls back to the default voice there.
|
|
15
|
-
/** The features route's own title cap (web/src/app/api/showcase/features/route.ts). */
|
|
16
7
|
const MAX_TITLE_CHARS = 160;
|
|
17
8
|
const MAX_DESCRIPTION_CHARS = 4000;
|
|
18
|
-
/** Creative notes ride inside every agent's prompt, so the route caps them (video-direction.ts). */
|
|
19
9
|
const MAX_DIRECTION_CHARS = 600;
|
|
20
|
-
/** A long (50s) film at 1080p is tens of megabytes; this is a generous ceiling, not a target. */
|
|
21
10
|
export const MAX_VIDEO_BYTES = 250 * 1024 * 1024;
|
|
22
11
|
const MUSIC_MOODS = ["Cinematic", "Upbeat", "Minimal", "Warm", "Driving"];
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
"
|
|
12
|
+
const NARRATION_STYLES = ["talk", "thread", "story", "duo", "team", "launch", "doc"];
|
|
13
|
+
const STYLES = "talk (one person talking to one viewer who has the problem, the default); thread (says how many updates there are, then goes through each by name, the default for a product update); " +
|
|
14
|
+
"story (a day in the life of one person in your audience); duo (two voices: the viewer's inner voice and a narrator); team (from the team who built it); launch (quick and bright); doc (a calm, third-person documentary)";
|
|
15
|
+
const VOICE_NAMES = ["Reece", "Ivy", "Janet", "Sarah", "Craig"];
|
|
16
|
+
const VOICES = "Reece, warm conversational narrator; Ivy, bright friendly presenter; Janet, warm expressive explainer; " +
|
|
17
|
+
"Sarah, upbeat lively presenter; Craig, deep polished broadcast voice";
|
|
26
18
|
const POLL_GUIDANCE = "Poll tday_poll_launch_video with this videoId every 20-30 seconds until it is ready.";
|
|
27
19
|
const VOICEOVER_NOT_INCLUDED = "Voiceover is not included on this organisation's plan, so the film is silent and no voice was charged.";
|
|
28
20
|
const videoIdSchema = safeIdSchema.describe("videoId returned by tday_create_launch_video");
|
|
@@ -48,24 +40,36 @@ const outputSchemas = {
|
|
|
48
40
|
.passthrough(),
|
|
49
41
|
revise: z.object({ videoId: z.string(), status: z.string(), reviewUrl: z.string() }).passthrough(),
|
|
50
42
|
};
|
|
51
|
-
/** How long a launch film's first export usually takes; later asks return the stored file. */
|
|
52
43
|
const FIRST_EXPORT = "The first request for a cut at a frame rate renders its file, which usually takes a minute or two";
|
|
53
44
|
export const launchTools = [
|
|
54
45
|
{
|
|
55
46
|
name: "tday_create_launch_video",
|
|
56
47
|
title: "Create launch video",
|
|
57
48
|
description: "Make a launch video: a short animated film announcing a feature or product, in the brand's own look. tday's pipeline writes the brief, storyboard, motion, music and narration from the title and description; describe the subject well and pick the options. " +
|
|
49
|
+
"For a feature tday already has (tday_list_features), pass its featureId instead of brandId, title and description: the film is made of that feature, with what tday knows about it. " +
|
|
58
50
|
"It spends credits from the brand's organisation: a standard (20s), scored, silent film is 40 credits, and a voiceover or another length changes that (tday_get_credit_prices has the full list). " +
|
|
59
51
|
"A 402 means the balance cannot cover it: nothing was started, so do not retry. Returns a videoId and a reviewUrl at once; the film takes about 2-5 minutes, so poll tday_poll_launch_video.",
|
|
60
52
|
inputSchema: argsObject({
|
|
61
|
-
|
|
62
|
-
|
|
53
|
+
featureId: safeIdSchema
|
|
54
|
+
.optional()
|
|
55
|
+
.describe("A feature's id from tday_list_features: the film is made of it, and brandId, title and description are not needed."),
|
|
56
|
+
brandId: safeIdSchema
|
|
57
|
+
.optional()
|
|
58
|
+
.describe("Brand ID from tday_list_brands, for a new subject (without featureId). The film uses this brand's colours, fonts and logo."),
|
|
59
|
+
title: z
|
|
60
|
+
.string()
|
|
61
|
+
.trim()
|
|
62
|
+
.min(1)
|
|
63
|
+
.max(MAX_TITLE_CHARS)
|
|
64
|
+
.optional()
|
|
65
|
+
.describe("The feature or product the film launches, as a short name (without featureId)."),
|
|
63
66
|
description: z
|
|
64
67
|
.string()
|
|
65
68
|
.trim()
|
|
66
69
|
.min(1)
|
|
67
70
|
.max(MAX_DESCRIPTION_CHARS)
|
|
68
|
-
.
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("What the feature or product does and who it is for (without featureId). The film's brief is written from this, so be specific."),
|
|
69
73
|
length: z
|
|
70
74
|
.enum(["short", "standard", "long"])
|
|
71
75
|
.default("standard")
|
|
@@ -76,7 +80,8 @@ export const launchTools = [
|
|
|
76
80
|
.describe("silent (music and motion carry it, the default) or voiceover (narrated, with captions; costs more)."),
|
|
77
81
|
music: z.enum(["score", "none"]).default("score").describe("score (a bed written to the film's length, the default) or none."),
|
|
78
82
|
musicMood: z.enum(MUSIC_MOODS).optional().describe("A mood for the score. Omit to let the planner choose."),
|
|
79
|
-
|
|
83
|
+
voice: z.enum(VOICE_NAMES).optional().describe(`The narrator of a voiceover film. Omit for the default. One of: ${VOICES}.`),
|
|
84
|
+
narrationStyle: z.enum(NARRATION_STYLES).optional().describe(`How a voiceover film tells its story. Omit for the default. One of: ${STYLES}.`),
|
|
80
85
|
direction: z
|
|
81
86
|
.string()
|
|
82
87
|
.trim()
|
|
@@ -88,14 +93,11 @@ export const launchTools = [
|
|
|
88
93
|
outputSchema: outputSchemas.create,
|
|
89
94
|
annotations: writeRemote,
|
|
90
95
|
handler: async (args, config) => {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
body: JSON.stringify({ brandId: args.brandId, title: args.title, description: args.description, typeId: null }),
|
|
94
|
-
});
|
|
95
|
-
const featureId = saved?.feature?.id;
|
|
96
|
-
if (typeof featureId !== "string")
|
|
97
|
-
throw new Error("tday could not start that film. Try again in a moment.");
|
|
96
|
+
const existing = typeof args.featureId === "string" ? args.featureId : null;
|
|
97
|
+
const featureId = existing ?? (await saveSubject(args, config));
|
|
98
98
|
const created = await api(config, safePath("/api/showcase/features/:id/create-launch-video", { id: featureId }), { method: "POST", body: JSON.stringify(launchAxes(args)) }).catch((error) => {
|
|
99
|
+
if (existing)
|
|
100
|
+
throw error;
|
|
99
101
|
throw new Error(`${error instanceof Error ? error.message : String(error)} ${subjectSaved(args.title)}`);
|
|
100
102
|
});
|
|
101
103
|
const videoId = created?.runFeatureId;
|
|
@@ -156,7 +158,6 @@ export const launchTools = [
|
|
|
156
158
|
const videoId = String(args.videoId);
|
|
157
159
|
const versionId = typeof args.versionId === "string" ? args.versionId : await newestReadyVersion(config, videoId);
|
|
158
160
|
const fps = args.fps === 30 ? 30 : 60;
|
|
159
|
-
// Asks for the file: the stored one, or its export is started (or joined while it renders).
|
|
160
161
|
const answer = await api(config, safePath("/api/showcase/videos/:videoId/render", args), {
|
|
161
162
|
method: "POST",
|
|
162
163
|
body: JSON.stringify({ versionId, ...(fps === 30 ? { fps } : {}), ...(args.retry ? { retry: true } : {}) }),
|
|
@@ -202,23 +203,30 @@ export const launchTools = [
|
|
|
202
203
|
},
|
|
203
204
|
},
|
|
204
205
|
];
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
206
|
+
async function saveSubject(args, config) {
|
|
207
|
+
if (typeof args.brandId !== "string" || typeof args.title !== "string" || typeof args.description !== "string") {
|
|
208
|
+
throw new Error("Pass featureId (from tday_list_features), or brandId with a title and description for a new subject.");
|
|
209
|
+
}
|
|
210
|
+
const saved = await api(config, "/api/showcase/features", {
|
|
211
|
+
method: "POST",
|
|
212
|
+
body: JSON.stringify({ brandId: args.brandId, title: args.title, description: args.description, typeId: null }),
|
|
213
|
+
});
|
|
214
|
+
const featureId = saved?.feature?.id;
|
|
215
|
+
if (typeof featureId !== "string")
|
|
216
|
+
throw new Error("tday could not start that film. Try again in a moment.");
|
|
217
|
+
return featureId;
|
|
218
|
+
}
|
|
211
219
|
function subjectSaved(title) {
|
|
212
220
|
return `The subject "${String(title)}" was saved on the brand's Launch page, where a film can be made from it later.`;
|
|
213
221
|
}
|
|
214
|
-
/** The create route's body: the axes, with the optional ones only when they were given. */
|
|
215
222
|
function launchAxes(args) {
|
|
216
223
|
return {
|
|
217
224
|
length: args.length,
|
|
218
225
|
narration: args.narration,
|
|
219
226
|
music: args.music,
|
|
220
227
|
...(args.musicMood ? { musicMood: args.musicMood } : {}),
|
|
221
|
-
...(args.
|
|
228
|
+
...(args.voice ? { voiceId: args.voice } : {}),
|
|
229
|
+
...(args.narrationStyle ? { narrationStyle: args.narrationStyle } : {}),
|
|
222
230
|
...(args.direction ? { direction: args.direction } : {}),
|
|
223
231
|
};
|
|
224
232
|
}
|
|
@@ -233,11 +241,6 @@ async function newestReadyVersion(config, videoId) {
|
|
|
233
241
|
}
|
|
234
242
|
return versionId;
|
|
235
243
|
}
|
|
236
|
-
/**
|
|
237
|
-
* The review payload as a poll answer. The newest version decides the state (a revision in
|
|
238
|
-
* flight is in progress even while an older cut is watchable); `readyVersionId` is the newest
|
|
239
|
-
* cut that can be watched or downloaded, which a failed revision leaves in place.
|
|
240
|
-
*/
|
|
241
244
|
function summarizeLaunchVideo(videoId, payload, url) {
|
|
242
245
|
const status = stringOr(payload.feature?.status, "unknown");
|
|
243
246
|
const first = Array.isArray(payload.versions) ? payload.versions[0] : undefined;
|
|
@@ -246,9 +249,6 @@ function summarizeLaunchVideo(videoId, payload, url) {
|
|
|
246
249
|
const readyVersionId = stringOr(payload.video?.versionId, null);
|
|
247
250
|
const state = launchState(status, latestStatus);
|
|
248
251
|
const failureMessage = state === "failed" ? stringOr(latest?.failureMessage, null) : null;
|
|
249
|
-
// No `mp4Url` here: the review payload's MP4 is the stored file, the clean preview. The
|
|
250
|
-
// file an agent may take away is the MP4 tool's, which asks the render route for the
|
|
251
|
-
// DOWNLOAD (badged for a brand whose downloads carry the Free-tier badge).
|
|
252
252
|
const durationMs = typeof payload.video?.durationMs === "number" ? payload.video.durationMs : null;
|
|
253
253
|
return {
|
|
254
254
|
videoId,
|
|
@@ -266,7 +266,6 @@ function summarizeLaunchVideo(videoId, payload, url) {
|
|
|
266
266
|
next: nextStep(state, readyVersionId),
|
|
267
267
|
};
|
|
268
268
|
}
|
|
269
|
-
/** An export still rendering, or one that failed, as the MP4 tool's answer; null when the file is ready. */
|
|
270
269
|
function exportWaiting(answer, film) {
|
|
271
270
|
if (answer?.status === "rendering" || answer?.status === "none") {
|
|
272
271
|
const progress = typeof answer.progress === "number" ? answer.progress : null;
|
|
@@ -309,12 +308,6 @@ function nextStep(state, readyVersionId) {
|
|
|
309
308
|
function stringOr(value, fallback) {
|
|
310
309
|
return typeof value === "string" && value ? value : fallback;
|
|
311
310
|
}
|
|
312
|
-
/**
|
|
313
|
-
* Save the film inside the working directory. The same guards as tday_download_image
|
|
314
|
-
* (download-safety.ts: the tday CDN allowlist, the cwd sandbox, no symlinks, no silent
|
|
315
|
-
* overwrite), but streamed to disk rather than buffered, since a film is far larger than an
|
|
316
|
-
* image. A refused or interrupted download leaves no partial file behind.
|
|
317
|
-
*/
|
|
318
311
|
async function saveVideo(mp4Url, rawPath, overwrite) {
|
|
319
312
|
const url = validateImageUrl(mp4Url);
|
|
320
313
|
const filePath = await prepareOutputFilePath(rawPath, { overwrite });
|
package/dist/tools.js
CHANGED
|
@@ -4,15 +4,13 @@ import { z } from "zod";
|
|
|
4
4
|
import { api, firstImageUrl, normalizeWebsite, query, } from "./api-client.js";
|
|
5
5
|
import { MAX_IMAGE_BYTES, prepareOutputFilePath, validateImageUrl, } from "./download-safety.js";
|
|
6
6
|
import { argsObject, emptyArgsSchema, listArgsSchema, outputSchemas, readOnly, safeIdSchema, safePath, stripPresentationArgs, writeLocal, writeRemote, } from "./tool-schemas.js";
|
|
7
|
-
import { demoImportAvailable, demoTools } from "./tools-demo.js";
|
|
8
|
-
import { knowledgeTools } from "./tools-knowledge.js";
|
|
9
7
|
import { accountTools, imageFormatSchema } from "./tools-account.js";
|
|
10
8
|
import { launchTools } from "./tools-launch.js";
|
|
9
|
+
import { featureTools } from "./tools-features.js";
|
|
11
10
|
const PLATFORM_BADGE_NOTE = 'This image carries a "Made with tday.com" badge because the organisation is on the Free plan. The owner can remove it by upgrading (tday_whoami has the link).';
|
|
12
11
|
export { responseFormatFromArgs, safePath } from "./tool-schemas.js";
|
|
13
12
|
export { ALLOWED_IMAGE_HOSTS, MAX_IMAGE_BYTES, prepareOutputFilePath, sandboxFilePath, validateImageUrl, } from "./download-safety.js";
|
|
14
13
|
export { formatToolResultText, stringifyResult, toStructuredContent, } from "./result-format.js";
|
|
15
|
-
export { knowledgeTools, KNOWLEDGE_ENTRY_TYPES } from "./tools-knowledge.js";
|
|
16
14
|
export const tools = [
|
|
17
15
|
{
|
|
18
16
|
name: "tday_whoami",
|
|
@@ -181,8 +179,6 @@ export const tools = [
|
|
|
181
179
|
if ((args.designId && !args.generationId) || (!args.designId && args.generationId)) {
|
|
182
180
|
throw new Error("designId and generationId must be provided together.");
|
|
183
181
|
}
|
|
184
|
-
// The ids win: the export route answers the file a download gets, which for a brand whose
|
|
185
|
-
// downloads carry the Free-tier badge is a badged copy of the stored (clean) preview.
|
|
186
182
|
const imageUrl = hasIds
|
|
187
183
|
? await imageUrlForGeneration(args, config)
|
|
188
184
|
: String(args.imageUrl);
|
|
@@ -206,7 +202,6 @@ export const tools = [
|
|
|
206
202
|
!contentType.toLowerCase().startsWith("application/octet-stream")) {
|
|
207
203
|
throw new Error(`Download did not return an image (content-type: ${contentType})`);
|
|
208
204
|
}
|
|
209
|
-
// Stream with size cap to prevent OOM from oversized responses.
|
|
210
205
|
const chunks = [];
|
|
211
206
|
let totalBytes = 0;
|
|
212
207
|
const reader = res.body?.getReader();
|
|
@@ -249,24 +244,8 @@ export const tools = [
|
|
|
249
244
|
},
|
|
250
245
|
...accountTools,
|
|
251
246
|
...launchTools,
|
|
252
|
-
...
|
|
253
|
-
...demoTools,
|
|
247
|
+
...featureTools,
|
|
254
248
|
];
|
|
255
|
-
/**
|
|
256
|
-
* The tools this account is shown: every tool, minus the demo-import tools unless the
|
|
257
|
-
* account may import (tools-demo.ts). `tools` stays the full set so `callTool` can still
|
|
258
|
-
* name them; their routes refuse an account without access.
|
|
259
|
-
*/
|
|
260
|
-
export async function listedTools(config) {
|
|
261
|
-
if (await demoImportAvailable(config))
|
|
262
|
-
return tools;
|
|
263
|
-
const hidden = new Set(demoTools.map((tool) => tool.name));
|
|
264
|
-
return tools.filter((tool) => !hidden.has(tool.name));
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* The generation's DOWNLOAD at 1x, from the export route. The image a generation reports is
|
|
268
|
-
* the clean preview; the export route hands out the file that may leave tday.
|
|
269
|
-
*/
|
|
270
249
|
async function imageUrlForGeneration(args, config) {
|
|
271
250
|
const data = await api(config, safePath("/api/designs/:designId/generations/:generationId/export", args), { method: "POST", body: JSON.stringify({ scale: 1 }) });
|
|
272
251
|
const imageUrl = typeof data?.image?.url === "string" ? data.image.url : null;
|
|
@@ -284,10 +263,10 @@ function formatZodError(name, error) {
|
|
|
284
263
|
.join("; ");
|
|
285
264
|
return `Invalid arguments for ${name}: ${details}`;
|
|
286
265
|
}
|
|
287
|
-
export async function callTool(name, args, config) {
|
|
288
|
-
const tool =
|
|
266
|
+
export async function callTool(name, args, config, available = tools) {
|
|
267
|
+
const tool = available.find((t) => t.name === name);
|
|
289
268
|
if (!tool) {
|
|
290
|
-
throw new Error(`Unknown tool: ${name}. Available tools: ${
|
|
269
|
+
throw new Error(`Unknown tool: ${name}. Available tools: ${available.map((t) => t.name).join(", ")}`);
|
|
291
270
|
}
|
|
292
271
|
const parsed = tool.inputSchema.safeParse(args);
|
|
293
272
|
if (!parsed.success)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tday-com/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "MCP server and CLI installer for tday.com - connect Claude Code, Codex, Cursor, and other AI agents to your tday.com account.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -29,26 +29,16 @@
|
|
|
29
29
|
"node": ">=18"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
|
-
"build": "tsc -p tsconfig.json && chmod +x dist/cli.js",
|
|
33
|
-
"dev": "tsc -p tsconfig.json --watch",
|
|
32
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json && chmod +x dist/cli.js",
|
|
33
|
+
"dev": "tsc -p tsconfig.build.json --watch",
|
|
34
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
34
35
|
"test": "bun test",
|
|
35
36
|
"prepublishOnly": "bun run build",
|
|
36
37
|
"start": "node dist/cli.js serve"
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
39
40
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
40
|
-
"
|
|
41
|
-
"@opentelemetry/api-logs": "^0.222.0",
|
|
42
|
-
"@opentelemetry/auto-instrumentations-node": "^0.80.0",
|
|
43
|
-
"@opentelemetry/exporter-logs-otlp-http": "^0.222.0",
|
|
44
|
-
"@opentelemetry/exporter-metrics-otlp-http": "^0.222.0",
|
|
45
|
-
"@opentelemetry/exporter-trace-otlp-http": "^0.222.0",
|
|
46
|
-
"@opentelemetry/resources": "^2.11.0",
|
|
47
|
-
"@opentelemetry/sdk-logs": "^0.222.0",
|
|
48
|
-
"@opentelemetry/sdk-metrics": "^2.11.0",
|
|
49
|
-
"@opentelemetry/sdk-node": "^0.222.0",
|
|
50
|
-
"@opentelemetry/semantic-conventions": "^1.43.0",
|
|
51
|
-
"zod": "^3.25.76"
|
|
41
|
+
"zod": "^4.6.6"
|
|
52
42
|
},
|
|
53
43
|
"devDependencies": {
|
|
54
44
|
"@types/node": "^26.1.2",
|
package/dist/api-client.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { TdayConfig } from "./config.js";
|
|
2
|
-
export declare function api<T = unknown>(config: TdayConfig, path: string, init?: RequestInit): Promise<T>;
|
|
3
|
-
export declare function normalizeWebsite(raw: string): string;
|
|
4
|
-
export interface GenerationLike {
|
|
5
|
-
id?: string;
|
|
6
|
-
status?: string;
|
|
7
|
-
imageUrl?: string | null;
|
|
8
|
-
masterImageUrl?: string | null;
|
|
9
|
-
completedAt?: string | null;
|
|
10
|
-
panels?: Array<{
|
|
11
|
-
imageUrl?: string | null;
|
|
12
|
-
}>;
|
|
13
|
-
}
|
|
14
|
-
export interface StatusPollResponse {
|
|
15
|
-
status?: string;
|
|
16
|
-
complete?: {
|
|
17
|
-
generation?: GenerationLike;
|
|
18
|
-
message?: string;
|
|
19
|
-
} | null;
|
|
20
|
-
platformBadge?: boolean;
|
|
21
|
-
}
|
|
22
|
-
export declare function firstImageUrl(generation: GenerationLike | undefined | null): string | null;
|
|
23
|
-
export declare function query(params: Record<string, string | number | boolean | undefined>): string;
|
package/dist/cli.d.ts
DELETED
package/dist/config.d.ts
DELETED
package/dist/demo-import.d.ts
DELETED
package/dist/demo-import.js
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { open, realpath } from "node:fs/promises";
|
|
2
|
-
import { constants } from "node:fs";
|
|
3
|
-
import { resolve, relative, isAbsolute, dirname } from "node:path";
|
|
4
|
-
import { createHash } from "node:crypto";
|
|
5
|
-
import { z } from "zod";
|
|
6
|
-
import { api } from "./api-client.js";
|
|
7
|
-
const ASSET_NAME = /^(master-take\.mp4|final\.mp4|poster\.png|(?:voiceoverUrl|musicUrl|mixedAudioUrl|beat-\d+)\.mp3)$/;
|
|
8
|
-
const resultSchema = z.object({ projectId: z.string(), editorUrl: z.string().url(), reused: z.boolean() });
|
|
9
|
-
const prepareSchema = z.object({ reused: z.literal(false), ticket: z.string(), uploads: z.array(z.object({
|
|
10
|
-
name: z.string(), uploadUrl: z.string().url(), headers: z.record(z.string()),
|
|
11
|
-
})) });
|
|
12
|
-
function within(root, path) {
|
|
13
|
-
const rel = relative(root, path);
|
|
14
|
-
if (rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute(rel))
|
|
15
|
-
throw new Error("Demo files must remain inside the working directory and bundle");
|
|
16
|
-
}
|
|
17
|
-
async function readBounded(root, path, max) {
|
|
18
|
-
const actual = await realpath(path);
|
|
19
|
-
within(root, actual);
|
|
20
|
-
// Check the opened descriptor, not a preceding stat. O_NOFOLLOW rejects a
|
|
21
|
-
// last-moment symlink substitution. Parent directories are resolved first.
|
|
22
|
-
const file = await open(actual, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
23
|
-
try {
|
|
24
|
-
const info = await file.stat();
|
|
25
|
-
if (!info.isFile() || info.size <= 0 || info.size > max)
|
|
26
|
-
throw new Error("Demo file is empty or exceeds the size limit");
|
|
27
|
-
const bytes = Buffer.alloc(info.size + 1);
|
|
28
|
-
let size = 0;
|
|
29
|
-
while (size < bytes.length) {
|
|
30
|
-
const read = await file.read(bytes, size, bytes.length - size, null);
|
|
31
|
-
if (!read.bytesRead)
|
|
32
|
-
break;
|
|
33
|
-
size += read.bytesRead;
|
|
34
|
-
}
|
|
35
|
-
if (size !== info.size)
|
|
36
|
-
throw new Error("Demo file changed while reading");
|
|
37
|
-
return bytes.subarray(0, size);
|
|
38
|
-
}
|
|
39
|
-
finally {
|
|
40
|
-
await file.close();
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
44
|
-
export async function importDemoBundle(config, manifestPath, brandId, title) {
|
|
45
|
-
const cwd = await realpath(process.cwd());
|
|
46
|
-
const path = await realpath(resolve(cwd, manifestPath));
|
|
47
|
-
within(cwd, path);
|
|
48
|
-
const root = dirname(path);
|
|
49
|
-
const manifest = JSON.parse((await readBounded(root, path, 1024 * 1024)).toString("utf8"));
|
|
50
|
-
const files = z.record(z.enum(["video/mp4", "audio/mpeg", "image/png"])).parse(manifest.files);
|
|
51
|
-
if (Object.keys(files).length > 200)
|
|
52
|
-
throw new Error("Too many demo assets");
|
|
53
|
-
const assets = [];
|
|
54
|
-
let total = 0;
|
|
55
|
-
for (const name of Object.keys(files).sort()) {
|
|
56
|
-
if (!ASSET_NAME.test(name))
|
|
57
|
-
throw new Error("Unexpected demo asset name");
|
|
58
|
-
const bytes = await readBounded(root, resolve(root, name), 100 * 1024 * 1024);
|
|
59
|
-
total += bytes.length;
|
|
60
|
-
if (total > 500 * 1024 * 1024)
|
|
61
|
-
throw new Error("Demo bundle exceeds 500 MB");
|
|
62
|
-
assets.push({ name, contentType: files[name], sizeBytes: bytes.length, sha256: digest(bytes) });
|
|
63
|
-
}
|
|
64
|
-
const metadata = { brandId, title, manifest, assets };
|
|
65
|
-
if (Buffer.byteLength(JSON.stringify(metadata)) > 1024 * 1024 - 8192)
|
|
66
|
-
throw new Error("Demo metadata exceeds request limit");
|
|
67
|
-
const prepared = await api(config, "/api/studio/demo-import/prepare", { method: "POST", body: JSON.stringify(metadata) });
|
|
68
|
-
const reused = resultSchema.safeParse(prepared);
|
|
69
|
-
if (reused.success && reused.data.reused)
|
|
70
|
-
return reused.data;
|
|
71
|
-
const { ticket, uploads } = prepareSchema.parse(prepared);
|
|
72
|
-
if (uploads.length !== assets.length || new Set(uploads.map((u) => u.name)).size !== assets.length)
|
|
73
|
-
throw new Error("Invalid demo upload response");
|
|
74
|
-
for (const upload of uploads) {
|
|
75
|
-
const asset = assets.find((a) => a.name === upload.name);
|
|
76
|
-
if (!asset)
|
|
77
|
-
throw new Error("Unexpected demo upload asset");
|
|
78
|
-
const url = new URL(upload.uploadUrl);
|
|
79
|
-
if (url.protocol !== "https:" || url.username || url.password || url.port
|
|
80
|
-
|| !/^[a-z0-9.-]+\.s3\.[a-z0-9-]+\.amazonaws\.com$/.test(url.hostname))
|
|
81
|
-
throw new Error("Demo upload must use signed S3 storage");
|
|
82
|
-
const bytes = await readBounded(root, resolve(root, asset.name), 100 * 1024 * 1024);
|
|
83
|
-
if (bytes.length !== asset.sizeBytes || digest(bytes) !== asset.sha256)
|
|
84
|
-
throw new Error("Demo asset changed since upload preparation");
|
|
85
|
-
// Never forward tday auth or arbitrary server-provided headers to storage.
|
|
86
|
-
const headers = { "content-type": asset.contentType, "cache-control": "private, no-store",
|
|
87
|
-
"x-amz-checksum-sha256": Buffer.from(asset.sha256, "hex").toString("base64") };
|
|
88
|
-
if (Object.entries(headers).some(([key, value]) => upload.headers[key] !== value))
|
|
89
|
-
throw new Error("Invalid demo upload headers");
|
|
90
|
-
let response;
|
|
91
|
-
try {
|
|
92
|
-
response = await fetch(url, { method: "PUT", body: new Uint8Array(bytes), headers, redirect: "error", signal: AbortSignal.timeout(120000) });
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
throw new Error("Demo asset upload failed. Prepare again and retry.");
|
|
96
|
-
}
|
|
97
|
-
if (!response.ok)
|
|
98
|
-
throw new Error(`Demo asset upload failed (${response.status})`);
|
|
99
|
-
}
|
|
100
|
-
return resultSchema.parse(await api(config, "/api/studio/demo-import/complete", { method: "POST", body: JSON.stringify({ ...metadata, ticket }) }));
|
|
101
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export declare const ALLOWED_IMAGE_HOSTS: Set<string>;
|
|
2
|
-
export declare const MAX_IMAGE_BYTES: number;
|
|
3
|
-
export declare function validateImageUrl(raw: string): URL;
|
|
4
|
-
export declare function sandboxFilePath(raw: string): string;
|
|
5
|
-
export declare function prepareOutputFilePath(raw: string, options?: {
|
|
6
|
-
overwrite?: boolean;
|
|
7
|
-
}): Promise<string>;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export type AgentId = "claude-code" | "cursor" | "gemini-cli" | "codex";
|
|
2
|
-
export interface AgentPatchResult {
|
|
3
|
-
id: AgentId;
|
|
4
|
-
name: string;
|
|
5
|
-
action: "patched" | "skipped" | "manual";
|
|
6
|
-
configPath?: string;
|
|
7
|
-
message: string;
|
|
8
|
-
}
|
|
9
|
-
interface McpServerEntry {
|
|
10
|
-
command: string;
|
|
11
|
-
args: string[];
|
|
12
|
-
env?: Record<string, string>;
|
|
13
|
-
}
|
|
14
|
-
export declare function mcpCommand(): McpServerEntry;
|
|
15
|
-
export declare function patchAllAgents(): AgentPatchResult[];
|
|
16
|
-
export declare function previewAllAgents(): AgentPatchResult[];
|
|
17
|
-
export {};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
interface CallbackResult {
|
|
2
|
-
token: string;
|
|
3
|
-
state: string;
|
|
4
|
-
}
|
|
5
|
-
interface WaitOptions {
|
|
6
|
-
expectedState: string;
|
|
7
|
-
timeoutMs: number;
|
|
8
|
-
}
|
|
9
|
-
export declare function startCallbackServer(options: WaitOptions): Promise<{
|
|
10
|
-
port: number;
|
|
11
|
-
done: Promise<CallbackResult>;
|
|
12
|
-
}>;
|
|
13
|
-
export {};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export interface SlashCommandResult {
|
|
2
|
-
path: string;
|
|
3
|
-
installed: boolean;
|
|
4
|
-
}
|
|
5
|
-
/** A command's text by filename, without installing anything (tests read it without touching ~/.claude). */
|
|
6
|
-
export declare function slashCommandBody(filename: string): string | undefined;
|
|
7
|
-
export declare function installSlashCommands(): SlashCommandResult[];
|
|
8
|
-
export declare function cleanupLegacySlashCommands(commandsDir: string): string[];
|
|
9
|
-
export declare function installSlashCommand(): {
|
|
10
|
-
path: string;
|
|
11
|
-
installed: boolean;
|
|
12
|
-
};
|
package/dist/installer.d.ts
DELETED
package/dist/observability.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function startObservability(): void;
|
package/dist/observability.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
2
|
-
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
3
|
-
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
|
4
|
-
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
|
5
|
-
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
6
|
-
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
|
|
7
|
-
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
|
8
|
-
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
9
|
-
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, } from "@opentelemetry/semantic-conventions";
|
|
10
|
-
const SUPERLOG_ENDPOINT = "https://intake.superlog.sh";
|
|
11
|
-
const SUPERLOG_PUBLIC_TOKEN = "sl_public_IY6nDv2SVznCdynG-aIoRhUdDK1yRNFRBOB8TDagtEc";
|
|
12
|
-
const headers = { Authorization: `Bearer ${SUPERLOG_PUBLIC_TOKEN}` };
|
|
13
|
-
// Telemetry leaves the user's machine, so it is OFF unless they opt in with
|
|
14
|
-
// TDAY_MCP_TELEMETRY=1. It used to switch on whenever NODE_ENV was "production", which many
|
|
15
|
-
// shells set, without the user knowing. When on, it sends traces/metrics/logs of this process
|
|
16
|
-
// (tool calls and their outbound HTTP to tday) to our collector, never the token or file
|
|
17
|
-
// contents: auto-instrumentation records URLs and timings, and fs instrumentation is disabled.
|
|
18
|
-
const TELEMETRY_OPT_IN = process.env.TDAY_MCP_TELEMETRY === "1";
|
|
19
|
-
const resource = resourceFromAttributes({
|
|
20
|
-
[ATTR_SERVICE_NAME]: "tday-mcp",
|
|
21
|
-
[ATTR_SERVICE_VERSION]: "0.0.1",
|
|
22
|
-
});
|
|
23
|
-
let started = false;
|
|
24
|
-
export function startObservability() {
|
|
25
|
-
if (!TELEMETRY_OPT_IN)
|
|
26
|
-
return;
|
|
27
|
-
if (started)
|
|
28
|
-
return;
|
|
29
|
-
started = true;
|
|
30
|
-
const sdk = new NodeSDK({
|
|
31
|
-
resource,
|
|
32
|
-
traceExporter: new OTLPTraceExporter({
|
|
33
|
-
url: `${SUPERLOG_ENDPOINT}/v1/traces`,
|
|
34
|
-
headers,
|
|
35
|
-
}),
|
|
36
|
-
metricReader: new PeriodicExportingMetricReader({
|
|
37
|
-
exporter: new OTLPMetricExporter({
|
|
38
|
-
url: `${SUPERLOG_ENDPOINT}/v1/metrics`,
|
|
39
|
-
headers,
|
|
40
|
-
}),
|
|
41
|
-
exportIntervalMillis: 30000,
|
|
42
|
-
}),
|
|
43
|
-
logRecordProcessors: [
|
|
44
|
-
// @opentelemetry/sdk-logs 0.222 moved the exporter from a positional
|
|
45
|
-
// argument into BatchLogRecordProcessorOptions, matching the shape
|
|
46
|
-
// PeriodicExportingMetricReader already uses above.
|
|
47
|
-
new BatchLogRecordProcessor({
|
|
48
|
-
exporter: new OTLPLogExporter({
|
|
49
|
-
url: `${SUPERLOG_ENDPOINT}/v1/logs`,
|
|
50
|
-
headers,
|
|
51
|
-
}),
|
|
52
|
-
}),
|
|
53
|
-
],
|
|
54
|
-
instrumentations: [
|
|
55
|
-
getNodeAutoInstrumentations({
|
|
56
|
-
"@opentelemetry/instrumentation-fs": { enabled: false },
|
|
57
|
-
}),
|
|
58
|
-
],
|
|
59
|
-
});
|
|
60
|
-
sdk.start();
|
|
61
|
-
const shutdown = async () => {
|
|
62
|
-
try {
|
|
63
|
-
await sdk.shutdown();
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
// ignore
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
const shutdownAndExit = (signal) => async () => {
|
|
70
|
-
await shutdown();
|
|
71
|
-
// Re-raise default termination behavior so the process actually exits.
|
|
72
|
-
process.kill(process.pid, signal);
|
|
73
|
-
};
|
|
74
|
-
process.once("SIGTERM", shutdownAndExit("SIGTERM"));
|
|
75
|
-
process.once("SIGINT", shutdownAndExit("SIGINT"));
|
|
76
|
-
process.once("beforeExit", shutdown);
|
|
77
|
-
}
|
package/dist/package-info.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export declare const PACKAGE_NAME = "@tday-com/mcp";
|
|
2
|
-
/** This build's own version, read from the package.json it shipped in (dist/ and src/ both sit one level down). */
|
|
3
|
-
export declare function packageVersion(): string;
|
|
4
|
-
/**
|
|
5
|
-
* What an agent's MCP config launches: this exact version, not whatever `latest` is on the day
|
|
6
|
-
* the agent next starts. `npx -y <name>` re-resolves on every launch, so a bad publish (a
|
|
7
|
-
* compromised maintainer account, a mistaken tag) would reach every installed agent, and the
|
|
8
|
-
* token in ~/.tday/config.json with it, without anyone running anything. Pinned, an update only
|
|
9
|
-
* arrives when someone re-runs `install`, which writes the version it was itself run as.
|
|
10
|
-
*/
|
|
11
|
-
export declare function pinnedPackageSpec(): string;
|
package/dist/result-format.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { ResponseFormat } from "./tool-schemas.js";
|
|
2
|
-
export declare function stringifyResult(result: unknown): string;
|
|
3
|
-
export declare function toStructuredContent(result: unknown): Record<string, unknown>;
|
|
4
|
-
export declare function formatToolResultText(toolName: string, result: unknown, format: ResponseFormat): string;
|
package/dist/server.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function runServer(): Promise<void>;
|