@linktr.ee/arbor-mcp 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +305 -46
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33,7 +33,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
33
33
|
var require_rules = __commonJS({
|
|
34
34
|
"../../docs/content/ux-writing/rules.json"(exports, module) {
|
|
35
35
|
module.exports = {
|
|
36
|
-
$schema: "./types.schema.json",
|
|
37
36
|
version: "1.0.0",
|
|
38
37
|
description: "Static lint rules for UX writing. Consumable by Lambda, Figma plugin, Claude skill, and CI scripts.",
|
|
39
38
|
rules: {
|
|
@@ -160,6 +159,86 @@ var require_rules = __commonJS({
|
|
|
160
159
|
pattern: "are you sure",
|
|
161
160
|
reason: "Patronizing. State consequences clearly instead.",
|
|
162
161
|
suggestion: "Replace with clear consequence statement: 'This will delete all data'"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
pattern: "\\btier\\b",
|
|
165
|
+
matchType: "regex",
|
|
166
|
+
label: "tier",
|
|
167
|
+
reason: 'Linktree plans are called "plans", not "tiers". "Tier" is internal language.',
|
|
168
|
+
suggestion: 'Use "plan" instead of "tier".'
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
pattern: "\\bPRO\\b",
|
|
172
|
+
matchType: "regex",
|
|
173
|
+
caseSensitive: true,
|
|
174
|
+
label: "PRO",
|
|
175
|
+
reason: 'Plan names use title case: "Pro", not "PRO".',
|
|
176
|
+
suggestion: 'Write the plan name as "Pro".'
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
pattern: "\\b(?:your|my|their|our|the|a|an|this|that)\\s+profiles?\\b(?!\\s+(?:photo|image|picture|avatar|url|link))",
|
|
180
|
+
matchType: "regex",
|
|
181
|
+
label: "profile",
|
|
182
|
+
reason: `Don't call a Linktree a generic "profile".`,
|
|
183
|
+
suggestion: 'Refer to it as your "Linktree" (the proper noun "Linktree Profile" is fine).'
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
pattern: "\\bpackage(s)?\\b",
|
|
187
|
+
matchType: "regex",
|
|
188
|
+
label: "package",
|
|
189
|
+
reason: 'Linktree plans are called "plans", not "packages".',
|
|
190
|
+
suggestion: 'Use "plan" instead of "package".'
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
pattern: "\\bbucket(s)?\\b",
|
|
194
|
+
matchType: "regex",
|
|
195
|
+
label: "bucket",
|
|
196
|
+
reason: 'Linktree plans are called "plans", not "buckets".',
|
|
197
|
+
suggestion: 'Use "plan" instead of "bucket".'
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
pattern: "\\busers?\\b(?!\\s*-?\\s*(?:friendly|name|names|generated|facing|flow|flows|journey|journeys|persona|personas|research|testing|experience|interface|story|stories))",
|
|
201
|
+
matchType: "regex",
|
|
202
|
+
label: "users",
|
|
203
|
+
reason: 'People who use Linktree are "Linkers", not "users".',
|
|
204
|
+
suggestion: 'Use "Linkers" \u2014 or just say "you".'
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
pattern: "\\bLink Tree\\b",
|
|
208
|
+
matchType: "regex",
|
|
209
|
+
label: "Link Tree",
|
|
210
|
+
reason: 'Linktree is one word: "Linktree", not "Link Tree".',
|
|
211
|
+
suggestion: 'Write it as "Linktree".'
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
pattern: "\\bLinkTree\\b",
|
|
215
|
+
matchType: "regex",
|
|
216
|
+
caseSensitive: true,
|
|
217
|
+
label: "LinkTree",
|
|
218
|
+
reason: 'Linktree capitalizes only the L: "Linktree", not "LinkTree".',
|
|
219
|
+
suggestion: 'Write it as "Linktree".'
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
pattern: "\\b(linkinbio|link-in-bio|bio link)\\b",
|
|
223
|
+
matchType: "regex",
|
|
224
|
+
label: "link-in-bio",
|
|
225
|
+
reason: `It's "link in bio" \u2014 three words, no hyphen.`,
|
|
226
|
+
suggestion: 'Use "link in bio" (not "linkinbio", "link-in-bio" or "bio link").'
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
pattern: "subscription",
|
|
230
|
+
reason: 'Linktree billing is a "plan", not a "subscription".',
|
|
231
|
+
suggestion: 'Use "plan". (Keep "subscribe"/"subscriber" for the visitor notification feature.)'
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
pattern: "utilize",
|
|
235
|
+
reason: "Formal and stiff. Plain language is friendlier.",
|
|
236
|
+
suggestion: 'Use "use" instead of "utilize".'
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
pattern: "facilitate",
|
|
240
|
+
reason: "Formal and stiff. Plain language is friendlier.",
|
|
241
|
+
suggestion: 'Use "help" instead of "facilitate".'
|
|
163
242
|
}
|
|
164
243
|
]
|
|
165
244
|
},
|
|
@@ -462,7 +541,7 @@ var require_patterns = __commonJS({
|
|
|
462
541
|
examples: {
|
|
463
542
|
do: [
|
|
464
543
|
"Update required. This version is no longer supported. Update now to continue.",
|
|
465
|
-
"
|
|
544
|
+
"Plan expired. Your account is paused. Renew your plan to restore access.",
|
|
466
545
|
"Verification needed. Confirm your email to access features. Check your inbox."
|
|
467
546
|
],
|
|
468
547
|
dont: [
|
|
@@ -511,7 +590,7 @@ var require_patterns = __commonJS({
|
|
|
511
590
|
do: [
|
|
512
591
|
"Changes saved",
|
|
513
592
|
"Email sent",
|
|
514
|
-
"
|
|
593
|
+
"Linktree updated",
|
|
515
594
|
"You're all set",
|
|
516
595
|
"You're officially a Pro!"
|
|
517
596
|
],
|
|
@@ -915,6 +994,7 @@ var require_engine = __commonJS({
|
|
|
915
994
|
var contentTypesData = require_content_types();
|
|
916
995
|
var rules = rulesData.rules;
|
|
917
996
|
var scoring = rulesData.scoring;
|
|
997
|
+
var MIN_CONTENT_TYPE_CONFIDENCE = 30;
|
|
918
998
|
var passiveVoiceRegex = new RegExp(
|
|
919
999
|
rules["passive-voice"].check.pattern,
|
|
920
1000
|
"gi"
|
|
@@ -931,6 +1011,15 @@ var require_engine = __commonJS({
|
|
|
931
1011
|
}
|
|
932
1012
|
compiledTextPatterns.set(typeId, regexes);
|
|
933
1013
|
}
|
|
1014
|
+
var compiledBannedPhrases = rules["banned-phrases"].phrases.map((phrase) => {
|
|
1015
|
+
if (phrase.matchType !== "regex") return phrase;
|
|
1016
|
+
try {
|
|
1017
|
+
const flags = phrase.caseSensitive ? "" : "i";
|
|
1018
|
+
return { ...phrase, _regex: new RegExp(phrase.pattern, flags) };
|
|
1019
|
+
} catch {
|
|
1020
|
+
return { ...phrase, _regex: null };
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
934
1023
|
function countSyllables(word) {
|
|
935
1024
|
const w = word.toLowerCase().replace(/[^a-z]/g, "");
|
|
936
1025
|
if (w.length <= 3) return 1;
|
|
@@ -946,34 +1035,42 @@ var require_engine = __commonJS({
|
|
|
946
1035
|
}
|
|
947
1036
|
function checkSentenceCase(text, issues) {
|
|
948
1037
|
const words = text.split(/\s+/);
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
if (
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1038
|
+
let sentenceStart = true;
|
|
1039
|
+
for (let i = 0; i < words.length; i++) {
|
|
1040
|
+
const raw = words[i];
|
|
1041
|
+
if (!raw) continue;
|
|
1042
|
+
const endsSentence = /[.!?][)\]"'\u2019\u201d]*$/.test(raw);
|
|
1043
|
+
if (!sentenceStart) {
|
|
1044
|
+
const word = raw.replace(/^[^a-zA-Z]+/, "").replace(/[^a-zA-Z]+$/, "");
|
|
1045
|
+
const isPronounI = word.replace(/[^a-zA-Z]/g, "") === "I" || /^[^a-zA-Z]*I['\u2019\u2018]/.test(raw);
|
|
1046
|
+
if (!isPronounI && word.length > 0 && word[0] === word[0].toUpperCase() && word[0] !== word[0].toLowerCase() && !properNounAllowlist.has(word)) {
|
|
1047
|
+
const sentenceCased = word === word.toUpperCase() ? word.toLowerCase() : word[0].toLowerCase() + word.slice(1);
|
|
1048
|
+
issues.push({
|
|
1049
|
+
ruleId: "sentence-case",
|
|
1050
|
+
ruleName: "Sentence case",
|
|
1051
|
+
severity: "warning",
|
|
1052
|
+
message: `"${word}" appears capitalized. Use sentence case for UI text.`,
|
|
1053
|
+
suggestion: `Change to "${sentenceCased}"`
|
|
1054
|
+
});
|
|
1055
|
+
break;
|
|
1056
|
+
}
|
|
961
1057
|
}
|
|
1058
|
+
sentenceStart = endsSentence;
|
|
962
1059
|
}
|
|
963
1060
|
}
|
|
964
1061
|
function checkBannedPhrases(text, contentType, issues) {
|
|
965
1062
|
const lower = text.toLowerCase();
|
|
966
|
-
for (const phrase of
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
}
|
|
1063
|
+
for (const phrase of compiledBannedPhrases) {
|
|
1064
|
+
const matched = phrase.matchType === "regex" ? phrase._regex !== null && phrase._regex.test(text) : lower.includes(phrase.pattern);
|
|
1065
|
+
if (!matched) continue;
|
|
1066
|
+
const severity = contentType === "button" ? rules["banned-phrases"].severityByContext.button : rules["banned-phrases"].severityByContext.other;
|
|
1067
|
+
issues.push({
|
|
1068
|
+
ruleId: "banned-phrases",
|
|
1069
|
+
ruleName: "Banned phrase",
|
|
1070
|
+
severity,
|
|
1071
|
+
message: `"${phrase.label || phrase.pattern}" \u2014 ${phrase.reason}`,
|
|
1072
|
+
suggestion: phrase.suggestion
|
|
1073
|
+
});
|
|
977
1074
|
}
|
|
978
1075
|
}
|
|
979
1076
|
function checkGenericButtons(text, issues) {
|
|
@@ -1113,6 +1210,9 @@ var require_engine = __commonJS({
|
|
|
1113
1210
|
bestMatch = { type: typeId, confidence: score };
|
|
1114
1211
|
}
|
|
1115
1212
|
}
|
|
1213
|
+
if (!bestMatch || bestMatch.confidence < MIN_CONTENT_TYPE_CONFIDENCE) {
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1116
1216
|
return bestMatch;
|
|
1117
1217
|
}
|
|
1118
1218
|
function getPattern(contentType) {
|
|
@@ -5930,6 +6030,7 @@ async function querySupernova(resource, opts = {}) {
|
|
|
5930
6030
|
const {
|
|
5931
6031
|
designSystemId,
|
|
5932
6032
|
versionId,
|
|
6033
|
+
versionSelector,
|
|
5933
6034
|
proxyUrl = process.env.ARBOR_MCP_SUPERNOVA_PROXY_URL ?? "",
|
|
5934
6035
|
proxyToken = process.env.ARBOR_MCP_SUPERNOVA_PROXY_TOKEN ?? "",
|
|
5935
6036
|
fetchImpl = globalThis.fetch,
|
|
@@ -5948,12 +6049,13 @@ async function querySupernova(resource, opts = {}) {
|
|
|
5948
6049
|
if (parsed.protocol !== "https:") {
|
|
5949
6050
|
return { ok: false, reason: "not_configured", detail: `proxy URL must be https (got ${parsed.protocol})` };
|
|
5950
6051
|
}
|
|
5951
|
-
const cacheKey = `${resource}|${designSystemId ?? ""}|${versionId ?? ""}`;
|
|
6052
|
+
const cacheKey = `${resource}|${designSystemId ?? ""}|${versionId ?? ""}|${versionSelector ?? ""}`;
|
|
5952
6053
|
const cached = cache.get(cacheKey);
|
|
5953
6054
|
if (cached && now() - cached.fetchedAt < CACHE_TTL_MS2) return cached.value;
|
|
5954
6055
|
const body = { resource };
|
|
5955
6056
|
if (designSystemId) body.designSystemId = designSystemId;
|
|
5956
6057
|
if (versionId) body.versionId = versionId;
|
|
6058
|
+
if (versionSelector && !versionId) body.versionSelector = versionSelector;
|
|
5957
6059
|
const controller = new AbortController();
|
|
5958
6060
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
5959
6061
|
let res;
|
|
@@ -5998,11 +6100,49 @@ async function querySupernova(resource, opts = {}) {
|
|
|
5998
6100
|
return value;
|
|
5999
6101
|
}
|
|
6000
6102
|
|
|
6103
|
+
// src/lib/resolve-version.ts
|
|
6104
|
+
function extractVersionList(data) {
|
|
6105
|
+
const wrapped = data?.result?.designSystemVersions;
|
|
6106
|
+
const list = Array.isArray(wrapped) ? wrapped : Array.isArray(data) ? data : null;
|
|
6107
|
+
if (list === null) return null;
|
|
6108
|
+
return list.filter((x) => !!x && typeof x === "object");
|
|
6109
|
+
}
|
|
6110
|
+
async function resolveVersionId(version, deps = {}) {
|
|
6111
|
+
const query = deps.query ?? ((r) => querySupernova(r));
|
|
6112
|
+
const result = await query("versions");
|
|
6113
|
+
if (!result.ok) {
|
|
6114
|
+
return { ok: false, reason: "unavailable", detail: `could not load versions (${result.reason})` };
|
|
6115
|
+
}
|
|
6116
|
+
const list = extractVersionList(result.data);
|
|
6117
|
+
if (list === null) {
|
|
6118
|
+
return { ok: false, reason: "unavailable", detail: "proxy returned an unrecognized versions payload" };
|
|
6119
|
+
}
|
|
6120
|
+
const match = list.find((v) => v.isReadonly === true && v.version === version);
|
|
6121
|
+
if (!match) {
|
|
6122
|
+
return { ok: false, reason: "not_found", detail: `no frozen Supernova version "${version}" \u2014 it may not be cut yet (see arbor_list_versions)` };
|
|
6123
|
+
}
|
|
6124
|
+
const versionId = String(match.id ?? "");
|
|
6125
|
+
if (!versionId) {
|
|
6126
|
+
return { ok: false, reason: "unavailable", detail: `frozen version "${version}" has no id` };
|
|
6127
|
+
}
|
|
6128
|
+
const meta = match.meta;
|
|
6129
|
+
const versionName = meta && typeof meta === "object" && typeof meta.name === "string" && meta.name.length > 0 ? meta.name : version;
|
|
6130
|
+
return { ok: true, versionId, versionName };
|
|
6131
|
+
}
|
|
6132
|
+
|
|
6001
6133
|
// src/tools/list-components.ts
|
|
6002
6134
|
var TOOL_NAME3 = "arbor_list_components";
|
|
6003
6135
|
var TOOL_TITLE3 = "List Arbor components";
|
|
6004
6136
|
var TOOL_DESCRIPTION3 = "List the components in the Arbor design system (name, id, short description) from Supernova via the Arbor federation proxy. Read-only metadata for discovery; returns top-level components, not Figma variants. Does NOT return token values, source code, or props \u2014 use arbor_get_component for one component, the shadcn registry/Storybook for source, arbor_open_in_playroom for a runnable snippet. Does NOT change anything.";
|
|
6005
|
-
var inputSchema3 = external_exports.object({
|
|
6137
|
+
var inputSchema3 = external_exports.object({
|
|
6138
|
+
query: external_exports.string().trim().min(1).max(100).optional().describe("Optional case-insensitive name/description filter"),
|
|
6139
|
+
versionSelector: external_exports.enum(["draft", "latest_released"]).optional().describe(
|
|
6140
|
+
"Which design system version to read: 'draft' (default \u2014 the editable working version) or 'latest_released' (the newest frozen release; available only once a release has been cut, otherwise unavailable)"
|
|
6141
|
+
),
|
|
6142
|
+
version: external_exports.string().trim().min(1).max(50).optional().describe(
|
|
6143
|
+
'List components as of a specific released version, e.g. "15.1.0" \u2014 resolves to that frozen Supernova version ("what shipped in 15.1.0"). Takes precedence over versionSelector. See arbor_list_versions for the versions that have been cut.'
|
|
6144
|
+
)
|
|
6145
|
+
}).strict();
|
|
6006
6146
|
var outputSchema3 = external_exports.object({
|
|
6007
6147
|
source: external_exports.enum(["remote", "unavailable"]),
|
|
6008
6148
|
total: external_exports.number(),
|
|
@@ -6025,8 +6165,23 @@ function extractComponents(data) {
|
|
|
6025
6165
|
});
|
|
6026
6166
|
}
|
|
6027
6167
|
async function handleListComponents(args, deps = {}) {
|
|
6028
|
-
const query = deps.query ?? ((r) => querySupernova(r));
|
|
6029
|
-
|
|
6168
|
+
const query = deps.query ?? ((r, opts) => querySupernova(r, opts));
|
|
6169
|
+
let queryOpts;
|
|
6170
|
+
if (args.version) {
|
|
6171
|
+
const resolveVersion = deps.resolveVersion ?? ((v) => resolveVersionId(v));
|
|
6172
|
+
const resolved = await resolveVersion(args.version);
|
|
6173
|
+
if (!resolved.ok) {
|
|
6174
|
+
const out2 = { source: "unavailable", total: 0, components: [], detail: resolved.detail };
|
|
6175
|
+
return {
|
|
6176
|
+
content: [{ type: "text", text: `Could not read version ${args.version}: ${resolved.detail}. Treat as unavailable \u2014 do not assume there are none.` }],
|
|
6177
|
+
structuredContent: out2
|
|
6178
|
+
};
|
|
6179
|
+
}
|
|
6180
|
+
queryOpts = { versionId: resolved.versionId };
|
|
6181
|
+
} else {
|
|
6182
|
+
queryOpts = { versionSelector: args.versionSelector };
|
|
6183
|
+
}
|
|
6184
|
+
const result = await query("components", queryOpts);
|
|
6030
6185
|
let out;
|
|
6031
6186
|
const extracted = result.ok ? extractComponents(result.data) : null;
|
|
6032
6187
|
if (!result.ok) {
|
|
@@ -6058,7 +6213,13 @@ var TOOL_TITLE4 = "Get one Arbor component";
|
|
|
6058
6213
|
var TOOL_DESCRIPTION4 = "Get a single Arbor component's metadata by name (case-insensitive) or id from Supernova via the Arbor federation proxy (name, id, description). Read-only; matches top-level components, not Figma variants. Does NOT return token values or rendered source \u2014 use the shadcn registry / Storybook for implementation and arbor_open_in_playroom for a runnable snippet. Does NOT change anything.";
|
|
6059
6214
|
var inputSchema4 = external_exports.object({
|
|
6060
6215
|
name: external_exports.string().trim().min(1).max(100).optional().describe('Component name (case-insensitive), e.g. "Button"'),
|
|
6061
|
-
id: external_exports.string().trim().min(1).max(100).optional().describe("Supernova component id")
|
|
6216
|
+
id: external_exports.string().trim().min(1).max(100).optional().describe("Supernova component id"),
|
|
6217
|
+
versionSelector: external_exports.enum(["draft", "latest_released"]).optional().describe(
|
|
6218
|
+
"Which design system version to read: 'draft' (default \u2014 the editable working version) or 'latest_released' (the newest frozen release; available only once a release has been cut, otherwise unavailable)"
|
|
6219
|
+
),
|
|
6220
|
+
version: external_exports.string().trim().min(1).max(50).optional().describe(
|
|
6221
|
+
'Read a specific released version, e.g. "15.1.0" \u2014 resolves to that frozen Supernova version and reads the component there ("what shipped in 15.1.0"). Takes precedence over versionSelector. See arbor_list_versions for the versions that have been cut.'
|
|
6222
|
+
)
|
|
6062
6223
|
}).strict();
|
|
6063
6224
|
var outputSchema4 = external_exports.object({
|
|
6064
6225
|
source: external_exports.enum(["remote", "unavailable"]),
|
|
@@ -6077,8 +6238,23 @@ async function handleGetComponent(args, deps = {}) {
|
|
|
6077
6238
|
structuredContent: out2
|
|
6078
6239
|
};
|
|
6079
6240
|
}
|
|
6080
|
-
const query = deps.query ?? ((r) => querySupernova(r));
|
|
6081
|
-
|
|
6241
|
+
const query = deps.query ?? ((r, opts) => querySupernova(r, opts));
|
|
6242
|
+
let queryOpts;
|
|
6243
|
+
if (args.version) {
|
|
6244
|
+
const resolveVersion = deps.resolveVersion ?? ((v) => resolveVersionId(v));
|
|
6245
|
+
const resolved = await resolveVersion(args.version);
|
|
6246
|
+
if (!resolved.ok) {
|
|
6247
|
+
const out2 = { source: "unavailable", found: false, component: null, detail: resolved.detail };
|
|
6248
|
+
return {
|
|
6249
|
+
content: [{ type: "text", text: `Could not read version ${args.version}: ${resolved.detail}. Treat as unavailable.` }],
|
|
6250
|
+
structuredContent: out2
|
|
6251
|
+
};
|
|
6252
|
+
}
|
|
6253
|
+
queryOpts = { versionId: resolved.versionId };
|
|
6254
|
+
} else {
|
|
6255
|
+
queryOpts = { versionSelector: args.versionSelector };
|
|
6256
|
+
}
|
|
6257
|
+
const result = await query("components", queryOpts);
|
|
6082
6258
|
let out;
|
|
6083
6259
|
if (!result.ok) {
|
|
6084
6260
|
out = { source: "unavailable", found: false, component: null, detail: `could not load components (${result.reason})` };
|
|
@@ -6103,15 +6279,38 @@ var getComponentTool = {
|
|
|
6103
6279
|
// src/tools/list-versions.ts
|
|
6104
6280
|
var TOOL_NAME5 = "arbor_list_versions";
|
|
6105
6281
|
var TOOL_TITLE5 = "List Arbor design system versions";
|
|
6106
|
-
var TOOL_DESCRIPTION5 = "List the version history of the Arbor design system (id, name, created date, read-only flag
|
|
6282
|
+
var TOOL_DESCRIPTION5 = "List the version history of the Arbor design system from Supernova (id, name, semver, created date, read-only flag, draft / latest-released markers) via the Arbor federation proxy, plus the committed release manifest (npm release \u2194 Supernova version). Read-only metadata. Does NOT return token values (use the compiled CSS / registry) and does NOT change anything.";
|
|
6107
6283
|
var inputSchema5 = external_exports.object({}).strict();
|
|
6284
|
+
var versionSchema = external_exports.object({
|
|
6285
|
+
id: external_exports.string(),
|
|
6286
|
+
name: external_exports.string(),
|
|
6287
|
+
semver: external_exports.string().nullable().describe('The version string when it parses as semver (X.Y.Z); null for the draft, whose version is a label like "Shared Draft"'),
|
|
6288
|
+
createdAt: external_exports.string().nullable(),
|
|
6289
|
+
readOnly: external_exports.boolean().nullable(),
|
|
6290
|
+
isDraft: external_exports.boolean().nullable().describe("The editable working version (readOnly === false). DNG-594: a draft's version is a label, not null \u2014 so readOnly is the signal."),
|
|
6291
|
+
isLatestReleased: external_exports.boolean().describe("True for the single newest read-only (frozen) version by createdAt")
|
|
6292
|
+
});
|
|
6293
|
+
var manifestReleaseSchema = external_exports.object({
|
|
6294
|
+
releaseId: external_exports.string(),
|
|
6295
|
+
date: external_exports.string().nullable(),
|
|
6296
|
+
frontDoorVersion: external_exports.string().nullable().describe("The @linktr.ee/arbor version for this release (DL-076 front-door number)"),
|
|
6297
|
+
supernovaVersionId: external_exports.string().nullable().describe("The frozen Supernova version id this release was cut as (null until the cutter + stamper run \u2014 later phases)"),
|
|
6298
|
+
supernovaStatus: external_exports.string().nullable()
|
|
6299
|
+
});
|
|
6108
6300
|
var outputSchema5 = external_exports.object({
|
|
6109
|
-
source: external_exports.enum(["remote", "unavailable"]).describe("'remote' = fetched; 'unavailable' = proxy fetch failed"),
|
|
6110
|
-
total: external_exports.number().describe("Number of versions returned"),
|
|
6111
|
-
versions: external_exports.array(
|
|
6301
|
+
source: external_exports.enum(["remote", "unavailable"]).describe("'remote' = Supernova versions fetched; 'unavailable' = proxy fetch failed"),
|
|
6302
|
+
total: external_exports.number().describe("Number of Supernova versions returned"),
|
|
6303
|
+
versions: external_exports.array(versionSchema).describe("Supernova versions, in the order returned"),
|
|
6304
|
+
manifest: external_exports.object({
|
|
6305
|
+
source: external_exports.enum(["local", "unavailable"]).describe("'local' = release manifest loaded; 'unavailable' = not published yet / degraded"),
|
|
6306
|
+
total: external_exports.number(),
|
|
6307
|
+
releases: external_exports.array(manifestReleaseSchema),
|
|
6308
|
+
detail: external_exports.string().nullable()
|
|
6309
|
+
}).describe("The committed release manifest (releases.json) mapping npm releases \u2194 Supernova versions"),
|
|
6112
6310
|
detail: external_exports.string().nullable().describe("Degraded reason when source is unavailable")
|
|
6113
6311
|
});
|
|
6114
6312
|
var annotations5 = { readOnlyHint: true, idempotentHint: true, openWorldHint: true, destructiveHint: false };
|
|
6313
|
+
var SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+].*)?$/;
|
|
6115
6314
|
function extractList(data) {
|
|
6116
6315
|
const wrapped = data?.result?.designSystemVersions;
|
|
6117
6316
|
const list = Array.isArray(wrapped) ? wrapped : Array.isArray(data) ? data : null;
|
|
@@ -6119,26 +6318,86 @@ function extractList(data) {
|
|
|
6119
6318
|
return list.filter((x) => !!x && typeof x === "object");
|
|
6120
6319
|
}
|
|
6121
6320
|
var str2 = (v) => typeof v === "string" && v.length > 0 ? v : null;
|
|
6321
|
+
var semverOf = (v) => {
|
|
6322
|
+
const s = str2(v);
|
|
6323
|
+
return s && SEMVER_RE.test(s) ? s : null;
|
|
6324
|
+
};
|
|
6325
|
+
var createdAtEpoch = (s) => {
|
|
6326
|
+
const t = new Date(s ?? 0).getTime();
|
|
6327
|
+
return Number.isFinite(t) ? t : 0;
|
|
6328
|
+
};
|
|
6329
|
+
var metaName = (v) => {
|
|
6330
|
+
const meta = v.meta;
|
|
6331
|
+
return meta && typeof meta === "object" ? str2(meta.name) : null;
|
|
6332
|
+
};
|
|
6333
|
+
function latestReleasedId(versions) {
|
|
6334
|
+
const frozen = versions.filter((v) => v.readOnly === true);
|
|
6335
|
+
if (frozen.length === 0) return null;
|
|
6336
|
+
frozen.sort((a, b) => createdAtEpoch(b.createdAt) - createdAtEpoch(a.createdAt));
|
|
6337
|
+
return frozen[0].id || null;
|
|
6338
|
+
}
|
|
6339
|
+
async function buildManifestLane(load) {
|
|
6340
|
+
const res = await load("releases.json");
|
|
6341
|
+
if (!res.ok) {
|
|
6342
|
+
return { source: "unavailable", total: 0, releases: [], detail: `could not load release manifest (${res.reason})` };
|
|
6343
|
+
}
|
|
6344
|
+
const data = res.data;
|
|
6345
|
+
if (data?.degraded === true) {
|
|
6346
|
+
return { source: "unavailable", total: 0, releases: [], detail: "release manifest is a degraded fallback" };
|
|
6347
|
+
}
|
|
6348
|
+
if (!data || !Array.isArray(data.releases)) {
|
|
6349
|
+
return { source: "unavailable", total: 0, releases: [], detail: "unrecognized releases.json payload" };
|
|
6350
|
+
}
|
|
6351
|
+
const releases = data.releases.filter((r) => !!r && typeof r === "object").map((r) => {
|
|
6352
|
+
const sn = r.supernova ?? {};
|
|
6353
|
+
return {
|
|
6354
|
+
releaseId: String(r.releaseId ?? ""),
|
|
6355
|
+
date: str2(r.date),
|
|
6356
|
+
frontDoorVersion: str2(r.frontDoorVersion),
|
|
6357
|
+
supernovaVersionId: str2(sn.versionId),
|
|
6358
|
+
supernovaStatus: str2(sn.status)
|
|
6359
|
+
};
|
|
6360
|
+
});
|
|
6361
|
+
return { source: "local", total: releases.length, releases, detail: null };
|
|
6362
|
+
}
|
|
6122
6363
|
async function handleListVersions(_args, deps = {}) {
|
|
6123
6364
|
const query = deps.query ?? ((r) => querySupernova(r));
|
|
6365
|
+
const load = deps.load ?? ((f) => loadPublishedJson(f));
|
|
6124
6366
|
const result = await query("versions");
|
|
6125
|
-
let out;
|
|
6126
6367
|
const list = result.ok ? extractList(result.data) : null;
|
|
6368
|
+
let source;
|
|
6369
|
+
let versions;
|
|
6370
|
+
let detail;
|
|
6127
6371
|
if (!result.ok) {
|
|
6128
|
-
|
|
6372
|
+
source = "unavailable";
|
|
6373
|
+
versions = [];
|
|
6374
|
+
detail = `could not load versions (${result.reason})`;
|
|
6129
6375
|
} else if (list === null) {
|
|
6130
|
-
|
|
6376
|
+
source = "unavailable";
|
|
6377
|
+
versions = [];
|
|
6378
|
+
detail = "proxy returned an unrecognized versions payload";
|
|
6131
6379
|
} else {
|
|
6132
|
-
const
|
|
6380
|
+
const base = list.map((v) => ({
|
|
6133
6381
|
id: String(v.id ?? ""),
|
|
6134
|
-
name:
|
|
6382
|
+
name: metaName(v) ?? str2(v.version) ?? "(unnamed)",
|
|
6383
|
+
semver: semverOf(v.version),
|
|
6135
6384
|
createdAt: str2(v.createdAt),
|
|
6136
6385
|
readOnly: typeof v.isReadonly === "boolean" ? v.isReadonly : null
|
|
6137
6386
|
}));
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6387
|
+
const latest = latestReleasedId(base);
|
|
6388
|
+
versions = base.map((v) => ({
|
|
6389
|
+
...v,
|
|
6390
|
+
isDraft: v.readOnly === null ? null : v.readOnly === false,
|
|
6391
|
+
isLatestReleased: latest !== null && v.id === latest
|
|
6392
|
+
}));
|
|
6393
|
+
source = "remote";
|
|
6394
|
+
detail = null;
|
|
6395
|
+
}
|
|
6396
|
+
const manifest = await buildManifestLane(load);
|
|
6397
|
+
const out = { source, total: versions.length, versions, manifest, detail };
|
|
6398
|
+
const versionsLine = out.source === "unavailable" ? `Supernova versions unavailable (${out.detail}) \u2014 do not assume there are none.` : `${out.total} Supernova version${out.total === 1 ? "" : "s"}: ${out.versions.map((v) => v.name).join(", ") || "(none)"}.`;
|
|
6399
|
+
const manifestLine = manifest.source === "unavailable" ? `Release manifest unavailable (${manifest.detail}).` : `${manifest.total} manifest release${manifest.total === 1 ? "" : "s"} (front-door: ${manifest.releases.map((r) => r.frontDoorVersion).filter(Boolean).join(", ") || "n/a"}).`;
|
|
6400
|
+
return { content: [{ type: "text", text: `${versionsLine} ${manifestLine}` }], structuredContent: out };
|
|
6142
6401
|
}
|
|
6143
6402
|
var listVersionsTool = {
|
|
6144
6403
|
name: TOOL_NAME5,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linktr.ee/arbor-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Model Context Protocol server exposing Arbor design system tools: Playroom snippets, Figma→code sync health, UX-writing checks, decision-log lookup, and federated component/version metadata.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"arbor",
|