@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.
Files changed (2) hide show
  1. package/dist/ideon.js +806 -530
  2. 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 path3 from "path";
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 as mkdir2, writeFile as writeFile2 } from "fs/promises";
228
- import path2 from "path";
229
- function resolveOutputPaths(settings, cwd2 = process.cwd()) {
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: resolveConfiguredDir(settings.markdownOutputDir, cwd2),
232
- assetOutputDir: resolveConfiguredDir(settings.assetOutputDir, cwd2)
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
- mkdir2(paths.markdownOutputDir, { recursive: true }),
238
- mkdir2(paths.assetOutputDir, { recursive: true })
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(path2.join(markdownOutputDir, `${candidate}.md`))) {
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 = path2.join(current, entry.name);
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 mkdir2(path2.dirname(filePath), { recursive: true });
296
- await writeFile2(filePath, content, "utf8");
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 = path2.parse(markdownPath);
304
- return path2.join(parsed.dir, `${parsed.name}.links.json`);
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 = path2.parse(markdownPath);
311
- return path2.join(parsed.dir, `${parsed.name}.analytics.json`);
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 path2.relative(path2.dirname(markdownPath), assetPath).split(path2.sep).join("/");
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 [savedSettings, envSettings] = await Promise.all([loadSavedSettings(), Promise.resolve(readEnvSettings())]);
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: path3.dirname(markdownPath)
159
+ assetDir: path2.dirname(markdownPath)
389
160
  };
390
161
  }
391
162
  async function resolveMarkdownPathForSlug(markdownOutputDir, slug) {
392
- const directPath = path3.join(markdownOutputDir, `${slug}.md`);
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 = path3.join(current, entry.name);
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 = path3.relative(cwd2, targetPath);
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 mkdir3, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
521
- import path4 from "path";
522
- import envPaths2 from "env-paths";
523
- import { z as z2 } from "zod";
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 = z2.object({
526
- runtime: z2.enum(supportedAgentRuntimeValues),
527
- installedAt: z2.string(),
528
- updatedAt: z2.string()
296
+ var integrationEntrySchema = z.object({
297
+ runtime: z.enum(supportedAgentRuntimeValues),
298
+ installedAt: z.string(),
299
+ updatedAt: z.string()
529
300
  });
530
- var integrationStoreSchema = z2.object({
531
- version: z2.literal(1),
532
- integrations: z2.record(z2.string(), integrationEntrySchema).default({})
301
+ var integrationStoreSchema = z.object({
302
+ version: z.literal(1),
303
+ integrations: z.record(z.string(), integrationEntrySchema).default({})
533
304
  });
534
- var ideonPaths2 = envPaths2("ideon", { suffix: "" });
535
- var storeDir = ideonPaths2.config;
536
- var storePath = path4.join(storeDir, "agent-integrations.json");
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 readFile2(targetStorePath, "utf8");
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 mkdir3(path4.dirname(targetStorePath), { recursive: true });
592
- await writeFile3(targetStorePath, `${JSON.stringify(store, null, 2)}
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
- "markdownOutputDir",
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
- markdownOutputDir: settings.markdownOutputDir,
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 "markdownOutputDir":
920
- return settings.markdownOutputDir;
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 "markdownOutputDir":
948
- return { ...settings, markdownOutputDir: value2 };
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.21",
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 mkdir5, stat as stat2 } from "fs/promises";
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 buildArticlePlanGuideInstruction(intent, contentType) {
1995
- return buildGuideBundle([
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/articlePlan.ts
2224
- function deriveArticleSectionCounts(targetLengthWords) {
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 buildArticlePlanJsonSchema(targetLengthWords) {
2236
- const sectionCounts = deriveArticleSectionCounts(targetLengthWords);
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: 2,
2283
- maxItems: 3,
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 buildArticlePlanMessages(idea, options) {
2298
- const sectionCounts = deriveArticleSectionCounts(options.targetLength);
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 article plan for a polished long-form Markdown article.",
2301
- buildArticlePlanGuideInstruction(options.intent, "article"),
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("article", options.targetLength),
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
- "Create an article plan from this idea:",
2436
+ `Create a ${options.contentType} plan from this idea:`,
2315
2437
  idea,
2316
2438
  "",
2317
2439
  "Requirements:",
2318
- "- The article should feel authoritative, practical, and clearly structured for scanning and deep reading.",
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 article-only structure and must not be treated as requirements for non-article channels.",
2326
- "- Include a cover image description and 2 to 3 inline image descriptions.",
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
- "- inlineImages: array of 2 to 3 objects, each with a description string and an anchorAfterSection number (1-based section index)",
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 articlePlanSchema = z5.object({
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/planArticle.ts
2383
- async function planArticle({
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 basePlan = dryRun || !openRouter ? buildDryRunPlan(idea, contentPlan) : await openRouter.requestStructured({
2394
- schemaName: "article_plan",
2395
- schema: buildArticlePlanJsonSchema(settings.targetLength),
2396
- messages: buildArticlePlanMessages(idea, {
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: "planning:article-plan"
2600
+ operationId: `planning:${contentType}-plan`
2406
2601
  },
2407
2602
  onInteraction,
2408
2603
  onMetrics: onLlmMetrics,
2409
2604
  parse(data) {
2410
- return articlePlanSchema.parse(data);
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
- const sectionCount = basePlan.sections.length;
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 a strong article",
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 articles need structure, intent, and editorial judgment."
2655
+ description: "Explain why strong content needs structure, intent, and editorial judgment."
2440
2656
  },
2441
2657
  {
2442
- title: "Designing the article before drafting",
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 article outline on a desk full of notes.",
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-article";
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, "article"),
2860
+ buildArticleSectionGuideInstruction(style, intent, contentType),
2629
2861
  buildRunContextDirective(contentTypes),
2630
- buildTargetLengthDirective("article", targetLengthWords)
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
- return normalized.replace(/^```(?:markdown)?\s*/i, "").replace(/```\s*$/i, "").trim();
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 mkdir4, readFile as readFile5, rm as rm2, writeFile as writeFile5 } from "fs/promises";
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: articlePlanSchema.nullable(),
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 createHash("sha256").update(path7.resolve(workingDir)).digest("hex").slice(0, 16);
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 mkdir4(writeRoot, { recursive: true });
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 (!isNotFoundError(error)) {
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 (isNotFoundError(error)) {
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 mkdir4(path7.dirname(statePath), { recursive: true });
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 isNotFoundError(error) {
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(options = { isArticlePrimary: true }) {
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: planningTitle,
4203
+ title: "Planning Primary Content",
3920
4204
  status: "pending",
3921
- detail: planningDetail
4205
+ detail: "Generating title, slug, and content plan for the primary output."
3922
4206
  },
3923
4207
  {
3924
4208
  id: "sections",
3925
- title: sectionsTitle,
4209
+ title: "Writing Primary Content",
3926
4210
  status: "pending",
3927
- detail: sectionsDetail
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 isArticlePrimary = primaryTarget.contentType === "article";
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(input.config.settings, workingDir);
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
- if (hasArticlePrimary) {
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: "running",
4109
- detail: "Generating title, slug, section plan, and image slots."
4398
+ status: "succeeded",
4399
+ detail: "Reused saved plan from cached session.",
4400
+ summary: buildPlanSummary(plan),
4401
+ stageAnalytics: snapshotStageAnalytics(stageTracking, "planning")
4110
4402
  };
4111
- markStageStarted(stageTracking, "planning");
4112
- options.onUpdate?.(cloneStages(stages));
4113
- if (plan) {
4114
- markStageCompleted(stageTracking, "planning");
4115
- stages[1] = {
4116
- ...stages[1],
4117
- status: "succeeded",
4118
- detail: "Reused saved plan from cached session.",
4119
- summary: `${plan.title} \u2022 ${plan.slug} \u2022 ${plan.sections.length} sections \u2022 ${plan.inlineImages.length + 1} images`,
4120
- stageAnalytics: snapshotStageAnalytics(stageTracking, "planning")
4121
- };
4122
- } else {
4123
- if (!contentPlan) {
4124
- throw new Error("Shared content plan is missing for article planning stage.");
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
- plan = await planArticle({
4127
- idea: input.idea,
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
- settings: input.config.settings,
4130
- markdownOutputDir: writeSession.outputPaths.markdownOutputDir,
4131
- openRouter,
4132
- dryRun,
4133
- onInteraction(interaction) {
4134
- onLlmInteraction(interaction);
4135
- },
4136
- onLlmMetrics(metrics) {
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(plan.sections.map((section) => section.title))
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(plan, text.sections, { maxImages: options.maxImages }),
4306
- planContext: plan,
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, primaryMarkdownTemplate)];
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 mkdir5(generationDir, { recursive: true });
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 (hasArticlePrimary) {
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, primaryMarkdown) {
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: `Cover image for ${primaryContentType}`,
5580
+ description: plan.coverImageDescription,
5312
5581
  anchorAfterSection: null,
5313
5582
  prompt: [
5314
- `Cover image for ${primaryContentType}.`,
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 yamlKeywords = plan.keywords.map((kw) => ` - "${kw}"`).join("\n");
5384
- const sectionsRows = plan.sections.map((section, index) => `| ${index + 1} | ${section.title} | ${section.description} |`).join("\n");
5385
- const inlineImageRows = plan.inlineImages.map((img, index) => `| Inline ${index + 1} | ${img.description} |`).join("\n");
5386
- const imageRows = `| Cover | ${plan.coverImageDescription} |
5387
- ${inlineImageRows}`;
5388
- return `---
5389
- title: "${plan.title.replace(/"/g, '\\"')}"
5390
- subtitle: "${plan.subtitle.replace(/"/g, '\\"')}"
5391
- slug: "${plan.slug}"
5392
- keywords:
5393
- ${yamlKeywords}
5394
- ---
5395
-
5396
- ## Description
5397
-
5398
- ${plan.description}
5399
-
5400
- ## Introduction Brief
5401
-
5402
- ${plan.introBrief}
5403
-
5404
- ## Sections
5405
-
5406
- | # | Title | Description |
5407
- |---|-------|-------------|
5408
- ${sectionsRows}
5409
-
5410
- ## Outro Brief
5411
-
5412
- ${plan.outroBrief}
5413
-
5414
- ## Image Plan
5415
-
5416
- | Type | Description |
5417
- |------|-------------|
5418
- ${imageRows}
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(resolved.config.settings, slug, cwd2);
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(settings, slug, cwd2) {
5528
- const outputPaths = resolveOutputPaths(settings, cwd2);
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 mkdir6, readFile as readFile8, stat as stat5, writeFile as writeFile6 } from "fs/promises";
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(resolved.config.settings, cwd2);
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 mkdir6(destinationDir, { recursive: true });
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 mkdir6(path11.dirname(destImagePath), { recursive: true });
6084
- await copyFile(absoluteImageSrc, destImagePath);
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 [savedSettings, envSettings] = await Promise.all([loadSavedSettings(), Promise.resolve(readEnvSettings())]);
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);