@telepat/ideon 0.1.21 → 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 +1010 -643
- package/package.json +1 -1
package/dist/ideon.js
CHANGED
|
@@ -5,243 +5,29 @@ import { Command } from "commander";
|
|
|
5
5
|
|
|
6
6
|
// src/cli/commands/delete.ts
|
|
7
7
|
import { rm, stat } from "fs/promises";
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
// src/config/schema.ts
|
|
11
|
-
import { z } from "zod";
|
|
12
|
-
var contentTypeValues = [
|
|
13
|
-
"article",
|
|
14
|
-
"blog-post",
|
|
15
|
-
"linkedin-post",
|
|
16
|
-
"newsletter",
|
|
17
|
-
"press-release",
|
|
18
|
-
"reddit-post",
|
|
19
|
-
"science-paper",
|
|
20
|
-
"x-post",
|
|
21
|
-
"x-thread"
|
|
22
|
-
];
|
|
23
|
-
var writingStyleValues = [
|
|
24
|
-
"academic",
|
|
25
|
-
"analytical",
|
|
26
|
-
"authoritative",
|
|
27
|
-
"conversational",
|
|
28
|
-
"empathetic",
|
|
29
|
-
"friendly",
|
|
30
|
-
"journalistic",
|
|
31
|
-
"minimalist",
|
|
32
|
-
"persuasive",
|
|
33
|
-
"playful",
|
|
34
|
-
"professional",
|
|
35
|
-
"storytelling",
|
|
36
|
-
"technical"
|
|
37
|
-
];
|
|
38
|
-
var contentIntentValues = [
|
|
39
|
-
"announcement",
|
|
40
|
-
"case-study",
|
|
41
|
-
"cornerstone",
|
|
42
|
-
"counterargument",
|
|
43
|
-
"critique-review",
|
|
44
|
-
"deep-dive-analysis",
|
|
45
|
-
"how-to-guide",
|
|
46
|
-
"interview-q-and-a",
|
|
47
|
-
"listicle",
|
|
48
|
-
"opinion-piece",
|
|
49
|
-
"personal-essay",
|
|
50
|
-
"roundup-curation",
|
|
51
|
-
"tutorial"
|
|
52
|
-
];
|
|
53
|
-
var targetLengthValues = ["small", "medium", "large"];
|
|
54
|
-
var targetLengthAliasWordCounts = {
|
|
55
|
-
small: 500,
|
|
56
|
-
medium: 900,
|
|
57
|
-
large: 1400
|
|
58
|
-
};
|
|
59
|
-
var defaultTargetLengthWords = targetLengthAliasWordCounts.medium;
|
|
60
|
-
function parseTargetLengthWords(value2) {
|
|
61
|
-
if (typeof value2 === "number") {
|
|
62
|
-
return Number.isInteger(value2) && value2 > 0 ? value2 : void 0;
|
|
63
|
-
}
|
|
64
|
-
if (typeof value2 !== "string") {
|
|
65
|
-
return void 0;
|
|
66
|
-
}
|
|
67
|
-
const normalized = value2.trim().toLowerCase();
|
|
68
|
-
if (normalized.length === 0) {
|
|
69
|
-
return void 0;
|
|
70
|
-
}
|
|
71
|
-
if (targetLengthValues.includes(normalized)) {
|
|
72
|
-
return targetLengthAliasWordCounts[normalized];
|
|
73
|
-
}
|
|
74
|
-
if (!/^\d+$/.test(normalized)) {
|
|
75
|
-
return void 0;
|
|
76
|
-
}
|
|
77
|
-
const parsed = Number.parseInt(normalized, 10);
|
|
78
|
-
return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
79
|
-
}
|
|
80
|
-
var targetLengthWordsSchema = z.preprocess(
|
|
81
|
-
(value2) => parseTargetLengthWords(value2),
|
|
82
|
-
z.number().int().positive()
|
|
83
|
-
);
|
|
84
|
-
function resolveTargetLengthAlias(targetLengthWords) {
|
|
85
|
-
if (!Number.isFinite(targetLengthWords) || targetLengthWords <= 0) {
|
|
86
|
-
return "medium";
|
|
87
|
-
}
|
|
88
|
-
if (targetLengthWords <= 700) {
|
|
89
|
-
return "small";
|
|
90
|
-
}
|
|
91
|
-
if (targetLengthWords <= 1150) {
|
|
92
|
-
return "medium";
|
|
93
|
-
}
|
|
94
|
-
return "large";
|
|
95
|
-
}
|
|
96
|
-
function resolveDefaultMaxLinks(targetLengthWords) {
|
|
97
|
-
const alias = resolveTargetLengthAlias(targetLengthWords);
|
|
98
|
-
if (alias === "small") return 5;
|
|
99
|
-
if (alias === "medium") return 8;
|
|
100
|
-
return 12;
|
|
101
|
-
}
|
|
102
|
-
var contentTargetRoleValues = ["primary", "secondary"];
|
|
103
|
-
var contentTargetSchema = z.object({
|
|
104
|
-
contentType: z.enum(contentTypeValues),
|
|
105
|
-
role: z.enum(contentTargetRoleValues),
|
|
106
|
-
count: z.number().int().positive().default(1)
|
|
107
|
-
});
|
|
108
|
-
var modelSettingsSchema = z.object({
|
|
109
|
-
temperature: z.number().min(0).max(2).default(0.7),
|
|
110
|
-
maxTokens: z.number().int().positive().default(4e3),
|
|
111
|
-
topP: z.number().min(0).max(1).default(1)
|
|
112
|
-
});
|
|
113
|
-
var baseT2ISettingsSchema = z.object({
|
|
114
|
-
modelId: z.string().default("flux"),
|
|
115
|
-
inputOverrides: z.record(z.string(), z.unknown()).default({})
|
|
116
|
-
});
|
|
117
|
-
var notificationsSettingsSchema = z.object({
|
|
118
|
-
enabled: z.boolean().default(false)
|
|
119
|
-
});
|
|
120
|
-
var appSettingsSchema = z.object({
|
|
121
|
-
model: z.string().default("deepseek/deepseek-v4-pro"),
|
|
122
|
-
modelSettings: modelSettingsSchema.default(modelSettingsSchema.parse({})),
|
|
123
|
-
modelRequestTimeoutMs: z.number().int().positive().default(9e4),
|
|
124
|
-
t2i: baseT2ISettingsSchema.default(baseT2ISettingsSchema.parse({})),
|
|
125
|
-
notifications: notificationsSettingsSchema.default(notificationsSettingsSchema.parse({})),
|
|
126
|
-
markdownOutputDir: z.string().default("/output"),
|
|
127
|
-
assetOutputDir: z.string().default("/output/assets"),
|
|
128
|
-
contentTargets: z.array(contentTargetSchema).min(1).refine((targets) => targets.filter((target) => target.role === "primary").length === 1, {
|
|
129
|
-
message: "contentTargets must include exactly one primary target."
|
|
130
|
-
}).default([{ contentType: "article", role: "primary", count: 1 }]),
|
|
131
|
-
style: z.enum(writingStyleValues).default("professional"),
|
|
132
|
-
intent: z.enum(contentIntentValues).default("tutorial"),
|
|
133
|
-
targetLength: targetLengthWordsSchema.default(defaultTargetLengthWords)
|
|
134
|
-
});
|
|
135
|
-
var envSettingsSchema = z.object({
|
|
136
|
-
openRouterApiKey: z.string().optional(),
|
|
137
|
-
replicateApiToken: z.string().optional(),
|
|
138
|
-
disableKeytar: z.boolean().optional(),
|
|
139
|
-
model: z.string().optional(),
|
|
140
|
-
temperature: z.number().min(0).max(2).optional(),
|
|
141
|
-
maxTokens: z.number().int().positive().optional(),
|
|
142
|
-
topP: z.number().min(0).max(1).optional(),
|
|
143
|
-
modelRequestTimeoutMs: z.number().int().positive().optional(),
|
|
144
|
-
notificationsEnabled: z.boolean().optional(),
|
|
145
|
-
markdownOutputDir: z.string().optional(),
|
|
146
|
-
assetOutputDir: z.string().optional(),
|
|
147
|
-
style: z.enum(writingStyleValues).optional(),
|
|
148
|
-
intent: z.enum(contentIntentValues).optional(),
|
|
149
|
-
targetLength: targetLengthWordsSchema.optional()
|
|
150
|
-
});
|
|
151
|
-
var jobInputSchema = z.object({
|
|
152
|
-
idea: z.string().min(1).optional(),
|
|
153
|
-
prompt: z.string().min(1).optional(),
|
|
154
|
-
targetAudience: z.string().min(1).optional(),
|
|
155
|
-
settings: appSettingsSchema.partial().optional()
|
|
156
|
-
});
|
|
157
|
-
var defaultAppSettings = appSettingsSchema.parse({});
|
|
158
|
-
|
|
159
|
-
// src/config/env.ts
|
|
160
|
-
function parseNumber(value2) {
|
|
161
|
-
if (!value2) {
|
|
162
|
-
return void 0;
|
|
163
|
-
}
|
|
164
|
-
const parsed = Number(value2);
|
|
165
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
166
|
-
}
|
|
167
|
-
function parseBoolean(value2) {
|
|
168
|
-
if (!value2) {
|
|
169
|
-
return void 0;
|
|
170
|
-
}
|
|
171
|
-
const normalized = value2.trim().toLowerCase();
|
|
172
|
-
if (normalized === "true") {
|
|
173
|
-
return true;
|
|
174
|
-
}
|
|
175
|
-
if (normalized === "false") {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
return void 0;
|
|
179
|
-
}
|
|
180
|
-
function readEnvSettings(env = process.env) {
|
|
181
|
-
return envSettingsSchema.parse({
|
|
182
|
-
openRouterApiKey: env.IDEON_OPENROUTER_API_KEY,
|
|
183
|
-
replicateApiToken: env.IDEON_REPLICATE_API_TOKEN,
|
|
184
|
-
disableKeytar: parseBoolean(env.IDEON_DISABLE_KEYTAR),
|
|
185
|
-
model: env.IDEON_MODEL,
|
|
186
|
-
temperature: parseNumber(env.IDEON_TEMPERATURE),
|
|
187
|
-
maxTokens: parseNumber(env.IDEON_MAX_TOKENS),
|
|
188
|
-
topP: parseNumber(env.IDEON_TOP_P),
|
|
189
|
-
modelRequestTimeoutMs: parseNumber(env.IDEON_MODEL_REQUEST_TIMEOUT_MS),
|
|
190
|
-
notificationsEnabled: parseBoolean(env.IDEON_NOTIFICATIONS_ENABLED),
|
|
191
|
-
markdownOutputDir: env.IDEON_MARKDOWN_OUTPUT_DIR,
|
|
192
|
-
assetOutputDir: env.IDEON_ASSET_OUTPUT_DIR,
|
|
193
|
-
style: env.IDEON_STYLE,
|
|
194
|
-
intent: env.IDEON_INTENT,
|
|
195
|
-
targetLength: env.IDEON_TARGET_LENGTH
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// src/config/settingsFile.ts
|
|
200
|
-
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
201
|
-
import path from "path";
|
|
202
|
-
import envPaths from "env-paths";
|
|
203
|
-
var ideonPaths = envPaths("ideon", { suffix: "" });
|
|
204
|
-
var settingsDir = path.join(ideonPaths.config);
|
|
205
|
-
var settingsFilePath = path.join(settingsDir, "settings.json");
|
|
206
|
-
function getSettingsFilePath() {
|
|
207
|
-
return settingsFilePath;
|
|
208
|
-
}
|
|
209
|
-
async function loadSavedSettings() {
|
|
210
|
-
try {
|
|
211
|
-
const raw = await readFile(settingsFilePath, "utf8");
|
|
212
|
-
return appSettingsSchema.parse(JSON.parse(raw));
|
|
213
|
-
} catch (error) {
|
|
214
|
-
if (error.code === "ENOENT") {
|
|
215
|
-
return defaultAppSettings;
|
|
216
|
-
}
|
|
217
|
-
throw error;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
async function saveSettings(settings) {
|
|
221
|
-
await mkdir(settingsDir, { recursive: true });
|
|
222
|
-
await writeFile(settingsFilePath, `${JSON.stringify(settings, null, 2)}
|
|
223
|
-
`, "utf8");
|
|
224
|
-
}
|
|
8
|
+
import path2 from "path";
|
|
225
9
|
|
|
226
10
|
// src/output/filesystem.ts
|
|
227
|
-
import { access, mkdir
|
|
228
|
-
import
|
|
229
|
-
|
|
11
|
+
import { access, mkdir, writeFile } from "fs/promises";
|
|
12
|
+
import os from "os";
|
|
13
|
+
import path from "path";
|
|
14
|
+
function resolveOutputPaths() {
|
|
15
|
+
const base = path.join(os.homedir(), ".ideon", "output");
|
|
230
16
|
return {
|
|
231
|
-
markdownOutputDir:
|
|
232
|
-
assetOutputDir:
|
|
17
|
+
markdownOutputDir: base,
|
|
18
|
+
assetOutputDir: path.join(base, "assets")
|
|
233
19
|
};
|
|
234
20
|
}
|
|
235
21
|
async function ensureOutputDirectories(paths) {
|
|
236
22
|
await Promise.all([
|
|
237
|
-
|
|
238
|
-
|
|
23
|
+
mkdir(paths.markdownOutputDir, { recursive: true }),
|
|
24
|
+
mkdir(paths.assetOutputDir, { recursive: true })
|
|
239
25
|
]);
|
|
240
26
|
}
|
|
241
27
|
async function resolveUniqueSlug(markdownOutputDir, baseSlug) {
|
|
242
28
|
let attempt = 0;
|
|
243
29
|
let candidate = baseSlug;
|
|
244
|
-
while (await fileExists(
|
|
30
|
+
while (await fileExists(path.join(markdownOutputDir, `${candidate}.md`))) {
|
|
245
31
|
attempt += 1;
|
|
246
32
|
candidate = `${baseSlug}-${attempt}`;
|
|
247
33
|
}
|
|
@@ -279,7 +65,7 @@ async function listFilesRecursively(rootDir, predicate) {
|
|
|
279
65
|
continue;
|
|
280
66
|
}
|
|
281
67
|
for (const entry of entries) {
|
|
282
|
-
const fullPath =
|
|
68
|
+
const fullPath = path.join(current, entry.name);
|
|
283
69
|
if (entry.isDirectory()) {
|
|
284
70
|
stack.push(fullPath);
|
|
285
71
|
continue;
|
|
@@ -292,35 +78,26 @@ async function listFilesRecursively(rootDir, predicate) {
|
|
|
292
78
|
return results;
|
|
293
79
|
}
|
|
294
80
|
async function writeUtf8File(filePath, content) {
|
|
295
|
-
await
|
|
296
|
-
await
|
|
81
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
82
|
+
await writeFile(filePath, content, "utf8");
|
|
297
83
|
}
|
|
298
84
|
async function writeJsonFile(filePath, data) {
|
|
299
85
|
await writeUtf8File(filePath, `${JSON.stringify(data, null, 2)}
|
|
300
86
|
`);
|
|
301
87
|
}
|
|
302
88
|
function resolveLinksPath(markdownPath) {
|
|
303
|
-
const parsed =
|
|
304
|
-
return
|
|
89
|
+
const parsed = path.parse(markdownPath);
|
|
90
|
+
return path.join(parsed.dir, `${parsed.name}.links.json`);
|
|
305
91
|
}
|
|
306
92
|
async function writeLinksFile(markdownPath, links) {
|
|
307
93
|
await writeJsonFile(resolveLinksPath(markdownPath), links);
|
|
308
94
|
}
|
|
309
95
|
function resolveAnalyticsPath(markdownPath) {
|
|
310
|
-
const parsed =
|
|
311
|
-
return
|
|
96
|
+
const parsed = path.parse(markdownPath);
|
|
97
|
+
return path.join(parsed.dir, `${parsed.name}.analytics.json`);
|
|
312
98
|
}
|
|
313
99
|
function relativeAssetPath(markdownPath, assetPath) {
|
|
314
|
-
return
|
|
315
|
-
}
|
|
316
|
-
function resolveConfiguredDir(configuredPath, cwd2) {
|
|
317
|
-
if (configuredPath === "/output" || configuredPath.startsWith("/output/")) {
|
|
318
|
-
return path2.join(cwd2, configuredPath.slice(1));
|
|
319
|
-
}
|
|
320
|
-
if (path2.isAbsolute(configuredPath)) {
|
|
321
|
-
return configuredPath;
|
|
322
|
-
}
|
|
323
|
-
return path2.resolve(cwd2, configuredPath);
|
|
100
|
+
return path.relative(path.dirname(markdownPath), assetPath).split(path.sep).join("/");
|
|
324
101
|
}
|
|
325
102
|
async function fileExists(filePath) {
|
|
326
103
|
try {
|
|
@@ -372,24 +149,18 @@ async function runDeleteCommand(options, dependencies = {}) {
|
|
|
372
149
|
log(`Removed ${relativeMarkdown} and cleaned ${relativeAssetDir}.`);
|
|
373
150
|
}
|
|
374
151
|
async function resolveDeleteTargets(slug, cwd2) {
|
|
375
|
-
const
|
|
376
|
-
const mergedSettings = appSettingsSchema.parse({
|
|
377
|
-
...savedSettings,
|
|
378
|
-
...envSettings.markdownOutputDir ? { markdownOutputDir: envSettings.markdownOutputDir } : {},
|
|
379
|
-
...envSettings.assetOutputDir ? { assetOutputDir: envSettings.assetOutputDir } : {}
|
|
380
|
-
});
|
|
381
|
-
const outputPaths = resolveOutputPaths(mergedSettings, cwd2);
|
|
152
|
+
const outputPaths = resolveOutputPaths();
|
|
382
153
|
const markdownPath = await resolveMarkdownPathForSlug(outputPaths.markdownOutputDir, slug);
|
|
383
154
|
await assertMarkdownExists(markdownPath, slug);
|
|
384
155
|
return {
|
|
385
156
|
slug,
|
|
386
157
|
markdownPath,
|
|
387
158
|
analyticsPath: resolveAnalyticsPath(markdownPath),
|
|
388
|
-
assetDir:
|
|
159
|
+
assetDir: path2.dirname(markdownPath)
|
|
389
160
|
};
|
|
390
161
|
}
|
|
391
162
|
async function resolveMarkdownPathForSlug(markdownOutputDir, slug) {
|
|
392
|
-
const directPath =
|
|
163
|
+
const directPath = path2.join(markdownOutputDir, `${slug}.md`);
|
|
393
164
|
if (await pathExists(directPath)) {
|
|
394
165
|
return directPath;
|
|
395
166
|
}
|
|
@@ -424,7 +195,7 @@ async function findMarkdownCandidates(rootDir, fileName) {
|
|
|
424
195
|
continue;
|
|
425
196
|
}
|
|
426
197
|
for (const entry of entries) {
|
|
427
|
-
const fullPath =
|
|
198
|
+
const fullPath = path2.join(current, entry.name);
|
|
428
199
|
if (entry.isDirectory()) {
|
|
429
200
|
stack.push(fullPath);
|
|
430
201
|
continue;
|
|
@@ -503,7 +274,7 @@ function isNodeError(error) {
|
|
|
503
274
|
return error instanceof Error;
|
|
504
275
|
}
|
|
505
276
|
function formatRelativePath(cwd2, targetPath) {
|
|
506
|
-
const relativePath =
|
|
277
|
+
const relativePath = path2.relative(cwd2, targetPath);
|
|
507
278
|
return relativePath.length > 0 ? relativePath : targetPath;
|
|
508
279
|
}
|
|
509
280
|
async function directoryContainsMarkdown(dirPath) {
|
|
@@ -517,23 +288,23 @@ async function directoryContainsMarkdown(dirPath) {
|
|
|
517
288
|
}
|
|
518
289
|
|
|
519
290
|
// src/integrations/agent/store.ts
|
|
520
|
-
import { mkdir as
|
|
521
|
-
import
|
|
522
|
-
import
|
|
523
|
-
import { z
|
|
291
|
+
import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "fs/promises";
|
|
292
|
+
import path3 from "path";
|
|
293
|
+
import envPaths from "env-paths";
|
|
294
|
+
import { z } from "zod";
|
|
524
295
|
var supportedAgentRuntimeValues = ["claude", "claude-desktop", "chatgpt", "gemini", "codex", "cursor", "vscode", "opencode", "generic-mcp"];
|
|
525
|
-
var integrationEntrySchema =
|
|
526
|
-
runtime:
|
|
527
|
-
installedAt:
|
|
528
|
-
updatedAt:
|
|
296
|
+
var integrationEntrySchema = z.object({
|
|
297
|
+
runtime: z.enum(supportedAgentRuntimeValues),
|
|
298
|
+
installedAt: z.string(),
|
|
299
|
+
updatedAt: z.string()
|
|
529
300
|
});
|
|
530
|
-
var integrationStoreSchema =
|
|
531
|
-
version:
|
|
532
|
-
integrations:
|
|
301
|
+
var integrationStoreSchema = z.object({
|
|
302
|
+
version: z.literal(1),
|
|
303
|
+
integrations: z.record(z.string(), integrationEntrySchema).default({})
|
|
533
304
|
});
|
|
534
|
-
var
|
|
535
|
-
var storeDir =
|
|
536
|
-
var storePath =
|
|
305
|
+
var ideonPaths = envPaths("ideon", { suffix: "" });
|
|
306
|
+
var storeDir = ideonPaths.config;
|
|
307
|
+
var storePath = path3.join(storeDir, "agent-integrations.json");
|
|
537
308
|
function getAgentIntegrationStorePath() {
|
|
538
309
|
return storePath;
|
|
539
310
|
}
|
|
@@ -578,7 +349,7 @@ async function uninstallAgentIntegration(runtime, targetStorePath = storePath) {
|
|
|
578
349
|
}
|
|
579
350
|
async function readStore(targetStorePath) {
|
|
580
351
|
try {
|
|
581
|
-
const raw = await
|
|
352
|
+
const raw = await readFile(targetStorePath, "utf8");
|
|
582
353
|
return integrationStoreSchema.parse(JSON.parse(raw));
|
|
583
354
|
} catch (error) {
|
|
584
355
|
if (error.code === "ENOENT") {
|
|
@@ -588,8 +359,301 @@ async function readStore(targetStorePath) {
|
|
|
588
359
|
}
|
|
589
360
|
}
|
|
590
361
|
async function writeStore(store, targetStorePath) {
|
|
591
|
-
await
|
|
592
|
-
await
|
|
362
|
+
await mkdir2(path3.dirname(targetStorePath), { recursive: true });
|
|
363
|
+
await writeFile2(targetStorePath, `${JSON.stringify(store, null, 2)}
|
|
364
|
+
`, "utf8");
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// src/config/schema.ts
|
|
368
|
+
import { z as z2 } from "zod";
|
|
369
|
+
|
|
370
|
+
// src/images/limnModelCatalog.ts
|
|
371
|
+
import { getSupportedModelCatalog } from "@telepat/limn";
|
|
372
|
+
function getLimnGenerationModels() {
|
|
373
|
+
return getSupportedModelCatalog().filter((entry) => entry.generationEnabled);
|
|
374
|
+
}
|
|
375
|
+
var DEFAULT_LIMN_MODEL_ID = "flux";
|
|
376
|
+
function resolveFamilyFromReplicateModelId(replicateModelId) {
|
|
377
|
+
const match = getLimnGenerationModels().find((model) => model.replicateModelIds.includes(replicateModelId));
|
|
378
|
+
return match?.family ?? null;
|
|
379
|
+
}
|
|
380
|
+
function isKnownLimnFamily(family) {
|
|
381
|
+
return getLimnGenerationModels().some((model) => model.family === family);
|
|
382
|
+
}
|
|
383
|
+
function isKnownReplicateModelId(replicateModelId) {
|
|
384
|
+
return getLimnGenerationModels().some((model) => model.replicateModelIds.includes(replicateModelId));
|
|
385
|
+
}
|
|
386
|
+
function isReplicateModelIdForFamily(family, replicateModelId) {
|
|
387
|
+
const match = getLimnGenerationModels().find((model) => model.family === family);
|
|
388
|
+
if (!match) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
return match.replicateModelIds.includes(replicateModelId);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// src/config/schema.ts
|
|
395
|
+
var contentTypeValues = [
|
|
396
|
+
"article",
|
|
397
|
+
"blog-post",
|
|
398
|
+
"linkedin-post",
|
|
399
|
+
"newsletter",
|
|
400
|
+
"press-release",
|
|
401
|
+
"reddit-post",
|
|
402
|
+
"science-paper",
|
|
403
|
+
"x-post",
|
|
404
|
+
"x-thread"
|
|
405
|
+
];
|
|
406
|
+
var writingStyleValues = [
|
|
407
|
+
"academic",
|
|
408
|
+
"analytical",
|
|
409
|
+
"authoritative",
|
|
410
|
+
"conversational",
|
|
411
|
+
"empathetic",
|
|
412
|
+
"friendly",
|
|
413
|
+
"journalistic",
|
|
414
|
+
"minimalist",
|
|
415
|
+
"persuasive",
|
|
416
|
+
"playful",
|
|
417
|
+
"professional",
|
|
418
|
+
"storytelling",
|
|
419
|
+
"technical"
|
|
420
|
+
];
|
|
421
|
+
var contentIntentValues = [
|
|
422
|
+
"announcement",
|
|
423
|
+
"case-study",
|
|
424
|
+
"cornerstone",
|
|
425
|
+
"counterargument",
|
|
426
|
+
"critique-review",
|
|
427
|
+
"deep-dive-analysis",
|
|
428
|
+
"how-to-guide",
|
|
429
|
+
"interview-q-and-a",
|
|
430
|
+
"listicle",
|
|
431
|
+
"opinion-piece",
|
|
432
|
+
"personal-essay",
|
|
433
|
+
"roundup-curation",
|
|
434
|
+
"tutorial"
|
|
435
|
+
];
|
|
436
|
+
var targetLengthValues = ["small", "medium", "large"];
|
|
437
|
+
var targetLengthAliasWordCounts = {
|
|
438
|
+
small: 500,
|
|
439
|
+
medium: 900,
|
|
440
|
+
large: 1400
|
|
441
|
+
};
|
|
442
|
+
var defaultTargetLengthWords = targetLengthAliasWordCounts.medium;
|
|
443
|
+
function parseTargetLengthWords(value2) {
|
|
444
|
+
if (typeof value2 === "number") {
|
|
445
|
+
return Number.isInteger(value2) && value2 > 0 ? value2 : void 0;
|
|
446
|
+
}
|
|
447
|
+
if (typeof value2 !== "string") {
|
|
448
|
+
return void 0;
|
|
449
|
+
}
|
|
450
|
+
const normalized = value2.trim().toLowerCase();
|
|
451
|
+
if (normalized.length === 0) {
|
|
452
|
+
return void 0;
|
|
453
|
+
}
|
|
454
|
+
if (targetLengthValues.includes(normalized)) {
|
|
455
|
+
return targetLengthAliasWordCounts[normalized];
|
|
456
|
+
}
|
|
457
|
+
if (!/^\d+$/.test(normalized)) {
|
|
458
|
+
return void 0;
|
|
459
|
+
}
|
|
460
|
+
const parsed = Number.parseInt(normalized, 10);
|
|
461
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
462
|
+
}
|
|
463
|
+
var targetLengthWordsSchema = z2.preprocess(
|
|
464
|
+
(value2) => parseTargetLengthWords(value2),
|
|
465
|
+
z2.number().int().positive()
|
|
466
|
+
);
|
|
467
|
+
function resolveTargetLengthAlias(targetLengthWords) {
|
|
468
|
+
if (!Number.isFinite(targetLengthWords) || targetLengthWords <= 0) {
|
|
469
|
+
return "medium";
|
|
470
|
+
}
|
|
471
|
+
if (targetLengthWords <= 700) {
|
|
472
|
+
return "small";
|
|
473
|
+
}
|
|
474
|
+
if (targetLengthWords <= 1150) {
|
|
475
|
+
return "medium";
|
|
476
|
+
}
|
|
477
|
+
return "large";
|
|
478
|
+
}
|
|
479
|
+
function resolveDefaultMaxLinks(targetLengthWords) {
|
|
480
|
+
const alias = resolveTargetLengthAlias(targetLengthWords);
|
|
481
|
+
if (alias === "small") return 5;
|
|
482
|
+
if (alias === "medium") return 8;
|
|
483
|
+
return 12;
|
|
484
|
+
}
|
|
485
|
+
function resolveDefaultInlineImageCount(targetLengthWords) {
|
|
486
|
+
const alias = resolveTargetLengthAlias(targetLengthWords);
|
|
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
|
+
}
|
|
491
|
+
var contentTargetRoleValues = ["primary", "secondary"];
|
|
492
|
+
var contentTargetSchema = z2.object({
|
|
493
|
+
contentType: z2.enum(contentTypeValues),
|
|
494
|
+
role: z2.enum(contentTargetRoleValues),
|
|
495
|
+
count: z2.number().int().positive().default(1)
|
|
496
|
+
});
|
|
497
|
+
var modelSettingsSchema = z2.object({
|
|
498
|
+
temperature: z2.number().min(0).max(2).default(0.7),
|
|
499
|
+
maxTokens: z2.number().int().positive().default(4e3),
|
|
500
|
+
topP: z2.number().min(0).max(1).default(1)
|
|
501
|
+
});
|
|
502
|
+
function normalizeT2ISettings(value2) {
|
|
503
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) {
|
|
504
|
+
return value2;
|
|
505
|
+
}
|
|
506
|
+
const raw = value2;
|
|
507
|
+
const rawModelId = typeof raw.modelId === "string" ? raw.modelId.trim() : "";
|
|
508
|
+
const rawReplicateModelId = typeof raw.replicateModelId === "string" ? raw.replicateModelId.trim() : "";
|
|
509
|
+
let modelId = rawModelId || DEFAULT_LIMN_MODEL_ID;
|
|
510
|
+
let replicateModelId = rawReplicateModelId || void 0;
|
|
511
|
+
if (isKnownLimnFamily(modelId)) {
|
|
512
|
+
if (replicateModelId && !isReplicateModelIdForFamily(modelId, replicateModelId)) {
|
|
513
|
+
replicateModelId = void 0;
|
|
514
|
+
}
|
|
515
|
+
return {
|
|
516
|
+
...raw,
|
|
517
|
+
modelId,
|
|
518
|
+
replicateModelId
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
if (isKnownReplicateModelId(modelId)) {
|
|
522
|
+
const derivedFamily = resolveFamilyFromReplicateModelId(modelId);
|
|
523
|
+
if (derivedFamily) {
|
|
524
|
+
modelId = derivedFamily;
|
|
525
|
+
replicateModelId = replicateModelId && isReplicateModelIdForFamily(modelId, replicateModelId) ? replicateModelId : rawModelId;
|
|
526
|
+
return {
|
|
527
|
+
...raw,
|
|
528
|
+
modelId,
|
|
529
|
+
replicateModelId
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (replicateModelId && isKnownReplicateModelId(replicateModelId)) {
|
|
534
|
+
const derivedFamily = resolveFamilyFromReplicateModelId(replicateModelId);
|
|
535
|
+
if (derivedFamily) {
|
|
536
|
+
return {
|
|
537
|
+
...raw,
|
|
538
|
+
modelId: derivedFamily,
|
|
539
|
+
replicateModelId
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return {
|
|
544
|
+
...raw,
|
|
545
|
+
modelId: DEFAULT_LIMN_MODEL_ID,
|
|
546
|
+
replicateModelId: void 0
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
var baseT2ISettingsSchema = z2.preprocess(
|
|
550
|
+
normalizeT2ISettings,
|
|
551
|
+
z2.object({
|
|
552
|
+
modelId: z2.string().default(DEFAULT_LIMN_MODEL_ID),
|
|
553
|
+
replicateModelId: z2.string().optional(),
|
|
554
|
+
inputOverrides: z2.record(z2.string(), z2.unknown()).default({})
|
|
555
|
+
})
|
|
556
|
+
);
|
|
557
|
+
var notificationsSettingsSchema = z2.object({
|
|
558
|
+
enabled: z2.boolean().default(false)
|
|
559
|
+
});
|
|
560
|
+
var appSettingsSchema = z2.object({
|
|
561
|
+
model: z2.string().default("deepseek/deepseek-v4-pro"),
|
|
562
|
+
modelSettings: modelSettingsSchema.default(modelSettingsSchema.parse({})),
|
|
563
|
+
modelRequestTimeoutMs: z2.number().int().positive().default(9e4),
|
|
564
|
+
t2i: baseT2ISettingsSchema.default(baseT2ISettingsSchema.parse({})),
|
|
565
|
+
notifications: notificationsSettingsSchema.default(notificationsSettingsSchema.parse({})),
|
|
566
|
+
contentTargets: z2.array(contentTargetSchema).min(1).refine((targets) => targets.filter((target) => target.role === "primary").length === 1, {
|
|
567
|
+
message: "contentTargets must include exactly one primary target."
|
|
568
|
+
}).default([{ contentType: "article", role: "primary", count: 1 }]),
|
|
569
|
+
style: z2.enum(writingStyleValues).default("professional"),
|
|
570
|
+
intent: z2.enum(contentIntentValues).default("tutorial"),
|
|
571
|
+
targetLength: targetLengthWordsSchema.default(defaultTargetLengthWords)
|
|
572
|
+
});
|
|
573
|
+
var envSettingsSchema = z2.object({
|
|
574
|
+
openRouterApiKey: z2.string().optional(),
|
|
575
|
+
replicateApiToken: z2.string().optional(),
|
|
576
|
+
disableKeytar: z2.boolean().optional(),
|
|
577
|
+
model: z2.string().optional(),
|
|
578
|
+
temperature: z2.number().min(0).max(2).optional(),
|
|
579
|
+
maxTokens: z2.number().int().positive().optional(),
|
|
580
|
+
topP: z2.number().min(0).max(1).optional(),
|
|
581
|
+
modelRequestTimeoutMs: z2.number().int().positive().optional(),
|
|
582
|
+
notificationsEnabled: z2.boolean().optional(),
|
|
583
|
+
style: z2.enum(writingStyleValues).optional(),
|
|
584
|
+
intent: z2.enum(contentIntentValues).optional(),
|
|
585
|
+
targetLength: targetLengthWordsSchema.optional()
|
|
586
|
+
});
|
|
587
|
+
var jobInputSchema = z2.object({
|
|
588
|
+
idea: z2.string().min(1).optional(),
|
|
589
|
+
prompt: z2.string().min(1).optional(),
|
|
590
|
+
targetAudience: z2.string().min(1).optional(),
|
|
591
|
+
settings: appSettingsSchema.partial().optional()
|
|
592
|
+
});
|
|
593
|
+
var defaultAppSettings = appSettingsSchema.parse({});
|
|
594
|
+
|
|
595
|
+
// src/config/env.ts
|
|
596
|
+
function parseNumber(value2) {
|
|
597
|
+
if (!value2) {
|
|
598
|
+
return void 0;
|
|
599
|
+
}
|
|
600
|
+
const parsed = Number(value2);
|
|
601
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
602
|
+
}
|
|
603
|
+
function parseBoolean(value2) {
|
|
604
|
+
if (!value2) {
|
|
605
|
+
return void 0;
|
|
606
|
+
}
|
|
607
|
+
const normalized = value2.trim().toLowerCase();
|
|
608
|
+
if (normalized === "true") {
|
|
609
|
+
return true;
|
|
610
|
+
}
|
|
611
|
+
if (normalized === "false") {
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
return void 0;
|
|
615
|
+
}
|
|
616
|
+
function readEnvSettings(env = process.env) {
|
|
617
|
+
return envSettingsSchema.parse({
|
|
618
|
+
openRouterApiKey: env.IDEON_OPENROUTER_API_KEY,
|
|
619
|
+
replicateApiToken: env.IDEON_REPLICATE_API_TOKEN,
|
|
620
|
+
disableKeytar: parseBoolean(env.IDEON_DISABLE_KEYTAR),
|
|
621
|
+
model: env.IDEON_MODEL,
|
|
622
|
+
temperature: parseNumber(env.IDEON_TEMPERATURE),
|
|
623
|
+
maxTokens: parseNumber(env.IDEON_MAX_TOKENS),
|
|
624
|
+
topP: parseNumber(env.IDEON_TOP_P),
|
|
625
|
+
modelRequestTimeoutMs: parseNumber(env.IDEON_MODEL_REQUEST_TIMEOUT_MS),
|
|
626
|
+
notificationsEnabled: parseBoolean(env.IDEON_NOTIFICATIONS_ENABLED),
|
|
627
|
+
style: env.IDEON_STYLE,
|
|
628
|
+
intent: env.IDEON_INTENT,
|
|
629
|
+
targetLength: env.IDEON_TARGET_LENGTH
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// src/config/settingsFile.ts
|
|
634
|
+
import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
|
|
635
|
+
import path4 from "path";
|
|
636
|
+
import envPaths2 from "env-paths";
|
|
637
|
+
var ideonPaths2 = envPaths2("ideon", { suffix: "" });
|
|
638
|
+
var settingsDir = path4.join(ideonPaths2.config);
|
|
639
|
+
var settingsFilePath = path4.join(settingsDir, "settings.json");
|
|
640
|
+
function getSettingsFilePath() {
|
|
641
|
+
return settingsFilePath;
|
|
642
|
+
}
|
|
643
|
+
async function loadSavedSettings() {
|
|
644
|
+
try {
|
|
645
|
+
const raw = await readFile2(settingsFilePath, "utf8");
|
|
646
|
+
return appSettingsSchema.parse(JSON.parse(raw));
|
|
647
|
+
} catch (error) {
|
|
648
|
+
if (error.code === "ENOENT") {
|
|
649
|
+
return defaultAppSettings;
|
|
650
|
+
}
|
|
651
|
+
throw error;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
async function saveSettings(settings) {
|
|
655
|
+
await mkdir3(settingsDir, { recursive: true });
|
|
656
|
+
await writeFile3(settingsFilePath, `${JSON.stringify(settings, null, 2)}
|
|
593
657
|
`, "utf8");
|
|
594
658
|
}
|
|
595
659
|
|
|
@@ -746,8 +810,7 @@ var configSettingKeys = [
|
|
|
746
810
|
"modelSettings.topP",
|
|
747
811
|
"modelRequestTimeoutMs",
|
|
748
812
|
"notifications.enabled",
|
|
749
|
-
"
|
|
750
|
-
"assetOutputDir",
|
|
813
|
+
"t2i.replicateModelId",
|
|
751
814
|
"style",
|
|
752
815
|
"intent",
|
|
753
816
|
"targetLength"
|
|
@@ -775,8 +838,7 @@ async function configList() {
|
|
|
775
838
|
"modelSettings.topP": settings.modelSettings.topP,
|
|
776
839
|
modelRequestTimeoutMs: settings.modelRequestTimeoutMs,
|
|
777
840
|
"notifications.enabled": settings.notifications.enabled,
|
|
778
|
-
|
|
779
|
-
assetOutputDir: settings.assetOutputDir,
|
|
841
|
+
"t2i.replicateModelId": settings.t2i.replicateModelId,
|
|
780
842
|
style: settings.style,
|
|
781
843
|
intent: settings.intent,
|
|
782
844
|
targetLength: settings.targetLength
|
|
@@ -837,12 +899,15 @@ function coerceSettingValue(key, rawValue) {
|
|
|
837
899
|
const trimmed = rawValue.trim();
|
|
838
900
|
switch (key) {
|
|
839
901
|
case "model":
|
|
840
|
-
case "markdownOutputDir":
|
|
841
|
-
case "assetOutputDir": {
|
|
842
902
|
if (trimmed.length === 0) {
|
|
843
903
|
throw new Error(`${key} cannot be empty.`);
|
|
844
904
|
}
|
|
845
905
|
return trimmed;
|
|
906
|
+
case "t2i.replicateModelId": {
|
|
907
|
+
if (trimmed.length === 0) {
|
|
908
|
+
throw new Error("t2i.replicateModelId cannot be empty. Use config unset to clear it.");
|
|
909
|
+
}
|
|
910
|
+
return trimmed;
|
|
846
911
|
}
|
|
847
912
|
case "modelSettings.temperature": {
|
|
848
913
|
const parsed = Number(trimmed);
|
|
@@ -916,10 +981,8 @@ function getSettingValue(settings, key) {
|
|
|
916
981
|
return settings.modelRequestTimeoutMs;
|
|
917
982
|
case "notifications.enabled":
|
|
918
983
|
return settings.notifications.enabled;
|
|
919
|
-
case "
|
|
920
|
-
return settings.
|
|
921
|
-
case "assetOutputDir":
|
|
922
|
-
return settings.assetOutputDir;
|
|
984
|
+
case "t2i.replicateModelId":
|
|
985
|
+
return settings.t2i.replicateModelId;
|
|
923
986
|
case "style":
|
|
924
987
|
return settings.style;
|
|
925
988
|
case "intent":
|
|
@@ -944,10 +1007,8 @@ function setSettingValue(settings, key, value2) {
|
|
|
944
1007
|
return { ...settings, modelRequestTimeoutMs: value2 };
|
|
945
1008
|
case "notifications.enabled":
|
|
946
1009
|
return { ...settings, notifications: { ...settings.notifications, enabled: value2 } };
|
|
947
|
-
case "
|
|
948
|
-
return { ...settings,
|
|
949
|
-
case "assetOutputDir":
|
|
950
|
-
return { ...settings, assetOutputDir: value2 };
|
|
1010
|
+
case "t2i.replicateModelId":
|
|
1011
|
+
return { ...settings, t2i: { ...settings.t2i, replicateModelId: value2 } };
|
|
951
1012
|
case "style":
|
|
952
1013
|
return { ...settings, style: value2 };
|
|
953
1014
|
case "intent":
|
|
@@ -1362,7 +1423,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
1362
1423
|
// package.json
|
|
1363
1424
|
var package_default = {
|
|
1364
1425
|
name: "@telepat/ideon",
|
|
1365
|
-
version: "0.1.
|
|
1426
|
+
version: "0.1.25",
|
|
1366
1427
|
description: "CLI for generating rich articles and images from ideas.",
|
|
1367
1428
|
type: "module",
|
|
1368
1429
|
repository: {
|
|
@@ -1500,8 +1561,6 @@ async function resolveRunInput(input) {
|
|
|
1500
1561
|
...envSettings.topP !== void 0 ? { topP: envSettings.topP } : {}
|
|
1501
1562
|
}
|
|
1502
1563
|
} : {},
|
|
1503
|
-
...envSettings.markdownOutputDir ? { markdownOutputDir: envSettings.markdownOutputDir } : {},
|
|
1504
|
-
...envSettings.assetOutputDir ? { assetOutputDir: envSettings.assetOutputDir } : {},
|
|
1505
1564
|
...envSettings.style ? { style: envSettings.style } : {},
|
|
1506
1565
|
...envSettings.intent ? { intent: envSettings.intent } : {},
|
|
1507
1566
|
...envSettings.targetLength ? { targetLength: envSettings.targetLength } : {},
|
|
@@ -1564,9 +1623,9 @@ function assertNoLegacyXMode(contentTargets, sourceLabel) {
|
|
|
1564
1623
|
}
|
|
1565
1624
|
|
|
1566
1625
|
// src/pipeline/runner.ts
|
|
1567
|
-
import { mkdir as
|
|
1626
|
+
import { mkdir as mkdir6, stat as stat2 } from "fs/promises";
|
|
1568
1627
|
import { randomUUID } from "crypto";
|
|
1569
|
-
import
|
|
1628
|
+
import path9 from "path";
|
|
1570
1629
|
|
|
1571
1630
|
// src/generation/enrichLinks.ts
|
|
1572
1631
|
import { readFile as readFile4 } from "fs/promises";
|
|
@@ -1940,6 +1999,23 @@ function buildTargetLengthDirective(contentType, targetLengthWords) {
|
|
|
1940
1999
|
// src/llm/prompts/guideBundles.ts
|
|
1941
2000
|
import { existsSync, readFileSync } from "fs";
|
|
1942
2001
|
import path5 from "path";
|
|
2002
|
+
|
|
2003
|
+
// src/types/article.ts
|
|
2004
|
+
var LONG_FORM_CONTENT_TYPES = [
|
|
2005
|
+
"article",
|
|
2006
|
+
"blog-post",
|
|
2007
|
+
"newsletter",
|
|
2008
|
+
"press-release",
|
|
2009
|
+
"science-paper"
|
|
2010
|
+
];
|
|
2011
|
+
function isLongFormContentType(contentType) {
|
|
2012
|
+
return LONG_FORM_CONTENT_TYPES.includes(contentType);
|
|
2013
|
+
}
|
|
2014
|
+
function isLongFormPlan(plan) {
|
|
2015
|
+
return isLongFormContentType(plan.contentType) && plan.sections !== void 0 && plan.sections.length > 0;
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
// src/llm/prompts/guideBundles.ts
|
|
1943
2019
|
var guideCache = /* @__PURE__ */ new Map();
|
|
1944
2020
|
function normalizeGuideContent(content) {
|
|
1945
2021
|
return content.replace(/\r\n/g, "\n").trim();
|
|
@@ -1991,14 +2067,22 @@ function buildGuideBundle(relativePaths) {
|
|
|
1991
2067
|
...blocks
|
|
1992
2068
|
].join("\n\n");
|
|
1993
2069
|
}
|
|
1994
|
-
function
|
|
1995
|
-
|
|
2070
|
+
function buildPrimaryPlanGuideInstruction(intent, contentType) {
|
|
2071
|
+
const baseGuides = [
|
|
1996
2072
|
"writing-guide/references/headline-writing-systems.md",
|
|
1997
2073
|
"writing-guide/references/ideation-and-credibility-systems.md",
|
|
1998
2074
|
"writing-guide/references/content-frameworks.md",
|
|
1999
2075
|
intentToGuidePath(intent),
|
|
2000
2076
|
formatToGuidePath(contentType)
|
|
2001
|
-
]
|
|
2077
|
+
];
|
|
2078
|
+
if (!isLongFormContentType(contentType)) {
|
|
2079
|
+
return buildGuideBundle([
|
|
2080
|
+
"writing-guide/references/headline-writing-systems.md",
|
|
2081
|
+
intentToGuidePath(intent),
|
|
2082
|
+
formatToGuidePath(contentType)
|
|
2083
|
+
]);
|
|
2084
|
+
}
|
|
2085
|
+
return buildGuideBundle(baseGuides);
|
|
2002
2086
|
}
|
|
2003
2087
|
function buildArticleSectionGuideInstruction(style, intent, contentType) {
|
|
2004
2088
|
return buildGuideBundle([
|
|
@@ -2220,8 +2304,8 @@ function deriveTitleFromIdea(idea) {
|
|
|
2220
2304
|
return idea.split(/\s+/).slice(0, 8).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
2221
2305
|
}
|
|
2222
2306
|
|
|
2223
|
-
// src/llm/prompts/
|
|
2224
|
-
function
|
|
2307
|
+
// src/llm/prompts/primaryPlan.ts
|
|
2308
|
+
function deriveSectionCounts(targetLengthWords) {
|
|
2225
2309
|
const normalizedWords = Number.isFinite(targetLengthWords) && targetLengthWords > 0 ? targetLengthWords : 900;
|
|
2226
2310
|
const center = Math.max(2, Math.min(10, Math.round(normalizedWords / 220)));
|
|
2227
2311
|
const min = Math.max(2, center - 1);
|
|
@@ -2232,12 +2316,20 @@ function deriveArticleSectionCounts(targetLengthWords) {
|
|
|
2232
2316
|
label: `${min} to ${max}`
|
|
2233
2317
|
};
|
|
2234
2318
|
}
|
|
2235
|
-
function
|
|
2236
|
-
|
|
2319
|
+
function buildPrimaryPlanJsonSchema(contentType, targetLengthWords) {
|
|
2320
|
+
if (!isLongFormContentType(contentType)) {
|
|
2321
|
+
return buildShortFormPlanJsonSchema();
|
|
2322
|
+
}
|
|
2323
|
+
return buildLongFormPlanJsonSchema(targetLengthWords);
|
|
2324
|
+
}
|
|
2325
|
+
function buildLongFormPlanJsonSchema(targetLengthWords) {
|
|
2326
|
+
const sectionCounts = deriveSectionCounts(targetLengthWords);
|
|
2327
|
+
const imageCounts = resolveDefaultInlineImageCount(targetLengthWords);
|
|
2237
2328
|
return {
|
|
2238
2329
|
type: "object",
|
|
2239
2330
|
additionalProperties: false,
|
|
2240
2331
|
required: [
|
|
2332
|
+
"contentType",
|
|
2241
2333
|
"title",
|
|
2242
2334
|
"subtitle",
|
|
2243
2335
|
"keywords",
|
|
@@ -2250,6 +2342,7 @@ function buildArticlePlanJsonSchema(targetLengthWords) {
|
|
|
2250
2342
|
"inlineImages"
|
|
2251
2343
|
],
|
|
2252
2344
|
properties: {
|
|
2345
|
+
contentType: { type: "string" },
|
|
2253
2346
|
title: { type: "string" },
|
|
2254
2347
|
subtitle: { type: "string" },
|
|
2255
2348
|
keywords: {
|
|
@@ -2279,28 +2372,57 @@ function buildArticlePlanJsonSchema(targetLengthWords) {
|
|
|
2279
2372
|
coverImageDescription: { type: "string" },
|
|
2280
2373
|
inlineImages: {
|
|
2281
2374
|
type: "array",
|
|
2282
|
-
minItems:
|
|
2283
|
-
maxItems:
|
|
2375
|
+
minItems: imageCounts.min,
|
|
2376
|
+
maxItems: imageCounts.max,
|
|
2284
2377
|
items: {
|
|
2285
2378
|
type: "object",
|
|
2286
2379
|
additionalProperties: false,
|
|
2287
2380
|
required: ["description", "anchorAfterSection"],
|
|
2288
2381
|
properties: {
|
|
2289
2382
|
description: { type: "string" },
|
|
2290
|
-
anchorAfterSection: { type: "number", minimum:
|
|
2383
|
+
anchorAfterSection: { type: "number", minimum: 2 }
|
|
2291
2384
|
}
|
|
2292
2385
|
}
|
|
2293
2386
|
}
|
|
2294
2387
|
}
|
|
2295
2388
|
};
|
|
2296
2389
|
}
|
|
2297
|
-
function
|
|
2298
|
-
|
|
2390
|
+
function buildShortFormPlanJsonSchema() {
|
|
2391
|
+
return {
|
|
2392
|
+
type: "object",
|
|
2393
|
+
additionalProperties: false,
|
|
2394
|
+
required: [
|
|
2395
|
+
"contentType",
|
|
2396
|
+
"title",
|
|
2397
|
+
"slug",
|
|
2398
|
+
"description",
|
|
2399
|
+
"coverImageDescription",
|
|
2400
|
+
"angle"
|
|
2401
|
+
],
|
|
2402
|
+
properties: {
|
|
2403
|
+
contentType: { type: "string" },
|
|
2404
|
+
title: { type: "string" },
|
|
2405
|
+
slug: { type: "string" },
|
|
2406
|
+
description: { type: "string" },
|
|
2407
|
+
coverImageDescription: { type: "string" },
|
|
2408
|
+
angle: { type: "string" }
|
|
2409
|
+
}
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
function buildPrimaryPlanMessages(idea, options) {
|
|
2413
|
+
if (!isLongFormContentType(options.contentType)) {
|
|
2414
|
+
return buildShortFormPlanMessages(idea, options);
|
|
2415
|
+
}
|
|
2416
|
+
return buildLongFormPlanMessages(idea, options);
|
|
2417
|
+
}
|
|
2418
|
+
function buildLongFormPlanMessages(idea, options) {
|
|
2419
|
+
const sectionCounts = deriveSectionCounts(options.targetLength);
|
|
2420
|
+
const imageCounts = resolveDefaultInlineImageCount(options.targetLength);
|
|
2299
2421
|
const systemInstruction = [
|
|
2300
|
-
"You are a senior editorial strategist. Produce a rigorous
|
|
2301
|
-
|
|
2422
|
+
"You are a senior editorial strategist. Produce a rigorous content plan for a polished long-form Markdown output.",
|
|
2423
|
+
buildPrimaryPlanGuideInstruction(options.intent, options.contentType),
|
|
2302
2424
|
buildRunContextDirective(options.contentTypes),
|
|
2303
|
-
buildTargetLengthDirective(
|
|
2425
|
+
buildTargetLengthDirective(options.contentType, options.targetLength),
|
|
2304
2426
|
"Return only the requested JSON."
|
|
2305
2427
|
].join(" ");
|
|
2306
2428
|
return [
|
|
@@ -2311,20 +2433,20 @@ function buildArticlePlanMessages(idea, options) {
|
|
|
2311
2433
|
{
|
|
2312
2434
|
role: "user",
|
|
2313
2435
|
content: [
|
|
2314
|
-
|
|
2436
|
+
`Create a ${options.contentType} plan from this idea:`,
|
|
2315
2437
|
idea,
|
|
2316
2438
|
"",
|
|
2317
2439
|
"Requirements:",
|
|
2318
|
-
"- The
|
|
2440
|
+
"- The content should feel authoritative, practical, and clearly structured for scanning and deep reading.",
|
|
2319
2441
|
"- Generate a memorable title and a sharp subtitle that promise a concrete benefit, mechanism, or outcome.",
|
|
2320
2442
|
"- The slug must be lowercase kebab-case and publication-ready.",
|
|
2321
2443
|
"- The description should work as a concise meta description and align with the shared content plan.",
|
|
2322
2444
|
`- Plan ${sectionCounts.label} strong sections with distinct focus areas and logical progression (no repetitive section intent).`,
|
|
2323
2445
|
"- Frame section titles to reflect likely search intent or practical reader questions when appropriate.",
|
|
2324
2446
|
"- Each section description should name the mechanism, evidence type, or practical action that makes the section useful.",
|
|
2325
|
-
"- Sections are
|
|
2326
|
-
|
|
2327
|
-
"- Each inline image must specify which section it follows (anchorAfterSection,
|
|
2447
|
+
"- Sections are primary-only structure and must not be treated as requirements for non-primary channels.",
|
|
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, 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.",
|
|
2328
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.",
|
|
2329
2451
|
"- Image descriptions must be concrete and contextual, not generic stock-photo phrasing.",
|
|
2330
2452
|
"",
|
|
@@ -2336,16 +2458,63 @@ function buildArticlePlanMessages(idea, options) {
|
|
|
2336
2458
|
`- voiceNotes: ${options.contentPlan.voiceNotes}`,
|
|
2337
2459
|
"",
|
|
2338
2460
|
"Return JSON with all required fields:",
|
|
2461
|
+
`- contentType: set to "${options.contentType}" exactly`,
|
|
2339
2462
|
"- title: string",
|
|
2340
2463
|
"- subtitle: string",
|
|
2341
2464
|
"- keywords: array of 3 to 8 strings",
|
|
2342
2465
|
"- slug: string in lowercase kebab-case",
|
|
2343
|
-
"- description: string",
|
|
2344
|
-
"- introBrief: string",
|
|
2345
|
-
"- outroBrief: string",
|
|
2346
|
-
`- sections: array of ${sectionCounts.label} objects, each with title and description strings`,
|
|
2466
|
+
"- description: string",
|
|
2467
|
+
"- introBrief: string",
|
|
2468
|
+
"- outroBrief: string",
|
|
2469
|
+
`- sections: array of ${sectionCounts.label} objects, each with title and description strings`,
|
|
2470
|
+
"- coverImageDescription: string",
|
|
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
|
+
"",
|
|
2473
|
+
"Do not omit any required fields. Return strict JSON only."
|
|
2474
|
+
].join("\n")
|
|
2475
|
+
}
|
|
2476
|
+
];
|
|
2477
|
+
}
|
|
2478
|
+
function buildShortFormPlanMessages(idea, options) {
|
|
2479
|
+
const systemInstruction = [
|
|
2480
|
+
"You are a senior content strategist. Produce a concise content plan for a short-form social media post.",
|
|
2481
|
+
buildPrimaryPlanGuideInstruction(options.intent, options.contentType),
|
|
2482
|
+
buildRunContextDirective(options.contentTypes),
|
|
2483
|
+
"Return only the requested JSON."
|
|
2484
|
+
].join(" ");
|
|
2485
|
+
return [
|
|
2486
|
+
{
|
|
2487
|
+
role: "system",
|
|
2488
|
+
content: systemInstruction
|
|
2489
|
+
},
|
|
2490
|
+
{
|
|
2491
|
+
role: "user",
|
|
2492
|
+
content: [
|
|
2493
|
+
`Create a ${options.contentType} plan from this idea:`,
|
|
2494
|
+
idea,
|
|
2495
|
+
"",
|
|
2496
|
+
"Requirements:",
|
|
2497
|
+
"- Generate a sharp, attention-grabbing title suitable for social media.",
|
|
2498
|
+
"- The slug must be lowercase kebab-case and publication-ready.",
|
|
2499
|
+
"- The description should capture the core message in one sentence.",
|
|
2500
|
+
"- The angle should describe the hook, framing, or unique take that makes this post compelling.",
|
|
2501
|
+
"- Include a cover image description that works as a visual anchor for the post.",
|
|
2502
|
+
"- Do NOT include sections, subtitles, keywords, intros, or outros \u2014 this is short-form content.",
|
|
2503
|
+
"",
|
|
2504
|
+
"Shared content plan context:",
|
|
2505
|
+
`- description: ${options.contentPlan.description}`,
|
|
2506
|
+
`- targetAudience: ${options.contentPlan.targetAudience}`,
|
|
2507
|
+
`- corePromise: ${options.contentPlan.corePromise}`,
|
|
2508
|
+
`- keyPoints: ${options.contentPlan.keyPoints.join(" | ")}`,
|
|
2509
|
+
`- voiceNotes: ${options.contentPlan.voiceNotes}`,
|
|
2510
|
+
"",
|
|
2511
|
+
"Return JSON with all required fields:",
|
|
2512
|
+
`- contentType: set to "${options.contentType}" exactly`,
|
|
2513
|
+
"- title: string (short, punchy, social-media-ready)",
|
|
2514
|
+
"- slug: string in lowercase kebab-case",
|
|
2515
|
+
"- description: string (one-sentence core message)",
|
|
2347
2516
|
"- coverImageDescription: string",
|
|
2348
|
-
"-
|
|
2517
|
+
"- angle: string (the hook or framing that makes this post work)",
|
|
2349
2518
|
"",
|
|
2350
2519
|
"Do not omit any required fields. Return strict JSON only."
|
|
2351
2520
|
].join("\n")
|
|
@@ -2361,9 +2530,24 @@ var articleSectionPlanSchema = z5.object({
|
|
|
2361
2530
|
});
|
|
2362
2531
|
var inlineImagePlanSchema = z5.object({
|
|
2363
2532
|
description: z5.string().min(1),
|
|
2364
|
-
anchorAfterSection: z5.number().int().min(
|
|
2533
|
+
anchorAfterSection: z5.number().int().min(2)
|
|
2534
|
+
});
|
|
2535
|
+
var primaryPlanSchema = z5.object({
|
|
2536
|
+
contentType: z5.string().min(1).default("article"),
|
|
2537
|
+
title: z5.string().min(1),
|
|
2538
|
+
slug: z5.string().min(1),
|
|
2539
|
+
description: z5.string().min(1),
|
|
2540
|
+
coverImageDescription: z5.string().min(1),
|
|
2541
|
+
subtitle: z5.string().min(1).optional(),
|
|
2542
|
+
keywords: z5.array(z5.string().min(1)).min(3).max(8).optional(),
|
|
2543
|
+
introBrief: z5.string().min(1).optional(),
|
|
2544
|
+
outroBrief: z5.string().min(1).optional(),
|
|
2545
|
+
sections: z5.array(articleSectionPlanSchema).min(2).max(10).optional(),
|
|
2546
|
+
inlineImages: z5.array(inlineImagePlanSchema).min(0).max(4).optional(),
|
|
2547
|
+
angle: z5.string().min(1).optional()
|
|
2365
2548
|
});
|
|
2366
|
-
var
|
|
2549
|
+
var longFormPlanSchema = z5.object({
|
|
2550
|
+
contentType: z5.string().min(1),
|
|
2367
2551
|
title: z5.string().min(1),
|
|
2368
2552
|
subtitle: z5.string().min(1),
|
|
2369
2553
|
keywords: z5.array(z5.string().min(1)).min(3).max(8),
|
|
@@ -2373,15 +2557,24 @@ var articlePlanSchema = z5.object({
|
|
|
2373
2557
|
outroBrief: z5.string().min(1),
|
|
2374
2558
|
sections: z5.array(articleSectionPlanSchema).min(2).max(10),
|
|
2375
2559
|
coverImageDescription: z5.string().min(1),
|
|
2376
|
-
inlineImages: z5.array(inlineImagePlanSchema).min(
|
|
2560
|
+
inlineImages: z5.array(inlineImagePlanSchema).min(0).max(4)
|
|
2561
|
+
});
|
|
2562
|
+
var shortFormPlanSchema = z5.object({
|
|
2563
|
+
contentType: z5.string().min(1),
|
|
2564
|
+
title: z5.string().min(1),
|
|
2565
|
+
slug: z5.string().min(1),
|
|
2566
|
+
description: z5.string().min(1),
|
|
2567
|
+
coverImageDescription: z5.string().min(1),
|
|
2568
|
+
angle: z5.string().min(1).optional()
|
|
2377
2569
|
});
|
|
2378
2570
|
var imagePromptResultSchema = z5.object({
|
|
2379
2571
|
prompt: z5.string().min(1)
|
|
2380
2572
|
});
|
|
2381
2573
|
|
|
2382
|
-
// src/generation/
|
|
2383
|
-
async function
|
|
2574
|
+
// src/generation/planPrimaryContent.ts
|
|
2575
|
+
async function planPrimaryContent({
|
|
2384
2576
|
idea,
|
|
2577
|
+
contentType,
|
|
2385
2578
|
contentPlan,
|
|
2386
2579
|
settings,
|
|
2387
2580
|
markdownOutputDir,
|
|
@@ -2390,10 +2583,12 @@ async function planArticle({
|
|
|
2390
2583
|
onLlmMetrics,
|
|
2391
2584
|
onInteraction
|
|
2392
2585
|
}) {
|
|
2393
|
-
const
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2586
|
+
const isLongForm = isLongFormContentType(contentType);
|
|
2587
|
+
const basePlan = dryRun || !openRouter ? buildDryRunPlan(idea, contentType, contentPlan) : await openRouter.requestStructured({
|
|
2588
|
+
schemaName: "primary_plan",
|
|
2589
|
+
schema: buildPrimaryPlanJsonSchema(contentType, settings.targetLength),
|
|
2590
|
+
messages: buildPrimaryPlanMessages(idea, {
|
|
2591
|
+
contentType,
|
|
2397
2592
|
intent: settings.intent,
|
|
2398
2593
|
contentTypes: settings.contentTargets.map((target) => target.contentType),
|
|
2399
2594
|
contentPlan,
|
|
@@ -2402,32 +2597,53 @@ async function planArticle({
|
|
|
2402
2597
|
settings,
|
|
2403
2598
|
interactionContext: {
|
|
2404
2599
|
stageId: "planning",
|
|
2405
|
-
operationId:
|
|
2600
|
+
operationId: `planning:${contentType}-plan`
|
|
2406
2601
|
},
|
|
2407
2602
|
onInteraction,
|
|
2408
2603
|
onMetrics: onLlmMetrics,
|
|
2409
2604
|
parse(data) {
|
|
2410
|
-
|
|
2605
|
+
if (isLongForm) {
|
|
2606
|
+
return longFormPlanSchema.parse(data);
|
|
2607
|
+
}
|
|
2608
|
+
return shortFormPlanSchema.parse(data);
|
|
2411
2609
|
}
|
|
2412
2610
|
});
|
|
2413
2611
|
const normalizedSlug = slugify(basePlan.slug || basePlan.title);
|
|
2414
2612
|
const uniqueSlug = await resolveUniqueSlug(markdownOutputDir, normalizedSlug);
|
|
2415
|
-
|
|
2613
|
+
if (isLongForm) {
|
|
2614
|
+
const longPlan = basePlan;
|
|
2615
|
+
const sectionCount = longPlan.sections.length;
|
|
2616
|
+
return {
|
|
2617
|
+
...longPlan,
|
|
2618
|
+
slug: uniqueSlug,
|
|
2619
|
+
keywords: longPlan.keywords.slice(0, 8),
|
|
2620
|
+
inlineImages: longPlan.inlineImages.slice(0, 3).map((img) => ({
|
|
2621
|
+
...img,
|
|
2622
|
+
anchorAfterSection: Math.max(2, Math.min(sectionCount, img.anchorAfterSection))
|
|
2623
|
+
}))
|
|
2624
|
+
};
|
|
2625
|
+
}
|
|
2416
2626
|
return {
|
|
2417
2627
|
...basePlan,
|
|
2418
|
-
slug: uniqueSlug
|
|
2419
|
-
keywords: basePlan.keywords.slice(0, 8),
|
|
2420
|
-
inlineImages: basePlan.inlineImages.slice(0, 3).map((img) => ({
|
|
2421
|
-
...img,
|
|
2422
|
-
anchorAfterSection: Math.max(1, Math.min(sectionCount, img.anchorAfterSection))
|
|
2423
|
-
}))
|
|
2628
|
+
slug: uniqueSlug
|
|
2424
2629
|
};
|
|
2425
2630
|
}
|
|
2426
|
-
function buildDryRunPlan(idea, contentPlan) {
|
|
2427
|
-
const title = idea.trim().split(/\s+/).slice(0, 7).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
2631
|
+
function buildDryRunPlan(idea, contentType, contentPlan) {
|
|
2632
|
+
const title = idea.trim().split(/\s+/).slice(0, 7).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ") || contentPlan.title;
|
|
2633
|
+
if (!isLongFormContentType(contentType)) {
|
|
2634
|
+
return {
|
|
2635
|
+
contentType,
|
|
2636
|
+
title,
|
|
2637
|
+
slug: slugify(title),
|
|
2638
|
+
description: contentPlan.description,
|
|
2639
|
+
coverImageDescription: `A visually striking cover image for a ${contentType} about ${idea.trim().split(/\s+/).slice(0, 5).join(" ")}.`,
|
|
2640
|
+
angle: `Direct, practical framing that hooks the audience immediately with a clear value proposition tied to ${contentPlan.corePromise}`
|
|
2641
|
+
};
|
|
2642
|
+
}
|
|
2428
2643
|
return {
|
|
2644
|
+
contentType,
|
|
2429
2645
|
title,
|
|
2430
|
-
subtitle: "A practical editorial blueprint for turning a good idea into
|
|
2646
|
+
subtitle: "A practical editorial blueprint for turning a good idea into strong published content",
|
|
2431
2647
|
keywords: ["writing", "editorial workflow", "ai tools", "content strategy"],
|
|
2432
2648
|
slug: slugify(title),
|
|
2433
2649
|
description: contentPlan.description,
|
|
@@ -2436,10 +2652,10 @@ function buildDryRunPlan(idea, contentPlan) {
|
|
|
2436
2652
|
sections: [
|
|
2437
2653
|
{
|
|
2438
2654
|
title: "Why raw ideas are not enough",
|
|
2439
|
-
description: "Explain why strong
|
|
2655
|
+
description: "Explain why strong content needs structure, intent, and editorial judgment."
|
|
2440
2656
|
},
|
|
2441
2657
|
{
|
|
2442
|
-
title: "Designing the
|
|
2658
|
+
title: "Designing the content before drafting",
|
|
2443
2659
|
description: "Show how planning title, sections, and narrative flow improves the final result."
|
|
2444
2660
|
},
|
|
2445
2661
|
{
|
|
@@ -2458,7 +2674,7 @@ function buildDryRunPlan(idea, contentPlan) {
|
|
|
2458
2674
|
coverImageDescription: "A refined editorial workspace with notebooks, sketches, and glowing structured outlines, cinematic but minimal.",
|
|
2459
2675
|
inlineImages: [
|
|
2460
2676
|
{
|
|
2461
|
-
description: "A rough idea evolving into a structured
|
|
2677
|
+
description: "A rough idea evolving into a structured content outline on a desk full of notes.",
|
|
2462
2678
|
anchorAfterSection: 2
|
|
2463
2679
|
},
|
|
2464
2680
|
{
|
|
@@ -2469,7 +2685,7 @@ function buildDryRunPlan(idea, contentPlan) {
|
|
|
2469
2685
|
};
|
|
2470
2686
|
}
|
|
2471
2687
|
function slugify(value2) {
|
|
2472
|
-
return value2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "untitled-
|
|
2688
|
+
return value2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "untitled-content";
|
|
2473
2689
|
}
|
|
2474
2690
|
|
|
2475
2691
|
// src/llm/prompts/channelContent.ts
|
|
@@ -2495,6 +2711,14 @@ function buildSingleShotContentMessages(options) {
|
|
|
2495
2711
|
`This output is secondary content and must promote or incite interest in the primary ${options.primaryContentType} content.`,
|
|
2496
2712
|
"Keep it independently useful, avoid sounding like an ad, and include channel-native cues that point back to the primary narrative."
|
|
2497
2713
|
].join(" ");
|
|
2714
|
+
const planContext = options.plan ? [
|
|
2715
|
+
"",
|
|
2716
|
+
"Primary content plan (use to guide tone, angle, and structure):",
|
|
2717
|
+
`- title: ${options.plan.title}`,
|
|
2718
|
+
`- description: ${options.plan.description}`,
|
|
2719
|
+
`- coverImageDescription: ${options.plan.coverImageDescription}`,
|
|
2720
|
+
...options.plan.angle ? [`- angle: ${options.plan.angle}`] : []
|
|
2721
|
+
].join("\n") : "";
|
|
2498
2722
|
return [
|
|
2499
2723
|
{
|
|
2500
2724
|
role: "system",
|
|
@@ -2525,6 +2749,7 @@ function buildSingleShotContentMessages(options) {
|
|
|
2525
2749
|
`- primaryContentType: ${options.contentPlan.primaryContentType}`,
|
|
2526
2750
|
`- secondaryContentTypes: ${options.contentPlan.secondaryContentTypes.join(" | ") || "none"}`,
|
|
2527
2751
|
`- secondaryContentStrategy: ${options.contentPlan.secondaryContentStrategy}`,
|
|
2752
|
+
planContext,
|
|
2528
2753
|
"",
|
|
2529
2754
|
articleContext,
|
|
2530
2755
|
"",
|
|
@@ -2549,6 +2774,7 @@ async function writeSingleShotContent({
|
|
|
2549
2774
|
outputCountForType,
|
|
2550
2775
|
articleReferenceMarkdown,
|
|
2551
2776
|
contentPlan,
|
|
2777
|
+
plan,
|
|
2552
2778
|
settings,
|
|
2553
2779
|
openRouter,
|
|
2554
2780
|
dryRun,
|
|
@@ -2564,6 +2790,7 @@ async function writeSingleShotContent({
|
|
|
2564
2790
|
outputIndex,
|
|
2565
2791
|
outputCountForType,
|
|
2566
2792
|
contentPlan,
|
|
2793
|
+
plan,
|
|
2567
2794
|
articleReferenceMarkdown
|
|
2568
2795
|
});
|
|
2569
2796
|
}
|
|
@@ -2578,6 +2805,7 @@ async function writeSingleShotContent({
|
|
|
2578
2805
|
outputIndex,
|
|
2579
2806
|
outputCountForType,
|
|
2580
2807
|
contentPlan,
|
|
2808
|
+
plan,
|
|
2581
2809
|
articleReferenceMarkdown,
|
|
2582
2810
|
targetLength: settings.targetLength
|
|
2583
2811
|
}),
|
|
@@ -2592,6 +2820,9 @@ async function writeSingleShotContent({
|
|
|
2592
2820
|
}
|
|
2593
2821
|
function buildDryRunContent(options) {
|
|
2594
2822
|
const anchorNote = options.articleReferenceMarkdown ? "Anchored to generated primary context from this run." : "No primary anchor available; generated directly from idea.";
|
|
2823
|
+
const planNote = options.plan ? `Plan title: ${options.plan.title}
|
|
2824
|
+
Plan description: ${options.plan.description}${options.plan.angle ? `
|
|
2825
|
+
Angle: ${options.plan.angle}` : ""}` : "No primary plan available.";
|
|
2595
2826
|
return [
|
|
2596
2827
|
`# ${options.contentType} draft ${options.outputIndex}`,
|
|
2597
2828
|
"",
|
|
@@ -2600,6 +2831,7 @@ function buildDryRunContent(options) {
|
|
|
2600
2831
|
`Role: ${options.role}`,
|
|
2601
2832
|
`Primary content type: ${options.primaryContentType}`,
|
|
2602
2833
|
`Shared plan: ${options.contentPlan.description}`,
|
|
2834
|
+
planNote,
|
|
2603
2835
|
anchorNote,
|
|
2604
2836
|
"",
|
|
2605
2837
|
"This is a dry-run placeholder for single-prompt channel generation."
|
|
@@ -2622,12 +2854,12 @@ var OUTRO_PARAGRAPH_COUNTS = {
|
|
|
2622
2854
|
medium: "2 to 3",
|
|
2623
2855
|
large: "3 to 5"
|
|
2624
2856
|
};
|
|
2625
|
-
function buildSystemInstruction(base, style, intent, contentTypes, targetLengthWords) {
|
|
2857
|
+
function buildSystemInstruction(base, style, intent, contentTypes, targetLengthWords, contentType) {
|
|
2626
2858
|
return [
|
|
2627
2859
|
base,
|
|
2628
|
-
buildArticleSectionGuideInstruction(style, intent,
|
|
2860
|
+
buildArticleSectionGuideInstruction(style, intent, contentType),
|
|
2629
2861
|
buildRunContextDirective(contentTypes),
|
|
2630
|
-
buildTargetLengthDirective(
|
|
2862
|
+
buildTargetLengthDirective(contentType, targetLengthWords)
|
|
2631
2863
|
].join(" ");
|
|
2632
2864
|
}
|
|
2633
2865
|
function sharedPlanContext(plan) {
|
|
@@ -2657,7 +2889,8 @@ function buildIntroMessages(plan, style, intent, contentTypes, targetLengthWords
|
|
|
2657
2889
|
style,
|
|
2658
2890
|
intent,
|
|
2659
2891
|
contentTypes,
|
|
2660
|
-
targetLengthWords
|
|
2892
|
+
targetLengthWords,
|
|
2893
|
+
plan.contentType
|
|
2661
2894
|
);
|
|
2662
2895
|
const targetLengthAlias = resolveTargetLengthAlias(targetLengthWords);
|
|
2663
2896
|
const paragraphCount = INTRO_PARAGRAPH_COUNTS[targetLengthAlias] ?? INTRO_PARAGRAPH_COUNTS["medium"];
|
|
@@ -2687,7 +2920,8 @@ function buildSectionMessages(plan, section, articleSoFar, style, intent, conten
|
|
|
2687
2920
|
style,
|
|
2688
2921
|
intent,
|
|
2689
2922
|
contentTypes,
|
|
2690
|
-
targetLengthWords
|
|
2923
|
+
targetLengthWords,
|
|
2924
|
+
plan.contentType
|
|
2691
2925
|
);
|
|
2692
2926
|
const targetLengthAlias = resolveTargetLengthAlias(targetLengthWords);
|
|
2693
2927
|
const paragraphCount = SECTION_PARAGRAPH_COUNTS[targetLengthAlias] ?? SECTION_PARAGRAPH_COUNTS["medium"];
|
|
@@ -2721,7 +2955,8 @@ function buildOutroMessages(plan, style, intent, contentTypes, targetLengthWords
|
|
|
2721
2955
|
style,
|
|
2722
2956
|
intent,
|
|
2723
2957
|
contentTypes,
|
|
2724
|
-
targetLengthWords
|
|
2958
|
+
targetLengthWords,
|
|
2959
|
+
plan.contentType
|
|
2725
2960
|
);
|
|
2726
2961
|
const targetLengthAlias = resolveTargetLengthAlias(targetLengthWords);
|
|
2727
2962
|
const paragraphCount = OUTRO_PARAGRAPH_COUNTS[targetLengthAlias] ?? OUTRO_PARAGRAPH_COUNTS["medium"];
|
|
@@ -2876,19 +3111,36 @@ function buildArticleSoFarContext(intro, sections) {
|
|
|
2876
3111
|
}
|
|
2877
3112
|
return parts.join("\n\n").trim();
|
|
2878
3113
|
}
|
|
3114
|
+
function normalizeWhitespaceForHeadingMatch(text) {
|
|
3115
|
+
return text.toLowerCase().replace(/\s+/g, " ").trim();
|
|
3116
|
+
}
|
|
3117
|
+
function stripMatchingLeadingHeading(content, label2) {
|
|
3118
|
+
const headingMatch = content.match(/^#{1,6}\s+(.+?)(?:\r?\n|$)/);
|
|
3119
|
+
if (!headingMatch) {
|
|
3120
|
+
return content;
|
|
3121
|
+
}
|
|
3122
|
+
const headingText = normalizeWhitespaceForHeadingMatch(headingMatch[1] ?? "");
|
|
3123
|
+
const expectedLabel = normalizeWhitespaceForHeadingMatch(label2);
|
|
3124
|
+
if (headingText !== expectedLabel) {
|
|
3125
|
+
return content;
|
|
3126
|
+
}
|
|
3127
|
+
return content.slice(headingMatch[0].length).trimStart();
|
|
3128
|
+
}
|
|
2879
3129
|
function normalizeGeneratedSection(content, label2) {
|
|
2880
3130
|
const normalized = content.trim();
|
|
2881
3131
|
if (!normalized) {
|
|
2882
3132
|
throw new Error(`The model returned an empty ${label2} draft.`);
|
|
2883
3133
|
}
|
|
2884
|
-
|
|
3134
|
+
const withoutFences = normalized.replace(/^```(?:markdown)?\s*/i, "").replace(/```\s*$/i, "").trim();
|
|
3135
|
+
return stripMatchingLeadingHeading(withoutFences, label2).trim();
|
|
2885
3136
|
}
|
|
2886
3137
|
|
|
2887
3138
|
// src/pipeline/runner.ts
|
|
2888
3139
|
import { Limn } from "@telepat/limn";
|
|
2889
3140
|
|
|
2890
3141
|
// src/images/renderImages.ts
|
|
2891
|
-
import { writeFile as writeFile4 } from "fs/promises";
|
|
3142
|
+
import { copyFile, mkdir as mkdir4, rename, unlink, writeFile as writeFile4 } from "fs/promises";
|
|
3143
|
+
import { createHash } from "crypto";
|
|
2892
3144
|
import path6 from "path";
|
|
2893
3145
|
|
|
2894
3146
|
// src/llm/prompts/imagePrompt.ts
|
|
@@ -2965,6 +3217,39 @@ function sumKnownCosts(values) {
|
|
|
2965
3217
|
|
|
2966
3218
|
// src/images/renderImages.ts
|
|
2967
3219
|
var MIN_IMAGE_BYTES = 1024;
|
|
3220
|
+
function getLocalSessionArtifactDir(workingDir = process.cwd()) {
|
|
3221
|
+
const hash = createHash("sha256").update(path6.resolve(workingDir)).digest("hex").slice(0, 16);
|
|
3222
|
+
return path6.join(workingDir, ".ideon", "sessions", hash, "limn-artifacts");
|
|
3223
|
+
}
|
|
3224
|
+
function isNotFoundError(error) {
|
|
3225
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
3226
|
+
}
|
|
3227
|
+
function isCrossDeviceError(error) {
|
|
3228
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EXDEV";
|
|
3229
|
+
}
|
|
3230
|
+
async function moveLimnTemporaryArtifact(savedPath) {
|
|
3231
|
+
if (!savedPath || savedPath.trim().length === 0) {
|
|
3232
|
+
return;
|
|
3233
|
+
}
|
|
3234
|
+
const sourcePath = path6.resolve(savedPath);
|
|
3235
|
+
const destinationDir = getLocalSessionArtifactDir(path6.dirname(sourcePath));
|
|
3236
|
+
const destinationPath = path6.join(destinationDir, path6.basename(sourcePath));
|
|
3237
|
+
await mkdir4(destinationDir, { recursive: true });
|
|
3238
|
+
try {
|
|
3239
|
+
await rename(sourcePath, destinationPath);
|
|
3240
|
+
return;
|
|
3241
|
+
} catch (error) {
|
|
3242
|
+
if (isCrossDeviceError(error)) {
|
|
3243
|
+
await copyFile(sourcePath, destinationPath);
|
|
3244
|
+
await unlink(sourcePath);
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
if (isNotFoundError(error)) {
|
|
3248
|
+
return;
|
|
3249
|
+
}
|
|
3250
|
+
throw error;
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
2968
3253
|
function buildImageSlots(plan, sections, options) {
|
|
2969
3254
|
const sectionCount = sections.length;
|
|
2970
3255
|
const slots = [
|
|
@@ -2991,7 +3276,7 @@ function buildImageSlots(plan, sections, options) {
|
|
|
2991
3276
|
kind: "inline",
|
|
2992
3277
|
prompt: "",
|
|
2993
3278
|
description: img.description,
|
|
2994
|
-
anchorAfterSection: Math.max(
|
|
3279
|
+
anchorAfterSection: Math.max(2, Math.min(sectionCount, img.anchorAfterSection))
|
|
2995
3280
|
});
|
|
2996
3281
|
}
|
|
2997
3282
|
return slots;
|
|
@@ -3140,12 +3425,14 @@ async function renderExpandedImages({
|
|
|
3140
3425
|
continue;
|
|
3141
3426
|
}
|
|
3142
3427
|
const family = settings.t2i.modelId;
|
|
3428
|
+
const replicateModelOverride = settings.t2i.replicateModelId;
|
|
3429
|
+
const limnOptions = {
|
|
3430
|
+
aspectRatio: "16:9",
|
|
3431
|
+
...replicateModelOverride && isReplicateModelIdForFamily(family, replicateModelOverride) ? { replicateModel: replicateModelOverride } : {}
|
|
3432
|
+
};
|
|
3143
3433
|
const renderStartedAtMs = Date.now();
|
|
3144
3434
|
try {
|
|
3145
|
-
const result = await limn.generate(prompt.prompt, family,
|
|
3146
|
-
replicateModel: settings.t2i.modelId,
|
|
3147
|
-
aspectRatio: "16:9"
|
|
3148
|
-
});
|
|
3435
|
+
const result = await limn.generate(prompt.prompt, family, limnOptions);
|
|
3149
3436
|
const ext = mimeTypeToExtension(result.mimeType);
|
|
3150
3437
|
const liveFileName = `${prompt.kind === "cover" ? "cover" : `inline-${prompt.anchorAfterSection}`}-${index + 1}.${ext}`;
|
|
3151
3438
|
const liveOutputPath = path6.join(assetDir, liveFileName);
|
|
@@ -3155,6 +3442,7 @@ async function renderExpandedImages({
|
|
|
3155
3442
|
);
|
|
3156
3443
|
}
|
|
3157
3444
|
await writeFile4(liveOutputPath, result.image);
|
|
3445
|
+
await moveLimnTemporaryArtifact(result.savedPath);
|
|
3158
3446
|
renderedImages.push({
|
|
3159
3447
|
...prompt,
|
|
3160
3448
|
outputPath: liveOutputPath,
|
|
@@ -3715,10 +4003,69 @@ ${body.join("\n").trim()}
|
|
|
3715
4003
|
`;
|
|
3716
4004
|
}
|
|
3717
4005
|
|
|
3718
|
-
// src/
|
|
3719
|
-
import { createHash } from "crypto";
|
|
3720
|
-
import { mkdir as mkdir4, readFile as readFile5, rm as rm2, writeFile as writeFile5 } from "fs/promises";
|
|
4006
|
+
// src/output/meta.ts
|
|
3721
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
|
+
|
|
4065
|
+
// src/pipeline/sessionStore.ts
|
|
4066
|
+
import { createHash as createHash2 } from "crypto";
|
|
4067
|
+
import { mkdir as mkdir5, readFile as readFile5, rm as rm2, writeFile as writeFile5 } from "fs/promises";
|
|
4068
|
+
import path8 from "path";
|
|
3722
4069
|
import envPaths3 from "env-paths";
|
|
3723
4070
|
import { z as z6 } from "zod";
|
|
3724
4071
|
var STAGE_IDS = ["shared-plan", "planning", "sections", "image-prompts", "images", "output", "links"];
|
|
@@ -3761,7 +4108,8 @@ var pipelineArtifactSummarySchema = z6.object({
|
|
|
3761
4108
|
assetDir: z6.string().min(1),
|
|
3762
4109
|
analyticsPath: z6.string().min(1).default("unknown.analytics.json"),
|
|
3763
4110
|
interactionsPath: z6.string().min(1).default("unknown.interactions.json"),
|
|
3764
|
-
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")
|
|
3765
4113
|
});
|
|
3766
4114
|
var resolvedPathsSchema = z6.object({
|
|
3767
4115
|
markdownOutputDir: z6.string().min(1),
|
|
@@ -3782,7 +4130,7 @@ var writeSessionStateSchema = z6.object({
|
|
|
3782
4130
|
failedStage: z6.enum(STAGE_IDS).nullable(),
|
|
3783
4131
|
errorMessage: z6.string().nullable(),
|
|
3784
4132
|
contentPlan: contentPlanSchema2.nullable().default(null),
|
|
3785
|
-
plan:
|
|
4133
|
+
plan: primaryPlanSchema.nullable(),
|
|
3786
4134
|
text: z6.object({
|
|
3787
4135
|
intro: z6.string().min(1),
|
|
3788
4136
|
sections: z6.array(generatedArticleSectionSchema),
|
|
@@ -3797,23 +4145,23 @@ var writeSessionStateSchema = z6.object({
|
|
|
3797
4145
|
artifact: pipelineArtifactSummarySchema.nullable()
|
|
3798
4146
|
});
|
|
3799
4147
|
var ideonPaths3 = envPaths3("ideon", { suffix: "" });
|
|
3800
|
-
var sessionsDir =
|
|
4148
|
+
var sessionsDir = path8.join(ideonPaths3.config, "sessions");
|
|
3801
4149
|
function hashProjectPath(workingDir) {
|
|
3802
|
-
return
|
|
4150
|
+
return createHash2("sha256").update(path8.resolve(workingDir)).digest("hex").slice(0, 16);
|
|
3803
4151
|
}
|
|
3804
4152
|
function resolveWriteRoot(workingDir) {
|
|
3805
|
-
return
|
|
4153
|
+
return path8.join(sessionsDir, hashProjectPath(workingDir));
|
|
3806
4154
|
}
|
|
3807
4155
|
function resolveStateFilePath(workingDir) {
|
|
3808
|
-
return
|
|
4156
|
+
return path8.join(resolveWriteRoot(workingDir), "state.json");
|
|
3809
4157
|
}
|
|
3810
4158
|
function resolveLegacyStatePath(workingDir) {
|
|
3811
|
-
return
|
|
4159
|
+
return path8.join(workingDir, ".ideon", "write", "state.json");
|
|
3812
4160
|
}
|
|
3813
4161
|
async function startFreshWriteSession(seed, workingDir = process.cwd()) {
|
|
3814
4162
|
const writeRoot = resolveWriteRoot(workingDir);
|
|
3815
4163
|
await rm2(writeRoot, { recursive: true, force: true });
|
|
3816
|
-
await
|
|
4164
|
+
await mkdir5(writeRoot, { recursive: true });
|
|
3817
4165
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3818
4166
|
const state = {
|
|
3819
4167
|
version: 1,
|
|
@@ -3846,7 +4194,7 @@ async function loadWriteSession(workingDir = process.cwd()) {
|
|
|
3846
4194
|
const raw = await readFile5(statePath, "utf8");
|
|
3847
4195
|
return writeSessionStateSchema.parse(JSON.parse(raw));
|
|
3848
4196
|
} catch (error) {
|
|
3849
|
-
if (!
|
|
4197
|
+
if (!isNotFoundError2(error)) {
|
|
3850
4198
|
throw error;
|
|
3851
4199
|
}
|
|
3852
4200
|
}
|
|
@@ -3857,7 +4205,7 @@ async function loadWriteSession(workingDir = process.cwd()) {
|
|
|
3857
4205
|
await saveWriteSession(state, workingDir);
|
|
3858
4206
|
return state;
|
|
3859
4207
|
} catch (error) {
|
|
3860
|
-
if (
|
|
4208
|
+
if (isNotFoundError2(error)) {
|
|
3861
4209
|
return null;
|
|
3862
4210
|
}
|
|
3863
4211
|
throw error;
|
|
@@ -3869,7 +4217,7 @@ async function saveWriteSession(state, workingDir = process.cwd()) {
|
|
|
3869
4217
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3870
4218
|
});
|
|
3871
4219
|
const statePath = resolveStateFilePath(workingDir);
|
|
3872
|
-
await
|
|
4220
|
+
await mkdir5(path8.dirname(statePath), { recursive: true });
|
|
3873
4221
|
await writeFile5(statePath, `${JSON.stringify(next, null, 2)}
|
|
3874
4222
|
`, "utf8");
|
|
3875
4223
|
return next;
|
|
@@ -3897,16 +4245,12 @@ async function patchWriteSession(patch, workingDir = process.cwd()) {
|
|
|
3897
4245
|
};
|
|
3898
4246
|
return saveWriteSession(merged, workingDir);
|
|
3899
4247
|
}
|
|
3900
|
-
function
|
|
4248
|
+
function isNotFoundError2(error) {
|
|
3901
4249
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
3902
4250
|
}
|
|
3903
4251
|
|
|
3904
4252
|
// src/pipeline/runner.ts
|
|
3905
|
-
function createInitialStages(
|
|
3906
|
-
const planningTitle = options.isArticlePrimary ? "Planning Primary Article" : "Planning Primary Content";
|
|
3907
|
-
const planningDetail = options.isArticlePrimary ? "Generating title, slug, section plan, and image slots." : "Defining the primary angle and output intent.";
|
|
3908
|
-
const sectionsTitle = options.isArticlePrimary ? "Writing Sections" : "Generating Primary Content";
|
|
3909
|
-
const sectionsDetail = options.isArticlePrimary ? "Waiting for the approved article plan." : "Waiting for primary content generation to begin.";
|
|
4253
|
+
function createInitialStages() {
|
|
3910
4254
|
return [
|
|
3911
4255
|
{
|
|
3912
4256
|
id: "shared-plan",
|
|
@@ -3916,15 +4260,15 @@ function createInitialStages(options = { isArticlePrimary: true }) {
|
|
|
3916
4260
|
},
|
|
3917
4261
|
{
|
|
3918
4262
|
id: "planning",
|
|
3919
|
-
title:
|
|
4263
|
+
title: "Planning Primary Content",
|
|
3920
4264
|
status: "pending",
|
|
3921
|
-
detail:
|
|
4265
|
+
detail: "Generating title, slug, and content plan for the primary output."
|
|
3922
4266
|
},
|
|
3923
4267
|
{
|
|
3924
4268
|
id: "sections",
|
|
3925
|
-
title:
|
|
4269
|
+
title: "Writing Primary Content",
|
|
3926
4270
|
status: "pending",
|
|
3927
|
-
detail:
|
|
4271
|
+
detail: "Waiting for the approved primary plan."
|
|
3928
4272
|
},
|
|
3929
4273
|
{
|
|
3930
4274
|
id: "image-prompts",
|
|
@@ -3958,8 +4302,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
3958
4302
|
const runId = randomUUID();
|
|
3959
4303
|
const primaryTarget = getPrimaryTarget(input.config.settings.contentTargets);
|
|
3960
4304
|
const secondaryTargets = getSecondaryTargets(input.config.settings.contentTargets);
|
|
3961
|
-
const
|
|
3962
|
-
const stages = createInitialStages({ isArticlePrimary });
|
|
4305
|
+
const stages = createInitialStages();
|
|
3963
4306
|
options.onUpdate?.(cloneStages(stages));
|
|
3964
4307
|
const dryRun = options.dryRun ?? false;
|
|
3965
4308
|
const shouldEnrichLinks = options.enrichLinks ?? false;
|
|
@@ -3968,8 +4311,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
3968
4311
|
const pipelineCustomLinkRaws = options.customLinks ?? [];
|
|
3969
4312
|
const pipelineUnlinks = options.unlinks ?? [];
|
|
3970
4313
|
const pipelineMaxLinks = options.maxLinks;
|
|
3971
|
-
const outputPaths = resolveOutputPaths(
|
|
3972
|
-
const hasArticlePrimary = isArticlePrimary;
|
|
4314
|
+
const outputPaths = resolveOutputPaths();
|
|
3973
4315
|
const stageTracking = /* @__PURE__ */ new Map();
|
|
3974
4316
|
const stageRetryState = /* @__PURE__ */ new Map();
|
|
3975
4317
|
const llmOperationRetryState = /* @__PURE__ */ new Map();
|
|
@@ -4102,66 +4444,69 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4102
4444
|
workingDir
|
|
4103
4445
|
);
|
|
4104
4446
|
}
|
|
4105
|
-
|
|
4447
|
+
stages[1] = {
|
|
4448
|
+
...stages[1],
|
|
4449
|
+
status: "running",
|
|
4450
|
+
detail: `Planning primary ${primaryTarget.contentType} content.`
|
|
4451
|
+
};
|
|
4452
|
+
markStageStarted(stageTracking, "planning");
|
|
4453
|
+
options.onUpdate?.(cloneStages(stages));
|
|
4454
|
+
if (plan) {
|
|
4455
|
+
markStageCompleted(stageTracking, "planning");
|
|
4106
4456
|
stages[1] = {
|
|
4107
4457
|
...stages[1],
|
|
4108
|
-
status: "
|
|
4109
|
-
detail: "
|
|
4458
|
+
status: "succeeded",
|
|
4459
|
+
detail: "Reused saved plan from cached session.",
|
|
4460
|
+
summary: buildPlanSummary(plan),
|
|
4461
|
+
stageAnalytics: snapshotStageAnalytics(stageTracking, "planning")
|
|
4110
4462
|
};
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4463
|
+
} else {
|
|
4464
|
+
if (!contentPlan) {
|
|
4465
|
+
throw new Error("Shared content plan is missing for primary planning stage.");
|
|
4466
|
+
}
|
|
4467
|
+
plan = await planPrimaryContent({
|
|
4468
|
+
idea: input.idea,
|
|
4469
|
+
contentType: primaryTarget.contentType,
|
|
4470
|
+
contentPlan,
|
|
4471
|
+
settings: input.config.settings,
|
|
4472
|
+
markdownOutputDir: writeSession.outputPaths.markdownOutputDir,
|
|
4473
|
+
openRouter,
|
|
4474
|
+
dryRun,
|
|
4475
|
+
onInteraction(interaction) {
|
|
4476
|
+
onLlmInteraction(interaction);
|
|
4477
|
+
},
|
|
4478
|
+
onLlmMetrics(metrics) {
|
|
4479
|
+
recordLlmMetrics(stageTracking, "planning", metrics);
|
|
4125
4480
|
}
|
|
4126
|
-
|
|
4127
|
-
|
|
4481
|
+
});
|
|
4482
|
+
markStageCompleted(stageTracking, "planning");
|
|
4483
|
+
stages[1] = {
|
|
4484
|
+
...stages[1],
|
|
4485
|
+
status: "succeeded",
|
|
4486
|
+
detail: "Plan generated successfully.",
|
|
4487
|
+
summary: buildPlanSummary(plan),
|
|
4488
|
+
stageAnalytics: snapshotStageAnalytics(stageTracking, "planning")
|
|
4489
|
+
};
|
|
4490
|
+
writeSession = await patchWriteSession(
|
|
4491
|
+
{
|
|
4492
|
+
status: "running",
|
|
4493
|
+
lastCompletedStage: "planning",
|
|
4494
|
+
failedStage: null,
|
|
4495
|
+
errorMessage: null,
|
|
4128
4496
|
contentPlan,
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
recordLlmMetrics(stageTracking, "planning", metrics);
|
|
4138
|
-
}
|
|
4139
|
-
});
|
|
4140
|
-
markStageCompleted(stageTracking, "planning");
|
|
4141
|
-
stages[1] = {
|
|
4142
|
-
...stages[1],
|
|
4143
|
-
status: "succeeded",
|
|
4144
|
-
detail: "Plan generated successfully.",
|
|
4145
|
-
summary: `${plan.title} \u2022 ${plan.slug} \u2022 ${plan.sections.length} sections \u2022 ${plan.inlineImages.length + 1} images`,
|
|
4146
|
-
stageAnalytics: snapshotStageAnalytics(stageTracking, "planning")
|
|
4147
|
-
};
|
|
4148
|
-
writeSession = await patchWriteSession(
|
|
4149
|
-
{
|
|
4150
|
-
status: "running",
|
|
4151
|
-
lastCompletedStage: "planning",
|
|
4152
|
-
failedStage: null,
|
|
4153
|
-
errorMessage: null,
|
|
4154
|
-
contentPlan,
|
|
4155
|
-
plan
|
|
4156
|
-
},
|
|
4157
|
-
workingDir
|
|
4158
|
-
);
|
|
4159
|
-
}
|
|
4497
|
+
plan
|
|
4498
|
+
},
|
|
4499
|
+
workingDir
|
|
4500
|
+
);
|
|
4501
|
+
}
|
|
4502
|
+
const isLongForm = isLongFormPlan(plan);
|
|
4503
|
+
if (isLongForm) {
|
|
4504
|
+
const longPlan = plan;
|
|
4160
4505
|
stages[2] = {
|
|
4161
4506
|
...stages[2],
|
|
4162
4507
|
status: "running",
|
|
4163
4508
|
detail: "Writing introduction.",
|
|
4164
|
-
items: buildSectionItems(
|
|
4509
|
+
items: buildSectionItems(longPlan.sections.map((section) => section.title))
|
|
4165
4510
|
};
|
|
4166
4511
|
markStageStarted(stageTracking, "sections");
|
|
4167
4512
|
options.onUpdate?.(cloneStages(stages));
|
|
@@ -4189,7 +4534,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4189
4534
|
} else {
|
|
4190
4535
|
const sectionItemTracking = /* @__PURE__ */ new Map();
|
|
4191
4536
|
text = await writeArticleSections({
|
|
4192
|
-
plan,
|
|
4537
|
+
plan: longPlan,
|
|
4193
4538
|
settings: input.config.settings,
|
|
4194
4539
|
openRouter,
|
|
4195
4540
|
dryRun,
|
|
@@ -4302,8 +4647,8 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4302
4647
|
options.onUpdate?.(cloneStages(stages));
|
|
4303
4648
|
} else {
|
|
4304
4649
|
imagePrompts = await expandImagePrompts({
|
|
4305
|
-
slots: buildImageSlots(
|
|
4306
|
-
planContext:
|
|
4650
|
+
slots: buildImageSlots(longPlan, text.sections, { maxImages: options.maxImages }),
|
|
4651
|
+
planContext: longPlan,
|
|
4307
4652
|
sections: text.sections,
|
|
4308
4653
|
settings: input.config.settings,
|
|
4309
4654
|
openRouter,
|
|
@@ -4364,24 +4709,6 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4364
4709
|
);
|
|
4365
4710
|
}
|
|
4366
4711
|
} else {
|
|
4367
|
-
if (!contentPlan) {
|
|
4368
|
-
throw new Error("Shared content plan is missing for primary content planning stage.");
|
|
4369
|
-
}
|
|
4370
|
-
stages[1] = {
|
|
4371
|
-
...stages[1],
|
|
4372
|
-
status: "running",
|
|
4373
|
-
detail: `Defining primary direction for ${primaryTarget.contentType}.`
|
|
4374
|
-
};
|
|
4375
|
-
markStageStarted(stageTracking, "planning");
|
|
4376
|
-
options.onUpdate?.(cloneStages(stages));
|
|
4377
|
-
markStageCompleted(stageTracking, "planning");
|
|
4378
|
-
stages[1] = {
|
|
4379
|
-
...stages[1],
|
|
4380
|
-
status: "succeeded",
|
|
4381
|
-
detail: `Primary direction locked for ${primaryTarget.contentType}.`,
|
|
4382
|
-
summary: `Primary: ${primaryTarget.contentType}`,
|
|
4383
|
-
stageAnalytics: snapshotStageAnalytics(stageTracking, "planning")
|
|
4384
|
-
};
|
|
4385
4712
|
stages[2] = {
|
|
4386
4713
|
...stages[2],
|
|
4387
4714
|
status: "running",
|
|
@@ -4400,6 +4727,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4400
4727
|
outputCountForType: 1,
|
|
4401
4728
|
articleReferenceMarkdown: void 0,
|
|
4402
4729
|
contentPlan,
|
|
4730
|
+
plan,
|
|
4403
4731
|
settings: input.config.settings,
|
|
4404
4732
|
openRouter,
|
|
4405
4733
|
dryRun,
|
|
@@ -4425,7 +4753,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4425
4753
|
};
|
|
4426
4754
|
markStageStarted(stageTracking, "image-prompts");
|
|
4427
4755
|
options.onUpdate?.(cloneStages(stages));
|
|
4428
|
-
imagePrompts = [buildPrimaryCoverPrompt(contentPlan, primaryTarget.contentType
|
|
4756
|
+
imagePrompts = [buildPrimaryCoverPrompt(plan, contentPlan, primaryTarget.contentType)];
|
|
4429
4757
|
markStageCompleted(stageTracking, "image-prompts");
|
|
4430
4758
|
stages[3] = {
|
|
4431
4759
|
...stages[3],
|
|
@@ -4448,12 +4776,12 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4448
4776
|
options.onUpdate?.(cloneStages(stages));
|
|
4449
4777
|
}
|
|
4450
4778
|
const baseSlug = plan?.slug ?? resolveGenerationSlug(input.idea, contentPlan?.title);
|
|
4451
|
-
const generationDir =
|
|
4779
|
+
const generationDir = path9.join(
|
|
4452
4780
|
writeSession.outputPaths.markdownOutputDir,
|
|
4453
4781
|
buildGenerationDirectoryName(baseSlug)
|
|
4454
4782
|
);
|
|
4455
|
-
await
|
|
4456
|
-
const jobDefinitionPath =
|
|
4783
|
+
await mkdir6(generationDir, { recursive: true });
|
|
4784
|
+
const jobDefinitionPath = path9.join(generationDir, "job.json");
|
|
4457
4785
|
await writeJsonFile(
|
|
4458
4786
|
jobDefinitionPath,
|
|
4459
4787
|
buildRunJobDefinition({
|
|
@@ -4465,14 +4793,15 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4465
4793
|
sourceJob: input.job
|
|
4466
4794
|
})
|
|
4467
4795
|
);
|
|
4468
|
-
const planPath = plan ?
|
|
4796
|
+
const planPath = plan ? path9.join(generationDir, "plan.md") : null;
|
|
4469
4797
|
if (plan && planPath) {
|
|
4470
4798
|
await writeUtf8File(planPath, renderPlanMarkdown(plan));
|
|
4471
4799
|
}
|
|
4472
4800
|
const primaryFilePrefix = toFilePrefix(primaryTarget.contentType);
|
|
4473
|
-
const primaryMarkdownPath =
|
|
4801
|
+
const primaryMarkdownPath = path9.join(generationDir, `${primaryFilePrefix}-1.md`);
|
|
4474
4802
|
const sharedAssetDir = generationDir;
|
|
4475
|
-
if (
|
|
4803
|
+
if (isLongForm) {
|
|
4804
|
+
const longPlan = plan;
|
|
4476
4805
|
if (imageArtifacts) {
|
|
4477
4806
|
markStageCompleted(stageTracking, "images");
|
|
4478
4807
|
stages[4] = {
|
|
@@ -4548,7 +4877,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4548
4877
|
throw new Error("Article generation requested but required article artifacts are missing.");
|
|
4549
4878
|
}
|
|
4550
4879
|
const article = {
|
|
4551
|
-
plan,
|
|
4880
|
+
plan: longPlan,
|
|
4552
4881
|
intro: text.intro,
|
|
4553
4882
|
sections: text.sections,
|
|
4554
4883
|
outro: text.outro,
|
|
@@ -4633,11 +4962,11 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4633
4962
|
}
|
|
4634
4963
|
const coverImage = imageArtifacts?.renderedImages.find((image) => image.kind === "cover") ?? null;
|
|
4635
4964
|
if (coverImage) {
|
|
4636
|
-
primaryMarkdownTemplate = withCoverImage(primaryMarkdownTemplate, coverImage.relativePath, deriveTitleFromIdea2(input.idea));
|
|
4965
|
+
primaryMarkdownTemplate = withCoverImage(primaryMarkdownTemplate, coverImage.relativePath, plan.title || deriveTitleFromIdea2(input.idea));
|
|
4637
4966
|
}
|
|
4638
4967
|
primaryMarkdownTemplate = applyPrimaryTitleHeading(
|
|
4639
4968
|
primaryMarkdownTemplate,
|
|
4640
|
-
contentPlan.title || deriveTitleFromIdea2(input.idea)
|
|
4969
|
+
plan.title || contentPlan.title || deriveTitleFromIdea2(input.idea)
|
|
4641
4970
|
);
|
|
4642
4971
|
}
|
|
4643
4972
|
const markdownPaths = [];
|
|
@@ -4693,7 +5022,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4693
5022
|
})
|
|
4694
5023
|
};
|
|
4695
5024
|
options.onUpdate?.(cloneStages(stages));
|
|
4696
|
-
const markdownPath =
|
|
5025
|
+
const markdownPath = path9.join(generationDir, `${output.filePrefix}-${output.index}.md`);
|
|
4697
5026
|
try {
|
|
4698
5027
|
const content = await writeSingleShotContent({
|
|
4699
5028
|
idea: input.idea,
|
|
@@ -4704,6 +5033,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4704
5033
|
outputCountForType: output.outputCountForType,
|
|
4705
5034
|
articleReferenceMarkdown: primaryMarkdownTemplate ?? void 0,
|
|
4706
5035
|
contentPlan,
|
|
5036
|
+
plan,
|
|
4707
5037
|
settings: input.config.settings,
|
|
4708
5038
|
openRouter,
|
|
4709
5039
|
dryRun,
|
|
@@ -4755,7 +5085,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4755
5085
|
...item,
|
|
4756
5086
|
status: "succeeded",
|
|
4757
5087
|
detail: "Saved markdown output.",
|
|
4758
|
-
summary:
|
|
5088
|
+
summary: path9.basename(markdownPath),
|
|
4759
5089
|
analytics: {
|
|
4760
5090
|
durationMs: itemDurationMs,
|
|
4761
5091
|
costUsd: knownItemCost.usd,
|
|
@@ -4970,10 +5300,24 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4970
5300
|
llmCalls: llmInteractions,
|
|
4971
5301
|
t2iCalls: t2iInteractions
|
|
4972
5302
|
};
|
|
4973
|
-
const analyticsPath =
|
|
4974
|
-
const interactionsPath =
|
|
5303
|
+
const analyticsPath = path9.join(generationDir, "generation.analytics.json");
|
|
5304
|
+
const interactionsPath = path9.join(generationDir, "model.interactions.json");
|
|
4975
5305
|
await writeJsonFile(analyticsPath, analytics);
|
|
4976
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);
|
|
4977
5321
|
const primaryMarkdownPathForArtifact = markdownPaths[0] ?? primaryMarkdownPath;
|
|
4978
5322
|
const artifact = {
|
|
4979
5323
|
title: plan?.title ?? contentPlan.title ?? deriveTitleFromIdea2(input.idea),
|
|
@@ -4987,7 +5331,8 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4987
5331
|
assetDir: sharedAssetDir,
|
|
4988
5332
|
analyticsPath,
|
|
4989
5333
|
interactionsPath,
|
|
4990
|
-
planPath
|
|
5334
|
+
planPath,
|
|
5335
|
+
metaJsonPath
|
|
4991
5336
|
};
|
|
4992
5337
|
writeSession = await patchWriteSession(
|
|
4993
5338
|
{
|
|
@@ -5303,20 +5648,19 @@ function getSecondaryTargets(contentTargets) {
|
|
|
5303
5648
|
count: target.count
|
|
5304
5649
|
}));
|
|
5305
5650
|
}
|
|
5306
|
-
function buildPrimaryCoverPrompt(contentPlan, primaryContentType
|
|
5307
|
-
const markdownExcerpt = primaryMarkdown.replace(/\s+/g, " ").trim().slice(0, 240);
|
|
5651
|
+
function buildPrimaryCoverPrompt(plan, contentPlan, primaryContentType) {
|
|
5308
5652
|
return {
|
|
5309
5653
|
id: "cover",
|
|
5310
5654
|
kind: "cover",
|
|
5311
|
-
description:
|
|
5655
|
+
description: plan.coverImageDescription,
|
|
5312
5656
|
anchorAfterSection: null,
|
|
5313
5657
|
prompt: [
|
|
5314
|
-
|
|
5658
|
+
plan.coverImageDescription,
|
|
5659
|
+
`Content type: ${primaryContentType}`,
|
|
5315
5660
|
`Core angle: ${contentPlan.description}`,
|
|
5316
5661
|
`Audience: ${contentPlan.targetAudience}`,
|
|
5317
5662
|
`Promise: ${contentPlan.corePromise}`,
|
|
5318
5663
|
`Voice: ${contentPlan.voiceNotes}`,
|
|
5319
|
-
`Primary excerpt: ${markdownExcerpt}`,
|
|
5320
5664
|
"Do not include any words, letters, numbers, logos, watermarks, or signage in the image."
|
|
5321
5665
|
].join(" ")
|
|
5322
5666
|
};
|
|
@@ -5380,43 +5724,60 @@ function buildRunJobDefinition(input) {
|
|
|
5380
5724
|
};
|
|
5381
5725
|
}
|
|
5382
5726
|
function renderPlanMarkdown(plan) {
|
|
5383
|
-
const
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
${
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
${
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
## Sections
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
##
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5727
|
+
const lines = [
|
|
5728
|
+
`# ${plan.title}`,
|
|
5729
|
+
"",
|
|
5730
|
+
`**Content type:** ${plan.contentType}`,
|
|
5731
|
+
`**Slug:** ${plan.slug}`,
|
|
5732
|
+
"",
|
|
5733
|
+
"## Description",
|
|
5734
|
+
"",
|
|
5735
|
+
plan.description,
|
|
5736
|
+
""
|
|
5737
|
+
];
|
|
5738
|
+
if (plan.subtitle) {
|
|
5739
|
+
lines.push("## Subtitle", "", plan.subtitle, "");
|
|
5740
|
+
}
|
|
5741
|
+
if (plan.keywords && plan.keywords.length > 0) {
|
|
5742
|
+
lines.push("## Keywords", "", ...plan.keywords.map((kw) => `- ${kw}`), "");
|
|
5743
|
+
}
|
|
5744
|
+
if (plan.introBrief) {
|
|
5745
|
+
lines.push("## Introduction Brief", "", plan.introBrief, "");
|
|
5746
|
+
}
|
|
5747
|
+
if (plan.sections && plan.sections.length > 0) {
|
|
5748
|
+
lines.push("## Sections", "", "| # | Title | Description |", "|---|-------|-------------|");
|
|
5749
|
+
plan.sections.forEach((section, index) => {
|
|
5750
|
+
lines.push(`| ${index + 1} | ${section.title} | ${section.description} |`);
|
|
5751
|
+
});
|
|
5752
|
+
lines.push("");
|
|
5753
|
+
}
|
|
5754
|
+
if (plan.outroBrief) {
|
|
5755
|
+
lines.push("## Outro Brief", "", plan.outroBrief, "");
|
|
5756
|
+
}
|
|
5757
|
+
if (plan.angle) {
|
|
5758
|
+
lines.push("## Angle", "", plan.angle, "");
|
|
5759
|
+
}
|
|
5760
|
+
lines.push("## Image Plan", "");
|
|
5761
|
+
lines.push(`- **Cover:** ${plan.coverImageDescription}`);
|
|
5762
|
+
if (plan.inlineImages && plan.inlineImages.length > 0) {
|
|
5763
|
+
plan.inlineImages.forEach((img, index) => {
|
|
5764
|
+
lines.push(`- **Inline ${index + 1}:** ${img.description} (after section ${img.anchorAfterSection})`);
|
|
5765
|
+
});
|
|
5766
|
+
}
|
|
5767
|
+
lines.push("");
|
|
5768
|
+
return lines.join("\n");
|
|
5769
|
+
}
|
|
5770
|
+
function buildPlanSummary(plan) {
|
|
5771
|
+
const parts = [plan.title, plan.slug];
|
|
5772
|
+
if (plan.sections && plan.sections.length > 0) {
|
|
5773
|
+
parts.push(`${plan.sections.length} sections`);
|
|
5774
|
+
}
|
|
5775
|
+
if (plan.inlineImages && plan.inlineImages.length > 0) {
|
|
5776
|
+
parts.push(`${plan.inlineImages.length + 1} images`);
|
|
5777
|
+
} else {
|
|
5778
|
+
parts.push("1 image");
|
|
5779
|
+
}
|
|
5780
|
+
return parts.join(" \u2022 ");
|
|
5420
5781
|
}
|
|
5421
5782
|
function asWriteStageId(stageId) {
|
|
5422
5783
|
if (stageId === "shared-plan" || stageId === "planning" || stageId === "sections" || stageId === "image-prompts" || stageId === "images" || stageId === "output" || stageId === "links") {
|
|
@@ -5445,7 +5806,7 @@ function parsePipelineCustomLinks(rawLinks, unlinks) {
|
|
|
5445
5806
|
|
|
5446
5807
|
// src/cli/commands/links.ts
|
|
5447
5808
|
import { readFile as readFile6, stat as stat3 } from "fs/promises";
|
|
5448
|
-
import
|
|
5809
|
+
import path10 from "path";
|
|
5449
5810
|
async function runLinksCommand(options, dependencies = {}) {
|
|
5450
5811
|
const slug = normalizeSlug2(options.slug);
|
|
5451
5812
|
const mode = normalizeMode(options.mode);
|
|
@@ -5454,9 +5815,9 @@ async function runLinksCommand(options, dependencies = {}) {
|
|
|
5454
5815
|
const resolved = await resolveRunInput({
|
|
5455
5816
|
idea: `Enrich links for ${slug}`
|
|
5456
5817
|
});
|
|
5457
|
-
const markdownPath = await resolveMarkdownPathForSlug2(
|
|
5818
|
+
const markdownPath = await resolveMarkdownPathForSlug2(slug, cwd2);
|
|
5458
5819
|
const frontmatter = await readFrontmatter(markdownPath);
|
|
5459
|
-
const fileId =
|
|
5820
|
+
const fileId = path10.parse(markdownPath).name;
|
|
5460
5821
|
const articleTitle = frontmatter.title ?? toTitleFromSlug(slug);
|
|
5461
5822
|
const articleDescription = frontmatter.description ?? "";
|
|
5462
5823
|
const openRouterApiKey = resolved.config.secrets.openRouterApiKey;
|
|
@@ -5524,16 +5885,16 @@ function normalizeSlug2(rawSlug) {
|
|
|
5524
5885
|
}
|
|
5525
5886
|
return slug;
|
|
5526
5887
|
}
|
|
5527
|
-
async function resolveMarkdownPathForSlug2(
|
|
5528
|
-
const outputPaths = resolveOutputPaths(
|
|
5529
|
-
const directPath =
|
|
5888
|
+
async function resolveMarkdownPathForSlug2(slug, cwd2) {
|
|
5889
|
+
const outputPaths = resolveOutputPaths();
|
|
5890
|
+
const directPath = path10.join(outputPaths.markdownOutputDir, `${slug}.md`);
|
|
5530
5891
|
if (await isReadableFile(directPath)) {
|
|
5531
5892
|
return directPath;
|
|
5532
5893
|
}
|
|
5533
5894
|
const markdownFiles = await listMarkdownFilesRecursively(outputPaths.markdownOutputDir);
|
|
5534
5895
|
const matches = [];
|
|
5535
5896
|
for (const candidate of markdownFiles) {
|
|
5536
|
-
if (
|
|
5897
|
+
if (path10.basename(candidate) === `${slug}.md`) {
|
|
5537
5898
|
matches.push(candidate);
|
|
5538
5899
|
continue;
|
|
5539
5900
|
}
|
|
@@ -5667,7 +6028,7 @@ function readErrorCode2(error) {
|
|
|
5667
6028
|
return typeof code === "string" ? code : null;
|
|
5668
6029
|
}
|
|
5669
6030
|
function formatRelativePath2(cwd2, targetPath) {
|
|
5670
|
-
const relativePath =
|
|
6031
|
+
const relativePath = path10.relative(cwd2, targetPath);
|
|
5671
6032
|
return relativePath.length > 0 ? relativePath : targetPath;
|
|
5672
6033
|
}
|
|
5673
6034
|
function logProgress(event, log) {
|
|
@@ -5706,8 +6067,8 @@ function resolveCustomLinks(existing, addRaw, removeExpressions) {
|
|
|
5706
6067
|
}
|
|
5707
6068
|
|
|
5708
6069
|
// src/cli/commands/export.ts
|
|
5709
|
-
import { copyFile, mkdir as
|
|
5710
|
-
import
|
|
6070
|
+
import { copyFile as copyFile2, mkdir as mkdir7, readFile as readFile8, stat as stat5, writeFile as writeFile6 } from "fs/promises";
|
|
6071
|
+
import path12 from "path";
|
|
5711
6072
|
|
|
5712
6073
|
// src/output/enrichMarkdownWithLinks.ts
|
|
5713
6074
|
function enrichMarkdownWithLinks(markdown, links) {
|
|
@@ -5764,7 +6125,7 @@ function escapeRegExp(value2) {
|
|
|
5764
6125
|
|
|
5765
6126
|
// src/server/previewHelpers.ts
|
|
5766
6127
|
import { readdir, stat as stat4, readFile as readFile7 } from "fs/promises";
|
|
5767
|
-
import
|
|
6128
|
+
import path11 from "path";
|
|
5768
6129
|
var DEFAULT_PORT = 4173;
|
|
5769
6130
|
var CONTENT_TYPE_ORDER = ["article", "blog-post", "x-thread", "x-post", "linkedin-post", "reddit-post", "newsletter"];
|
|
5770
6131
|
var FILE_PREFIX_TO_CONTENT_TYPE = {
|
|
@@ -5822,8 +6183,8 @@ function extractHeadingTitle(markdown) {
|
|
|
5822
6183
|
}
|
|
5823
6184
|
async function resolveMarkdownPath(markdownPathArg, markdownOutputDir, cwd2) {
|
|
5824
6185
|
if (markdownPathArg) {
|
|
5825
|
-
const resolved =
|
|
5826
|
-
if (
|
|
6186
|
+
const resolved = path11.isAbsolute(markdownPathArg) ? markdownPathArg : path11.resolve(cwd2, markdownPathArg);
|
|
6187
|
+
if (path11.extname(resolved).toLowerCase() !== ".md") {
|
|
5827
6188
|
throw new Error(`Expected a markdown file (.md), received: ${resolved}`);
|
|
5828
6189
|
}
|
|
5829
6190
|
await assertFileExists(resolved, "Could not find markdown file");
|
|
@@ -5867,7 +6228,7 @@ function extractCoverImageUrl(markdown) {
|
|
|
5867
6228
|
async function extractArticleMetadata(markdownPath) {
|
|
5868
6229
|
const markdown = await readFile7(markdownPath, "utf8");
|
|
5869
6230
|
const fileStat = await stat4(markdownPath);
|
|
5870
|
-
const slug = extractFrontmatterSlug(markdown) ??
|
|
6231
|
+
const slug = extractFrontmatterSlug(markdown) ?? path11.basename(markdownPath, ".md");
|
|
5871
6232
|
const title = extractHeadingTitle(stripFrontmatter2(markdown)) ?? slug;
|
|
5872
6233
|
const body = stripFrontmatter2(markdown);
|
|
5873
6234
|
const previewSnippet = body.replace(/[#\[\]()!\-*_`]/g, "").trim().substring(0, 150);
|
|
@@ -5932,16 +6293,16 @@ async function listAllGenerations(markdownOutputDir) {
|
|
|
5932
6293
|
return generations;
|
|
5933
6294
|
}
|
|
5934
6295
|
function deriveGenerationId(markdownPath, markdownOutputDir) {
|
|
5935
|
-
const relative =
|
|
5936
|
-
const normalized = relative.split(
|
|
6296
|
+
const relative = path11.relative(markdownOutputDir, markdownPath);
|
|
6297
|
+
const normalized = relative.split(path11.sep).join("/");
|
|
5937
6298
|
if (!normalized || normalized.startsWith("../")) {
|
|
5938
|
-
return
|
|
6299
|
+
return path11.basename(markdownPath, ".md");
|
|
5939
6300
|
}
|
|
5940
6301
|
const segments = normalized.split("/").filter(Boolean);
|
|
5941
6302
|
if (segments.length <= 1) {
|
|
5942
|
-
return
|
|
6303
|
+
return path11.basename(markdownPath, ".md");
|
|
5943
6304
|
}
|
|
5944
|
-
return segments[0] ??
|
|
6305
|
+
return segments[0] ?? path11.basename(markdownPath, ".md");
|
|
5945
6306
|
}
|
|
5946
6307
|
async function findMarkdownFiles(markdownOutputDir) {
|
|
5947
6308
|
const files = [];
|
|
@@ -5958,7 +6319,7 @@ async function findMarkdownFiles(markdownOutputDir) {
|
|
|
5958
6319
|
continue;
|
|
5959
6320
|
}
|
|
5960
6321
|
for (const entry of entries) {
|
|
5961
|
-
const fullPath =
|
|
6322
|
+
const fullPath = path11.join(current, entry.name);
|
|
5962
6323
|
if (entry.isDirectory()) {
|
|
5963
6324
|
stack.push(fullPath);
|
|
5964
6325
|
continue;
|
|
@@ -5972,7 +6333,7 @@ async function findMarkdownFiles(markdownOutputDir) {
|
|
|
5972
6333
|
}
|
|
5973
6334
|
function deriveOutputIdentity(markdownPath, markdownOutputDir) {
|
|
5974
6335
|
const generationId = deriveGenerationId(markdownPath, markdownOutputDir);
|
|
5975
|
-
const fileBase =
|
|
6336
|
+
const fileBase = path11.basename(markdownPath, ".md");
|
|
5976
6337
|
const parsed = fileBase.match(/^([a-z0-9-]+)-(\d+)$/i);
|
|
5977
6338
|
if (!parsed || !parsed[1] || !parsed[2]) {
|
|
5978
6339
|
return {
|
|
@@ -6008,11 +6369,11 @@ function toContentTypeLabel(contentType) {
|
|
|
6008
6369
|
}
|
|
6009
6370
|
async function resolvePrimaryContentType(outputs) {
|
|
6010
6371
|
const fallback = outputs.find((output) => output.contentType === "article")?.contentType ?? outputs[0]?.contentType ?? "article";
|
|
6011
|
-
const generationDir =
|
|
6372
|
+
const generationDir = path11.dirname(outputs[0]?.sourcePath ?? "");
|
|
6012
6373
|
if (!generationDir) {
|
|
6013
6374
|
return fallback;
|
|
6014
6375
|
}
|
|
6015
|
-
const jobPath =
|
|
6376
|
+
const jobPath = path11.join(generationDir, "job.json");
|
|
6016
6377
|
try {
|
|
6017
6378
|
const raw = await readFile7(jobPath, "utf8");
|
|
6018
6379
|
const parsed = JSON.parse(raw);
|
|
@@ -6028,12 +6389,17 @@ async function resolvePrimaryContentType(outputs) {
|
|
|
6028
6389
|
}
|
|
6029
6390
|
|
|
6030
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
|
+
]);
|
|
6031
6397
|
async function runOutputCommand(options, dependencies = {}) {
|
|
6032
6398
|
const cwd2 = dependencies.cwd ?? process.cwd();
|
|
6033
6399
|
const log = dependencies.log ?? ((message) => console.log(message));
|
|
6034
6400
|
const targetIndex = options.index ?? 1;
|
|
6035
6401
|
const resolved = await resolveRunInput({ idea: `Export generation ${options.generationId}` });
|
|
6036
|
-
const outputPaths = resolveOutputPaths(
|
|
6402
|
+
const outputPaths = resolveOutputPaths();
|
|
6037
6403
|
const generations = await listAllGenerations(outputPaths.markdownOutputDir);
|
|
6038
6404
|
const generation = resolveGeneration(generations, options.generationId);
|
|
6039
6405
|
const articleOutputs = generation.outputs.filter((output) => output.contentType === generation.primaryContentType);
|
|
@@ -6051,44 +6417,36 @@ async function runOutputCommand(options, dependencies = {}) {
|
|
|
6051
6417
|
}
|
|
6052
6418
|
const sourceMarkdownPath = articleOutput.sourcePath;
|
|
6053
6419
|
const sourceMarkdown = await readFile8(sourceMarkdownPath, "utf8");
|
|
6054
|
-
const slug = extractFrontmatterSlug2(sourceMarkdown) ??
|
|
6420
|
+
const slug = extractFrontmatterSlug2(sourceMarkdown) ?? path12.basename(sourceMarkdownPath, ".md");
|
|
6055
6421
|
const exportFilename = `${slug}.md`;
|
|
6056
6422
|
const destinationDir = await resolveDestinationDir(options.destinationPath, cwd2);
|
|
6057
|
-
const destinationFilePath =
|
|
6423
|
+
const destinationFilePath = path12.join(destinationDir, exportFilename);
|
|
6058
6424
|
if (!options.overwrite && await fileExists2(destinationFilePath)) {
|
|
6059
6425
|
throw new ReportedError(
|
|
6060
6426
|
`Export file already exists: ${destinationFilePath}. Pass --overwrite to replace it.`
|
|
6061
6427
|
);
|
|
6062
6428
|
}
|
|
6063
|
-
await
|
|
6429
|
+
await mkdir7(destinationDir, { recursive: true });
|
|
6064
6430
|
const links = await loadLinks(sourceMarkdownPath);
|
|
6065
6431
|
const enrichedMarkdown = enrichWithFrontmatterGuard(sourceMarkdown, links);
|
|
6066
|
-
const sourceDir =
|
|
6067
|
-
const
|
|
6068
|
-
const
|
|
6069
|
-
for (const
|
|
6070
|
-
const
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
}
|
|
6079
|
-
if (!imageStat.isFile()) {
|
|
6080
|
-
throw new ReportedError(`Referenced image path is not a file: ${absoluteImageSrc}.`);
|
|
6081
|
-
}
|
|
6082
|
-
const destImagePath = path11.join(destinationDir, relImagePath);
|
|
6083
|
-
await mkdir6(path11.dirname(destImagePath), { recursive: true });
|
|
6084
|
-
await copyFile(absoluteImageSrc, destImagePath);
|
|
6085
|
-
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);
|
|
6086
6444
|
}
|
|
6087
6445
|
await writeFile6(destinationFilePath, enrichedMarkdown, "utf8");
|
|
6088
|
-
const relDest =
|
|
6446
|
+
const relDest = path12.relative(cwd2, destinationFilePath);
|
|
6089
6447
|
log(`Exported "${generation.id}" (${generation.primaryContentType} #${targetIndex}) \u2192 ${relDest}`);
|
|
6090
|
-
if (
|
|
6091
|
-
log(`Copied ${
|
|
6448
|
+
if (copiedFiles.length > 0) {
|
|
6449
|
+
log(`Copied ${copiedFiles.length} file${copiedFiles.length === 1 ? "" : "s"}: ${copiedFiles.join(", ")}`);
|
|
6092
6450
|
}
|
|
6093
6451
|
if (links.length > 0) {
|
|
6094
6452
|
log(`Injected ${links.length} inline link${links.length === 1 ? "" : "s"}.`);
|
|
@@ -6110,7 +6468,7 @@ function resolveGeneration(generations, generationId) {
|
|
|
6110
6468
|
);
|
|
6111
6469
|
}
|
|
6112
6470
|
async function resolveDestinationDir(destinationPath, cwd2) {
|
|
6113
|
-
const resolved =
|
|
6471
|
+
const resolved = path12.isAbsolute(destinationPath) ? destinationPath : path12.resolve(cwd2, destinationPath);
|
|
6114
6472
|
return resolved;
|
|
6115
6473
|
}
|
|
6116
6474
|
async function fileExists2(filePath) {
|
|
@@ -6180,22 +6538,6 @@ function extractFrontmatterSlug2(markdown) {
|
|
|
6180
6538
|
const unquoted = rawSlug.replace(/^['""]|['""]$/g, "").trim();
|
|
6181
6539
|
return unquoted.length > 0 ? unquoted : null;
|
|
6182
6540
|
}
|
|
6183
|
-
function extractLocalImagePaths(markdown) {
|
|
6184
|
-
const imagePattern = /!\[[^\]]*\]\(([^)]+)\)/g;
|
|
6185
|
-
const paths = [];
|
|
6186
|
-
let match;
|
|
6187
|
-
while ((match = imagePattern.exec(markdown)) !== null) {
|
|
6188
|
-
const rawPath = match[1]?.trim();
|
|
6189
|
-
if (!rawPath) {
|
|
6190
|
-
continue;
|
|
6191
|
-
}
|
|
6192
|
-
if (rawPath.startsWith("http://") || rawPath.startsWith("https://") || rawPath.startsWith("data:") || rawPath.startsWith("/") || rawPath.startsWith("#")) {
|
|
6193
|
-
continue;
|
|
6194
|
-
}
|
|
6195
|
-
paths.push(rawPath);
|
|
6196
|
-
}
|
|
6197
|
-
return paths;
|
|
6198
|
-
}
|
|
6199
6541
|
|
|
6200
6542
|
// src/cli/commands/writeTargetSpecs.ts
|
|
6201
6543
|
function parseTargetSpec(spec) {
|
|
@@ -6642,12 +6984,6 @@ import { Box, Text, useApp, useInput } from "ink";
|
|
|
6642
6984
|
import SelectInput from "ink-select-input";
|
|
6643
6985
|
import TextInput from "ink-text-input";
|
|
6644
6986
|
|
|
6645
|
-
// src/images/limnModelCatalog.ts
|
|
6646
|
-
import { getSupportedModelCatalog } from "@telepat/limn";
|
|
6647
|
-
function getLimnGenerationModels() {
|
|
6648
|
-
return getSupportedModelCatalog().filter((entry) => entry.generationEnabled);
|
|
6649
|
-
}
|
|
6650
|
-
|
|
6651
6987
|
// src/cli/flows/settingsFlowLogic.ts
|
|
6652
6988
|
function handleMenuSelect(action, settings, secrets, setEditing, setShowModelSelect, setMenuMode, onDone, exit) {
|
|
6653
6989
|
switch (action) {
|
|
@@ -6672,12 +7008,6 @@ function handleMenuSelect(action, settings, secrets, setEditing, setShowModelSel
|
|
|
6672
7008
|
case "topP":
|
|
6673
7009
|
setEditing({ key: action, label: "Top p", value: String(settings.modelSettings.topP) });
|
|
6674
7010
|
return;
|
|
6675
|
-
case "markdownOutputDir":
|
|
6676
|
-
setEditing({ key: action, label: "Markdown output directory", value: settings.markdownOutputDir });
|
|
6677
|
-
return;
|
|
6678
|
-
case "assetOutputDir":
|
|
6679
|
-
setEditing({ key: action, label: "Asset output directory", value: settings.assetOutputDir });
|
|
6680
|
-
return;
|
|
6681
7011
|
case "t2i-settings":
|
|
6682
7012
|
setMenuMode("t2i");
|
|
6683
7013
|
return;
|
|
@@ -6691,6 +7021,13 @@ function handleMenuSelect(action, settings, secrets, setEditing, setShowModelSel
|
|
|
6691
7021
|
value: JSON.stringify(settings.t2i.inputOverrides, null, 2)
|
|
6692
7022
|
});
|
|
6693
7023
|
return;
|
|
7024
|
+
case "t2i-replicate-model-id":
|
|
7025
|
+
setEditing({
|
|
7026
|
+
key: action,
|
|
7027
|
+
label: "T2I Replicate model ID override (blank to clear)",
|
|
7028
|
+
value: settings.t2i.replicateModelId ?? ""
|
|
7029
|
+
});
|
|
7030
|
+
return;
|
|
6694
7031
|
case "t2i-back":
|
|
6695
7032
|
setMenuMode("main");
|
|
6696
7033
|
return;
|
|
@@ -6761,14 +7098,6 @@ function applyEdit(action, value2, settings, secrets, setSettings, setSecrets) {
|
|
|
6761
7098
|
});
|
|
6762
7099
|
return true;
|
|
6763
7100
|
}
|
|
6764
|
-
if (action === "markdownOutputDir") {
|
|
6765
|
-
setSettings({ ...settings, markdownOutputDir: value2.trim() || settings.markdownOutputDir });
|
|
6766
|
-
return true;
|
|
6767
|
-
}
|
|
6768
|
-
if (action === "assetOutputDir") {
|
|
6769
|
-
setSettings({ ...settings, assetOutputDir: value2.trim() || settings.assetOutputDir });
|
|
6770
|
-
return true;
|
|
6771
|
-
}
|
|
6772
7101
|
if (action === "t2i-input-overrides") {
|
|
6773
7102
|
const trimmed = value2.trim();
|
|
6774
7103
|
if (trimmed.length === 0) {
|
|
@@ -6798,6 +7127,17 @@ function applyEdit(action, value2, settings, secrets, setSettings, setSecrets) {
|
|
|
6798
7127
|
return false;
|
|
6799
7128
|
}
|
|
6800
7129
|
}
|
|
7130
|
+
if (action === "t2i-replicate-model-id") {
|
|
7131
|
+
const trimmed = value2.trim();
|
|
7132
|
+
setSettings({
|
|
7133
|
+
...settings,
|
|
7134
|
+
t2i: {
|
|
7135
|
+
...settings.t2i,
|
|
7136
|
+
replicateModelId: trimmed.length > 0 ? trimmed : void 0
|
|
7137
|
+
}
|
|
7138
|
+
});
|
|
7139
|
+
return true;
|
|
7140
|
+
}
|
|
6801
7141
|
return false;
|
|
6802
7142
|
}
|
|
6803
7143
|
function parseNumberOrFallback(value2, fallback) {
|
|
@@ -6852,12 +7192,19 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
|
|
|
6852
7192
|
const count = Object.keys(overrides).length;
|
|
6853
7193
|
return count === 0 ? "none" : `${count} override${count === 1 ? "" : "s"}`;
|
|
6854
7194
|
};
|
|
7195
|
+
const formatReplicateOverrideSummary = (replicateModelId) => {
|
|
7196
|
+
return replicateModelId && replicateModelId.length > 0 ? replicateModelId : "auto";
|
|
7197
|
+
};
|
|
6855
7198
|
const menuItems = useMemo(() => {
|
|
6856
7199
|
const t2iSubmenu = [
|
|
6857
7200
|
{
|
|
6858
7201
|
label: `T2I model: ${currentModelEntry?.displayName ?? settings.t2i.modelId}`,
|
|
6859
7202
|
value: "t2i-model"
|
|
6860
7203
|
},
|
|
7204
|
+
{
|
|
7205
|
+
label: `T2I Replicate model override: ${formatReplicateOverrideSummary(settings.t2i.replicateModelId)}`,
|
|
7206
|
+
value: "t2i-replicate-model-id"
|
|
7207
|
+
},
|
|
6861
7208
|
{
|
|
6862
7209
|
label: `T2I input overrides: ${formatT2iOverridesSummary(settings.t2i.inputOverrides)}`,
|
|
6863
7210
|
value: "t2i-input-overrides"
|
|
@@ -6899,14 +7246,6 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
|
|
|
6899
7246
|
label: `Top p: ${settings.modelSettings.topP}`,
|
|
6900
7247
|
value: "topP"
|
|
6901
7248
|
},
|
|
6902
|
-
{
|
|
6903
|
-
label: `Markdown output directory: ${settings.markdownOutputDir}`,
|
|
6904
|
-
value: "markdownOutputDir"
|
|
6905
|
-
},
|
|
6906
|
-
{
|
|
6907
|
-
label: `Asset output directory: ${settings.assetOutputDir}`,
|
|
6908
|
-
value: "assetOutputDir"
|
|
6909
|
-
},
|
|
6910
7249
|
{
|
|
6911
7250
|
label: `T2I settings: ${currentModelEntry?.displayName ?? settings.t2i.modelId}`,
|
|
6912
7251
|
value: "t2i-settings"
|
|
@@ -6938,6 +7277,7 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
|
|
|
6938
7277
|
...current,
|
|
6939
7278
|
t2i: {
|
|
6940
7279
|
modelId: item.value,
|
|
7280
|
+
replicateModelId: current.t2i.replicateModelId && isReplicateModelIdForFamily(item.value, current.t2i.replicateModelId) ? current.t2i.replicateModelId : void 0,
|
|
6941
7281
|
inputOverrides: {}
|
|
6942
7282
|
}
|
|
6943
7283
|
}));
|
|
@@ -7059,7 +7399,7 @@ async function openSettings() {
|
|
|
7059
7399
|
}
|
|
7060
7400
|
|
|
7061
7401
|
// src/cli/commands/serve.ts
|
|
7062
|
-
import
|
|
7402
|
+
import path14 from "path";
|
|
7063
7403
|
import { spawn } from "child_process";
|
|
7064
7404
|
|
|
7065
7405
|
// src/server/previewServer.ts
|
|
@@ -7067,7 +7407,7 @@ import { execFile } from "child_process";
|
|
|
7067
7407
|
import { promisify } from "util";
|
|
7068
7408
|
import { readFile as readFile9, stat as stat6 } from "fs/promises";
|
|
7069
7409
|
import { watch as fsWatch } from "fs";
|
|
7070
|
-
import
|
|
7410
|
+
import path13 from "path";
|
|
7071
7411
|
import { fileURLToPath } from "url";
|
|
7072
7412
|
import express from "express";
|
|
7073
7413
|
import { marked } from "marked";
|
|
@@ -7165,7 +7505,7 @@ async function startPreviewServer(options) {
|
|
|
7165
7505
|
if (options.watch) {
|
|
7166
7506
|
let html2;
|
|
7167
7507
|
try {
|
|
7168
|
-
html2 = await readFile9(
|
|
7508
|
+
html2 = await readFile9(path13.join(previewClientDir, "index.html"), "utf8");
|
|
7169
7509
|
} catch {
|
|
7170
7510
|
res.status(200).type("html").send(
|
|
7171
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>`
|
|
@@ -7176,7 +7516,7 @@ async function startPreviewServer(options) {
|
|
|
7176
7516
|
const injected = html2.replace("</body>", `${reloadScript}</body>`);
|
|
7177
7517
|
res.status(200).type("html").send(injected);
|
|
7178
7518
|
} else {
|
|
7179
|
-
res.status(200).sendFile(
|
|
7519
|
+
res.status(200).sendFile(path13.join(previewClientDir, "index.html"));
|
|
7180
7520
|
}
|
|
7181
7521
|
return;
|
|
7182
7522
|
}
|
|
@@ -7247,15 +7587,17 @@ async function getArticleContent(generationId, markdownOutputDir) {
|
|
|
7247
7587
|
};
|
|
7248
7588
|
})
|
|
7249
7589
|
);
|
|
7250
|
-
const generationDir =
|
|
7590
|
+
const generationDir = path13.dirname(generation.outputs[0]?.sourcePath ?? "");
|
|
7251
7591
|
const interactions = generationDir ? await loadSavedInteractions(generationDir) : { llmCalls: [], t2iCalls: [] };
|
|
7252
7592
|
const analyticsSummary = generationDir ? await loadSavedAnalyticsSummary(generationDir) : null;
|
|
7593
|
+
const metaJson = generationDir ? await loadSavedMetaJson(generationDir) : null;
|
|
7253
7594
|
return {
|
|
7254
7595
|
title: generation.title,
|
|
7255
7596
|
generationId: generation.id,
|
|
7256
7597
|
sourcePath,
|
|
7257
7598
|
interactions,
|
|
7258
7599
|
analyticsSummary,
|
|
7600
|
+
metaJson,
|
|
7259
7601
|
outputs
|
|
7260
7602
|
};
|
|
7261
7603
|
}
|
|
@@ -7276,7 +7618,7 @@ async function resolveActivePreviewArticle(preferredMarkdownPath, markdownOutput
|
|
|
7276
7618
|
};
|
|
7277
7619
|
}
|
|
7278
7620
|
function resolveGenerationSourcePath(generation, markdownOutputDir) {
|
|
7279
|
-
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);
|
|
7280
7622
|
}
|
|
7281
7623
|
function isMissingFileError(error) {
|
|
7282
7624
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
@@ -7315,7 +7657,7 @@ async function loadSavedLinks(markdownPath) {
|
|
|
7315
7657
|
}
|
|
7316
7658
|
}
|
|
7317
7659
|
async function loadSavedInteractions(generationDir) {
|
|
7318
|
-
const interactionsPath =
|
|
7660
|
+
const interactionsPath = path13.join(generationDir, "model.interactions.json");
|
|
7319
7661
|
try {
|
|
7320
7662
|
const raw = await readFile9(interactionsPath, "utf8");
|
|
7321
7663
|
const parsed = JSON.parse(raw);
|
|
@@ -7333,7 +7675,7 @@ async function loadSavedInteractions(generationDir) {
|
|
|
7333
7675
|
}
|
|
7334
7676
|
}
|
|
7335
7677
|
async function loadSavedAnalyticsSummary(generationDir) {
|
|
7336
|
-
const analyticsPath =
|
|
7678
|
+
const analyticsPath = path13.join(generationDir, "generation.analytics.json");
|
|
7337
7679
|
try {
|
|
7338
7680
|
const raw = await readFile9(analyticsPath, "utf8");
|
|
7339
7681
|
const parsed = JSON.parse(raw);
|
|
@@ -7356,6 +7698,15 @@ async function loadSavedAnalyticsSummary(generationDir) {
|
|
|
7356
7698
|
return null;
|
|
7357
7699
|
}
|
|
7358
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
|
+
}
|
|
7359
7710
|
async function getPreviewBootstrapData(preferredMarkdownPath, markdownOutputDir) {
|
|
7360
7711
|
const activeArticle = await resolveActivePreviewArticle(preferredMarkdownPath, markdownOutputDir);
|
|
7361
7712
|
const emptyStateMessage = activeArticle ? null : `No generated content found in ${markdownOutputDir}. Run ideon write "your idea" first.`;
|
|
@@ -7367,14 +7718,14 @@ async function getPreviewBootstrapData(preferredMarkdownPath, markdownOutputDir)
|
|
|
7367
7718
|
};
|
|
7368
7719
|
}
|
|
7369
7720
|
async function resolvePreviewClientBuildDir() {
|
|
7370
|
-
const currentDir =
|
|
7721
|
+
const currentDir = path13.dirname(fileURLToPath(import.meta.url));
|
|
7371
7722
|
const candidates = [
|
|
7372
|
-
|
|
7373
|
-
|
|
7723
|
+
path13.resolve(currentDir, "preview"),
|
|
7724
|
+
path13.resolve(currentDir, "../../dist/preview")
|
|
7374
7725
|
];
|
|
7375
7726
|
for (const candidate of candidates) {
|
|
7376
7727
|
try {
|
|
7377
|
-
const indexStat = await stat6(
|
|
7728
|
+
const indexStat = await stat6(path13.join(candidate, "index.html"));
|
|
7378
7729
|
if (indexStat.isFile()) {
|
|
7379
7730
|
return candidate;
|
|
7380
7731
|
}
|
|
@@ -7436,17 +7787,17 @@ async function resolveGenerationAssetPath(generationId, rawAssetPath, markdownOu
|
|
|
7436
7787
|
throw new MissingArticleError(`Generation "${generationId}" no longer exists.`);
|
|
7437
7788
|
}
|
|
7438
7789
|
const decodedAssetPath = decodeURIComponent(rawAssetPath);
|
|
7439
|
-
const normalizedRelative =
|
|
7440
|
-
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)) {
|
|
7441
7792
|
throw new Error("Invalid generation asset path.");
|
|
7442
7793
|
}
|
|
7443
|
-
const generationDir =
|
|
7794
|
+
const generationDir = path13.dirname(generation.outputs[0]?.sourcePath ?? "");
|
|
7444
7795
|
if (!generationDir) {
|
|
7445
7796
|
throw new MissingArticleError(`Generation "${generationId}" has no source directory.`);
|
|
7446
7797
|
}
|
|
7447
|
-
const resolvedPath =
|
|
7448
|
-
const relativeToGeneration =
|
|
7449
|
-
if (relativeToGeneration.startsWith("..") ||
|
|
7798
|
+
const resolvedPath = path13.resolve(generationDir, normalizedRelative);
|
|
7799
|
+
const relativeToGeneration = path13.relative(generationDir, resolvedPath);
|
|
7800
|
+
if (relativeToGeneration.startsWith("..") || path13.isAbsolute(relativeToGeneration)) {
|
|
7450
7801
|
throw new Error("Invalid generation asset path.");
|
|
7451
7802
|
}
|
|
7452
7803
|
try {
|
|
@@ -8920,17 +9271,11 @@ function escapeHtml(value2) {
|
|
|
8920
9271
|
|
|
8921
9272
|
// src/cli/commands/serve.ts
|
|
8922
9273
|
async function runServeCommand(options) {
|
|
8923
|
-
const
|
|
8924
|
-
const mergedSettings = appSettingsSchema.parse({
|
|
8925
|
-
...savedSettings,
|
|
8926
|
-
...envSettings.markdownOutputDir ? { markdownOutputDir: envSettings.markdownOutputDir } : {},
|
|
8927
|
-
...envSettings.assetOutputDir ? { assetOutputDir: envSettings.assetOutputDir } : {}
|
|
8928
|
-
});
|
|
8929
|
-
const outputPaths = resolveOutputPaths(mergedSettings);
|
|
9274
|
+
const outputPaths = resolveOutputPaths();
|
|
8930
9275
|
const markdownPath = await resolveMarkdownPath(options.markdownPath, outputPaths.markdownOutputDir, process.cwd());
|
|
8931
9276
|
const port = parsePort(options.port);
|
|
8932
9277
|
if (options.watch) {
|
|
8933
|
-
const viteBin =
|
|
9278
|
+
const viteBin = path14.resolve(process.cwd(), "node_modules", ".bin", "vite");
|
|
8934
9279
|
const viteProcess = spawn(viteBin, ["build", "--watch"], {
|
|
8935
9280
|
stdio: "inherit",
|
|
8936
9281
|
shell: process.platform === "win32"
|
|
@@ -8956,8 +9301,8 @@ async function runServeCommand(options) {
|
|
|
8956
9301
|
openBrowser: options.openBrowser,
|
|
8957
9302
|
watch: options.watch
|
|
8958
9303
|
});
|
|
8959
|
-
const relativeArticle =
|
|
8960
|
-
const relativeAssets =
|
|
9304
|
+
const relativeArticle = path14.relative(process.cwd(), markdownPath);
|
|
9305
|
+
const relativeAssets = path14.relative(process.cwd(), outputPaths.assetOutputDir);
|
|
8961
9306
|
console.log(`Previewing ${relativeArticle || markdownPath}`);
|
|
8962
9307
|
console.log(`Serving assets from ${relativeAssets || outputPaths.assetOutputDir}`);
|
|
8963
9308
|
console.log(`Open ${server.url}`);
|
|
@@ -9507,6 +9852,7 @@ async function renderPlainPipeline(input, dryRun, enrichLinks2, runMode, links,
|
|
|
9507
9852
|
title: result.artifact.title,
|
|
9508
9853
|
slug: result.artifact.slug
|
|
9509
9854
|
});
|
|
9855
|
+
return result;
|
|
9510
9856
|
} catch (error) {
|
|
9511
9857
|
const message = error instanceof Error ? withWriteResumeHint(error.message) : withWriteResumeHint("Pipeline failed.");
|
|
9512
9858
|
await notifyWriteFailed({
|
|
@@ -9823,13 +10169,12 @@ function WriteApp({
|
|
|
9823
10169
|
unlinks,
|
|
9824
10170
|
maxLinks,
|
|
9825
10171
|
maxImages,
|
|
10172
|
+
onSuccess,
|
|
9826
10173
|
onError
|
|
9827
10174
|
}) {
|
|
9828
10175
|
const { exit } = useApp3();
|
|
9829
10176
|
const [stages, setStages] = useState4(
|
|
9830
|
-
() => createInitialStages(
|
|
9831
|
-
isArticlePrimary: input.config.settings.contentTargets.some((target) => target.role === "primary" && target.contentType === "article")
|
|
9832
|
-
})
|
|
10177
|
+
() => createInitialStages()
|
|
9833
10178
|
);
|
|
9834
10179
|
const [result, setResult] = useState4(null);
|
|
9835
10180
|
const [errorMessage, setErrorMessage] = useState4(null);
|
|
@@ -9860,6 +10205,7 @@ function WriteApp({
|
|
|
9860
10205
|
return;
|
|
9861
10206
|
}
|
|
9862
10207
|
setResult(runResult);
|
|
10208
|
+
onSuccess?.(runResult);
|
|
9863
10209
|
await notifyWriteSucceeded({
|
|
9864
10210
|
enabled: input.config.settings.notifications.enabled,
|
|
9865
10211
|
title: runResult.artifact.title,
|
|
@@ -9898,7 +10244,7 @@ function WriteApp({
|
|
|
9898
10244
|
}
|
|
9899
10245
|
async function runWriteCommand(options) {
|
|
9900
10246
|
const input = await resolveInputWithInteractiveIdeaFallback(options);
|
|
9901
|
-
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);
|
|
9902
10248
|
}
|
|
9903
10249
|
async function runWriteResumeCommand(options = {}) {
|
|
9904
10250
|
const session = await loadWriteSession();
|
|
@@ -9920,9 +10266,9 @@ async function runWriteResumeCommand(options = {}) {
|
|
|
9920
10266
|
secrets: resolved.config.secrets
|
|
9921
10267
|
}
|
|
9922
10268
|
};
|
|
9923
|
-
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);
|
|
9924
10270
|
}
|
|
9925
|
-
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) {
|
|
9926
10272
|
let interruptHandled = false;
|
|
9927
10273
|
const handleSignal = (signal) => {
|
|
9928
10274
|
if (interruptHandled) {
|
|
@@ -9956,10 +10302,17 @@ async function runWritePipeline(input, dryRun, enrichLinks2, runMode, noInteract
|
|
|
9956
10302
|
process.on("SIGTERM", onSigterm);
|
|
9957
10303
|
try {
|
|
9958
10304
|
if (noInteractive || !process.stdout.isTTY) {
|
|
9959
|
-
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
|
+
}
|
|
9960
10312
|
return;
|
|
9961
10313
|
}
|
|
9962
10314
|
let commandError = null;
|
|
10315
|
+
let pipelineResult = null;
|
|
9963
10316
|
const app = render2(
|
|
9964
10317
|
/* @__PURE__ */ jsx7(
|
|
9965
10318
|
WriteApp,
|
|
@@ -9972,6 +10325,9 @@ async function runWritePipeline(input, dryRun, enrichLinks2, runMode, noInteract
|
|
|
9972
10325
|
unlinks,
|
|
9973
10326
|
maxLinks,
|
|
9974
10327
|
maxImages,
|
|
10328
|
+
onSuccess: (result) => {
|
|
10329
|
+
pipelineResult = result;
|
|
10330
|
+
},
|
|
9975
10331
|
onError: (error) => {
|
|
9976
10332
|
commandError = error;
|
|
9977
10333
|
}
|
|
@@ -9984,6 +10340,9 @@ async function runWritePipeline(input, dryRun, enrichLinks2, runMode, noInteract
|
|
|
9984
10340
|
if (finalError) {
|
|
9985
10341
|
throw new ReportedError(withWriteResumeHint(finalError.message));
|
|
9986
10342
|
}
|
|
10343
|
+
if (exportPath && pipelineResult) {
|
|
10344
|
+
await autoExport(exportPath, pipelineResult);
|
|
10345
|
+
}
|
|
9987
10346
|
} finally {
|
|
9988
10347
|
cleanupSignalHandlers();
|
|
9989
10348
|
}
|
|
@@ -10131,6 +10490,12 @@ async function promptForIdea() {
|
|
|
10131
10490
|
readline.close();
|
|
10132
10491
|
}
|
|
10133
10492
|
}
|
|
10493
|
+
async function autoExport(exportPath, result) {
|
|
10494
|
+
await runOutputCommand({
|
|
10495
|
+
generationId: result.artifact.slug,
|
|
10496
|
+
destinationPath: exportPath
|
|
10497
|
+
});
|
|
10498
|
+
}
|
|
10134
10499
|
|
|
10135
10500
|
// src/cli/app.ts
|
|
10136
10501
|
var { version } = package_default;
|
|
@@ -10200,7 +10565,7 @@ async function runCli(argv) {
|
|
|
10200
10565
|
watch: options.watch
|
|
10201
10566
|
});
|
|
10202
10567
|
});
|
|
10203
|
-
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) => {
|
|
10204
10569
|
await runWriteCommand({
|
|
10205
10570
|
idea: options.idea ?? ideaArg,
|
|
10206
10571
|
audience: options.audience,
|
|
@@ -10216,17 +10581,19 @@ async function runCli(argv) {
|
|
|
10216
10581
|
links: options.link,
|
|
10217
10582
|
unlinks: options.unlink,
|
|
10218
10583
|
maxLinks: options.maxLinks,
|
|
10219
|
-
maxImages: options.maxImages
|
|
10584
|
+
maxImages: options.maxImages,
|
|
10585
|
+
exportPath: options.export
|
|
10220
10586
|
});
|
|
10221
10587
|
});
|
|
10222
|
-
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) => {
|
|
10223
10589
|
await runWriteResumeCommand({
|
|
10224
10590
|
noInteractive: options.noInteractive,
|
|
10225
10591
|
enrichLinks: options.enrichLinks,
|
|
10226
10592
|
links: options.link,
|
|
10227
10593
|
unlinks: options.unlink,
|
|
10228
10594
|
maxLinks: options.maxLinks,
|
|
10229
|
-
maxImages: options.maxImages
|
|
10595
|
+
maxImages: options.maxImages,
|
|
10596
|
+
exportPath: options.export
|
|
10230
10597
|
});
|
|
10231
10598
|
});
|
|
10232
10599
|
await program.parseAsync(argv);
|