@telepat/ideon 0.1.13 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ideon.js
CHANGED
|
@@ -93,6 +93,12 @@ function resolveTargetLengthAlias(targetLengthWords) {
|
|
|
93
93
|
}
|
|
94
94
|
return "large";
|
|
95
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
|
+
}
|
|
96
102
|
var contentTargetRoleValues = ["primary", "secondary"];
|
|
97
103
|
var contentTargetSchema = z.object({
|
|
98
104
|
contentType: z.enum(contentTypeValues),
|
|
@@ -1299,7 +1305,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
1299
1305
|
// package.json
|
|
1300
1306
|
var package_default = {
|
|
1301
1307
|
name: "@telepat/ideon",
|
|
1302
|
-
version: "0.1.
|
|
1308
|
+
version: "0.1.14",
|
|
1303
1309
|
description: "CLI for generating rich articles and images from ideas.",
|
|
1304
1310
|
type: "module",
|
|
1305
1311
|
repository: {
|
|
@@ -1508,7 +1514,7 @@ import path8 from "path";
|
|
|
1508
1514
|
import { readFile as readFile4 } from "fs/promises";
|
|
1509
1515
|
|
|
1510
1516
|
// src/llm/prompts/linkEnrichment.ts
|
|
1511
|
-
function buildLinkCandidatesJsonSchema() {
|
|
1517
|
+
function buildLinkCandidatesJsonSchema(maxLinks = 10) {
|
|
1512
1518
|
return {
|
|
1513
1519
|
type: "object",
|
|
1514
1520
|
additionalProperties: false,
|
|
@@ -1517,13 +1523,13 @@ function buildLinkCandidatesJsonSchema() {
|
|
|
1517
1523
|
expressions: {
|
|
1518
1524
|
type: "array",
|
|
1519
1525
|
minItems: 0,
|
|
1520
|
-
maxItems:
|
|
1526
|
+
maxItems: maxLinks,
|
|
1521
1527
|
items: { type: "string", minLength: 2 }
|
|
1522
1528
|
}
|
|
1523
1529
|
}
|
|
1524
1530
|
};
|
|
1525
1531
|
}
|
|
1526
|
-
function buildLinkCandidatesMessages(content, contentType) {
|
|
1532
|
+
function buildLinkCandidatesMessages(content, contentType, maxLinks = 10) {
|
|
1527
1533
|
return [
|
|
1528
1534
|
{
|
|
1529
1535
|
role: "system",
|
|
@@ -1539,7 +1545,7 @@ function buildLinkCandidatesMessages(content, contentType) {
|
|
|
1539
1545
|
role: "user",
|
|
1540
1546
|
content: [
|
|
1541
1547
|
`Content type: ${contentType}`,
|
|
1542
|
-
|
|
1548
|
+
`Select up to ${maxLinks} expressions that should become links in this content.`,
|
|
1543
1549
|
"Each expression must be copied exactly from the text and be useful to link.",
|
|
1544
1550
|
"",
|
|
1545
1551
|
"Content:",
|
|
@@ -1594,6 +1600,8 @@ async function enrichLinks({
|
|
|
1594
1600
|
openRouter,
|
|
1595
1601
|
settings,
|
|
1596
1602
|
dryRun,
|
|
1603
|
+
customLinks = [],
|
|
1604
|
+
maxLinks = 10,
|
|
1597
1605
|
onLlmMetrics,
|
|
1598
1606
|
onItemProgress,
|
|
1599
1607
|
onInteraction
|
|
@@ -1615,7 +1623,8 @@ async function enrichLinks({
|
|
|
1615
1623
|
fileId: item.fileId,
|
|
1616
1624
|
contentType: item.contentType,
|
|
1617
1625
|
markdownPath: item.markdownPath,
|
|
1618
|
-
links: []
|
|
1626
|
+
links: [],
|
|
1627
|
+
customLinks
|
|
1619
1628
|
});
|
|
1620
1629
|
continue;
|
|
1621
1630
|
}
|
|
@@ -1634,7 +1643,8 @@ async function enrichLinks({
|
|
|
1634
1643
|
fileId: item.fileId,
|
|
1635
1644
|
contentType: item.contentType,
|
|
1636
1645
|
markdownPath: item.markdownPath,
|
|
1637
|
-
links: []
|
|
1646
|
+
links: [],
|
|
1647
|
+
customLinks
|
|
1638
1648
|
});
|
|
1639
1649
|
continue;
|
|
1640
1650
|
}
|
|
@@ -1645,8 +1655,8 @@ async function enrichLinks({
|
|
|
1645
1655
|
});
|
|
1646
1656
|
const candidateResult = await openRouter.requestStructured({
|
|
1647
1657
|
schemaName: "link_candidates",
|
|
1648
|
-
schema: buildLinkCandidatesJsonSchema(),
|
|
1649
|
-
messages: buildLinkCandidatesMessages(content, item.contentType),
|
|
1658
|
+
schema: buildLinkCandidatesJsonSchema(maxLinks),
|
|
1659
|
+
messages: buildLinkCandidatesMessages(content, item.contentType, maxLinks),
|
|
1650
1660
|
settings,
|
|
1651
1661
|
reasoning: LINKS_REASONING_SETTINGS,
|
|
1652
1662
|
interactionContext: {
|
|
@@ -1657,7 +1667,10 @@ async function enrichLinks({
|
|
|
1657
1667
|
parse(data) {
|
|
1658
1668
|
const record = data;
|
|
1659
1669
|
const expressions = Array.isArray(record.expressions) ? record.expressions.filter((value2) => typeof value2 === "string") : [];
|
|
1660
|
-
|
|
1670
|
+
const customExpressions = new Set(customLinks.map((e) => e.expression.trim().toLowerCase()));
|
|
1671
|
+
return {
|
|
1672
|
+
expressions: dedupeExpressions(expressions).filter((expr) => !customExpressions.has(expr.trim().toLowerCase())).slice(0, maxLinks)
|
|
1673
|
+
};
|
|
1661
1674
|
},
|
|
1662
1675
|
onMetrics(metrics) {
|
|
1663
1676
|
onLlmMetrics?.(item.fileId, metrics);
|
|
@@ -1734,7 +1747,8 @@ async function enrichLinks({
|
|
|
1734
1747
|
fileId: item.fileId,
|
|
1735
1748
|
contentType: item.contentType,
|
|
1736
1749
|
markdownPath: item.markdownPath,
|
|
1737
|
-
links
|
|
1750
|
+
links,
|
|
1751
|
+
customLinks
|
|
1738
1752
|
});
|
|
1739
1753
|
}
|
|
1740
1754
|
return results;
|
|
@@ -4355,7 +4369,8 @@ var linksResultSchema = z6.object({
|
|
|
4355
4369
|
fileId: z6.string().min(1),
|
|
4356
4370
|
contentType: z6.string().min(1),
|
|
4357
4371
|
markdownPath: z6.string().min(1),
|
|
4358
|
-
links: z6.array(linkEntrySchema)
|
|
4372
|
+
links: z6.array(linkEntrySchema),
|
|
4373
|
+
customLinks: z6.array(linkEntrySchema).default([])
|
|
4359
4374
|
});
|
|
4360
4375
|
var pipelineArtifactSummarySchema = z6.object({
|
|
4361
4376
|
title: z6.string().min(1),
|
|
@@ -4553,6 +4568,9 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4553
4568
|
const shouldEnrichLinks = options.enrichLinks ?? false;
|
|
4554
4569
|
const runMode = options.runMode ?? "fresh";
|
|
4555
4570
|
const workingDir = options.workingDir ?? process.cwd();
|
|
4571
|
+
const pipelineCustomLinkRaws = options.customLinks ?? [];
|
|
4572
|
+
const pipelineUnlinks = options.unlinks ?? [];
|
|
4573
|
+
const pipelineMaxLinks = options.maxLinks;
|
|
4556
4574
|
const outputPaths = resolveOutputPaths(input.config.settings, workingDir);
|
|
4557
4575
|
const hasArticlePrimary = isArticlePrimary;
|
|
4558
4576
|
const stageTracking = /* @__PURE__ */ new Map();
|
|
@@ -5422,15 +5440,19 @@ async function runPipelineShell(input, options = {}) {
|
|
|
5422
5440
|
options.onUpdate?.(cloneStages(stages));
|
|
5423
5441
|
} else if (linksResult) {
|
|
5424
5442
|
const linksByFileId = new Map(linksResult.map((item) => [item.fileId, item.links]));
|
|
5425
|
-
|
|
5443
|
+
const customLinksByFileId = new Map(linksResult.map((item) => [item.fileId, item.customLinks]));
|
|
5444
|
+
const resumedLinks = eligibleOutputsForLinks.map((output) => ({
|
|
5426
5445
|
fileId: output.fileId,
|
|
5427
5446
|
contentType: output.contentType,
|
|
5428
5447
|
markdownPath: output.markdownPath,
|
|
5429
|
-
links: linksByFileId.get(output.fileId) ?? []
|
|
5448
|
+
links: linksByFileId.get(output.fileId) ?? [],
|
|
5449
|
+
customLinks: customLinksByFileId.get(output.fileId) ?? []
|
|
5430
5450
|
}));
|
|
5431
|
-
|
|
5451
|
+
linksResult = resumedLinks;
|
|
5452
|
+
for (const item of resumedLinks) {
|
|
5432
5453
|
await writeLinksFile(item.markdownPath, {
|
|
5433
|
-
version:
|
|
5454
|
+
version: 2,
|
|
5455
|
+
customLinks: item.customLinks,
|
|
5434
5456
|
links: item.links
|
|
5435
5457
|
});
|
|
5436
5458
|
}
|
|
@@ -5439,7 +5461,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
5439
5461
|
...stages[6],
|
|
5440
5462
|
status: "succeeded",
|
|
5441
5463
|
detail: "Reused saved link metadata from .ideon/write.",
|
|
5442
|
-
summary: `${
|
|
5464
|
+
summary: `${resumedLinks.reduce((sum, item) => sum + item.links.length, 0)} links`,
|
|
5443
5465
|
items: (stages[6].items ?? []).map((item) => ({
|
|
5444
5466
|
...item,
|
|
5445
5467
|
status: "succeeded",
|
|
@@ -5457,6 +5479,8 @@ async function runPipelineShell(input, options = {}) {
|
|
|
5457
5479
|
openRouter,
|
|
5458
5480
|
settings: input.config.settings,
|
|
5459
5481
|
dryRun,
|
|
5482
|
+
customLinks: parsePipelineCustomLinks(pipelineCustomLinkRaws, pipelineUnlinks),
|
|
5483
|
+
maxLinks: pipelineMaxLinks ?? resolveDefaultMaxLinks(input.config.settings.targetLength),
|
|
5460
5484
|
onInteraction(interaction) {
|
|
5461
5485
|
onLlmInteraction(interaction);
|
|
5462
5486
|
},
|
|
@@ -5501,7 +5525,8 @@ async function runPipelineShell(input, options = {}) {
|
|
|
5501
5525
|
costSource
|
|
5502
5526
|
});
|
|
5503
5527
|
await writeLinksFile(item.markdownPath, {
|
|
5504
|
-
version:
|
|
5528
|
+
version: 2,
|
|
5529
|
+
customLinks: item.customLinks,
|
|
5505
5530
|
links: item.links
|
|
5506
5531
|
});
|
|
5507
5532
|
}
|
|
@@ -5866,7 +5891,6 @@ function toFilePrefix(contentType) {
|
|
|
5866
5891
|
if (contentType === "reddit-post") return "reddit";
|
|
5867
5892
|
if (contentType === "linkedin-post") return "linkedin";
|
|
5868
5893
|
if (contentType === "newsletter") return "newsletter";
|
|
5869
|
-
if (contentType === "landing-page-copy") return "landing";
|
|
5870
5894
|
return contentType.replace(/[^a-z0-9]+/gi, "-").replace(/^-+|-+$/g, "").toLowerCase() || "content";
|
|
5871
5895
|
}
|
|
5872
5896
|
function getPrimaryTarget(contentTargets) {
|
|
@@ -5962,6 +5986,24 @@ function asWriteStageId(stageId) {
|
|
|
5962
5986
|
}
|
|
5963
5987
|
return null;
|
|
5964
5988
|
}
|
|
5989
|
+
function parsePipelineCustomLinks(rawLinks, unlinks) {
|
|
5990
|
+
const result = /* @__PURE__ */ new Map();
|
|
5991
|
+
for (const raw of rawLinks) {
|
|
5992
|
+
const separatorIndex = raw.indexOf("->");
|
|
5993
|
+
if (separatorIndex < 0) {
|
|
5994
|
+
continue;
|
|
5995
|
+
}
|
|
5996
|
+
const expression = raw.slice(0, separatorIndex).trim();
|
|
5997
|
+
const url = raw.slice(separatorIndex + 2).trim();
|
|
5998
|
+
if (expression && url) {
|
|
5999
|
+
result.set(expression.toLowerCase(), { expression, url, title: null });
|
|
6000
|
+
}
|
|
6001
|
+
}
|
|
6002
|
+
for (const expr of unlinks) {
|
|
6003
|
+
result.delete(expr.trim().toLowerCase());
|
|
6004
|
+
}
|
|
6005
|
+
return Array.from(result.values());
|
|
6006
|
+
}
|
|
5965
6007
|
|
|
5966
6008
|
// src/cli/commands/writeTargetSpecs.ts
|
|
5967
6009
|
function parseTargetSpec(spec) {
|
|
@@ -6273,6 +6315,10 @@ async function runLinksCommand(options, dependencies = {}) {
|
|
|
6273
6315
|
);
|
|
6274
6316
|
}
|
|
6275
6317
|
const openRouter = new OpenRouterClient(openRouterApiKey);
|
|
6318
|
+
const linksPath = resolveLinksPath(markdownPath);
|
|
6319
|
+
const existing = await readExistingLinks(linksPath);
|
|
6320
|
+
const updatedCustomLinks = resolveCustomLinks(existing?.customLinks ?? [], options.links ?? [], options.unlinks ?? []);
|
|
6321
|
+
const effectiveMaxLinks = options.maxLinks;
|
|
6276
6322
|
const linksResult = await enrichLinks({
|
|
6277
6323
|
markdownFiles: [{ markdownPath, fileId, contentType: "article" }],
|
|
6278
6324
|
articleTitle,
|
|
@@ -6280,31 +6326,32 @@ async function runLinksCommand(options, dependencies = {}) {
|
|
|
6280
6326
|
openRouter,
|
|
6281
6327
|
settings: resolved.config.settings,
|
|
6282
6328
|
dryRun: false,
|
|
6329
|
+
customLinks: updatedCustomLinks,
|
|
6330
|
+
maxLinks: effectiveMaxLinks,
|
|
6283
6331
|
onItemProgress(event) {
|
|
6284
6332
|
logProgress(event, log);
|
|
6285
6333
|
}
|
|
6286
6334
|
});
|
|
6287
6335
|
const generatedLinks = linksResult[0]?.links ?? [];
|
|
6288
|
-
const
|
|
6289
|
-
const
|
|
6290
|
-
const mergedLinks = mode === "append" ? mergeLinks(existing?.links ?? [], generatedLinks) : generatedLinks;
|
|
6291
|
-
const appendedCount = Math.max(0, mergedLinks.length - (existing?.links.length ?? 0));
|
|
6336
|
+
const mergedGeneratedLinks = mode === "append" ? mergeLinks(existing?.links ?? [], generatedLinks) : generatedLinks;
|
|
6337
|
+
const appendedCount = Math.max(0, mergedGeneratedLinks.length - (existing?.links.length ?? 0));
|
|
6292
6338
|
await writeLinksFile(markdownPath, {
|
|
6293
|
-
version:
|
|
6294
|
-
|
|
6339
|
+
version: 2,
|
|
6340
|
+
customLinks: updatedCustomLinks,
|
|
6341
|
+
links: mergedGeneratedLinks
|
|
6295
6342
|
});
|
|
6296
6343
|
const relativeMarkdownPath = formatRelativePath2(cwd2, markdownPath);
|
|
6297
6344
|
const relativeLinksPath = formatRelativePath2(cwd2, linksPath);
|
|
6298
6345
|
if (mode === "fresh") {
|
|
6299
6346
|
const replaced = existing ? "Replaced existing links." : "Created links sidecar.";
|
|
6300
6347
|
log(`Enriched links for "${slug}".`);
|
|
6301
|
-
log(`${replaced} Saved ${generatedLinks.length} links to ${relativeLinksPath} (${relativeMarkdownPath}).`);
|
|
6348
|
+
log(`${replaced} Saved ${generatedLinks.length} generated + ${updatedCustomLinks.length} custom links to ${relativeLinksPath} (${relativeMarkdownPath}).`);
|
|
6302
6349
|
return;
|
|
6303
6350
|
}
|
|
6304
6351
|
const baseCount = existing?.links.length ?? 0;
|
|
6305
6352
|
const verb = existing ? "Appended and deduplicated links." : "Created links sidecar.";
|
|
6306
6353
|
log(`Enriched links for "${slug}".`);
|
|
6307
|
-
log(`${verb} Base ${baseCount}, added ${appendedCount}, total ${
|
|
6354
|
+
log(`${verb} Base ${baseCount}, added ${appendedCount}, total ${mergedGeneratedLinks.length} generated + ${updatedCustomLinks.length} custom in ${relativeLinksPath} (${relativeMarkdownPath}).`);
|
|
6308
6355
|
}
|
|
6309
6356
|
function normalizeMode(rawMode) {
|
|
6310
6357
|
const normalized = rawMode.trim().toLowerCase();
|
|
@@ -6420,8 +6467,14 @@ async function readExistingLinks(linksPath) {
|
|
|
6420
6467
|
if (!links) {
|
|
6421
6468
|
throw new ReportedError(`Invalid links sidecar format at ${linksPath}. Expected { version, links[] }.`);
|
|
6422
6469
|
}
|
|
6470
|
+
const customLinks = Array.isArray(parsed.customLinks) ? parsed.customLinks.filter((entry) => isValidLinkEntry(entry)).map((entry) => ({
|
|
6471
|
+
expression: entry.expression.trim(),
|
|
6472
|
+
url: entry.url.trim(),
|
|
6473
|
+
title: typeof entry.title === "string" ? entry.title : null
|
|
6474
|
+
})) : [];
|
|
6423
6475
|
return {
|
|
6424
6476
|
version: typeof parsed.version === "number" ? parsed.version : 1,
|
|
6477
|
+
customLinks,
|
|
6425
6478
|
links
|
|
6426
6479
|
};
|
|
6427
6480
|
} catch (error) {
|
|
@@ -6472,6 +6525,34 @@ function logProgress(event, log) {
|
|
|
6472
6525
|
}
|
|
6473
6526
|
log(event.detail);
|
|
6474
6527
|
}
|
|
6528
|
+
function parseCustomLinkFlag(raw) {
|
|
6529
|
+
const separatorIndex = raw.indexOf("->");
|
|
6530
|
+
if (separatorIndex < 0) {
|
|
6531
|
+
throw new ReportedError(`Invalid --link value "${raw}". Expected format: "expression->url".`);
|
|
6532
|
+
}
|
|
6533
|
+
const expression = raw.slice(0, separatorIndex).trim();
|
|
6534
|
+
const url = raw.slice(separatorIndex + 2).trim();
|
|
6535
|
+
if (!expression) {
|
|
6536
|
+
throw new ReportedError(`Invalid --link value "${raw}": expression (left side of ->) cannot be empty.`);
|
|
6537
|
+
}
|
|
6538
|
+
if (!url) {
|
|
6539
|
+
throw new ReportedError(`Invalid --link value "${raw}": url (right side of ->) cannot be empty.`);
|
|
6540
|
+
}
|
|
6541
|
+
return { expression, url };
|
|
6542
|
+
}
|
|
6543
|
+
function resolveCustomLinks(existing, addRaw, removeExpressions) {
|
|
6544
|
+
const result = new Map(
|
|
6545
|
+
existing.map((entry) => [entry.expression.trim().toLowerCase(), entry])
|
|
6546
|
+
);
|
|
6547
|
+
for (const raw of addRaw) {
|
|
6548
|
+
const { expression, url } = parseCustomLinkFlag(raw);
|
|
6549
|
+
result.set(expression.toLowerCase(), { expression, url, title: null });
|
|
6550
|
+
}
|
|
6551
|
+
for (const expr of removeExpressions) {
|
|
6552
|
+
result.delete(expr.trim().toLowerCase());
|
|
6553
|
+
}
|
|
6554
|
+
return Array.from(result.values());
|
|
6555
|
+
}
|
|
6475
6556
|
|
|
6476
6557
|
// src/cli/commands/settings.tsx
|
|
6477
6558
|
import { render } from "ink";
|
|
@@ -6852,7 +6933,7 @@ import { spawn } from "child_process";
|
|
|
6852
6933
|
import { readdir, stat as stat4, readFile as readFile7 } from "fs/promises";
|
|
6853
6934
|
import path10 from "path";
|
|
6854
6935
|
var DEFAULT_PORT = 4173;
|
|
6855
|
-
var CONTENT_TYPE_ORDER = ["article", "blog-post", "x-thread", "x-post", "linkedin-post", "reddit-post", "newsletter"
|
|
6936
|
+
var CONTENT_TYPE_ORDER = ["article", "blog-post", "x-thread", "x-post", "linkedin-post", "reddit-post", "newsletter"];
|
|
6856
6937
|
var FILE_PREFIX_TO_CONTENT_TYPE = {
|
|
6857
6938
|
article: "article",
|
|
6858
6939
|
blog: "blog-post",
|
|
@@ -6861,8 +6942,7 @@ var FILE_PREFIX_TO_CONTENT_TYPE = {
|
|
|
6861
6942
|
x: "x-post",
|
|
6862
6943
|
reddit: "reddit-post",
|
|
6863
6944
|
linkedin: "linkedin-post",
|
|
6864
|
-
newsletter: "newsletter"
|
|
6865
|
-
landing: "landing-page-copy"
|
|
6945
|
+
newsletter: "newsletter"
|
|
6866
6946
|
};
|
|
6867
6947
|
var CONTENT_TYPE_LABELS = {
|
|
6868
6948
|
article: "Article",
|
|
@@ -6871,8 +6951,7 @@ var CONTENT_TYPE_LABELS = {
|
|
|
6871
6951
|
"x-post": "X Post",
|
|
6872
6952
|
"reddit-post": "Reddit Post",
|
|
6873
6953
|
"linkedin-post": "LinkedIn Post",
|
|
6874
|
-
newsletter: "Newsletter"
|
|
6875
|
-
"landing-page-copy": "Landing Page Copy"
|
|
6954
|
+
newsletter: "Newsletter"
|
|
6876
6955
|
};
|
|
6877
6956
|
function parsePort(portOption) {
|
|
6878
6957
|
if (!portOption) {
|
|
@@ -7630,10 +7709,6 @@ function renderShell({
|
|
|
7630
7709
|
--newsletter-bg: #fffdf4;
|
|
7631
7710
|
--newsletter-header-bg: #fff5cc;
|
|
7632
7711
|
--newsletter-border: #cfb95a;
|
|
7633
|
-
--landing-bg: linear-gradient(155deg, #10395c 0%, #3d7fa0 100%);
|
|
7634
|
-
--landing-text: #f8fdff;
|
|
7635
|
-
--landing-link: #d7f0ff;
|
|
7636
|
-
--landing-border: rgba(255, 255, 255, 0.3);
|
|
7637
7712
|
color-scheme: light;
|
|
7638
7713
|
}
|
|
7639
7714
|
|
|
@@ -7673,10 +7748,6 @@ function renderShell({
|
|
|
7673
7748
|
--newsletter-bg: #2e291b;
|
|
7674
7749
|
--newsletter-header-bg: #3b331e;
|
|
7675
7750
|
--newsletter-border: #d6b25f;
|
|
7676
|
-
--landing-bg: linear-gradient(155deg, #0f2236 0%, #245d7e 100%);
|
|
7677
|
-
--landing-text: #e7f4ff;
|
|
7678
|
-
--landing-link: #b8e4ff;
|
|
7679
|
-
--landing-border: rgba(220, 239, 255, 0.35);
|
|
7680
7751
|
color-scheme: dark;
|
|
7681
7752
|
}
|
|
7682
7753
|
}
|
|
@@ -7716,10 +7787,6 @@ function renderShell({
|
|
|
7716
7787
|
--newsletter-bg: #fffdf4;
|
|
7717
7788
|
--newsletter-header-bg: #fff5cc;
|
|
7718
7789
|
--newsletter-border: #cfb95a;
|
|
7719
|
-
--landing-bg: linear-gradient(155deg, #10395c 0%, #3d7fa0 100%);
|
|
7720
|
-
--landing-text: #f8fdff;
|
|
7721
|
-
--landing-link: #d7f0ff;
|
|
7722
|
-
--landing-border: rgba(255, 255, 255, 0.3);
|
|
7723
7790
|
color-scheme: light;
|
|
7724
7791
|
}
|
|
7725
7792
|
|
|
@@ -7758,10 +7825,6 @@ function renderShell({
|
|
|
7758
7825
|
--newsletter-bg: #2e291b;
|
|
7759
7826
|
--newsletter-header-bg: #3b331e;
|
|
7760
7827
|
--newsletter-border: #d6b25f;
|
|
7761
|
-
--landing-bg: linear-gradient(155deg, #0f2236 0%, #245d7e 100%);
|
|
7762
|
-
--landing-text: #e7f4ff;
|
|
7763
|
-
--landing-link: #b8e4ff;
|
|
7764
|
-
--landing-border: rgba(220, 239, 255, 0.35);
|
|
7765
7828
|
color-scheme: dark;
|
|
7766
7829
|
}
|
|
7767
7830
|
|
|
@@ -8281,21 +8344,6 @@ function renderShell({
|
|
|
8281
8344
|
background: var(--newsletter-header-bg);
|
|
8282
8345
|
}
|
|
8283
8346
|
|
|
8284
|
-
.channel-landing-page-copy {
|
|
8285
|
-
background: var(--landing-bg);
|
|
8286
|
-
color: var(--landing-text);
|
|
8287
|
-
border: none;
|
|
8288
|
-
}
|
|
8289
|
-
|
|
8290
|
-
.channel-landing-page-copy .channel-header {
|
|
8291
|
-
border-bottom: 1px solid var(--landing-border);
|
|
8292
|
-
}
|
|
8293
|
-
|
|
8294
|
-
.channel-landing-page-copy .channel-meta,
|
|
8295
|
-
.channel-landing-page-copy a {
|
|
8296
|
-
color: var(--landing-link);
|
|
8297
|
-
}
|
|
8298
|
-
|
|
8299
8347
|
.channel-article,
|
|
8300
8348
|
.channel-blog-post {
|
|
8301
8349
|
background: var(--paper);
|
|
@@ -8481,7 +8529,7 @@ function renderShell({
|
|
|
8481
8529
|
const articleElement = document.getElementById('article');
|
|
8482
8530
|
const articleListElement = document.getElementById('articleList');
|
|
8483
8531
|
const themeToggleButton = document.getElementById('themeToggle');
|
|
8484
|
-
const typeOrder = ['article', 'blog-post', 'x-thread', 'x-post', 'linkedin-post', 'reddit-post', 'newsletter'
|
|
8532
|
+
const typeOrder = ['article', 'blog-post', 'x-thread', 'x-post', 'linkedin-post', 'reddit-post', 'newsletter'];
|
|
8485
8533
|
const stageOrder = ['shared-brief', 'planning', 'sections', 'image-prompts', 'images', 'output', 'links'];
|
|
8486
8534
|
|
|
8487
8535
|
let currentGeneration = null;
|
|
@@ -9582,7 +9630,7 @@ function formatPipelineStageCost(stage) {
|
|
|
9582
9630
|
}
|
|
9583
9631
|
return stage.costSource === "estimated" ? `~${formatted}` : formatted;
|
|
9584
9632
|
}
|
|
9585
|
-
async function renderPlainPipeline(input, dryRun, enrichLinks2, runMode) {
|
|
9633
|
+
async function renderPlainPipeline(input, dryRun, enrichLinks2, runMode, links, unlinks, maxLinks) {
|
|
9586
9634
|
let previousStages = /* @__PURE__ */ new Map();
|
|
9587
9635
|
let previousItemStatuses = /* @__PURE__ */ new Map();
|
|
9588
9636
|
const notificationsEnabled = input.config.settings.notifications.enabled;
|
|
@@ -9596,6 +9644,9 @@ async function renderPlainPipeline(input, dryRun, enrichLinks2, runMode) {
|
|
|
9596
9644
|
dryRun,
|
|
9597
9645
|
enrichLinks: enrichLinks2,
|
|
9598
9646
|
runMode,
|
|
9647
|
+
customLinks: links,
|
|
9648
|
+
unlinks,
|
|
9649
|
+
maxLinks,
|
|
9599
9650
|
onUpdate(stages) {
|
|
9600
9651
|
for (const stage of stages) {
|
|
9601
9652
|
const previous = previousStages.get(stage.id);
|
|
@@ -9954,6 +10005,9 @@ function WriteApp({
|
|
|
9954
10005
|
dryRun,
|
|
9955
10006
|
enrichLinks: enrichLinks2,
|
|
9956
10007
|
runMode,
|
|
10008
|
+
links,
|
|
10009
|
+
unlinks,
|
|
10010
|
+
maxLinks,
|
|
9957
10011
|
onError
|
|
9958
10012
|
}) {
|
|
9959
10013
|
const { exit } = useApp3();
|
|
@@ -9977,6 +10031,9 @@ function WriteApp({
|
|
|
9977
10031
|
dryRun,
|
|
9978
10032
|
enrichLinks: enrichLinks2,
|
|
9979
10033
|
runMode,
|
|
10034
|
+
customLinks: links,
|
|
10035
|
+
unlinks,
|
|
10036
|
+
maxLinks,
|
|
9980
10037
|
onUpdate(nextStages) {
|
|
9981
10038
|
if (mounted) {
|
|
9982
10039
|
setStages(nextStages);
|
|
@@ -10009,7 +10066,7 @@ function WriteApp({
|
|
|
10009
10066
|
return () => {
|
|
10010
10067
|
mounted = false;
|
|
10011
10068
|
};
|
|
10012
|
-
}, [dryRun, enrichLinks2, input, onError, runMode]);
|
|
10069
|
+
}, [dryRun, enrichLinks2, input, links, unlinks, maxLinks, onError, runMode]);
|
|
10013
10070
|
useEffect2(() => {
|
|
10014
10071
|
if (!result && !errorMessage) {
|
|
10015
10072
|
return;
|
|
@@ -10025,7 +10082,7 @@ function WriteApp({
|
|
|
10025
10082
|
}
|
|
10026
10083
|
async function runWriteCommand(options) {
|
|
10027
10084
|
const input = await resolveInputWithInteractiveIdeaFallback(options);
|
|
10028
|
-
await runWritePipeline(input, options.dryRun, options.enrichLinks, "fresh", options.noInteractive);
|
|
10085
|
+
await runWritePipeline(input, options.dryRun, options.enrichLinks, "fresh", options.noInteractive, options.links, options.unlinks, options.maxLinks);
|
|
10029
10086
|
}
|
|
10030
10087
|
async function runWriteResumeCommand(options = {}) {
|
|
10031
10088
|
const session = await loadWriteSession();
|
|
@@ -10047,9 +10104,9 @@ async function runWriteResumeCommand(options = {}) {
|
|
|
10047
10104
|
secrets: resolved.config.secrets
|
|
10048
10105
|
}
|
|
10049
10106
|
};
|
|
10050
|
-
await runWritePipeline(input, session.dryRun, options.enrichLinks ?? false, "resume", options.noInteractive ?? false);
|
|
10107
|
+
await runWritePipeline(input, session.dryRun, options.enrichLinks ?? false, "resume", options.noInteractive ?? false, options.links, options.unlinks, options.maxLinks);
|
|
10051
10108
|
}
|
|
10052
|
-
async function runWritePipeline(input, dryRun, enrichLinks2, runMode, noInteractive) {
|
|
10109
|
+
async function runWritePipeline(input, dryRun, enrichLinks2, runMode, noInteractive, links, unlinks, maxLinks) {
|
|
10053
10110
|
let interruptHandled = false;
|
|
10054
10111
|
const handleSignal = (signal) => {
|
|
10055
10112
|
if (interruptHandled) {
|
|
@@ -10083,7 +10140,7 @@ async function runWritePipeline(input, dryRun, enrichLinks2, runMode, noInteract
|
|
|
10083
10140
|
process.on("SIGTERM", onSigterm);
|
|
10084
10141
|
try {
|
|
10085
10142
|
if (noInteractive || !process.stdout.isTTY) {
|
|
10086
|
-
await renderPlainPipeline(input, dryRun, enrichLinks2, runMode);
|
|
10143
|
+
await renderPlainPipeline(input, dryRun, enrichLinks2, runMode, links, unlinks, maxLinks);
|
|
10087
10144
|
return;
|
|
10088
10145
|
}
|
|
10089
10146
|
let commandError = null;
|
|
@@ -10095,6 +10152,9 @@ async function runWritePipeline(input, dryRun, enrichLinks2, runMode, noInteract
|
|
|
10095
10152
|
dryRun,
|
|
10096
10153
|
enrichLinks: enrichLinks2,
|
|
10097
10154
|
runMode,
|
|
10155
|
+
links,
|
|
10156
|
+
unlinks,
|
|
10157
|
+
maxLinks,
|
|
10098
10158
|
onError: (error) => {
|
|
10099
10159
|
commandError = error;
|
|
10100
10160
|
}
|
|
@@ -10298,10 +10358,13 @@ async function runCli(argv) {
|
|
|
10298
10358
|
force: options.force
|
|
10299
10359
|
});
|
|
10300
10360
|
});
|
|
10301
|
-
program.command("links").description("Run link enrichment for a previously generated article by slug.").argument("<slug>", "Slug of the generated article to enrich").option("--mode <mode>", "Link merge mode: fresh or append", "fresh").action(async (slug, options) => {
|
|
10361
|
+
program.command("links").description("Run link enrichment for a previously generated article by slug.").argument("<slug>", "Slug of the generated article to enrich").option("--mode <mode>", "Link merge mode: fresh or append", "fresh").option("--link <pair>", 'Custom link "expression->url", repeatable', collectOptionValue).option("--unlink <expression>", "Remove a custom link by expression, repeatable", collectOptionValue).option("--max-links <n>", "Max number of generated links", (v) => Number.parseInt(v, 10)).action(async (slug, options) => {
|
|
10302
10362
|
await runLinksCommand({
|
|
10303
10363
|
slug,
|
|
10304
|
-
mode: options.mode
|
|
10364
|
+
mode: options.mode,
|
|
10365
|
+
links: options.link,
|
|
10366
|
+
unlinks: options.unlink,
|
|
10367
|
+
maxLinks: options.maxLinks
|
|
10305
10368
|
});
|
|
10306
10369
|
});
|
|
10307
10370
|
program.command("preview").description("Preview a generated article in a local browser with linked assets.").argument("[markdownPath]", "Path to the markdown file to preview").option("-p, --port <port>", "Port for the local preview server (default: 4173)").option("--no-open", "Do not auto-open browser after server startup").option("--watch", "Rebuild the preview UI on source changes and auto-reload the browser", false).action(async (markdownPath, options) => {
|
|
@@ -10312,7 +10375,7 @@ async function runCli(argv) {
|
|
|
10312
10375
|
watch: options.watch
|
|
10313
10376
|
});
|
|
10314
10377
|
});
|
|
10315
|
-
const writeCommand = program.command("write").description("Generate one primary content output plus optional secondary outputs from a prompt or job file.").argument("[idea]", "Natural-language idea for the generation run").option("-i, --idea <idea>", "Natural-language idea for the generation run").option("--audience <description>", "Optional natural-language audience description for shared-brief targeting").option("-j, --job <path>", "Path to a JSON job definition").option("--primary <type=count>", "Required primary output target (for example: article=1 or x-post=1)").option("--secondary <type=count>", "Secondary output target, repeatable (for example: x-thread=3, linkedin-post=2)", collectOptionValue).option("--style <style>", "Writing style (academic, analytical, authoritative, conversational, empathetic, friendly, journalistic, minimalist, persuasive, playful, professional, storytelling, technical)").option("--intent <intent>", "Content intent (announcement, case-study, cornerstone, counterargument, critique-review, deep-dive-analysis, how-to-guide, interview-q-and-a, listicle, opinion-piece, personal-essay, roundup-curation, tutorial)").option("--length <size>", "Target length: small, medium, large, or a positive integer word count").option("--no-interactive", "Fail instead of prompting for missing input in TTY mode").option("--dry-run", "Run the pipeline shell without external API calls", false).option("--enrich-links", "Run link enrichment after markdown generation", false).action(async (ideaArg, options) => {
|
|
10378
|
+
const writeCommand = program.command("write").description("Generate one primary content output plus optional secondary outputs from a prompt or job file.").argument("[idea]", "Natural-language idea for the generation run").option("-i, --idea <idea>", "Natural-language idea for the generation run").option("--audience <description>", "Optional natural-language audience description for shared-brief targeting").option("-j, --job <path>", "Path to a JSON job definition").option("--primary <type=count>", "Required primary output target (for example: article=1 or x-post=1)").option("--secondary <type=count>", "Secondary output target, repeatable (for example: x-thread=3, linkedin-post=2)", collectOptionValue).option("--style <style>", "Writing style (academic, analytical, authoritative, conversational, empathetic, friendly, journalistic, minimalist, persuasive, playful, professional, storytelling, technical)").option("--intent <intent>", "Content intent (announcement, case-study, cornerstone, counterargument, critique-review, deep-dive-analysis, how-to-guide, interview-q-and-a, listicle, opinion-piece, personal-essay, roundup-curation, tutorial)").option("--length <size>", "Target length: small, medium, large, or a positive integer word count").option("--no-interactive", "Fail instead of prompting for missing input in TTY mode").option("--dry-run", "Run the pipeline shell without external API calls", false).option("--enrich-links", "Run link enrichment after markdown generation", false).option("--link <pair>", 'Custom link "expression->url", repeatable', collectOptionValue).option("--unlink <expression>", "Remove a custom link by expression, repeatable", collectOptionValue).option("--max-links <n>", "Max number of generated links", (v) => Number.parseInt(v, 10)).action(async (ideaArg, options) => {
|
|
10316
10379
|
await runWriteCommand({
|
|
10317
10380
|
idea: options.idea ?? ideaArg,
|
|
10318
10381
|
audience: options.audience,
|
|
@@ -10324,13 +10387,19 @@ async function runCli(argv) {
|
|
|
10324
10387
|
length: options.length,
|
|
10325
10388
|
noInteractive: !options.interactive,
|
|
10326
10389
|
dryRun: options.dryRun,
|
|
10327
|
-
enrichLinks: options.enrichLinks
|
|
10390
|
+
enrichLinks: options.enrichLinks,
|
|
10391
|
+
links: options.link,
|
|
10392
|
+
unlinks: options.unlink,
|
|
10393
|
+
maxLinks: options.maxLinks
|
|
10328
10394
|
});
|
|
10329
10395
|
});
|
|
10330
|
-
writeCommand.command("resume").description("Resume the last failed or interrupted write session from .ideon/write.").option("--no-interactive", "Force plain non-interactive output even in TTY mode", false).option("--enrich-links", "Run link enrichment after markdown generation", false).action(async (options) => {
|
|
10396
|
+
writeCommand.command("resume").description("Resume the last failed or interrupted write session from .ideon/write.").option("--no-interactive", "Force plain non-interactive output even in TTY mode", false).option("--enrich-links", "Run link enrichment after markdown generation", false).option("--link <pair>", 'Custom link "expression->url", repeatable', collectOptionValue).option("--unlink <expression>", "Remove a custom link by expression, repeatable", collectOptionValue).option("--max-links <n>", "Max number of generated links", (v) => Number.parseInt(v, 10)).action(async (options) => {
|
|
10331
10397
|
await runWriteResumeCommand({
|
|
10332
10398
|
noInteractive: options.noInteractive,
|
|
10333
|
-
enrichLinks: options.enrichLinks
|
|
10399
|
+
enrichLinks: options.enrichLinks,
|
|
10400
|
+
links: options.link,
|
|
10401
|
+
unlinks: options.unlink,
|
|
10402
|
+
maxLinks: options.maxLinks
|
|
10334
10403
|
});
|
|
10335
10404
|
});
|
|
10336
10405
|
await program.parseAsync(argv);
|