@telepat/ideon 0.1.21 → 0.1.24
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 +806 -530
- 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: 1, max: 2 };
|
|
488
|
+
if (alias === "medium") return { min: 2, max: 3 };
|
|
489
|
+
return { min: 3, 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.24",
|
|
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,7 +1623,7 @@ 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
1628
|
import path8 from "path";
|
|
1570
1629
|
|
|
@@ -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,8 +2372,8 @@ 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,
|
|
@@ -2294,13 +2387,42 @@ function buildArticlePlanJsonSchema(targetLengthWords) {
|
|
|
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,19 +2433,19 @@ 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
|
-
|
|
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.`,
|
|
2327
2449
|
"- Each inline image must specify which section it follows (anchorAfterSection, 1-based index). Choose sections where visual reinforcement adds the most value.",
|
|
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.",
|
|
@@ -2336,6 +2458,7 @@ 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",
|
|
@@ -2345,7 +2468,53 @@ function buildArticlePlanMessages(idea, options) {
|
|
|
2345
2468
|
"- outroBrief: string",
|
|
2346
2469
|
`- sections: array of ${sectionCounts.label} objects, each with title and description strings`,
|
|
2347
2470
|
"- coverImageDescription: string",
|
|
2348
|
-
|
|
2471
|
+
`- inlineImages: array of ${imageCounts.min} to ${imageCounts.max} objects, each with a description string and an anchorAfterSection number (1-based section index)`,
|
|
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)",
|
|
2516
|
+
"- coverImageDescription: string",
|
|
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")
|
|
@@ -2363,7 +2532,22 @@ var inlineImagePlanSchema = z5.object({
|
|
|
2363
2532
|
description: z5.string().min(1),
|
|
2364
2533
|
anchorAfterSection: z5.number().int().min(1)
|
|
2365
2534
|
});
|
|
2366
|
-
var
|
|
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(2).max(3).optional(),
|
|
2547
|
+
angle: z5.string().min(1).optional()
|
|
2548
|
+
});
|
|
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),
|
|
@@ -2375,13 +2559,22 @@ var articlePlanSchema = z5.object({
|
|
|
2375
2559
|
coverImageDescription: z5.string().min(1),
|
|
2376
2560
|
inlineImages: z5.array(inlineImagePlanSchema).min(2).max(3)
|
|
2377
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()
|
|
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(1, 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 = [
|
|
@@ -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,
|
|
@@ -3716,8 +4004,8 @@ ${body.join("\n").trim()}
|
|
|
3716
4004
|
}
|
|
3717
4005
|
|
|
3718
4006
|
// src/pipeline/sessionStore.ts
|
|
3719
|
-
import { createHash } from "crypto";
|
|
3720
|
-
import { mkdir as
|
|
4007
|
+
import { createHash as createHash2 } from "crypto";
|
|
4008
|
+
import { mkdir as mkdir5, readFile as readFile5, rm as rm2, writeFile as writeFile5 } from "fs/promises";
|
|
3721
4009
|
import path7 from "path";
|
|
3722
4010
|
import envPaths3 from "env-paths";
|
|
3723
4011
|
import { z as z6 } from "zod";
|
|
@@ -3782,7 +4070,7 @@ var writeSessionStateSchema = z6.object({
|
|
|
3782
4070
|
failedStage: z6.enum(STAGE_IDS).nullable(),
|
|
3783
4071
|
errorMessage: z6.string().nullable(),
|
|
3784
4072
|
contentPlan: contentPlanSchema2.nullable().default(null),
|
|
3785
|
-
plan:
|
|
4073
|
+
plan: primaryPlanSchema.nullable(),
|
|
3786
4074
|
text: z6.object({
|
|
3787
4075
|
intro: z6.string().min(1),
|
|
3788
4076
|
sections: z6.array(generatedArticleSectionSchema),
|
|
@@ -3799,7 +4087,7 @@ var writeSessionStateSchema = z6.object({
|
|
|
3799
4087
|
var ideonPaths3 = envPaths3("ideon", { suffix: "" });
|
|
3800
4088
|
var sessionsDir = path7.join(ideonPaths3.config, "sessions");
|
|
3801
4089
|
function hashProjectPath(workingDir) {
|
|
3802
|
-
return
|
|
4090
|
+
return createHash2("sha256").update(path7.resolve(workingDir)).digest("hex").slice(0, 16);
|
|
3803
4091
|
}
|
|
3804
4092
|
function resolveWriteRoot(workingDir) {
|
|
3805
4093
|
return path7.join(sessionsDir, hashProjectPath(workingDir));
|
|
@@ -3813,7 +4101,7 @@ function resolveLegacyStatePath(workingDir) {
|
|
|
3813
4101
|
async function startFreshWriteSession(seed, workingDir = process.cwd()) {
|
|
3814
4102
|
const writeRoot = resolveWriteRoot(workingDir);
|
|
3815
4103
|
await rm2(writeRoot, { recursive: true, force: true });
|
|
3816
|
-
await
|
|
4104
|
+
await mkdir5(writeRoot, { recursive: true });
|
|
3817
4105
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3818
4106
|
const state = {
|
|
3819
4107
|
version: 1,
|
|
@@ -3846,7 +4134,7 @@ async function loadWriteSession(workingDir = process.cwd()) {
|
|
|
3846
4134
|
const raw = await readFile5(statePath, "utf8");
|
|
3847
4135
|
return writeSessionStateSchema.parse(JSON.parse(raw));
|
|
3848
4136
|
} catch (error) {
|
|
3849
|
-
if (!
|
|
4137
|
+
if (!isNotFoundError2(error)) {
|
|
3850
4138
|
throw error;
|
|
3851
4139
|
}
|
|
3852
4140
|
}
|
|
@@ -3857,7 +4145,7 @@ async function loadWriteSession(workingDir = process.cwd()) {
|
|
|
3857
4145
|
await saveWriteSession(state, workingDir);
|
|
3858
4146
|
return state;
|
|
3859
4147
|
} catch (error) {
|
|
3860
|
-
if (
|
|
4148
|
+
if (isNotFoundError2(error)) {
|
|
3861
4149
|
return null;
|
|
3862
4150
|
}
|
|
3863
4151
|
throw error;
|
|
@@ -3869,7 +4157,7 @@ async function saveWriteSession(state, workingDir = process.cwd()) {
|
|
|
3869
4157
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3870
4158
|
});
|
|
3871
4159
|
const statePath = resolveStateFilePath(workingDir);
|
|
3872
|
-
await
|
|
4160
|
+
await mkdir5(path7.dirname(statePath), { recursive: true });
|
|
3873
4161
|
await writeFile5(statePath, `${JSON.stringify(next, null, 2)}
|
|
3874
4162
|
`, "utf8");
|
|
3875
4163
|
return next;
|
|
@@ -3897,16 +4185,12 @@ async function patchWriteSession(patch, workingDir = process.cwd()) {
|
|
|
3897
4185
|
};
|
|
3898
4186
|
return saveWriteSession(merged, workingDir);
|
|
3899
4187
|
}
|
|
3900
|
-
function
|
|
4188
|
+
function isNotFoundError2(error) {
|
|
3901
4189
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
3902
4190
|
}
|
|
3903
4191
|
|
|
3904
4192
|
// 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.";
|
|
4193
|
+
function createInitialStages() {
|
|
3910
4194
|
return [
|
|
3911
4195
|
{
|
|
3912
4196
|
id: "shared-plan",
|
|
@@ -3916,15 +4200,15 @@ function createInitialStages(options = { isArticlePrimary: true }) {
|
|
|
3916
4200
|
},
|
|
3917
4201
|
{
|
|
3918
4202
|
id: "planning",
|
|
3919
|
-
title:
|
|
4203
|
+
title: "Planning Primary Content",
|
|
3920
4204
|
status: "pending",
|
|
3921
|
-
detail:
|
|
4205
|
+
detail: "Generating title, slug, and content plan for the primary output."
|
|
3922
4206
|
},
|
|
3923
4207
|
{
|
|
3924
4208
|
id: "sections",
|
|
3925
|
-
title:
|
|
4209
|
+
title: "Writing Primary Content",
|
|
3926
4210
|
status: "pending",
|
|
3927
|
-
detail:
|
|
4211
|
+
detail: "Waiting for the approved primary plan."
|
|
3928
4212
|
},
|
|
3929
4213
|
{
|
|
3930
4214
|
id: "image-prompts",
|
|
@@ -3958,8 +4242,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
3958
4242
|
const runId = randomUUID();
|
|
3959
4243
|
const primaryTarget = getPrimaryTarget(input.config.settings.contentTargets);
|
|
3960
4244
|
const secondaryTargets = getSecondaryTargets(input.config.settings.contentTargets);
|
|
3961
|
-
const
|
|
3962
|
-
const stages = createInitialStages({ isArticlePrimary });
|
|
4245
|
+
const stages = createInitialStages();
|
|
3963
4246
|
options.onUpdate?.(cloneStages(stages));
|
|
3964
4247
|
const dryRun = options.dryRun ?? false;
|
|
3965
4248
|
const shouldEnrichLinks = options.enrichLinks ?? false;
|
|
@@ -3968,8 +4251,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
3968
4251
|
const pipelineCustomLinkRaws = options.customLinks ?? [];
|
|
3969
4252
|
const pipelineUnlinks = options.unlinks ?? [];
|
|
3970
4253
|
const pipelineMaxLinks = options.maxLinks;
|
|
3971
|
-
const outputPaths = resolveOutputPaths(
|
|
3972
|
-
const hasArticlePrimary = isArticlePrimary;
|
|
4254
|
+
const outputPaths = resolveOutputPaths();
|
|
3973
4255
|
const stageTracking = /* @__PURE__ */ new Map();
|
|
3974
4256
|
const stageRetryState = /* @__PURE__ */ new Map();
|
|
3975
4257
|
const llmOperationRetryState = /* @__PURE__ */ new Map();
|
|
@@ -4102,66 +4384,69 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4102
4384
|
workingDir
|
|
4103
4385
|
);
|
|
4104
4386
|
}
|
|
4105
|
-
|
|
4387
|
+
stages[1] = {
|
|
4388
|
+
...stages[1],
|
|
4389
|
+
status: "running",
|
|
4390
|
+
detail: `Planning primary ${primaryTarget.contentType} content.`
|
|
4391
|
+
};
|
|
4392
|
+
markStageStarted(stageTracking, "planning");
|
|
4393
|
+
options.onUpdate?.(cloneStages(stages));
|
|
4394
|
+
if (plan) {
|
|
4395
|
+
markStageCompleted(stageTracking, "planning");
|
|
4106
4396
|
stages[1] = {
|
|
4107
4397
|
...stages[1],
|
|
4108
|
-
status: "
|
|
4109
|
-
detail: "
|
|
4398
|
+
status: "succeeded",
|
|
4399
|
+
detail: "Reused saved plan from cached session.",
|
|
4400
|
+
summary: buildPlanSummary(plan),
|
|
4401
|
+
stageAnalytics: snapshotStageAnalytics(stageTracking, "planning")
|
|
4110
4402
|
};
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4403
|
+
} else {
|
|
4404
|
+
if (!contentPlan) {
|
|
4405
|
+
throw new Error("Shared content plan is missing for primary planning stage.");
|
|
4406
|
+
}
|
|
4407
|
+
plan = await planPrimaryContent({
|
|
4408
|
+
idea: input.idea,
|
|
4409
|
+
contentType: primaryTarget.contentType,
|
|
4410
|
+
contentPlan,
|
|
4411
|
+
settings: input.config.settings,
|
|
4412
|
+
markdownOutputDir: writeSession.outputPaths.markdownOutputDir,
|
|
4413
|
+
openRouter,
|
|
4414
|
+
dryRun,
|
|
4415
|
+
onInteraction(interaction) {
|
|
4416
|
+
onLlmInteraction(interaction);
|
|
4417
|
+
},
|
|
4418
|
+
onLlmMetrics(metrics) {
|
|
4419
|
+
recordLlmMetrics(stageTracking, "planning", metrics);
|
|
4125
4420
|
}
|
|
4126
|
-
|
|
4127
|
-
|
|
4421
|
+
});
|
|
4422
|
+
markStageCompleted(stageTracking, "planning");
|
|
4423
|
+
stages[1] = {
|
|
4424
|
+
...stages[1],
|
|
4425
|
+
status: "succeeded",
|
|
4426
|
+
detail: "Plan generated successfully.",
|
|
4427
|
+
summary: buildPlanSummary(plan),
|
|
4428
|
+
stageAnalytics: snapshotStageAnalytics(stageTracking, "planning")
|
|
4429
|
+
};
|
|
4430
|
+
writeSession = await patchWriteSession(
|
|
4431
|
+
{
|
|
4432
|
+
status: "running",
|
|
4433
|
+
lastCompletedStage: "planning",
|
|
4434
|
+
failedStage: null,
|
|
4435
|
+
errorMessage: null,
|
|
4128
4436
|
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
|
-
}
|
|
4437
|
+
plan
|
|
4438
|
+
},
|
|
4439
|
+
workingDir
|
|
4440
|
+
);
|
|
4441
|
+
}
|
|
4442
|
+
const isLongForm = isLongFormPlan(plan);
|
|
4443
|
+
if (isLongForm) {
|
|
4444
|
+
const longPlan = plan;
|
|
4160
4445
|
stages[2] = {
|
|
4161
4446
|
...stages[2],
|
|
4162
4447
|
status: "running",
|
|
4163
4448
|
detail: "Writing introduction.",
|
|
4164
|
-
items: buildSectionItems(
|
|
4449
|
+
items: buildSectionItems(longPlan.sections.map((section) => section.title))
|
|
4165
4450
|
};
|
|
4166
4451
|
markStageStarted(stageTracking, "sections");
|
|
4167
4452
|
options.onUpdate?.(cloneStages(stages));
|
|
@@ -4189,7 +4474,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4189
4474
|
} else {
|
|
4190
4475
|
const sectionItemTracking = /* @__PURE__ */ new Map();
|
|
4191
4476
|
text = await writeArticleSections({
|
|
4192
|
-
plan,
|
|
4477
|
+
plan: longPlan,
|
|
4193
4478
|
settings: input.config.settings,
|
|
4194
4479
|
openRouter,
|
|
4195
4480
|
dryRun,
|
|
@@ -4302,8 +4587,8 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4302
4587
|
options.onUpdate?.(cloneStages(stages));
|
|
4303
4588
|
} else {
|
|
4304
4589
|
imagePrompts = await expandImagePrompts({
|
|
4305
|
-
slots: buildImageSlots(
|
|
4306
|
-
planContext:
|
|
4590
|
+
slots: buildImageSlots(longPlan, text.sections, { maxImages: options.maxImages }),
|
|
4591
|
+
planContext: longPlan,
|
|
4307
4592
|
sections: text.sections,
|
|
4308
4593
|
settings: input.config.settings,
|
|
4309
4594
|
openRouter,
|
|
@@ -4364,24 +4649,6 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4364
4649
|
);
|
|
4365
4650
|
}
|
|
4366
4651
|
} 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
4652
|
stages[2] = {
|
|
4386
4653
|
...stages[2],
|
|
4387
4654
|
status: "running",
|
|
@@ -4400,6 +4667,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4400
4667
|
outputCountForType: 1,
|
|
4401
4668
|
articleReferenceMarkdown: void 0,
|
|
4402
4669
|
contentPlan,
|
|
4670
|
+
plan,
|
|
4403
4671
|
settings: input.config.settings,
|
|
4404
4672
|
openRouter,
|
|
4405
4673
|
dryRun,
|
|
@@ -4425,7 +4693,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4425
4693
|
};
|
|
4426
4694
|
markStageStarted(stageTracking, "image-prompts");
|
|
4427
4695
|
options.onUpdate?.(cloneStages(stages));
|
|
4428
|
-
imagePrompts = [buildPrimaryCoverPrompt(contentPlan, primaryTarget.contentType
|
|
4696
|
+
imagePrompts = [buildPrimaryCoverPrompt(plan, contentPlan, primaryTarget.contentType)];
|
|
4429
4697
|
markStageCompleted(stageTracking, "image-prompts");
|
|
4430
4698
|
stages[3] = {
|
|
4431
4699
|
...stages[3],
|
|
@@ -4452,7 +4720,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4452
4720
|
writeSession.outputPaths.markdownOutputDir,
|
|
4453
4721
|
buildGenerationDirectoryName(baseSlug)
|
|
4454
4722
|
);
|
|
4455
|
-
await
|
|
4723
|
+
await mkdir6(generationDir, { recursive: true });
|
|
4456
4724
|
const jobDefinitionPath = path8.join(generationDir, "job.json");
|
|
4457
4725
|
await writeJsonFile(
|
|
4458
4726
|
jobDefinitionPath,
|
|
@@ -4472,7 +4740,8 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4472
4740
|
const primaryFilePrefix = toFilePrefix(primaryTarget.contentType);
|
|
4473
4741
|
const primaryMarkdownPath = path8.join(generationDir, `${primaryFilePrefix}-1.md`);
|
|
4474
4742
|
const sharedAssetDir = generationDir;
|
|
4475
|
-
if (
|
|
4743
|
+
if (isLongForm) {
|
|
4744
|
+
const longPlan = plan;
|
|
4476
4745
|
if (imageArtifacts) {
|
|
4477
4746
|
markStageCompleted(stageTracking, "images");
|
|
4478
4747
|
stages[4] = {
|
|
@@ -4548,7 +4817,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4548
4817
|
throw new Error("Article generation requested but required article artifacts are missing.");
|
|
4549
4818
|
}
|
|
4550
4819
|
const article = {
|
|
4551
|
-
plan,
|
|
4820
|
+
plan: longPlan,
|
|
4552
4821
|
intro: text.intro,
|
|
4553
4822
|
sections: text.sections,
|
|
4554
4823
|
outro: text.outro,
|
|
@@ -4633,11 +4902,11 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4633
4902
|
}
|
|
4634
4903
|
const coverImage = imageArtifacts?.renderedImages.find((image) => image.kind === "cover") ?? null;
|
|
4635
4904
|
if (coverImage) {
|
|
4636
|
-
primaryMarkdownTemplate = withCoverImage(primaryMarkdownTemplate, coverImage.relativePath, deriveTitleFromIdea2(input.idea));
|
|
4905
|
+
primaryMarkdownTemplate = withCoverImage(primaryMarkdownTemplate, coverImage.relativePath, plan.title || deriveTitleFromIdea2(input.idea));
|
|
4637
4906
|
}
|
|
4638
4907
|
primaryMarkdownTemplate = applyPrimaryTitleHeading(
|
|
4639
4908
|
primaryMarkdownTemplate,
|
|
4640
|
-
contentPlan.title || deriveTitleFromIdea2(input.idea)
|
|
4909
|
+
plan.title || contentPlan.title || deriveTitleFromIdea2(input.idea)
|
|
4641
4910
|
);
|
|
4642
4911
|
}
|
|
4643
4912
|
const markdownPaths = [];
|
|
@@ -4704,6 +4973,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4704
4973
|
outputCountForType: output.outputCountForType,
|
|
4705
4974
|
articleReferenceMarkdown: primaryMarkdownTemplate ?? void 0,
|
|
4706
4975
|
contentPlan,
|
|
4976
|
+
plan,
|
|
4707
4977
|
settings: input.config.settings,
|
|
4708
4978
|
openRouter,
|
|
4709
4979
|
dryRun,
|
|
@@ -5303,20 +5573,19 @@ function getSecondaryTargets(contentTargets) {
|
|
|
5303
5573
|
count: target.count
|
|
5304
5574
|
}));
|
|
5305
5575
|
}
|
|
5306
|
-
function buildPrimaryCoverPrompt(contentPlan, primaryContentType
|
|
5307
|
-
const markdownExcerpt = primaryMarkdown.replace(/\s+/g, " ").trim().slice(0, 240);
|
|
5576
|
+
function buildPrimaryCoverPrompt(plan, contentPlan, primaryContentType) {
|
|
5308
5577
|
return {
|
|
5309
5578
|
id: "cover",
|
|
5310
5579
|
kind: "cover",
|
|
5311
|
-
description:
|
|
5580
|
+
description: plan.coverImageDescription,
|
|
5312
5581
|
anchorAfterSection: null,
|
|
5313
5582
|
prompt: [
|
|
5314
|
-
|
|
5583
|
+
plan.coverImageDescription,
|
|
5584
|
+
`Content type: ${primaryContentType}`,
|
|
5315
5585
|
`Core angle: ${contentPlan.description}`,
|
|
5316
5586
|
`Audience: ${contentPlan.targetAudience}`,
|
|
5317
5587
|
`Promise: ${contentPlan.corePromise}`,
|
|
5318
5588
|
`Voice: ${contentPlan.voiceNotes}`,
|
|
5319
|
-
`Primary excerpt: ${markdownExcerpt}`,
|
|
5320
5589
|
"Do not include any words, letters, numbers, logos, watermarks, or signage in the image."
|
|
5321
5590
|
].join(" ")
|
|
5322
5591
|
};
|
|
@@ -5380,43 +5649,60 @@ function buildRunJobDefinition(input) {
|
|
|
5380
5649
|
};
|
|
5381
5650
|
}
|
|
5382
5651
|
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
|
-
|
|
5652
|
+
const lines = [
|
|
5653
|
+
`# ${plan.title}`,
|
|
5654
|
+
"",
|
|
5655
|
+
`**Content type:** ${plan.contentType}`,
|
|
5656
|
+
`**Slug:** ${plan.slug}`,
|
|
5657
|
+
"",
|
|
5658
|
+
"## Description",
|
|
5659
|
+
"",
|
|
5660
|
+
plan.description,
|
|
5661
|
+
""
|
|
5662
|
+
];
|
|
5663
|
+
if (plan.subtitle) {
|
|
5664
|
+
lines.push("## Subtitle", "", plan.subtitle, "");
|
|
5665
|
+
}
|
|
5666
|
+
if (plan.keywords && plan.keywords.length > 0) {
|
|
5667
|
+
lines.push("## Keywords", "", ...plan.keywords.map((kw) => `- ${kw}`), "");
|
|
5668
|
+
}
|
|
5669
|
+
if (plan.introBrief) {
|
|
5670
|
+
lines.push("## Introduction Brief", "", plan.introBrief, "");
|
|
5671
|
+
}
|
|
5672
|
+
if (plan.sections && plan.sections.length > 0) {
|
|
5673
|
+
lines.push("## Sections", "", "| # | Title | Description |", "|---|-------|-------------|");
|
|
5674
|
+
plan.sections.forEach((section, index) => {
|
|
5675
|
+
lines.push(`| ${index + 1} | ${section.title} | ${section.description} |`);
|
|
5676
|
+
});
|
|
5677
|
+
lines.push("");
|
|
5678
|
+
}
|
|
5679
|
+
if (plan.outroBrief) {
|
|
5680
|
+
lines.push("## Outro Brief", "", plan.outroBrief, "");
|
|
5681
|
+
}
|
|
5682
|
+
if (plan.angle) {
|
|
5683
|
+
lines.push("## Angle", "", plan.angle, "");
|
|
5684
|
+
}
|
|
5685
|
+
lines.push("## Image Plan", "");
|
|
5686
|
+
lines.push(`- **Cover:** ${plan.coverImageDescription}`);
|
|
5687
|
+
if (plan.inlineImages && plan.inlineImages.length > 0) {
|
|
5688
|
+
plan.inlineImages.forEach((img, index) => {
|
|
5689
|
+
lines.push(`- **Inline ${index + 1}:** ${img.description} (after section ${img.anchorAfterSection})`);
|
|
5690
|
+
});
|
|
5691
|
+
}
|
|
5692
|
+
lines.push("");
|
|
5693
|
+
return lines.join("\n");
|
|
5694
|
+
}
|
|
5695
|
+
function buildPlanSummary(plan) {
|
|
5696
|
+
const parts = [plan.title, plan.slug];
|
|
5697
|
+
if (plan.sections && plan.sections.length > 0) {
|
|
5698
|
+
parts.push(`${plan.sections.length} sections`);
|
|
5699
|
+
}
|
|
5700
|
+
if (plan.inlineImages && plan.inlineImages.length > 0) {
|
|
5701
|
+
parts.push(`${plan.inlineImages.length + 1} images`);
|
|
5702
|
+
} else {
|
|
5703
|
+
parts.push("1 image");
|
|
5704
|
+
}
|
|
5705
|
+
return parts.join(" \u2022 ");
|
|
5420
5706
|
}
|
|
5421
5707
|
function asWriteStageId(stageId) {
|
|
5422
5708
|
if (stageId === "shared-plan" || stageId === "planning" || stageId === "sections" || stageId === "image-prompts" || stageId === "images" || stageId === "output" || stageId === "links") {
|
|
@@ -5454,7 +5740,7 @@ async function runLinksCommand(options, dependencies = {}) {
|
|
|
5454
5740
|
const resolved = await resolveRunInput({
|
|
5455
5741
|
idea: `Enrich links for ${slug}`
|
|
5456
5742
|
});
|
|
5457
|
-
const markdownPath = await resolveMarkdownPathForSlug2(
|
|
5743
|
+
const markdownPath = await resolveMarkdownPathForSlug2(slug, cwd2);
|
|
5458
5744
|
const frontmatter = await readFrontmatter(markdownPath);
|
|
5459
5745
|
const fileId = path9.parse(markdownPath).name;
|
|
5460
5746
|
const articleTitle = frontmatter.title ?? toTitleFromSlug(slug);
|
|
@@ -5524,8 +5810,8 @@ function normalizeSlug2(rawSlug) {
|
|
|
5524
5810
|
}
|
|
5525
5811
|
return slug;
|
|
5526
5812
|
}
|
|
5527
|
-
async function resolveMarkdownPathForSlug2(
|
|
5528
|
-
const outputPaths = resolveOutputPaths(
|
|
5813
|
+
async function resolveMarkdownPathForSlug2(slug, cwd2) {
|
|
5814
|
+
const outputPaths = resolveOutputPaths();
|
|
5529
5815
|
const directPath = path9.join(outputPaths.markdownOutputDir, `${slug}.md`);
|
|
5530
5816
|
if (await isReadableFile(directPath)) {
|
|
5531
5817
|
return directPath;
|
|
@@ -5706,7 +5992,7 @@ function resolveCustomLinks(existing, addRaw, removeExpressions) {
|
|
|
5706
5992
|
}
|
|
5707
5993
|
|
|
5708
5994
|
// src/cli/commands/export.ts
|
|
5709
|
-
import { copyFile, mkdir as
|
|
5995
|
+
import { copyFile as copyFile2, mkdir as mkdir7, readFile as readFile8, stat as stat5, writeFile as writeFile6 } from "fs/promises";
|
|
5710
5996
|
import path11 from "path";
|
|
5711
5997
|
|
|
5712
5998
|
// src/output/enrichMarkdownWithLinks.ts
|
|
@@ -6033,7 +6319,7 @@ async function runOutputCommand(options, dependencies = {}) {
|
|
|
6033
6319
|
const log = dependencies.log ?? ((message) => console.log(message));
|
|
6034
6320
|
const targetIndex = options.index ?? 1;
|
|
6035
6321
|
const resolved = await resolveRunInput({ idea: `Export generation ${options.generationId}` });
|
|
6036
|
-
const outputPaths = resolveOutputPaths(
|
|
6322
|
+
const outputPaths = resolveOutputPaths();
|
|
6037
6323
|
const generations = await listAllGenerations(outputPaths.markdownOutputDir);
|
|
6038
6324
|
const generation = resolveGeneration(generations, options.generationId);
|
|
6039
6325
|
const articleOutputs = generation.outputs.filter((output) => output.contentType === generation.primaryContentType);
|
|
@@ -6060,7 +6346,7 @@ async function runOutputCommand(options, dependencies = {}) {
|
|
|
6060
6346
|
`Export file already exists: ${destinationFilePath}. Pass --overwrite to replace it.`
|
|
6061
6347
|
);
|
|
6062
6348
|
}
|
|
6063
|
-
await
|
|
6349
|
+
await mkdir7(destinationDir, { recursive: true });
|
|
6064
6350
|
const links = await loadLinks(sourceMarkdownPath);
|
|
6065
6351
|
const enrichedMarkdown = enrichWithFrontmatterGuard(sourceMarkdown, links);
|
|
6066
6352
|
const sourceDir = path11.dirname(sourceMarkdownPath);
|
|
@@ -6080,8 +6366,8 @@ async function runOutputCommand(options, dependencies = {}) {
|
|
|
6080
6366
|
throw new ReportedError(`Referenced image path is not a file: ${absoluteImageSrc}.`);
|
|
6081
6367
|
}
|
|
6082
6368
|
const destImagePath = path11.join(destinationDir, relImagePath);
|
|
6083
|
-
await
|
|
6084
|
-
await
|
|
6369
|
+
await mkdir7(path11.dirname(destImagePath), { recursive: true });
|
|
6370
|
+
await copyFile2(absoluteImageSrc, destImagePath);
|
|
6085
6371
|
copiedImages.push(relImagePath);
|
|
6086
6372
|
}
|
|
6087
6373
|
await writeFile6(destinationFilePath, enrichedMarkdown, "utf8");
|
|
@@ -6642,12 +6928,6 @@ import { Box, Text, useApp, useInput } from "ink";
|
|
|
6642
6928
|
import SelectInput from "ink-select-input";
|
|
6643
6929
|
import TextInput from "ink-text-input";
|
|
6644
6930
|
|
|
6645
|
-
// src/images/limnModelCatalog.ts
|
|
6646
|
-
import { getSupportedModelCatalog } from "@telepat/limn";
|
|
6647
|
-
function getLimnGenerationModels() {
|
|
6648
|
-
return getSupportedModelCatalog().filter((entry) => entry.generationEnabled);
|
|
6649
|
-
}
|
|
6650
|
-
|
|
6651
6931
|
// src/cli/flows/settingsFlowLogic.ts
|
|
6652
6932
|
function handleMenuSelect(action, settings, secrets, setEditing, setShowModelSelect, setMenuMode, onDone, exit) {
|
|
6653
6933
|
switch (action) {
|
|
@@ -6672,12 +6952,6 @@ function handleMenuSelect(action, settings, secrets, setEditing, setShowModelSel
|
|
|
6672
6952
|
case "topP":
|
|
6673
6953
|
setEditing({ key: action, label: "Top p", value: String(settings.modelSettings.topP) });
|
|
6674
6954
|
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
6955
|
case "t2i-settings":
|
|
6682
6956
|
setMenuMode("t2i");
|
|
6683
6957
|
return;
|
|
@@ -6691,6 +6965,13 @@ function handleMenuSelect(action, settings, secrets, setEditing, setShowModelSel
|
|
|
6691
6965
|
value: JSON.stringify(settings.t2i.inputOverrides, null, 2)
|
|
6692
6966
|
});
|
|
6693
6967
|
return;
|
|
6968
|
+
case "t2i-replicate-model-id":
|
|
6969
|
+
setEditing({
|
|
6970
|
+
key: action,
|
|
6971
|
+
label: "T2I Replicate model ID override (blank to clear)",
|
|
6972
|
+
value: settings.t2i.replicateModelId ?? ""
|
|
6973
|
+
});
|
|
6974
|
+
return;
|
|
6694
6975
|
case "t2i-back":
|
|
6695
6976
|
setMenuMode("main");
|
|
6696
6977
|
return;
|
|
@@ -6761,14 +7042,6 @@ function applyEdit(action, value2, settings, secrets, setSettings, setSecrets) {
|
|
|
6761
7042
|
});
|
|
6762
7043
|
return true;
|
|
6763
7044
|
}
|
|
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
7045
|
if (action === "t2i-input-overrides") {
|
|
6773
7046
|
const trimmed = value2.trim();
|
|
6774
7047
|
if (trimmed.length === 0) {
|
|
@@ -6798,6 +7071,17 @@ function applyEdit(action, value2, settings, secrets, setSettings, setSecrets) {
|
|
|
6798
7071
|
return false;
|
|
6799
7072
|
}
|
|
6800
7073
|
}
|
|
7074
|
+
if (action === "t2i-replicate-model-id") {
|
|
7075
|
+
const trimmed = value2.trim();
|
|
7076
|
+
setSettings({
|
|
7077
|
+
...settings,
|
|
7078
|
+
t2i: {
|
|
7079
|
+
...settings.t2i,
|
|
7080
|
+
replicateModelId: trimmed.length > 0 ? trimmed : void 0
|
|
7081
|
+
}
|
|
7082
|
+
});
|
|
7083
|
+
return true;
|
|
7084
|
+
}
|
|
6801
7085
|
return false;
|
|
6802
7086
|
}
|
|
6803
7087
|
function parseNumberOrFallback(value2, fallback) {
|
|
@@ -6852,12 +7136,19 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
|
|
|
6852
7136
|
const count = Object.keys(overrides).length;
|
|
6853
7137
|
return count === 0 ? "none" : `${count} override${count === 1 ? "" : "s"}`;
|
|
6854
7138
|
};
|
|
7139
|
+
const formatReplicateOverrideSummary = (replicateModelId) => {
|
|
7140
|
+
return replicateModelId && replicateModelId.length > 0 ? replicateModelId : "auto";
|
|
7141
|
+
};
|
|
6855
7142
|
const menuItems = useMemo(() => {
|
|
6856
7143
|
const t2iSubmenu = [
|
|
6857
7144
|
{
|
|
6858
7145
|
label: `T2I model: ${currentModelEntry?.displayName ?? settings.t2i.modelId}`,
|
|
6859
7146
|
value: "t2i-model"
|
|
6860
7147
|
},
|
|
7148
|
+
{
|
|
7149
|
+
label: `T2I Replicate model override: ${formatReplicateOverrideSummary(settings.t2i.replicateModelId)}`,
|
|
7150
|
+
value: "t2i-replicate-model-id"
|
|
7151
|
+
},
|
|
6861
7152
|
{
|
|
6862
7153
|
label: `T2I input overrides: ${formatT2iOverridesSummary(settings.t2i.inputOverrides)}`,
|
|
6863
7154
|
value: "t2i-input-overrides"
|
|
@@ -6899,14 +7190,6 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
|
|
|
6899
7190
|
label: `Top p: ${settings.modelSettings.topP}`,
|
|
6900
7191
|
value: "topP"
|
|
6901
7192
|
},
|
|
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
7193
|
{
|
|
6911
7194
|
label: `T2I settings: ${currentModelEntry?.displayName ?? settings.t2i.modelId}`,
|
|
6912
7195
|
value: "t2i-settings"
|
|
@@ -6938,6 +7221,7 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
|
|
|
6938
7221
|
...current,
|
|
6939
7222
|
t2i: {
|
|
6940
7223
|
modelId: item.value,
|
|
7224
|
+
replicateModelId: current.t2i.replicateModelId && isReplicateModelIdForFamily(item.value, current.t2i.replicateModelId) ? current.t2i.replicateModelId : void 0,
|
|
6941
7225
|
inputOverrides: {}
|
|
6942
7226
|
}
|
|
6943
7227
|
}));
|
|
@@ -8920,13 +9204,7 @@ function escapeHtml(value2) {
|
|
|
8920
9204
|
|
|
8921
9205
|
// src/cli/commands/serve.ts
|
|
8922
9206
|
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);
|
|
9207
|
+
const outputPaths = resolveOutputPaths();
|
|
8930
9208
|
const markdownPath = await resolveMarkdownPath(options.markdownPath, outputPaths.markdownOutputDir, process.cwd());
|
|
8931
9209
|
const port = parsePort(options.port);
|
|
8932
9210
|
if (options.watch) {
|
|
@@ -9827,9 +10105,7 @@ function WriteApp({
|
|
|
9827
10105
|
}) {
|
|
9828
10106
|
const { exit } = useApp3();
|
|
9829
10107
|
const [stages, setStages] = useState4(
|
|
9830
|
-
() => createInitialStages(
|
|
9831
|
-
isArticlePrimary: input.config.settings.contentTargets.some((target) => target.role === "primary" && target.contentType === "article")
|
|
9832
|
-
})
|
|
10108
|
+
() => createInitialStages()
|
|
9833
10109
|
);
|
|
9834
10110
|
const [result, setResult] = useState4(null);
|
|
9835
10111
|
const [errorMessage, setErrorMessage] = useState4(null);
|