@sellable/mcp 0.1.240 → 0.1.243
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 +2 -1
- package/dist/engage-memory.d.ts +1 -0
- package/dist/engage-memory.js +3 -0
- package/dist/identity-memory.d.ts +2 -0
- package/dist/identity-memory.js +5 -0
- package/dist/server.js +13 -1
- package/dist/tools/content-posts.d.ts +315 -0
- package/dist/tools/content-posts.js +370 -15
- package/dist/tools/engage-memory.js +1 -1
- package/dist/tools/registry.d.ts +204 -0
- package/package.json +1 -1
- package/skills/create-post/SKILL.md +132 -18
- package/skills/create-post/references/hook-research-playbook.md +69 -2
- package/skills/create-post/references/post-file-contract.md +24 -2
- package/skills/create-post/references/post-validation.md +101 -6
- package/skills/create-post/references/premise-development.md +159 -0
- package/skills/engage/SKILL.md +1 -0
- package/skills/engage/core/README.md +15 -12
|
@@ -80,13 +80,18 @@ export const contentPostToolDefinitions = [
|
|
|
80
80
|
},
|
|
81
81
|
{
|
|
82
82
|
name: "save_post_draft",
|
|
83
|
-
description: "Save a validated LinkedIn post draft under ~/.sellable/content/linkedin/drafts.",
|
|
83
|
+
description: "Save a validated LinkedIn post draft under ~/.sellable/content/linkedin/drafts. Use versioned draft IDs for multiple drafts of the same idea.",
|
|
84
84
|
inputSchema: {
|
|
85
85
|
type: "object",
|
|
86
86
|
properties: {
|
|
87
87
|
draftId: { type: "string" },
|
|
88
88
|
ideaId: { type: "string" },
|
|
89
89
|
hookResearchId: { type: "string" },
|
|
90
|
+
priorDraftId: {
|
|
91
|
+
type: "string",
|
|
92
|
+
description: "Optional previous draft ID this draft is trying to improve on.",
|
|
93
|
+
},
|
|
94
|
+
iteration: draftIterationSchema(),
|
|
90
95
|
title: { type: "string" },
|
|
91
96
|
body: { type: "string" },
|
|
92
97
|
validationReceipt: {
|
|
@@ -99,11 +104,38 @@ export const contentPostToolDefinitions = [
|
|
|
99
104
|
additionalProperties: false,
|
|
100
105
|
},
|
|
101
106
|
},
|
|
107
|
+
{
|
|
108
|
+
name: "update_post_draft",
|
|
109
|
+
description: "Update an existing LinkedIn post draft in place while preserving omitted fields. Useful for adding iteration receipts, status changes, or final copy edits without rewriting the whole artifact.",
|
|
110
|
+
inputSchema: {
|
|
111
|
+
type: "object",
|
|
112
|
+
properties: {
|
|
113
|
+
draftId: { type: "string" },
|
|
114
|
+
hookResearchId: { type: "string" },
|
|
115
|
+
priorDraftId: { type: "string" },
|
|
116
|
+
iteration: draftIterationSchema(),
|
|
117
|
+
title: { type: "string" },
|
|
118
|
+
body: { type: "string" },
|
|
119
|
+
validationReceipt: {
|
|
120
|
+
description: "Markdown string or structured object to replace the saved validation receipt.",
|
|
121
|
+
},
|
|
122
|
+
status: { type: "string" },
|
|
123
|
+
updatedAt: { type: "string" },
|
|
124
|
+
},
|
|
125
|
+
required: ["draftId"],
|
|
126
|
+
additionalProperties: false,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
102
129
|
{
|
|
103
130
|
name: "list_post_drafts",
|
|
104
131
|
description: "List saved LinkedIn post drafts with compact sanitized previews. Use get_post_draft for full Markdown.",
|
|
105
132
|
inputSchema: listInputSchema(),
|
|
106
133
|
},
|
|
134
|
+
{
|
|
135
|
+
name: "list_post_draft_iterations",
|
|
136
|
+
description: "List all draft versions for one idea, including iteration scores, verdicts, prior draft links, status, and sanitized previews.",
|
|
137
|
+
inputSchema: idInputSchema("ideaId"),
|
|
138
|
+
},
|
|
107
139
|
{
|
|
108
140
|
name: "get_post_draft",
|
|
109
141
|
description: "Read one saved LinkedIn post draft by ID.",
|
|
@@ -121,11 +153,47 @@ export const contentPostToolDefinitions = [
|
|
|
121
153
|
publishedAt: { type: "string" },
|
|
122
154
|
finalText: { type: "string" },
|
|
123
155
|
title: { type: "string" },
|
|
156
|
+
updateDraftStatus: {
|
|
157
|
+
type: "boolean",
|
|
158
|
+
description: "Defaults to true when draftId is supplied. Marks the source draft as published and links it to the published record.",
|
|
159
|
+
},
|
|
124
160
|
},
|
|
125
161
|
required: ["publishUrl"],
|
|
126
162
|
additionalProperties: false,
|
|
127
163
|
},
|
|
128
164
|
},
|
|
165
|
+
{
|
|
166
|
+
name: "get_published_post",
|
|
167
|
+
description: "Read one published LinkedIn post record by ID. If year is omitted, searches all published year folders.",
|
|
168
|
+
inputSchema: {
|
|
169
|
+
type: "object",
|
|
170
|
+
properties: {
|
|
171
|
+
publishedPostId: { type: "string" },
|
|
172
|
+
year: { type: "string" },
|
|
173
|
+
},
|
|
174
|
+
required: ["publishedPostId"],
|
|
175
|
+
additionalProperties: false,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: "update_published_post_metrics",
|
|
180
|
+
description: "Append a metrics snapshot to a published LinkedIn post record and update latest metric fields for post-performance learning.",
|
|
181
|
+
inputSchema: {
|
|
182
|
+
type: "object",
|
|
183
|
+
properties: {
|
|
184
|
+
publishedPostId: { type: "string" },
|
|
185
|
+
year: { type: "string" },
|
|
186
|
+
capturedAt: { type: "string" },
|
|
187
|
+
metrics: {
|
|
188
|
+
type: "object",
|
|
189
|
+
additionalProperties: true,
|
|
190
|
+
},
|
|
191
|
+
note: { type: "string" },
|
|
192
|
+
},
|
|
193
|
+
required: ["publishedPostId", "metrics"],
|
|
194
|
+
additionalProperties: false,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
129
197
|
{
|
|
130
198
|
name: "list_published_posts",
|
|
131
199
|
description: "List published LinkedIn post records with compact sanitized previews.",
|
|
@@ -152,6 +220,13 @@ function idInputSchema(propertyName) {
|
|
|
152
220
|
additionalProperties: false,
|
|
153
221
|
};
|
|
154
222
|
}
|
|
223
|
+
function draftIterationSchema() {
|
|
224
|
+
return {
|
|
225
|
+
type: "object",
|
|
226
|
+
description: "Optional draft iteration metadata: version, priorDraftId, changeIntent, whatChanged, whatImproved, whatGotWorse, score, and verdict.",
|
|
227
|
+
additionalProperties: true,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
155
230
|
export function resolveContentRoot() {
|
|
156
231
|
const envValue = process.env[CONTENT_ROOT_ENV]?.trim();
|
|
157
232
|
if (envValue) {
|
|
@@ -274,14 +349,13 @@ export function savePostDraftTool(input) {
|
|
|
274
349
|
const safeResearchId = input.hookResearchId
|
|
275
350
|
? normalizeArtifactId(input.hookResearchId, "hookResearchId")
|
|
276
351
|
: undefined;
|
|
352
|
+
const iteration = normalizeDraftIteration(input.iteration, input.priorDraftId);
|
|
277
353
|
requireString(input.body, "body");
|
|
278
354
|
const id = input.draftId ??
|
|
279
355
|
`draft_${dateStamp(now)}_${slugify(input.title || safeIdeaId)}_v1`;
|
|
280
356
|
const safeId = normalizeArtifactId(id, "draftId");
|
|
281
357
|
const status = input.status || "draft";
|
|
282
|
-
|
|
283
|
-
throw new Error(`Unsupported draft status: ${status}`);
|
|
284
|
-
}
|
|
358
|
+
assertDraftStatus(status);
|
|
285
359
|
const metadata = {
|
|
286
360
|
id: safeId,
|
|
287
361
|
type: "draft",
|
|
@@ -289,6 +363,9 @@ export function savePostDraftTool(input) {
|
|
|
289
363
|
title: input.title,
|
|
290
364
|
ideaId: safeIdeaId,
|
|
291
365
|
hookResearchId: safeResearchId,
|
|
366
|
+
draftVersion: iteration?.version || inferDraftVersion(safeId),
|
|
367
|
+
priorDraftId: iteration?.priorDraftId,
|
|
368
|
+
iteration,
|
|
292
369
|
createdAt: now,
|
|
293
370
|
updatedAt: now,
|
|
294
371
|
};
|
|
@@ -304,13 +381,101 @@ export function savePostDraftTool(input) {
|
|
|
304
381
|
status,
|
|
305
382
|
ideaId: safeIdeaId,
|
|
306
383
|
hookResearchId: safeResearchId,
|
|
384
|
+
draftVersion: metadata.draftVersion,
|
|
385
|
+
priorDraftId: metadata.priorDraftId,
|
|
386
|
+
iterationScore: iteration?.score,
|
|
387
|
+
iterationVerdict: iteration?.verdict,
|
|
307
388
|
updatedAt: now,
|
|
308
389
|
preview: sanitizedPreview(input.body),
|
|
309
390
|
};
|
|
310
391
|
}
|
|
392
|
+
export function updatePostDraftTool(input) {
|
|
393
|
+
const safeDraftId = normalizeArtifactId(input.draftId, "draftId");
|
|
394
|
+
const relativePath = `${RELATIVE_DIRS.drafts}/${safeDraftId}.md`;
|
|
395
|
+
const existing = readArtifactIfExists(relativePath);
|
|
396
|
+
if (!existing) {
|
|
397
|
+
throw new Error(`No draftId found for ID: ${safeDraftId}`);
|
|
398
|
+
}
|
|
399
|
+
const now = normalizeDate(input.updatedAt);
|
|
400
|
+
const existingBody = extractMarkdownSection(existing.markdown, "Draft Body");
|
|
401
|
+
const existingReceipt = extractMarkdownSection(existing.markdown, "Validation Receipt");
|
|
402
|
+
const body = input.body ?? existingBody;
|
|
403
|
+
requireString(body, "body");
|
|
404
|
+
const validationReceipt = input.validationReceipt === undefined
|
|
405
|
+
? existingReceipt
|
|
406
|
+
: formatReceipt(input.validationReceipt);
|
|
407
|
+
requireString(validationReceipt, "validationReceipt");
|
|
408
|
+
const safeResearchId = input.hookResearchId
|
|
409
|
+
? normalizeArtifactId(input.hookResearchId, "hookResearchId")
|
|
410
|
+
: existing.metadata.hookResearchId;
|
|
411
|
+
const iteration = normalizeDraftIteration(input.iteration ??
|
|
412
|
+
existing.metadata.iteration, input.priorDraftId ?? existing.metadata.priorDraftId);
|
|
413
|
+
const status = input.status || existing.metadata.status || "draft";
|
|
414
|
+
assertDraftStatus(status);
|
|
415
|
+
const metadata = {
|
|
416
|
+
...existing.metadata,
|
|
417
|
+
status,
|
|
418
|
+
title: input.title ?? existing.metadata.title,
|
|
419
|
+
hookResearchId: safeResearchId,
|
|
420
|
+
draftVersion: iteration?.version ||
|
|
421
|
+
existing.metadata.draftVersion ||
|
|
422
|
+
inferDraftVersion(safeDraftId),
|
|
423
|
+
priorDraftId: iteration?.priorDraftId,
|
|
424
|
+
iteration,
|
|
425
|
+
updatedAt: now,
|
|
426
|
+
};
|
|
427
|
+
const markdown = buildMarkdown(metadata, [
|
|
428
|
+
["Draft Body", body],
|
|
429
|
+
["Validation Receipt", validationReceipt],
|
|
430
|
+
]);
|
|
431
|
+
writeArtifact(relativePath, markdown);
|
|
432
|
+
return {
|
|
433
|
+
id: safeDraftId,
|
|
434
|
+
path: relativePath,
|
|
435
|
+
status,
|
|
436
|
+
ideaId: metadata.ideaId,
|
|
437
|
+
hookResearchId: safeResearchId,
|
|
438
|
+
draftVersion: metadata.draftVersion,
|
|
439
|
+
priorDraftId: metadata.priorDraftId,
|
|
440
|
+
iterationScore: iteration?.score,
|
|
441
|
+
iterationVerdict: iteration?.verdict,
|
|
442
|
+
updatedAt: now,
|
|
443
|
+
preview: sanitizedPreview(body),
|
|
444
|
+
};
|
|
445
|
+
}
|
|
311
446
|
export function listPostDraftsTool(input) {
|
|
312
447
|
return listArtifacts(RELATIVE_DIRS.drafts, "draft", input?.limit);
|
|
313
448
|
}
|
|
449
|
+
export function listPostDraftIterationsTool(input) {
|
|
450
|
+
const safeIdeaId = normalizeArtifactId(input.ideaId, "ideaId");
|
|
451
|
+
const drafts = readArtifactsFromDir(RELATIVE_DIRS.drafts)
|
|
452
|
+
.filter((artifact) => artifact.metadata.type === "draft" &&
|
|
453
|
+
artifact.metadata.ideaId === safeIdeaId)
|
|
454
|
+
.map((artifact) => {
|
|
455
|
+
const iteration = artifact.metadata.iteration ||
|
|
456
|
+
extractIterationFromReceipt(extractMarkdownSection(artifact.markdown, "Validation Receipt"));
|
|
457
|
+
return {
|
|
458
|
+
id: artifact.metadata.id,
|
|
459
|
+
type: artifact.metadata.type,
|
|
460
|
+
status: artifact.metadata.status,
|
|
461
|
+
title: artifact.metadata.title,
|
|
462
|
+
path: artifact.relativePath,
|
|
463
|
+
ideaId: artifact.metadata.ideaId,
|
|
464
|
+
hookResearchId: artifact.metadata.hookResearchId,
|
|
465
|
+
draftVersion: artifact.metadata.draftVersion ||
|
|
466
|
+
iteration?.version ||
|
|
467
|
+
inferDraftVersion(artifact.metadata.id),
|
|
468
|
+
priorDraftId: artifact.metadata.priorDraftId || iteration?.priorDraftId,
|
|
469
|
+
iterationScore: iteration?.score,
|
|
470
|
+
iterationVerdict: iteration?.verdict,
|
|
471
|
+
publishedPostId: artifact.metadata.publishedPostId,
|
|
472
|
+
publishedAt: artifact.metadata.publishedAt,
|
|
473
|
+
updatedAt: artifact.metadata.updatedAt,
|
|
474
|
+
preview: sanitizedPreview(previewBasis(artifact.markdown)),
|
|
475
|
+
};
|
|
476
|
+
});
|
|
477
|
+
return drafts.sort(compareDraftIterations);
|
|
478
|
+
}
|
|
314
479
|
export function getPostDraftTool(input) {
|
|
315
480
|
return getArtifact(RELATIVE_DIRS.drafts, input.draftId, "draftId");
|
|
316
481
|
}
|
|
@@ -327,6 +492,15 @@ export function markPostPublishedTool(input) {
|
|
|
327
492
|
const relativePath = `${RELATIVE_DIRS.published}/${year}/${safeId}.md`;
|
|
328
493
|
const existing = readArtifactIfExists(relativePath);
|
|
329
494
|
const createdAt = existing?.metadata.createdAt || publishedAt;
|
|
495
|
+
const finalText = input.finalText ??
|
|
496
|
+
extractMarkdownSection(existing?.markdown || "", "Final Text");
|
|
497
|
+
const publishMetadata = readJsonSection(existing?.markdown || "", "Publish Metadata", {});
|
|
498
|
+
const futureMetrics = readJsonSection(existing?.markdown || "", "Future Metrics", {
|
|
499
|
+
impressions: null,
|
|
500
|
+
reactions: null,
|
|
501
|
+
comments: null,
|
|
502
|
+
snapshots: [],
|
|
503
|
+
});
|
|
330
504
|
const metadata = {
|
|
331
505
|
id: safeId,
|
|
332
506
|
type: "published_post",
|
|
@@ -340,27 +514,23 @@ export function markPostPublishedTool(input) {
|
|
|
340
514
|
updatedAt: publishedAt,
|
|
341
515
|
};
|
|
342
516
|
const markdown = buildMarkdown(metadata, [
|
|
343
|
-
["Final Text",
|
|
517
|
+
["Final Text", finalText],
|
|
344
518
|
[
|
|
345
519
|
"Publish Metadata",
|
|
346
520
|
jsonBlock(stripUndefined({
|
|
521
|
+
...publishMetadata,
|
|
347
522
|
draftId: safeDraftId,
|
|
348
523
|
publishUrl: input.publishUrl,
|
|
349
524
|
activityId: activityId || undefined,
|
|
350
525
|
publishedAt,
|
|
351
526
|
})),
|
|
352
527
|
],
|
|
353
|
-
[
|
|
354
|
-
"Future Metrics",
|
|
355
|
-
jsonBlock({
|
|
356
|
-
impressions: null,
|
|
357
|
-
reactions: null,
|
|
358
|
-
comments: null,
|
|
359
|
-
snapshots: [],
|
|
360
|
-
}),
|
|
361
|
-
],
|
|
528
|
+
["Future Metrics", jsonBlock(futureMetrics)],
|
|
362
529
|
]);
|
|
363
530
|
writeArtifact(relativePath, markdown);
|
|
531
|
+
if (safeDraftId && input.updateDraftStatus !== false) {
|
|
532
|
+
markDraftAsPublished(safeDraftId, safeId, publishedAt);
|
|
533
|
+
}
|
|
364
534
|
return {
|
|
365
535
|
id: safeId,
|
|
366
536
|
path: relativePath,
|
|
@@ -369,7 +539,65 @@ export function markPostPublishedTool(input) {
|
|
|
369
539
|
publishUrl: input.publishUrl,
|
|
370
540
|
publishedAt,
|
|
371
541
|
updatedAt: publishedAt,
|
|
372
|
-
preview: sanitizedPreview(
|
|
542
|
+
preview: sanitizedPreview(finalText || input.publishUrl),
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
export function getPublishedPostTool(input) {
|
|
546
|
+
const found = findPublishedArtifact(input.publishedPostId, input.year);
|
|
547
|
+
if (!found) {
|
|
548
|
+
throw new Error(`No publishedPostId found for ID: ${normalizeArtifactId(input.publishedPostId, "publishedPostId")}`);
|
|
549
|
+
}
|
|
550
|
+
return {
|
|
551
|
+
id: found.artifact.metadata.id,
|
|
552
|
+
path: found.relativePath,
|
|
553
|
+
metadata: found.artifact.metadata,
|
|
554
|
+
markdown: found.artifact.markdown,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
export function updatePublishedPostMetricsTool(input) {
|
|
558
|
+
if (!input.metrics || typeof input.metrics !== "object") {
|
|
559
|
+
throw new Error("metrics is required.");
|
|
560
|
+
}
|
|
561
|
+
const found = findPublishedArtifact(input.publishedPostId, input.year);
|
|
562
|
+
if (!found) {
|
|
563
|
+
throw new Error(`No publishedPostId found for ID: ${normalizeArtifactId(input.publishedPostId, "publishedPostId")}`);
|
|
564
|
+
}
|
|
565
|
+
const capturedAt = normalizeDate(input.capturedAt);
|
|
566
|
+
const finalText = extractMarkdownSection(found.artifact.markdown, "Final Text");
|
|
567
|
+
const publishMetadata = readJsonSection(found.artifact.markdown, "Publish Metadata", {});
|
|
568
|
+
const futureMetrics = readJsonSection(found.artifact.markdown, "Future Metrics", {});
|
|
569
|
+
const existingSnapshots = Array.isArray(futureMetrics.snapshots)
|
|
570
|
+
? futureMetrics.snapshots
|
|
571
|
+
: [];
|
|
572
|
+
const snapshot = stripUndefined({
|
|
573
|
+
capturedAt,
|
|
574
|
+
metrics: input.metrics,
|
|
575
|
+
note: input.note,
|
|
576
|
+
});
|
|
577
|
+
const nextMetrics = {
|
|
578
|
+
...futureMetrics,
|
|
579
|
+
...input.metrics,
|
|
580
|
+
lastCapturedAt: capturedAt,
|
|
581
|
+
snapshots: [...existingSnapshots, snapshot],
|
|
582
|
+
};
|
|
583
|
+
const metadata = {
|
|
584
|
+
...found.artifact.metadata,
|
|
585
|
+
updatedAt: capturedAt,
|
|
586
|
+
};
|
|
587
|
+
const markdown = buildMarkdown(metadata, [
|
|
588
|
+
["Final Text", finalText],
|
|
589
|
+
["Publish Metadata", jsonBlock(publishMetadata)],
|
|
590
|
+
["Future Metrics", jsonBlock(nextMetrics)],
|
|
591
|
+
]);
|
|
592
|
+
writeArtifact(found.relativePath, markdown);
|
|
593
|
+
return {
|
|
594
|
+
id: metadata.id,
|
|
595
|
+
path: found.relativePath,
|
|
596
|
+
status: metadata.status,
|
|
597
|
+
updatedAt: capturedAt,
|
|
598
|
+
metrics: input.metrics,
|
|
599
|
+
snapshotsCount: existingSnapshots.length + 1,
|
|
600
|
+
preview: sanitizedPreview(finalText || String(metadata.publishUrl || "")),
|
|
373
601
|
};
|
|
374
602
|
}
|
|
375
603
|
export function listPublishedPostsTool(input) {
|
|
@@ -423,8 +651,135 @@ function summarizeArtifacts(artifacts, limit) {
|
|
|
423
651
|
path: artifact.relativePath,
|
|
424
652
|
updatedAt: artifact.metadata.updatedAt,
|
|
425
653
|
preview: sanitizedPreview(previewBasis(artifact.markdown)),
|
|
654
|
+
ideaId: artifact.metadata.ideaId,
|
|
655
|
+
hookResearchId: artifact.metadata.hookResearchId,
|
|
656
|
+
draftVersion: artifact.metadata.draftVersion,
|
|
657
|
+
priorDraftId: artifact.metadata.priorDraftId,
|
|
658
|
+
iterationScore: artifact.metadata.iteration?.score,
|
|
659
|
+
iterationVerdict: artifact.metadata.iteration?.verdict,
|
|
660
|
+
publishedPostId: artifact.metadata.publishedPostId,
|
|
661
|
+
publishedAt: artifact.metadata.publishedAt,
|
|
426
662
|
}));
|
|
427
663
|
}
|
|
664
|
+
function assertDraftStatus(status) {
|
|
665
|
+
if (!["draft", "ready", "needs_revision", "published", "archived"].includes(status)) {
|
|
666
|
+
throw new Error(`Unsupported draft status: ${status}`);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
function normalizeDraftIteration(iteration, priorDraftId) {
|
|
670
|
+
if (!iteration && !priorDraftId)
|
|
671
|
+
return undefined;
|
|
672
|
+
const safePriorDraftId = priorDraftId
|
|
673
|
+
? normalizeArtifactId(priorDraftId, "priorDraftId")
|
|
674
|
+
: iteration?.priorDraftId
|
|
675
|
+
? normalizeArtifactId(iteration.priorDraftId, "priorDraftId")
|
|
676
|
+
: undefined;
|
|
677
|
+
return stripUndefined({
|
|
678
|
+
...iteration,
|
|
679
|
+
priorDraftId: safePriorDraftId,
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
function inferDraftVersion(draftId) {
|
|
683
|
+
const match = draftId.match(/(?:^|_)v(\d+)(?:$|_)/i);
|
|
684
|
+
return match ? `v${match[1]}` : undefined;
|
|
685
|
+
}
|
|
686
|
+
function compareDraftIterations(a, b) {
|
|
687
|
+
const aVersion = numericVersion(a.draftVersion);
|
|
688
|
+
const bVersion = numericVersion(b.draftVersion);
|
|
689
|
+
if (aVersion !== bVersion)
|
|
690
|
+
return aVersion - bVersion;
|
|
691
|
+
const updated = String(a.updatedAt).localeCompare(String(b.updatedAt));
|
|
692
|
+
if (updated !== 0)
|
|
693
|
+
return updated;
|
|
694
|
+
return a.id.localeCompare(b.id);
|
|
695
|
+
}
|
|
696
|
+
function numericVersion(version) {
|
|
697
|
+
const match = version?.match(/^v(\d+)$/i);
|
|
698
|
+
return match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
|
|
699
|
+
}
|
|
700
|
+
function extractIterationFromReceipt(receipt) {
|
|
701
|
+
if (!receipt.includes("iteration:"))
|
|
702
|
+
return undefined;
|
|
703
|
+
const version = receipt.match(/^\s*version:\s*(\S+)/m)?.[1];
|
|
704
|
+
const priorDraftId = receipt.match(/^\s*priorDraftId:\s*(\S+)/m)?.[1];
|
|
705
|
+
const verdict = receipt.match(/^\s*verdict:\s*(\S+)/m)?.[1];
|
|
706
|
+
const scoreBlock = receipt.match(/^\s*score:\s*\n((?:\s{4}[A-Za-z][A-Za-z0-9_-]*:\s*[-\d.]+\n?)+)/m)?.[1];
|
|
707
|
+
const score = scoreBlock
|
|
708
|
+
? Object.fromEntries(scoreBlock
|
|
709
|
+
.trim()
|
|
710
|
+
.split(/\n/)
|
|
711
|
+
.map((line) => line.trim().split(/:\s*/))
|
|
712
|
+
.filter(([key, value]) => key && value)
|
|
713
|
+
.map(([key, value]) => [key, Number(value)]))
|
|
714
|
+
: undefined;
|
|
715
|
+
return stripUndefined({
|
|
716
|
+
version,
|
|
717
|
+
priorDraftId: priorDraftId && priorDraftId !== "none" ? priorDraftId : undefined,
|
|
718
|
+
verdict,
|
|
719
|
+
score,
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
function extractMarkdownSection(markdown, heading) {
|
|
723
|
+
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
724
|
+
const match = markdown.match(new RegExp(`(?:^|\\n)## ${escaped}\\n\\n([\\s\\S]*?)(?=\\n## |$)`));
|
|
725
|
+
return match?.[1]?.trimEnd() ?? "";
|
|
726
|
+
}
|
|
727
|
+
function readJsonSection(markdown, heading, fallback) {
|
|
728
|
+
const section = extractMarkdownSection(markdown, heading);
|
|
729
|
+
const match = section.match(/```json\n([\s\S]*?)\n```/);
|
|
730
|
+
if (!match)
|
|
731
|
+
return fallback;
|
|
732
|
+
try {
|
|
733
|
+
return JSON.parse(match[1]);
|
|
734
|
+
}
|
|
735
|
+
catch {
|
|
736
|
+
return fallback;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function findPublishedArtifact(publishedPostId, year) {
|
|
740
|
+
const safeId = normalizeArtifactId(publishedPostId, "publishedPostId");
|
|
741
|
+
if (year) {
|
|
742
|
+
if (!/^\d{4}$/.test(year))
|
|
743
|
+
throw new Error("year must be YYYY.");
|
|
744
|
+
const relativePath = `${RELATIVE_DIRS.published}/${year}/${safeId}.md`;
|
|
745
|
+
const artifact = readArtifactIfExists(relativePath);
|
|
746
|
+
return artifact ? { artifact, relativePath } : null;
|
|
747
|
+
}
|
|
748
|
+
const root = resolveContentRoot();
|
|
749
|
+
ensureContentLayout(root);
|
|
750
|
+
const publishedRoot = safePath(root, RELATIVE_DIRS.published);
|
|
751
|
+
if (!fs.existsSync(publishedRoot))
|
|
752
|
+
return null;
|
|
753
|
+
for (const yearDir of fs.readdirSync(publishedRoot).sort()) {
|
|
754
|
+
if (!/^\d{4}$/.test(yearDir))
|
|
755
|
+
continue;
|
|
756
|
+
const relativePath = `${RELATIVE_DIRS.published}/${yearDir}/${safeId}.md`;
|
|
757
|
+
const artifact = readArtifactIfExists(relativePath);
|
|
758
|
+
if (artifact)
|
|
759
|
+
return { artifact, relativePath };
|
|
760
|
+
}
|
|
761
|
+
return null;
|
|
762
|
+
}
|
|
763
|
+
function markDraftAsPublished(draftId, publishedPostId, publishedAt) {
|
|
764
|
+
const relativePath = `${RELATIVE_DIRS.drafts}/${draftId}.md`;
|
|
765
|
+
const existing = readArtifactIfExists(relativePath);
|
|
766
|
+
if (!existing)
|
|
767
|
+
return;
|
|
768
|
+
const body = extractMarkdownSection(existing.markdown, "Draft Body");
|
|
769
|
+
const receipt = extractMarkdownSection(existing.markdown, "Validation Receipt");
|
|
770
|
+
const metadata = {
|
|
771
|
+
...existing.metadata,
|
|
772
|
+
status: "published",
|
|
773
|
+
publishedPostId,
|
|
774
|
+
publishedAt,
|
|
775
|
+
updatedAt: publishedAt,
|
|
776
|
+
};
|
|
777
|
+
const markdown = buildMarkdown(metadata, [
|
|
778
|
+
["Draft Body", body],
|
|
779
|
+
["Validation Receipt", receipt],
|
|
780
|
+
]);
|
|
781
|
+
writeArtifact(relativePath, markdown);
|
|
782
|
+
}
|
|
428
783
|
function readArtifactsFromDir(relativeDir) {
|
|
429
784
|
const root = resolveContentRoot();
|
|
430
785
|
ensureContentLayout(root);
|
|
@@ -2,7 +2,7 @@ import { copySenderConfig, getEngageMemory, migrateFlatConfigs, recordProvenSear
|
|
|
2
2
|
export const engageMemoryToolDefinitions = [
|
|
3
3
|
{
|
|
4
4
|
name: "get_engage_memory",
|
|
5
|
-
description: "Load
|
|
5
|
+
description: "Load unified Sellable memory from ~/.sellable/configs/: core identity/company/proof/story memory, transcript/content-memory clusters, references, style and post writing rules, proven search keywords, and tracked people. The get_engage_memory name is backward-compatible; all data lives in readable markdown files the user can edit directly. When senderId is provided, reads compatibility overrides from senders/{senderId}/ with flat-path fallback.",
|
|
6
6
|
inputSchema: {
|
|
7
7
|
type: "object",
|
|
8
8
|
properties: {
|