@telepat/ideon 0.1.24 → 0.1.27
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")
|
|
@@ -484,9 +485,9 @@ function resolveDefaultMaxLinks(targetLengthWords) {
|
|
|
484
485
|
}
|
|
485
486
|
function resolveDefaultInlineImageCount(targetLengthWords) {
|
|
486
487
|
const alias = resolveTargetLengthAlias(targetLengthWords);
|
|
487
|
-
if (alias === "small") return { min:
|
|
488
|
-
if (alias === "medium") return { min:
|
|
489
|
-
return { min:
|
|
488
|
+
if (alias === "small") return { min: 0, max: 1 };
|
|
489
|
+
if (alias === "medium") return { min: 1, max: 2 };
|
|
490
|
+
return { min: 2, max: 4 };
|
|
490
491
|
}
|
|
491
492
|
var contentTargetRoleValues = ["primary", "secondary"];
|
|
492
493
|
var contentTargetSchema = z2.object({
|
|
@@ -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.27",
|
|
1427
1428
|
description: "CLI for generating rich articles and images from ideas.",
|
|
1428
1429
|
type: "module",
|
|
1429
1430
|
repository: {
|
|
@@ -1623,9 +1624,9 @@ 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
|
-
import
|
|
1629
|
+
import path9 from "path";
|
|
1629
1630
|
|
|
1630
1631
|
// src/generation/enrichLinks.ts
|
|
1631
1632
|
import { readFile as readFile4 } from "fs/promises";
|
|
@@ -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, 1
|
|
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 (
|
|
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")
|
|
@@ -2543,7 +2544,7 @@ var primaryPlanSchema = z5.object({
|
|
|
2543
2544
|
introBrief: z5.string().min(1).optional(),
|
|
2544
2545
|
outroBrief: z5.string().min(1).optional(),
|
|
2545
2546
|
sections: z5.array(articleSectionPlanSchema).min(2).max(10).optional(),
|
|
2546
|
-
inlineImages: z5.array(inlineImagePlanSchema).min(
|
|
2547
|
+
inlineImages: z5.array(inlineImagePlanSchema).min(0).max(4).optional(),
|
|
2547
2548
|
angle: z5.string().min(1).optional()
|
|
2548
2549
|
});
|
|
2549
2550
|
var longFormPlanSchema = z5.object({
|
|
@@ -2557,7 +2558,7 @@ var longFormPlanSchema = z5.object({
|
|
|
2557
2558
|
outroBrief: z5.string().min(1),
|
|
2558
2559
|
sections: z5.array(articleSectionPlanSchema).min(2).max(10),
|
|
2559
2560
|
coverImageDescription: z5.string().min(1),
|
|
2560
|
-
inlineImages: z5.array(inlineImagePlanSchema).min(
|
|
2561
|
+
inlineImages: z5.array(inlineImagePlanSchema).min(0).max(4)
|
|
2561
2562
|
});
|
|
2562
2563
|
var shortFormPlanSchema = z5.object({
|
|
2563
2564
|
contentType: z5.string().min(1),
|
|
@@ -4003,10 +4004,69 @@ ${body.join("\n").trim()}
|
|
|
4003
4004
|
`;
|
|
4004
4005
|
}
|
|
4005
4006
|
|
|
4007
|
+
// src/output/meta.ts
|
|
4008
|
+
import path7 from "path";
|
|
4009
|
+
function buildMetaJson(input) {
|
|
4010
|
+
const plan = input.plan;
|
|
4011
|
+
const contentPlan = input.contentPlan;
|
|
4012
|
+
const generationDir = input.generationDir;
|
|
4013
|
+
const title = plan?.title ?? contentPlan?.title ?? input.idea;
|
|
4014
|
+
const slug = plan?.slug ?? "";
|
|
4015
|
+
const description = plan?.description ?? contentPlan?.description ?? "";
|
|
4016
|
+
const subtitle = (plan && "subtitle" in plan ? plan.subtitle : null) ?? null;
|
|
4017
|
+
const keywords = (plan && "keywords" in plan ? plan.keywords : null) ?? [];
|
|
4018
|
+
const contentType = plan?.contentType ?? contentPlan?.primaryContentType ?? "article";
|
|
4019
|
+
const angle = plan?.angle ?? null;
|
|
4020
|
+
const coverImage = input.renderedImages.find((image) => image.kind === "cover") ?? null;
|
|
4021
|
+
const cover = coverImage ? {
|
|
4022
|
+
path: coverImage.outputPath,
|
|
4023
|
+
relativePath: coverImage.relativePath,
|
|
4024
|
+
description: coverImage.description
|
|
4025
|
+
} : null;
|
|
4026
|
+
const sections = plan?.sections?.map((section) => ({
|
|
4027
|
+
title: section.title,
|
|
4028
|
+
description: section.description
|
|
4029
|
+
})) ?? [];
|
|
4030
|
+
const images = input.renderedImages.map((image) => ({
|
|
4031
|
+
id: image.id,
|
|
4032
|
+
kind: image.kind,
|
|
4033
|
+
path: image.outputPath,
|
|
4034
|
+
relativePath: image.relativePath,
|
|
4035
|
+
description: image.description,
|
|
4036
|
+
anchorAfterSection: image.anchorAfterSection
|
|
4037
|
+
}));
|
|
4038
|
+
const outputs = input.outputs.map((output) => ({
|
|
4039
|
+
fileId: output.fileId,
|
|
4040
|
+
contentType: output.contentType,
|
|
4041
|
+
path: output.markdownPath,
|
|
4042
|
+
relativePath: path7.relative(generationDir, output.markdownPath)
|
|
4043
|
+
}));
|
|
4044
|
+
return {
|
|
4045
|
+
version: 1,
|
|
4046
|
+
title,
|
|
4047
|
+
slug,
|
|
4048
|
+
idea: input.idea,
|
|
4049
|
+
description,
|
|
4050
|
+
subtitle,
|
|
4051
|
+
keywords,
|
|
4052
|
+
contentType,
|
|
4053
|
+
style: input.style,
|
|
4054
|
+
intent: input.intent,
|
|
4055
|
+
targetLength: input.targetLength,
|
|
4056
|
+
angle,
|
|
4057
|
+
cover,
|
|
4058
|
+
sections,
|
|
4059
|
+
images,
|
|
4060
|
+
outputs,
|
|
4061
|
+
generatedAt: input.generatedAt,
|
|
4062
|
+
generationDir
|
|
4063
|
+
};
|
|
4064
|
+
}
|
|
4065
|
+
|
|
4006
4066
|
// src/pipeline/sessionStore.ts
|
|
4007
4067
|
import { createHash as createHash2 } from "crypto";
|
|
4008
4068
|
import { mkdir as mkdir5, readFile as readFile5, rm as rm2, writeFile as writeFile5 } from "fs/promises";
|
|
4009
|
-
import
|
|
4069
|
+
import path8 from "path";
|
|
4010
4070
|
import envPaths3 from "env-paths";
|
|
4011
4071
|
import { z as z6 } from "zod";
|
|
4012
4072
|
var STAGE_IDS = ["shared-plan", "planning", "sections", "image-prompts", "images", "output", "links"];
|
|
@@ -4049,7 +4109,8 @@ var pipelineArtifactSummarySchema = z6.object({
|
|
|
4049
4109
|
assetDir: z6.string().min(1),
|
|
4050
4110
|
analyticsPath: z6.string().min(1).default("unknown.analytics.json"),
|
|
4051
4111
|
interactionsPath: z6.string().min(1).default("unknown.interactions.json"),
|
|
4052
|
-
planPath: z6.string().min(1).nullable().default(null)
|
|
4112
|
+
planPath: z6.string().min(1).nullable().default(null),
|
|
4113
|
+
metaJsonPath: z6.string().min(1).default("meta.json")
|
|
4053
4114
|
});
|
|
4054
4115
|
var resolvedPathsSchema = z6.object({
|
|
4055
4116
|
markdownOutputDir: z6.string().min(1),
|
|
@@ -4085,18 +4146,18 @@ var writeSessionStateSchema = z6.object({
|
|
|
4085
4146
|
artifact: pipelineArtifactSummarySchema.nullable()
|
|
4086
4147
|
});
|
|
4087
4148
|
var ideonPaths3 = envPaths3("ideon", { suffix: "" });
|
|
4088
|
-
var sessionsDir =
|
|
4149
|
+
var sessionsDir = path8.join(ideonPaths3.config, "sessions");
|
|
4089
4150
|
function hashProjectPath(workingDir) {
|
|
4090
|
-
return createHash2("sha256").update(
|
|
4151
|
+
return createHash2("sha256").update(path8.resolve(workingDir)).digest("hex").slice(0, 16);
|
|
4091
4152
|
}
|
|
4092
4153
|
function resolveWriteRoot(workingDir) {
|
|
4093
|
-
return
|
|
4154
|
+
return path8.join(sessionsDir, hashProjectPath(workingDir));
|
|
4094
4155
|
}
|
|
4095
4156
|
function resolveStateFilePath(workingDir) {
|
|
4096
|
-
return
|
|
4157
|
+
return path8.join(resolveWriteRoot(workingDir), "state.json");
|
|
4097
4158
|
}
|
|
4098
4159
|
function resolveLegacyStatePath(workingDir) {
|
|
4099
|
-
return
|
|
4160
|
+
return path8.join(workingDir, ".ideon", "write", "state.json");
|
|
4100
4161
|
}
|
|
4101
4162
|
async function startFreshWriteSession(seed, workingDir = process.cwd()) {
|
|
4102
4163
|
const writeRoot = resolveWriteRoot(workingDir);
|
|
@@ -4157,7 +4218,7 @@ async function saveWriteSession(state, workingDir = process.cwd()) {
|
|
|
4157
4218
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4158
4219
|
});
|
|
4159
4220
|
const statePath = resolveStateFilePath(workingDir);
|
|
4160
|
-
await mkdir5(
|
|
4221
|
+
await mkdir5(path8.dirname(statePath), { recursive: true });
|
|
4161
4222
|
await writeFile5(statePath, `${JSON.stringify(next, null, 2)}
|
|
4162
4223
|
`, "utf8");
|
|
4163
4224
|
return next;
|
|
@@ -4716,12 +4777,12 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4716
4777
|
options.onUpdate?.(cloneStages(stages));
|
|
4717
4778
|
}
|
|
4718
4779
|
const baseSlug = plan?.slug ?? resolveGenerationSlug(input.idea, contentPlan?.title);
|
|
4719
|
-
const generationDir =
|
|
4780
|
+
const generationDir = path9.join(
|
|
4720
4781
|
writeSession.outputPaths.markdownOutputDir,
|
|
4721
4782
|
buildGenerationDirectoryName(baseSlug)
|
|
4722
4783
|
);
|
|
4723
4784
|
await mkdir6(generationDir, { recursive: true });
|
|
4724
|
-
const jobDefinitionPath =
|
|
4785
|
+
const jobDefinitionPath = path9.join(generationDir, "job.json");
|
|
4725
4786
|
await writeJsonFile(
|
|
4726
4787
|
jobDefinitionPath,
|
|
4727
4788
|
buildRunJobDefinition({
|
|
@@ -4733,12 +4794,12 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4733
4794
|
sourceJob: input.job
|
|
4734
4795
|
})
|
|
4735
4796
|
);
|
|
4736
|
-
const planPath = plan ?
|
|
4797
|
+
const planPath = plan ? path9.join(generationDir, "plan.md") : null;
|
|
4737
4798
|
if (plan && planPath) {
|
|
4738
4799
|
await writeUtf8File(planPath, renderPlanMarkdown(plan));
|
|
4739
4800
|
}
|
|
4740
4801
|
const primaryFilePrefix = toFilePrefix(primaryTarget.contentType);
|
|
4741
|
-
const primaryMarkdownPath =
|
|
4802
|
+
const primaryMarkdownPath = path9.join(generationDir, `${primaryFilePrefix}-1.md`);
|
|
4742
4803
|
const sharedAssetDir = generationDir;
|
|
4743
4804
|
if (isLongForm) {
|
|
4744
4805
|
const longPlan = plan;
|
|
@@ -4962,7 +5023,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4962
5023
|
})
|
|
4963
5024
|
};
|
|
4964
5025
|
options.onUpdate?.(cloneStages(stages));
|
|
4965
|
-
const markdownPath =
|
|
5026
|
+
const markdownPath = path9.join(generationDir, `${output.filePrefix}-${output.index}.md`);
|
|
4966
5027
|
try {
|
|
4967
5028
|
const content = await writeSingleShotContent({
|
|
4968
5029
|
idea: input.idea,
|
|
@@ -5025,7 +5086,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
5025
5086
|
...item,
|
|
5026
5087
|
status: "succeeded",
|
|
5027
5088
|
detail: "Saved markdown output.",
|
|
5028
|
-
summary:
|
|
5089
|
+
summary: path9.basename(markdownPath),
|
|
5029
5090
|
analytics: {
|
|
5030
5091
|
durationMs: itemDurationMs,
|
|
5031
5092
|
costUsd: knownItemCost.usd,
|
|
@@ -5077,15 +5138,47 @@ async function runPipelineShell(input, options = {}) {
|
|
|
5077
5138
|
markStageStarted(stageTracking, "links");
|
|
5078
5139
|
options.onUpdate?.(cloneStages(stages));
|
|
5079
5140
|
if (!shouldEnrichLinks) {
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
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
|
+
}
|
|
5089
5182
|
} else if (eligibleOutputsForLinks.length === 0) {
|
|
5090
5183
|
markStageCompleted(stageTracking, "links");
|
|
5091
5184
|
stages[6] = {
|
|
@@ -5240,10 +5333,24 @@ async function runPipelineShell(input, options = {}) {
|
|
|
5240
5333
|
llmCalls: llmInteractions,
|
|
5241
5334
|
t2iCalls: t2iInteractions
|
|
5242
5335
|
};
|
|
5243
|
-
const analyticsPath =
|
|
5244
|
-
const interactionsPath =
|
|
5336
|
+
const analyticsPath = path9.join(generationDir, "generation.analytics.json");
|
|
5337
|
+
const interactionsPath = path9.join(generationDir, "model.interactions.json");
|
|
5245
5338
|
await writeJsonFile(analyticsPath, analytics);
|
|
5246
5339
|
await writeJsonFile(interactionsPath, interactions);
|
|
5340
|
+
const metaJson = buildMetaJson({
|
|
5341
|
+
idea: input.idea,
|
|
5342
|
+
generationDir,
|
|
5343
|
+
contentPlan,
|
|
5344
|
+
plan,
|
|
5345
|
+
renderedImages: imageArtifacts?.renderedImages ?? [],
|
|
5346
|
+
outputs: generatedOutputs,
|
|
5347
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5348
|
+
style: input.config.settings.style,
|
|
5349
|
+
intent: input.config.settings.intent,
|
|
5350
|
+
targetLength: input.config.settings.targetLength ? resolveTargetLengthAlias(input.config.settings.targetLength) : null
|
|
5351
|
+
});
|
|
5352
|
+
const metaJsonPath = path9.join(generationDir, "meta.json");
|
|
5353
|
+
await writeJsonFile(metaJsonPath, metaJson);
|
|
5247
5354
|
const primaryMarkdownPathForArtifact = markdownPaths[0] ?? primaryMarkdownPath;
|
|
5248
5355
|
const artifact = {
|
|
5249
5356
|
title: plan?.title ?? contentPlan.title ?? deriveTitleFromIdea2(input.idea),
|
|
@@ -5257,7 +5364,8 @@ async function runPipelineShell(input, options = {}) {
|
|
|
5257
5364
|
assetDir: sharedAssetDir,
|
|
5258
5365
|
analyticsPath,
|
|
5259
5366
|
interactionsPath,
|
|
5260
|
-
planPath
|
|
5367
|
+
planPath,
|
|
5368
|
+
metaJsonPath
|
|
5261
5369
|
};
|
|
5262
5370
|
writeSession = await patchWriteSession(
|
|
5263
5371
|
{
|
|
@@ -5728,10 +5836,62 @@ function parsePipelineCustomLinks(rawLinks, unlinks) {
|
|
|
5728
5836
|
}
|
|
5729
5837
|
return Array.from(result.values());
|
|
5730
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
|
+
}
|
|
5731
5891
|
|
|
5732
5892
|
// src/cli/commands/links.ts
|
|
5733
|
-
import { readFile as
|
|
5734
|
-
import
|
|
5893
|
+
import { readFile as readFile7, stat as stat3 } from "fs/promises";
|
|
5894
|
+
import path10 from "path";
|
|
5735
5895
|
async function runLinksCommand(options, dependencies = {}) {
|
|
5736
5896
|
const slug = normalizeSlug2(options.slug);
|
|
5737
5897
|
const mode = normalizeMode(options.mode);
|
|
@@ -5742,7 +5902,7 @@ async function runLinksCommand(options, dependencies = {}) {
|
|
|
5742
5902
|
});
|
|
5743
5903
|
const markdownPath = await resolveMarkdownPathForSlug2(slug, cwd2);
|
|
5744
5904
|
const frontmatter = await readFrontmatter(markdownPath);
|
|
5745
|
-
const fileId =
|
|
5905
|
+
const fileId = path10.parse(markdownPath).name;
|
|
5746
5906
|
const articleTitle = frontmatter.title ?? toTitleFromSlug(slug);
|
|
5747
5907
|
const articleDescription = frontmatter.description ?? "";
|
|
5748
5908
|
const openRouterApiKey = resolved.config.secrets.openRouterApiKey;
|
|
@@ -5753,7 +5913,7 @@ async function runLinksCommand(options, dependencies = {}) {
|
|
|
5753
5913
|
}
|
|
5754
5914
|
const openRouter = new OpenRouterClient(openRouterApiKey);
|
|
5755
5915
|
const linksPath = resolveLinksPath(markdownPath);
|
|
5756
|
-
const existing = await
|
|
5916
|
+
const existing = await readExistingLinks2(linksPath);
|
|
5757
5917
|
const updatedCustomLinks = resolveCustomLinks(existing?.customLinks ?? [], options.links ?? [], options.unlinks ?? []);
|
|
5758
5918
|
const effectiveMaxLinks = options.maxLinks;
|
|
5759
5919
|
const linksResult = await enrichLinks({
|
|
@@ -5812,14 +5972,14 @@ function normalizeSlug2(rawSlug) {
|
|
|
5812
5972
|
}
|
|
5813
5973
|
async function resolveMarkdownPathForSlug2(slug, cwd2) {
|
|
5814
5974
|
const outputPaths = resolveOutputPaths();
|
|
5815
|
-
const directPath =
|
|
5975
|
+
const directPath = path10.join(outputPaths.markdownOutputDir, `${slug}.md`);
|
|
5816
5976
|
if (await isReadableFile(directPath)) {
|
|
5817
5977
|
return directPath;
|
|
5818
5978
|
}
|
|
5819
5979
|
const markdownFiles = await listMarkdownFilesRecursively(outputPaths.markdownOutputDir);
|
|
5820
5980
|
const matches = [];
|
|
5821
5981
|
for (const candidate of markdownFiles) {
|
|
5822
|
-
if (
|
|
5982
|
+
if (path10.basename(candidate) === `${slug}.md`) {
|
|
5823
5983
|
matches.push(candidate);
|
|
5824
5984
|
continue;
|
|
5825
5985
|
}
|
|
@@ -5848,7 +6008,7 @@ async function newestPath(paths) {
|
|
|
5848
6008
|
return latestPath;
|
|
5849
6009
|
}
|
|
5850
6010
|
async function readFrontmatter(markdownPath) {
|
|
5851
|
-
const markdown = await
|
|
6011
|
+
const markdown = await readFile7(markdownPath, "utf8");
|
|
5852
6012
|
return parseFrontmatter(markdown);
|
|
5853
6013
|
}
|
|
5854
6014
|
function parseFrontmatter(markdown) {
|
|
@@ -5892,11 +6052,11 @@ async function isReadableFile(filePath) {
|
|
|
5892
6052
|
return false;
|
|
5893
6053
|
}
|
|
5894
6054
|
}
|
|
5895
|
-
async function
|
|
6055
|
+
async function readExistingLinks2(linksPath) {
|
|
5896
6056
|
try {
|
|
5897
|
-
const raw = await
|
|
6057
|
+
const raw = await readFile7(linksPath, "utf8");
|
|
5898
6058
|
const parsed = JSON.parse(raw);
|
|
5899
|
-
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) => ({
|
|
5900
6060
|
expression: entry.expression.trim(),
|
|
5901
6061
|
url: entry.url.trim(),
|
|
5902
6062
|
title: typeof entry.title === "string" ? entry.title : null
|
|
@@ -5904,7 +6064,7 @@ async function readExistingLinks(linksPath) {
|
|
|
5904
6064
|
if (!links) {
|
|
5905
6065
|
throw new ReportedError(`Invalid links sidecar format at ${linksPath}. Expected { version, links[] }.`);
|
|
5906
6066
|
}
|
|
5907
|
-
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) => ({
|
|
5908
6068
|
expression: entry.expression.trim(),
|
|
5909
6069
|
url: entry.url.trim(),
|
|
5910
6070
|
title: typeof entry.title === "string" ? entry.title : null
|
|
@@ -5938,7 +6098,7 @@ function mergeLinks(existingLinks, generatedLinks) {
|
|
|
5938
6098
|
}
|
|
5939
6099
|
return merged;
|
|
5940
6100
|
}
|
|
5941
|
-
function
|
|
6101
|
+
function isValidLinkEntry2(value2) {
|
|
5942
6102
|
if (typeof value2 !== "object" || value2 === null) {
|
|
5943
6103
|
return false;
|
|
5944
6104
|
}
|
|
@@ -5953,7 +6113,7 @@ function readErrorCode2(error) {
|
|
|
5953
6113
|
return typeof code === "string" ? code : null;
|
|
5954
6114
|
}
|
|
5955
6115
|
function formatRelativePath2(cwd2, targetPath) {
|
|
5956
|
-
const relativePath =
|
|
6116
|
+
const relativePath = path10.relative(cwd2, targetPath);
|
|
5957
6117
|
return relativePath.length > 0 ? relativePath : targetPath;
|
|
5958
6118
|
}
|
|
5959
6119
|
function logProgress(event, log) {
|
|
@@ -5992,8 +6152,8 @@ function resolveCustomLinks(existing, addRaw, removeExpressions) {
|
|
|
5992
6152
|
}
|
|
5993
6153
|
|
|
5994
6154
|
// src/cli/commands/export.ts
|
|
5995
|
-
import { copyFile as copyFile2, mkdir as mkdir7, readFile as
|
|
5996
|
-
import
|
|
6155
|
+
import { copyFile as copyFile2, mkdir as mkdir7, readFile as readFile9, stat as stat5, writeFile as writeFile6 } from "fs/promises";
|
|
6156
|
+
import path12 from "path";
|
|
5997
6157
|
|
|
5998
6158
|
// src/output/enrichMarkdownWithLinks.ts
|
|
5999
6159
|
function enrichMarkdownWithLinks(markdown, links) {
|
|
@@ -6049,8 +6209,8 @@ function escapeRegExp(value2) {
|
|
|
6049
6209
|
}
|
|
6050
6210
|
|
|
6051
6211
|
// src/server/previewHelpers.ts
|
|
6052
|
-
import { readdir, stat as stat4, readFile as
|
|
6053
|
-
import
|
|
6212
|
+
import { readdir, stat as stat4, readFile as readFile8 } from "fs/promises";
|
|
6213
|
+
import path11 from "path";
|
|
6054
6214
|
var DEFAULT_PORT = 4173;
|
|
6055
6215
|
var CONTENT_TYPE_ORDER = ["article", "blog-post", "x-thread", "x-post", "linkedin-post", "reddit-post", "newsletter"];
|
|
6056
6216
|
var FILE_PREFIX_TO_CONTENT_TYPE = {
|
|
@@ -6108,8 +6268,8 @@ function extractHeadingTitle(markdown) {
|
|
|
6108
6268
|
}
|
|
6109
6269
|
async function resolveMarkdownPath(markdownPathArg, markdownOutputDir, cwd2) {
|
|
6110
6270
|
if (markdownPathArg) {
|
|
6111
|
-
const resolved =
|
|
6112
|
-
if (
|
|
6271
|
+
const resolved = path11.isAbsolute(markdownPathArg) ? markdownPathArg : path11.resolve(cwd2, markdownPathArg);
|
|
6272
|
+
if (path11.extname(resolved).toLowerCase() !== ".md") {
|
|
6113
6273
|
throw new Error(`Expected a markdown file (.md), received: ${resolved}`);
|
|
6114
6274
|
}
|
|
6115
6275
|
await assertFileExists(resolved, "Could not find markdown file");
|
|
@@ -6151,9 +6311,9 @@ function extractCoverImageUrl(markdown) {
|
|
|
6151
6311
|
return match?.[1] ?? null;
|
|
6152
6312
|
}
|
|
6153
6313
|
async function extractArticleMetadata(markdownPath) {
|
|
6154
|
-
const markdown = await
|
|
6314
|
+
const markdown = await readFile8(markdownPath, "utf8");
|
|
6155
6315
|
const fileStat = await stat4(markdownPath);
|
|
6156
|
-
const slug = extractFrontmatterSlug(markdown) ??
|
|
6316
|
+
const slug = extractFrontmatterSlug(markdown) ?? path11.basename(markdownPath, ".md");
|
|
6157
6317
|
const title = extractHeadingTitle(stripFrontmatter2(markdown)) ?? slug;
|
|
6158
6318
|
const body = stripFrontmatter2(markdown);
|
|
6159
6319
|
const previewSnippet = body.replace(/[#\[\]()!\-*_`]/g, "").trim().substring(0, 150);
|
|
@@ -6168,7 +6328,7 @@ async function extractArticleMetadata(markdownPath) {
|
|
|
6168
6328
|
}
|
|
6169
6329
|
async function listAllGenerations(markdownOutputDir) {
|
|
6170
6330
|
const markdownFiles = await findMarkdownFiles(markdownOutputDir);
|
|
6171
|
-
const
|
|
6331
|
+
const outputMap = /* @__PURE__ */ new Map();
|
|
6172
6332
|
for (const filePath of markdownFiles) {
|
|
6173
6333
|
try {
|
|
6174
6334
|
const metadata = await extractArticleMetadata(filePath);
|
|
@@ -6186,15 +6346,23 @@ async function listAllGenerations(markdownOutputDir) {
|
|
|
6186
6346
|
contentTypeLabel: toContentTypeLabel(identity.contentType),
|
|
6187
6347
|
index: identity.index
|
|
6188
6348
|
};
|
|
6189
|
-
const
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
grouped.set(identity.generationId, [output]);
|
|
6349
|
+
const outputKey = `${output.generationId}:${output.contentType}:${output.index}`;
|
|
6350
|
+
const existing = outputMap.get(outputKey);
|
|
6351
|
+
if (!existing || output.mtime > existing.mtime) {
|
|
6352
|
+
outputMap.set(outputKey, output);
|
|
6194
6353
|
}
|
|
6195
6354
|
} catch {
|
|
6196
6355
|
}
|
|
6197
6356
|
}
|
|
6357
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
6358
|
+
for (const output of outputMap.values()) {
|
|
6359
|
+
const existing = grouped.get(output.generationId);
|
|
6360
|
+
if (existing) {
|
|
6361
|
+
existing.push(output);
|
|
6362
|
+
} else {
|
|
6363
|
+
grouped.set(output.generationId, [output]);
|
|
6364
|
+
}
|
|
6365
|
+
}
|
|
6198
6366
|
const generations = [];
|
|
6199
6367
|
for (const [id, outputs] of grouped.entries()) {
|
|
6200
6368
|
outputs.sort((a, b) => compareContentTypes(a.contentType, b.contentType) || a.index - b.index || b.mtime - a.mtime);
|
|
@@ -6218,16 +6386,16 @@ async function listAllGenerations(markdownOutputDir) {
|
|
|
6218
6386
|
return generations;
|
|
6219
6387
|
}
|
|
6220
6388
|
function deriveGenerationId(markdownPath, markdownOutputDir) {
|
|
6221
|
-
const relative =
|
|
6222
|
-
const normalized = relative.split(
|
|
6389
|
+
const relative = path11.relative(markdownOutputDir, markdownPath);
|
|
6390
|
+
const normalized = relative.split(path11.sep).join("/");
|
|
6223
6391
|
if (!normalized || normalized.startsWith("../")) {
|
|
6224
|
-
return
|
|
6392
|
+
return path11.basename(markdownPath, ".md");
|
|
6225
6393
|
}
|
|
6226
6394
|
const segments = normalized.split("/").filter(Boolean);
|
|
6227
6395
|
if (segments.length <= 1) {
|
|
6228
|
-
return
|
|
6396
|
+
return path11.basename(markdownPath, ".md");
|
|
6229
6397
|
}
|
|
6230
|
-
return segments[0] ??
|
|
6398
|
+
return segments[0] ?? path11.basename(markdownPath, ".md");
|
|
6231
6399
|
}
|
|
6232
6400
|
async function findMarkdownFiles(markdownOutputDir) {
|
|
6233
6401
|
const files = [];
|
|
@@ -6244,7 +6412,7 @@ async function findMarkdownFiles(markdownOutputDir) {
|
|
|
6244
6412
|
continue;
|
|
6245
6413
|
}
|
|
6246
6414
|
for (const entry of entries) {
|
|
6247
|
-
const fullPath =
|
|
6415
|
+
const fullPath = path11.join(current, entry.name);
|
|
6248
6416
|
if (entry.isDirectory()) {
|
|
6249
6417
|
stack.push(fullPath);
|
|
6250
6418
|
continue;
|
|
@@ -6258,7 +6426,7 @@ async function findMarkdownFiles(markdownOutputDir) {
|
|
|
6258
6426
|
}
|
|
6259
6427
|
function deriveOutputIdentity(markdownPath, markdownOutputDir) {
|
|
6260
6428
|
const generationId = deriveGenerationId(markdownPath, markdownOutputDir);
|
|
6261
|
-
const fileBase =
|
|
6429
|
+
const fileBase = path11.basename(markdownPath, ".md");
|
|
6262
6430
|
const parsed = fileBase.match(/^([a-z0-9-]+)-(\d+)$/i);
|
|
6263
6431
|
if (!parsed || !parsed[1] || !parsed[2]) {
|
|
6264
6432
|
return {
|
|
@@ -6294,13 +6462,13 @@ function toContentTypeLabel(contentType) {
|
|
|
6294
6462
|
}
|
|
6295
6463
|
async function resolvePrimaryContentType(outputs) {
|
|
6296
6464
|
const fallback = outputs.find((output) => output.contentType === "article")?.contentType ?? outputs[0]?.contentType ?? "article";
|
|
6297
|
-
const generationDir =
|
|
6465
|
+
const generationDir = path11.dirname(outputs[0]?.sourcePath ?? "");
|
|
6298
6466
|
if (!generationDir) {
|
|
6299
6467
|
return fallback;
|
|
6300
6468
|
}
|
|
6301
|
-
const jobPath =
|
|
6469
|
+
const jobPath = path11.join(generationDir, "job.json");
|
|
6302
6470
|
try {
|
|
6303
|
-
const raw = await
|
|
6471
|
+
const raw = await readFile8(jobPath, "utf8");
|
|
6304
6472
|
const parsed = JSON.parse(raw);
|
|
6305
6473
|
const targets = Array.isArray(parsed.contentTargets) ? parsed.contentTargets : Array.isArray(parsed.settings?.contentTargets) ? parsed.settings.contentTargets : [];
|
|
6306
6474
|
const primary = targets.find((target) => target?.role === "primary" && typeof target.contentType === "string");
|
|
@@ -6314,6 +6482,11 @@ async function resolvePrimaryContentType(outputs) {
|
|
|
6314
6482
|
}
|
|
6315
6483
|
|
|
6316
6484
|
// src/cli/commands/export.ts
|
|
6485
|
+
var INTERNAL_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
6486
|
+
"job.json",
|
|
6487
|
+
"model.interactions.json",
|
|
6488
|
+
"generation.analytics.json"
|
|
6489
|
+
]);
|
|
6317
6490
|
async function runOutputCommand(options, dependencies = {}) {
|
|
6318
6491
|
const cwd2 = dependencies.cwd ?? process.cwd();
|
|
6319
6492
|
const log = dependencies.log ?? ((message) => console.log(message));
|
|
@@ -6336,11 +6509,11 @@ async function runOutputCommand(options, dependencies = {}) {
|
|
|
6336
6509
|
);
|
|
6337
6510
|
}
|
|
6338
6511
|
const sourceMarkdownPath = articleOutput.sourcePath;
|
|
6339
|
-
const sourceMarkdown = await
|
|
6340
|
-
const slug = extractFrontmatterSlug2(sourceMarkdown) ??
|
|
6512
|
+
const sourceMarkdown = await readFile9(sourceMarkdownPath, "utf8");
|
|
6513
|
+
const slug = extractFrontmatterSlug2(sourceMarkdown) ?? path12.basename(sourceMarkdownPath, ".md");
|
|
6341
6514
|
const exportFilename = `${slug}.md`;
|
|
6342
6515
|
const destinationDir = await resolveDestinationDir(options.destinationPath, cwd2);
|
|
6343
|
-
const destinationFilePath =
|
|
6516
|
+
const destinationFilePath = path12.join(destinationDir, exportFilename);
|
|
6344
6517
|
if (!options.overwrite && await fileExists2(destinationFilePath)) {
|
|
6345
6518
|
throw new ReportedError(
|
|
6346
6519
|
`Export file already exists: ${destinationFilePath}. Pass --overwrite to replace it.`
|
|
@@ -6349,32 +6522,24 @@ async function runOutputCommand(options, dependencies = {}) {
|
|
|
6349
6522
|
await mkdir7(destinationDir, { recursive: true });
|
|
6350
6523
|
const links = await loadLinks(sourceMarkdownPath);
|
|
6351
6524
|
const enrichedMarkdown = enrichWithFrontmatterGuard(sourceMarkdown, links);
|
|
6352
|
-
const sourceDir =
|
|
6353
|
-
const
|
|
6354
|
-
const
|
|
6355
|
-
for (const
|
|
6356
|
-
const
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
}
|
|
6365
|
-
if (!imageStat.isFile()) {
|
|
6366
|
-
throw new ReportedError(`Referenced image path is not a file: ${absoluteImageSrc}.`);
|
|
6367
|
-
}
|
|
6368
|
-
const destImagePath = path11.join(destinationDir, relImagePath);
|
|
6369
|
-
await mkdir7(path11.dirname(destImagePath), { recursive: true });
|
|
6370
|
-
await copyFile2(absoluteImageSrc, destImagePath);
|
|
6371
|
-
copiedImages.push(relImagePath);
|
|
6525
|
+
const sourceDir = path12.dirname(sourceMarkdownPath);
|
|
6526
|
+
const allFiles = await listFilesRecursively(sourceDir, () => true);
|
|
6527
|
+
const copiedFiles = [];
|
|
6528
|
+
for (const absoluteSrc of allFiles) {
|
|
6529
|
+
const basename = path12.basename(absoluteSrc);
|
|
6530
|
+
if (INTERNAL_FILE_NAMES.has(basename)) continue;
|
|
6531
|
+
if (path12.resolve(absoluteSrc) === path12.resolve(sourceMarkdownPath)) continue;
|
|
6532
|
+
const relativePath = path12.relative(sourceDir, absoluteSrc);
|
|
6533
|
+
const destPath = path12.join(destinationDir, relativePath);
|
|
6534
|
+
await mkdir7(path12.dirname(destPath), { recursive: true });
|
|
6535
|
+
await copyFile2(absoluteSrc, destPath);
|
|
6536
|
+
copiedFiles.push(relativePath);
|
|
6372
6537
|
}
|
|
6373
6538
|
await writeFile6(destinationFilePath, enrichedMarkdown, "utf8");
|
|
6374
|
-
const relDest =
|
|
6539
|
+
const relDest = path12.relative(cwd2, destinationFilePath);
|
|
6375
6540
|
log(`Exported "${generation.id}" (${generation.primaryContentType} #${targetIndex}) \u2192 ${relDest}`);
|
|
6376
|
-
if (
|
|
6377
|
-
log(`Copied ${
|
|
6541
|
+
if (copiedFiles.length > 0) {
|
|
6542
|
+
log(`Copied ${copiedFiles.length} file${copiedFiles.length === 1 ? "" : "s"}: ${copiedFiles.join(", ")}`);
|
|
6378
6543
|
}
|
|
6379
6544
|
if (links.length > 0) {
|
|
6380
6545
|
log(`Injected ${links.length} inline link${links.length === 1 ? "" : "s"}.`);
|
|
@@ -6396,7 +6561,7 @@ function resolveGeneration(generations, generationId) {
|
|
|
6396
6561
|
);
|
|
6397
6562
|
}
|
|
6398
6563
|
async function resolveDestinationDir(destinationPath, cwd2) {
|
|
6399
|
-
const resolved =
|
|
6564
|
+
const resolved = path12.isAbsolute(destinationPath) ? destinationPath : path12.resolve(cwd2, destinationPath);
|
|
6400
6565
|
return resolved;
|
|
6401
6566
|
}
|
|
6402
6567
|
async function fileExists2(filePath) {
|
|
@@ -6411,7 +6576,7 @@ async function loadLinks(markdownPath) {
|
|
|
6411
6576
|
const linksPath = resolveLinksPath(markdownPath);
|
|
6412
6577
|
let raw;
|
|
6413
6578
|
try {
|
|
6414
|
-
raw = await
|
|
6579
|
+
raw = await readFile9(linksPath, "utf8");
|
|
6415
6580
|
} catch {
|
|
6416
6581
|
return [];
|
|
6417
6582
|
}
|
|
@@ -6466,22 +6631,6 @@ function extractFrontmatterSlug2(markdown) {
|
|
|
6466
6631
|
const unquoted = rawSlug.replace(/^['""]|['""]$/g, "").trim();
|
|
6467
6632
|
return unquoted.length > 0 ? unquoted : null;
|
|
6468
6633
|
}
|
|
6469
|
-
function extractLocalImagePaths(markdown) {
|
|
6470
|
-
const imagePattern = /!\[[^\]]*\]\(([^)]+)\)/g;
|
|
6471
|
-
const paths = [];
|
|
6472
|
-
let match;
|
|
6473
|
-
while ((match = imagePattern.exec(markdown)) !== null) {
|
|
6474
|
-
const rawPath = match[1]?.trim();
|
|
6475
|
-
if (!rawPath) {
|
|
6476
|
-
continue;
|
|
6477
|
-
}
|
|
6478
|
-
if (rawPath.startsWith("http://") || rawPath.startsWith("https://") || rawPath.startsWith("data:") || rawPath.startsWith("/") || rawPath.startsWith("#")) {
|
|
6479
|
-
continue;
|
|
6480
|
-
}
|
|
6481
|
-
paths.push(rawPath);
|
|
6482
|
-
}
|
|
6483
|
-
return paths;
|
|
6484
|
-
}
|
|
6485
6634
|
|
|
6486
6635
|
// src/cli/commands/writeTargetSpecs.ts
|
|
6487
6636
|
function parseTargetSpec(spec) {
|
|
@@ -7343,15 +7492,15 @@ async function openSettings() {
|
|
|
7343
7492
|
}
|
|
7344
7493
|
|
|
7345
7494
|
// src/cli/commands/serve.ts
|
|
7346
|
-
import
|
|
7495
|
+
import path14 from "path";
|
|
7347
7496
|
import { spawn } from "child_process";
|
|
7348
7497
|
|
|
7349
7498
|
// src/server/previewServer.ts
|
|
7350
7499
|
import { execFile } from "child_process";
|
|
7351
7500
|
import { promisify } from "util";
|
|
7352
|
-
import { readFile as
|
|
7501
|
+
import { readFile as readFile10, stat as stat6 } from "fs/promises";
|
|
7353
7502
|
import { watch as fsWatch } from "fs";
|
|
7354
|
-
import
|
|
7503
|
+
import path13 from "path";
|
|
7355
7504
|
import { fileURLToPath } from "url";
|
|
7356
7505
|
import express from "express";
|
|
7357
7506
|
import { marked } from "marked";
|
|
@@ -7366,7 +7515,7 @@ async function startPreviewServer(options) {
|
|
|
7366
7515
|
const app = express();
|
|
7367
7516
|
const previewClientDir = await resolvePreviewClientBuildDir();
|
|
7368
7517
|
app.disable("x-powered-by");
|
|
7369
|
-
app.use("/assets", express.static(options.assetDir));
|
|
7518
|
+
app.use("/assets", express.static(options.assetDir, { dotfiles: "allow" }));
|
|
7370
7519
|
if (previewClientDir) {
|
|
7371
7520
|
app.use(express.static(previewClientDir, { index: false }));
|
|
7372
7521
|
}
|
|
@@ -7400,7 +7549,7 @@ async function startPreviewServer(options) {
|
|
|
7400
7549
|
const assetPathParam = req.params.assetPath;
|
|
7401
7550
|
const rawAssetPath = Array.isArray(assetPathParam) ? assetPathParam.join("/") : assetPathParam ?? "";
|
|
7402
7551
|
const resolvedAssetPath = await resolveGenerationAssetPath(generationId, rawAssetPath, options.markdownOutputDir);
|
|
7403
|
-
res.sendFile(resolvedAssetPath);
|
|
7552
|
+
res.sendFile(resolvedAssetPath, { dotfiles: "allow" });
|
|
7404
7553
|
} catch (error) {
|
|
7405
7554
|
const message = error instanceof Error ? error.message : "Unknown error loading generation asset";
|
|
7406
7555
|
const status = error instanceof MissingArticleError ? 404 : 400;
|
|
@@ -7449,7 +7598,7 @@ async function startPreviewServer(options) {
|
|
|
7449
7598
|
if (options.watch) {
|
|
7450
7599
|
let html2;
|
|
7451
7600
|
try {
|
|
7452
|
-
html2 = await
|
|
7601
|
+
html2 = await readFile10(path13.join(previewClientDir, "index.html"), "utf8");
|
|
7453
7602
|
} catch {
|
|
7454
7603
|
res.status(200).type("html").send(
|
|
7455
7604
|
`<!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>`
|
|
@@ -7460,7 +7609,7 @@ async function startPreviewServer(options) {
|
|
|
7460
7609
|
const injected = html2.replace("</body>", `${reloadScript}</body>`);
|
|
7461
7610
|
res.status(200).type("html").send(injected);
|
|
7462
7611
|
} else {
|
|
7463
|
-
res.status(200).sendFile(
|
|
7612
|
+
res.status(200).sendFile(path13.join(previewClientDir, "index.html"));
|
|
7464
7613
|
}
|
|
7465
7614
|
return;
|
|
7466
7615
|
}
|
|
@@ -7513,7 +7662,7 @@ async function getArticleContent(generationId, markdownOutputDir) {
|
|
|
7513
7662
|
generation.outputs.map(async (output) => {
|
|
7514
7663
|
let markdown = "";
|
|
7515
7664
|
try {
|
|
7516
|
-
markdown = await
|
|
7665
|
+
markdown = await readFile10(output.sourcePath, "utf8");
|
|
7517
7666
|
} catch (error) {
|
|
7518
7667
|
if (isMissingFileError(error)) {
|
|
7519
7668
|
throw new MissingArticleError(`Generation "${generationId}" no longer exists.`);
|
|
@@ -7531,15 +7680,17 @@ async function getArticleContent(generationId, markdownOutputDir) {
|
|
|
7531
7680
|
};
|
|
7532
7681
|
})
|
|
7533
7682
|
);
|
|
7534
|
-
const generationDir =
|
|
7683
|
+
const generationDir = path13.dirname(generation.outputs[0]?.sourcePath ?? "");
|
|
7535
7684
|
const interactions = generationDir ? await loadSavedInteractions(generationDir) : { llmCalls: [], t2iCalls: [] };
|
|
7536
7685
|
const analyticsSummary = generationDir ? await loadSavedAnalyticsSummary(generationDir) : null;
|
|
7686
|
+
const metaJson = generationDir ? await loadSavedMetaJson(generationDir) : null;
|
|
7537
7687
|
return {
|
|
7538
7688
|
title: generation.title,
|
|
7539
7689
|
generationId: generation.id,
|
|
7540
7690
|
sourcePath,
|
|
7541
7691
|
interactions,
|
|
7542
7692
|
analyticsSummary,
|
|
7693
|
+
metaJson,
|
|
7543
7694
|
outputs
|
|
7544
7695
|
};
|
|
7545
7696
|
}
|
|
@@ -7560,7 +7711,7 @@ async function resolveActivePreviewArticle(preferredMarkdownPath, markdownOutput
|
|
|
7560
7711
|
};
|
|
7561
7712
|
}
|
|
7562
7713
|
function resolveGenerationSourcePath(generation, markdownOutputDir) {
|
|
7563
|
-
return generation.outputs.find((output) => output.contentType === generation.primaryContentType)?.sourcePath ?? generation.outputs[0]?.sourcePath ??
|
|
7714
|
+
return generation.outputs.find((output) => output.contentType === generation.primaryContentType)?.sourcePath ?? generation.outputs[0]?.sourcePath ?? path13.join(markdownOutputDir, generation.id);
|
|
7564
7715
|
}
|
|
7565
7716
|
function isMissingFileError(error) {
|
|
7566
7717
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
@@ -7575,7 +7726,7 @@ async function renderArticleHtml(markdown, generationId, sourcePath) {
|
|
|
7575
7726
|
async function loadSavedLinks(markdownPath) {
|
|
7576
7727
|
const linksPath = resolveLinksPath(markdownPath);
|
|
7577
7728
|
try {
|
|
7578
|
-
const raw = await
|
|
7729
|
+
const raw = await readFile10(linksPath, "utf8");
|
|
7579
7730
|
const parsed = JSON.parse(raw);
|
|
7580
7731
|
if (!Array.isArray(parsed.links)) {
|
|
7581
7732
|
return [];
|
|
@@ -7599,9 +7750,9 @@ async function loadSavedLinks(markdownPath) {
|
|
|
7599
7750
|
}
|
|
7600
7751
|
}
|
|
7601
7752
|
async function loadSavedInteractions(generationDir) {
|
|
7602
|
-
const interactionsPath =
|
|
7753
|
+
const interactionsPath = path13.join(generationDir, "model.interactions.json");
|
|
7603
7754
|
try {
|
|
7604
|
-
const raw = await
|
|
7755
|
+
const raw = await readFile10(interactionsPath, "utf8");
|
|
7605
7756
|
const parsed = JSON.parse(raw);
|
|
7606
7757
|
const llmCalls = Array.isArray(parsed.llmCalls) ? parsed.llmCalls.filter(isPreviewLlmInteraction) : [];
|
|
7607
7758
|
const t2iCalls = Array.isArray(parsed.t2iCalls) ? parsed.t2iCalls.filter(isPreviewT2IInteraction) : [];
|
|
@@ -7617,9 +7768,9 @@ async function loadSavedInteractions(generationDir) {
|
|
|
7617
7768
|
}
|
|
7618
7769
|
}
|
|
7619
7770
|
async function loadSavedAnalyticsSummary(generationDir) {
|
|
7620
|
-
const analyticsPath =
|
|
7771
|
+
const analyticsPath = path13.join(generationDir, "generation.analytics.json");
|
|
7621
7772
|
try {
|
|
7622
|
-
const raw = await
|
|
7773
|
+
const raw = await readFile10(analyticsPath, "utf8");
|
|
7623
7774
|
const parsed = JSON.parse(raw);
|
|
7624
7775
|
const summary = parsed.summary;
|
|
7625
7776
|
if (!summary || typeof summary !== "object") {
|
|
@@ -7640,6 +7791,15 @@ async function loadSavedAnalyticsSummary(generationDir) {
|
|
|
7640
7791
|
return null;
|
|
7641
7792
|
}
|
|
7642
7793
|
}
|
|
7794
|
+
async function loadSavedMetaJson(generationDir) {
|
|
7795
|
+
const metaJsonPath = path13.join(generationDir, "meta.json");
|
|
7796
|
+
try {
|
|
7797
|
+
const raw = await readFile10(metaJsonPath, "utf8");
|
|
7798
|
+
return JSON.parse(raw);
|
|
7799
|
+
} catch {
|
|
7800
|
+
return null;
|
|
7801
|
+
}
|
|
7802
|
+
}
|
|
7643
7803
|
async function getPreviewBootstrapData(preferredMarkdownPath, markdownOutputDir) {
|
|
7644
7804
|
const activeArticle = await resolveActivePreviewArticle(preferredMarkdownPath, markdownOutputDir);
|
|
7645
7805
|
const emptyStateMessage = activeArticle ? null : `No generated content found in ${markdownOutputDir}. Run ideon write "your idea" first.`;
|
|
@@ -7651,14 +7811,14 @@ async function getPreviewBootstrapData(preferredMarkdownPath, markdownOutputDir)
|
|
|
7651
7811
|
};
|
|
7652
7812
|
}
|
|
7653
7813
|
async function resolvePreviewClientBuildDir() {
|
|
7654
|
-
const currentDir =
|
|
7814
|
+
const currentDir = path13.dirname(fileURLToPath(import.meta.url));
|
|
7655
7815
|
const candidates = [
|
|
7656
|
-
|
|
7657
|
-
|
|
7816
|
+
path13.resolve(currentDir, "preview"),
|
|
7817
|
+
path13.resolve(currentDir, "../../dist/preview")
|
|
7658
7818
|
];
|
|
7659
7819
|
for (const candidate of candidates) {
|
|
7660
7820
|
try {
|
|
7661
|
-
const indexStat = await stat6(
|
|
7821
|
+
const indexStat = await stat6(path13.join(candidate, "index.html"));
|
|
7662
7822
|
if (indexStat.isFile()) {
|
|
7663
7823
|
return candidate;
|
|
7664
7824
|
}
|
|
@@ -7720,17 +7880,17 @@ async function resolveGenerationAssetPath(generationId, rawAssetPath, markdownOu
|
|
|
7720
7880
|
throw new MissingArticleError(`Generation "${generationId}" no longer exists.`);
|
|
7721
7881
|
}
|
|
7722
7882
|
const decodedAssetPath = decodeURIComponent(rawAssetPath);
|
|
7723
|
-
const normalizedRelative =
|
|
7724
|
-
if (normalizedRelative.length === 0 || normalizedRelative === "." || normalizedRelative.startsWith("../") || normalizedRelative.includes("/../") ||
|
|
7883
|
+
const normalizedRelative = path13.posix.normalize(decodedAssetPath.replace(/\\/g, "/"));
|
|
7884
|
+
if (normalizedRelative.length === 0 || normalizedRelative === "." || normalizedRelative.startsWith("../") || normalizedRelative.includes("/../") || path13.posix.isAbsolute(normalizedRelative)) {
|
|
7725
7885
|
throw new Error("Invalid generation asset path.");
|
|
7726
7886
|
}
|
|
7727
|
-
const generationDir =
|
|
7887
|
+
const generationDir = path13.dirname(generation.outputs[0]?.sourcePath ?? "");
|
|
7728
7888
|
if (!generationDir) {
|
|
7729
7889
|
throw new MissingArticleError(`Generation "${generationId}" has no source directory.`);
|
|
7730
7890
|
}
|
|
7731
|
-
const resolvedPath =
|
|
7732
|
-
const relativeToGeneration =
|
|
7733
|
-
if (relativeToGeneration.startsWith("..") ||
|
|
7891
|
+
const resolvedPath = path13.resolve(generationDir, normalizedRelative);
|
|
7892
|
+
const relativeToGeneration = path13.relative(generationDir, resolvedPath);
|
|
7893
|
+
if (relativeToGeneration.startsWith("..") || path13.isAbsolute(relativeToGeneration)) {
|
|
7734
7894
|
throw new Error("Invalid generation asset path.");
|
|
7735
7895
|
}
|
|
7736
7896
|
try {
|
|
@@ -9208,7 +9368,7 @@ async function runServeCommand(options) {
|
|
|
9208
9368
|
const markdownPath = await resolveMarkdownPath(options.markdownPath, outputPaths.markdownOutputDir, process.cwd());
|
|
9209
9369
|
const port = parsePort(options.port);
|
|
9210
9370
|
if (options.watch) {
|
|
9211
|
-
const viteBin =
|
|
9371
|
+
const viteBin = path14.resolve(process.cwd(), "node_modules", ".bin", "vite");
|
|
9212
9372
|
const viteProcess = spawn(viteBin, ["build", "--watch"], {
|
|
9213
9373
|
stdio: "inherit",
|
|
9214
9374
|
shell: process.platform === "win32"
|
|
@@ -9234,8 +9394,8 @@ async function runServeCommand(options) {
|
|
|
9234
9394
|
openBrowser: options.openBrowser,
|
|
9235
9395
|
watch: options.watch
|
|
9236
9396
|
});
|
|
9237
|
-
const relativeArticle =
|
|
9238
|
-
const relativeAssets =
|
|
9397
|
+
const relativeArticle = path14.relative(process.cwd(), markdownPath);
|
|
9398
|
+
const relativeAssets = path14.relative(process.cwd(), outputPaths.assetOutputDir);
|
|
9239
9399
|
console.log(`Previewing ${relativeArticle || markdownPath}`);
|
|
9240
9400
|
console.log(`Serving assets from ${relativeAssets || outputPaths.assetOutputDir}`);
|
|
9241
9401
|
console.log(`Open ${server.url}`);
|
|
@@ -9785,6 +9945,7 @@ async function renderPlainPipeline(input, dryRun, enrichLinks2, runMode, links,
|
|
|
9785
9945
|
title: result.artifact.title,
|
|
9786
9946
|
slug: result.artifact.slug
|
|
9787
9947
|
});
|
|
9948
|
+
return result;
|
|
9788
9949
|
} catch (error) {
|
|
9789
9950
|
const message = error instanceof Error ? withWriteResumeHint(error.message) : withWriteResumeHint("Pipeline failed.");
|
|
9790
9951
|
await notifyWriteFailed({
|
|
@@ -10101,6 +10262,7 @@ function WriteApp({
|
|
|
10101
10262
|
unlinks,
|
|
10102
10263
|
maxLinks,
|
|
10103
10264
|
maxImages,
|
|
10265
|
+
onSuccess,
|
|
10104
10266
|
onError
|
|
10105
10267
|
}) {
|
|
10106
10268
|
const { exit } = useApp3();
|
|
@@ -10136,6 +10298,7 @@ function WriteApp({
|
|
|
10136
10298
|
return;
|
|
10137
10299
|
}
|
|
10138
10300
|
setResult(runResult);
|
|
10301
|
+
onSuccess?.(runResult);
|
|
10139
10302
|
await notifyWriteSucceeded({
|
|
10140
10303
|
enabled: input.config.settings.notifications.enabled,
|
|
10141
10304
|
title: runResult.artifact.title,
|
|
@@ -10174,7 +10337,7 @@ function WriteApp({
|
|
|
10174
10337
|
}
|
|
10175
10338
|
async function runWriteCommand(options) {
|
|
10176
10339
|
const input = await resolveInputWithInteractiveIdeaFallback(options);
|
|
10177
|
-
await runWritePipeline(input, options.dryRun, options.enrichLinks, "fresh", options.noInteractive, options.links, options.unlinks, options.maxLinks, options.maxImages);
|
|
10340
|
+
await runWritePipeline(input, options.dryRun, options.enrichLinks, "fresh", options.noInteractive, options.links, options.unlinks, options.maxLinks, options.maxImages, options.exportPath);
|
|
10178
10341
|
}
|
|
10179
10342
|
async function runWriteResumeCommand(options = {}) {
|
|
10180
10343
|
const session = await loadWriteSession();
|
|
@@ -10196,9 +10359,9 @@ async function runWriteResumeCommand(options = {}) {
|
|
|
10196
10359
|
secrets: resolved.config.secrets
|
|
10197
10360
|
}
|
|
10198
10361
|
};
|
|
10199
|
-
await runWritePipeline(input, session.dryRun, options.enrichLinks ?? false, "resume", options.noInteractive ?? false, options.links, options.unlinks, options.maxLinks, options.maxImages);
|
|
10362
|
+
await runWritePipeline(input, session.dryRun, options.enrichLinks ?? false, "resume", options.noInteractive ?? false, options.links, options.unlinks, options.maxLinks, options.maxImages, options.exportPath);
|
|
10200
10363
|
}
|
|
10201
|
-
async function runWritePipeline(input, dryRun, enrichLinks2, runMode, noInteractive, links, unlinks, maxLinks, maxImages) {
|
|
10364
|
+
async function runWritePipeline(input, dryRun, enrichLinks2, runMode, noInteractive, links, unlinks, maxLinks, maxImages, exportPath) {
|
|
10202
10365
|
let interruptHandled = false;
|
|
10203
10366
|
const handleSignal = (signal) => {
|
|
10204
10367
|
if (interruptHandled) {
|
|
@@ -10232,10 +10395,17 @@ async function runWritePipeline(input, dryRun, enrichLinks2, runMode, noInteract
|
|
|
10232
10395
|
process.on("SIGTERM", onSigterm);
|
|
10233
10396
|
try {
|
|
10234
10397
|
if (noInteractive || !process.stdout.isTTY) {
|
|
10235
|
-
await renderPlainPipeline(input, dryRun, enrichLinks2, runMode, links, unlinks, maxLinks, maxImages);
|
|
10398
|
+
const result = await renderPlainPipeline(input, dryRun, enrichLinks2, runMode, links, unlinks, maxLinks, maxImages);
|
|
10399
|
+
if (exportPath) {
|
|
10400
|
+
await runOutputCommand({
|
|
10401
|
+
generationId: result.artifact.slug,
|
|
10402
|
+
destinationPath: exportPath
|
|
10403
|
+
});
|
|
10404
|
+
}
|
|
10236
10405
|
return;
|
|
10237
10406
|
}
|
|
10238
10407
|
let commandError = null;
|
|
10408
|
+
let pipelineResult = null;
|
|
10239
10409
|
const app = render2(
|
|
10240
10410
|
/* @__PURE__ */ jsx7(
|
|
10241
10411
|
WriteApp,
|
|
@@ -10248,6 +10418,9 @@ async function runWritePipeline(input, dryRun, enrichLinks2, runMode, noInteract
|
|
|
10248
10418
|
unlinks,
|
|
10249
10419
|
maxLinks,
|
|
10250
10420
|
maxImages,
|
|
10421
|
+
onSuccess: (result) => {
|
|
10422
|
+
pipelineResult = result;
|
|
10423
|
+
},
|
|
10251
10424
|
onError: (error) => {
|
|
10252
10425
|
commandError = error;
|
|
10253
10426
|
}
|
|
@@ -10260,6 +10433,9 @@ async function runWritePipeline(input, dryRun, enrichLinks2, runMode, noInteract
|
|
|
10260
10433
|
if (finalError) {
|
|
10261
10434
|
throw new ReportedError(withWriteResumeHint(finalError.message));
|
|
10262
10435
|
}
|
|
10436
|
+
if (exportPath && pipelineResult) {
|
|
10437
|
+
await autoExport(exportPath, pipelineResult);
|
|
10438
|
+
}
|
|
10263
10439
|
} finally {
|
|
10264
10440
|
cleanupSignalHandlers();
|
|
10265
10441
|
}
|
|
@@ -10407,6 +10583,12 @@ async function promptForIdea() {
|
|
|
10407
10583
|
readline.close();
|
|
10408
10584
|
}
|
|
10409
10585
|
}
|
|
10586
|
+
async function autoExport(exportPath, result) {
|
|
10587
|
+
await runOutputCommand({
|
|
10588
|
+
generationId: result.artifact.slug,
|
|
10589
|
+
destinationPath: exportPath
|
|
10590
|
+
});
|
|
10591
|
+
}
|
|
10410
10592
|
|
|
10411
10593
|
// src/cli/app.ts
|
|
10412
10594
|
var { version } = package_default;
|
|
@@ -10476,7 +10658,7 @@ async function runCli(argv) {
|
|
|
10476
10658
|
watch: options.watch
|
|
10477
10659
|
});
|
|
10478
10660
|
});
|
|
10479
|
-
const writeCommand = program.command("write").description("Generate one primary content output plus optional secondary outputs from a prompt or job file.").argument("[idea]", "Natural-language idea for the generation run").option("-i, --idea <idea>", "Natural-language idea for the generation run").option("--audience <description>", "Optional natural-language audience description for shared-plan targeting").option("-j, --job <path>", "Path to a JSON job definition").option("--primary <type=count>", "Required primary output target (for example: article=1 or x-post=1)").option("--secondary <type=count>", "Secondary output target, repeatable (for example: x-thread=3, linkedin-post=2)", collectOptionValue).option("--style <style>", "Writing style (academic, analytical, authoritative, conversational, empathetic, friendly, journalistic, minimalist, persuasive, playful, professional, storytelling, technical)").option("--intent <intent>", "Content intent (announcement, case-study, cornerstone, counterargument, critique-review, deep-dive-analysis, how-to-guide, interview-q-and-a, listicle, opinion-piece, personal-essay, roundup-curation, tutorial)").option("--length <size>", "Target length: small, medium, large, or a positive integer word count").option("--no-interactive", "Fail instead of prompting for missing input in TTY mode").option("--dry-run", "Run the pipeline shell without external API calls", false).option("--enrich-links", "Run link enrichment after markdown generation", false).option("--link <pair>", 'Custom link "expression->url", repeatable', collectOptionValue).option("--unlink <expression>", "Remove a custom link by expression, repeatable", collectOptionValue).option("--max-links <n>", "Max number of generated links", (v) => Number.parseInt(v, 10)).option("--max-images <n>", "Max total images including cover (1=cover only, 2=cover+1 inline, 3=cover+2 inline)", (v) => Number.parseInt(v, 10)).action(async (ideaArg, options) => {
|
|
10661
|
+
const writeCommand = program.command("write").description("Generate one primary content output plus optional secondary outputs from a prompt or job file.").argument("[idea]", "Natural-language idea for the generation run").option("-i, --idea <idea>", "Natural-language idea for the generation run").option("--audience <description>", "Optional natural-language audience description for shared-plan targeting").option("-j, --job <path>", "Path to a JSON job definition").option("--primary <type=count>", "Required primary output target (for example: article=1 or x-post=1)").option("--secondary <type=count>", "Secondary output target, repeatable (for example: x-thread=3, linkedin-post=2)", collectOptionValue).option("--style <style>", "Writing style (academic, analytical, authoritative, conversational, empathetic, friendly, journalistic, minimalist, persuasive, playful, professional, storytelling, technical)").option("--intent <intent>", "Content intent (announcement, case-study, cornerstone, counterargument, critique-review, deep-dive-analysis, how-to-guide, interview-q-and-a, listicle, opinion-piece, personal-essay, roundup-curation, tutorial)").option("--length <size>", "Target length: small, medium, large, or a positive integer word count").option("--no-interactive", "Fail instead of prompting for missing input in TTY mode").option("--dry-run", "Run the pipeline shell without external API calls", false).option("--enrich-links", "Run link enrichment after markdown generation", false).option("--link <pair>", 'Custom link "expression->url", repeatable', collectOptionValue).option("--unlink <expression>", "Remove a custom link by expression, repeatable", collectOptionValue).option("--max-links <n>", "Max number of generated links", (v) => Number.parseInt(v, 10)).option("--max-images <n>", "Max total images including cover (1=cover only, 2=cover+1 inline, 3=cover+2 inline)", (v) => Number.parseInt(v, 10)).option("--export <path>", "Export the generated article to the given directory after writing").action(async (ideaArg, options) => {
|
|
10480
10662
|
await runWriteCommand({
|
|
10481
10663
|
idea: options.idea ?? ideaArg,
|
|
10482
10664
|
audience: options.audience,
|
|
@@ -10492,17 +10674,19 @@ async function runCli(argv) {
|
|
|
10492
10674
|
links: options.link,
|
|
10493
10675
|
unlinks: options.unlink,
|
|
10494
10676
|
maxLinks: options.maxLinks,
|
|
10495
|
-
maxImages: options.maxImages
|
|
10677
|
+
maxImages: options.maxImages,
|
|
10678
|
+
exportPath: options.export
|
|
10496
10679
|
});
|
|
10497
10680
|
});
|
|
10498
|
-
writeCommand.command("resume").description("Resume the last failed or interrupted write session.").option("--no-interactive", "Force plain non-interactive output even in TTY mode", false).option("--enrich-links", "Run link enrichment after markdown generation", false).option("--link <pair>", 'Custom link "expression->url", repeatable', collectOptionValue).option("--unlink <expression>", "Remove a custom link by expression, repeatable", collectOptionValue).option("--max-links <n>", "Max number of generated links", (v) => Number.parseInt(v, 10)).option("--max-images <n>", "Max total images including cover (1=cover only, 2=cover+1 inline, 3=cover+2 inline)", (v) => Number.parseInt(v, 10)).action(async (options) => {
|
|
10681
|
+
writeCommand.command("resume").description("Resume the last failed or interrupted write session.").option("--no-interactive", "Force plain non-interactive output even in TTY mode", false).option("--enrich-links", "Run link enrichment after markdown generation", false).option("--link <pair>", 'Custom link "expression->url", repeatable', collectOptionValue).option("--unlink <expression>", "Remove a custom link by expression, repeatable", collectOptionValue).option("--max-links <n>", "Max number of generated links", (v) => Number.parseInt(v, 10)).option("--max-images <n>", "Max total images including cover (1=cover only, 2=cover+1 inline, 3=cover+2 inline)", (v) => Number.parseInt(v, 10)).option("--export <path>", "Export the generated article to the given directory after writing").action(async (options) => {
|
|
10499
10682
|
await runWriteResumeCommand({
|
|
10500
10683
|
noInteractive: options.noInteractive,
|
|
10501
10684
|
enrichLinks: options.enrichLinks,
|
|
10502
10685
|
links: options.link,
|
|
10503
10686
|
unlinks: options.unlink,
|
|
10504
10687
|
maxLinks: options.maxLinks,
|
|
10505
|
-
maxImages: options.maxImages
|
|
10688
|
+
maxImages: options.maxImages,
|
|
10689
|
+
exportPath: options.export
|
|
10506
10690
|
});
|
|
10507
10691
|
});
|
|
10508
10692
|
await program.parseAsync(argv);
|