@tday-com/mcp 0.3.1 → 0.4.2

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.
@@ -1,321 +0,0 @@
1
- import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
- const commands = [
5
- {
6
- filename: "tday.md",
7
- body: `---
8
- name: tday
9
- description: Interact with your tday.com account. Generate designs, manage brands, check credits.
10
- ---
11
-
12
- You are the tday.com assistant. You ONLY use tday.com MCP tools (tday_*). Do NOT use Bash, Read, Glob, Grep, Write, Edit, or any other local tools. Do NOT browse the filesystem, read source code, list directories, or run shell commands. You are scoped to the tday.com API only.
13
-
14
- If the user asks something outside your scope (codebase questions, file operations, general coding), say: "I can only help with tday.com tasks like generating designs, managing brands, and checking credits. Ask me to design something."
15
-
16
- **Tools available:**
17
- - \`tday_whoami\` .. every organisation you belong to: plan, credits, what the plan includes
18
- - \`tday_get_credit_prices\` .. what each thing costs, to quote before starting
19
- - \`tday_get_usage\` .. what an organisation has spent this period
20
- - \`tday_list_brands\` / \`tday_get_brand\` .. brand library
21
- - \`tday_create_brand\` .. create brand from a website URL
22
- - \`tday_list_designs\` / \`tday_get_design\` .. design history
23
- - \`tday_list_features\` .. the brand's features, newest first, with the launch videos each has
24
- - \`tday_generate_design\` .. create a new design from a prompt
25
- - \`tday_poll_generation\` .. poll generation status until done
26
- - \`tday_download_image\` .. save a generated image to a local file
27
-
28
- **When generating a design:**
29
- 1. Resolve the brand via \`tday_list_brands\`. Fuzzy-match the user's text against brand names. If no brands exist, offer to create one with \`tday_create_brand\`.
30
- 2. Tell the user it costs 10 credits from the brand's organisation, then call \`tday_generate_design\` with { prompt, brandId, name } (add format only if they asked for a size).
31
- 3. Poll \`tday_poll_generation\` repeatedly. Do NOT use sleep or Bash between polls. Show one compact line per poll with a spinner: \`◐ Crafting layout...\`, \`◓ Rendering visuals...\`, etc. Map status to human phrases. No paragraphs, no "still generating", no "taking longer". Designs take 30-90s.
32
- 4. When completed: show ONLY the \`editorUrl\` as a clickable link. NEVER show the raw imageUrl, it is an internal CDN link and looks unprofessional. If the user wants to save the image locally, use \`tday_download_image\` with designId + generationId and a local file path.
33
-
34
- **When creating a brand:**
35
- - Accept bare domains (stripe.com) or full URLs.
36
- - Call \`tday_create_brand\` with { website }. It auto-extracts colors, fonts, logos.
37
- - Show what was extracted: color count, font families, logo count.
38
-
39
- **Formatting:** Keep responses tight. Show URLs as clickable markdown links. No em dashes.
40
-
41
- **STRICT RULES (violations break user trust):**
42
- - ONLY use tday_* MCP tools. NEVER use Bash, Read, Glob, Grep, Write, Edit, Agent, or any other tool.
43
- - NEVER mention: localhost, dev server, bun, npm, API keys, AI model or vendor names, workers, queues, pipelines, cold starts, rate limits, backend, rendering engine, or any implementation detail.
44
- - NEVER suggest: checking logs, restarting servers, debugging infrastructure, investigating code, or any developer action.
45
- - NEVER say: "stuck", "hanging", "slow", "taking longer than usual", "seems to be stuck", "might be an issue".
46
- - If a design is still generating: just say "Still creating your design..." and keep polling. That's it.
47
- - If a design failed: say "Generation didn't complete. Try a different prompt or check back later at [editorUrl]."
48
- - If tday says there are not enough credits or the plan does not include something: show tday's own sentence as it is, with the usage and billing links it gives, and do not retry. Only the organisation's owner can change the plan (tday_whoami says who that is). Credits are not sold on their own, so never suggest buying more.
49
- - NEVER offer to investigate code, look at the codebase, or step outside the tday.com scope.
50
- - NEVER display raw imageUrl or CDN links. They are internal infrastructure URLs. Only show the editorUrl. Use designId + generationId for \`tday_download_image\`.
51
- - You are the public face of tday.com. Speak like a polished product, not a developer debugging a side project.
52
- `,
53
- },
54
- {
55
- filename: "tday-design.md",
56
- body: `---
57
- name: tday-design
58
- description: Generate a design with tday.com. Pass a brand name and prompt, or just start and you'll be asked.
59
- ---
60
-
61
- You are the tday.com design assistant. ONLY use tday_* MCP tools. Do NOT use Bash, Read, Glob, Grep, Write, Edit, or any local tools. Do NOT browse the filesystem or run shell commands.
62
-
63
- This is a focused workflow: resolve brand, generate, poll, deliver.
64
-
65
- **Parse the user's input ($ARGUMENTS):**
66
- - If two quoted strings like \`"Acme" "Instagram post for summer sale"\`: first is brand name, second is the design prompt.
67
- - If one string: treat it as the design prompt and ask which brand to use (or pick the most recent one).
68
- - If empty: ask the user what they want to design and for which brand.
69
-
70
- **Steps:**
71
- 1. **Resolve brand.** Call \`tday_list_brands\`. Fuzzy-match the brand name the user gave. If ambiguous, show matches and ask. If no brands exist, tell the user to run \`/tday-brand\` first.
72
- 2. **Generate.** Call \`tday_generate_design\` with { prompt, brandId, name }. Use a short descriptive name derived from the prompt, for example "Summer Sale Post".
73
- 3. **Poll with progress.** Call \`tday_poll_generation\` repeatedly. Do NOT use sleep or Bash between polls. Just call the tool again directly.
74
-
75
- **Progress output:** After each poll, output a single compact progress line. Use these exact spinner characters to show life. Cycle through them on each poll:
76
- \`\`\`
77
- ◐ Crafting layout...
78
- ◓ Rendering visuals...
79
- ◑ AI reviewing composition...
80
- ◒ Polishing details...
81
- \`\`\`
82
- Map the API status to a human phrase:
83
- - pending -> "Queued, waiting for a slot..."
84
- - ideating -> "Brainstorming the concept..."
85
- - generating -> "Crafting the layout..."
86
- - visualising -> "Rendering visuals..."
87
- - reviewing -> "AI reviewing the composition..."
88
- - refining -> "Polishing the details..."
89
-
90
- Keep it to ONE line per poll. No paragraphs, no explanations, no "let me check again". Just the spinner + phrase. Do NOT say "still generating" or "taking longer than usual". Designs take 30-90 seconds. Be patient.
91
-
92
- 4. **Deliver.** When status is \`completed\`:
93
- \`\`\`
94
- ✓ Design ready!
95
-
96
- Open in editor: <editorUrl>
97
- \`\`\`
98
- NEVER show the raw imageUrl. It is an internal CDN link and looks unprofessional. Only show the editorUrl.
99
- Then ask: "Save locally?" If yes, call \`tday_download_image\` with { designId, generationId, filePath: "./tday-designs/<name>.png" }.
100
-
101
- 5. **If failed:** Show the error briefly. Suggest a simpler prompt. If tday says there are not enough credits, show its sentence as it is with the links it gives instead; do not retry. A failed design costs no credits.
102
-
103
- **Tone:** Tight. One line per update. No filler. You're a machine that makes designs, not a narrator.
104
-
105
- **STRICT RULES (violations break user trust):**
106
- - ONLY use tday_* MCP tools. NEVER use Bash, Read, Glob, Grep, Write, Edit, Agent, or any other tool.
107
- - NEVER mention: localhost, dev server, bun, npm, API keys, AI model or vendor names, workers, queues, pipelines, cold starts, rate limits, backend, rendering engine, or any implementation detail.
108
- - NEVER suggest: checking logs, restarting servers, debugging infrastructure, investigating code, or any developer action.
109
- - NEVER say: "stuck", "hanging", "slow", "taking longer than usual", "seems to be stuck", "might be an issue".
110
- - If a design is still generating: just say "Still creating your design..." and keep polling. Designs take 30-90 seconds. That is normal. Be patient.
111
- - If a design failed: say "Generation didn't complete. Try a different prompt or check back later at [editorUrl]."
112
- - NEVER offer to investigate code, look at the codebase, or step outside the tday.com scope.
113
- - NEVER display raw imageUrl or CDN links. They are internal infrastructure URLs. Only show the editorUrl. Use designId + generationId for \`tday_download_image\`.
114
- - You are the public face of tday.com. Speak like a polished product, not a developer debugging a side project.
115
- `,
116
- },
117
- {
118
- filename: "tday-brand.md",
119
- body: `---
120
- name: tday-brand
121
- description: Create a brand from a URL, list your brands, or view brand details.
122
- ---
123
-
124
- You are the tday.com brand assistant. ONLY use tday_* MCP tools. Do NOT use Bash, Read, Glob, Grep, Write, Edit, or any local tools.
125
-
126
- **Parse the user's input ($ARGUMENTS):**
127
- - If it looks like a URL or domain (contains a dot, e.g. "stripe.com" or "https://notion.so"): create a new brand from that website.
128
- - If it's "list" or empty: list all brands.
129
- - Otherwise: fuzzy-match it as a brand name and show details.
130
-
131
- **Creating a brand from URL:**
132
- 1. Call \`tday_create_brand\` with { website: "<the url>" }. The API auto-extracts colors, fonts, and logos from the site.
133
- 2. Show a summary of what was found:
134
- - Brand name (auto-detected)
135
- - Colors extracted (show hex values)
136
- - Fonts found (show family names)
137
- - Logos detected (count)
138
- 3. Show the link to the brand page on tday.com: \`https://tday.com/brands/<brandId>\`
139
- 4. Suggest: "Run \`/tday-design\` to create your first design with this brand."
140
-
141
- **Listing brands:**
142
- 1. Call \`tday_list_brands\`.
143
- 2. Show a clean table: name, color count, font count, industry.
144
- 3. If no brands: "No brands yet. Run \`/tday-brand example.com\` to create one from any website."
145
-
146
- **Viewing a brand:**
147
- 1. Call \`tday_list_brands\`, fuzzy-match the name.
148
- 2. Call \`tday_get_brand\` with the matched id.
149
- 3. Show: name, description, industry, colors (hex), fonts (families), logo count, knowledge base entries.
150
-
151
- **Tone:** Efficient. Brands are the foundation, designs come next. Guide the user toward their first design.
152
-
153
- **STRICT RULES:** ONLY use tday_* MCP tools. NEVER use Bash, Read, Glob, Grep, Write, Edit, Agent, or any other tool. NEVER mention localhost, dev servers, API keys, internal systems, or implementation details. NEVER offer to investigate code or debug infrastructure. You are the public face of tday.com.
154
- `,
155
- },
156
- {
157
- filename: "tday-credits.md",
158
- body: `---
159
- name: tday-credits
160
- description: Check your plan and credit balance.
161
- ---
162
-
163
- ONLY use tday_* MCP tools. NEVER use Bash, Read, Glob, Grep, Write, Edit, Agent, or any other tool. NEVER mention internal systems or implementation details. You are the public face of tday.com.
164
-
165
- Quick plan and credit check for tday.com.
166
-
167
- 1. Call \`tday_whoami\`.
168
- 2. For each organisation in \`organizations\` (the one in \`defaultOrganizationId\` first), show its \`summary\` sentence as it is. It already says the plan, the credits left, when they refresh, and whether the user can change the plan or who can. Credits are whole numbers: never convert them to dollars.
169
- 3. If an organisation's \`upgrade\` is present and the user asks what more they would get, list \`upgrade.unlocks\`. Only an owner can change the plan; for an admin or member, name the owner instead of telling them to upgrade.
170
- 4. If the user asks what something costs, call \`tday_get_credit_prices\`. Credits are not sold on their own, so never suggest buying more.
171
-
172
- Keep it to 3-5 lines per organisation. No filler.
173
- `,
174
- },
175
- {
176
- filename: "tday-ideas.md",
177
- body: `---
178
- name: tday-ideas
179
- description: Brainstorm design ideas for a brand, then generate the one you pick.
180
- ---
181
-
182
- You are a creative design strategist for tday.com. ONLY use tday_* MCP tools. Do NOT use Bash, Read, Glob, Grep, Write, Edit, or any local tools.
183
-
184
- **Parse the user's input ($ARGUMENTS):**
185
- - If a brand name is given: resolve it via \`tday_list_brands\`.
186
- - If empty: ask which brand, or pick the most recent one.
187
-
188
- **Steps:**
189
- 1. **Understand the brand.** Call \`tday_get_brand\` to see colors, fonts, industry, description, and any knowledge base entries. This gives you creative context.
190
- 2. **Brainstorm.** Suggest 5 design ideas tailored to this brand. Each idea should be:
191
- - A specific format (Instagram post, LinkedIn banner, event flyer, product card, story ad, etc.)
192
- - A concrete concept (not generic). Reference the brand's industry, colors, or identity.
193
- - One line each, numbered.
194
-
195
- Example for a fintech brand:
196
- 1. Instagram carousel, "3 Steps to Smarter Savings", using brand blues with data visualization style
197
- 2. LinkedIn banner, founder quote with geometric brand pattern overlay
198
- 3. Product launch card, new feature announcement with screenshot mockup
199
- 4. Story ad, "Did you know?" stat with bold typography on dark background
200
- 5. Event flyer, upcoming webinar with speaker photo placeholder and brand gradients
201
-
202
- 3. **Ask:** "Pick a number (or describe your own idea) and I'll generate it."
203
- 4. **Generate.** When the user picks, call \`tday_generate_design\` with a well-crafted prompt based on the chosen idea. Include specifics from the brand context (mention colors by name, reference the industry, include the brand name in the design).
204
- 5. **Poll and deliver.** Poll \`tday_poll_generation\` repeatedly (no sleep, no Bash). Show one spinner line per poll: \`◐ Crafting layout...\`, \`◓ Rendering visuals...\`. When done: show ONLY the editor URL (never the raw imageUrl, it is an internal CDN link). Offer to save locally via \`tday_download_image\` with designId + generationId.
205
-
206
- **Tone:** Creative, specific, opinionated. You have taste. Don't suggest generic "professional marketing material". Every idea should feel like it was designed for this specific brand.
207
-
208
- **STRICT RULES:** ONLY use tday_* MCP tools. NEVER use Bash, Read, Glob, Grep, Write, Edit, Agent, or any other tool. NEVER mention localhost, dev servers, API keys, AI model or vendor names, workers, queues, or any implementation detail. NEVER say "stuck", "hanging", "slow", or suggest debugging. If generation is in progress, just keep polling with a spinner line. If it fails, say "Generation didn't complete. Try a different prompt." You are the public face of tday.com.
209
- `,
210
- },
211
- {
212
- filename: "tday-launch-video.md",
213
- needs: "launchVideos",
214
- body: `---
215
- name: tday-launch-video
216
- description: Make a launch video in tday for a feature. Pass a feature name, or leave it empty for the newest feature without one.
217
- ---
218
-
219
- You are the tday.com launch video assistant. ONLY use tday_* MCP tools. Do NOT use Bash, Read, Glob, Grep, Write, Edit, or any local tools.
220
-
221
- **Which feature ($ARGUMENTS):**
222
- - A feature name: call \`tday_list_features\` with \`search\` set to it and use the match's id. If several match, show them and ask.
223
- - Empty: call \`tday_list_features\` with \`without: "launch_video"\`. Offer the newest feature it lists (name and when it was added), and the next two as alternatives.
224
- - If tday asks for a brand, call \`tday_list_brands\` and ask which one.
225
-
226
- **Steps:**
227
- 1. Quote the price before spending anything: \`tday_get_credit_prices\` (a standard 20s scored, silent film is 40 credits) and the balance from \`tday_whoami\`. Ask the user to confirm, and whether they want a voiceover or another length.
228
- 2. Call \`tday_create_launch_video\` with { featureId } and the options they chose.
229
- 3. Poll \`tday_poll_launch_video\` every 20-30 seconds, one short line per poll. A film takes about 2-5 minutes.
230
- 4. When it is ready, show the \`reviewUrl\` as a link and offer the MP4 (\`tday_get_launch_video_mp4\`).
231
-
232
- If tday says there are not enough credits or the plan does not include launch videos, show its sentence as it is, with the links it gives, and do not retry.
233
-
234
- **STRICT RULES:** ONLY use tday_* MCP tools. NEVER use Bash, Read, Glob, Grep, Write, Edit, Agent, or any other tool. NEVER mention internal systems or implementation details. You are the public face of tday.com.
235
- `,
236
- },
237
- {
238
- filename: "tday-demo.md",
239
- needs: "demos",
240
- body: `---
241
- name: tday-demo
242
- description: Record a narrated demo video of a feature of this repo's app for tday. Pass a feature name, or leave it empty for the newest feature without a demo.
243
- ---
244
-
245
- Record a demo video for tday with the tday-capture MCP tools, from the app in this repo running on this machine. tday adds the voice, cursor, camera and style, and the demo arrives in tday ready to edit.
246
-
247
- **Which feature ($ARGUMENTS):**
248
- - A feature name or a tday feature id: call \`demo_features\` with \`search\` set to it and use the match's id.
249
- - Empty: call \`demo_features\` with \`without: "demo"\`. Show the newest few features that have no demo yet, recommend the newest, and ask which to record.
250
-
251
- **Steps:**
252
- 1. Call \`demo_guide\` first and follow it: it is how tday wants the demo made (a separate demo instance with throwaway data, narration, title and end cards).
253
- 2. Call \`demo_app_notes\` for how this product's apps were started before.
254
- 3. Start a SEPARATE demo instance of the app on a free port with throwaway data. Never restart the developer's own server or touch their data.
255
- 4. Call \`demo_start\` with the feature's id in \`featureIds\`, then one \`demo_beat\` per narrated line.
256
- 5. Call \`demo_review\` and show the developer the review page. Only after they say yes, \`demo_upload\` with the confirmation code, then \`demo_finish\`. Share the links it returns.
257
-
258
- If the tday-capture tools are not available, tell the developer to run \`curl -fsSL https://tday.com/install.sh | bash\` once and restart this session.
259
- `,
260
- },
261
- {
262
- filename: "tday-demos-needed.md",
263
- needs: "demos",
264
- body: `---
265
- name: tday-demos-needed
266
- description: List this product's features and docs pages that have no demo video yet in tday, then record the ones you pick.
267
- ---
268
-
269
- Use the tday-capture MCP tools.
270
-
271
- 1. Call \`demo_features\` with \`without: "demo"\`: the features with no demo yet, newest first.
272
- 2. Call \`demo_docs\`: the docs pages whose sections still need a demo. If tday says the product has no docs connected, skip this list quietly.
273
- 3. Show both lists compactly: each feature's name and when it was added; each docs page with how many of its sections need a video. Ask which to record.
274
- 4. For a feature, record it as /tday-demo does. For a docs page, follow what \`demo_docs\` says for that page. Run \`demo_features\` or \`demo_docs\` again at the end to show what is left.
275
-
276
- If the tday-capture tools are not available, tell the developer to run \`curl -fsSL https://tday.com/install.sh | bash\` once and restart this session.
277
- `,
278
- },
279
- ];
280
- export function slashCommandBody(filename) {
281
- return commands.find((command) => command.filename === filename)?.body;
282
- }
283
- const commandName = (filename) => filename.replace(/\.md$/, "");
284
- export function slashCommandsFor(access) {
285
- return commands.filter((cmd) => !cmd.needs || access[cmd.needs]).map((cmd) => cmd.filename);
286
- }
287
- export function installSlashCommands(access = { demos: false, launchVideos: false }, home = homedir()) {
288
- const wanted = new Set(slashCommandsFor(access));
289
- const included = commands.filter((cmd) => wanted.has(cmd.filename));
290
- const claudeDir = join(home, ".claude");
291
- if (!existsSync(claudeDir)) {
292
- return included.map((c) => ({
293
- name: commandName(c.filename),
294
- path: join(claudeDir, "commands", c.filename),
295
- installed: false,
296
- }));
297
- }
298
- const commandsDir = join(claudeDir, "commands");
299
- if (!existsSync(commandsDir)) {
300
- mkdirSync(commandsDir, { recursive: true });
301
- }
302
- for (const cmd of commands) {
303
- if (wanted.has(cmd.filename))
304
- continue;
305
- try {
306
- rmSync(join(commandsDir, cmd.filename), { force: true });
307
- }
308
- catch {
309
- }
310
- }
311
- return included.map((cmd) => {
312
- const path = join(commandsDir, cmd.filename);
313
- writeFileSync(path, cmd.body, { encoding: "utf8", mode: 0o644 });
314
- return { name: commandName(cmd.filename), path, installed: true };
315
- });
316
- }
317
- export function installSlashCommand(home = homedir()) {
318
- const results = installSlashCommands(undefined, home);
319
- const firstInstalled = results.find((r) => r.installed);
320
- return firstInstalled ?? { path: results[0]?.path ?? "", installed: false };
321
- }
package/dist/installer.js DELETED
@@ -1,150 +0,0 @@
1
- import { randomBytes } from "node:crypto";
2
- import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
3
- import { homedir } from "node:os";
4
- import { spawn } from "node:child_process";
5
- import { dirname, join } from "node:path";
6
- import { startCallbackServer } from "./installer-callback.js";
7
- import { patchAllAgents, previewAllAgents } from "./installer-agents.js";
8
- import { installSlashCommands } from "./installer-slash-command.js";
9
- import { capturePublished } from "./package-info.js";
10
- const WEB_URL = (process.env.TDAY_WEB_URL ?? "https://tday.com").replace(/\/+$/, "");
11
- const API_URL = (process.env.TDAY_API_URL ?? WEB_URL).replace(/\/+$/, "").replace(/\/api$/i, "");
12
- const TIMEOUT_MS = 5 * 60 * 1000;
13
- export async function runInstaller(args) {
14
- const skipBrowser = args.includes("--no-browser");
15
- const dryRun = args.includes("--dry-run");
16
- console.log("tday MCP installer\n");
17
- if (dryRun) {
18
- console.log("Dry run - no files will be written, no browser opened.\n");
19
- const results = previewAllAgents({ capture: true });
20
- for (const r of results) {
21
- console.log(` [${r.action}] ${r.name} - ${r.message}`);
22
- }
23
- return;
24
- }
25
- const state = randomBytes(16).toString("hex");
26
- const clientName = detectClientLabel();
27
- console.log("1. Starting local callback listener…");
28
- const { port, done } = await startCallbackServer({
29
- expectedState: state,
30
- timeoutMs: TIMEOUT_MS,
31
- });
32
- const callbackUrl = `http://127.0.0.1:${port}/cb`;
33
- const authUrl = `${WEB_URL}/cli/auth` +
34
- `?callback=${encodeURIComponent(callbackUrl)}` +
35
- `&state=${encodeURIComponent(state)}` +
36
- `&client=${encodeURIComponent(clientName)}`;
37
- console.log("2. Sign in to tday in your browser to authorize this MCP connection:\n");
38
- console.log(` ${authUrl}\n`);
39
- if (!skipBrowser) {
40
- openBrowser(authUrl);
41
- }
42
- console.log("3. Waiting for authorization (timeout 5 min)…\n");
43
- let token;
44
- try {
45
- const result = await done;
46
- token = result.token;
47
- }
48
- catch (err) {
49
- console.error(`\nAuthorization failed: ${err.message}`);
50
- process.exit(1);
51
- }
52
- console.log("4. Verifying account link…");
53
- const ok = await verifyToken(token);
54
- if (!ok) {
55
- console.error(`Account link verification failed against ${API_URL}/api/billing/info. Nothing was saved.`);
56
- process.exit(1);
57
- }
58
- saveConfig(token);
59
- console.log(` Saved secure local connection to ${configPath()} (chmod 0600)\n`);
60
- const access = await accountAccess(token);
61
- console.log("5. Patching installed AI agents (Claude Code, Cursor, Gemini CLI, Codex, VS Code)…");
62
- const results = patchAllAgents({ capture: access.capture });
63
- for (const r of results) {
64
- const tag = r.action === "patched" ? "+" : r.action === "manual" ? "!" : "-";
65
- console.log(` ${tag} ${r.name}: ${r.message}`);
66
- }
67
- console.log("\n6. Installing Claude Code slash commands...");
68
- const cmds = installSlashCommands(access);
69
- for (const cmd of cmds.filter((c) => c.installed))
70
- console.log(` + ${cmd.path}`);
71
- if (cmds.some((cmd) => !cmd.installed))
72
- console.log(" - Skipped (Claude Code not found)");
73
- const names = cmds.filter((cmd) => cmd.installed).map((cmd) => `/${cmd.name}`);
74
- console.log(`\nDone. Restart your AI agent.${names.length ? ` In Claude Code, type / to see: ${names.join(", ")}` : ""}`);
75
- }
76
- async function accountAccess(token) {
77
- const read = async (path) => {
78
- try {
79
- const res = await fetch(`${API_URL}${path}`, { headers: { authorization: `Bearer ${token}`, accept: "application/json" } });
80
- if (!res.ok)
81
- return null;
82
- const body = (await res.json());
83
- return typeof body === "object" && body !== null && !Array.isArray(body) ? body : null;
84
- }
85
- catch {
86
- return null;
87
- }
88
- };
89
- const [me, account, published] = await Promise.all([read("/api/capture/me"), read("/api/v1/account"), capturePublished()]);
90
- const brands = published && Array.isArray(me?.brands) ? me.brands : [];
91
- const features = account?.features;
92
- return {
93
- capture: brands.length > 0,
94
- demos: brands.some((brand) => brand?.localDemos === true),
95
- launchVideos: features?.launchVideos === true,
96
- };
97
- }
98
- function configPath() {
99
- return join(homedir(), ".tday", "config.json");
100
- }
101
- function saveConfig(token) {
102
- const path = configPath();
103
- const dir = dirname(path);
104
- if (!existsSync(dir))
105
- mkdirSync(dir, { recursive: true, mode: 0o700 });
106
- chmodSync(dir, 0o700);
107
- writeFileSync(path, JSON.stringify({ token, apiBaseUrl: API_URL, webBaseUrl: WEB_URL }, null, 2) + "\n", { encoding: "utf8", mode: 0o600 });
108
- chmodSync(path, 0o600);
109
- }
110
- async function verifyToken(token) {
111
- try {
112
- const res = await fetch(`${API_URL}/api/billing/info`, {
113
- headers: { authorization: `Bearer ${token}` },
114
- });
115
- return res.ok;
116
- }
117
- catch {
118
- return false;
119
- }
120
- }
121
- function detectClientLabel() {
122
- if (process.env.TDAY_CLIENT_LABEL)
123
- return process.env.TDAY_CLIENT_LABEL;
124
- if (process.env.TERM_PROGRAM === "Claude Code")
125
- return "claude-code";
126
- if (process.env.CURSOR_TRACE_ID)
127
- return "cursor";
128
- return "tday-cli";
129
- }
130
- export function openBrowser(url) {
131
- const platform = process.platform;
132
- const warn = () => console.warn("Could not open the browser. Open the printed URL manually to continue.");
133
- try {
134
- let child;
135
- if (platform === "darwin") {
136
- child = spawn("open", [url], { detached: true, stdio: "ignore" });
137
- }
138
- else if (platform === "win32") {
139
- child = spawn("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" });
140
- }
141
- else {
142
- child = spawn("xdg-open", [url], { detached: true, stdio: "ignore" });
143
- }
144
- child.once("error", warn);
145
- child.unref();
146
- }
147
- catch {
148
- warn();
149
- }
150
- }
@@ -1,23 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- export const PACKAGE_NAME = "@tday-com/mcp";
3
- export function packageVersion() {
4
- const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
5
- return pkg.version;
6
- }
7
- export function pinnedPackageSpec() {
8
- return `${PACKAGE_NAME}@${packageVersion()}`;
9
- }
10
- export const CAPTURE_PACKAGE_NAME = "@tday-com/capture";
11
- export const CAPTURE_PACKAGE_VERSION = "0.2.2";
12
- export function pinnedCaptureSpec() {
13
- return `${CAPTURE_PACKAGE_NAME}@${CAPTURE_PACKAGE_VERSION}`;
14
- }
15
- export async function capturePublished() {
16
- try {
17
- const res = await fetch(`https://registry.npmjs.org/${CAPTURE_PACKAGE_NAME.replace("/", "%2f")}/${CAPTURE_PACKAGE_VERSION}`);
18
- return res.ok;
19
- }
20
- catch {
21
- return false;
22
- }
23
- }
package/dist/prompts.js DELETED
@@ -1,36 +0,0 @@
1
- import { z } from "zod";
2
- export function featureArg(raw) {
3
- const text = (raw ?? "").replace(/["\r\n\t]+/g, " ").replace(/\s+/g, " ").trim().slice(0, 160);
4
- return text || null;
5
- }
6
- export function launchVideoPrompt(raw) {
7
- const feature = featureArg(raw);
8
- return [
9
- `Make a tday launch video of ${feature ? `the feature "${feature}"` : "the newest feature that has no launch video yet"}.`,
10
- feature
11
- ? `1. Find it with tday_list_features (search "${feature}") and use its id. If several match, show them and ask which.`
12
- : '1. Call tday_list_features with without "launch_video" and offer the newest feature it lists, with the next two as alternatives.',
13
- " If tday asks for a brand, list them with tday_list_brands and ask which.",
14
- "2. Before spending anything, quote the price (tday_get_credit_prices) and the balance (tday_whoami), and ask me to confirm and whether I want a voiceover or another length.",
15
- "3. Call tday_create_launch_video with the featureId and the options I chose, then poll tday_poll_launch_video every 20-30 seconds until it is ready.",
16
- "4. Show me the reviewUrl as a link and offer the MP4 (tday_get_launch_video_mp4).",
17
- ].join("\n");
18
- }
19
- export const FEATURES_PROMPT = [
20
- "Show me my tday features, newest first, and which have a launch video: call tday_list_features (if tday asks for a brand, list them with tday_list_brands and ask which).",
21
- "List each feature's name, when it was added, and its launch video: finished, being made, or none.",
22
- "Then offer to make a launch video of the newest feature that has none.",
23
- ].join("\n");
24
- const userText = (text) => ({ messages: [{ role: "user", content: { type: "text", text } }] });
25
- export function registerPrompts(server) {
26
- const registerPrompt = server.registerPrompt.bind(server);
27
- registerPrompt("launch-video", {
28
- title: "Launch video of a feature",
29
- description: "Make a launch video of a feature: the one you name, or the newest feature without one. Quotes the credits before it starts.",
30
- argsSchema: { feature: z.string().optional().describe("The feature's name. Leave empty for the newest feature without a launch video.") },
31
- }, ({ feature }) => userText(launchVideoPrompt(feature)));
32
- registerPrompt("features", {
33
- title: "Latest features",
34
- description: "The brand's newest features and which already have a launch video.",
35
- }, () => userText(FEATURES_PROMPT));
36
- }
@@ -1,130 +0,0 @@
1
- const MAX_TEXT_CHARS = 25_000;
2
- export function stringifyResult(result) {
3
- return JSON.stringify(result, (_key, value) => (typeof value === "bigint" ? value.toString() : value), 2);
4
- }
5
- export function toStructuredContent(result) {
6
- return isRecord(result) ? result : { result };
7
- }
8
- export function formatToolResultText(toolName, result, format, markdown) {
9
- const text = format === "markdown" ? markdownResult(toolName, result, markdown) : stringifyResult(result);
10
- return truncateText(text);
11
- }
12
- function isRecord(value) {
13
- return typeof value === "object" && value !== null && !Array.isArray(value);
14
- }
15
- function truncateText(text) {
16
- if (text.length <= MAX_TEXT_CHARS)
17
- return text;
18
- return `${text.slice(0, MAX_TEXT_CHARS)}\n\n[Response text truncated at ${MAX_TEXT_CHARS} characters. Use structuredContent or narrower list/get calls for the complete data.]`;
19
- }
20
- function markdownResult(toolName, result, markdown) {
21
- if (!isRecord(result))
22
- return String(result);
23
- if (markdown)
24
- return markdown(result);
25
- switch (toolName) {
26
- case "tday_whoami":
27
- return markdownWhoami(result);
28
- case "tday_list_brands":
29
- return markdownList("Brands", result.brands, ["id", "name", "role", "status", "industry"]);
30
- case "tday_list_designs":
31
- return markdownList("Designs", result.designs, ["id", "name", "brandName", "currentStep", "updatedAt"]);
32
- case "tday_generate_design":
33
- return [
34
- "# Generation queued",
35
- `- Design ID: ${result.designId ?? "unknown"}`,
36
- `- Generation ID: ${result.generationId ?? "unknown"}`,
37
- `- Status: ${result.status ?? "unknown"}`,
38
- result.editorUrl ? `- Editor: ${result.editorUrl}` : undefined,
39
- "",
40
- "Poll with `tday_poll_generation` until status is `completed` or `failed`.",
41
- ].filter(Boolean).join("\n");
42
- case "tday_poll_generation":
43
- return [
44
- "# Generation status",
45
- `- Design ID: ${result.designId ?? "unknown"}`,
46
- `- Generation ID: ${result.generationId ?? "unknown"}`,
47
- `- Status: ${result.status ?? "unknown"}`,
48
- result.imageUrl ? `- Image URL: ${result.imageUrl}` : undefined,
49
- result.editorUrl ? `- Editor: ${result.editorUrl}` : undefined,
50
- result.failureMessage ? `- Failure: ${result.failureMessage}` : undefined,
51
- ].filter(Boolean).join("\n");
52
- case "tday_download_image":
53
- return [
54
- "# Image downloaded",
55
- `- Saved: ${result.saved ? "yes" : "no"}`,
56
- `- Path: ${result.path ?? "unknown"}`,
57
- `- Size: ${result.size ?? 0} bytes`,
58
- ].join("\n");
59
- default:
60
- return `\`\`\`json\n${stringifyResult(result)}\n\`\`\``;
61
- }
62
- }
63
- function markdownWhoami(result) {
64
- const account = isRecord(result.account) ? result.account : {};
65
- const subscription = isRecord(result.subscription) ? result.subscription : {};
66
- return [
67
- "# tday account",
68
- `- Name: ${display(account.name)}`,
69
- `- Email: ${display(account.email)}`,
70
- `- Auth source: ${display(isRecord(result.auth) ? result.auth.source : undefined)}`,
71
- `- Plan: ${display(subscription.plan ?? subscription.name ?? subscription.tier)}`,
72
- balanceLine(result),
73
- ].join("\n");
74
- }
75
- function balanceLine(result) {
76
- const credits = isRecord(result.credits) ? result.credits : null;
77
- if (credits && credits.enabled === true) {
78
- if (typeof credits.balance !== "number")
79
- return `- Credits: ${display(credits.balance)}`;
80
- if (credits.grant === null)
81
- return "- Credits: usage is covered by your contract";
82
- const grant = typeof credits.grant === "number" ? ` of ${formatCount(credits.grant)}` : "";
83
- const refresh = typeof credits.refreshesAt === "string" ? `, refreshes ${formatDay(credits.refreshesAt)}` : "";
84
- const low = credits.lowBalance === "low" || credits.lowBalance === "critical" ? " (running low)" : "";
85
- return `- Credits: ${formatCount(credits.balance)}${grant} left${refresh}${low}`;
86
- }
87
- const legacy = isRecord(result.apiBalance) ? result.apiBalance : null;
88
- if (legacy && typeof legacy.balanceCents === "number") {
89
- return `- Prepaid balance: $${(legacy.balanceCents / 100).toFixed(2)}`;
90
- }
91
- return `- Balance: ${display(result.apiBalance)}`;
92
- }
93
- function formatCount(value) {
94
- return Math.trunc(value).toLocaleString("en-US");
95
- }
96
- function formatDay(iso) {
97
- const date = new Date(iso);
98
- if (Number.isNaN(date.getTime()))
99
- return iso;
100
- return date.toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" });
101
- }
102
- export function markdownList(title, value, columns) {
103
- const items = Array.isArray(value) ? value.filter(isRecord) : [];
104
- const lines = [`# ${title}`, ""];
105
- if (items.length === 0) {
106
- lines.push("No items returned.");
107
- return lines.join("\n");
108
- }
109
- lines.push(`Returned ${items.length} item${items.length === 1 ? "" : "s"}.`, "");
110
- lines.push(`| ${columns.join(" | ")} |`);
111
- lines.push(`| ${columns.map(() => "---").join(" | ")} |`);
112
- for (const item of items) {
113
- lines.push(`| ${columns.map((column) => escapeMarkdownCell(display(item[column]))).join(" | ")} |`);
114
- }
115
- return lines.join("\n");
116
- }
117
- export function display(value) {
118
- if (value === null || value === undefined || value === "")
119
- return "-";
120
- if (typeof value === "string" ||
121
- typeof value === "number" ||
122
- typeof value === "boolean" ||
123
- typeof value === "bigint") {
124
- return String(value);
125
- }
126
- return JSON.stringify(value, (_key, v) => (typeof v === "bigint" ? v.toString() : v));
127
- }
128
- function escapeMarkdownCell(value) {
129
- return value.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\n/g, " ");
130
- }