honeybook-mcp 1.3.1 → 1.4.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +2 -2
- package/dist/bundle.js +592 -65
- package/dist/feed.js +21 -27
- package/dist/tools/messages.js +35 -16
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
- package/skills/honeybook/SKILL.md +2 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "HoneyBook client-portal tools for Claude Code",
|
|
10
|
-
"version": "1.
|
|
10
|
+
"version": "1.4.0"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "HoneyBook",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "HoneyBook client-portal MCP for Claude — view wedding-vendor contracts and invoices via MCP",
|
|
18
|
-
"version": "1.
|
|
18
|
+
"version": "1.4.0",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Chall"
|
|
21
21
|
},
|
package/README.md
CHANGED
|
@@ -140,8 +140,8 @@ Tools that touch a vendor accept an optional `origin` argument (e.g. `https://ac
|
|
|
140
140
|
| `list_projects` | Your projects with a vendor + their workspace ids | Auto |
|
|
141
141
|
| `get_project` | Project details: date, location, people, custom fields | Auto |
|
|
142
142
|
| `list_messages` | Messages (or the activity log) in a workspace, newest first | Auto |
|
|
143
|
-
| `get_message` | One message in full: body, attachments, delivery status
|
|
144
|
-
| `send_message` | Send or reply through the portal (after you confirm)
|
|
143
|
+
| `get_message` | One message in full: body (text, Markdown or HTML), attachments, delivery status | Auto |
|
|
144
|
+
| `send_message` | Send or reply through the portal, in text, Markdown or HTML (after you confirm) | Confirm |
|
|
145
145
|
| `mark_messages_seen` | Mark feed items seen (reads never do this on their own) | Auto |
|
|
146
146
|
| `list_meetings` | Scheduled meetings with join links, latest time wins | Auto |
|
|
147
147
|
| `list_tasks` | Tasks the vendor assigned you, with counts | Auto |
|
package/dist/bundle.js
CHANGED
|
@@ -233,7 +233,7 @@ var require_util = __commonJS({
|
|
|
233
233
|
else
|
|
234
234
|
target[key] = value;
|
|
235
235
|
}
|
|
236
|
-
function mirrorShape2(target, source, keys,
|
|
236
|
+
function mirrorShape2(target, source, keys, wrap2) {
|
|
237
237
|
const raw = sourceShape2(source);
|
|
238
238
|
for (const key of keys) {
|
|
239
239
|
const desc = Object.getOwnPropertyDescriptor(raw, key);
|
|
@@ -242,10 +242,10 @@ var require_util = __commonJS({
|
|
|
242
242
|
if (desc.get) {
|
|
243
243
|
deferProp2(target, key, () => {
|
|
244
244
|
const value = source._zod.def.shape[key];
|
|
245
|
-
return
|
|
245
|
+
return wrap2 ? wrap2(value, key) : value;
|
|
246
246
|
});
|
|
247
247
|
} else
|
|
248
|
-
putProp2(target, key,
|
|
248
|
+
putProp2(target, key, wrap2 ? wrap2(desc.value, key) : desc.value);
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
function mirrorProps2(target, source) {
|
|
@@ -777,9 +777,9 @@ var require_util = __commonJS({
|
|
|
777
777
|
}
|
|
778
778
|
};
|
|
779
779
|
exports.Class = Class2;
|
|
780
|
-
function members2(proto,
|
|
781
|
-
for (const key in
|
|
782
|
-
const desc = Object.getOwnPropertyDescriptor(
|
|
780
|
+
function members2(proto, table2) {
|
|
781
|
+
for (const key in table2) {
|
|
782
|
+
const desc = Object.getOwnPropertyDescriptor(table2, key);
|
|
783
783
|
if (desc.get)
|
|
784
784
|
Object.defineProperty(proto, key, { ...desc, enumerable: false });
|
|
785
785
|
else
|
|
@@ -793,10 +793,10 @@ var require_util = __commonJS({
|
|
|
793
793
|
function hide2(inst, key, value) {
|
|
794
794
|
return own2(inst, key, value, false);
|
|
795
795
|
}
|
|
796
|
-
function derived2(computes,
|
|
796
|
+
function derived2(computes, table2) {
|
|
797
797
|
for (const key in computes) {
|
|
798
798
|
const compute = computes[key];
|
|
799
|
-
Object.defineProperty(
|
|
799
|
+
Object.defineProperty(table2, key, {
|
|
800
800
|
configurable: true,
|
|
801
801
|
enumerable: true,
|
|
802
802
|
get() {
|
|
@@ -807,7 +807,7 @@ var require_util = __commonJS({
|
|
|
807
807
|
}
|
|
808
808
|
});
|
|
809
809
|
}
|
|
810
|
-
return
|
|
810
|
+
return table2;
|
|
811
811
|
}
|
|
812
812
|
function defineBound2(proto, key, fn) {
|
|
813
813
|
Object.defineProperty(proto, key, {
|
|
@@ -18823,8 +18823,8 @@ var require_json_schema_processors = __commonJS({
|
|
|
18823
18823
|
function aggregateChecks2(schema) {
|
|
18824
18824
|
const agg = {};
|
|
18825
18825
|
const def = schema._zod.def;
|
|
18826
|
-
const
|
|
18827
|
-
for (const ch of
|
|
18826
|
+
const list2 = schema._zod.traits.has("$ZodCheck") ? [schema, ...def.checks ?? []] : def.checks ?? [];
|
|
18827
|
+
for (const ch of list2)
|
|
18828
18828
|
contributors2[ch._zod.def.check]?.(agg, ch._zod.def);
|
|
18829
18829
|
const bag = schema._zod.bag;
|
|
18830
18830
|
if (bag.minimum !== void 0)
|
|
@@ -23386,13 +23386,13 @@ var require_buffer_util = __commonJS({
|
|
|
23386
23386
|
"use strict";
|
|
23387
23387
|
var { EMPTY_BUFFER } = require_constants();
|
|
23388
23388
|
var FastBuffer = Buffer[Symbol.species];
|
|
23389
|
-
function concat(
|
|
23390
|
-
if (
|
|
23391
|
-
if (
|
|
23389
|
+
function concat(list2, totalLength) {
|
|
23390
|
+
if (list2.length === 0) return EMPTY_BUFFER;
|
|
23391
|
+
if (list2.length === 1) return list2[0];
|
|
23392
23392
|
const target = Buffer.allocUnsafe(totalLength);
|
|
23393
23393
|
let offset = 0;
|
|
23394
|
-
for (let i = 0; i <
|
|
23395
|
-
const buf =
|
|
23394
|
+
for (let i = 0; i < list2.length; i++) {
|
|
23395
|
+
const buf = list2[i];
|
|
23396
23396
|
target.set(buf, offset);
|
|
23397
23397
|
offset += buf.length;
|
|
23398
23398
|
}
|
|
@@ -25178,14 +25178,14 @@ var require_sender = __commonJS({
|
|
|
25178
25178
|
* @param {Function} [cb] Callback
|
|
25179
25179
|
* @private
|
|
25180
25180
|
*/
|
|
25181
|
-
sendFrame(
|
|
25182
|
-
if (
|
|
25181
|
+
sendFrame(list2, cb) {
|
|
25182
|
+
if (list2.length === 2) {
|
|
25183
25183
|
this._socket.cork();
|
|
25184
|
-
this._socket.write(
|
|
25185
|
-
this._socket.write(
|
|
25184
|
+
this._socket.write(list2[0]);
|
|
25185
|
+
this._socket.write(list2[1], cb);
|
|
25186
25186
|
this._socket.uncork();
|
|
25187
25187
|
} else {
|
|
25188
|
-
this._socket.write(
|
|
25188
|
+
this._socket.write(list2[0], cb);
|
|
25189
25189
|
}
|
|
25190
25190
|
}
|
|
25191
25191
|
};
|
|
@@ -37708,7 +37708,7 @@ var require_contains = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
37708
37708
|
const count = gen.let("count", 0);
|
|
37709
37709
|
validateItems(schValid, () => gen.if(schValid, () => checkLimits(count)));
|
|
37710
37710
|
}
|
|
37711
|
-
function validateItems(_valid,
|
|
37711
|
+
function validateItems(_valid, block2) {
|
|
37712
37712
|
gen.forRange("i", 0, len, (i) => {
|
|
37713
37713
|
cxt.subschema({
|
|
37714
37714
|
keyword: "contains",
|
|
@@ -37716,7 +37716,7 @@ var require_contains = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
37716
37716
|
dataPropType: util_1.Type.Num,
|
|
37717
37717
|
compositeRule: true
|
|
37718
37718
|
}, _valid);
|
|
37719
|
-
|
|
37719
|
+
block2();
|
|
37720
37720
|
});
|
|
37721
37721
|
}
|
|
37722
37722
|
function checkLimits(count) {
|
|
@@ -40086,11 +40086,11 @@ var require_dist = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
40086
40086
|
if (!f) throw new Error(`Unknown format "${name}"`);
|
|
40087
40087
|
return f;
|
|
40088
40088
|
};
|
|
40089
|
-
function addFormats2(ajv,
|
|
40089
|
+
function addFormats2(ajv, list2, fs, exportName) {
|
|
40090
40090
|
var _a3;
|
|
40091
40091
|
var _b;
|
|
40092
40092
|
(_a3 = (_b = ajv.opts.code).formats) !== null && _a3 !== void 0 || (_b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`);
|
|
40093
|
-
for (const f of
|
|
40093
|
+
for (const f of list2) ajv.addFormat(f, fs[f]);
|
|
40094
40094
|
}
|
|
40095
40095
|
module.exports = exports = formatsPlugin;
|
|
40096
40096
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -43506,7 +43506,7 @@ function putProp(target, key, value) {
|
|
|
43506
43506
|
else
|
|
43507
43507
|
target[key] = value;
|
|
43508
43508
|
}
|
|
43509
|
-
function mirrorShape(target, source, keys,
|
|
43509
|
+
function mirrorShape(target, source, keys, wrap2) {
|
|
43510
43510
|
const raw = sourceShape(source);
|
|
43511
43511
|
for (const key of keys) {
|
|
43512
43512
|
const desc = Object.getOwnPropertyDescriptor(raw, key);
|
|
@@ -43515,10 +43515,10 @@ function mirrorShape(target, source, keys, wrap) {
|
|
|
43515
43515
|
if (desc.get) {
|
|
43516
43516
|
deferProp(target, key, () => {
|
|
43517
43517
|
const value = source._zod.def.shape[key];
|
|
43518
|
-
return
|
|
43518
|
+
return wrap2 ? wrap2(value, key) : value;
|
|
43519
43519
|
});
|
|
43520
43520
|
} else
|
|
43521
|
-
putProp(target, key,
|
|
43521
|
+
putProp(target, key, wrap2 ? wrap2(desc.value, key) : desc.value);
|
|
43522
43522
|
}
|
|
43523
43523
|
}
|
|
43524
43524
|
function mirrorProps(target, source) {
|
|
@@ -44048,9 +44048,9 @@ var Class = class {
|
|
|
44048
44048
|
constructor(..._args) {
|
|
44049
44049
|
}
|
|
44050
44050
|
};
|
|
44051
|
-
function members(proto,
|
|
44052
|
-
for (const key in
|
|
44053
|
-
const desc = Object.getOwnPropertyDescriptor(
|
|
44051
|
+
function members(proto, table2) {
|
|
44052
|
+
for (const key in table2) {
|
|
44053
|
+
const desc = Object.getOwnPropertyDescriptor(table2, key);
|
|
44054
44054
|
if (desc.get)
|
|
44055
44055
|
Object.defineProperty(proto, key, { ...desc, enumerable: false });
|
|
44056
44056
|
else
|
|
@@ -44065,10 +44065,10 @@ function hide(inst, key, value) {
|
|
|
44065
44065
|
return own(inst, key, value, false);
|
|
44066
44066
|
}
|
|
44067
44067
|
// @__NO_SIDE_EFFECTS__
|
|
44068
|
-
function derived(computes,
|
|
44068
|
+
function derived(computes, table2) {
|
|
44069
44069
|
for (const key in computes) {
|
|
44070
44070
|
const compute = computes[key];
|
|
44071
|
-
Object.defineProperty(
|
|
44071
|
+
Object.defineProperty(table2, key, {
|
|
44072
44072
|
configurable: true,
|
|
44073
44073
|
enumerable: true,
|
|
44074
44074
|
get() {
|
|
@@ -44079,7 +44079,7 @@ function derived(computes, table) {
|
|
|
44079
44079
|
}
|
|
44080
44080
|
});
|
|
44081
44081
|
}
|
|
44082
|
-
return
|
|
44082
|
+
return table2;
|
|
44083
44083
|
}
|
|
44084
44084
|
function defineBound(proto, key, fn) {
|
|
44085
44085
|
Object.defineProperty(proto, key, {
|
|
@@ -58931,8 +58931,8 @@ var contributors = {
|
|
|
58931
58931
|
function aggregateChecks(schema) {
|
|
58932
58932
|
const agg = {};
|
|
58933
58933
|
const def = schema._zod.def;
|
|
58934
|
-
const
|
|
58935
|
-
for (const ch of
|
|
58934
|
+
const list2 = schema._zod.traits.has("$ZodCheck") ? [schema, ...def.checks ?? []] : def.checks ?? [];
|
|
58935
|
+
for (const ch of list2)
|
|
58936
58936
|
contributors[ch._zod.def.check]?.(agg, ch._zod.def);
|
|
58937
58937
|
const bag = schema._zod.bag;
|
|
58938
58938
|
if (bag.minimum !== void 0)
|
|
@@ -64467,7 +64467,7 @@ var pageSchema = {
|
|
|
64467
64467
|
|
|
64468
64468
|
// src/version.ts
|
|
64469
64469
|
var SERVER_NAME = "honeybook-mcp";
|
|
64470
|
-
var VERSION = "1.
|
|
64470
|
+
var VERSION = "1.4.0";
|
|
64471
64471
|
|
|
64472
64472
|
// src/sessions.ts
|
|
64473
64473
|
import { homedir } from "node:os";
|
|
@@ -66264,14 +66264,14 @@ function assertAcceptsList(accepts) {
|
|
|
66264
66264
|
}
|
|
66265
66265
|
}
|
|
66266
66266
|
}
|
|
66267
|
-
function assertUnavailableCapabilities(
|
|
66267
|
+
function assertUnavailableCapabilities(list2) {
|
|
66268
66268
|
const label = "hello.unavailableCapabilities";
|
|
66269
|
-
if (!Array.isArray(
|
|
66269
|
+
if (!Array.isArray(list2))
|
|
66270
66270
|
throw new ProtocolError3(`${label}: expected array`);
|
|
66271
|
-
if (
|
|
66272
|
-
throw new ProtocolError3(`${label}: at most ${MAX_UNAVAILABLE_CAPABILITIES} entries, got ${
|
|
66271
|
+
if (list2.length > MAX_UNAVAILABLE_CAPABILITIES) {
|
|
66272
|
+
throw new ProtocolError3(`${label}: at most ${MAX_UNAVAILABLE_CAPABILITIES} entries, got ${list2.length}`);
|
|
66273
66273
|
}
|
|
66274
|
-
for (const c of
|
|
66274
|
+
for (const c of list2) {
|
|
66275
66275
|
if (typeof c !== "string") {
|
|
66276
66276
|
throw new ProtocolError3(`${label}: entry must be string, got ${typeof c}`);
|
|
66277
66277
|
}
|
|
@@ -70414,8 +70414,8 @@ var FetchproxyServer = class {
|
|
|
70414
70414
|
throw new Error("FetchproxyServer.captureRequestHeader: no captureHeaders declared on this server \u2014 declare at least one entry in FetchproxyServerOpts.captureHeaders, or pass {host, headerName} explicitly");
|
|
70415
70415
|
}
|
|
70416
70416
|
if (decls.length > 1) {
|
|
70417
|
-
const
|
|
70418
|
-
throw new Error(`FetchproxyServer.captureRequestHeader: multiple captureHeaders declared (${decls.length}: ${
|
|
70417
|
+
const list2 = decls.map((d) => `${JSON.stringify(d.host)}${JSON.stringify(normPath(d.path))}/${JSON.stringify(d.headerName)}`).join(", ");
|
|
70418
|
+
throw new Error(`FetchproxyServer.captureRequestHeader: multiple captureHeaders declared (${decls.length}: ${list2}); pass {host, headerName} to disambiguate`);
|
|
70419
70419
|
}
|
|
70420
70420
|
resolved = decls[0];
|
|
70421
70421
|
} else {
|
|
@@ -72860,6 +72860,524 @@ function registerProjectTools(server) {
|
|
|
72860
72860
|
);
|
|
72861
72861
|
}
|
|
72862
72862
|
|
|
72863
|
+
// node_modules/@chrischall/mcp-utils/dist/rich/html.js
|
|
72864
|
+
var NAMED_ENTITIES = {
|
|
72865
|
+
amp: "&",
|
|
72866
|
+
lt: "<",
|
|
72867
|
+
gt: ">",
|
|
72868
|
+
quot: '"',
|
|
72869
|
+
apos: "'",
|
|
72870
|
+
nbsp: " "
|
|
72871
|
+
};
|
|
72872
|
+
function decodeEntities(s) {
|
|
72873
|
+
return s.replace(/&(?:#(\d{1,8})|#x([0-9a-f]{1,8})|([a-z]{1,10}));|&(amp|lt|gt|quot|nbsp)(?![a-z0-9=])/gi, (whole, dec2, hex3, name, legacy) => {
|
|
72874
|
+
if (legacy !== void 0)
|
|
72875
|
+
return NAMED_ENTITIES[legacy.toLowerCase()];
|
|
72876
|
+
if (name !== void 0)
|
|
72877
|
+
return NAMED_ENTITIES[name.toLowerCase()] ?? whole;
|
|
72878
|
+
const code2 = dec2 !== void 0 ? Number(dec2) : parseInt(hex3, 16);
|
|
72879
|
+
if (!Number.isFinite(code2) || code2 > 1114111)
|
|
72880
|
+
return whole;
|
|
72881
|
+
if (code2 < 32 && code2 !== 9 && code2 !== 10)
|
|
72882
|
+
return "";
|
|
72883
|
+
return String.fromCodePoint(code2);
|
|
72884
|
+
});
|
|
72885
|
+
}
|
|
72886
|
+
var HTML_ESCAPES = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
72887
|
+
function escapeHtml(s) {
|
|
72888
|
+
return s.replace(/[&<>"']/g, (c) => HTML_ESCAPES[c]);
|
|
72889
|
+
}
|
|
72890
|
+
function attr(attrs, name) {
|
|
72891
|
+
const m = new RegExp(`(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'>]+))`, "i").exec(attrs);
|
|
72892
|
+
return m ? m[1] ?? m[2] ?? m[3] : void 0;
|
|
72893
|
+
}
|
|
72894
|
+
var INVISIBLE = /[\u200b\u200c\u2060\ufeff]/g;
|
|
72895
|
+
var visible = (s) => s.replace(INVISIBLE, "");
|
|
72896
|
+
var MAX_DEPTH = 200;
|
|
72897
|
+
var VOID = /* @__PURE__ */ new Set(["br", "img", "hr", "wbr", "input", "meta", "link", "col", "source", "area", "base", "embed", "param", "track"]);
|
|
72898
|
+
var DROPPED_WITH_CONTENT = "script|style|head|title|template|noscript|iframe|object|svg|math|xml";
|
|
72899
|
+
var BLOCK = /* @__PURE__ */ new Set(["p", "div", "ul", "ol", "pre", "blockquote", "table", "h1", "h2", "h3", "h4", "h5", "h6"]);
|
|
72900
|
+
var TAG = /<(\/?)([a-z][a-z0-9]*)\b((?:[^<>"']|"[^"<]*"|'[^'<]*')*)>/gi;
|
|
72901
|
+
var HIDDEN_STYLE = /(?:^|[;\s])(?:display\s*:\s*none|visibility\s*:\s*hidden|mso-hide\s*:\s*all)\b/i;
|
|
72902
|
+
function isHidden(attrs) {
|
|
72903
|
+
const names = attrs.replace(/"[^"]*"|'[^']*'/g, '""');
|
|
72904
|
+
if (/(?:^|\s)hidden(?:\s|=|\/|$)/i.test(names))
|
|
72905
|
+
return true;
|
|
72906
|
+
const style = attr(attrs, "style");
|
|
72907
|
+
return style !== void 0 && HIDDEN_STYLE.test(decodeEntities(style));
|
|
72908
|
+
}
|
|
72909
|
+
function parseHtml(html, opts = {}) {
|
|
72910
|
+
const root = { tag: "#root", attrs: "", children: [] };
|
|
72911
|
+
const stack = [root];
|
|
72912
|
+
const top = () => stack[stack.length - 1];
|
|
72913
|
+
const src = html.replace(/[\u0001-\u0005]/g, "").replace(/<!--[\s\S]*?(?:-->|$)/g, "").replace(/<!\[CDATA\[[\s\S]*?(?:\]\]>|$)/g, "").replace(/<![^>]*>/g, "").replace(new RegExp(`<(${DROPPED_WITH_CONTENT})\\b[\\s\\S]*?(?:<\\/\\1\\s*>|$)`, "gi"), "");
|
|
72914
|
+
let at = 0;
|
|
72915
|
+
for (const m of src.matchAll(TAG)) {
|
|
72916
|
+
if (m.index > at)
|
|
72917
|
+
top().children.push(src.slice(at, m.index));
|
|
72918
|
+
at = m.index + m[0].length;
|
|
72919
|
+
const tag = m[2].toLowerCase();
|
|
72920
|
+
if (m[1] === "/") {
|
|
72921
|
+
let i = stack.length - 1;
|
|
72922
|
+
while (i > 0 && stack[i].tag !== tag)
|
|
72923
|
+
i--;
|
|
72924
|
+
if (i > 0)
|
|
72925
|
+
stack.length = i;
|
|
72926
|
+
continue;
|
|
72927
|
+
}
|
|
72928
|
+
if (tag === "li" && top().tag === "li" || BLOCK.has(tag) && top().tag === "p")
|
|
72929
|
+
stack.pop();
|
|
72930
|
+
if ((tag === "td" || tag === "th" || tag === "tr") && (top().tag === "td" || top().tag === "th"))
|
|
72931
|
+
stack.pop();
|
|
72932
|
+
if (tag === "tr" && top().tag === "tr")
|
|
72933
|
+
stack.pop();
|
|
72934
|
+
const attrs = m[3];
|
|
72935
|
+
const el = { tag, attrs, children: [] };
|
|
72936
|
+
const hidden = opts.keepHidden !== true && isHidden(attrs);
|
|
72937
|
+
if (!hidden)
|
|
72938
|
+
top().children.push(el);
|
|
72939
|
+
const opens = !VOID.has(tag) && !attrs.trimEnd().endsWith("/");
|
|
72940
|
+
if (opens && (hidden || stack.length <= MAX_DEPTH))
|
|
72941
|
+
stack.push(el);
|
|
72942
|
+
}
|
|
72943
|
+
if (at < src.length)
|
|
72944
|
+
top().children.push(src.slice(at));
|
|
72945
|
+
return root;
|
|
72946
|
+
}
|
|
72947
|
+
function safeHref(attrs) {
|
|
72948
|
+
const raw = attr(attrs, "href");
|
|
72949
|
+
if (raw === void 0)
|
|
72950
|
+
return void 0;
|
|
72951
|
+
const href = decodeEntities(raw).trim();
|
|
72952
|
+
return /^(https?:\/\/|mailto:)/i.test(href) ? href : void 0;
|
|
72953
|
+
}
|
|
72954
|
+
function defaultImageText(el) {
|
|
72955
|
+
const alt = decodeEntities(attr(el.attrs, "alt") ?? "").replace(/\s+/g, " ").trim();
|
|
72956
|
+
return alt === "" ? "" : `[${alt}]`;
|
|
72957
|
+
}
|
|
72958
|
+
var QUOTE_OPEN = "";
|
|
72959
|
+
var QUOTE_CLOSE = "";
|
|
72960
|
+
var PARA = "";
|
|
72961
|
+
var BR = "";
|
|
72962
|
+
var MARKS = /[\u0004\u0005]/g;
|
|
72963
|
+
var paraBlock = (inner) => `
|
|
72964
|
+
${PARA}
|
|
72965
|
+
${inner}
|
|
72966
|
+
${PARA}
|
|
72967
|
+
`;
|
|
72968
|
+
var TEXT_BLOCK = /* @__PURE__ */ new Set(["p", "div", "h1", "h2", "h3", "h4", "h5", "h6", "table", "hr"]);
|
|
72969
|
+
var LOOKS_LIKE_ADDRESS = /^(https?:\/\/|www\.)/i;
|
|
72970
|
+
function linkText(el, label) {
|
|
72971
|
+
const url4 = safeHref(el.attrs);
|
|
72972
|
+
if (url4 === void 0)
|
|
72973
|
+
return label;
|
|
72974
|
+
const shown = label.trim();
|
|
72975
|
+
if (shown === "" || shown === url4 || LOOKS_LIKE_ADDRESS.test(shown))
|
|
72976
|
+
return url4;
|
|
72977
|
+
return `${label} (${url4})`;
|
|
72978
|
+
}
|
|
72979
|
+
function rowText(tr, o) {
|
|
72980
|
+
const cells = tr.children.filter((n) => typeof n !== "string" && (n.tag === "td" || n.tag === "th"));
|
|
72981
|
+
if (cells.length === 0)
|
|
72982
|
+
return toText(tr.children, o);
|
|
72983
|
+
const raw = cells.map((c) => toText(c.children, o));
|
|
72984
|
+
const filled = raw.filter((t) => t.replace(/[\s\u0001-\u0005]+/g, "") !== "");
|
|
72985
|
+
if (filled.length === 1)
|
|
72986
|
+
return filled[0];
|
|
72987
|
+
const texts = raw.map((t) => t.replace(/[\n\u0001-\u0005]+/g, " ").trim());
|
|
72988
|
+
while (texts.length > 0 && texts[texts.length - 1] === "")
|
|
72989
|
+
texts.pop();
|
|
72990
|
+
return texts.join(" | ");
|
|
72991
|
+
}
|
|
72992
|
+
function toText(nodes, o) {
|
|
72993
|
+
const memo3 = o.textMemo ??= /* @__PURE__ */ new WeakMap();
|
|
72994
|
+
const hit = memo3.get(nodes);
|
|
72995
|
+
if (hit !== void 0)
|
|
72996
|
+
return hit;
|
|
72997
|
+
let out = "";
|
|
72998
|
+
for (const n of nodes) {
|
|
72999
|
+
if (typeof n === "string") {
|
|
73000
|
+
out += visible(decodeEntities(n));
|
|
73001
|
+
continue;
|
|
73002
|
+
}
|
|
73003
|
+
if (n.tag === "br") {
|
|
73004
|
+
out += `
|
|
73005
|
+
${BR}`;
|
|
73006
|
+
continue;
|
|
73007
|
+
}
|
|
73008
|
+
const code2 = n.tag === "code" || n.tag === "pre";
|
|
73009
|
+
const inner = n.tag === "img" ? "" : code2 ? codeText(n.children) : toText(n.children, o);
|
|
73010
|
+
const hooked = o.element?.(n, "text", inner);
|
|
73011
|
+
if (hooked !== void 0)
|
|
73012
|
+
out += hooked;
|
|
73013
|
+
else if (n.tag === "img")
|
|
73014
|
+
out += defaultImageText(n);
|
|
73015
|
+
else if (n.tag === "a")
|
|
73016
|
+
out += linkText(n, inner);
|
|
73017
|
+
else if (n.tag === "tr")
|
|
73018
|
+
out += `
|
|
73019
|
+
${rowText(n, o)}
|
|
73020
|
+
`;
|
|
73021
|
+
else if (n.tag === "blockquote")
|
|
73022
|
+
out += paraBlock(`${QUOTE_OPEN}
|
|
73023
|
+
${inner}
|
|
73024
|
+
${QUOTE_CLOSE}`);
|
|
73025
|
+
else if (n.tag === "ol" || n.tag === "ul") {
|
|
73026
|
+
const start = Number(attr(n.attrs, "start"));
|
|
73027
|
+
let i = n.tag === "ol" && Number.isInteger(start) && start >= 0 ? start : 1;
|
|
73028
|
+
let items = "";
|
|
73029
|
+
for (const c of n.children) {
|
|
73030
|
+
if (typeof c !== "string" && c.tag === "li") {
|
|
73031
|
+
const text2 = toText(c.children, o).replace(/^[\s\u0004\u0005]+|[\s\u0004\u0005]+$/g, "");
|
|
73032
|
+
items += `
|
|
73033
|
+
${n.tag === "ol" ? `${i++}. ` : "- "}${text2}
|
|
73034
|
+
`;
|
|
73035
|
+
} else
|
|
73036
|
+
items += toText([c], o);
|
|
73037
|
+
}
|
|
73038
|
+
out += paraBlock(items);
|
|
73039
|
+
} else if (n.tag === "pre" || TEXT_BLOCK.has(n.tag))
|
|
73040
|
+
out += paraBlock(inner);
|
|
73041
|
+
else if (n.tag === "li")
|
|
73042
|
+
out += `
|
|
73043
|
+
${inner}
|
|
73044
|
+
`;
|
|
73045
|
+
else
|
|
73046
|
+
out += inner;
|
|
73047
|
+
}
|
|
73048
|
+
memo3.set(nodes, out);
|
|
73049
|
+
return out;
|
|
73050
|
+
}
|
|
73051
|
+
function htmlToText(html, opts = {}) {
|
|
73052
|
+
const out = [];
|
|
73053
|
+
const paragraphs = opts.paragraphs === true;
|
|
73054
|
+
let depth = 0;
|
|
73055
|
+
let lastDepth = 0;
|
|
73056
|
+
let pendingBreak = false;
|
|
73057
|
+
for (const raw of toText(parseHtml(html, opts).children, { ...opts }).split(/\r?\n/)) {
|
|
73058
|
+
const line = raw.replace(/[ \t\u00a0]+/g, " ").trim();
|
|
73059
|
+
if (line === QUOTE_OPEN)
|
|
73060
|
+
depth++;
|
|
73061
|
+
else if (line === QUOTE_CLOSE)
|
|
73062
|
+
depth = Math.max(0, depth - 1);
|
|
73063
|
+
else if (line === PARA || line === BR)
|
|
73064
|
+
pendingBreak = paragraphs && out.length > 0;
|
|
73065
|
+
else {
|
|
73066
|
+
const text2 = line.replace(MARKS, "").trim();
|
|
73067
|
+
if (text2 === "")
|
|
73068
|
+
continue;
|
|
73069
|
+
if (pendingBreak)
|
|
73070
|
+
out.push("> ".repeat(Math.min(depth, lastDepth)).trimEnd());
|
|
73071
|
+
pendingBreak = false;
|
|
73072
|
+
out.push(`${"> ".repeat(depth)}${text2}`);
|
|
73073
|
+
lastDepth = depth;
|
|
73074
|
+
}
|
|
73075
|
+
}
|
|
73076
|
+
return out.join("\n");
|
|
73077
|
+
}
|
|
73078
|
+
var CODE_HOLE = "";
|
|
73079
|
+
var block = (s) => `
|
|
73080
|
+
|
|
73081
|
+
${s}
|
|
73082
|
+
|
|
73083
|
+
`;
|
|
73084
|
+
function wrap(marker, inner) {
|
|
73085
|
+
const m = /^(\s*)([\s\S]*?)(\s*)$/.exec(inner);
|
|
73086
|
+
return m[2] === "" ? inner : `${m[1]}${marker}${m[2]}${marker}${m[3]}`;
|
|
73087
|
+
}
|
|
73088
|
+
function codeText(nodes) {
|
|
73089
|
+
return nodes.map((n) => typeof n === "string" ? decodeEntities(n) : n.tag === "br" ? "\n" : codeText(n.children)).join("");
|
|
73090
|
+
}
|
|
73091
|
+
function inlineCode(text2) {
|
|
73092
|
+
const longest = Math.max(0, ...[...text2.matchAll(/`+/g)].map((m) => m[0].length));
|
|
73093
|
+
const fence = "`".repeat(longest + 1);
|
|
73094
|
+
const pad = text2.startsWith("`") || text2.endsWith("`") ? " " : "";
|
|
73095
|
+
return `${fence}${pad}${text2}${pad}${fence}`;
|
|
73096
|
+
}
|
|
73097
|
+
var tidyInner = (s) => s.replace(/\n{3,}/g, "\n\n").trim();
|
|
73098
|
+
function list(el, codes, o) {
|
|
73099
|
+
const ordered = el.tag === "ol";
|
|
73100
|
+
const startAttr = Number(attr(el.attrs, "start"));
|
|
73101
|
+
const start = ordered && Number.isInteger(startAttr) && startAttr >= 0 ? startAttr : 1;
|
|
73102
|
+
const items = el.children.filter((n) => typeof n !== "string" && n.tag === "li");
|
|
73103
|
+
return items.map((li, i) => {
|
|
73104
|
+
const marker = ordered ? `${start + i}. ` : "- ";
|
|
73105
|
+
const lines = md(li.children, codes, o).replace(/\n{2,}/g, "\n").trim().split("\n");
|
|
73106
|
+
return lines.map((l, n) => n === 0 ? marker + l : l === "" ? "" : " ".repeat(marker.length) + l).join("\n");
|
|
73107
|
+
}).join("\n");
|
|
73108
|
+
}
|
|
73109
|
+
function rowsOf(el) {
|
|
73110
|
+
const out = [];
|
|
73111
|
+
for (const n of el.children) {
|
|
73112
|
+
if (typeof n === "string")
|
|
73113
|
+
continue;
|
|
73114
|
+
if (n.tag === "tr")
|
|
73115
|
+
out.push(n);
|
|
73116
|
+
else if (n.tag === "thead" || n.tag === "tbody" || n.tag === "tfoot")
|
|
73117
|
+
out.push(...rowsOf(n));
|
|
73118
|
+
}
|
|
73119
|
+
return out;
|
|
73120
|
+
}
|
|
73121
|
+
function table(el, codes, o) {
|
|
73122
|
+
const cellsOf = (tr) => tr.children.filter((n) => typeof n !== "string" && (n.tag === "td" || n.tag === "th")).map((c) => tidyInner(md(c.children, codes, o)));
|
|
73123
|
+
const raw = rowsOf(el).map(cellsOf).filter((cells) => cells.some((c) => c !== ""));
|
|
73124
|
+
if (raw.length === 0)
|
|
73125
|
+
return "";
|
|
73126
|
+
if (raw.every((cells) => cells.filter((c) => c !== "").length <= 1)) {
|
|
73127
|
+
return raw.map((cells) => cells.find((c) => c !== "") ?? "").join("\n\n");
|
|
73128
|
+
}
|
|
73129
|
+
if (raw.length === 1)
|
|
73130
|
+
return raw[0].filter((c) => c !== "").join(" ");
|
|
73131
|
+
const lines = raw.map((cells) => cells.map((c) => c.replace(/\s*\n\s*/g, " ").replace(/\|/g, "\\|")));
|
|
73132
|
+
const width = Math.max(...lines.map((cells) => cells.length));
|
|
73133
|
+
const row = (cells) => `| ${[...cells, ...Array(width - cells.length).fill("")].join(" | ")} |`;
|
|
73134
|
+
const sep = `| ${Array.from({ length: width }, () => "---").join(" | ")} |`;
|
|
73135
|
+
return [row(lines[0]), sep, ...lines.slice(1).map(row)].join("\n");
|
|
73136
|
+
}
|
|
73137
|
+
function md(nodes, codes, o) {
|
|
73138
|
+
const memo3 = o.mdMemo ??= /* @__PURE__ */ new WeakMap();
|
|
73139
|
+
const hit = memo3.get(nodes);
|
|
73140
|
+
if (hit !== void 0)
|
|
73141
|
+
return hit;
|
|
73142
|
+
let out = "";
|
|
73143
|
+
for (const n of nodes) {
|
|
73144
|
+
if (typeof n === "string") {
|
|
73145
|
+
out += visible(decodeEntities(n)).replace(/[\s\u00a0]+/g, " ");
|
|
73146
|
+
continue;
|
|
73147
|
+
}
|
|
73148
|
+
const inner = () => md(n.children, codes, o);
|
|
73149
|
+
if (o.element !== void 0 && n.tag !== "br") {
|
|
73150
|
+
const hooked = o.element(n, "markdown", n.tag === "img" ? "" : inner());
|
|
73151
|
+
if (hooked !== void 0) {
|
|
73152
|
+
out += hooked;
|
|
73153
|
+
continue;
|
|
73154
|
+
}
|
|
73155
|
+
}
|
|
73156
|
+
switch (n.tag) {
|
|
73157
|
+
case "br":
|
|
73158
|
+
out += "\n";
|
|
73159
|
+
break;
|
|
73160
|
+
case "p":
|
|
73161
|
+
case "div":
|
|
73162
|
+
out += block(inner().trim());
|
|
73163
|
+
break;
|
|
73164
|
+
case "hr":
|
|
73165
|
+
out += block("---");
|
|
73166
|
+
break;
|
|
73167
|
+
case "h1":
|
|
73168
|
+
case "h2":
|
|
73169
|
+
case "h3":
|
|
73170
|
+
case "h4":
|
|
73171
|
+
case "h5":
|
|
73172
|
+
case "h6": {
|
|
73173
|
+
const text2 = inner().replace(/\s+/g, " ").trim();
|
|
73174
|
+
out += text2 === "" ? "" : block(`${"#".repeat(Number(n.tag[1]))} ${text2}`);
|
|
73175
|
+
break;
|
|
73176
|
+
}
|
|
73177
|
+
case "b":
|
|
73178
|
+
case "strong":
|
|
73179
|
+
out += wrap("**", inner());
|
|
73180
|
+
break;
|
|
73181
|
+
case "i":
|
|
73182
|
+
case "em":
|
|
73183
|
+
out += wrap("_", inner());
|
|
73184
|
+
break;
|
|
73185
|
+
case "s":
|
|
73186
|
+
case "strike":
|
|
73187
|
+
case "del":
|
|
73188
|
+
out += wrap("~~", inner());
|
|
73189
|
+
break;
|
|
73190
|
+
case "code":
|
|
73191
|
+
out += inlineCode(codeText(n.children));
|
|
73192
|
+
break;
|
|
73193
|
+
case "pre": {
|
|
73194
|
+
codes.push("```\n" + codeText(n.children).replace(/\n+$/, "") + "\n```");
|
|
73195
|
+
out += block(`${CODE_HOLE}${codes.length - 1}${CODE_HOLE}`);
|
|
73196
|
+
break;
|
|
73197
|
+
}
|
|
73198
|
+
case "a": {
|
|
73199
|
+
const label = inner().trim().replace(/^\[([^\[\]]*)\]$/, "$1");
|
|
73200
|
+
const href = safeHref(n.attrs);
|
|
73201
|
+
if (href === void 0)
|
|
73202
|
+
out += label;
|
|
73203
|
+
else if (label === "" || label === href || LOOKS_LIKE_ADDRESS.test(label))
|
|
73204
|
+
out += href;
|
|
73205
|
+
else
|
|
73206
|
+
out += `[${label}](${href.replace(/[()\s]/g, (c) => encodeURIComponent(c))})`;
|
|
73207
|
+
break;
|
|
73208
|
+
}
|
|
73209
|
+
case "blockquote":
|
|
73210
|
+
out += block(tidyInner(inner()).split("\n").map((l) => l === "" ? ">" : `> ${l}`).join("\n"));
|
|
73211
|
+
break;
|
|
73212
|
+
case "ul":
|
|
73213
|
+
case "ol":
|
|
73214
|
+
out += block(list(n, codes, o));
|
|
73215
|
+
break;
|
|
73216
|
+
case "table":
|
|
73217
|
+
out += block(table(n, codes, o));
|
|
73218
|
+
break;
|
|
73219
|
+
case "img":
|
|
73220
|
+
out += defaultImageText(n);
|
|
73221
|
+
break;
|
|
73222
|
+
default:
|
|
73223
|
+
out += inner();
|
|
73224
|
+
}
|
|
73225
|
+
}
|
|
73226
|
+
memo3.set(nodes, out);
|
|
73227
|
+
return out;
|
|
73228
|
+
}
|
|
73229
|
+
function htmlToMarkdown(html, opts = {}) {
|
|
73230
|
+
const codes = [];
|
|
73231
|
+
const lines = md(parseHtml(html, opts).children, codes, { ...opts }).split("\n");
|
|
73232
|
+
const tidy = [];
|
|
73233
|
+
for (const raw of lines) {
|
|
73234
|
+
const line = raw.trimEnd();
|
|
73235
|
+
tidy.push(tidy.length === 0 || tidy[tidy.length - 1] === "" ? line.trimStart() : line);
|
|
73236
|
+
}
|
|
73237
|
+
return tidy.join("\n").replace(/\n{3,}/g, "\n\n").trim().replace(new RegExp(`${CODE_HOLE}(\\d+)${CODE_HOLE}`, "g"), (_w, i) => codes[Number(i)]);
|
|
73238
|
+
}
|
|
73239
|
+
|
|
73240
|
+
// node_modules/@chrischall/mcp-utils/dist/rich/markdown.js
|
|
73241
|
+
var HOLE_OPEN = "\uE000";
|
|
73242
|
+
var HOLE_CLOSE = "\uE001";
|
|
73243
|
+
var RESERVED2 = /[-]/g;
|
|
73244
|
+
var SAFE_HREF = /^(https?:\/\/|mailto:)/i;
|
|
73245
|
+
function renderInline(src, o) {
|
|
73246
|
+
const holes = [];
|
|
73247
|
+
const hole = (html) => `${HOLE_OPEN}${holes.push(html) - 1}${HOLE_CLOSE}`;
|
|
73248
|
+
const token = /(`+)([\s\S]*?[^`])\1(?!`)|\[([^\]\n]+)\]\(\s*([^\s()]+(?:\([^\s()]*\)[^\s()]*)*)\s*\)|\\([\\`*_{}[\]()#+\-.!~>|:])/g;
|
|
73249
|
+
let s = src.replace(token, (whole, _ticks, code2, label, href, escaped) => {
|
|
73250
|
+
if (code2 !== void 0)
|
|
73251
|
+
return hole(`<code>${escapeHtml(code2.replace(/^ (.*[^ ].*) $/s, "$1"))}</code>`);
|
|
73252
|
+
if (escaped !== void 0)
|
|
73253
|
+
return hole(escapeHtml(escaped));
|
|
73254
|
+
if (!SAFE_HREF.test(href))
|
|
73255
|
+
return whole;
|
|
73256
|
+
return hole(`<a href="${escapeHtml(href)}">${renderInline(label, o)}</a>`);
|
|
73257
|
+
});
|
|
73258
|
+
if (o.shortcode !== void 0) {
|
|
73259
|
+
const render2 = o.shortcode;
|
|
73260
|
+
s = s.replace(/:([a-z0-9_+-]+):/g, (whole, key) => {
|
|
73261
|
+
const html = render2(key);
|
|
73262
|
+
return html === void 0 ? whole : hole(html);
|
|
73263
|
+
});
|
|
73264
|
+
}
|
|
73265
|
+
s = escapeHtml(s).replace(/\*\*(?=\S)([\s\S]*?\S)\*\*/g, "<strong>$1</strong>").replace(/(^|[^\w])__(?=\S)([\s\S]*?\S)__(?!\w)/g, "$1<strong>$2</strong>").replace(/~~(?=\S)([\s\S]*?\S)~~/g, "<s>$1</s>").replace(/(^|[^\w*])\*(?=[^\s*])([\s\S]*?[^\s*])\*(?![\w*])/g, "$1<em>$2</em>").replace(/(^|[^\w])_(?=[^\s_])([\s\S]*?[^\s_])_(?!\w)/g, "$1<em>$2</em>").replace(/\n/g, "<br>");
|
|
73266
|
+
const fill = new RegExp(`${HOLE_OPEN}(\\d+)${HOLE_CLOSE}`, "g");
|
|
73267
|
+
return s.replace(fill, (_w, n) => holes[Number(n)]);
|
|
73268
|
+
}
|
|
73269
|
+
var LIST_ITEM = /^( *)([-*+]|\d{1,9}[.)])[ \t]+(.*)$/;
|
|
73270
|
+
var HEADING = /^ {0,3}(#{1,6})[ \t]+(.+?)[ \t]*#*[ \t]*$/;
|
|
73271
|
+
var QUOTE = /^ {0,3}> ?(.*)$/;
|
|
73272
|
+
function renderList(list2, o) {
|
|
73273
|
+
const tag = list2.ordered ? "ol" : "ul";
|
|
73274
|
+
const start = list2.ordered && list2.start !== 1 ? ` start="${list2.start}"` : "";
|
|
73275
|
+
const items = list2.items.map((i) => `<li>${renderInline(i.lines.join("\n"), o)}${i.children.map((c) => renderList(c, o)).join("")}</li>`);
|
|
73276
|
+
return `<${tag}${start}>${items.join("")}</${tag}>`;
|
|
73277
|
+
}
|
|
73278
|
+
function parseList(lines, from, o) {
|
|
73279
|
+
const roots = [];
|
|
73280
|
+
const stack = [];
|
|
73281
|
+
let i = from;
|
|
73282
|
+
for (; i < lines.length; i++) {
|
|
73283
|
+
const line = lines[i];
|
|
73284
|
+
const m = LIST_ITEM.exec(line);
|
|
73285
|
+
if (m === null) {
|
|
73286
|
+
const last = stack.at(-1)?.items.at(-1);
|
|
73287
|
+
if (line.trim() !== "" && /^\s/.test(line) && last !== void 0) {
|
|
73288
|
+
last.lines.push(line.trim());
|
|
73289
|
+
continue;
|
|
73290
|
+
}
|
|
73291
|
+
break;
|
|
73292
|
+
}
|
|
73293
|
+
const indent = m[1].length;
|
|
73294
|
+
const ordered = /\d/.test(m[2]);
|
|
73295
|
+
while (stack.length > 0 && indent < stack[stack.length - 1].indent)
|
|
73296
|
+
stack.pop();
|
|
73297
|
+
let top = stack.at(-1);
|
|
73298
|
+
if (top !== void 0 && indent >= top.indent + 2 && top.items.length > 0) {
|
|
73299
|
+
const nested = { indent, ordered, start: ordered ? Number.parseInt(m[2], 10) : 1, items: [] };
|
|
73300
|
+
top.items[top.items.length - 1].children.push(nested);
|
|
73301
|
+
stack.push(nested);
|
|
73302
|
+
top = nested;
|
|
73303
|
+
} else if (top === void 0 || top.ordered !== ordered) {
|
|
73304
|
+
const fresh = { indent, ordered, start: ordered ? Number.parseInt(m[2], 10) : 1, items: [] };
|
|
73305
|
+
if (top !== void 0 && stack.length > 1) {
|
|
73306
|
+
stack.pop();
|
|
73307
|
+
stack[stack.length - 1].items.at(-1).children.push(fresh);
|
|
73308
|
+
} else {
|
|
73309
|
+
roots.push(fresh);
|
|
73310
|
+
stack.length = 0;
|
|
73311
|
+
}
|
|
73312
|
+
stack.push(fresh);
|
|
73313
|
+
top = fresh;
|
|
73314
|
+
}
|
|
73315
|
+
top.items.push({ lines: [m[3]], children: [] });
|
|
73316
|
+
}
|
|
73317
|
+
return { html: roots.map((r) => renderList(r, o)).join(""), next: i };
|
|
73318
|
+
}
|
|
73319
|
+
function render(src, o, depth) {
|
|
73320
|
+
const lines = src.split("\n");
|
|
73321
|
+
const out = [];
|
|
73322
|
+
let para = [];
|
|
73323
|
+
const flush = () => {
|
|
73324
|
+
if (para.length > 0)
|
|
73325
|
+
out.push(`<p>${renderInline(para.join("\n"), o)}</p>`);
|
|
73326
|
+
para = [];
|
|
73327
|
+
};
|
|
73328
|
+
for (let i = 0; i < lines.length; ) {
|
|
73329
|
+
const line = lines[i];
|
|
73330
|
+
const fence = /^ {0,3}(`{3,}|~{3,})/.exec(line);
|
|
73331
|
+
if (fence !== null) {
|
|
73332
|
+
flush();
|
|
73333
|
+
const close = new RegExp(`^ {0,3}${fence[1][0] === "`" ? "`" : "~"}{${fence[1].length},}\\s*$`);
|
|
73334
|
+
const code2 = [];
|
|
73335
|
+
for (i++; i < lines.length && !close.test(lines[i]); i++)
|
|
73336
|
+
code2.push(lines[i]);
|
|
73337
|
+
i++;
|
|
73338
|
+
out.push(`<pre><code>${escapeHtml(code2.join("\n"))}</code></pre>`);
|
|
73339
|
+
continue;
|
|
73340
|
+
}
|
|
73341
|
+
if (LIST_ITEM.test(line)) {
|
|
73342
|
+
flush();
|
|
73343
|
+
const { html, next } = parseList(lines, i, o);
|
|
73344
|
+
out.push(html);
|
|
73345
|
+
i = next;
|
|
73346
|
+
continue;
|
|
73347
|
+
}
|
|
73348
|
+
const heading = o.headings === true ? HEADING.exec(line) : null;
|
|
73349
|
+
if (heading !== null) {
|
|
73350
|
+
flush();
|
|
73351
|
+
const level = heading[1].length;
|
|
73352
|
+
out.push(`<h${level}>${renderInline(heading[2], o)}</h${level}>`);
|
|
73353
|
+
i++;
|
|
73354
|
+
continue;
|
|
73355
|
+
}
|
|
73356
|
+
if (o.quotes === true && depth < 20 && QUOTE.test(line)) {
|
|
73357
|
+
flush();
|
|
73358
|
+
const quoted = [];
|
|
73359
|
+
for (; i < lines.length; i++) {
|
|
73360
|
+
const q = QUOTE.exec(lines[i]);
|
|
73361
|
+
if (q === null)
|
|
73362
|
+
break;
|
|
73363
|
+
quoted.push(q[1]);
|
|
73364
|
+
}
|
|
73365
|
+
out.push(`<blockquote>${render(quoted.join("\n"), o, depth + 1)}</blockquote>`);
|
|
73366
|
+
continue;
|
|
73367
|
+
}
|
|
73368
|
+
if (line.trim() === "")
|
|
73369
|
+
flush();
|
|
73370
|
+
else
|
|
73371
|
+
para.push(line);
|
|
73372
|
+
i++;
|
|
73373
|
+
}
|
|
73374
|
+
flush();
|
|
73375
|
+
return out.join("");
|
|
73376
|
+
}
|
|
73377
|
+
function renderMarkdown(src, opts = {}) {
|
|
73378
|
+
return render(src.replace(/\r\n?/g, "\n").replace(RESERVED2, ""), opts, 0);
|
|
73379
|
+
}
|
|
73380
|
+
|
|
72863
73381
|
// src/feed.ts
|
|
72864
73382
|
var MESSAGE_TYPES = /* @__PURE__ */ new Set(["feed_message", "workspace_email", "workspace_file_email"]);
|
|
72865
73383
|
function str2(o, k) {
|
|
@@ -72917,9 +73435,6 @@ function itemAttachments(item) {
|
|
|
72917
73435
|
function collapseWhitespace(s) {
|
|
72918
73436
|
return s.replace(/\s+/g, " ").trim();
|
|
72919
73437
|
}
|
|
72920
|
-
function collapseHorizontalWhitespace(s) {
|
|
72921
|
-
return s.replace(/[^\S\n]+/g, " ").replace(/ ?\n ?/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
72922
|
-
}
|
|
72923
73438
|
var PREVIEW_CHARS = 240;
|
|
72924
73439
|
function summarizeMessage(item, feed, meId) {
|
|
72925
73440
|
const data = obj(item, "data");
|
|
@@ -72951,22 +73466,24 @@ function summarizeMessage(item, feed, meId) {
|
|
|
72951
73466
|
}
|
|
72952
73467
|
return out;
|
|
72953
73468
|
}
|
|
73469
|
+
var BODY_FORMATS = ["text", "markdown", "html"];
|
|
73470
|
+
function messageBody(data, format) {
|
|
73471
|
+
const html = str2(data, "html_body");
|
|
73472
|
+
const plain = str2(data, "plain_text");
|
|
73473
|
+
if (format === "html") return html ?? plain ?? "";
|
|
73474
|
+
if (format === "markdown") return html !== void 0 ? htmlToMarkdown(html) : plain ?? "";
|
|
73475
|
+
return plain ?? (html !== void 0 ? htmlToText(html, { paragraphs: true }) : "");
|
|
73476
|
+
}
|
|
72954
73477
|
function expandMessage(item, feed, meId, format) {
|
|
72955
73478
|
const data = obj(item, "data");
|
|
72956
73479
|
const summary = summarizeMessage(item, feed, meId);
|
|
72957
73480
|
delete summary.preview;
|
|
72958
|
-
const body = format === "html" ? str2(data, "html_body") ?? str2(data, "plain_text") ?? "" : str2(data, "plain_text") ?? htmlToText(str2(data, "html_body") ?? "");
|
|
72959
73481
|
const delivery = arr(item, "email_tracking").map((t) => ({
|
|
72960
73482
|
recipient: t.recipient_name,
|
|
72961
73483
|
status: t.status,
|
|
72962
73484
|
date: t.date ?? null
|
|
72963
73485
|
}));
|
|
72964
|
-
return { ...summary, body_format: format, body:
|
|
72965
|
-
}
|
|
72966
|
-
function htmlToText(html) {
|
|
72967
|
-
return collapseHorizontalWhitespace(
|
|
72968
|
-
html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div|li|h\d|tr)>/gi, "\n").replace(/<[^>]+>/g, "").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'")
|
|
72969
|
-
);
|
|
73486
|
+
return { ...summary, body_format: format, body: messageBody(data, format).trim(), delivery };
|
|
72970
73487
|
}
|
|
72971
73488
|
function compactCalendarItem(c) {
|
|
72972
73489
|
const opt = (k) => {
|
|
@@ -73249,7 +73766,7 @@ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
|
|
|
73249
73766
|
var HTML_TAG = /<\/?(p|br|div|a|b|i|u|strong|em|ul|ol|li|span|h[1-6]|table|tr|td|th|blockquote|pre|code|hr)\b[^>]*>/i;
|
|
73250
73767
|
var ANY_TAG = /<(\/?)([a-zA-Z][a-zA-Z0-9]*)\b([^<>]*)>/g;
|
|
73251
73768
|
var HREF_ATTR = /(?:^|\s)href\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/i;
|
|
73252
|
-
var
|
|
73769
|
+
var SAFE_HREF2 = /^(?:https?:\/\/|mailto:)/i;
|
|
73253
73770
|
var escapeAngles = (s) => s.replace(/</g, "<").replace(/>/g, ">");
|
|
73254
73771
|
function sanitizeTag(closing, name, attrs) {
|
|
73255
73772
|
const tag = name.toLowerCase();
|
|
@@ -73258,7 +73775,7 @@ function sanitizeTag(closing, name, attrs) {
|
|
|
73258
73775
|
if (tag === "a") {
|
|
73259
73776
|
const m = HREF_ATTR.exec(attrs);
|
|
73260
73777
|
const href = (m?.[1] ?? m?.[2] ?? m?.[3] ?? "").trim();
|
|
73261
|
-
if (
|
|
73778
|
+
if (SAFE_HREF2.test(href)) return `<a href="${href.replace(/"/g, """)}">`;
|
|
73262
73779
|
}
|
|
73263
73780
|
return `<${tag}>`;
|
|
73264
73781
|
}
|
|
@@ -73272,9 +73789,12 @@ function sanitizeHtml(html) {
|
|
|
73272
73789
|
}
|
|
73273
73790
|
return out + escapeAngles(html.slice(last));
|
|
73274
73791
|
}
|
|
73275
|
-
|
|
73276
|
-
|
|
73277
|
-
|
|
73792
|
+
var WRITE_FORMATS = ["text", "markdown", "html"];
|
|
73793
|
+
function bodyToHtml(body, format) {
|
|
73794
|
+
const as = format ?? (HTML_TAG.test(body) ? "html" : "text");
|
|
73795
|
+
if (as === "markdown") return renderMarkdown(body, { headings: true, quotes: true });
|
|
73796
|
+
if (as === "html") return sanitizeHtml(body);
|
|
73797
|
+
return escapeHtml(body).replace(/\r?\n/g, "<br>");
|
|
73278
73798
|
}
|
|
73279
73799
|
async function sendMessage(args, ctx) {
|
|
73280
73800
|
const client = await getActiveClient(args.origin);
|
|
@@ -73303,7 +73823,7 @@ async function sendMessage(args, ctx) {
|
|
|
73303
73823
|
const taskData = {
|
|
73304
73824
|
ws_id: args.workspace_id,
|
|
73305
73825
|
subject,
|
|
73306
|
-
html_body: bodyToHtml(args.body),
|
|
73826
|
+
html_body: bodyToHtml(args.body, args.format),
|
|
73307
73827
|
force: false,
|
|
73308
73828
|
general_files: [],
|
|
73309
73829
|
image_files: [],
|
|
@@ -73411,11 +73931,13 @@ function registerMessageTools(server) {
|
|
|
73411
73931
|
server.registerTool(
|
|
73412
73932
|
"get_message",
|
|
73413
73933
|
{
|
|
73414
|
-
description: "Read one message in full: body (plain text by default, or the original HTML), sender, recipients, attachments and per-recipient delivery status.",
|
|
73934
|
+
description: "Read one message in full: body (HoneyBook's plain text by default, Markdown, or the original HTML), sender, recipients, attachments and per-recipient delivery status.",
|
|
73415
73935
|
inputSchema: external_exports.object({
|
|
73416
73936
|
workspace_id: external_exports.string().describe(WORKSPACE_DESC),
|
|
73417
73937
|
message_id: external_exports.string().describe("The message _id from list_messages."),
|
|
73418
|
-
format: external_exports.enum(
|
|
73938
|
+
format: external_exports.enum(BODY_FORMATS).optional().describe(
|
|
73939
|
+
`Body format: "text" (default, HoneyBook's plain text), "markdown" (keeps lists, paragraphs and links with their address, which the plain text loses; tracking pixels and hidden text dropped) or "html" (the original, raw).`
|
|
73940
|
+
),
|
|
73419
73941
|
origin: schemaOrigin.describe(ORIGIN_DESC)
|
|
73420
73942
|
}),
|
|
73421
73943
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
@@ -73429,7 +73951,12 @@ function registerMessageTools(server) {
|
|
|
73429
73951
|
description: 'Send a message to the vendor (and the other members of the workspace) through the HoneyBook portal, exactly as the Activity tab composer does. HoneyBook emails it to every recipient. Pass reply_to_message_id to reply in-thread (the subject is inherited). Asks the user to confirm first: a confirmation prompt where the client supports one; otherwise the first call returns a preview and a confirmToken, and only a repeat call with that token proceeds (see MCP_CONFIRM_MODE). If HoneyBook has not finished sending within about a minute the result has status "pending" and a task_id; if it did not answer the send request at all the status is "unknown". Either way the message may still be delivered, so check list_messages before resending.',
|
|
73430
73952
|
inputSchema: external_exports.object({
|
|
73431
73953
|
workspace_id: external_exports.string().describe(WORKSPACE_DESC),
|
|
73432
|
-
body: external_exports.string().describe(
|
|
73954
|
+
body: external_exports.string().describe(
|
|
73955
|
+
"Message text, written as `format` says. With no format, plain text is sent as-is (line breaks preserved) and a body using HTML tags is read as HTML."
|
|
73956
|
+
),
|
|
73957
|
+
format: external_exports.enum(WRITE_FORMATS).optional().describe(
|
|
73958
|
+
'"text" (sent as typed), "markdown" (**bold**, _italic_, lists, [links](https://\u2026), `code`, # headings, > quotes \u2014 sent as formatted HTML, and cheaper to write than HTML) or "html" (simple formatting kept: p, br, b, i, a href, lists, tables; other tags shown as text, attributes other than an http(s)/mailto link dropped). Omitted: "html" if the body uses an HTML tag, else "text".'
|
|
73959
|
+
),
|
|
73433
73960
|
subject: external_exports.string().optional().describe("Required for a new message; optional on a reply."),
|
|
73434
73961
|
reply_to_message_id: external_exports.string().optional().describe("A message _id from list_messages to reply to."),
|
|
73435
73962
|
origin: schemaOrigin.describe(ORIGIN_DESC),
|
|
@@ -73818,13 +74345,13 @@ async function listAllProjects() {
|
|
|
73818
74345
|
}
|
|
73819
74346
|
return rows;
|
|
73820
74347
|
}
|
|
73821
|
-
async function loadProjectDirectory(
|
|
74348
|
+
async function loadProjectDirectory(list2 = listAllProjects, timeoutMs = PROJECT_LIST_TIMEOUT_MS) {
|
|
73822
74349
|
let timer;
|
|
73823
74350
|
const deadline = new Promise((resolve2) => {
|
|
73824
74351
|
timer = setTimeout(() => resolve2(null), timeoutMs);
|
|
73825
74352
|
timer.unref();
|
|
73826
74353
|
});
|
|
73827
|
-
const listing = Promise.resolve().then(
|
|
74354
|
+
const listing = Promise.resolve().then(list2).then(buildProjectDirectory).catch(() => null);
|
|
73828
74355
|
try {
|
|
73829
74356
|
return await Promise.race([listing, deadline]);
|
|
73830
74357
|
} finally {
|
package/dist/feed.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { htmlToMarkdown, htmlToText } from '@chrischall/mcp-utils/rich';
|
|
1
2
|
import { apiPath } from './client.js';
|
|
2
3
|
import { VENDOR_SECRET_KEYS, scrubVendorSecrets } from './vendor-secrets.js';
|
|
3
4
|
/**
|
|
@@ -72,14 +73,6 @@ export function itemAttachments(item) {
|
|
|
72
73
|
export function collapseWhitespace(s) {
|
|
73
74
|
return s.replace(/\s+/g, ' ').trim();
|
|
74
75
|
}
|
|
75
|
-
/** Collapse runs of spaces but keep paragraph breaks (at most one blank line). */
|
|
76
|
-
export function collapseHorizontalWhitespace(s) {
|
|
77
|
-
return s
|
|
78
|
-
.replace(/[^\S\n]+/g, ' ')
|
|
79
|
-
.replace(/ ?\n ?/g, '\n')
|
|
80
|
-
.replace(/\n{3,}/g, '\n\n')
|
|
81
|
-
.trim();
|
|
82
|
-
}
|
|
83
76
|
const PREVIEW_CHARS = 240;
|
|
84
77
|
/** Compact card for a message item. Bodies are deliberately left out. */
|
|
85
78
|
export function summarizeMessage(item, feed, meId) {
|
|
@@ -120,35 +113,36 @@ export function summarizeMessage(item, feed, meId) {
|
|
|
120
113
|
}
|
|
121
114
|
return out;
|
|
122
115
|
}
|
|
116
|
+
/** How get_message returns a body. */
|
|
117
|
+
export const BODY_FORMATS = ['text', 'markdown', 'html'];
|
|
118
|
+
/**
|
|
119
|
+
* A message body in the format asked for. "text" is HoneyBook's own
|
|
120
|
+
* `plain_text`, and "html" the raw `html_body`, as they always were.
|
|
121
|
+
* "markdown" reads the HTML (lists, paragraphs and links with their address
|
|
122
|
+
* survive, which `plain_text` loses; tracking pixels, styles and hidden text
|
|
123
|
+
* go). `str` folds '' to undefined, so an empty field falls through to the
|
|
124
|
+
* other rather than returning an empty body next to a real one.
|
|
125
|
+
*/
|
|
126
|
+
function messageBody(data, format) {
|
|
127
|
+
const html = str(data, 'html_body');
|
|
128
|
+
const plain = str(data, 'plain_text');
|
|
129
|
+
if (format === 'html')
|
|
130
|
+
return html ?? plain ?? '';
|
|
131
|
+
if (format === 'markdown')
|
|
132
|
+
return html !== undefined ? htmlToMarkdown(html) : (plain ?? '');
|
|
133
|
+
return plain ?? (html !== undefined ? htmlToText(html, { paragraphs: true }) : '');
|
|
134
|
+
}
|
|
123
135
|
/** Everything a person needs to read one message. */
|
|
124
136
|
export function expandMessage(item, feed, meId, format) {
|
|
125
137
|
const data = obj(item, 'data');
|
|
126
138
|
const summary = summarizeMessage(item, feed, meId);
|
|
127
139
|
delete summary.preview;
|
|
128
|
-
// `str` folds '' to undefined, so an empty plain_text falls through to the
|
|
129
|
-
// html_body rather than returning an empty body next to a real one.
|
|
130
|
-
const body = format === 'html'
|
|
131
|
-
? (str(data, 'html_body') ?? str(data, 'plain_text') ?? '')
|
|
132
|
-
: (str(data, 'plain_text') ?? htmlToText(str(data, 'html_body') ?? ''));
|
|
133
140
|
const delivery = arr(item, 'email_tracking').map((t) => ({
|
|
134
141
|
recipient: t.recipient_name,
|
|
135
142
|
status: t.status,
|
|
136
143
|
date: t.date ?? null,
|
|
137
144
|
}));
|
|
138
|
-
return { ...summary, body_format: format, body:
|
|
139
|
-
}
|
|
140
|
-
/** Last-resort text for an item whose `plain_text` is missing. Paragraph breaks survive. */
|
|
141
|
-
export function htmlToText(html) {
|
|
142
|
-
return collapseHorizontalWhitespace(html
|
|
143
|
-
.replace(/<br\s*\/?>/gi, '\n')
|
|
144
|
-
.replace(/<\/(p|div|li|h\d|tr)>/gi, '\n')
|
|
145
|
-
.replace(/<[^>]+>/g, '')
|
|
146
|
-
.replace(/ /g, ' ')
|
|
147
|
-
.replace(/&/g, '&')
|
|
148
|
-
.replace(/</g, '<')
|
|
149
|
-
.replace(/>/g, '>')
|
|
150
|
-
.replace(/"/g, '"')
|
|
151
|
-
.replace(/'/g, "'"));
|
|
145
|
+
return { ...summary, body_format: format, body: messageBody(data, format).trim(), delivery };
|
|
152
146
|
}
|
|
153
147
|
/**
|
|
154
148
|
* A calendar item as the feed embeds it, minus the vendor's host link
|
package/dist/tools/messages.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { confirmationFromEnv, confirmTokenParam, minifiedResult, requireConfirmationWithFallback, schemaOrigin, } from '@chrischall/mcp-utils';
|
|
3
|
+
import { escapeHtml, renderMarkdown } from '@chrischall/mcp-utils/rich';
|
|
3
4
|
import { apiPath, getActiveClient } from '../client.js';
|
|
4
|
-
import { fetchWorkspaceFeed, isMessageItem, itemDate, summarizeMessage, summarizeActivity, expandMessage, } from '../feed.js';
|
|
5
|
+
import { fetchWorkspaceFeed, isMessageItem, itemDate, summarizeMessage, summarizeActivity, expandMessage, BODY_FORMATS, } from '../feed.js';
|
|
5
6
|
import { runClientPendingTask, isPendingTaskTimeoutError, isPendingTaskCreateTimeoutError, } from '../pending-tasks.js';
|
|
6
7
|
import { moneyMeta } from './hints.js';
|
|
7
8
|
export const FEED_KINDS = ['messages', 'activity', 'all'];
|
|
@@ -65,10 +66,12 @@ export async function getMessage(args) {
|
|
|
65
66
|
return minifiedResult(expandMessage(item, feed, client.scope.userId, args.format ?? 'text'));
|
|
66
67
|
}
|
|
67
68
|
/**
|
|
68
|
-
* The composer sends HTML.
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
69
|
+
* The composer sends HTML. A body is written as `format` says: "text" is
|
|
70
|
+
* escaped with line breaks as `<br>`; "markdown" is rendered (raw HTML in it
|
|
71
|
+
* escaped, links http(s)/mailto only); "html" is sanitised. With no format, a
|
|
72
|
+
* body that uses a real HTML tag is treated as authored HTML and anything else
|
|
73
|
+
* as text. The tag list is an allowlist on purpose: "<Ivy>" in a sentence is
|
|
74
|
+
* prose, not markup.
|
|
72
75
|
*
|
|
73
76
|
* Authored HTML is SANITIZED to that allowlist, not passed through
|
|
74
77
|
* (fleet-audit#506): HoneyBook emails it under the user's name, and a body the
|
|
@@ -77,6 +80,10 @@ export async function getMessage(args) {
|
|
|
77
80
|
* text; allowlisted tags lose every attribute except an http(s)/mailto `href`
|
|
78
81
|
* on `<a>`. `<img>` is deliberately not allowlisted — a remote image in an
|
|
79
82
|
* email is a read receipt.
|
|
83
|
+
*
|
|
84
|
+
* This stays local rather than /rich's `sanitiseHtml`: that is a READ
|
|
85
|
+
* sanitiser and removes unknown tags (and everything after an unclosed
|
|
86
|
+
* `<script`), which on a write would silently drop the caller's own words.
|
|
80
87
|
*/
|
|
81
88
|
const ALLOWED_TAGS = new Set([
|
|
82
89
|
'p', 'br', 'div', 'a', 'b', 'i', 'u', 'strong', 'em', 'ul', 'ol', 'li', 'span',
|
|
@@ -111,14 +118,14 @@ export function sanitizeHtml(html) {
|
|
|
111
118
|
}
|
|
112
119
|
return out + escapeAngles(html.slice(last));
|
|
113
120
|
}
|
|
114
|
-
export
|
|
115
|
-
|
|
121
|
+
export const WRITE_FORMATS = ['text', 'markdown', 'html'];
|
|
122
|
+
export function bodyToHtml(body, format) {
|
|
123
|
+
const as = format ?? (HTML_TAG.test(body) ? 'html' : 'text');
|
|
124
|
+
if (as === 'markdown')
|
|
125
|
+
return renderMarkdown(body, { headings: true, quotes: true });
|
|
126
|
+
if (as === 'html')
|
|
116
127
|
return sanitizeHtml(body);
|
|
117
|
-
return body
|
|
118
|
-
.replace(/&/g, '&')
|
|
119
|
-
.replace(/</g, '<')
|
|
120
|
-
.replace(/>/g, '>')
|
|
121
|
-
.replace(/\r?\n/g, '<br>');
|
|
128
|
+
return escapeHtml(body).replace(/\r?\n/g, '<br>');
|
|
122
129
|
}
|
|
123
130
|
export async function sendMessage(args, ctx) {
|
|
124
131
|
const client = await getActiveClient(args.origin);
|
|
@@ -146,7 +153,7 @@ export async function sendMessage(args, ctx) {
|
|
|
146
153
|
const taskData = {
|
|
147
154
|
ws_id: args.workspace_id,
|
|
148
155
|
subject,
|
|
149
|
-
html_body: bodyToHtml(args.body),
|
|
156
|
+
html_body: bodyToHtml(args.body, args.format),
|
|
150
157
|
force: false,
|
|
151
158
|
general_files: [],
|
|
152
159
|
image_files: [],
|
|
@@ -262,12 +269,16 @@ export function registerMessageTools(server) {
|
|
|
262
269
|
_meta: moneyMeta('whats-new', { aliases: ['messages', 'emails', 'vendor said', 'inbox'] }),
|
|
263
270
|
}, listMessages);
|
|
264
271
|
server.registerTool('get_message', {
|
|
265
|
-
description: 'Read one message in full: body (plain text by default, or the original HTML), sender, recipients, ' +
|
|
272
|
+
description: 'Read one message in full: body (HoneyBook\'s plain text by default, Markdown, or the original HTML), sender, recipients, ' +
|
|
266
273
|
'attachments and per-recipient delivery status.',
|
|
267
274
|
inputSchema: z.object({
|
|
268
275
|
workspace_id: z.string().describe(WORKSPACE_DESC),
|
|
269
276
|
message_id: z.string().describe('The message _id from list_messages.'),
|
|
270
|
-
format: z
|
|
277
|
+
format: z
|
|
278
|
+
.enum(BODY_FORMATS)
|
|
279
|
+
.optional()
|
|
280
|
+
.describe('Body format: "text" (default, HoneyBook\'s plain text), "markdown" (keeps lists, paragraphs and links ' +
|
|
281
|
+
'with their address, which the plain text loses; tracking pixels and hidden text dropped) or "html" (the original, raw).'),
|
|
271
282
|
origin: schemaOrigin.describe(ORIGIN_DESC),
|
|
272
283
|
}),
|
|
273
284
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
@@ -285,7 +296,15 @@ export function registerMessageTools(server) {
|
|
|
285
296
|
workspace_id: z.string().describe(WORKSPACE_DESC),
|
|
286
297
|
body: z
|
|
287
298
|
.string()
|
|
288
|
-
.describe('Message text
|
|
299
|
+
.describe('Message text, written as `format` says. With no format, plain text is sent as-is (line breaks preserved) ' +
|
|
300
|
+
'and a body using HTML tags is read as HTML.'),
|
|
301
|
+
format: z
|
|
302
|
+
.enum(WRITE_FORMATS)
|
|
303
|
+
.optional()
|
|
304
|
+
.describe('"text" (sent as typed), "markdown" (**bold**, _italic_, lists, [links](https://…), `code`, # headings, ' +
|
|
305
|
+
'> quotes — sent as formatted HTML, and cheaper to write than HTML) or "html" (simple formatting kept: ' +
|
|
306
|
+
'p, br, b, i, a href, lists, tables; other tags shown as text, attributes other than an ' +
|
|
307
|
+
'http(s)/mailto link dropped). Omitted: "html" if the body uses an HTML tag, else "text".'),
|
|
289
308
|
subject: z.string().optional().describe('Required for a new message; optional on a reply.'),
|
|
290
309
|
reply_to_message_id: z
|
|
291
310
|
.string()
|
package/dist/version.js
CHANGED
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
// package.json drift. Importing these instead of package.json keeps the
|
|
5
5
|
// whole manifest — npm scripts included — out of dist/bundle.js.
|
|
6
6
|
export const SERVER_NAME = 'honeybook-mcp';
|
|
7
|
-
export const VERSION = '1.
|
|
7
|
+
export const VERSION = '1.4.0'; // x-release-please-version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "honeybook-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"mcpName": "io.github.chrischall/honeybook-mcp",
|
|
5
5
|
"description": "HoneyBook client-portal MCP server for Claude",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@chrischall/mcp-utils": "^3.
|
|
47
|
+
"@chrischall/mcp-utils": "^3.8.0",
|
|
48
48
|
"@fetchproxy/bootstrap": "^3.6.2",
|
|
49
49
|
"@fetchproxy/server": "^3.6.2",
|
|
50
50
|
"@modelcontextprotocol/server": "^2.3.0",
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/chrischall/honeybook-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.4.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "honeybook-mcp",
|
|
14
|
-
"version": "1.
|
|
14
|
+
"version": "1.4.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
|
@@ -22,8 +22,8 @@ MCP server for HoneyBook's client portal — contracts, invoices, questionnaires
|
|
|
22
22
|
- `list_projects` — Your projects (HoneyBook "events") with a vendor, each with the `workspace_id` the tools below take
|
|
23
23
|
- `get_project(project_id, view?)` — Project details: date, time, location, guests, custom fields, people (name/email/phone/role)
|
|
24
24
|
- `list_messages` — Messages in a workspace (`kind=activity` for the activity log, `kind=all` for both): compact cards, newest first; never marks anything seen
|
|
25
|
-
- `get_message` — One message in full (`format=text` default, or `html`) with attachments and delivery status
|
|
26
|
-
- `send_message` — Send a new message (`subject` + `body`) or reply (`reply_to_message_id`, subject inherited) through the portal; asks the user to confirm first
|
|
25
|
+
- `get_message` — One message in full (`format=text` default, `markdown` to keep lists and links, or the raw `html`) with attachments and delivery status
|
|
26
|
+
- `send_message` — Send a new message (`subject` + `body`) or reply (`reply_to_message_id`, subject inherited) through the portal; `format=markdown` sends a Markdown body as formatted HTML; asks the user to confirm first
|
|
27
27
|
- `mark_messages_seen` — Mark feed items seen, the way opening the Activity tab does
|
|
28
28
|
- `list_meetings` — Meetings the vendor scheduled (consultations, Zoom calls): time, join link, password; rescheduled meetings show their latest time
|
|
29
29
|
- `list_tasks` — Tasks assigned to you with today/this-week/overdue counts and task groups
|