@telepat/ideon 0.1.25 → 0.1.28
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/ideon.js
CHANGED
|
@@ -12,7 +12,8 @@ import { access, mkdir, writeFile } from "fs/promises";
|
|
|
12
12
|
import os from "os";
|
|
13
13
|
import path from "path";
|
|
14
14
|
function resolveOutputPaths() {
|
|
15
|
-
const
|
|
15
|
+
const ideonHome = process.env.IDEON_HOME || os.homedir();
|
|
16
|
+
const base = path.join(ideonHome, ".ideon", "output");
|
|
16
17
|
return {
|
|
17
18
|
markdownOutputDir: base,
|
|
18
19
|
assetOutputDir: path.join(base, "assets")
|
|
@@ -1423,7 +1424,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
1423
1424
|
// package.json
|
|
1424
1425
|
var package_default = {
|
|
1425
1426
|
name: "@telepat/ideon",
|
|
1426
|
-
version: "0.1.
|
|
1427
|
+
version: "0.1.28",
|
|
1427
1428
|
description: "CLI for generating rich articles and images from ideas.",
|
|
1428
1429
|
type: "module",
|
|
1429
1430
|
repository: {
|
|
@@ -1623,7 +1624,7 @@ function assertNoLegacyXMode(contentTargets, sourceLabel) {
|
|
|
1623
1624
|
}
|
|
1624
1625
|
|
|
1625
1626
|
// src/pipeline/runner.ts
|
|
1626
|
-
import { mkdir as mkdir6, stat as stat2 } from "fs/promises";
|
|
1627
|
+
import { mkdir as mkdir6, readFile as readFile6, stat as stat2 } from "fs/promises";
|
|
1627
1628
|
import { randomUUID } from "crypto";
|
|
1628
1629
|
import path9 from "path";
|
|
1629
1630
|
|
|
@@ -2380,7 +2381,7 @@ function buildLongFormPlanJsonSchema(targetLengthWords) {
|
|
|
2380
2381
|
required: ["description", "anchorAfterSection"],
|
|
2381
2382
|
properties: {
|
|
2382
2383
|
description: { type: "string" },
|
|
2383
|
-
anchorAfterSection: { type: "number", minimum:
|
|
2384
|
+
anchorAfterSection: { type: "number", minimum: 1 }
|
|
2384
2385
|
}
|
|
2385
2386
|
}
|
|
2386
2387
|
}
|
|
@@ -2446,7 +2447,7 @@ function buildLongFormPlanMessages(idea, options) {
|
|
|
2446
2447
|
"- Each section description should name the mechanism, evidence type, or practical action that makes the section useful.",
|
|
2447
2448
|
"- Sections are primary-only structure and must not be treated as requirements for non-primary channels.",
|
|
2448
2449
|
`- Include a cover image description and ${imageCounts.min} to ${imageCounts.max} inline image descriptions.`,
|
|
2449
|
-
"- Each inline image must specify which section it follows (anchorAfterSection, starting at
|
|
2450
|
+
"- Each inline image must specify which section it follows (anchorAfterSection, starting at 1). Choose sections where visual reinforcement adds the most value.",
|
|
2450
2451
|
"- Image descriptions should capture the general concept and mood \u2014 the exact text-to-image prompt will be refined later using the actual section content.",
|
|
2451
2452
|
"- Image descriptions must be concrete and contextual, not generic stock-photo phrasing.",
|
|
2452
2453
|
"",
|
|
@@ -2468,7 +2469,7 @@ function buildLongFormPlanMessages(idea, options) {
|
|
|
2468
2469
|
"- outroBrief: string",
|
|
2469
2470
|
`- sections: array of ${sectionCounts.label} objects, each with title and description strings`,
|
|
2470
2471
|
"- coverImageDescription: string",
|
|
2471
|
-
`- inlineImages: array of ${imageCounts.min} to ${imageCounts.max} objects, each with a description string and an anchorAfterSection number (starting at
|
|
2472
|
+
`- inlineImages: array of ${imageCounts.min} to ${imageCounts.max} objects, each with a description string and an anchorAfterSection number (starting at 1).`,
|
|
2472
2473
|
"",
|
|
2473
2474
|
"Do not omit any required fields. Return strict JSON only."
|
|
2474
2475
|
].join("\n")
|
|
@@ -2530,7 +2531,7 @@ var articleSectionPlanSchema = z5.object({
|
|
|
2530
2531
|
});
|
|
2531
2532
|
var inlineImagePlanSchema = z5.object({
|
|
2532
2533
|
description: z5.string().min(1),
|
|
2533
|
-
anchorAfterSection: z5.number().int().min(
|
|
2534
|
+
anchorAfterSection: z5.number().int().min(1)
|
|
2534
2535
|
});
|
|
2535
2536
|
var primaryPlanSchema = z5.object({
|
|
2536
2537
|
contentType: z5.string().min(1).default("article"),
|
|
@@ -2619,7 +2620,7 @@ async function planPrimaryContent({
|
|
|
2619
2620
|
keywords: longPlan.keywords.slice(0, 8),
|
|
2620
2621
|
inlineImages: longPlan.inlineImages.slice(0, 3).map((img) => ({
|
|
2621
2622
|
...img,
|
|
2622
|
-
anchorAfterSection: Math.max(
|
|
2623
|
+
anchorAfterSection: Math.max(1, Math.min(sectionCount, img.anchorAfterSection))
|
|
2623
2624
|
}))
|
|
2624
2625
|
};
|
|
2625
2626
|
}
|
|
@@ -3276,7 +3277,7 @@ function buildImageSlots(plan, sections, options) {
|
|
|
3276
3277
|
kind: "inline",
|
|
3277
3278
|
prompt: "",
|
|
3278
3279
|
description: img.description,
|
|
3279
|
-
anchorAfterSection: Math.max(
|
|
3280
|
+
anchorAfterSection: Math.max(1, Math.min(sectionCount, img.anchorAfterSection))
|
|
3280
3281
|
});
|
|
3281
3282
|
}
|
|
3282
3283
|
return slots;
|
|
@@ -5137,15 +5138,47 @@ async function runPipelineShell(input, options = {}) {
|
|
|
5137
5138
|
markStageStarted(stageTracking, "links");
|
|
5138
5139
|
options.onUpdate?.(cloneStages(stages));
|
|
5139
5140
|
if (!shouldEnrichLinks) {
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5141
|
+
const customLinkActions = pipelineCustomLinkRaws.length > 0 || pipelineUnlinks.length > 0;
|
|
5142
|
+
if (customLinkActions && eligibleOutputsForLinks.length > 0) {
|
|
5143
|
+
for (const output of eligibleOutputsForLinks) {
|
|
5144
|
+
const existingLinks = await readExistingLinks(resolveLinksPath(output.markdownPath));
|
|
5145
|
+
const mergedCustomLinks = resolvePipelineCustomLinks(
|
|
5146
|
+
existingLinks?.customLinks ?? [],
|
|
5147
|
+
pipelineCustomLinkRaws,
|
|
5148
|
+
pipelineUnlinks
|
|
5149
|
+
);
|
|
5150
|
+
const generatedLinks = existingLinks?.links ?? [];
|
|
5151
|
+
await writeLinksFile(output.markdownPath, {
|
|
5152
|
+
version: 2,
|
|
5153
|
+
customLinks: mergedCustomLinks,
|
|
5154
|
+
links: generatedLinks
|
|
5155
|
+
});
|
|
5156
|
+
}
|
|
5157
|
+
markStageCompleted(stageTracking, "links");
|
|
5158
|
+
stages[6] = {
|
|
5159
|
+
...stages[6],
|
|
5160
|
+
status: "succeeded",
|
|
5161
|
+
detail: "Updated custom links without generating new links.",
|
|
5162
|
+
summary: `${eligibleOutputsForLinks.length} files updated`,
|
|
5163
|
+
items: (stages[6].items ?? []).map((stageItem) => ({
|
|
5164
|
+
...stageItem,
|
|
5165
|
+
status: "succeeded",
|
|
5166
|
+
detail: "Saved custom links sidecar."
|
|
5167
|
+
})),
|
|
5168
|
+
stageAnalytics: snapshotStageAnalytics(stageTracking, "links")
|
|
5169
|
+
};
|
|
5170
|
+
options.onUpdate?.(cloneStages(stages));
|
|
5171
|
+
} else {
|
|
5172
|
+
markStageCompleted(stageTracking, "links");
|
|
5173
|
+
stages[6] = {
|
|
5174
|
+
...stages[6],
|
|
5175
|
+
status: "succeeded",
|
|
5176
|
+
detail: "Skipped link enrichment (enable with --enrich-links).",
|
|
5177
|
+
summary: "Link enrichment disabled for this run",
|
|
5178
|
+
stageAnalytics: snapshotStageAnalytics(stageTracking, "links")
|
|
5179
|
+
};
|
|
5180
|
+
options.onUpdate?.(cloneStages(stages));
|
|
5181
|
+
}
|
|
5149
5182
|
} else if (eligibleOutputsForLinks.length === 0) {
|
|
5150
5183
|
markStageCompleted(stageTracking, "links");
|
|
5151
5184
|
stages[6] = {
|
|
@@ -5803,9 +5836,61 @@ function parsePipelineCustomLinks(rawLinks, unlinks) {
|
|
|
5803
5836
|
}
|
|
5804
5837
|
return Array.from(result.values());
|
|
5805
5838
|
}
|
|
5839
|
+
function resolvePipelineCustomLinks(existing, rawLinks, unlinks) {
|
|
5840
|
+
const result = new Map(
|
|
5841
|
+
existing.map((entry) => [entry.expression.trim().toLowerCase(), entry])
|
|
5842
|
+
);
|
|
5843
|
+
for (const raw of rawLinks) {
|
|
5844
|
+
const separatorIndex = raw.indexOf("->");
|
|
5845
|
+
if (separatorIndex < 0) {
|
|
5846
|
+
continue;
|
|
5847
|
+
}
|
|
5848
|
+
const expression = raw.slice(0, separatorIndex).trim();
|
|
5849
|
+
const url = raw.slice(separatorIndex + 2).trim();
|
|
5850
|
+
if (expression && url) {
|
|
5851
|
+
result.set(expression.toLowerCase(), { expression, url, title: null });
|
|
5852
|
+
}
|
|
5853
|
+
}
|
|
5854
|
+
for (const expr of unlinks) {
|
|
5855
|
+
result.delete(expr.trim().toLowerCase());
|
|
5856
|
+
}
|
|
5857
|
+
return Array.from(result.values());
|
|
5858
|
+
}
|
|
5859
|
+
async function readExistingLinks(linksPath) {
|
|
5860
|
+
try {
|
|
5861
|
+
const raw = await readFile6(linksPath, "utf8");
|
|
5862
|
+
const parsed = JSON.parse(raw);
|
|
5863
|
+
const links = Array.isArray(parsed.links) ? parsed.links.filter((entry) => isValidLinkEntry(entry)).map((entry) => ({
|
|
5864
|
+
expression: entry.expression.trim(),
|
|
5865
|
+
url: entry.url.trim(),
|
|
5866
|
+
title: typeof entry.title === "string" ? entry.title : null
|
|
5867
|
+
})) : null;
|
|
5868
|
+
if (!links) {
|
|
5869
|
+
throw new Error(`Invalid links sidecar format at ${linksPath}. Expected { version, links[] }.`);
|
|
5870
|
+
}
|
|
5871
|
+
const customLinks = Array.isArray(parsed.customLinks) ? parsed.customLinks.filter((entry) => isValidLinkEntry(entry)).map((entry) => ({
|
|
5872
|
+
expression: entry.expression.trim(),
|
|
5873
|
+
url: entry.url.trim(),
|
|
5874
|
+
title: typeof entry.title === "string" ? entry.title : null
|
|
5875
|
+
})) : [];
|
|
5876
|
+
return {
|
|
5877
|
+
version: typeof parsed.version === "number" ? parsed.version : 2,
|
|
5878
|
+
customLinks,
|
|
5879
|
+
links
|
|
5880
|
+
};
|
|
5881
|
+
} catch (error) {
|
|
5882
|
+
if (error.code === "ENOENT") {
|
|
5883
|
+
return null;
|
|
5884
|
+
}
|
|
5885
|
+
throw error;
|
|
5886
|
+
}
|
|
5887
|
+
}
|
|
5888
|
+
function isValidLinkEntry(entry) {
|
|
5889
|
+
return typeof entry === "object" && entry !== null && typeof entry.expression === "string" && typeof entry.url === "string";
|
|
5890
|
+
}
|
|
5806
5891
|
|
|
5807
5892
|
// src/cli/commands/links.ts
|
|
5808
|
-
import { readFile as
|
|
5893
|
+
import { readFile as readFile7, stat as stat3 } from "fs/promises";
|
|
5809
5894
|
import path10 from "path";
|
|
5810
5895
|
async function runLinksCommand(options, dependencies = {}) {
|
|
5811
5896
|
const slug = normalizeSlug2(options.slug);
|
|
@@ -5828,7 +5913,7 @@ async function runLinksCommand(options, dependencies = {}) {
|
|
|
5828
5913
|
}
|
|
5829
5914
|
const openRouter = new OpenRouterClient(openRouterApiKey);
|
|
5830
5915
|
const linksPath = resolveLinksPath(markdownPath);
|
|
5831
|
-
const existing = await
|
|
5916
|
+
const existing = await readExistingLinks2(linksPath);
|
|
5832
5917
|
const updatedCustomLinks = resolveCustomLinks(existing?.customLinks ?? [], options.links ?? [], options.unlinks ?? []);
|
|
5833
5918
|
const effectiveMaxLinks = options.maxLinks;
|
|
5834
5919
|
const linksResult = await enrichLinks({
|
|
@@ -5923,7 +6008,7 @@ async function newestPath(paths) {
|
|
|
5923
6008
|
return latestPath;
|
|
5924
6009
|
}
|
|
5925
6010
|
async function readFrontmatter(markdownPath) {
|
|
5926
|
-
const markdown = await
|
|
6011
|
+
const markdown = await readFile7(markdownPath, "utf8");
|
|
5927
6012
|
return parseFrontmatter(markdown);
|
|
5928
6013
|
}
|
|
5929
6014
|
function parseFrontmatter(markdown) {
|
|
@@ -5967,11 +6052,11 @@ async function isReadableFile(filePath) {
|
|
|
5967
6052
|
return false;
|
|
5968
6053
|
}
|
|
5969
6054
|
}
|
|
5970
|
-
async function
|
|
6055
|
+
async function readExistingLinks2(linksPath) {
|
|
5971
6056
|
try {
|
|
5972
|
-
const raw = await
|
|
6057
|
+
const raw = await readFile7(linksPath, "utf8");
|
|
5973
6058
|
const parsed = JSON.parse(raw);
|
|
5974
|
-
const links = Array.isArray(parsed.links) ? parsed.links.filter((entry) =>
|
|
6059
|
+
const links = Array.isArray(parsed.links) ? parsed.links.filter((entry) => isValidLinkEntry2(entry)).map((entry) => ({
|
|
5975
6060
|
expression: entry.expression.trim(),
|
|
5976
6061
|
url: entry.url.trim(),
|
|
5977
6062
|
title: typeof entry.title === "string" ? entry.title : null
|
|
@@ -5979,7 +6064,7 @@ async function readExistingLinks(linksPath) {
|
|
|
5979
6064
|
if (!links) {
|
|
5980
6065
|
throw new ReportedError(`Invalid links sidecar format at ${linksPath}. Expected { version, links[] }.`);
|
|
5981
6066
|
}
|
|
5982
|
-
const customLinks = Array.isArray(parsed.customLinks) ? parsed.customLinks.filter((entry) =>
|
|
6067
|
+
const customLinks = Array.isArray(parsed.customLinks) ? parsed.customLinks.filter((entry) => isValidLinkEntry2(entry)).map((entry) => ({
|
|
5983
6068
|
expression: entry.expression.trim(),
|
|
5984
6069
|
url: entry.url.trim(),
|
|
5985
6070
|
title: typeof entry.title === "string" ? entry.title : null
|
|
@@ -6013,7 +6098,7 @@ function mergeLinks(existingLinks, generatedLinks) {
|
|
|
6013
6098
|
}
|
|
6014
6099
|
return merged;
|
|
6015
6100
|
}
|
|
6016
|
-
function
|
|
6101
|
+
function isValidLinkEntry2(value2) {
|
|
6017
6102
|
if (typeof value2 !== "object" || value2 === null) {
|
|
6018
6103
|
return false;
|
|
6019
6104
|
}
|
|
@@ -6067,10 +6152,44 @@ function resolveCustomLinks(existing, addRaw, removeExpressions) {
|
|
|
6067
6152
|
}
|
|
6068
6153
|
|
|
6069
6154
|
// src/cli/commands/export.ts
|
|
6070
|
-
import { copyFile as copyFile2, mkdir as mkdir7, readFile as
|
|
6155
|
+
import { copyFile as copyFile2, mkdir as mkdir7, readFile as readFile10, stat as stat5, writeFile as writeFile6 } from "fs/promises";
|
|
6071
6156
|
import path12 from "path";
|
|
6072
6157
|
|
|
6073
6158
|
// src/output/enrichMarkdownWithLinks.ts
|
|
6159
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
6160
|
+
async function loadLinksFromSidecar(markdownPath) {
|
|
6161
|
+
const linksPath = resolveLinksPath(markdownPath);
|
|
6162
|
+
let raw;
|
|
6163
|
+
try {
|
|
6164
|
+
raw = await readFile8(linksPath, "utf8");
|
|
6165
|
+
} catch {
|
|
6166
|
+
return [];
|
|
6167
|
+
}
|
|
6168
|
+
let parsed;
|
|
6169
|
+
try {
|
|
6170
|
+
parsed = JSON.parse(raw);
|
|
6171
|
+
} catch {
|
|
6172
|
+
return [];
|
|
6173
|
+
}
|
|
6174
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
6175
|
+
return [];
|
|
6176
|
+
}
|
|
6177
|
+
const record = parsed;
|
|
6178
|
+
const links = Array.isArray(record.links) ? record.links : [];
|
|
6179
|
+
const customLinks = Array.isArray(record.customLinks) ? record.customLinks : [];
|
|
6180
|
+
const combined = [...customLinks, ...links];
|
|
6181
|
+
return combined.filter((entry) => {
|
|
6182
|
+
if (typeof entry !== "object" || entry === null) {
|
|
6183
|
+
return false;
|
|
6184
|
+
}
|
|
6185
|
+
const e = entry;
|
|
6186
|
+
return typeof e.expression === "string" && typeof e.url === "string" && (e.title === null || typeof e.title === "string");
|
|
6187
|
+
}).map((entry) => ({
|
|
6188
|
+
expression: entry.expression.trim(),
|
|
6189
|
+
url: entry.url.trim(),
|
|
6190
|
+
title: entry.title
|
|
6191
|
+
})).filter((entry) => entry.expression.length > 0 && entry.url.length > 0);
|
|
6192
|
+
}
|
|
6074
6193
|
function enrichMarkdownWithLinks(markdown, links) {
|
|
6075
6194
|
if (links.length === 0) {
|
|
6076
6195
|
return markdown;
|
|
@@ -6117,14 +6236,29 @@ function isInProtectedSpan(content, start, end) {
|
|
|
6117
6236
|
return true;
|
|
6118
6237
|
}
|
|
6119
6238
|
}
|
|
6239
|
+
if (/^#{1,6}\s/.test(line)) {
|
|
6240
|
+
return true;
|
|
6241
|
+
}
|
|
6120
6242
|
return false;
|
|
6121
6243
|
}
|
|
6122
6244
|
function escapeRegExp(value2) {
|
|
6123
|
-
return value2.replace(/[
|
|
6245
|
+
return value2.replace(/[.*+?^{}()|[\]\\]/g, "\\$&");
|
|
6246
|
+
}
|
|
6247
|
+
function enrichWithFrontmatterGuard(markdown, links) {
|
|
6248
|
+
if (links.length === 0) {
|
|
6249
|
+
return markdown;
|
|
6250
|
+
}
|
|
6251
|
+
const frontmatterMatch = markdown.match(/^---\s*\n[\s\S]*?\n---\s*\n?/);
|
|
6252
|
+
if (!frontmatterMatch) {
|
|
6253
|
+
return enrichMarkdownWithLinks(markdown, links);
|
|
6254
|
+
}
|
|
6255
|
+
const frontmatter = frontmatterMatch[0];
|
|
6256
|
+
const body = markdown.slice(frontmatter.length);
|
|
6257
|
+
return `${frontmatter}${enrichMarkdownWithLinks(body, links)}`;
|
|
6124
6258
|
}
|
|
6125
6259
|
|
|
6126
6260
|
// src/server/previewHelpers.ts
|
|
6127
|
-
import { readdir, stat as stat4, readFile as
|
|
6261
|
+
import { readdir, stat as stat4, readFile as readFile9 } from "fs/promises";
|
|
6128
6262
|
import path11 from "path";
|
|
6129
6263
|
var DEFAULT_PORT = 4173;
|
|
6130
6264
|
var CONTENT_TYPE_ORDER = ["article", "blog-post", "x-thread", "x-post", "linkedin-post", "reddit-post", "newsletter"];
|
|
@@ -6226,7 +6360,7 @@ function extractCoverImageUrl(markdown) {
|
|
|
6226
6360
|
return match?.[1] ?? null;
|
|
6227
6361
|
}
|
|
6228
6362
|
async function extractArticleMetadata(markdownPath) {
|
|
6229
|
-
const markdown = await
|
|
6363
|
+
const markdown = await readFile9(markdownPath, "utf8");
|
|
6230
6364
|
const fileStat = await stat4(markdownPath);
|
|
6231
6365
|
const slug = extractFrontmatterSlug(markdown) ?? path11.basename(markdownPath, ".md");
|
|
6232
6366
|
const title = extractHeadingTitle(stripFrontmatter2(markdown)) ?? slug;
|
|
@@ -6243,7 +6377,7 @@ async function extractArticleMetadata(markdownPath) {
|
|
|
6243
6377
|
}
|
|
6244
6378
|
async function listAllGenerations(markdownOutputDir) {
|
|
6245
6379
|
const markdownFiles = await findMarkdownFiles(markdownOutputDir);
|
|
6246
|
-
const
|
|
6380
|
+
const outputMap = /* @__PURE__ */ new Map();
|
|
6247
6381
|
for (const filePath of markdownFiles) {
|
|
6248
6382
|
try {
|
|
6249
6383
|
const metadata = await extractArticleMetadata(filePath);
|
|
@@ -6261,15 +6395,23 @@ async function listAllGenerations(markdownOutputDir) {
|
|
|
6261
6395
|
contentTypeLabel: toContentTypeLabel(identity.contentType),
|
|
6262
6396
|
index: identity.index
|
|
6263
6397
|
};
|
|
6264
|
-
const
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
grouped.set(identity.generationId, [output]);
|
|
6398
|
+
const outputKey = `${output.generationId}:${output.contentType}:${output.index}`;
|
|
6399
|
+
const existing = outputMap.get(outputKey);
|
|
6400
|
+
if (!existing || output.mtime > existing.mtime) {
|
|
6401
|
+
outputMap.set(outputKey, output);
|
|
6269
6402
|
}
|
|
6270
6403
|
} catch {
|
|
6271
6404
|
}
|
|
6272
6405
|
}
|
|
6406
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
6407
|
+
for (const output of outputMap.values()) {
|
|
6408
|
+
const existing = grouped.get(output.generationId);
|
|
6409
|
+
if (existing) {
|
|
6410
|
+
existing.push(output);
|
|
6411
|
+
} else {
|
|
6412
|
+
grouped.set(output.generationId, [output]);
|
|
6413
|
+
}
|
|
6414
|
+
}
|
|
6273
6415
|
const generations = [];
|
|
6274
6416
|
for (const [id, outputs] of grouped.entries()) {
|
|
6275
6417
|
outputs.sort((a, b) => compareContentTypes(a.contentType, b.contentType) || a.index - b.index || b.mtime - a.mtime);
|
|
@@ -6375,7 +6517,7 @@ async function resolvePrimaryContentType(outputs) {
|
|
|
6375
6517
|
}
|
|
6376
6518
|
const jobPath = path11.join(generationDir, "job.json");
|
|
6377
6519
|
try {
|
|
6378
|
-
const raw = await
|
|
6520
|
+
const raw = await readFile9(jobPath, "utf8");
|
|
6379
6521
|
const parsed = JSON.parse(raw);
|
|
6380
6522
|
const targets = Array.isArray(parsed.contentTargets) ? parsed.contentTargets : Array.isArray(parsed.settings?.contentTargets) ? parsed.settings.contentTargets : [];
|
|
6381
6523
|
const primary = targets.find((target) => target?.role === "primary" && typeof target.contentType === "string");
|
|
@@ -6416,7 +6558,7 @@ async function runOutputCommand(options, dependencies = {}) {
|
|
|
6416
6558
|
);
|
|
6417
6559
|
}
|
|
6418
6560
|
const sourceMarkdownPath = articleOutput.sourcePath;
|
|
6419
|
-
const sourceMarkdown = await
|
|
6561
|
+
const sourceMarkdown = await readFile10(sourceMarkdownPath, "utf8");
|
|
6420
6562
|
const slug = extractFrontmatterSlug2(sourceMarkdown) ?? path12.basename(sourceMarkdownPath, ".md");
|
|
6421
6563
|
const exportFilename = `${slug}.md`;
|
|
6422
6564
|
const destinationDir = await resolveDestinationDir(options.destinationPath, cwd2);
|
|
@@ -6427,7 +6569,7 @@ async function runOutputCommand(options, dependencies = {}) {
|
|
|
6427
6569
|
);
|
|
6428
6570
|
}
|
|
6429
6571
|
await mkdir7(destinationDir, { recursive: true });
|
|
6430
|
-
const links = await
|
|
6572
|
+
const links = await loadLinksFromSidecar(sourceMarkdownPath);
|
|
6431
6573
|
const enrichedMarkdown = enrichWithFrontmatterGuard(sourceMarkdown, links);
|
|
6432
6574
|
const sourceDir = path12.dirname(sourceMarkdownPath);
|
|
6433
6575
|
const allFiles = await listFilesRecursively(sourceDir, () => true);
|
|
@@ -6479,51 +6621,6 @@ async function fileExists2(filePath) {
|
|
|
6479
6621
|
return false;
|
|
6480
6622
|
}
|
|
6481
6623
|
}
|
|
6482
|
-
async function loadLinks(markdownPath) {
|
|
6483
|
-
const linksPath = resolveLinksPath(markdownPath);
|
|
6484
|
-
let raw;
|
|
6485
|
-
try {
|
|
6486
|
-
raw = await readFile8(linksPath, "utf8");
|
|
6487
|
-
} catch {
|
|
6488
|
-
return [];
|
|
6489
|
-
}
|
|
6490
|
-
let parsed;
|
|
6491
|
-
try {
|
|
6492
|
-
parsed = JSON.parse(raw);
|
|
6493
|
-
} catch {
|
|
6494
|
-
return [];
|
|
6495
|
-
}
|
|
6496
|
-
if (typeof parsed !== "object" || parsed === null) {
|
|
6497
|
-
return [];
|
|
6498
|
-
}
|
|
6499
|
-
const record = parsed;
|
|
6500
|
-
const links = Array.isArray(record.links) ? record.links : [];
|
|
6501
|
-
const customLinks = Array.isArray(record.customLinks) ? record.customLinks : [];
|
|
6502
|
-
const combined = [...customLinks, ...links];
|
|
6503
|
-
return combined.filter((entry) => {
|
|
6504
|
-
if (typeof entry !== "object" || entry === null) {
|
|
6505
|
-
return false;
|
|
6506
|
-
}
|
|
6507
|
-
const e = entry;
|
|
6508
|
-
return typeof e.expression === "string" && typeof e.url === "string" && (e.title === null || typeof e.title === "string");
|
|
6509
|
-
}).map((entry) => ({
|
|
6510
|
-
expression: entry.expression.trim(),
|
|
6511
|
-
url: entry.url.trim(),
|
|
6512
|
-
title: entry.title
|
|
6513
|
-
})).filter((entry) => entry.expression.length > 0 && entry.url.length > 0);
|
|
6514
|
-
}
|
|
6515
|
-
function enrichWithFrontmatterGuard(markdown, links) {
|
|
6516
|
-
if (links.length === 0) {
|
|
6517
|
-
return markdown;
|
|
6518
|
-
}
|
|
6519
|
-
const frontmatterMatch = markdown.match(/^---\s*\n[\s\S]*?\n---\s*\n?/);
|
|
6520
|
-
if (!frontmatterMatch) {
|
|
6521
|
-
return enrichMarkdownWithLinks(markdown, links);
|
|
6522
|
-
}
|
|
6523
|
-
const frontmatter = frontmatterMatch[0];
|
|
6524
|
-
const body = markdown.slice(frontmatter.length);
|
|
6525
|
-
return `${frontmatter}${enrichMarkdownWithLinks(body, links)}`;
|
|
6526
|
-
}
|
|
6527
6624
|
function extractFrontmatterSlug2(markdown) {
|
|
6528
6625
|
const frontmatterMatch = markdown.match(/^---\s*\n([\s\S]*?)\n---\s*\n?/);
|
|
6529
6626
|
const block = frontmatterMatch?.[1];
|
|
@@ -7405,7 +7502,7 @@ import { spawn } from "child_process";
|
|
|
7405
7502
|
// src/server/previewServer.ts
|
|
7406
7503
|
import { execFile } from "child_process";
|
|
7407
7504
|
import { promisify } from "util";
|
|
7408
|
-
import { readFile as
|
|
7505
|
+
import { readFile as readFile11, stat as stat6 } from "fs/promises";
|
|
7409
7506
|
import { watch as fsWatch } from "fs";
|
|
7410
7507
|
import path13 from "path";
|
|
7411
7508
|
import { fileURLToPath } from "url";
|
|
@@ -7422,7 +7519,7 @@ async function startPreviewServer(options) {
|
|
|
7422
7519
|
const app = express();
|
|
7423
7520
|
const previewClientDir = await resolvePreviewClientBuildDir();
|
|
7424
7521
|
app.disable("x-powered-by");
|
|
7425
|
-
app.use("/assets", express.static(options.assetDir));
|
|
7522
|
+
app.use("/assets", express.static(options.assetDir, { dotfiles: "allow" }));
|
|
7426
7523
|
if (previewClientDir) {
|
|
7427
7524
|
app.use(express.static(previewClientDir, { index: false }));
|
|
7428
7525
|
}
|
|
@@ -7456,7 +7553,7 @@ async function startPreviewServer(options) {
|
|
|
7456
7553
|
const assetPathParam = req.params.assetPath;
|
|
7457
7554
|
const rawAssetPath = Array.isArray(assetPathParam) ? assetPathParam.join("/") : assetPathParam ?? "";
|
|
7458
7555
|
const resolvedAssetPath = await resolveGenerationAssetPath(generationId, rawAssetPath, options.markdownOutputDir);
|
|
7459
|
-
res.sendFile(resolvedAssetPath);
|
|
7556
|
+
res.sendFile(resolvedAssetPath, { dotfiles: "allow" });
|
|
7460
7557
|
} catch (error) {
|
|
7461
7558
|
const message = error instanceof Error ? error.message : "Unknown error loading generation asset";
|
|
7462
7559
|
const status = error instanceof MissingArticleError ? 404 : 400;
|
|
@@ -7505,7 +7602,7 @@ async function startPreviewServer(options) {
|
|
|
7505
7602
|
if (options.watch) {
|
|
7506
7603
|
let html2;
|
|
7507
7604
|
try {
|
|
7508
|
-
html2 = await
|
|
7605
|
+
html2 = await readFile11(path13.join(previewClientDir, "index.html"), "utf8");
|
|
7509
7606
|
} catch {
|
|
7510
7607
|
res.status(200).type("html").send(
|
|
7511
7608
|
`<!doctype html><html><head><meta charset="utf-8"><title>Rebuilding\u2026</title><style>body{margin:0;display:flex;align-items:center;justify-content:center;height:100vh;font-family:sans-serif;background:#101820;color:#e0eaf0}p{font-size:15px;opacity:.7}</style></head><body><p>Rebuilding\u2026</p><script>const s=new EventSource('/api/__reload');s.onmessage=function(){location.reload()};</script></body></html>`
|
|
@@ -7569,7 +7666,7 @@ async function getArticleContent(generationId, markdownOutputDir) {
|
|
|
7569
7666
|
generation.outputs.map(async (output) => {
|
|
7570
7667
|
let markdown = "";
|
|
7571
7668
|
try {
|
|
7572
|
-
markdown = await
|
|
7669
|
+
markdown = await readFile11(output.sourcePath, "utf8");
|
|
7573
7670
|
} catch (error) {
|
|
7574
7671
|
if (isMissingFileError(error)) {
|
|
7575
7672
|
throw new MissingArticleError(`Generation "${generationId}" no longer exists.`);
|
|
@@ -7625,41 +7722,15 @@ function isMissingFileError(error) {
|
|
|
7625
7722
|
}
|
|
7626
7723
|
async function renderArticleHtml(markdown, generationId, sourcePath) {
|
|
7627
7724
|
let content = stripFrontmatter2(markdown);
|
|
7628
|
-
const links = await
|
|
7725
|
+
const links = await loadLinksFromSidecar(sourcePath);
|
|
7629
7726
|
content = enrichMarkdownWithLinks(content, links);
|
|
7630
7727
|
const html = await marked.parse(content);
|
|
7631
7728
|
return rewriteRelativeAssetUrls(html, generationId);
|
|
7632
7729
|
}
|
|
7633
|
-
async function loadSavedLinks(markdownPath) {
|
|
7634
|
-
const linksPath = resolveLinksPath(markdownPath);
|
|
7635
|
-
try {
|
|
7636
|
-
const raw = await readFile9(linksPath, "utf8");
|
|
7637
|
-
const parsed = JSON.parse(raw);
|
|
7638
|
-
if (!Array.isArray(parsed.links)) {
|
|
7639
|
-
return [];
|
|
7640
|
-
}
|
|
7641
|
-
return parsed.links.filter((entry) => {
|
|
7642
|
-
if (typeof entry !== "object" || entry === null) {
|
|
7643
|
-
return false;
|
|
7644
|
-
}
|
|
7645
|
-
const record = entry;
|
|
7646
|
-
return typeof record.expression === "string" && typeof record.url === "string" && (record.title === null || typeof record.title === "string");
|
|
7647
|
-
}).map((entry) => ({
|
|
7648
|
-
expression: entry.expression.trim(),
|
|
7649
|
-
url: entry.url.trim(),
|
|
7650
|
-
title: entry.title
|
|
7651
|
-
})).filter((entry) => entry.expression.length > 0 && entry.url.length > 0);
|
|
7652
|
-
} catch (error) {
|
|
7653
|
-
if (isMissingFileError(error)) {
|
|
7654
|
-
return [];
|
|
7655
|
-
}
|
|
7656
|
-
return [];
|
|
7657
|
-
}
|
|
7658
|
-
}
|
|
7659
7730
|
async function loadSavedInteractions(generationDir) {
|
|
7660
7731
|
const interactionsPath = path13.join(generationDir, "model.interactions.json");
|
|
7661
7732
|
try {
|
|
7662
|
-
const raw = await
|
|
7733
|
+
const raw = await readFile11(interactionsPath, "utf8");
|
|
7663
7734
|
const parsed = JSON.parse(raw);
|
|
7664
7735
|
const llmCalls = Array.isArray(parsed.llmCalls) ? parsed.llmCalls.filter(isPreviewLlmInteraction) : [];
|
|
7665
7736
|
const t2iCalls = Array.isArray(parsed.t2iCalls) ? parsed.t2iCalls.filter(isPreviewT2IInteraction) : [];
|
|
@@ -7677,7 +7748,7 @@ async function loadSavedInteractions(generationDir) {
|
|
|
7677
7748
|
async function loadSavedAnalyticsSummary(generationDir) {
|
|
7678
7749
|
const analyticsPath = path13.join(generationDir, "generation.analytics.json");
|
|
7679
7750
|
try {
|
|
7680
|
-
const raw = await
|
|
7751
|
+
const raw = await readFile11(analyticsPath, "utf8");
|
|
7681
7752
|
const parsed = JSON.parse(raw);
|
|
7682
7753
|
const summary = parsed.summary;
|
|
7683
7754
|
if (!summary || typeof summary !== "object") {
|
|
@@ -7701,7 +7772,7 @@ async function loadSavedAnalyticsSummary(generationDir) {
|
|
|
7701
7772
|
async function loadSavedMetaJson(generationDir) {
|
|
7702
7773
|
const metaJsonPath = path13.join(generationDir, "meta.json");
|
|
7703
7774
|
try {
|
|
7704
|
-
const raw = await
|
|
7775
|
+
const raw = await readFile11(metaJsonPath, "utf8");
|
|
7705
7776
|
return JSON.parse(raw);
|
|
7706
7777
|
} catch {
|
|
7707
7778
|
return null;
|