@telepat/ideon 0.1.24 → 0.1.25
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 +211 -120
- package/package.json +1 -1
package/dist/ideon.js
CHANGED
|
@@ -484,9 +484,9 @@ function resolveDefaultMaxLinks(targetLengthWords) {
|
|
|
484
484
|
}
|
|
485
485
|
function resolveDefaultInlineImageCount(targetLengthWords) {
|
|
486
486
|
const alias = resolveTargetLengthAlias(targetLengthWords);
|
|
487
|
-
if (alias === "small") return { min:
|
|
488
|
-
if (alias === "medium") return { min:
|
|
489
|
-
return { min:
|
|
487
|
+
if (alias === "small") return { min: 0, max: 1 };
|
|
488
|
+
if (alias === "medium") return { min: 1, max: 2 };
|
|
489
|
+
return { min: 2, max: 4 };
|
|
490
490
|
}
|
|
491
491
|
var contentTargetRoleValues = ["primary", "secondary"];
|
|
492
492
|
var contentTargetSchema = z2.object({
|
|
@@ -1423,7 +1423,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
1423
1423
|
// package.json
|
|
1424
1424
|
var package_default = {
|
|
1425
1425
|
name: "@telepat/ideon",
|
|
1426
|
-
version: "0.1.
|
|
1426
|
+
version: "0.1.25",
|
|
1427
1427
|
description: "CLI for generating rich articles and images from ideas.",
|
|
1428
1428
|
type: "module",
|
|
1429
1429
|
repository: {
|
|
@@ -1625,7 +1625,7 @@ function assertNoLegacyXMode(contentTargets, sourceLabel) {
|
|
|
1625
1625
|
// src/pipeline/runner.ts
|
|
1626
1626
|
import { mkdir as mkdir6, stat as stat2 } from "fs/promises";
|
|
1627
1627
|
import { randomUUID } from "crypto";
|
|
1628
|
-
import
|
|
1628
|
+
import path9 from "path";
|
|
1629
1629
|
|
|
1630
1630
|
// src/generation/enrichLinks.ts
|
|
1631
1631
|
import { readFile as readFile4 } from "fs/promises";
|
|
@@ -2380,7 +2380,7 @@ function buildLongFormPlanJsonSchema(targetLengthWords) {
|
|
|
2380
2380
|
required: ["description", "anchorAfterSection"],
|
|
2381
2381
|
properties: {
|
|
2382
2382
|
description: { type: "string" },
|
|
2383
|
-
anchorAfterSection: { type: "number", minimum:
|
|
2383
|
+
anchorAfterSection: { type: "number", minimum: 2 }
|
|
2384
2384
|
}
|
|
2385
2385
|
}
|
|
2386
2386
|
}
|
|
@@ -2446,7 +2446,7 @@ function buildLongFormPlanMessages(idea, options) {
|
|
|
2446
2446
|
"- Each section description should name the mechanism, evidence type, or practical action that makes the section useful.",
|
|
2447
2447
|
"- Sections are primary-only structure and must not be treated as requirements for non-primary channels.",
|
|
2448
2448
|
`- 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,
|
|
2449
|
+
"- Each inline image must specify which section it follows (anchorAfterSection, starting at 2). Choose sections where visual reinforcement adds the most value. Do not anchor inline images after the first section because the cover image already appears near the title.",
|
|
2450
2450
|
"- 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
2451
|
"- Image descriptions must be concrete and contextual, not generic stock-photo phrasing.",
|
|
2452
2452
|
"",
|
|
@@ -2468,7 +2468,7 @@ function buildLongFormPlanMessages(idea, options) {
|
|
|
2468
2468
|
"- outroBrief: string",
|
|
2469
2469
|
`- sections: array of ${sectionCounts.label} objects, each with title and description strings`,
|
|
2470
2470
|
"- coverImageDescription: string",
|
|
2471
|
-
`- inlineImages: array of ${imageCounts.min} to ${imageCounts.max} objects, each with a description string and an anchorAfterSection number (
|
|
2471
|
+
`- inlineImages: array of ${imageCounts.min} to ${imageCounts.max} objects, each with a description string and an anchorAfterSection number (starting at 2, since the cover image already appears near the title)`,
|
|
2472
2472
|
"",
|
|
2473
2473
|
"Do not omit any required fields. Return strict JSON only."
|
|
2474
2474
|
].join("\n")
|
|
@@ -2530,7 +2530,7 @@ var articleSectionPlanSchema = z5.object({
|
|
|
2530
2530
|
});
|
|
2531
2531
|
var inlineImagePlanSchema = z5.object({
|
|
2532
2532
|
description: z5.string().min(1),
|
|
2533
|
-
anchorAfterSection: z5.number().int().min(
|
|
2533
|
+
anchorAfterSection: z5.number().int().min(2)
|
|
2534
2534
|
});
|
|
2535
2535
|
var primaryPlanSchema = z5.object({
|
|
2536
2536
|
contentType: z5.string().min(1).default("article"),
|
|
@@ -2543,7 +2543,7 @@ var primaryPlanSchema = z5.object({
|
|
|
2543
2543
|
introBrief: z5.string().min(1).optional(),
|
|
2544
2544
|
outroBrief: z5.string().min(1).optional(),
|
|
2545
2545
|
sections: z5.array(articleSectionPlanSchema).min(2).max(10).optional(),
|
|
2546
|
-
inlineImages: z5.array(inlineImagePlanSchema).min(
|
|
2546
|
+
inlineImages: z5.array(inlineImagePlanSchema).min(0).max(4).optional(),
|
|
2547
2547
|
angle: z5.string().min(1).optional()
|
|
2548
2548
|
});
|
|
2549
2549
|
var longFormPlanSchema = z5.object({
|
|
@@ -2557,7 +2557,7 @@ var longFormPlanSchema = z5.object({
|
|
|
2557
2557
|
outroBrief: z5.string().min(1),
|
|
2558
2558
|
sections: z5.array(articleSectionPlanSchema).min(2).max(10),
|
|
2559
2559
|
coverImageDescription: z5.string().min(1),
|
|
2560
|
-
inlineImages: z5.array(inlineImagePlanSchema).min(
|
|
2560
|
+
inlineImages: z5.array(inlineImagePlanSchema).min(0).max(4)
|
|
2561
2561
|
});
|
|
2562
2562
|
var shortFormPlanSchema = z5.object({
|
|
2563
2563
|
contentType: z5.string().min(1),
|
|
@@ -2619,7 +2619,7 @@ async function planPrimaryContent({
|
|
|
2619
2619
|
keywords: longPlan.keywords.slice(0, 8),
|
|
2620
2620
|
inlineImages: longPlan.inlineImages.slice(0, 3).map((img) => ({
|
|
2621
2621
|
...img,
|
|
2622
|
-
anchorAfterSection: Math.max(
|
|
2622
|
+
anchorAfterSection: Math.max(2, Math.min(sectionCount, img.anchorAfterSection))
|
|
2623
2623
|
}))
|
|
2624
2624
|
};
|
|
2625
2625
|
}
|
|
@@ -3276,7 +3276,7 @@ function buildImageSlots(plan, sections, options) {
|
|
|
3276
3276
|
kind: "inline",
|
|
3277
3277
|
prompt: "",
|
|
3278
3278
|
description: img.description,
|
|
3279
|
-
anchorAfterSection: Math.max(
|
|
3279
|
+
anchorAfterSection: Math.max(2, Math.min(sectionCount, img.anchorAfterSection))
|
|
3280
3280
|
});
|
|
3281
3281
|
}
|
|
3282
3282
|
return slots;
|
|
@@ -4003,10 +4003,69 @@ ${body.join("\n").trim()}
|
|
|
4003
4003
|
`;
|
|
4004
4004
|
}
|
|
4005
4005
|
|
|
4006
|
+
// src/output/meta.ts
|
|
4007
|
+
import path7 from "path";
|
|
4008
|
+
function buildMetaJson(input) {
|
|
4009
|
+
const plan = input.plan;
|
|
4010
|
+
const contentPlan = input.contentPlan;
|
|
4011
|
+
const generationDir = input.generationDir;
|
|
4012
|
+
const title = plan?.title ?? contentPlan?.title ?? input.idea;
|
|
4013
|
+
const slug = plan?.slug ?? "";
|
|
4014
|
+
const description = plan?.description ?? contentPlan?.description ?? "";
|
|
4015
|
+
const subtitle = (plan && "subtitle" in plan ? plan.subtitle : null) ?? null;
|
|
4016
|
+
const keywords = (plan && "keywords" in plan ? plan.keywords : null) ?? [];
|
|
4017
|
+
const contentType = plan?.contentType ?? contentPlan?.primaryContentType ?? "article";
|
|
4018
|
+
const angle = plan?.angle ?? null;
|
|
4019
|
+
const coverImage = input.renderedImages.find((image) => image.kind === "cover") ?? null;
|
|
4020
|
+
const cover = coverImage ? {
|
|
4021
|
+
path: coverImage.outputPath,
|
|
4022
|
+
relativePath: coverImage.relativePath,
|
|
4023
|
+
description: coverImage.description
|
|
4024
|
+
} : null;
|
|
4025
|
+
const sections = plan?.sections?.map((section) => ({
|
|
4026
|
+
title: section.title,
|
|
4027
|
+
description: section.description
|
|
4028
|
+
})) ?? [];
|
|
4029
|
+
const images = input.renderedImages.map((image) => ({
|
|
4030
|
+
id: image.id,
|
|
4031
|
+
kind: image.kind,
|
|
4032
|
+
path: image.outputPath,
|
|
4033
|
+
relativePath: image.relativePath,
|
|
4034
|
+
description: image.description,
|
|
4035
|
+
anchorAfterSection: image.anchorAfterSection
|
|
4036
|
+
}));
|
|
4037
|
+
const outputs = input.outputs.map((output) => ({
|
|
4038
|
+
fileId: output.fileId,
|
|
4039
|
+
contentType: output.contentType,
|
|
4040
|
+
path: output.markdownPath,
|
|
4041
|
+
relativePath: path7.relative(generationDir, output.markdownPath)
|
|
4042
|
+
}));
|
|
4043
|
+
return {
|
|
4044
|
+
version: 1,
|
|
4045
|
+
title,
|
|
4046
|
+
slug,
|
|
4047
|
+
idea: input.idea,
|
|
4048
|
+
description,
|
|
4049
|
+
subtitle,
|
|
4050
|
+
keywords,
|
|
4051
|
+
contentType,
|
|
4052
|
+
style: input.style,
|
|
4053
|
+
intent: input.intent,
|
|
4054
|
+
targetLength: input.targetLength,
|
|
4055
|
+
angle,
|
|
4056
|
+
cover,
|
|
4057
|
+
sections,
|
|
4058
|
+
images,
|
|
4059
|
+
outputs,
|
|
4060
|
+
generatedAt: input.generatedAt,
|
|
4061
|
+
generationDir
|
|
4062
|
+
};
|
|
4063
|
+
}
|
|
4064
|
+
|
|
4006
4065
|
// src/pipeline/sessionStore.ts
|
|
4007
4066
|
import { createHash as createHash2 } from "crypto";
|
|
4008
4067
|
import { mkdir as mkdir5, readFile as readFile5, rm as rm2, writeFile as writeFile5 } from "fs/promises";
|
|
4009
|
-
import
|
|
4068
|
+
import path8 from "path";
|
|
4010
4069
|
import envPaths3 from "env-paths";
|
|
4011
4070
|
import { z as z6 } from "zod";
|
|
4012
4071
|
var STAGE_IDS = ["shared-plan", "planning", "sections", "image-prompts", "images", "output", "links"];
|
|
@@ -4049,7 +4108,8 @@ var pipelineArtifactSummarySchema = z6.object({
|
|
|
4049
4108
|
assetDir: z6.string().min(1),
|
|
4050
4109
|
analyticsPath: z6.string().min(1).default("unknown.analytics.json"),
|
|
4051
4110
|
interactionsPath: z6.string().min(1).default("unknown.interactions.json"),
|
|
4052
|
-
planPath: z6.string().min(1).nullable().default(null)
|
|
4111
|
+
planPath: z6.string().min(1).nullable().default(null),
|
|
4112
|
+
metaJsonPath: z6.string().min(1).default("meta.json")
|
|
4053
4113
|
});
|
|
4054
4114
|
var resolvedPathsSchema = z6.object({
|
|
4055
4115
|
markdownOutputDir: z6.string().min(1),
|
|
@@ -4085,18 +4145,18 @@ var writeSessionStateSchema = z6.object({
|
|
|
4085
4145
|
artifact: pipelineArtifactSummarySchema.nullable()
|
|
4086
4146
|
});
|
|
4087
4147
|
var ideonPaths3 = envPaths3("ideon", { suffix: "" });
|
|
4088
|
-
var sessionsDir =
|
|
4148
|
+
var sessionsDir = path8.join(ideonPaths3.config, "sessions");
|
|
4089
4149
|
function hashProjectPath(workingDir) {
|
|
4090
|
-
return createHash2("sha256").update(
|
|
4150
|
+
return createHash2("sha256").update(path8.resolve(workingDir)).digest("hex").slice(0, 16);
|
|
4091
4151
|
}
|
|
4092
4152
|
function resolveWriteRoot(workingDir) {
|
|
4093
|
-
return
|
|
4153
|
+
return path8.join(sessionsDir, hashProjectPath(workingDir));
|
|
4094
4154
|
}
|
|
4095
4155
|
function resolveStateFilePath(workingDir) {
|
|
4096
|
-
return
|
|
4156
|
+
return path8.join(resolveWriteRoot(workingDir), "state.json");
|
|
4097
4157
|
}
|
|
4098
4158
|
function resolveLegacyStatePath(workingDir) {
|
|
4099
|
-
return
|
|
4159
|
+
return path8.join(workingDir, ".ideon", "write", "state.json");
|
|
4100
4160
|
}
|
|
4101
4161
|
async function startFreshWriteSession(seed, workingDir = process.cwd()) {
|
|
4102
4162
|
const writeRoot = resolveWriteRoot(workingDir);
|
|
@@ -4157,7 +4217,7 @@ async function saveWriteSession(state, workingDir = process.cwd()) {
|
|
|
4157
4217
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4158
4218
|
});
|
|
4159
4219
|
const statePath = resolveStateFilePath(workingDir);
|
|
4160
|
-
await mkdir5(
|
|
4220
|
+
await mkdir5(path8.dirname(statePath), { recursive: true });
|
|
4161
4221
|
await writeFile5(statePath, `${JSON.stringify(next, null, 2)}
|
|
4162
4222
|
`, "utf8");
|
|
4163
4223
|
return next;
|
|
@@ -4716,12 +4776,12 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4716
4776
|
options.onUpdate?.(cloneStages(stages));
|
|
4717
4777
|
}
|
|
4718
4778
|
const baseSlug = plan?.slug ?? resolveGenerationSlug(input.idea, contentPlan?.title);
|
|
4719
|
-
const generationDir =
|
|
4779
|
+
const generationDir = path9.join(
|
|
4720
4780
|
writeSession.outputPaths.markdownOutputDir,
|
|
4721
4781
|
buildGenerationDirectoryName(baseSlug)
|
|
4722
4782
|
);
|
|
4723
4783
|
await mkdir6(generationDir, { recursive: true });
|
|
4724
|
-
const jobDefinitionPath =
|
|
4784
|
+
const jobDefinitionPath = path9.join(generationDir, "job.json");
|
|
4725
4785
|
await writeJsonFile(
|
|
4726
4786
|
jobDefinitionPath,
|
|
4727
4787
|
buildRunJobDefinition({
|
|
@@ -4733,12 +4793,12 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4733
4793
|
sourceJob: input.job
|
|
4734
4794
|
})
|
|
4735
4795
|
);
|
|
4736
|
-
const planPath = plan ?
|
|
4796
|
+
const planPath = plan ? path9.join(generationDir, "plan.md") : null;
|
|
4737
4797
|
if (plan && planPath) {
|
|
4738
4798
|
await writeUtf8File(planPath, renderPlanMarkdown(plan));
|
|
4739
4799
|
}
|
|
4740
4800
|
const primaryFilePrefix = toFilePrefix(primaryTarget.contentType);
|
|
4741
|
-
const primaryMarkdownPath =
|
|
4801
|
+
const primaryMarkdownPath = path9.join(generationDir, `${primaryFilePrefix}-1.md`);
|
|
4742
4802
|
const sharedAssetDir = generationDir;
|
|
4743
4803
|
if (isLongForm) {
|
|
4744
4804
|
const longPlan = plan;
|
|
@@ -4962,7 +5022,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4962
5022
|
})
|
|
4963
5023
|
};
|
|
4964
5024
|
options.onUpdate?.(cloneStages(stages));
|
|
4965
|
-
const markdownPath =
|
|
5025
|
+
const markdownPath = path9.join(generationDir, `${output.filePrefix}-${output.index}.md`);
|
|
4966
5026
|
try {
|
|
4967
5027
|
const content = await writeSingleShotContent({
|
|
4968
5028
|
idea: input.idea,
|
|
@@ -5025,7 +5085,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
5025
5085
|
...item,
|
|
5026
5086
|
status: "succeeded",
|
|
5027
5087
|
detail: "Saved markdown output.",
|
|
5028
|
-
summary:
|
|
5088
|
+
summary: path9.basename(markdownPath),
|
|
5029
5089
|
analytics: {
|
|
5030
5090
|
durationMs: itemDurationMs,
|
|
5031
5091
|
costUsd: knownItemCost.usd,
|
|
@@ -5240,10 +5300,24 @@ async function runPipelineShell(input, options = {}) {
|
|
|
5240
5300
|
llmCalls: llmInteractions,
|
|
5241
5301
|
t2iCalls: t2iInteractions
|
|
5242
5302
|
};
|
|
5243
|
-
const analyticsPath =
|
|
5244
|
-
const interactionsPath =
|
|
5303
|
+
const analyticsPath = path9.join(generationDir, "generation.analytics.json");
|
|
5304
|
+
const interactionsPath = path9.join(generationDir, "model.interactions.json");
|
|
5245
5305
|
await writeJsonFile(analyticsPath, analytics);
|
|
5246
5306
|
await writeJsonFile(interactionsPath, interactions);
|
|
5307
|
+
const metaJson = buildMetaJson({
|
|
5308
|
+
idea: input.idea,
|
|
5309
|
+
generationDir,
|
|
5310
|
+
contentPlan,
|
|
5311
|
+
plan,
|
|
5312
|
+
renderedImages: imageArtifacts?.renderedImages ?? [],
|
|
5313
|
+
outputs: generatedOutputs,
|
|
5314
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5315
|
+
style: input.config.settings.style,
|
|
5316
|
+
intent: input.config.settings.intent,
|
|
5317
|
+
targetLength: input.config.settings.targetLength ? resolveTargetLengthAlias(input.config.settings.targetLength) : null
|
|
5318
|
+
});
|
|
5319
|
+
const metaJsonPath = path9.join(generationDir, "meta.json");
|
|
5320
|
+
await writeJsonFile(metaJsonPath, metaJson);
|
|
5247
5321
|
const primaryMarkdownPathForArtifact = markdownPaths[0] ?? primaryMarkdownPath;
|
|
5248
5322
|
const artifact = {
|
|
5249
5323
|
title: plan?.title ?? contentPlan.title ?? deriveTitleFromIdea2(input.idea),
|
|
@@ -5257,7 +5331,8 @@ async function runPipelineShell(input, options = {}) {
|
|
|
5257
5331
|
assetDir: sharedAssetDir,
|
|
5258
5332
|
analyticsPath,
|
|
5259
5333
|
interactionsPath,
|
|
5260
|
-
planPath
|
|
5334
|
+
planPath,
|
|
5335
|
+
metaJsonPath
|
|
5261
5336
|
};
|
|
5262
5337
|
writeSession = await patchWriteSession(
|
|
5263
5338
|
{
|
|
@@ -5731,7 +5806,7 @@ function parsePipelineCustomLinks(rawLinks, unlinks) {
|
|
|
5731
5806
|
|
|
5732
5807
|
// src/cli/commands/links.ts
|
|
5733
5808
|
import { readFile as readFile6, stat as stat3 } from "fs/promises";
|
|
5734
|
-
import
|
|
5809
|
+
import path10 from "path";
|
|
5735
5810
|
async function runLinksCommand(options, dependencies = {}) {
|
|
5736
5811
|
const slug = normalizeSlug2(options.slug);
|
|
5737
5812
|
const mode = normalizeMode(options.mode);
|
|
@@ -5742,7 +5817,7 @@ async function runLinksCommand(options, dependencies = {}) {
|
|
|
5742
5817
|
});
|
|
5743
5818
|
const markdownPath = await resolveMarkdownPathForSlug2(slug, cwd2);
|
|
5744
5819
|
const frontmatter = await readFrontmatter(markdownPath);
|
|
5745
|
-
const fileId =
|
|
5820
|
+
const fileId = path10.parse(markdownPath).name;
|
|
5746
5821
|
const articleTitle = frontmatter.title ?? toTitleFromSlug(slug);
|
|
5747
5822
|
const articleDescription = frontmatter.description ?? "";
|
|
5748
5823
|
const openRouterApiKey = resolved.config.secrets.openRouterApiKey;
|
|
@@ -5812,14 +5887,14 @@ function normalizeSlug2(rawSlug) {
|
|
|
5812
5887
|
}
|
|
5813
5888
|
async function resolveMarkdownPathForSlug2(slug, cwd2) {
|
|
5814
5889
|
const outputPaths = resolveOutputPaths();
|
|
5815
|
-
const directPath =
|
|
5890
|
+
const directPath = path10.join(outputPaths.markdownOutputDir, `${slug}.md`);
|
|
5816
5891
|
if (await isReadableFile(directPath)) {
|
|
5817
5892
|
return directPath;
|
|
5818
5893
|
}
|
|
5819
5894
|
const markdownFiles = await listMarkdownFilesRecursively(outputPaths.markdownOutputDir);
|
|
5820
5895
|
const matches = [];
|
|
5821
5896
|
for (const candidate of markdownFiles) {
|
|
5822
|
-
if (
|
|
5897
|
+
if (path10.basename(candidate) === `${slug}.md`) {
|
|
5823
5898
|
matches.push(candidate);
|
|
5824
5899
|
continue;
|
|
5825
5900
|
}
|
|
@@ -5953,7 +6028,7 @@ function readErrorCode2(error) {
|
|
|
5953
6028
|
return typeof code === "string" ? code : null;
|
|
5954
6029
|
}
|
|
5955
6030
|
function formatRelativePath2(cwd2, targetPath) {
|
|
5956
|
-
const relativePath =
|
|
6031
|
+
const relativePath = path10.relative(cwd2, targetPath);
|
|
5957
6032
|
return relativePath.length > 0 ? relativePath : targetPath;
|
|
5958
6033
|
}
|
|
5959
6034
|
function logProgress(event, log) {
|
|
@@ -5993,7 +6068,7 @@ function resolveCustomLinks(existing, addRaw, removeExpressions) {
|
|
|
5993
6068
|
|
|
5994
6069
|
// src/cli/commands/export.ts
|
|
5995
6070
|
import { copyFile as copyFile2, mkdir as mkdir7, readFile as readFile8, stat as stat5, writeFile as writeFile6 } from "fs/promises";
|
|
5996
|
-
import
|
|
6071
|
+
import path12 from "path";
|
|
5997
6072
|
|
|
5998
6073
|
// src/output/enrichMarkdownWithLinks.ts
|
|
5999
6074
|
function enrichMarkdownWithLinks(markdown, links) {
|
|
@@ -6050,7 +6125,7 @@ function escapeRegExp(value2) {
|
|
|
6050
6125
|
|
|
6051
6126
|
// src/server/previewHelpers.ts
|
|
6052
6127
|
import { readdir, stat as stat4, readFile as readFile7 } from "fs/promises";
|
|
6053
|
-
import
|
|
6128
|
+
import path11 from "path";
|
|
6054
6129
|
var DEFAULT_PORT = 4173;
|
|
6055
6130
|
var CONTENT_TYPE_ORDER = ["article", "blog-post", "x-thread", "x-post", "linkedin-post", "reddit-post", "newsletter"];
|
|
6056
6131
|
var FILE_PREFIX_TO_CONTENT_TYPE = {
|
|
@@ -6108,8 +6183,8 @@ function extractHeadingTitle(markdown) {
|
|
|
6108
6183
|
}
|
|
6109
6184
|
async function resolveMarkdownPath(markdownPathArg, markdownOutputDir, cwd2) {
|
|
6110
6185
|
if (markdownPathArg) {
|
|
6111
|
-
const resolved =
|
|
6112
|
-
if (
|
|
6186
|
+
const resolved = path11.isAbsolute(markdownPathArg) ? markdownPathArg : path11.resolve(cwd2, markdownPathArg);
|
|
6187
|
+
if (path11.extname(resolved).toLowerCase() !== ".md") {
|
|
6113
6188
|
throw new Error(`Expected a markdown file (.md), received: ${resolved}`);
|
|
6114
6189
|
}
|
|
6115
6190
|
await assertFileExists(resolved, "Could not find markdown file");
|
|
@@ -6153,7 +6228,7 @@ function extractCoverImageUrl(markdown) {
|
|
|
6153
6228
|
async function extractArticleMetadata(markdownPath) {
|
|
6154
6229
|
const markdown = await readFile7(markdownPath, "utf8");
|
|
6155
6230
|
const fileStat = await stat4(markdownPath);
|
|
6156
|
-
const slug = extractFrontmatterSlug(markdown) ??
|
|
6231
|
+
const slug = extractFrontmatterSlug(markdown) ?? path11.basename(markdownPath, ".md");
|
|
6157
6232
|
const title = extractHeadingTitle(stripFrontmatter2(markdown)) ?? slug;
|
|
6158
6233
|
const body = stripFrontmatter2(markdown);
|
|
6159
6234
|
const previewSnippet = body.replace(/[#\[\]()!\-*_`]/g, "").trim().substring(0, 150);
|
|
@@ -6218,16 +6293,16 @@ async function listAllGenerations(markdownOutputDir) {
|
|
|
6218
6293
|
return generations;
|
|
6219
6294
|
}
|
|
6220
6295
|
function deriveGenerationId(markdownPath, markdownOutputDir) {
|
|
6221
|
-
const relative =
|
|
6222
|
-
const normalized = relative.split(
|
|
6296
|
+
const relative = path11.relative(markdownOutputDir, markdownPath);
|
|
6297
|
+
const normalized = relative.split(path11.sep).join("/");
|
|
6223
6298
|
if (!normalized || normalized.startsWith("../")) {
|
|
6224
|
-
return
|
|
6299
|
+
return path11.basename(markdownPath, ".md");
|
|
6225
6300
|
}
|
|
6226
6301
|
const segments = normalized.split("/").filter(Boolean);
|
|
6227
6302
|
if (segments.length <= 1) {
|
|
6228
|
-
return
|
|
6303
|
+
return path11.basename(markdownPath, ".md");
|
|
6229
6304
|
}
|
|
6230
|
-
return segments[0] ??
|
|
6305
|
+
return segments[0] ?? path11.basename(markdownPath, ".md");
|
|
6231
6306
|
}
|
|
6232
6307
|
async function findMarkdownFiles(markdownOutputDir) {
|
|
6233
6308
|
const files = [];
|
|
@@ -6244,7 +6319,7 @@ async function findMarkdownFiles(markdownOutputDir) {
|
|
|
6244
6319
|
continue;
|
|
6245
6320
|
}
|
|
6246
6321
|
for (const entry of entries) {
|
|
6247
|
-
const fullPath =
|
|
6322
|
+
const fullPath = path11.join(current, entry.name);
|
|
6248
6323
|
if (entry.isDirectory()) {
|
|
6249
6324
|
stack.push(fullPath);
|
|
6250
6325
|
continue;
|
|
@@ -6258,7 +6333,7 @@ async function findMarkdownFiles(markdownOutputDir) {
|
|
|
6258
6333
|
}
|
|
6259
6334
|
function deriveOutputIdentity(markdownPath, markdownOutputDir) {
|
|
6260
6335
|
const generationId = deriveGenerationId(markdownPath, markdownOutputDir);
|
|
6261
|
-
const fileBase =
|
|
6336
|
+
const fileBase = path11.basename(markdownPath, ".md");
|
|
6262
6337
|
const parsed = fileBase.match(/^([a-z0-9-]+)-(\d+)$/i);
|
|
6263
6338
|
if (!parsed || !parsed[1] || !parsed[2]) {
|
|
6264
6339
|
return {
|
|
@@ -6294,11 +6369,11 @@ function toContentTypeLabel(contentType) {
|
|
|
6294
6369
|
}
|
|
6295
6370
|
async function resolvePrimaryContentType(outputs) {
|
|
6296
6371
|
const fallback = outputs.find((output) => output.contentType === "article")?.contentType ?? outputs[0]?.contentType ?? "article";
|
|
6297
|
-
const generationDir =
|
|
6372
|
+
const generationDir = path11.dirname(outputs[0]?.sourcePath ?? "");
|
|
6298
6373
|
if (!generationDir) {
|
|
6299
6374
|
return fallback;
|
|
6300
6375
|
}
|
|
6301
|
-
const jobPath =
|
|
6376
|
+
const jobPath = path11.join(generationDir, "job.json");
|
|
6302
6377
|
try {
|
|
6303
6378
|
const raw = await readFile7(jobPath, "utf8");
|
|
6304
6379
|
const parsed = JSON.parse(raw);
|
|
@@ -6314,6 +6389,11 @@ async function resolvePrimaryContentType(outputs) {
|
|
|
6314
6389
|
}
|
|
6315
6390
|
|
|
6316
6391
|
// src/cli/commands/export.ts
|
|
6392
|
+
var INTERNAL_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
6393
|
+
"job.json",
|
|
6394
|
+
"model.interactions.json",
|
|
6395
|
+
"generation.analytics.json"
|
|
6396
|
+
]);
|
|
6317
6397
|
async function runOutputCommand(options, dependencies = {}) {
|
|
6318
6398
|
const cwd2 = dependencies.cwd ?? process.cwd();
|
|
6319
6399
|
const log = dependencies.log ?? ((message) => console.log(message));
|
|
@@ -6337,10 +6417,10 @@ async function runOutputCommand(options, dependencies = {}) {
|
|
|
6337
6417
|
}
|
|
6338
6418
|
const sourceMarkdownPath = articleOutput.sourcePath;
|
|
6339
6419
|
const sourceMarkdown = await readFile8(sourceMarkdownPath, "utf8");
|
|
6340
|
-
const slug = extractFrontmatterSlug2(sourceMarkdown) ??
|
|
6420
|
+
const slug = extractFrontmatterSlug2(sourceMarkdown) ?? path12.basename(sourceMarkdownPath, ".md");
|
|
6341
6421
|
const exportFilename = `${slug}.md`;
|
|
6342
6422
|
const destinationDir = await resolveDestinationDir(options.destinationPath, cwd2);
|
|
6343
|
-
const destinationFilePath =
|
|
6423
|
+
const destinationFilePath = path12.join(destinationDir, exportFilename);
|
|
6344
6424
|
if (!options.overwrite && await fileExists2(destinationFilePath)) {
|
|
6345
6425
|
throw new ReportedError(
|
|
6346
6426
|
`Export file already exists: ${destinationFilePath}. Pass --overwrite to replace it.`
|
|
@@ -6349,32 +6429,24 @@ async function runOutputCommand(options, dependencies = {}) {
|
|
|
6349
6429
|
await mkdir7(destinationDir, { recursive: true });
|
|
6350
6430
|
const links = await loadLinks(sourceMarkdownPath);
|
|
6351
6431
|
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);
|
|
6432
|
+
const sourceDir = path12.dirname(sourceMarkdownPath);
|
|
6433
|
+
const allFiles = await listFilesRecursively(sourceDir, () => true);
|
|
6434
|
+
const copiedFiles = [];
|
|
6435
|
+
for (const absoluteSrc of allFiles) {
|
|
6436
|
+
const basename = path12.basename(absoluteSrc);
|
|
6437
|
+
if (INTERNAL_FILE_NAMES.has(basename)) continue;
|
|
6438
|
+
if (path12.resolve(absoluteSrc) === path12.resolve(sourceMarkdownPath)) continue;
|
|
6439
|
+
const relativePath = path12.relative(sourceDir, absoluteSrc);
|
|
6440
|
+
const destPath = path12.join(destinationDir, relativePath);
|
|
6441
|
+
await mkdir7(path12.dirname(destPath), { recursive: true });
|
|
6442
|
+
await copyFile2(absoluteSrc, destPath);
|
|
6443
|
+
copiedFiles.push(relativePath);
|
|
6372
6444
|
}
|
|
6373
6445
|
await writeFile6(destinationFilePath, enrichedMarkdown, "utf8");
|
|
6374
|
-
const relDest =
|
|
6446
|
+
const relDest = path12.relative(cwd2, destinationFilePath);
|
|
6375
6447
|
log(`Exported "${generation.id}" (${generation.primaryContentType} #${targetIndex}) \u2192 ${relDest}`);
|
|
6376
|
-
if (
|
|
6377
|
-
log(`Copied ${
|
|
6448
|
+
if (copiedFiles.length > 0) {
|
|
6449
|
+
log(`Copied ${copiedFiles.length} file${copiedFiles.length === 1 ? "" : "s"}: ${copiedFiles.join(", ")}`);
|
|
6378
6450
|
}
|
|
6379
6451
|
if (links.length > 0) {
|
|
6380
6452
|
log(`Injected ${links.length} inline link${links.length === 1 ? "" : "s"}.`);
|
|
@@ -6396,7 +6468,7 @@ function resolveGeneration(generations, generationId) {
|
|
|
6396
6468
|
);
|
|
6397
6469
|
}
|
|
6398
6470
|
async function resolveDestinationDir(destinationPath, cwd2) {
|
|
6399
|
-
const resolved =
|
|
6471
|
+
const resolved = path12.isAbsolute(destinationPath) ? destinationPath : path12.resolve(cwd2, destinationPath);
|
|
6400
6472
|
return resolved;
|
|
6401
6473
|
}
|
|
6402
6474
|
async function fileExists2(filePath) {
|
|
@@ -6466,22 +6538,6 @@ function extractFrontmatterSlug2(markdown) {
|
|
|
6466
6538
|
const unquoted = rawSlug.replace(/^['""]|['""]$/g, "").trim();
|
|
6467
6539
|
return unquoted.length > 0 ? unquoted : null;
|
|
6468
6540
|
}
|
|
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
6541
|
|
|
6486
6542
|
// src/cli/commands/writeTargetSpecs.ts
|
|
6487
6543
|
function parseTargetSpec(spec) {
|
|
@@ -7343,7 +7399,7 @@ async function openSettings() {
|
|
|
7343
7399
|
}
|
|
7344
7400
|
|
|
7345
7401
|
// src/cli/commands/serve.ts
|
|
7346
|
-
import
|
|
7402
|
+
import path14 from "path";
|
|
7347
7403
|
import { spawn } from "child_process";
|
|
7348
7404
|
|
|
7349
7405
|
// src/server/previewServer.ts
|
|
@@ -7351,7 +7407,7 @@ import { execFile } from "child_process";
|
|
|
7351
7407
|
import { promisify } from "util";
|
|
7352
7408
|
import { readFile as readFile9, stat as stat6 } from "fs/promises";
|
|
7353
7409
|
import { watch as fsWatch } from "fs";
|
|
7354
|
-
import
|
|
7410
|
+
import path13 from "path";
|
|
7355
7411
|
import { fileURLToPath } from "url";
|
|
7356
7412
|
import express from "express";
|
|
7357
7413
|
import { marked } from "marked";
|
|
@@ -7449,7 +7505,7 @@ async function startPreviewServer(options) {
|
|
|
7449
7505
|
if (options.watch) {
|
|
7450
7506
|
let html2;
|
|
7451
7507
|
try {
|
|
7452
|
-
html2 = await readFile9(
|
|
7508
|
+
html2 = await readFile9(path13.join(previewClientDir, "index.html"), "utf8");
|
|
7453
7509
|
} catch {
|
|
7454
7510
|
res.status(200).type("html").send(
|
|
7455
7511
|
`<!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 +7516,7 @@ async function startPreviewServer(options) {
|
|
|
7460
7516
|
const injected = html2.replace("</body>", `${reloadScript}</body>`);
|
|
7461
7517
|
res.status(200).type("html").send(injected);
|
|
7462
7518
|
} else {
|
|
7463
|
-
res.status(200).sendFile(
|
|
7519
|
+
res.status(200).sendFile(path13.join(previewClientDir, "index.html"));
|
|
7464
7520
|
}
|
|
7465
7521
|
return;
|
|
7466
7522
|
}
|
|
@@ -7531,15 +7587,17 @@ async function getArticleContent(generationId, markdownOutputDir) {
|
|
|
7531
7587
|
};
|
|
7532
7588
|
})
|
|
7533
7589
|
);
|
|
7534
|
-
const generationDir =
|
|
7590
|
+
const generationDir = path13.dirname(generation.outputs[0]?.sourcePath ?? "");
|
|
7535
7591
|
const interactions = generationDir ? await loadSavedInteractions(generationDir) : { llmCalls: [], t2iCalls: [] };
|
|
7536
7592
|
const analyticsSummary = generationDir ? await loadSavedAnalyticsSummary(generationDir) : null;
|
|
7593
|
+
const metaJson = generationDir ? await loadSavedMetaJson(generationDir) : null;
|
|
7537
7594
|
return {
|
|
7538
7595
|
title: generation.title,
|
|
7539
7596
|
generationId: generation.id,
|
|
7540
7597
|
sourcePath,
|
|
7541
7598
|
interactions,
|
|
7542
7599
|
analyticsSummary,
|
|
7600
|
+
metaJson,
|
|
7543
7601
|
outputs
|
|
7544
7602
|
};
|
|
7545
7603
|
}
|
|
@@ -7560,7 +7618,7 @@ async function resolveActivePreviewArticle(preferredMarkdownPath, markdownOutput
|
|
|
7560
7618
|
};
|
|
7561
7619
|
}
|
|
7562
7620
|
function resolveGenerationSourcePath(generation, markdownOutputDir) {
|
|
7563
|
-
return generation.outputs.find((output) => output.contentType === generation.primaryContentType)?.sourcePath ?? generation.outputs[0]?.sourcePath ??
|
|
7621
|
+
return generation.outputs.find((output) => output.contentType === generation.primaryContentType)?.sourcePath ?? generation.outputs[0]?.sourcePath ?? path13.join(markdownOutputDir, generation.id);
|
|
7564
7622
|
}
|
|
7565
7623
|
function isMissingFileError(error) {
|
|
7566
7624
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
@@ -7599,7 +7657,7 @@ async function loadSavedLinks(markdownPath) {
|
|
|
7599
7657
|
}
|
|
7600
7658
|
}
|
|
7601
7659
|
async function loadSavedInteractions(generationDir) {
|
|
7602
|
-
const interactionsPath =
|
|
7660
|
+
const interactionsPath = path13.join(generationDir, "model.interactions.json");
|
|
7603
7661
|
try {
|
|
7604
7662
|
const raw = await readFile9(interactionsPath, "utf8");
|
|
7605
7663
|
const parsed = JSON.parse(raw);
|
|
@@ -7617,7 +7675,7 @@ async function loadSavedInteractions(generationDir) {
|
|
|
7617
7675
|
}
|
|
7618
7676
|
}
|
|
7619
7677
|
async function loadSavedAnalyticsSummary(generationDir) {
|
|
7620
|
-
const analyticsPath =
|
|
7678
|
+
const analyticsPath = path13.join(generationDir, "generation.analytics.json");
|
|
7621
7679
|
try {
|
|
7622
7680
|
const raw = await readFile9(analyticsPath, "utf8");
|
|
7623
7681
|
const parsed = JSON.parse(raw);
|
|
@@ -7640,6 +7698,15 @@ async function loadSavedAnalyticsSummary(generationDir) {
|
|
|
7640
7698
|
return null;
|
|
7641
7699
|
}
|
|
7642
7700
|
}
|
|
7701
|
+
async function loadSavedMetaJson(generationDir) {
|
|
7702
|
+
const metaJsonPath = path13.join(generationDir, "meta.json");
|
|
7703
|
+
try {
|
|
7704
|
+
const raw = await readFile9(metaJsonPath, "utf8");
|
|
7705
|
+
return JSON.parse(raw);
|
|
7706
|
+
} catch {
|
|
7707
|
+
return null;
|
|
7708
|
+
}
|
|
7709
|
+
}
|
|
7643
7710
|
async function getPreviewBootstrapData(preferredMarkdownPath, markdownOutputDir) {
|
|
7644
7711
|
const activeArticle = await resolveActivePreviewArticle(preferredMarkdownPath, markdownOutputDir);
|
|
7645
7712
|
const emptyStateMessage = activeArticle ? null : `No generated content found in ${markdownOutputDir}. Run ideon write "your idea" first.`;
|
|
@@ -7651,14 +7718,14 @@ async function getPreviewBootstrapData(preferredMarkdownPath, markdownOutputDir)
|
|
|
7651
7718
|
};
|
|
7652
7719
|
}
|
|
7653
7720
|
async function resolvePreviewClientBuildDir() {
|
|
7654
|
-
const currentDir =
|
|
7721
|
+
const currentDir = path13.dirname(fileURLToPath(import.meta.url));
|
|
7655
7722
|
const candidates = [
|
|
7656
|
-
|
|
7657
|
-
|
|
7723
|
+
path13.resolve(currentDir, "preview"),
|
|
7724
|
+
path13.resolve(currentDir, "../../dist/preview")
|
|
7658
7725
|
];
|
|
7659
7726
|
for (const candidate of candidates) {
|
|
7660
7727
|
try {
|
|
7661
|
-
const indexStat = await stat6(
|
|
7728
|
+
const indexStat = await stat6(path13.join(candidate, "index.html"));
|
|
7662
7729
|
if (indexStat.isFile()) {
|
|
7663
7730
|
return candidate;
|
|
7664
7731
|
}
|
|
@@ -7720,17 +7787,17 @@ async function resolveGenerationAssetPath(generationId, rawAssetPath, markdownOu
|
|
|
7720
7787
|
throw new MissingArticleError(`Generation "${generationId}" no longer exists.`);
|
|
7721
7788
|
}
|
|
7722
7789
|
const decodedAssetPath = decodeURIComponent(rawAssetPath);
|
|
7723
|
-
const normalizedRelative =
|
|
7724
|
-
if (normalizedRelative.length === 0 || normalizedRelative === "." || normalizedRelative.startsWith("../") || normalizedRelative.includes("/../") ||
|
|
7790
|
+
const normalizedRelative = path13.posix.normalize(decodedAssetPath.replace(/\\/g, "/"));
|
|
7791
|
+
if (normalizedRelative.length === 0 || normalizedRelative === "." || normalizedRelative.startsWith("../") || normalizedRelative.includes("/../") || path13.posix.isAbsolute(normalizedRelative)) {
|
|
7725
7792
|
throw new Error("Invalid generation asset path.");
|
|
7726
7793
|
}
|
|
7727
|
-
const generationDir =
|
|
7794
|
+
const generationDir = path13.dirname(generation.outputs[0]?.sourcePath ?? "");
|
|
7728
7795
|
if (!generationDir) {
|
|
7729
7796
|
throw new MissingArticleError(`Generation "${generationId}" has no source directory.`);
|
|
7730
7797
|
}
|
|
7731
|
-
const resolvedPath =
|
|
7732
|
-
const relativeToGeneration =
|
|
7733
|
-
if (relativeToGeneration.startsWith("..") ||
|
|
7798
|
+
const resolvedPath = path13.resolve(generationDir, normalizedRelative);
|
|
7799
|
+
const relativeToGeneration = path13.relative(generationDir, resolvedPath);
|
|
7800
|
+
if (relativeToGeneration.startsWith("..") || path13.isAbsolute(relativeToGeneration)) {
|
|
7734
7801
|
throw new Error("Invalid generation asset path.");
|
|
7735
7802
|
}
|
|
7736
7803
|
try {
|
|
@@ -9208,7 +9275,7 @@ async function runServeCommand(options) {
|
|
|
9208
9275
|
const markdownPath = await resolveMarkdownPath(options.markdownPath, outputPaths.markdownOutputDir, process.cwd());
|
|
9209
9276
|
const port = parsePort(options.port);
|
|
9210
9277
|
if (options.watch) {
|
|
9211
|
-
const viteBin =
|
|
9278
|
+
const viteBin = path14.resolve(process.cwd(), "node_modules", ".bin", "vite");
|
|
9212
9279
|
const viteProcess = spawn(viteBin, ["build", "--watch"], {
|
|
9213
9280
|
stdio: "inherit",
|
|
9214
9281
|
shell: process.platform === "win32"
|
|
@@ -9234,8 +9301,8 @@ async function runServeCommand(options) {
|
|
|
9234
9301
|
openBrowser: options.openBrowser,
|
|
9235
9302
|
watch: options.watch
|
|
9236
9303
|
});
|
|
9237
|
-
const relativeArticle =
|
|
9238
|
-
const relativeAssets =
|
|
9304
|
+
const relativeArticle = path14.relative(process.cwd(), markdownPath);
|
|
9305
|
+
const relativeAssets = path14.relative(process.cwd(), outputPaths.assetOutputDir);
|
|
9239
9306
|
console.log(`Previewing ${relativeArticle || markdownPath}`);
|
|
9240
9307
|
console.log(`Serving assets from ${relativeAssets || outputPaths.assetOutputDir}`);
|
|
9241
9308
|
console.log(`Open ${server.url}`);
|
|
@@ -9785,6 +9852,7 @@ async function renderPlainPipeline(input, dryRun, enrichLinks2, runMode, links,
|
|
|
9785
9852
|
title: result.artifact.title,
|
|
9786
9853
|
slug: result.artifact.slug
|
|
9787
9854
|
});
|
|
9855
|
+
return result;
|
|
9788
9856
|
} catch (error) {
|
|
9789
9857
|
const message = error instanceof Error ? withWriteResumeHint(error.message) : withWriteResumeHint("Pipeline failed.");
|
|
9790
9858
|
await notifyWriteFailed({
|
|
@@ -10101,6 +10169,7 @@ function WriteApp({
|
|
|
10101
10169
|
unlinks,
|
|
10102
10170
|
maxLinks,
|
|
10103
10171
|
maxImages,
|
|
10172
|
+
onSuccess,
|
|
10104
10173
|
onError
|
|
10105
10174
|
}) {
|
|
10106
10175
|
const { exit } = useApp3();
|
|
@@ -10136,6 +10205,7 @@ function WriteApp({
|
|
|
10136
10205
|
return;
|
|
10137
10206
|
}
|
|
10138
10207
|
setResult(runResult);
|
|
10208
|
+
onSuccess?.(runResult);
|
|
10139
10209
|
await notifyWriteSucceeded({
|
|
10140
10210
|
enabled: input.config.settings.notifications.enabled,
|
|
10141
10211
|
title: runResult.artifact.title,
|
|
@@ -10174,7 +10244,7 @@ function WriteApp({
|
|
|
10174
10244
|
}
|
|
10175
10245
|
async function runWriteCommand(options) {
|
|
10176
10246
|
const input = await resolveInputWithInteractiveIdeaFallback(options);
|
|
10177
|
-
await runWritePipeline(input, options.dryRun, options.enrichLinks, "fresh", options.noInteractive, options.links, options.unlinks, options.maxLinks, options.maxImages);
|
|
10247
|
+
await runWritePipeline(input, options.dryRun, options.enrichLinks, "fresh", options.noInteractive, options.links, options.unlinks, options.maxLinks, options.maxImages, options.exportPath);
|
|
10178
10248
|
}
|
|
10179
10249
|
async function runWriteResumeCommand(options = {}) {
|
|
10180
10250
|
const session = await loadWriteSession();
|
|
@@ -10196,9 +10266,9 @@ async function runWriteResumeCommand(options = {}) {
|
|
|
10196
10266
|
secrets: resolved.config.secrets
|
|
10197
10267
|
}
|
|
10198
10268
|
};
|
|
10199
|
-
await runWritePipeline(input, session.dryRun, options.enrichLinks ?? false, "resume", options.noInteractive ?? false, options.links, options.unlinks, options.maxLinks, options.maxImages);
|
|
10269
|
+
await runWritePipeline(input, session.dryRun, options.enrichLinks ?? false, "resume", options.noInteractive ?? false, options.links, options.unlinks, options.maxLinks, options.maxImages, options.exportPath);
|
|
10200
10270
|
}
|
|
10201
|
-
async function runWritePipeline(input, dryRun, enrichLinks2, runMode, noInteractive, links, unlinks, maxLinks, maxImages) {
|
|
10271
|
+
async function runWritePipeline(input, dryRun, enrichLinks2, runMode, noInteractive, links, unlinks, maxLinks, maxImages, exportPath) {
|
|
10202
10272
|
let interruptHandled = false;
|
|
10203
10273
|
const handleSignal = (signal) => {
|
|
10204
10274
|
if (interruptHandled) {
|
|
@@ -10232,10 +10302,17 @@ async function runWritePipeline(input, dryRun, enrichLinks2, runMode, noInteract
|
|
|
10232
10302
|
process.on("SIGTERM", onSigterm);
|
|
10233
10303
|
try {
|
|
10234
10304
|
if (noInteractive || !process.stdout.isTTY) {
|
|
10235
|
-
await renderPlainPipeline(input, dryRun, enrichLinks2, runMode, links, unlinks, maxLinks, maxImages);
|
|
10305
|
+
const result = await renderPlainPipeline(input, dryRun, enrichLinks2, runMode, links, unlinks, maxLinks, maxImages);
|
|
10306
|
+
if (exportPath) {
|
|
10307
|
+
await runOutputCommand({
|
|
10308
|
+
generationId: result.artifact.slug,
|
|
10309
|
+
destinationPath: exportPath
|
|
10310
|
+
});
|
|
10311
|
+
}
|
|
10236
10312
|
return;
|
|
10237
10313
|
}
|
|
10238
10314
|
let commandError = null;
|
|
10315
|
+
let pipelineResult = null;
|
|
10239
10316
|
const app = render2(
|
|
10240
10317
|
/* @__PURE__ */ jsx7(
|
|
10241
10318
|
WriteApp,
|
|
@@ -10248,6 +10325,9 @@ async function runWritePipeline(input, dryRun, enrichLinks2, runMode, noInteract
|
|
|
10248
10325
|
unlinks,
|
|
10249
10326
|
maxLinks,
|
|
10250
10327
|
maxImages,
|
|
10328
|
+
onSuccess: (result) => {
|
|
10329
|
+
pipelineResult = result;
|
|
10330
|
+
},
|
|
10251
10331
|
onError: (error) => {
|
|
10252
10332
|
commandError = error;
|
|
10253
10333
|
}
|
|
@@ -10260,6 +10340,9 @@ async function runWritePipeline(input, dryRun, enrichLinks2, runMode, noInteract
|
|
|
10260
10340
|
if (finalError) {
|
|
10261
10341
|
throw new ReportedError(withWriteResumeHint(finalError.message));
|
|
10262
10342
|
}
|
|
10343
|
+
if (exportPath && pipelineResult) {
|
|
10344
|
+
await autoExport(exportPath, pipelineResult);
|
|
10345
|
+
}
|
|
10263
10346
|
} finally {
|
|
10264
10347
|
cleanupSignalHandlers();
|
|
10265
10348
|
}
|
|
@@ -10407,6 +10490,12 @@ async function promptForIdea() {
|
|
|
10407
10490
|
readline.close();
|
|
10408
10491
|
}
|
|
10409
10492
|
}
|
|
10493
|
+
async function autoExport(exportPath, result) {
|
|
10494
|
+
await runOutputCommand({
|
|
10495
|
+
generationId: result.artifact.slug,
|
|
10496
|
+
destinationPath: exportPath
|
|
10497
|
+
});
|
|
10498
|
+
}
|
|
10410
10499
|
|
|
10411
10500
|
// src/cli/app.ts
|
|
10412
10501
|
var { version } = package_default;
|
|
@@ -10476,7 +10565,7 @@ async function runCli(argv) {
|
|
|
10476
10565
|
watch: options.watch
|
|
10477
10566
|
});
|
|
10478
10567
|
});
|
|
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) => {
|
|
10568
|
+
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
10569
|
await runWriteCommand({
|
|
10481
10570
|
idea: options.idea ?? ideaArg,
|
|
10482
10571
|
audience: options.audience,
|
|
@@ -10492,17 +10581,19 @@ async function runCli(argv) {
|
|
|
10492
10581
|
links: options.link,
|
|
10493
10582
|
unlinks: options.unlink,
|
|
10494
10583
|
maxLinks: options.maxLinks,
|
|
10495
|
-
maxImages: options.maxImages
|
|
10584
|
+
maxImages: options.maxImages,
|
|
10585
|
+
exportPath: options.export
|
|
10496
10586
|
});
|
|
10497
10587
|
});
|
|
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) => {
|
|
10588
|
+
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
10589
|
await runWriteResumeCommand({
|
|
10500
10590
|
noInteractive: options.noInteractive,
|
|
10501
10591
|
enrichLinks: options.enrichLinks,
|
|
10502
10592
|
links: options.link,
|
|
10503
10593
|
unlinks: options.unlink,
|
|
10504
10594
|
maxLinks: options.maxLinks,
|
|
10505
|
-
maxImages: options.maxImages
|
|
10595
|
+
maxImages: options.maxImages,
|
|
10596
|
+
exportPath: options.export
|
|
10506
10597
|
});
|
|
10507
10598
|
});
|
|
10508
10599
|
await program.parseAsync(argv);
|