one-design-mcp 0.2.0 → 0.2.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.
- package/README.md +14 -1
- package/dist/server.js +173 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -103,6 +103,17 @@ the same email (`sign_in`). The session is kept in `~/.config/one-design/mcp-acc
|
|
|
103
103
|
|
|
104
104
|
Needs Node.js 20 or newer.
|
|
105
105
|
|
|
106
|
+
**One command** (adds it to Claude Desktop, Cursor and Claude Code, wherever they're installed, and
|
|
107
|
+
keeps a `.bak` of any config it changes):
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npx -y one-design-mcp setup
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`--dry-run` shows what it would change, `--app desktop|cursor|code` picks one app, and `--remove`
|
|
114
|
+
takes it out again. An agent with a terminal (Claude Code, Cursor) can run it for you: ask it to
|
|
115
|
+
"set up the One Design MCP server with npx -y one-design-mcp setup".
|
|
116
|
+
|
|
106
117
|
**Claude Code**
|
|
107
118
|
|
|
108
119
|
```bash
|
|
@@ -129,6 +140,8 @@ claude mcp add one-design -- node /path/to/one-design/mcp/dist/server.js
|
|
|
129
140
|
|
|
130
141
|
The build takes the site's public Supabase address and anon key from `../.env.local` (or
|
|
131
142
|
`ONE_DESIGN_SUPABASE_URL` / `ONE_DESIGN_SUPABASE_ANON_KEY`) so sign-in and checkout work.
|
|
132
|
-
`ONE_DESIGN_PAYMENTS=off` makes a free-beta build with every tool open.
|
|
143
|
+
`ONE_DESIGN_PAYMENTS=off` makes a free-beta build with every tool open. These are build settings
|
|
144
|
+
only: at run time nothing a user sets changes the plan (no environment variables, and the plan is
|
|
145
|
+
never read back from the saved session; it's checked against the account and kept in memory). Link Lock's encryption is
|
|
133
146
|
the website's own (`src/lib/linklock.ts`), bundled at build time, so links made here open on
|
|
134
147
|
one.design. Plan rules mirror `src/lib/plans.ts` and `src/lib/plan.ts`: keep `src/plan.ts` in step.
|
package/dist/server.js
CHANGED
|
@@ -1,10 +1,122 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res, err) => function __init() {
|
|
5
|
+
if (err) throw err[0];
|
|
6
|
+
try {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
} catch (e) {
|
|
9
|
+
throw err = [e], e;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
3
12
|
var __export = (target, all) => {
|
|
4
13
|
for (var name in all)
|
|
5
14
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
15
|
};
|
|
7
16
|
|
|
17
|
+
// src/setup.ts
|
|
18
|
+
var setup_exports = {};
|
|
19
|
+
__export(setup_exports, {
|
|
20
|
+
setup: () => setup
|
|
21
|
+
});
|
|
22
|
+
import { promises as fs9, existsSync } from "node:fs";
|
|
23
|
+
import path9 from "node:path";
|
|
24
|
+
import os7 from "node:os";
|
|
25
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
26
|
+
function entry() {
|
|
27
|
+
return { command: npxPath(), args: ["-y", "one-design-mcp@latest"] };
|
|
28
|
+
}
|
|
29
|
+
function npxPath() {
|
|
30
|
+
try {
|
|
31
|
+
const found = execFileSync2(process.platform === "win32" ? "where" : "which", ["npx"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).split(/\r?\n/)[0].trim();
|
|
32
|
+
if (found) return found;
|
|
33
|
+
} catch {
|
|
34
|
+
}
|
|
35
|
+
const beside = path9.join(path9.dirname(process.execPath), process.platform === "win32" ? "npx.cmd" : "npx");
|
|
36
|
+
return existsSync(beside) ? beside : "npx";
|
|
37
|
+
}
|
|
38
|
+
function desktopConfig() {
|
|
39
|
+
const home = os7.homedir();
|
|
40
|
+
if (process.platform === "darwin") return path9.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
41
|
+
if (process.platform === "win32") return path9.join(process.env.APPDATA ?? path9.join(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
42
|
+
return path9.join(process.env.XDG_CONFIG_HOME ?? path9.join(home, ".config"), "Claude", "claude_desktop_config.json");
|
|
43
|
+
}
|
|
44
|
+
async function editJson(file, remove, dry) {
|
|
45
|
+
if (!existsSync(path9.dirname(file))) return "not installed here, skipped";
|
|
46
|
+
let config = {};
|
|
47
|
+
let before = "";
|
|
48
|
+
if (existsSync(file)) {
|
|
49
|
+
before = await fs9.readFile(file, "utf8");
|
|
50
|
+
try {
|
|
51
|
+
config = before.trim() ? JSON.parse(before) : {};
|
|
52
|
+
} catch {
|
|
53
|
+
return `its config isn\u2019t valid JSON, left alone: ${file}`;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const servers = { ...config.mcpServers ?? {} };
|
|
57
|
+
const had = NAME in servers;
|
|
58
|
+
if (remove) {
|
|
59
|
+
if (!had) return "not set up there, nothing to remove";
|
|
60
|
+
delete servers[NAME];
|
|
61
|
+
} else servers[NAME] = entry();
|
|
62
|
+
const after = JSON.stringify({ ...config, mcpServers: servers }, null, 2) + "\n";
|
|
63
|
+
if (after === before) return "already set up";
|
|
64
|
+
if (dry) return remove ? "would remove One Design" : had ? "would update One Design" : "would add One Design";
|
|
65
|
+
await fs9.mkdir(path9.dirname(file), { recursive: true });
|
|
66
|
+
if (before) await fs9.writeFile(`${file}.bak`, before);
|
|
67
|
+
await fs9.writeFile(file, after);
|
|
68
|
+
return `${remove ? "removed" : had ? "updated" : "added"} (${file}${before ? ", old copy saved as .bak" : ""})`;
|
|
69
|
+
}
|
|
70
|
+
function claudeCode(remove, dry) {
|
|
71
|
+
try {
|
|
72
|
+
execFileSync2(process.platform === "win32" ? "where" : "which", ["claude"], { stdio: "ignore" });
|
|
73
|
+
} catch {
|
|
74
|
+
return "not installed here, skipped";
|
|
75
|
+
}
|
|
76
|
+
const list = (() => {
|
|
77
|
+
try {
|
|
78
|
+
return execFileSync2("claude", ["mcp", "list"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
79
|
+
} catch {
|
|
80
|
+
return "";
|
|
81
|
+
}
|
|
82
|
+
})();
|
|
83
|
+
const had = new RegExp(`^${NAME}:`, "m").test(list);
|
|
84
|
+
if (remove) {
|
|
85
|
+
if (!had) return "not set up there, nothing to remove";
|
|
86
|
+
if (dry) return "would remove One Design";
|
|
87
|
+
execFileSync2("claude", ["mcp", "remove", NAME, "--scope", "user"], { stdio: "ignore" });
|
|
88
|
+
return "removed";
|
|
89
|
+
}
|
|
90
|
+
if (had) return "already set up";
|
|
91
|
+
if (dry) return "would add One Design (for every project)";
|
|
92
|
+
execFileSync2("claude", ["mcp", "add", "--scope", "user", NAME, "--", "npx", "-y", "one-design-mcp@latest"], { stdio: "ignore" });
|
|
93
|
+
return "added (for every project)";
|
|
94
|
+
}
|
|
95
|
+
async function setup(argv) {
|
|
96
|
+
const dry = argv.includes("--dry-run");
|
|
97
|
+
const remove = argv.includes("--remove");
|
|
98
|
+
const only = argv[argv.indexOf("--app") + 1];
|
|
99
|
+
const want = (app) => !argv.includes("--app") || only === app;
|
|
100
|
+
const lines = [];
|
|
101
|
+
if (want("desktop")) lines.push(`Claude Desktop: ${await editJson(desktopConfig(), remove, dry)}`);
|
|
102
|
+
if (want("cursor")) lines.push(`Cursor: ${await editJson(cursorConfig(), remove, dry)}`);
|
|
103
|
+
if (want("code")) lines.push(`Claude Code: ${claudeCode(remove, dry)}`);
|
|
104
|
+
console.log(`One Design for Claude${dry ? " (dry run, nothing changed)" : ""}
|
|
105
|
+
|
|
106
|
+
${lines.map((l) => ` ${l}`).join("\n")}
|
|
107
|
+
`);
|
|
108
|
+
if (!dry && !remove && lines.some((l) => /added|updated/.test(l)))
|
|
109
|
+
console.log("Next: quit and reopen the app (on a Mac, \u2318Q), then ask Claude \u201CWhat can One Design do?\u201D.\nOther MCP apps: add a server with the command npx and the arguments -y one-design-mcp@latest.");
|
|
110
|
+
}
|
|
111
|
+
var NAME, cursorConfig;
|
|
112
|
+
var init_setup = __esm({
|
|
113
|
+
"src/setup.ts"() {
|
|
114
|
+
"use strict";
|
|
115
|
+
NAME = "one-design";
|
|
116
|
+
cursorConfig = () => path9.join(os7.homedir(), ".cursor", "mcp.json");
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
8
120
|
// src/server.ts
|
|
9
121
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
122
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -15,16 +127,33 @@ import path3 from "node:path";
|
|
|
15
127
|
import os3 from "node:os";
|
|
16
128
|
|
|
17
129
|
// src/images.ts
|
|
18
|
-
import
|
|
19
|
-
import { promises as fs } from "node:fs";
|
|
130
|
+
import sharpLib from "sharp";
|
|
131
|
+
import { promises as fs, rmSync, statSync } from "node:fs";
|
|
20
132
|
import path from "node:path";
|
|
21
133
|
import os from "node:os";
|
|
22
|
-
import { execFile } from "node:child_process";
|
|
134
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
23
135
|
import { promisify } from "node:util";
|
|
24
136
|
var EXT = { jpeg: ".jpg", png: ".png", webp: ".webp" };
|
|
25
137
|
var IMAGE_FILE = /\.(jpe?g|png|webp|avif|gif|tiff?|heic|heif)$/i;
|
|
26
138
|
var ToolError = class extends Error {
|
|
27
139
|
};
|
|
140
|
+
var heicCopies = /* @__PURE__ */ new Map();
|
|
141
|
+
process.once("exit", () => heicCopies.forEach((tmp) => rmSync(tmp, { force: true })));
|
|
142
|
+
function decodable(input) {
|
|
143
|
+
if (typeof input !== "string" || !/\.(heic|heif)$/i.test(input) || process.platform !== "darwin") return input;
|
|
144
|
+
const key = `${path.resolve(input)}:${statSync(input, { throwIfNoEntry: false })?.mtimeMs ?? 0}`;
|
|
145
|
+
const known = heicCopies.get(key);
|
|
146
|
+
if (known) return known;
|
|
147
|
+
const tmp = path.join(os.tmpdir(), `one-design-${process.pid}-${heicCopies.size}-${Math.random().toString(36).slice(2, 8)}.jpg`);
|
|
148
|
+
try {
|
|
149
|
+
execFileSync("sips", ["-s", "format", "jpeg", "-s", "formatOptions", "100", input, "--out", tmp], { stdio: "ignore" });
|
|
150
|
+
} catch {
|
|
151
|
+
throw new ToolError(`Couldn\u2019t open ${path.basename(input)} as a HEIC photo. It may be damaged.`);
|
|
152
|
+
}
|
|
153
|
+
heicCopies.set(key, tmp);
|
|
154
|
+
return tmp;
|
|
155
|
+
}
|
|
156
|
+
var sharp = Object.assign((input, options) => sharpLib(decodable(input), options), sharpLib);
|
|
28
157
|
async function openReadable(file) {
|
|
29
158
|
if (!/\.(heic|heif)$/i.test(file)) return { path: file, done: async () => void 0 };
|
|
30
159
|
if (process.platform !== "darwin") throw new ToolError(`${path.basename(file)} is a HEIC photo, which can only be opened here on a Mac. Convert it at https://one.design/heic-convert first.`);
|
|
@@ -206,9 +335,9 @@ import { promises as fs2 } from "node:fs";
|
|
|
206
335
|
import path2 from "node:path";
|
|
207
336
|
import os2 from "node:os";
|
|
208
337
|
import { exec } from "node:child_process";
|
|
209
|
-
var SB_URL =
|
|
210
|
-
var SB_KEY =
|
|
211
|
-
var PAYMENTS = (
|
|
338
|
+
var SB_URL = true ? "https://jaznopltkefnwooyksrk.supabase.co" : "";
|
|
339
|
+
var SB_KEY = true ? "sb_publishable_WfXjMAOVoXE9dtJMfJCshw_9rfMboU7" : "";
|
|
340
|
+
var PAYMENTS = (true ? "on" : "on").toLowerCase();
|
|
212
341
|
var SITE = "https://one.design";
|
|
213
342
|
var PRICING_URL = `${SITE}/pricing`;
|
|
214
343
|
var ACCOUNT_URL = `${SITE}/account`;
|
|
@@ -259,18 +388,19 @@ function isPaid(s) {
|
|
|
259
388
|
if (["active", "on_trial", "past_due"].includes(s.status)) return true;
|
|
260
389
|
return s.status === "cancelled" && !!s.ends_at && new Date(s.ends_at) > /* @__PURE__ */ new Date();
|
|
261
390
|
}
|
|
391
|
+
var checked = null;
|
|
262
392
|
async function currentPlan(force = false) {
|
|
263
393
|
if (freeBeta()) return { plan: "pro", label: "Free beta: every tool is open to everyone for now" };
|
|
264
394
|
if (!accountsAvailable()) return { plan: "free", label: "Free (sign-in isn\u2019t set up in this build)" };
|
|
265
395
|
const s = await freshSession();
|
|
266
396
|
if (!s) return { plan: "free", label: "Free (not signed in)" };
|
|
267
|
-
if (!force &&
|
|
397
|
+
if (!force && checked && checked.user === s.user_id && Date.now() - checked.at < 10 * 60 * 1e3) return { plan: checked.plan, email: s.email, label: checked.label };
|
|
268
398
|
const r = await sb(`/rest/v1/subscriptions?select=plan,status,ends_at&user_id=eq.${s.user_id}`, { token: s.access_token });
|
|
269
399
|
const rows = r.ok ? await r.json() : [];
|
|
270
400
|
const sub = rows[0] ?? null;
|
|
271
401
|
const paid = isPaid(sub);
|
|
272
402
|
const label = paid ? sub.plan === "lifetime" ? "Pro (Lifetime)" : "Pro" : "Free";
|
|
273
|
-
|
|
403
|
+
if (r.ok) checked = { user: s.user_id, plan: paid ? "pro" : "free", label, at: Date.now() };
|
|
274
404
|
return { plan: paid ? "pro" : "free", email: s.email, label };
|
|
275
405
|
}
|
|
276
406
|
async function sendCode(email) {
|
|
@@ -289,6 +419,7 @@ async function verifyCode(email, code) {
|
|
|
289
419
|
return currentPlan(true);
|
|
290
420
|
}
|
|
291
421
|
async function signOut() {
|
|
422
|
+
checked = null;
|
|
292
423
|
await writeSession(null);
|
|
293
424
|
}
|
|
294
425
|
async function checkoutUrl() {
|
|
@@ -307,6 +438,12 @@ async function billingPortalUrl() {
|
|
|
307
438
|
const j = await r.json().catch(() => ({}));
|
|
308
439
|
return j.url ?? ACCOUNT_URL;
|
|
309
440
|
}
|
|
441
|
+
async function librariesGrandfathered() {
|
|
442
|
+
const s = await freshSession();
|
|
443
|
+
if (!s) return false;
|
|
444
|
+
const r = await sb("/rest/v1/rpc/libraries_grandfathered", { method: "POST", body: "{}", token: s.access_token });
|
|
445
|
+
return r.ok && await r.json().catch(() => false) === true;
|
|
446
|
+
}
|
|
310
447
|
async function brandLibraries() {
|
|
311
448
|
const s = await freshSession();
|
|
312
449
|
if (!s) throw new Error("Sign in first (sign_in), with the email you use on one.design.");
|
|
@@ -320,7 +457,7 @@ function openInBrowser(url) {
|
|
|
320
457
|
}
|
|
321
458
|
function proPitch(what) {
|
|
322
459
|
return [
|
|
323
|
-
`${what} is part of One Design Pro.`,
|
|
460
|
+
`${what} ${/^your |s$/i.test(what) ? "are" : "is"} part of One Design Pro.`,
|
|
324
461
|
`Pro opens every tool (here and on one.design), batches of up to ${LIMITS.pro.batch} files instead of ${LIMITS.free.batch}, ${LIMITS.pro.qrCodes} QR codes at a time, and Brand Libraries.`,
|
|
325
462
|
`Lifetime is \xA3${PRICES.lifetime} once (was \xA3${PRICES.lifetimeWas}), launch price, no subscription.`,
|
|
326
463
|
'Say "buy Pro" and I\u2019ll open checkout (buy_pro). Already bought it? Sign in with that email (sign_in) and it unlocks here.'
|
|
@@ -584,7 +721,9 @@ function register(t2) {
|
|
|
584
721
|
});
|
|
585
722
|
t2.tool({
|
|
586
723
|
name: "brand_library",
|
|
587
|
-
|
|
724
|
+
// Not gated by the usual Pro check: accounts that had libraries before they became Pro-only keep
|
|
725
|
+
// them (checked below, and enforced by the database)
|
|
726
|
+
siteId: "",
|
|
588
727
|
title: "Your Brand Libraries",
|
|
589
728
|
description: "Reads the Brand Libraries synced to the signed-in one.design account: colours (name and HEX), fonts, logos. Use the colours in other tools (QR codes, watermarks, store shots, bento). save_logos writes the logos to a folder so other tools can use them.",
|
|
590
729
|
openWorld: true,
|
|
@@ -593,7 +732,8 @@ function register(t2) {
|
|
|
593
732
|
save_logos: z.boolean().default(false).describe("Save the logos (and fonts) as files."),
|
|
594
733
|
output_dir: z.string().optional().describe("Where to save them. Default: ~/Downloads/one-design.")
|
|
595
734
|
},
|
|
596
|
-
async run(a) {
|
|
735
|
+
async run(a, ctx) {
|
|
736
|
+
if (ctx.plan !== "pro" && !await librariesGrandfathered()) return text(proPitch("Your Brand Libraries"), true);
|
|
597
737
|
let libs = await brandLibraries();
|
|
598
738
|
if (a.library) libs = libs.filter((l) => l.name.toLowerCase() === a.library.toLowerCase());
|
|
599
739
|
if (!libs.length) throw new ToolError(a.library ? `No Brand Library called ${a.library}.` : "This account has no Brand Libraries yet. Make one at https://one.design/brand.");
|
|
@@ -4989,9 +5129,9 @@ async function postage(img, o) {
|
|
|
4989
5129
|
const inset = bordered ? margin : Math.round(hole * 2.2);
|
|
4990
5130
|
const value = o.value.trim();
|
|
4991
5131
|
if (value) {
|
|
4992
|
-
const
|
|
4993
|
-
const tw = await measure(value,
|
|
4994
|
-
const pad =
|
|
5132
|
+
const fs10 = Math.round(unit * 0.09);
|
|
5133
|
+
const tw = await measure(value, fs10, 400, "serif");
|
|
5134
|
+
const pad = fs10 * 0.35;
|
|
4995
5135
|
const bx = Math.max(0, Math.round(margin + iw - tw - pad * 2 - (bordered ? 0 : inset)));
|
|
4996
5136
|
const by = Math.max(0, Math.round(inset));
|
|
4997
5137
|
const flat = await sharp(pic).flatten({ background: o.paper }).raw().toBuffer({ resolveWithObject: true });
|
|
@@ -5000,7 +5140,7 @@ async function postage(img, o) {
|
|
|
5000
5140
|
const x0 = Math.max(0, bx - margin);
|
|
5001
5141
|
const y0 = Math.max(0, by - margin);
|
|
5002
5142
|
const x1 = Math.min(iw, bx - margin + Math.round(tw + pad * 2));
|
|
5003
|
-
const y1 = Math.min(ih, by - margin + Math.round(
|
|
5143
|
+
const y1 = Math.min(ih, by - margin + Math.round(fs10 + pad * 1.2));
|
|
5004
5144
|
for (let y = y0; y < y1; y++)
|
|
5005
5145
|
for (let x = x0; x < x1; x++) {
|
|
5006
5146
|
const i = (y * iw + x) * flat.info.channels;
|
|
@@ -5008,18 +5148,18 @@ async function postage(img, o) {
|
|
|
5008
5148
|
cnt++;
|
|
5009
5149
|
}
|
|
5010
5150
|
const under = cnt ? sum / cnt : 255;
|
|
5011
|
-
body.push(text2(margin + iw - tw - pad - (bordered ? 0 : inset), fromTop(inset + pad * 0.6,
|
|
5151
|
+
body.push(text2(margin + iw - tw - pad - (bordered ? 0 : inset), fromTop(inset + pad * 0.6, fs10), value, { size: fs10, font: "serif", fill: under < 110 ? o.paper : inkOnPaper }));
|
|
5012
5152
|
}
|
|
5013
5153
|
const caption = o.caption.trim();
|
|
5014
5154
|
if (caption) {
|
|
5015
5155
|
if (band) {
|
|
5016
|
-
const
|
|
5017
|
-
body.push(text2(W / 2, fromMiddle(margin + ih + band / 2 + margin * 0.3,
|
|
5156
|
+
const fs10 = await fitSize(caption, W - margin * 2, Math.round(band * 0.6), 400, "serif");
|
|
5157
|
+
body.push(text2(W / 2, fromMiddle(margin + ih + band / 2 + margin * 0.3, fs10), caption, { size: fs10, font: "serif", fill: inkOnPaper, anchor: "middle" }));
|
|
5018
5158
|
} else {
|
|
5019
5159
|
const max = Math.round(unit * 0.085);
|
|
5020
|
-
const
|
|
5160
|
+
const fs10 = await fitSize(caption, W - inset * 2, max, 400, "serif");
|
|
5021
5161
|
defs.push(`<filter id="capshadow" x="-20%" y="-50%" width="140%" height="200%"><feDropShadow dx="0" dy="0" stdDeviation="${n(max * 0.2)}" flood-color="#000000" flood-opacity="0.6"/></filter>`);
|
|
5022
|
-
body.push(text2(W / 2, fromMiddle(H - inset - max * 0.55,
|
|
5162
|
+
body.push(text2(W / 2, fromMiddle(H - inset - max * 0.55, fs10), caption, { size: fs10, font: "serif", fill: o.paper, anchor: "middle", extra: 'filter="url(#capshadow)"' }));
|
|
5023
5163
|
}
|
|
5024
5164
|
}
|
|
5025
5165
|
const holes = [];
|
|
@@ -5056,8 +5196,8 @@ async function rubber(img, o, seed) {
|
|
|
5056
5196
|
image(gap + line2 * 3, gap + line2 * 3, iw, ih, dataUri2(inked, "image/png"))
|
|
5057
5197
|
];
|
|
5058
5198
|
if (band) {
|
|
5059
|
-
const
|
|
5060
|
-
body.push(text2(W / 2, fromMiddle(H - line2 * 3 - band / 2 - gap * 0.3,
|
|
5199
|
+
const fs10 = await fitSize(caption, W - line2 * 10, Math.round(band * 0.5), 700);
|
|
5200
|
+
body.push(text2(W / 2, fromMiddle(H - line2 * 3 - band / 2 - gap * 0.3, fs10), caption, { size: fs10, weight: 700, fill: o.ink, anchor: "middle" }));
|
|
5061
5201
|
}
|
|
5062
5202
|
const png = await rasterise(svgDoc(W, H, body.join(""))).png().toBuffer();
|
|
5063
5203
|
return wearOut(png, distress(W, H, o.wear / 100, seed));
|
|
@@ -5078,10 +5218,10 @@ async function seal(img, o, seed) {
|
|
|
5078
5218
|
];
|
|
5079
5219
|
const ringText = o.caption.trim().toUpperCase();
|
|
5080
5220
|
const ringR = (c - line2 + inner) / 2;
|
|
5081
|
-
const
|
|
5221
|
+
const fs10 = Math.round((c - line2 - inner) * 0.52);
|
|
5082
5222
|
const chars = [...ringText];
|
|
5083
5223
|
const widths2 = [];
|
|
5084
|
-
for (const ch of chars) widths2.push(await measure(ch,
|
|
5224
|
+
for (const ch of chars) widths2.push(await measure(ch, fs10, 700));
|
|
5085
5225
|
const total = widths2.reduce((a2, b) => a2 + b, 0);
|
|
5086
5226
|
const spread = chars.length ? Math.min(Math.PI * 1.7, total * 1.18 / ringR) : 0;
|
|
5087
5227
|
let a = -Math.PI / 2 - spread / 2;
|
|
@@ -5090,13 +5230,13 @@ async function seal(img, o, seed) {
|
|
|
5090
5230
|
a += step / 2;
|
|
5091
5231
|
const x = c + Math.cos(a) * ringR;
|
|
5092
5232
|
const y = c + Math.sin(a) * ringR;
|
|
5093
|
-
body.push(`<g transform="translate(${n(x)},${n(y)}) rotate(${n((a + Math.PI / 2) * 180 / Math.PI)})">${text2(0, fromMiddle(0,
|
|
5233
|
+
body.push(`<g transform="translate(${n(x)},${n(y)}) rotate(${n((a + Math.PI / 2) * 180 / Math.PI)})">${text2(0, fromMiddle(0, fs10), ch, { size: fs10, weight: 700, fill: o.ink, anchor: "middle" })}</g>`);
|
|
5094
5234
|
a += step / 2;
|
|
5095
5235
|
});
|
|
5096
5236
|
const star = (ang) => {
|
|
5097
5237
|
const x = c + Math.cos(ang) * ringR;
|
|
5098
5238
|
const y = c + Math.sin(ang) * ringR;
|
|
5099
|
-
const R =
|
|
5239
|
+
const R = fs10 * 0.4;
|
|
5100
5240
|
const pts = Array.from({ length: 10 }, (_, i) => {
|
|
5101
5241
|
const rr2 = i % 2 ? R * 0.42 : R;
|
|
5102
5242
|
const t2 = -Math.PI / 2 + i * Math.PI / 5;
|
|
@@ -6040,7 +6180,7 @@ ${notes.join("\n")}` : ""
|
|
|
6040
6180
|
const r = { x, y, w: cols.start[tile.col + tile.colSpan - 1] + cols.size[tile.col + tile.colSpan - 1] - x, h: rows.start[tile.row + tile.rowSpan - 1] + rows.size[tile.row + tile.rowSpan - 1] - y };
|
|
6041
6181
|
if (r.w < 1 || r.h < 1) continue;
|
|
6042
6182
|
defs.push(`<clipPath id="t${i}"><path d="${rr(r.x, r.y, r.w, r.h, Math.max(0, Math.min(a.radius, r.w / 2, r.h / 2)))}"/></clipPath>`);
|
|
6043
|
-
const parts = [`<rect x="${n(r.x)}" y="${n(r.y)}" width="${n(r.w)}" height="${n(r.h)}" fill="${tile.fill}"/>`];
|
|
6183
|
+
const parts = tile.kind === "photo" && tile.file ? [] : [`<rect x="${n(r.x)}" y="${n(r.y)}" width="${n(r.w)}" height="${n(r.h)}" fill="${tile.fill}"/>`];
|
|
6044
6184
|
if (tile.kind === "photo") {
|
|
6045
6185
|
if (tile.file) {
|
|
6046
6186
|
let href;
|
|
@@ -6203,7 +6343,12 @@ ${notes.join("\n")}` : "");
|
|
|
6203
6343
|
}
|
|
6204
6344
|
|
|
6205
6345
|
// src/server.ts
|
|
6206
|
-
var VERSION = "0.2.
|
|
6346
|
+
var VERSION = "0.2.2";
|
|
6347
|
+
if (process.argv[2] === "setup") {
|
|
6348
|
+
const { setup: setup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
|
|
6349
|
+
await setup2(process.argv.slice(3));
|
|
6350
|
+
process.exit(0);
|
|
6351
|
+
}
|
|
6207
6352
|
var server = new McpServer({ name: "one-design", title: "One Design", version: VERSION, websiteUrl: "https://one.design" }, { instructions: INSTRUCTIONS });
|
|
6208
6353
|
var t = toolkit(server);
|
|
6209
6354
|
for (const m of [basics_exports, optimise_exports, privacy_docs_exports, brand_exports, account_exports]) m.register(t);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "one-design-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Every one.design tool for Claude and other MCP apps: compress, resize, convert, privacy, brand, store screenshots, PDFs and more, all on your own computer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|