vidpipe 1.3.6 → 1.3.7

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 (3) hide show
  1. package/dist/cli.js +1357 -269
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +2 -1
package/dist/cli.js CHANGED
@@ -316,7 +316,9 @@ function initConfig(cli = {}) {
316
316
  LATE_PROFILE_ID: cli.lateProfileId || process.env.LATE_PROFILE_ID || "",
317
317
  SKIP_SOCIAL_PUBLISH: cli.socialPublish === false,
318
318
  GEMINI_API_KEY: process.env.GEMINI_API_KEY || "",
319
- GEMINI_MODEL: process.env.GEMINI_MODEL || "gemini-2.5-pro"
319
+ GEMINI_MODEL: process.env.GEMINI_MODEL || "gemini-2.5-pro",
320
+ IDEAS_REPO: cli.ideasRepo || process.env.IDEAS_REPO || "htekdev/content-management",
321
+ GITHUB_TOKEN: cli.githubToken || process.env.GITHUB_TOKEN || ""
320
322
  };
321
323
  return config;
322
324
  }
@@ -402,6 +404,54 @@ var init_configLogger = __esm({
402
404
  }
403
405
  });
404
406
 
407
+ // src/L0-pure/types/index.ts
408
+ function toLatePlatform(platform) {
409
+ return platform === "x" /* X */ ? "twitter" : platform;
410
+ }
411
+ function fromLatePlatform(latePlatform) {
412
+ const normalized = normalizePlatformString(latePlatform);
413
+ if (normalized === "twitter") {
414
+ return "x" /* X */;
415
+ }
416
+ const platformValues2 = Object.values(Platform);
417
+ if (platformValues2.includes(normalized)) {
418
+ return normalized;
419
+ }
420
+ throw new Error(`Unsupported platform from Late API: ${latePlatform}`);
421
+ }
422
+ function normalizePlatformString(raw) {
423
+ const lower = raw.toLowerCase().trim();
424
+ if (lower === "x" || lower === "x (twitter)" || lower === "x/twitter") {
425
+ return "twitter";
426
+ }
427
+ return lower;
428
+ }
429
+ function isSupportedVideoExtension(ext) {
430
+ return SUPPORTED_VIDEO_EXTENSIONS.includes(ext.toLowerCase());
431
+ }
432
+ var Platform, PLATFORM_CHAR_LIMITS, SUPPORTED_VIDEO_EXTENSIONS;
433
+ var init_types = __esm({
434
+ "src/L0-pure/types/index.ts"() {
435
+ "use strict";
436
+ Platform = /* @__PURE__ */ ((Platform2) => {
437
+ Platform2["TikTok"] = "tiktok";
438
+ Platform2["YouTube"] = "youtube";
439
+ Platform2["Instagram"] = "instagram";
440
+ Platform2["LinkedIn"] = "linkedin";
441
+ Platform2["X"] = "x";
442
+ return Platform2;
443
+ })(Platform || {});
444
+ PLATFORM_CHAR_LIMITS = {
445
+ tiktok: 2200,
446
+ youtube: 5e3,
447
+ instagram: 2200,
448
+ linkedin: 3e3,
449
+ twitter: 280
450
+ };
451
+ SUPPORTED_VIDEO_EXTENSIONS = [".mp4", ".webm"];
452
+ }
453
+ });
454
+
405
455
  // src/L0-pure/pricing/pricing.ts
406
456
  function calculateTokenCost(model, inputTokens, outputTokens) {
407
457
  const pricing = getModelPricing(model);
@@ -1187,151 +1237,870 @@ var init_modelConfig = __esm({
1187
1237
  }
1188
1238
  });
1189
1239
 
1190
- // src/L1-infra/ideaStore/ideaStore.ts
1191
- function resolveIdeasDir(dir) {
1192
- return dir ? resolve(dir) : DEFAULT_IDEAS_DIR;
1240
+ // src/L2-clients/github/githubClient.ts
1241
+ import { Octokit } from "octokit";
1242
+ function getErrorStatus(error) {
1243
+ if (typeof error === "object" && error !== null && "status" in error && typeof error.status === "number") {
1244
+ return error.status;
1245
+ }
1246
+ return void 0;
1193
1247
  }
1194
1248
  function getErrorMessage(error) {
1195
- return error instanceof Error ? error.message : String(error);
1249
+ if (error instanceof Error) {
1250
+ return error.message;
1251
+ }
1252
+ if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
1253
+ return error.message ?? "Unknown GitHub API error";
1254
+ }
1255
+ return String(error);
1256
+ }
1257
+ function normalizeLabels(labels) {
1258
+ return Array.from(new Set(labels.map((label) => label.trim()).filter((label) => label.length > 0)));
1196
1259
  }
1197
- function validateIdeaId(id) {
1198
- if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(id)) {
1199
- throw new Error(`Invalid idea ID: ${id}`);
1260
+ function isIssueResponse(value) {
1261
+ return !("pull_request" in value);
1262
+ }
1263
+ function getGitHubClient() {
1264
+ const config2 = getConfig();
1265
+ const nextKey = `${config2.IDEAS_REPO}:${config2.GITHUB_TOKEN}`;
1266
+ if (!clientInstance || clientKey !== nextKey) {
1267
+ clientInstance = new GitHubClient(config2.GITHUB_TOKEN, config2.IDEAS_REPO);
1268
+ clientKey = nextKey;
1200
1269
  }
1201
- return id;
1270
+ return clientInstance;
1202
1271
  }
1203
- function getIdeaFilePath(id, dir) {
1204
- return join(resolveIdeasDir(dir), `${validateIdeaId(id)}${IDEA_FILE_EXTENSION}`);
1272
+ var DEFAULT_PER_PAGE, GitHubClientError, GitHubClient, clientInstance, clientKey;
1273
+ var init_githubClient = __esm({
1274
+ "src/L2-clients/github/githubClient.ts"() {
1275
+ "use strict";
1276
+ init_environment();
1277
+ init_configLogger();
1278
+ DEFAULT_PER_PAGE = 100;
1279
+ GitHubClientError = class extends Error {
1280
+ constructor(message, status) {
1281
+ super(message);
1282
+ this.status = status;
1283
+ this.name = "GitHubClientError";
1284
+ }
1285
+ };
1286
+ GitHubClient = class {
1287
+ octokit;
1288
+ owner;
1289
+ repo;
1290
+ constructor(token, repoFullName) {
1291
+ const config2 = getConfig();
1292
+ const authToken = token || config2.GITHUB_TOKEN;
1293
+ if (!authToken) {
1294
+ throw new Error("GITHUB_TOKEN is required for GitHub API access");
1295
+ }
1296
+ const fullName = repoFullName || config2.IDEAS_REPO;
1297
+ const [owner, repo] = fullName.split("/").map((part) => part.trim());
1298
+ if (!owner || !repo) {
1299
+ throw new Error(`Invalid IDEAS_REPO format: "${fullName}" \u2014 expected "owner/repo"`);
1300
+ }
1301
+ this.owner = owner;
1302
+ this.repo = repo;
1303
+ this.octokit = new Octokit({ auth: authToken });
1304
+ }
1305
+ async createIssue(input) {
1306
+ logger_default.debug(`[GitHubClient] Creating issue in ${this.owner}/${this.repo}: ${input.title}`);
1307
+ try {
1308
+ const response = await this.octokit.rest.issues.create({
1309
+ owner: this.owner,
1310
+ repo: this.repo,
1311
+ title: input.title,
1312
+ body: input.body,
1313
+ labels: input.labels ? normalizeLabels(input.labels) : void 0
1314
+ });
1315
+ const issue = this.mapIssue(response.data);
1316
+ logger_default.info(`[GitHubClient] Created issue #${issue.number}: ${input.title}`);
1317
+ return issue;
1318
+ } catch (error) {
1319
+ this.logError("create issue", error);
1320
+ throw new GitHubClientError(`Failed to create GitHub issue: ${getErrorMessage(error)}`, getErrorStatus(error));
1321
+ }
1322
+ }
1323
+ async updateIssue(issueNumber, input) {
1324
+ logger_default.debug(`[GitHubClient] Updating issue #${issueNumber} in ${this.owner}/${this.repo}`);
1325
+ try {
1326
+ const response = await this.octokit.rest.issues.update({
1327
+ owner: this.owner,
1328
+ repo: this.repo,
1329
+ issue_number: issueNumber,
1330
+ title: input.title,
1331
+ body: input.body,
1332
+ state: input.state,
1333
+ labels: input.labels ? normalizeLabels(input.labels) : void 0
1334
+ });
1335
+ return this.mapIssue(response.data);
1336
+ } catch (error) {
1337
+ this.logError(`update issue #${issueNumber}`, error);
1338
+ throw new GitHubClientError(
1339
+ `Failed to update GitHub issue #${issueNumber}: ${getErrorMessage(error)}`,
1340
+ getErrorStatus(error)
1341
+ );
1342
+ }
1343
+ }
1344
+ async getIssue(issueNumber) {
1345
+ logger_default.debug(`[GitHubClient] Fetching issue #${issueNumber} from ${this.owner}/${this.repo}`);
1346
+ try {
1347
+ const response = await this.octokit.rest.issues.get({
1348
+ owner: this.owner,
1349
+ repo: this.repo,
1350
+ issue_number: issueNumber
1351
+ });
1352
+ return this.mapIssue(response.data);
1353
+ } catch (error) {
1354
+ this.logError(`get issue #${issueNumber}`, error);
1355
+ throw new GitHubClientError(
1356
+ `Failed to fetch GitHub issue #${issueNumber}: ${getErrorMessage(error)}`,
1357
+ getErrorStatus(error)
1358
+ );
1359
+ }
1360
+ }
1361
+ async listIssues(options = {}) {
1362
+ logger_default.debug(`[GitHubClient] Listing issues for ${this.owner}/${this.repo}`);
1363
+ const issues = [];
1364
+ let page;
1365
+ const maxResults = options.maxResults ?? Number.POSITIVE_INFINITY;
1366
+ try {
1367
+ while (issues.length < maxResults) {
1368
+ const response = await this.octokit.rest.issues.listForRepo({
1369
+ owner: this.owner,
1370
+ repo: this.repo,
1371
+ state: "open",
1372
+ labels: options.labels && options.labels.length > 0 ? normalizeLabels(options.labels).join(",") : void 0,
1373
+ sort: void 0,
1374
+ direction: void 0,
1375
+ per_page: DEFAULT_PER_PAGE,
1376
+ page
1377
+ });
1378
+ const pageItems = response.data.filter(isIssueResponse).map((issue) => this.mapIssue(issue));
1379
+ issues.push(...pageItems);
1380
+ if (pageItems.length < DEFAULT_PER_PAGE) {
1381
+ break;
1382
+ }
1383
+ page = (page ?? 1) + 1;
1384
+ }
1385
+ return issues.slice(0, maxResults);
1386
+ } catch (error) {
1387
+ this.logError("list issues", error);
1388
+ throw new GitHubClientError(`Failed to list GitHub issues: ${getErrorMessage(error)}`, getErrorStatus(error));
1389
+ }
1390
+ }
1391
+ async searchIssues(query, options = {}) {
1392
+ const searchQuery = `repo:${this.owner}/${this.repo} is:issue ${query}`.trim();
1393
+ logger_default.debug(`[GitHubClient] Searching issues in ${this.owner}/${this.repo}: ${query}`);
1394
+ try {
1395
+ const items = await this.octokit.paginate(this.octokit.rest.search.issuesAndPullRequests, {
1396
+ q: searchQuery,
1397
+ per_page: DEFAULT_PER_PAGE
1398
+ });
1399
+ return items.filter(isIssueResponse).map((issue) => this.mapIssue(issue)).slice(0, options.maxResults ?? Number.POSITIVE_INFINITY);
1400
+ } catch (error) {
1401
+ this.logError("search issues", error);
1402
+ throw new GitHubClientError(`Failed to search GitHub issues: ${getErrorMessage(error)}`, getErrorStatus(error));
1403
+ }
1404
+ }
1405
+ async addLabels(issueNumber, labels) {
1406
+ if (labels.length === 0) {
1407
+ return;
1408
+ }
1409
+ logger_default.debug(`[GitHubClient] Adding labels to issue #${issueNumber} in ${this.owner}/${this.repo}`);
1410
+ try {
1411
+ await this.octokit.rest.issues.addLabels({
1412
+ owner: this.owner,
1413
+ repo: this.repo,
1414
+ issue_number: issueNumber,
1415
+ labels
1416
+ });
1417
+ } catch (error) {
1418
+ this.logError(`add labels to issue #${issueNumber}`, error);
1419
+ throw new GitHubClientError(
1420
+ `Failed to add labels to GitHub issue #${issueNumber}: ${getErrorMessage(error)}`,
1421
+ getErrorStatus(error)
1422
+ );
1423
+ }
1424
+ }
1425
+ async removeLabel(issueNumber, label) {
1426
+ logger_default.debug(`[GitHubClient] Removing label "${label}" from issue #${issueNumber} in ${this.owner}/${this.repo}`);
1427
+ try {
1428
+ await this.octokit.rest.issues.removeLabel({
1429
+ owner: this.owner,
1430
+ repo: this.repo,
1431
+ issue_number: issueNumber,
1432
+ name: label
1433
+ });
1434
+ } catch (error) {
1435
+ if (getErrorStatus(error) === 404) {
1436
+ return;
1437
+ }
1438
+ this.logError(`remove label from issue #${issueNumber}`, error);
1439
+ throw new GitHubClientError(
1440
+ `Failed to remove label from GitHub issue #${issueNumber}: ${getErrorMessage(error)}`,
1441
+ getErrorStatus(error)
1442
+ );
1443
+ }
1444
+ }
1445
+ async setLabels(issueNumber, labels) {
1446
+ logger_default.debug(`[GitHubClient] Setting labels on issue #${issueNumber} in ${this.owner}/${this.repo}`);
1447
+ try {
1448
+ await this.octokit.rest.issues.setLabels({
1449
+ owner: this.owner,
1450
+ repo: this.repo,
1451
+ issue_number: issueNumber,
1452
+ labels
1453
+ });
1454
+ } catch (error) {
1455
+ this.logError(`set labels on issue #${issueNumber}`, error);
1456
+ throw new GitHubClientError(
1457
+ `Failed to set labels on GitHub issue #${issueNumber}: ${getErrorMessage(error)}`,
1458
+ getErrorStatus(error)
1459
+ );
1460
+ }
1461
+ }
1462
+ async addComment(issueNumber, body) {
1463
+ logger_default.debug(`[GitHubClient] Adding comment to issue #${issueNumber} in ${this.owner}/${this.repo}`);
1464
+ try {
1465
+ const response = await this.octokit.rest.issues.createComment({
1466
+ owner: this.owner,
1467
+ repo: this.repo,
1468
+ issue_number: issueNumber,
1469
+ body
1470
+ });
1471
+ return this.mapComment(response.data);
1472
+ } catch (error) {
1473
+ this.logError(`add comment to issue #${issueNumber}`, error);
1474
+ throw new GitHubClientError(
1475
+ `Failed to add comment to GitHub issue #${issueNumber}: ${getErrorMessage(error)}`,
1476
+ getErrorStatus(error)
1477
+ );
1478
+ }
1479
+ }
1480
+ async listComments(issueNumber) {
1481
+ logger_default.debug(`[GitHubClient] Listing comments for issue #${issueNumber} in ${this.owner}/${this.repo}`);
1482
+ try {
1483
+ const comments = await this.octokit.paginate(this.octokit.rest.issues.listComments, {
1484
+ owner: this.owner,
1485
+ repo: this.repo,
1486
+ issue_number: issueNumber,
1487
+ per_page: DEFAULT_PER_PAGE
1488
+ });
1489
+ return comments.map((comment) => this.mapComment(comment));
1490
+ } catch (error) {
1491
+ this.logError(`list comments for issue #${issueNumber}`, error);
1492
+ throw new GitHubClientError(
1493
+ `Failed to list comments for GitHub issue #${issueNumber}: ${getErrorMessage(error)}`,
1494
+ getErrorStatus(error)
1495
+ );
1496
+ }
1497
+ }
1498
+ mapIssue(issue) {
1499
+ return {
1500
+ number: issue.number,
1501
+ title: issue.title,
1502
+ body: issue.body ?? "",
1503
+ state: issue.state,
1504
+ labels: issue.labels.map((label) => typeof label === "string" ? label : label.name ?? "").map((label) => label.trim()).filter((label) => label.length > 0),
1505
+ created_at: issue.created_at,
1506
+ updated_at: issue.updated_at,
1507
+ html_url: issue.html_url
1508
+ };
1509
+ }
1510
+ mapComment(comment) {
1511
+ return {
1512
+ id: comment.id,
1513
+ body: comment.body ?? "",
1514
+ created_at: comment.created_at,
1515
+ updated_at: comment.updated_at,
1516
+ html_url: comment.html_url
1517
+ };
1518
+ }
1519
+ logError(action, error) {
1520
+ logger_default.error(`[GitHubClient] Failed to ${action} in ${this.owner}/${this.repo}: ${getErrorMessage(error)}`);
1521
+ }
1522
+ };
1523
+ clientInstance = null;
1524
+ clientKey = "";
1525
+ }
1526
+ });
1527
+
1528
+ // src/L3-services/ideaService/ideaService.ts
1529
+ var ideaService_exports = {};
1530
+ __export(ideaService_exports, {
1531
+ createIdea: () => createIdea,
1532
+ findRelatedIdeas: () => findRelatedIdeas,
1533
+ getIdea: () => getIdea,
1534
+ getPublishHistory: () => getPublishHistory,
1535
+ getReadyIdeas: () => getReadyIdeas,
1536
+ linkVideoToIdea: () => linkVideoToIdea,
1537
+ listIdeas: () => listIdeas,
1538
+ markPublished: () => markPublished,
1539
+ markRecorded: () => markRecorded,
1540
+ recordPublish: () => recordPublish,
1541
+ searchIdeas: () => searchIdeas,
1542
+ updateIdea: () => updateIdea
1543
+ });
1544
+ function getErrorMessage2(error) {
1545
+ return error instanceof Error ? error.message : String(error);
1205
1546
  }
1206
- function isRecord(value) {
1207
- return typeof value === "object" && value !== null;
1547
+ function sanitizeMultilineValue(value) {
1548
+ return (value ?? "").trim();
1208
1549
  }
1209
- function isStringArray(value) {
1210
- return Array.isArray(value) && value.every((item) => typeof item === "string");
1550
+ function normalizeTag(tag) {
1551
+ return tag.trim().toLowerCase().replace(/\s+/g, "-");
1211
1552
  }
1212
- function isIdeaStatus(value) {
1213
- return typeof value === "string" && ideaStatuses.has(value);
1553
+ function normalizeTags(tags) {
1554
+ return Array.from(new Set(tags.map((tag) => normalizeTag(tag)).filter((tag) => tag.length > 0)));
1214
1555
  }
1215
1556
  function isPlatform(value) {
1216
- return typeof value === "string" && ideaPlatforms.has(value);
1557
+ return platformValues.has(value);
1217
1558
  }
1218
- function isPlatformArray(value) {
1219
- return Array.isArray(value) && value.every((item) => isPlatform(item));
1220
- }
1221
- function isValidIsoDateString(value) {
1222
- return typeof value === "string" && !Number.isNaN(new Date(value).getTime());
1559
+ function isIdeaStatus(value) {
1560
+ return ideaStatuses.has(value);
1223
1561
  }
1224
- function isIdeaPublishRecord(value) {
1225
- return isRecord(value) && typeof value.queueItemId === "string" && typeof value.publishedAt === "string" && typeof value.clipType === "string" && ideaClipTypes.has(value.clipType) && isPlatform(value.platform) && (value.publishedUrl === void 0 || typeof value.publishedUrl === "string");
1562
+ function uniquePlatforms(platforms) {
1563
+ return Array.from(new Set(platforms));
1226
1564
  }
1227
- function isIdea(value) {
1228
- return isRecord(value) && typeof value.id === "string" && typeof value.topic === "string" && typeof value.hook === "string" && typeof value.audience === "string" && typeof value.keyTakeaway === "string" && isStringArray(value.talkingPoints) && isPlatformArray(value.platforms) && isIdeaStatus(value.status) && isStringArray(value.tags) && typeof value.createdAt === "string" && typeof value.updatedAt === "string" && isValidIsoDateString(value.publishBy) && (value.sourceVideoSlug === void 0 || typeof value.sourceVideoSlug === "string") && (value.trendContext === void 0 || typeof value.trendContext === "string") && (value.publishedContent === void 0 || Array.isArray(value.publishedContent) && value.publishedContent.every((item) => isIdeaPublishRecord(item)));
1565
+ function classifyIdeaPriority(publishBy, createdAtIso) {
1566
+ const publishByTimestamp = new Date(publishBy).getTime();
1567
+ const createdAtTimestamp = new Date(createdAtIso).getTime();
1568
+ if (Number.isNaN(publishByTimestamp) || Number.isNaN(createdAtTimestamp)) {
1569
+ return "evergreen";
1570
+ }
1571
+ const diffDays = Math.ceil((publishByTimestamp - createdAtTimestamp) / (1e3 * 60 * 60 * 24));
1572
+ if (diffDays <= 7) {
1573
+ return "hot-trend";
1574
+ }
1575
+ if (diffDays <= 14) {
1576
+ return "timely";
1577
+ }
1578
+ return "evergreen";
1229
1579
  }
1230
- async function readIdeaBank(dir) {
1231
- const ideasDir = resolveIdeasDir(dir);
1232
- const ideaIds = await listIdeaIds(ideasDir);
1233
- const ideas = await Promise.all(
1234
- ideaIds.map(async (id) => {
1235
- try {
1236
- return await readIdea(id, ideasDir);
1237
- } catch (error) {
1238
- logger_default.warn(`Skipping invalid idea file ${id}${IDEA_FILE_EXTENSION}: ${getErrorMessage(error)}`);
1239
- return null;
1580
+ function extractLabelsFromIdea(idea, publishBy, createdAtIso) {
1581
+ const labels = [
1582
+ `${STATUS_LABEL_PREFIX}${idea.status}`,
1583
+ ...uniquePlatforms(idea.platforms).map((platform) => `${PLATFORM_LABEL_PREFIX}${platform}`),
1584
+ `${PRIORITY_LABEL_PREFIX}${classifyIdeaPriority(publishBy, createdAtIso)}`,
1585
+ ...normalizeTags(idea.tags)
1586
+ ];
1587
+ return Array.from(new Set(labels));
1588
+ }
1589
+ function parseLabelsToIdea(labels) {
1590
+ let status = "draft";
1591
+ const platforms = [];
1592
+ const tags = [];
1593
+ for (const label of labels) {
1594
+ const normalized = label.trim().toLowerCase();
1595
+ if (!normalized) {
1596
+ continue;
1597
+ }
1598
+ if (normalized.startsWith(STATUS_LABEL_PREFIX)) {
1599
+ const value = normalized.slice(STATUS_LABEL_PREFIX.length);
1600
+ if (isIdeaStatus(value)) {
1601
+ status = value;
1240
1602
  }
1241
- })
1242
- );
1243
- return ideas.filter((idea) => idea !== null);
1603
+ continue;
1604
+ }
1605
+ if (normalized.startsWith(PLATFORM_LABEL_PREFIX)) {
1606
+ const value = normalized.slice(PLATFORM_LABEL_PREFIX.length);
1607
+ if (isPlatform(value)) {
1608
+ platforms.push(value);
1609
+ }
1610
+ continue;
1611
+ }
1612
+ if (normalized.startsWith(PRIORITY_LABEL_PREFIX)) {
1613
+ continue;
1614
+ }
1615
+ tags.push(normalized);
1616
+ }
1617
+ return {
1618
+ status,
1619
+ platforms: uniquePlatforms(platforms),
1620
+ tags: Array.from(new Set(tags))
1621
+ };
1244
1622
  }
1245
- async function writeIdea(idea, dir) {
1246
- const ideasDir = resolveIdeasDir(dir);
1247
- const ideaPath = getIdeaFilePath(idea.id, ideasDir);
1248
- const now = (/* @__PURE__ */ new Date()).toISOString();
1249
- if (!isValidIsoDateString(idea.publishBy)) {
1250
- throw new Error(`Invalid publishBy date: ${idea.publishBy}`);
1623
+ function formatIdeaBody(input) {
1624
+ const sections = [
1625
+ `${MARKDOWN_SECTION_PREFIX}Hook`,
1626
+ sanitizeMultilineValue(input.hook),
1627
+ "",
1628
+ `${MARKDOWN_SECTION_PREFIX}Audience`,
1629
+ sanitizeMultilineValue(input.audience),
1630
+ "",
1631
+ `${MARKDOWN_SECTION_PREFIX}Key Takeaway`,
1632
+ sanitizeMultilineValue(input.keyTakeaway),
1633
+ "",
1634
+ `${MARKDOWN_SECTION_PREFIX}Talking Points`,
1635
+ ...input.talkingPoints.map((point) => `- ${sanitizeMultilineValue(point)}`),
1636
+ "",
1637
+ `${MARKDOWN_SECTION_PREFIX}Publish By`,
1638
+ sanitizeMultilineValue(input.publishBy)
1639
+ ];
1640
+ const trendContext = sanitizeMultilineValue(input.trendContext);
1641
+ if (trendContext) {
1642
+ sections.push("", `${MARKDOWN_SECTION_PREFIX}Trend Context`, trendContext);
1643
+ }
1644
+ return sections.join("\n").trim();
1645
+ }
1646
+ function parseIdeaBody(body, fallbackPublishBy) {
1647
+ const normalizedBody = body.replace(/\r\n/g, "\n");
1648
+ const sections = /* @__PURE__ */ new Map();
1649
+ let currentSection = null;
1650
+ for (const line of normalizedBody.split("\n")) {
1651
+ if (line.startsWith(MARKDOWN_SECTION_PREFIX)) {
1652
+ currentSection = line.slice(MARKDOWN_SECTION_PREFIX.length).trim();
1653
+ sections.set(currentSection, []);
1654
+ continue;
1655
+ }
1656
+ if (currentSection) {
1657
+ sections.get(currentSection)?.push(line);
1658
+ }
1251
1659
  }
1252
- idea.updatedAt = now;
1253
- await ensureDirectory(ideasDir);
1254
- await writeJsonFile(ideaPath, idea);
1660
+ const getSection = (heading) => (sections.get(heading) ?? []).join("\n").trim();
1661
+ const talkingPointsSection = sections.get("Talking Points") ?? [];
1662
+ const talkingPoints = talkingPointsSection.map((line) => line.trim()).filter((line) => line.startsWith("- ") || line.startsWith("* ")).map((line) => line.slice(2).trim()).filter((line) => line.length > 0);
1663
+ return {
1664
+ hook: getSection("Hook"),
1665
+ audience: getSection("Audience"),
1666
+ keyTakeaway: getSection("Key Takeaway"),
1667
+ talkingPoints,
1668
+ publishBy: getSection("Publish By") || fallbackPublishBy,
1669
+ trendContext: getSection("Trend Context") || void 0
1670
+ };
1671
+ }
1672
+ function formatIdeaComment(data) {
1673
+ return [
1674
+ COMMENT_MARKER,
1675
+ "```json",
1676
+ JSON.stringify(data, null, 2),
1677
+ "```"
1678
+ ].join("\n");
1679
+ }
1680
+ function formatPublishRecordComment(record) {
1681
+ return [
1682
+ "Published content recorded for this idea.",
1683
+ "",
1684
+ `- Clip type: ${record.clipType}`,
1685
+ `- Platform: ${record.platform}`,
1686
+ `- Queue item: ${record.queueItemId}`,
1687
+ `- Published at: ${record.publishedAt}`,
1688
+ `- Late post ID: ${record.latePostId}`,
1689
+ `- Late URL: ${record.lateUrl}`,
1690
+ "",
1691
+ formatIdeaComment({ type: "publish-record", record })
1692
+ ].join("\n");
1693
+ }
1694
+ function formatVideoLinkComment(videoSlug, linkedAt) {
1695
+ return [
1696
+ "Linked a source video to this idea.",
1697
+ "",
1698
+ `- Video slug: ${videoSlug}`,
1699
+ `- Linked at: ${linkedAt}`,
1700
+ "",
1701
+ formatIdeaComment({ type: "video-link", videoSlug, linkedAt })
1702
+ ].join("\n");
1255
1703
  }
1256
- async function readIdea(id, dir) {
1257
- const ideaPath = getIdeaFilePath(id, dir);
1258
- if (!await fileExists(ideaPath)) {
1704
+ function parseIdeaComment(commentBody) {
1705
+ const markerIndex = commentBody.indexOf(COMMENT_MARKER);
1706
+ if (markerIndex === -1) {
1259
1707
  return null;
1260
1708
  }
1261
- const idea = await readJsonFile(ideaPath);
1262
- if (!isIdea(idea)) {
1263
- throw new Error(`File does not contain a valid idea: ${ideaPath}`);
1709
+ const commentPayload = commentBody.slice(markerIndex + COMMENT_MARKER.length);
1710
+ const fencedJsonMatch = commentPayload.match(/```json\s*([\s\S]*?)\s*```/);
1711
+ const jsonText = fencedJsonMatch?.[1]?.trim() ?? commentPayload.trim();
1712
+ if (!jsonText) {
1713
+ return null;
1264
1714
  }
1265
- return idea;
1715
+ try {
1716
+ const parsed = JSON.parse(jsonText);
1717
+ if (parsed.type === "video-link" && typeof parsed.videoSlug === "string" && typeof parsed.linkedAt === "string") {
1718
+ return {
1719
+ type: "video-link",
1720
+ videoSlug: parsed.videoSlug,
1721
+ linkedAt: parsed.linkedAt
1722
+ };
1723
+ }
1724
+ if (parsed.type === "publish-record" && parsed.record) {
1725
+ const record = parsed.record;
1726
+ if (typeof record.clipType === "string" && typeof record.platform === "string" && isPlatform(record.platform) && typeof record.queueItemId === "string" && typeof record.publishedAt === "string" && typeof record.latePostId === "string" && typeof record.lateUrl === "string") {
1727
+ return {
1728
+ type: "publish-record",
1729
+ record: {
1730
+ clipType: record.clipType,
1731
+ platform: record.platform,
1732
+ queueItemId: record.queueItemId,
1733
+ publishedAt: record.publishedAt,
1734
+ latePostId: record.latePostId,
1735
+ lateUrl: record.lateUrl
1736
+ }
1737
+ };
1738
+ }
1739
+ }
1740
+ } catch {
1741
+ return null;
1742
+ }
1743
+ return null;
1266
1744
  }
1267
- async function listIdeaIds(dir) {
1268
- const ideasDir = resolveIdeasDir(dir);
1269
- if (!await fileExists(ideasDir)) {
1745
+ function buildLabelFilters(filters) {
1746
+ if (!filters) {
1270
1747
  return [];
1271
1748
  }
1272
- const entries = await listDirectory(ideasDir);
1273
- return entries.filter((entry) => entry.toLowerCase().endsWith(IDEA_FILE_EXTENSION)).map((entry) => entry.slice(0, -IDEA_FILE_EXTENSION.length));
1749
+ const labels = [];
1750
+ if (filters.status) {
1751
+ labels.push(`${STATUS_LABEL_PREFIX}${filters.status}`);
1752
+ }
1753
+ if (filters.platform) {
1754
+ labels.push(`${PLATFORM_LABEL_PREFIX}${filters.platform}`);
1755
+ }
1756
+ if (filters.tag) {
1757
+ labels.push(normalizeTag(filters.tag));
1758
+ }
1759
+ if (filters.priority) {
1760
+ labels.push(`${PRIORITY_LABEL_PREFIX}${filters.priority}`);
1761
+ }
1762
+ return labels;
1763
+ }
1764
+ function buildLabelsFromIssue(issue, overrides = {}) {
1765
+ const parsedLabels = parseLabelsToIdea(issue.labels);
1766
+ const parsedBody = parseIdeaBody(issue.body, issue.created_at.slice(0, 10));
1767
+ return extractLabelsFromIdea(
1768
+ {
1769
+ status: overrides.status ?? parsedLabels.status,
1770
+ platforms: overrides.platforms ?? parsedLabels.platforms,
1771
+ tags: overrides.tags ?? parsedLabels.tags
1772
+ },
1773
+ parsedBody.publishBy,
1774
+ issue.created_at
1775
+ );
1776
+ }
1777
+ function isNotFoundError(error) {
1778
+ return error instanceof GitHubClientError && error.status === 404;
1779
+ }
1780
+ function mapIssueToIdea(issue, comments) {
1781
+ const config2 = getConfig();
1782
+ const parsedLabels = parseLabelsToIdea(issue.labels);
1783
+ const parsedBody = parseIdeaBody(issue.body, issue.created_at.slice(0, 10));
1784
+ const publishRecords = [];
1785
+ let sourceVideoSlug;
1786
+ for (const comment of comments) {
1787
+ const parsedComment = parseIdeaComment(comment.body);
1788
+ if (!parsedComment) {
1789
+ continue;
1790
+ }
1791
+ if (parsedComment.type === "publish-record") {
1792
+ publishRecords.push(parsedComment.record);
1793
+ continue;
1794
+ }
1795
+ sourceVideoSlug = parsedComment.videoSlug;
1796
+ }
1797
+ return {
1798
+ issueNumber: issue.number,
1799
+ issueUrl: issue.html_url,
1800
+ repoFullName: config2.IDEAS_REPO,
1801
+ id: `idea-${issue.number}`,
1802
+ topic: issue.title,
1803
+ ...parsedBody,
1804
+ ...parsedLabels,
1805
+ createdAt: issue.created_at,
1806
+ updatedAt: issue.updated_at,
1807
+ sourceVideoSlug,
1808
+ publishedContent: publishRecords.length > 0 ? publishRecords : void 0
1809
+ };
1810
+ }
1811
+ async function createIdea(input) {
1812
+ const client = getGitHubClient();
1813
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
1814
+ try {
1815
+ const issue = await client.createIssue({
1816
+ title: input.topic,
1817
+ body: formatIdeaBody(input),
1818
+ labels: extractLabelsFromIdea(
1819
+ {
1820
+ status: "draft",
1821
+ platforms: input.platforms,
1822
+ tags: input.tags
1823
+ },
1824
+ input.publishBy,
1825
+ createdAt
1826
+ )
1827
+ });
1828
+ logger_default.info(`[IdeaService] Created idea #${issue.number}: ${input.topic}`);
1829
+ return mapIssueToIdea(issue, []);
1830
+ } catch (error) {
1831
+ const message = getErrorMessage2(error);
1832
+ logger_default.error(`[IdeaService] Failed to create idea "${input.topic}": ${message}`);
1833
+ throw new Error(`Failed to create idea "${input.topic}": ${message}`);
1834
+ }
1835
+ }
1836
+ async function updateIdea(issueNumber, updates) {
1837
+ const client = getGitHubClient();
1838
+ try {
1839
+ const currentIdea = await getIdea(issueNumber);
1840
+ if (!currentIdea) {
1841
+ throw new Error(`Idea #${issueNumber} was not found`);
1842
+ }
1843
+ const nextInput = {
1844
+ topic: updates.topic ?? currentIdea.topic,
1845
+ hook: updates.hook ?? currentIdea.hook,
1846
+ audience: updates.audience ?? currentIdea.audience,
1847
+ keyTakeaway: updates.keyTakeaway ?? currentIdea.keyTakeaway,
1848
+ talkingPoints: updates.talkingPoints ?? currentIdea.talkingPoints,
1849
+ platforms: updates.platforms ?? currentIdea.platforms,
1850
+ tags: updates.tags ?? currentIdea.tags,
1851
+ publishBy: updates.publishBy ?? currentIdea.publishBy,
1852
+ trendContext: updates.trendContext ?? currentIdea.trendContext
1853
+ };
1854
+ const shouldUpdateBody = updates.hook !== void 0 || updates.audience !== void 0 || updates.keyTakeaway !== void 0 || updates.talkingPoints !== void 0 || updates.publishBy !== void 0 || updates.trendContext !== void 0;
1855
+ const shouldUpdateLabels = updates.status !== void 0 || updates.platforms !== void 0 || updates.tags !== void 0 || updates.publishBy !== void 0;
1856
+ const issue = await client.updateIssue(issueNumber, {
1857
+ title: updates.topic,
1858
+ body: shouldUpdateBody ? formatIdeaBody(nextInput) : void 0,
1859
+ labels: shouldUpdateLabels ? extractLabelsFromIdea(
1860
+ {
1861
+ status: updates.status ?? currentIdea.status,
1862
+ platforms: nextInput.platforms,
1863
+ tags: nextInput.tags
1864
+ },
1865
+ nextInput.publishBy,
1866
+ currentIdea.createdAt
1867
+ ) : void 0
1868
+ });
1869
+ const comments = await client.listComments(issueNumber);
1870
+ return mapIssueToIdea(issue, comments);
1871
+ } catch (error) {
1872
+ const message = getErrorMessage2(error);
1873
+ logger_default.error(`[IdeaService] Failed to update idea #${issueNumber}: ${message}`);
1874
+ throw new Error(`Failed to update idea #${issueNumber}: ${message}`);
1875
+ }
1876
+ }
1877
+ async function getIdea(issueNumber) {
1878
+ const client = getGitHubClient();
1879
+ try {
1880
+ const [issue, comments] = await Promise.all([
1881
+ client.getIssue(issueNumber),
1882
+ client.listComments(issueNumber)
1883
+ ]);
1884
+ return mapIssueToIdea(issue, comments);
1885
+ } catch (error) {
1886
+ if (isNotFoundError(error)) {
1887
+ return null;
1888
+ }
1889
+ const message = getErrorMessage2(error);
1890
+ logger_default.error(`[IdeaService] Failed to get idea #${issueNumber}: ${message}`);
1891
+ throw new Error(`Failed to get idea #${issueNumber}: ${message}`);
1892
+ }
1893
+ }
1894
+ async function listIdeas(filters) {
1895
+ const client = getGitHubClient();
1896
+ try {
1897
+ const issues = await client.listIssues({
1898
+ labels: buildLabelFilters(filters),
1899
+ maxResults: filters?.limit
1900
+ });
1901
+ const ideas = await Promise.all(
1902
+ issues.map(async (issue) => {
1903
+ const comments = await client.listComments(issue.number);
1904
+ return mapIssueToIdea(issue, comments);
1905
+ })
1906
+ );
1907
+ return filters?.limit ? ideas.slice(0, filters.limit) : ideas;
1908
+ } catch (error) {
1909
+ const message = getErrorMessage2(error);
1910
+ logger_default.error(`[IdeaService] Failed to list ideas: ${message}`);
1911
+ throw new Error(`Failed to list ideas: ${message}`);
1912
+ }
1913
+ }
1914
+ async function searchIdeas(query) {
1915
+ const client = getGitHubClient();
1916
+ try {
1917
+ const issues = await client.searchIssues(query);
1918
+ return await Promise.all(
1919
+ issues.map(async (issue) => {
1920
+ const comments = await client.listComments(issue.number);
1921
+ return mapIssueToIdea(issue, comments);
1922
+ })
1923
+ );
1924
+ } catch (error) {
1925
+ const message = getErrorMessage2(error);
1926
+ logger_default.error(`[IdeaService] Failed to search ideas: ${message}`);
1927
+ throw new Error(`Failed to search ideas: ${message}`);
1928
+ }
1929
+ }
1930
+ async function findRelatedIdeas(idea) {
1931
+ const client = getGitHubClient();
1932
+ try {
1933
+ const relatedIssues = /* @__PURE__ */ new Map();
1934
+ for (const tag of normalizeTags(idea.tags)) {
1935
+ const matches = await client.listIssues({ labels: [tag], maxResults: 5 });
1936
+ for (const match of matches) {
1937
+ if (match.number !== idea.issueNumber) {
1938
+ relatedIssues.set(match.number, match);
1939
+ }
1940
+ }
1941
+ }
1942
+ const sortedIssues = Array.from(relatedIssues.values()).sort((left, right) => right.updated_at.localeCompare(left.updated_at)).slice(0, 5);
1943
+ return await Promise.all(
1944
+ sortedIssues.map(async (issue) => {
1945
+ const comments = await client.listComments(issue.number);
1946
+ return mapIssueToIdea(issue, comments);
1947
+ })
1948
+ );
1949
+ } catch (error) {
1950
+ const message = getErrorMessage2(error);
1951
+ logger_default.error(`[IdeaService] Failed to find related ideas for #${idea.issueNumber}: ${message}`);
1952
+ throw new Error(`Failed to find related ideas for #${idea.issueNumber}: ${message}`);
1953
+ }
1954
+ }
1955
+ async function linkVideoToIdea(issueNumber, videoSlug) {
1956
+ const client = getGitHubClient();
1957
+ try {
1958
+ const [issue] = await Promise.all([
1959
+ client.getIssue(issueNumber),
1960
+ client.addComment(issueNumber, formatVideoLinkComment(videoSlug, (/* @__PURE__ */ new Date()).toISOString()))
1961
+ ]);
1962
+ await client.updateIssue(issueNumber, {
1963
+ labels: buildLabelsFromIssue(issue, { status: "recorded" })
1964
+ });
1965
+ } catch (error) {
1966
+ const message = getErrorMessage2(error);
1967
+ logger_default.error(`[IdeaService] Failed to link video ${videoSlug} to idea #${issueNumber}: ${message}`);
1968
+ throw new Error(`Failed to link video ${videoSlug} to idea #${issueNumber}: ${message}`);
1969
+ }
1970
+ }
1971
+ async function recordPublish(issueNumber, record) {
1972
+ const client = getGitHubClient();
1973
+ try {
1974
+ const [issue, comments] = await Promise.all([
1975
+ client.getIssue(issueNumber),
1976
+ client.listComments(issueNumber)
1977
+ ]);
1978
+ const hasDuplicate = comments.some((comment) => {
1979
+ const parsedComment = parseIdeaComment(comment.body);
1980
+ return parsedComment?.type === "publish-record" && parsedComment.record.queueItemId === record.queueItemId;
1981
+ });
1982
+ if (!hasDuplicate) {
1983
+ await client.addComment(issueNumber, formatPublishRecordComment(record));
1984
+ }
1985
+ if (!issue.labels.includes(`${STATUS_LABEL_PREFIX}published`)) {
1986
+ await client.updateIssue(issueNumber, {
1987
+ labels: buildLabelsFromIssue(issue, { status: "published" })
1988
+ });
1989
+ }
1990
+ } catch (error) {
1991
+ const message = getErrorMessage2(error);
1992
+ logger_default.error(`[IdeaService] Failed to record publish for idea #${issueNumber}: ${message}`);
1993
+ throw new Error(`Failed to record publish for idea #${issueNumber}: ${message}`);
1994
+ }
1995
+ }
1996
+ async function getPublishHistory(issueNumber) {
1997
+ const client = getGitHubClient();
1998
+ try {
1999
+ const comments = await client.listComments(issueNumber);
2000
+ return comments.flatMap((comment) => {
2001
+ const parsedComment = parseIdeaComment(comment.body);
2002
+ return parsedComment?.type === "publish-record" ? [parsedComment.record] : [];
2003
+ });
2004
+ } catch (error) {
2005
+ const message = getErrorMessage2(error);
2006
+ logger_default.error(`[IdeaService] Failed to get publish history for idea #${issueNumber}: ${message}`);
2007
+ throw new Error(`Failed to get publish history for idea #${issueNumber}: ${message}`);
2008
+ }
1274
2009
  }
1275
- var DEFAULT_IDEAS_DIR, IDEA_FILE_EXTENSION, ideaStatuses, ideaClipTypes, ideaPlatforms;
1276
- var init_ideaStore = __esm({
1277
- "src/L1-infra/ideaStore/ideaStore.ts"() {
2010
+ async function getReadyIdeas() {
2011
+ return listIdeas({ status: "ready" });
2012
+ }
2013
+ async function markRecorded(issueNumber, videoSlug) {
2014
+ await linkVideoToIdea(issueNumber, videoSlug);
2015
+ }
2016
+ async function markPublished(issueNumber, record) {
2017
+ await recordPublish(issueNumber, record);
2018
+ }
2019
+ var STATUS_LABEL_PREFIX, PLATFORM_LABEL_PREFIX, PRIORITY_LABEL_PREFIX, COMMENT_MARKER, MARKDOWN_SECTION_PREFIX, platformValues, ideaStatuses;
2020
+ var init_ideaService = __esm({
2021
+ "src/L3-services/ideaService/ideaService.ts"() {
1278
2022
  "use strict";
1279
- init_fileSystem();
2023
+ init_types();
2024
+ init_githubClient();
2025
+ init_environment();
1280
2026
  init_configLogger();
1281
- init_paths();
1282
- DEFAULT_IDEAS_DIR = join(resolve("."), "ideas");
1283
- IDEA_FILE_EXTENSION = ".json";
2027
+ STATUS_LABEL_PREFIX = "status:";
2028
+ PLATFORM_LABEL_PREFIX = "platform:";
2029
+ PRIORITY_LABEL_PREFIX = "priority:";
2030
+ COMMENT_MARKER = "<!-- vidpipe:idea-comment -->";
2031
+ MARKDOWN_SECTION_PREFIX = "## ";
2032
+ platformValues = new Set(Object.values(Platform));
1284
2033
  ideaStatuses = /* @__PURE__ */ new Set(["draft", "ready", "recorded", "published"]);
1285
- ideaClipTypes = /* @__PURE__ */ new Set(["video", "short", "medium-clip"]);
1286
- ideaPlatforms = /* @__PURE__ */ new Set(["tiktok", "youtube", "instagram", "linkedin", "x"]);
1287
2034
  }
1288
2035
  });
1289
2036
 
1290
2037
  // src/L3-services/ideation/ideaService.ts
1291
- var ideaService_exports = {};
1292
- __export(ideaService_exports, {
2038
+ var ideaService_exports2 = {};
2039
+ __export(ideaService_exports2, {
1293
2040
  getIdeasByIds: () => getIdeasByIds,
1294
- getReadyIdeas: () => getReadyIdeas,
1295
- markPublished: () => markPublished,
1296
- markRecorded: () => markRecorded,
2041
+ getReadyIdeas: () => getReadyIdeas2,
2042
+ markPublished: () => markPublished2,
2043
+ markRecorded: () => markRecorded2,
1297
2044
  matchIdeasToTranscript: () => matchIdeasToTranscript
1298
2045
  });
1299
- async function getIdeasByIds(ids, dir) {
1300
- return Promise.all(
1301
- ids.map(async (id) => {
1302
- const idea = await readIdea(id, dir);
1303
- if (!idea) {
1304
- throw new Error(`Idea not found: ${id}`);
1305
- }
2046
+ function normalizeIdeaIdentifier(id) {
2047
+ return id.trim();
2048
+ }
2049
+ function buildIdeaLookup(ideas) {
2050
+ const lookup = /* @__PURE__ */ new Map();
2051
+ for (const idea of ideas) {
2052
+ lookup.set(idea.id, idea);
2053
+ lookup.set(String(idea.issueNumber), idea);
2054
+ }
2055
+ return lookup;
2056
+ }
2057
+ async function resolveIdeaByIdentifier(id, ideas) {
2058
+ const normalizedId = normalizeIdeaIdentifier(id);
2059
+ if (!normalizedId) {
2060
+ return null;
2061
+ }
2062
+ const issueNumber = Number.parseInt(normalizedId, 10);
2063
+ if (Number.isInteger(issueNumber)) {
2064
+ const idea = await getIdea(issueNumber);
2065
+ if (idea) {
1306
2066
  return idea;
1307
- })
1308
- );
2067
+ }
2068
+ }
2069
+ const availableIdeas = ideas ?? await listIdeas();
2070
+ return buildIdeaLookup(availableIdeas).get(normalizedId) ?? null;
2071
+ }
2072
+ async function getIdeasByIds(ids, _dir) {
2073
+ const ideas = await listIdeas();
2074
+ const lookup = buildIdeaLookup(ideas);
2075
+ return ids.map((id) => {
2076
+ const normalizedId = normalizeIdeaIdentifier(id);
2077
+ const idea = lookup.get(normalizedId);
2078
+ if (!idea) {
2079
+ throw new Error(`Idea not found: ${id}`);
2080
+ }
2081
+ return idea;
2082
+ });
1309
2083
  }
1310
- async function getReadyIdeas(dir) {
1311
- const ideas = await readIdeaBank(dir);
1312
- return ideas.filter((idea) => idea.status === "ready");
2084
+ async function getReadyIdeas2(_dir) {
2085
+ return getReadyIdeas();
1313
2086
  }
1314
- async function markRecorded(id, videoSlug, dir) {
1315
- const idea = await readIdea(id, dir);
2087
+ async function markRecorded2(id, videoSlug, _dir) {
2088
+ const idea = await resolveIdeaByIdentifier(id);
1316
2089
  if (!idea) {
1317
2090
  throw new Error(`Idea not found: ${id}`);
1318
2091
  }
1319
- idea.status = "recorded";
1320
- idea.sourceVideoSlug = videoSlug;
1321
- await writeIdea(idea, dir);
2092
+ await markRecorded(idea.issueNumber, videoSlug);
1322
2093
  }
1323
- async function markPublished(id, record, dir) {
1324
- const idea = await readIdea(id, dir);
2094
+ async function markPublished2(id, record, _dir) {
2095
+ const idea = await resolveIdeaByIdentifier(id);
1325
2096
  if (!idea) {
1326
2097
  throw new Error(`Idea not found: ${id}`);
1327
2098
  }
1328
- idea.publishedContent = [...idea.publishedContent ?? [], record];
1329
- idea.status = "published";
1330
- await writeIdea(idea, dir);
2099
+ await markPublished(idea.issueNumber, record);
1331
2100
  }
1332
- async function matchIdeasToTranscript(transcript, ideas, dir) {
2101
+ async function matchIdeasToTranscript(transcript, ideas, _dir) {
1333
2102
  try {
1334
- const readyIdeas = (ideas ?? await readIdeaBank(dir)).filter((idea) => idea.status === "ready");
2103
+ const readyIdeas = (ideas ?? await getReadyIdeas()).filter((idea) => idea.status === "ready");
1335
2104
  if (readyIdeas.length === 0) {
1336
2105
  return [];
1337
2106
  }
@@ -1349,9 +2118,7 @@ async function matchIdeasToTranscript(transcript, ideas, dir) {
1349
2118
  hook: idea.hook,
1350
2119
  keyTakeaway: idea.keyTakeaway
1351
2120
  }));
1352
- const knownIdeaIds = new Set(
1353
- ideas ? readyIdeaIds : await listIdeaIds(dir)
1354
- );
2121
+ const knownIdeaIds = readyIdeaIds;
1355
2122
  const session = await provider.createSession({
1356
2123
  systemPrompt: MATCH_IDEAS_SYSTEM_PROMPT,
1357
2124
  tools: [],
@@ -1370,7 +2137,7 @@ async function matchIdeasToTranscript(transcript, ideas, dir) {
1370
2137
  return matchedIdea ? [matchedIdea] : [];
1371
2138
  });
1372
2139
  }
1373
- return await getIdeasByIds(matchedIds, dir);
2140
+ return await getIdeasByIds(matchedIds);
1374
2141
  } finally {
1375
2142
  await session.close().catch((error) => {
1376
2143
  const message = error instanceof Error ? error.message : String(error);
@@ -1403,12 +2170,12 @@ function parseMatchedIdeaIds(rawContent, knownIdeaIds) {
1403
2170
  return Array.from(new Set(matchedIds.filter((id) => knownIdeaIds.has(id))));
1404
2171
  }
1405
2172
  var IDEA_MATCH_AGENT_NAME, IDEA_MATCH_LIMIT, TRANSCRIPT_SUMMARY_LIMIT, MATCH_IDEAS_SYSTEM_PROMPT;
1406
- var init_ideaService = __esm({
2173
+ var init_ideaService2 = __esm({
1407
2174
  "src/L3-services/ideation/ideaService.ts"() {
1408
2175
  "use strict";
1409
2176
  init_modelConfig();
1410
- init_ideaStore();
1411
2177
  init_configLogger();
2178
+ init_ideaService();
1412
2179
  init_providerFactory();
1413
2180
  IDEA_MATCH_AGENT_NAME = "IdeaService";
1414
2181
  IDEA_MATCH_LIMIT = 3;
@@ -1440,50 +2207,7 @@ init_environment();
1440
2207
  init_paths();
1441
2208
  init_fileSystem();
1442
2209
  init_configLogger();
1443
-
1444
- // src/L0-pure/types/index.ts
1445
- var Platform = /* @__PURE__ */ ((Platform2) => {
1446
- Platform2["TikTok"] = "tiktok";
1447
- Platform2["YouTube"] = "youtube";
1448
- Platform2["Instagram"] = "instagram";
1449
- Platform2["LinkedIn"] = "linkedin";
1450
- Platform2["X"] = "x";
1451
- return Platform2;
1452
- })(Platform || {});
1453
- var PLATFORM_CHAR_LIMITS = {
1454
- tiktok: 2200,
1455
- youtube: 5e3,
1456
- instagram: 2200,
1457
- linkedin: 3e3,
1458
- twitter: 280
1459
- };
1460
- function toLatePlatform(platform) {
1461
- return platform === "x" /* X */ ? "twitter" : platform;
1462
- }
1463
- function fromLatePlatform(latePlatform) {
1464
- const normalized = normalizePlatformString(latePlatform);
1465
- if (normalized === "twitter") {
1466
- return "x" /* X */;
1467
- }
1468
- const platformValues = Object.values(Platform);
1469
- if (platformValues.includes(normalized)) {
1470
- return normalized;
1471
- }
1472
- throw new Error(`Unsupported platform from Late API: ${latePlatform}`);
1473
- }
1474
- function normalizePlatformString(raw) {
1475
- const lower = raw.toLowerCase().trim();
1476
- if (lower === "x" || lower === "x (twitter)" || lower === "x/twitter") {
1477
- return "twitter";
1478
- }
1479
- return lower;
1480
- }
1481
- var SUPPORTED_VIDEO_EXTENSIONS = [".mp4", ".webm"];
1482
- function isSupportedVideoExtension(ext) {
1483
- return SUPPORTED_VIDEO_EXTENSIONS.includes(ext.toLowerCase());
1484
- }
1485
-
1486
- // src/L7-app/fileWatcher.ts
2210
+ init_types();
1487
2211
  var FileWatcher = class _FileWatcher extends EventEmitter {
1488
2212
  watchFolder;
1489
2213
  watcher = null;
@@ -4449,6 +5173,7 @@ var SocialPostAsset = class extends TextAsset {
4449
5173
  // src/L5-assets/ShortVideoAsset.ts
4450
5174
  init_paths();
4451
5175
  init_fileSystem();
5176
+ init_types();
4452
5177
  var ShortVideoAsset = class extends VideoAsset {
4453
5178
  /** Reference to the source video this short was extracted from */
4454
5179
  parent;
@@ -4581,6 +5306,7 @@ var ShortVideoAsset = class extends VideoAsset {
4581
5306
  // src/L5-assets/MediumClipAsset.ts
4582
5307
  init_paths();
4583
5308
  init_fileSystem();
5309
+ init_types();
4584
5310
  var MediumClipAsset = class extends VideoAsset {
4585
5311
  /** Parent video this clip was extracted from */
4586
5312
  parent;
@@ -6780,6 +7506,7 @@ init_fileSystem();
6780
7506
  init_paths();
6781
7507
  init_configLogger();
6782
7508
  init_environment();
7509
+ init_types();
6783
7510
  var SYSTEM_PROMPT5 = `You are a viral social-media content strategist.
6784
7511
  Given a video transcript and summary you MUST generate one post for each of the 5 platforms listed below.
6785
7512
  Each post must match the platform's tone, format, and constraints exactly.
@@ -7291,8 +8018,11 @@ async function commitAndPush(videoSlug, message) {
7291
8018
  init_fileSystem();
7292
8019
  init_paths();
7293
8020
  init_configLogger();
8021
+ init_types();
8022
+ init_types();
7294
8023
 
7295
8024
  // src/L3-services/socialPosting/platformContentStrategy.ts
8025
+ init_types();
7296
8026
  var CONTENT_MATRIX = {
7297
8027
  ["youtube" /* YouTube */]: {
7298
8028
  video: { captions: true, variantKey: null },
@@ -7325,6 +8055,7 @@ function platformAcceptsMedia(platform, clipType) {
7325
8055
  }
7326
8056
 
7327
8057
  // src/L3-services/postStore/postStore.ts
8058
+ init_types();
7328
8059
  init_environment();
7329
8060
  init_configLogger();
7330
8061
  init_fileSystem();
@@ -7533,14 +8264,40 @@ async function approveItem(id, publishData) {
7533
8264
  item.metadata.reviewedAt = now;
7534
8265
  if (item.metadata.ideaIds && item.metadata.ideaIds.length > 0) {
7535
8266
  try {
7536
- const { markPublished: markPublished2 } = await Promise.resolve().then(() => (init_ideaService(), ideaService_exports));
7537
- for (const ideaId of item.metadata.ideaIds) {
7538
- await markPublished2(ideaId, {
8267
+ const { getIdea: getIdea2, listIdeas: listIdeas2, markPublished: markPublished3 } = await Promise.resolve().then(() => (init_ideaService(), ideaService_exports));
8268
+ let cachedIdeas;
8269
+ for (const rawIdeaId of item.metadata.ideaIds) {
8270
+ const normalizedIdeaId = String(rawIdeaId).trim();
8271
+ if (!normalizedIdeaId) {
8272
+ continue;
8273
+ }
8274
+ const parsedIssueNumber = Number.parseInt(normalizedIdeaId, 10);
8275
+ let issueNumber;
8276
+ if (Number.isInteger(parsedIssueNumber)) {
8277
+ issueNumber = parsedIssueNumber;
8278
+ } else {
8279
+ if (!cachedIdeas) {
8280
+ const ideas = await listIdeas2();
8281
+ cachedIdeas = new Map(ideas.flatMap((idea2) => [[idea2.id, idea2.issueNumber], [String(idea2.issueNumber), idea2.issueNumber]]));
8282
+ }
8283
+ issueNumber = cachedIdeas.get(normalizedIdeaId);
8284
+ }
8285
+ if (!issueNumber) {
8286
+ logger_default.warn(`Skipping publish record for unknown idea identifier: ${normalizedIdeaId}`);
8287
+ continue;
8288
+ }
8289
+ const idea = await getIdea2(issueNumber);
8290
+ if (!idea) {
8291
+ logger_default.warn(`Skipping publish record for missing idea #${issueNumber}`);
8292
+ continue;
8293
+ }
8294
+ await markPublished3(issueNumber, {
7539
8295
  clipType: item.metadata.clipType,
7540
8296
  platform: fromLatePlatform(item.metadata.platform),
7541
8297
  queueItemId: id,
7542
8298
  publishedAt: now,
7543
- publishedUrl: item.metadata.publishedUrl ?? void 0
8299
+ latePostId: item.metadata.latePostId ?? "",
8300
+ lateUrl: item.metadata.publishedUrl || (item.metadata.latePostId ? `https://app.late.co/dashboard/post/${item.metadata.latePostId}` : "")
7544
8301
  });
7545
8302
  }
7546
8303
  } catch (err) {
@@ -8127,6 +8884,7 @@ async function enhanceVideo(videoPath, transcript, video) {
8127
8884
  // src/L5-assets/MainVideoAsset.ts
8128
8885
  init_environment();
8129
8886
  init_configLogger();
8887
+ init_types();
8130
8888
  var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
8131
8889
  sourcePath;
8132
8890
  videoDir;
@@ -9013,7 +9771,7 @@ var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
9013
9771
  */
9014
9772
  async buildPublishQueueData(shorts, mediumClips, socialPosts, captionedVideoPath) {
9015
9773
  const video = await this.toVideoFile();
9016
- const ideaIds = this._ideas.length > 0 ? this._ideas.map((idea) => idea.id) : void 0;
9774
+ const ideaIds = this._ideas.length > 0 ? this._ideas.map((idea) => String(idea.issueNumber)) : void 0;
9017
9775
  return buildPublishQueue2(video, shorts, mediumClips, socialPosts, captionedVideoPath, ideaIds);
9018
9776
  }
9019
9777
  /**
@@ -10733,17 +11491,26 @@ var ScheduleAgent = class extends BaseAgent {
10733
11491
  init_fileSystem();
10734
11492
  init_environment();
10735
11493
  init_modelConfig();
10736
- init_ideaStore();
10737
11494
  init_configLogger();
11495
+ init_ideaService();
10738
11496
  init_providerFactory();
11497
+ init_types();
10739
11498
  var BASE_SYSTEM_PROMPT = `You are a content strategist for a tech content creator. Your role is to research trending topics, analyze what's working, and generate compelling video ideas grounded in real-world data.
10740
11499
 
10741
11500
  ## CRITICAL: Research Before Creating
10742
11501
  You MUST research before creating ideas. Do NOT skip the research phase. Ideas generated without research will be generic and stale. The value you provide is grounding ideas in what's ACTUALLY trending right now.
10743
11502
 
11503
+ ## GitHub Issue Workflow
11504
+ Ideas are stored as GitHub Issues in a dedicated repository. Treat the issue tracker as the source of truth:
11505
+ - Use get_past_ideas to inspect the current issue backlog with optional filters.
11506
+ - Use search_ideas for full-text lookups before creating something new.
11507
+ - Use find_related_ideas to cluster overlapping ideas by tags and avoid duplicates.
11508
+ - Use create_idea to create new draft issues.
11509
+ - Use update_idea or organize_ideas when an existing issue should be refined instead of creating a duplicate.
11510
+
10744
11511
  ## Your Research Process
10745
11512
  1. Load the brand context (get_brand_context) to understand the creator's voice, expertise, and content pillars.
10746
- 2. Check existing ideas (get_past_ideas) to avoid duplicates.
11513
+ 2. Check existing GitHub issue ideas (get_past_ideas and search_ideas) to avoid duplicates.
10747
11514
  3. **RESEARCH PHASE** \u2014 This is the most important step. Use the available MCP tools:
10748
11515
  - **web_search_exa**: Search for trending topics, viral content, recent announcements, and hot takes in the creator's niche. Search for specific topics from the creator's content pillars.
10749
11516
  - **youtube_search_videos** or **youtube_search**: Find what videos are performing well right now. Look at view counts, recent uploads on trending topics, and gaps in existing content.
@@ -10769,15 +11536,27 @@ Every idea must:
10769
11536
  - Written (LinkedIn, X/Twitter): Thought leadership, hot takes, thread-worthy
10770
11537
 
10771
11538
  Generate 3-5 high-quality ideas. Quality over quantity. Every idea must be backed by research.`;
10772
- var SUPPORTED_PLATFORMS = ["tiktok", "youtube", "instagram", "linkedin", "x"];
11539
+ var SUPPORTED_PLATFORMS = [
11540
+ "tiktok" /* TikTok */,
11541
+ "youtube" /* YouTube */,
11542
+ "instagram" /* Instagram */,
11543
+ "linkedin" /* LinkedIn */,
11544
+ "x" /* X */
11545
+ ];
11546
+ var SUPPORTED_STATUSES = ["draft", "ready", "recorded", "published"];
11547
+ var SUPPORTED_PRIORITIES = ["hot-trend", "timely", "evergreen"];
10773
11548
  var MIN_IDEA_COUNT = 3;
10774
11549
  var MAX_IDEA_COUNT = 5;
10775
- function isRecord2(value) {
11550
+ var DEFAULT_EXISTING_IDEA_LIMIT = 50;
11551
+ function isRecord(value) {
10776
11552
  return typeof value === "object" && value !== null;
10777
11553
  }
10778
- function isStringArray2(value) {
11554
+ function isStringArray(value) {
10779
11555
  return Array.isArray(value) && value.every((item) => typeof item === "string");
10780
11556
  }
11557
+ function hasField(source, field) {
11558
+ return Object.prototype.hasOwnProperty.call(source, field);
11559
+ }
10781
11560
  function normalizeCount(count) {
10782
11561
  if (typeof count !== "number" || Number.isNaN(count)) {
10783
11562
  return MIN_IDEA_COUNT;
@@ -10790,7 +11569,7 @@ function normalizeSeedTopics(seedTopics) {
10790
11569
  }
10791
11570
  function extractStringArrayField(source, field) {
10792
11571
  const value = source[field];
10793
- return isStringArray2(value) ? value : [];
11572
+ return isStringArray(value) ? value : [];
10794
11573
  }
10795
11574
  function extractContentPillars(brand) {
10796
11575
  const raw = brand.contentPillars;
@@ -10802,7 +11581,7 @@ function extractContentPillars(brand) {
10802
11581
  const pillar2 = entry.trim();
10803
11582
  return pillar2 ? [{ pillar: pillar2 }] : [];
10804
11583
  }
10805
- if (!isRecord2(entry)) {
11584
+ if (!isRecord(entry)) {
10806
11585
  return [];
10807
11586
  }
10808
11587
  const pillar = typeof entry.pillar === "string" ? entry.pillar.trim() : "";
@@ -10811,21 +11590,22 @@ function extractContentPillars(brand) {
10811
11590
  }
10812
11591
  const description = typeof entry.description === "string" ? entry.description.trim() : void 0;
10813
11592
  const frequency = typeof entry.frequency === "string" ? entry.frequency.trim() : void 0;
10814
- const formats = isStringArray2(entry.formats) ? entry.formats.map((format) => format.trim()).filter((format) => format.length > 0) : void 0;
11593
+ const formats = isStringArray(entry.formats) ? entry.formats.map((format) => format.trim()).filter((format) => format.length > 0) : void 0;
10815
11594
  return [{ pillar, description, frequency, formats }];
10816
11595
  });
10817
11596
  }
10818
11597
  function summarizeExistingIdeas(ideas) {
10819
11598
  if (ideas.length === 0) {
10820
- return "No existing ideas found in the bank.";
11599
+ return "No existing GitHub issue ideas found in the repository.";
10821
11600
  }
10822
- return ideas.slice(0, 25).map((idea) => `- ${idea.id}: ${idea.topic} [${idea.status}]`).join("\n");
11601
+ return ideas.slice(0, 25).map((idea) => `- #${idea.issueNumber}: ${idea.topic} [${idea.status}] (${idea.issueUrl})`).join("\n");
10823
11602
  }
10824
11603
  function buildPlatformGuidance() {
10825
11604
  return [
10826
11605
  `Allowed platforms for create_idea: ${SUPPORTED_PLATFORMS.join(", ")}`,
10827
11606
  `Create between ${MIN_IDEA_COUNT} and ${MAX_IDEA_COUNT} ideas unless the user explicitly requests fewer within that range.`,
10828
- "Call create_idea once per idea, then call finalize_ideas exactly once when done."
11607
+ "Call create_idea once per new idea issue, then call finalize_ideas exactly once when done.",
11608
+ "Prefer update_idea or organize_ideas when you discover that a GitHub issue already covers the concept."
10829
11609
  ].join("\n");
10830
11610
  }
10831
11611
  function buildBrandPromptSection(brand) {
@@ -10859,20 +11639,33 @@ function buildBrandPromptSection(brand) {
10859
11639
  lines.push("Content pillars:");
10860
11640
  lines.push(
10861
11641
  ...contentPillars.map((pillar) => {
10862
- const details = [pillar.description, pillar.frequency && `Frequency: ${pillar.frequency}`, pillar.formats?.length ? `Formats: ${pillar.formats.join(", ")}` : void 0].filter((value) => typeof value === "string" && value.length > 0).join(" | ");
11642
+ const details = [
11643
+ pillar.description,
11644
+ pillar.frequency && `Frequency: ${pillar.frequency}`,
11645
+ pillar.formats?.length ? `Formats: ${pillar.formats.join(", ")}` : void 0
11646
+ ].filter((value) => typeof value === "string" && value.length > 0).join(" | ");
10863
11647
  return details ? `- ${pillar.pillar}: ${details}` : `- ${pillar.pillar}`;
10864
11648
  })
10865
11649
  );
10866
11650
  }
10867
11651
  return lines.join("\n");
10868
11652
  }
10869
- function buildSystemPrompt3(brand, existingIdeas, seedTopics, count) {
11653
+ function buildIdeaRepoPromptSection(ideaRepo) {
11654
+ return [
11655
+ "## GitHub Idea Repository",
11656
+ `Dedicated issue repo: ${ideaRepo}`,
11657
+ "Every idea is a GitHub Issue. The issue tracker is the source of truth for duplicates, lifecycle status, tags, and related concepts."
11658
+ ].join("\n");
11659
+ }
11660
+ function buildSystemPrompt3(brand, existingIdeas, seedTopics, count, ideaRepo) {
10870
11661
  const promptSections = [
10871
11662
  BASE_SYSTEM_PROMPT,
10872
11663
  "",
11664
+ buildIdeaRepoPromptSection(ideaRepo),
11665
+ "",
10873
11666
  buildBrandPromptSection(brand),
10874
11667
  "",
10875
- "## Existing Idea Bank",
11668
+ "## Existing Idea Issues",
10876
11669
  summarizeExistingIdeas(existingIdeas),
10877
11670
  "",
10878
11671
  "## Planning Constraints",
@@ -10888,7 +11681,7 @@ function buildUserMessage(count, seedTopics, hasMcpServers) {
10888
11681
  const focusText = seedTopics.length > 0 ? `Focus areas: ${seedTopics.join(", ")}` : "Focus areas: choose the strongest timely opportunities from the creator context and current trends.";
10889
11682
  const steps = [
10890
11683
  "1. Call get_brand_context to load the creator profile.",
10891
- "2. Call get_past_ideas to see what already exists."
11684
+ "2. Call get_past_ideas (and search_ideas if needed) to inspect existing GitHub issue ideas before proposing anything new."
10892
11685
  ];
10893
11686
  if (hasMcpServers) {
10894
11687
  steps.push(
@@ -10898,12 +11691,14 @@ function buildUserMessage(count, seedTopics, hasMcpServers) {
10898
11691
  " - Use perplexity-search to get current analysis on promising topics.",
10899
11692
  " Do at least 2-3 research queries. Each idea you create MUST reference specific findings from this research in its trendContext field.",
10900
11693
  `4. Call create_idea for each of the ${count} ideas, grounding each in your research findings.`,
10901
- "5. Call finalize_ideas when done."
11694
+ "5. If you uncover overlap with existing issues, prefer update_idea or organize_ideas over creating duplicates.",
11695
+ "6. Call finalize_ideas when done."
10902
11696
  );
10903
11697
  } else {
10904
11698
  steps.push(
10905
11699
  `3. Call create_idea for each of the ${count} ideas.`,
10906
- "4. Call finalize_ideas when done."
11700
+ "4. If you uncover overlap with existing issues, prefer update_idea or organize_ideas over creating duplicates.",
11701
+ "5. Call finalize_ideas when done."
10907
11702
  );
10908
11703
  }
10909
11704
  return [
@@ -10920,50 +11715,181 @@ async function loadBrandContext(brandPath) {
10920
11715
  }
10921
11716
  return readJsonFile(brandPath);
10922
11717
  }
10923
- function normalizePlatforms(platforms) {
10924
- const normalized = platforms.map((platform) => platform.trim().toLowerCase());
10925
- const invalid = normalized.filter((platform) => !SUPPORTED_PLATFORMS.includes(platform));
10926
- if (invalid.length > 0) {
10927
- throw new Error(`Unsupported platforms: ${invalid.join(", ")}`);
11718
+ function normalizeRequiredString(value, field) {
11719
+ if (typeof value !== "string") {
11720
+ throw new Error(`Invalid ${field}: expected string`);
11721
+ }
11722
+ const normalized = value.trim();
11723
+ if (!normalized) {
11724
+ throw new Error(`Invalid ${field}: value cannot be empty`);
10928
11725
  }
10929
11726
  return normalized;
10930
11727
  }
10931
- function assertKebabCaseId(id) {
10932
- const normalized = id.trim();
10933
- if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(normalized)) {
10934
- throw new Error(`Idea ID must be kebab-case: ${id}`);
11728
+ function normalizeOptionalString(value, field) {
11729
+ if (value === void 0) {
11730
+ return void 0;
10935
11731
  }
10936
- return normalized;
11732
+ if (typeof value !== "string") {
11733
+ throw new Error(`Invalid ${field}: expected string`);
11734
+ }
11735
+ const normalized = value.trim();
11736
+ return normalized || void 0;
10937
11737
  }
10938
- function buildIdea(args) {
10939
- const now = (/* @__PURE__ */ new Date()).toISOString();
10940
- const publishBy = args.publishBy.trim();
10941
- if (args.hook.trim().length > 80) {
10942
- throw new Error(`Idea hook must be 80 characters or fewer: ${args.id}`);
11738
+ function normalizeStringList(value, field) {
11739
+ if (!isStringArray(value)) {
11740
+ throw new Error(`Invalid ${field}: expected string[]`);
10943
11741
  }
11742
+ return value.map((item) => item.trim()).filter((item) => item.length > 0);
11743
+ }
11744
+ function normalizeIssueNumber(value) {
11745
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
11746
+ throw new Error("Invalid issueNumber: expected a positive integer");
11747
+ }
11748
+ return value;
11749
+ }
11750
+ function normalizePublishBy(value, field = "publishBy") {
11751
+ const publishBy = normalizeRequiredString(value, field);
10944
11752
  if (Number.isNaN(new Date(publishBy).getTime())) {
10945
- throw new Error(`Invalid publishBy date: ${args.publishBy}`);
11753
+ throw new Error(`Invalid ${field} date: ${publishBy}`);
11754
+ }
11755
+ return publishBy;
11756
+ }
11757
+ function normalizePlatforms(platforms) {
11758
+ const values = normalizeStringList(platforms, "platforms").map((platform) => platform.toLowerCase());
11759
+ const invalid = values.filter((platform) => !SUPPORTED_PLATFORMS.includes(platform));
11760
+ if (invalid.length > 0) {
11761
+ throw new Error(`Unsupported platforms: ${invalid.join(", ")}`);
11762
+ }
11763
+ return values;
11764
+ }
11765
+ function normalizeStatus(value) {
11766
+ const status = normalizeRequiredString(value, "status").toLowerCase();
11767
+ if (!SUPPORTED_STATUSES.includes(status)) {
11768
+ throw new Error(`Unsupported status: ${status}`);
11769
+ }
11770
+ return status;
11771
+ }
11772
+ function parseCreateIdeaInput(args) {
11773
+ const hook = normalizeRequiredString(args.hook, "hook");
11774
+ if (hook.length > 80) {
11775
+ throw new Error(`Idea hook must be 80 characters or fewer: ${hook}`);
10946
11776
  }
10947
11777
  return {
10948
- id: assertKebabCaseId(args.id),
10949
- topic: args.topic.trim(),
10950
- hook: args.hook.trim(),
10951
- audience: args.audience.trim(),
10952
- keyTakeaway: args.keyTakeaway.trim(),
10953
- talkingPoints: args.talkingPoints.map((point) => point.trim()).filter((point) => point.length > 0),
11778
+ topic: normalizeRequiredString(args.topic, "topic"),
11779
+ hook,
11780
+ audience: normalizeRequiredString(args.audience, "audience"),
11781
+ keyTakeaway: normalizeRequiredString(args.keyTakeaway, "keyTakeaway"),
11782
+ talkingPoints: normalizeStringList(args.talkingPoints, "talkingPoints"),
10954
11783
  platforms: normalizePlatforms(args.platforms),
10955
- status: "draft",
10956
- tags: args.tags.map((tag) => tag.trim()).filter((tag) => tag.length > 0),
10957
- trendContext: args.trendContext?.trim() || void 0,
10958
- createdAt: now,
10959
- updatedAt: now,
10960
- publishBy
11784
+ tags: normalizeStringList(args.tags, "tags"),
11785
+ publishBy: normalizePublishBy(args.publishBy),
11786
+ trendContext: normalizeOptionalString(args.trendContext, "trendContext")
11787
+ };
11788
+ }
11789
+ function parseIdeaFilters(args) {
11790
+ const filters = {};
11791
+ if (hasField(args, "status") && args.status !== void 0) {
11792
+ filters.status = normalizeStatus(args.status);
11793
+ }
11794
+ if (hasField(args, "platform") && args.platform !== void 0) {
11795
+ filters.platform = normalizePlatforms([args.platform].flat())[0];
11796
+ }
11797
+ if (hasField(args, "tag") && args.tag !== void 0) {
11798
+ filters.tag = normalizeRequiredString(args.tag, "tag");
11799
+ }
11800
+ if (hasField(args, "priority") && args.priority !== void 0) {
11801
+ const priority = normalizeRequiredString(args.priority, "priority").toLowerCase();
11802
+ if (!SUPPORTED_PRIORITIES.includes(priority)) {
11803
+ throw new Error(`Unsupported priority: ${priority}`);
11804
+ }
11805
+ filters.priority = priority;
11806
+ }
11807
+ if (hasField(args, "limit") && args.limit !== void 0) {
11808
+ if (typeof args.limit !== "number" || !Number.isInteger(args.limit) || args.limit <= 0) {
11809
+ throw new Error("Invalid limit: expected a positive integer");
11810
+ }
11811
+ filters.limit = args.limit;
11812
+ }
11813
+ return filters;
11814
+ }
11815
+ function extractIdeaUpdates(source) {
11816
+ const updates = {};
11817
+ if (hasField(source, "topic")) {
11818
+ updates.topic = normalizeRequiredString(source.topic, "updates.topic");
11819
+ }
11820
+ if (hasField(source, "hook")) {
11821
+ const hook = normalizeRequiredString(source.hook, "updates.hook");
11822
+ if (hook.length > 80) {
11823
+ throw new Error(`Idea hook must be 80 characters or fewer: ${hook}`);
11824
+ }
11825
+ updates.hook = hook;
11826
+ }
11827
+ if (hasField(source, "audience")) {
11828
+ updates.audience = normalizeRequiredString(source.audience, "updates.audience");
11829
+ }
11830
+ if (hasField(source, "keyTakeaway")) {
11831
+ updates.keyTakeaway = normalizeRequiredString(source.keyTakeaway, "updates.keyTakeaway");
11832
+ }
11833
+ if (hasField(source, "talkingPoints")) {
11834
+ updates.talkingPoints = normalizeStringList(source.talkingPoints, "updates.talkingPoints");
11835
+ }
11836
+ if (hasField(source, "platforms")) {
11837
+ updates.platforms = normalizePlatforms(source.platforms);
11838
+ }
11839
+ if (hasField(source, "tags")) {
11840
+ updates.tags = normalizeStringList(source.tags, "updates.tags");
11841
+ }
11842
+ if (hasField(source, "publishBy")) {
11843
+ updates.publishBy = normalizePublishBy(source.publishBy, "updates.publishBy");
11844
+ }
11845
+ if (hasField(source, "trendContext")) {
11846
+ updates.trendContext = normalizeOptionalString(source.trendContext, "updates.trendContext");
11847
+ }
11848
+ if (hasField(source, "status")) {
11849
+ updates.status = normalizeStatus(source.status);
11850
+ }
11851
+ return updates;
11852
+ }
11853
+ function parseUpdateIdeaArgs(args) {
11854
+ if (!isRecord(args.updates)) {
11855
+ throw new Error("Invalid update_idea arguments: updates must be an object");
11856
+ }
11857
+ return {
11858
+ issueNumber: normalizeIssueNumber(args.issueNumber),
11859
+ updates: extractIdeaUpdates(args.updates)
10961
11860
  };
10962
11861
  }
11862
+ function parseOrganizeIdeasArgs(args) {
11863
+ const { items } = args;
11864
+ if (!Array.isArray(items)) {
11865
+ throw new Error("Invalid organize_ideas arguments: items must be an array");
11866
+ }
11867
+ return items.map((item, index) => {
11868
+ if (!isRecord(item)) {
11869
+ throw new Error(`Invalid organize_ideas item at index ${index}`);
11870
+ }
11871
+ if (item.updates !== void 0 && !isRecord(item.updates)) {
11872
+ throw new Error(`Invalid organize_ideas item at index ${index}: updates must be an object`);
11873
+ }
11874
+ return {
11875
+ issueNumber: normalizeIssueNumber(item.issueNumber),
11876
+ updates: item.updates ? extractIdeaUpdates(item.updates) : void 0,
11877
+ includeRelated: item.includeRelated === void 0 ? true : Boolean(item.includeRelated)
11878
+ };
11879
+ });
11880
+ }
11881
+ function summarizeLinkedIssues(ideas) {
11882
+ return ideas.map((idea) => ({
11883
+ issueNumber: idea.issueNumber,
11884
+ issueUrl: idea.issueUrl,
11885
+ topic: idea.topic,
11886
+ tags: idea.tags
11887
+ }));
11888
+ }
10963
11889
  var IdeationAgent = class extends BaseAgent {
10964
11890
  brandContext;
10965
11891
  existingIdeas;
10966
- ideasDir;
11892
+ ideaRepo;
10967
11893
  targetCount;
10968
11894
  generatedIdeas = [];
10969
11895
  finalized = false;
@@ -10971,7 +11897,7 @@ var IdeationAgent = class extends BaseAgent {
10971
11897
  super("IdeationAgent", systemPrompt, getProvider2(), model ?? getModelForAgent("IdeationAgent"));
10972
11898
  this.brandContext = context.brandContext;
10973
11899
  this.existingIdeas = [...context.existingIdeas];
10974
- this.ideasDir = context.ideasDir;
11900
+ this.ideaRepo = context.ideaRepo;
10975
11901
  this.targetCount = context.targetCount;
10976
11902
  }
10977
11903
  resetForRetry() {
@@ -11022,20 +11948,25 @@ var IdeationAgent = class extends BaseAgent {
11022
11948
  },
11023
11949
  {
11024
11950
  name: "get_past_ideas",
11025
- description: "Return the current idea bank to help avoid duplicate ideas.",
11951
+ description: "List GitHub-backed ideas with optional status, platform, tag, priority, or limit filters.",
11026
11952
  parameters: {
11027
11953
  type: "object",
11028
- properties: {}
11954
+ properties: {
11955
+ status: { type: "string", enum: [...SUPPORTED_STATUSES] },
11956
+ platform: { type: "string", enum: [...SUPPORTED_PLATFORMS] },
11957
+ tag: { type: "string" },
11958
+ priority: { type: "string", enum: [...SUPPORTED_PRIORITIES] },
11959
+ limit: { type: "integer", minimum: 1 }
11960
+ }
11029
11961
  },
11030
11962
  handler: async (args) => this.handleToolCall("get_past_ideas", args)
11031
11963
  },
11032
11964
  {
11033
11965
  name: "create_idea",
11034
- description: "Create a new draft content idea and persist it to the idea bank.",
11966
+ description: `Create a new draft GitHub Issue in ${this.ideaRepo} using the full idea schema.`,
11035
11967
  parameters: {
11036
11968
  type: "object",
11037
11969
  properties: {
11038
- id: { type: "string", description: "Kebab-case idea identifier" },
11039
11970
  topic: { type: "string", description: "Main topic or title" },
11040
11971
  hook: { type: "string", description: "Attention-grabbing hook (80 chars max)" },
11041
11972
  audience: { type: "string", description: "Target audience" },
@@ -11067,10 +11998,98 @@ var IdeationAgent = class extends BaseAgent {
11067
11998
  description: "Why this idea is timely right now"
11068
11999
  }
11069
12000
  },
11070
- required: ["id", "topic", "hook", "audience", "keyTakeaway", "talkingPoints", "platforms", "tags", "publishBy"]
12001
+ required: ["topic", "hook", "audience", "keyTakeaway", "talkingPoints", "platforms", "tags", "publishBy"]
11071
12002
  },
11072
12003
  handler: async (args) => this.handleToolCall("create_idea", args)
11073
12004
  },
12005
+ {
12006
+ name: "search_ideas",
12007
+ description: "Search GitHub-backed ideas with full-text search across issue content.",
12008
+ parameters: {
12009
+ type: "object",
12010
+ properties: {
12011
+ query: { type: "string", description: "Full-text search query" }
12012
+ },
12013
+ required: ["query"]
12014
+ },
12015
+ handler: async (args) => this.handleToolCall("search_ideas", args)
12016
+ },
12017
+ {
12018
+ name: "find_related_ideas",
12019
+ description: "Find related ideas for an issue by looking up the issue and matching similar tagged GitHub ideas.",
12020
+ parameters: {
12021
+ type: "object",
12022
+ properties: {
12023
+ issueNumber: { type: "integer", description: "GitHub issue number for the idea" }
12024
+ },
12025
+ required: ["issueNumber"]
12026
+ },
12027
+ handler: async (args) => this.handleToolCall("find_related_ideas", args)
12028
+ },
12029
+ {
12030
+ name: "update_idea",
12031
+ description: "Update an existing GitHub idea issue: refine copy, adjust labels, or change lifecycle status.",
12032
+ parameters: {
12033
+ type: "object",
12034
+ properties: {
12035
+ issueNumber: { type: "integer", description: "GitHub issue number for the idea" },
12036
+ updates: {
12037
+ type: "object",
12038
+ properties: {
12039
+ topic: { type: "string" },
12040
+ hook: { type: "string" },
12041
+ audience: { type: "string" },
12042
+ keyTakeaway: { type: "string" },
12043
+ talkingPoints: { type: "array", items: { type: "string" } },
12044
+ platforms: { type: "array", items: { type: "string", enum: [...SUPPORTED_PLATFORMS] } },
12045
+ tags: { type: "array", items: { type: "string" } },
12046
+ publishBy: { type: "string" },
12047
+ trendContext: { type: "string" },
12048
+ status: { type: "string", enum: [...SUPPORTED_STATUSES] }
12049
+ }
12050
+ }
12051
+ },
12052
+ required: ["issueNumber", "updates"]
12053
+ },
12054
+ handler: async (args) => this.handleToolCall("update_idea", args)
12055
+ },
12056
+ {
12057
+ name: "organize_ideas",
12058
+ description: "Batch update GitHub idea issue labels/statuses and return related issue links for clustering.",
12059
+ parameters: {
12060
+ type: "object",
12061
+ properties: {
12062
+ items: {
12063
+ type: "array",
12064
+ items: {
12065
+ type: "object",
12066
+ properties: {
12067
+ issueNumber: { type: "integer" },
12068
+ updates: {
12069
+ type: "object",
12070
+ properties: {
12071
+ topic: { type: "string" },
12072
+ hook: { type: "string" },
12073
+ audience: { type: "string" },
12074
+ keyTakeaway: { type: "string" },
12075
+ talkingPoints: { type: "array", items: { type: "string" } },
12076
+ platforms: { type: "array", items: { type: "string", enum: [...SUPPORTED_PLATFORMS] } },
12077
+ tags: { type: "array", items: { type: "string" } },
12078
+ publishBy: { type: "string" },
12079
+ trendContext: { type: "string" },
12080
+ status: { type: "string", enum: [...SUPPORTED_STATUSES] }
12081
+ }
12082
+ },
12083
+ includeRelated: { type: "boolean" }
12084
+ },
12085
+ required: ["issueNumber"]
12086
+ }
12087
+ }
12088
+ },
12089
+ required: ["items"]
12090
+ },
12091
+ handler: async (args) => this.handleToolCall("organize_ideas", args)
12092
+ },
11074
12093
  {
11075
12094
  name: "finalize_ideas",
11076
12095
  description: "Signal that idea generation is complete.",
@@ -11086,16 +12105,18 @@ var IdeationAgent = class extends BaseAgent {
11086
12105
  switch (toolName) {
11087
12106
  case "get_brand_context":
11088
12107
  return this.brandContext ?? await Promise.resolve(getBrandConfig());
11089
- case "get_past_ideas": {
11090
- const ideas = await readIdeaBank(this.ideasDir);
11091
- return ideas.map((idea) => ({
11092
- id: idea.id,
11093
- topic: idea.topic,
11094
- status: idea.status
11095
- }));
11096
- }
12108
+ case "get_past_ideas":
12109
+ return await listIdeas(parseIdeaFilters(args));
11097
12110
  case "create_idea":
11098
- return this.handleCreateIdea(args);
12111
+ return await this.handleCreateIdea(args);
12112
+ case "search_ideas":
12113
+ return await searchIdeas(normalizeRequiredString(args.query, "query"));
12114
+ case "find_related_ideas":
12115
+ return await this.handleFindRelatedIdeas(args);
12116
+ case "update_idea":
12117
+ return await this.handleUpdateIdea(args);
12118
+ case "organize_ideas":
12119
+ return await this.handleOrganizeIdeas(args);
11099
12120
  case "finalize_ideas":
11100
12121
  this.finalized = true;
11101
12122
  return { success: true, count: this.generatedIdeas.length };
@@ -11107,43 +12128,70 @@ var IdeationAgent = class extends BaseAgent {
11107
12128
  if (this.generatedIdeas.length >= this.targetCount) {
11108
12129
  throw new Error(`Target idea count already reached (${this.targetCount})`);
11109
12130
  }
11110
- const createArgs = this.parseCreateIdeaArgs(args);
11111
- const idea = buildIdea(createArgs);
11112
- const duplicateTopic = this.findDuplicateTopic(idea.topic);
12131
+ const input = parseCreateIdeaInput(args);
12132
+ const duplicateTopic = this.findDuplicateTopic(input.topic);
11113
12133
  if (duplicateTopic) {
11114
12134
  throw new Error(`Duplicate idea topic detected: ${duplicateTopic}`);
11115
12135
  }
11116
- const duplicateId = this.findDuplicateId(idea.id);
11117
- if (duplicateId) {
11118
- throw new Error(`Duplicate idea ID detected: ${duplicateId}`);
11119
- }
11120
- await writeIdea(idea, this.ideasDir);
11121
- this.generatedIdeas.push(idea);
11122
- logger_default.info(`[IdeationAgent] Created idea ${idea.id}: ${idea.topic}`);
12136
+ const idea = await createIdea(input);
12137
+ this.upsertIdea(this.existingIdeas, idea);
12138
+ this.upsertIdea(this.generatedIdeas, idea);
12139
+ logger_default.info(`[IdeationAgent] Created GitHub idea #${idea.issueNumber}: ${idea.topic}`);
11123
12140
  return { success: true, idea };
11124
12141
  }
11125
- parseCreateIdeaArgs(args) {
11126
- const { id, topic, hook, audience, keyTakeaway, talkingPoints, platforms, tags, publishBy, trendContext } = args;
11127
- if (typeof id !== "string" || typeof topic !== "string" || typeof hook !== "string" || typeof audience !== "string" || typeof keyTakeaway !== "string" || !isStringArray2(talkingPoints) || !isStringArray2(platforms) || !isStringArray2(tags) || typeof publishBy !== "string" || trendContext !== void 0 && typeof trendContext !== "string") {
11128
- throw new Error("Invalid create_idea arguments");
12142
+ async handleFindRelatedIdeas(args) {
12143
+ const issueNumber = normalizeIssueNumber(args.issueNumber);
12144
+ const idea = await getIdea(issueNumber);
12145
+ if (!idea) {
12146
+ throw new Error(`Idea #${issueNumber} was not found in ${this.ideaRepo}`);
11129
12147
  }
12148
+ return await findRelatedIdeas(idea);
12149
+ }
12150
+ async handleUpdateIdea(args) {
12151
+ const { issueNumber, updates } = parseUpdateIdeaArgs(args);
12152
+ const idea = await updateIdea(issueNumber, updates);
12153
+ this.upsertIdea(this.existingIdeas, idea);
12154
+ this.syncGeneratedIdea(idea);
12155
+ logger_default.info(`[IdeationAgent] Updated GitHub idea #${idea.issueNumber}: ${idea.topic}`);
12156
+ return { success: true, idea };
12157
+ }
12158
+ async handleOrganizeIdeas(args) {
12159
+ const items = parseOrganizeIdeasArgs(args);
12160
+ const organizedItems = await Promise.all(
12161
+ items.map(async (item) => {
12162
+ const currentIdea = await getIdea(item.issueNumber);
12163
+ if (!currentIdea) {
12164
+ throw new Error(`Idea #${item.issueNumber} was not found in ${this.ideaRepo}`);
12165
+ }
12166
+ const nextIdea = item.updates ? await updateIdea(item.issueNumber, item.updates) : currentIdea;
12167
+ this.upsertIdea(this.existingIdeas, nextIdea);
12168
+ this.syncGeneratedIdea(nextIdea);
12169
+ const relatedIdeas = item.includeRelated ? await findRelatedIdeas(nextIdea) : [];
12170
+ return {
12171
+ issueNumber: nextIdea.issueNumber,
12172
+ idea: nextIdea,
12173
+ linkedIssues: summarizeLinkedIssues(relatedIdeas)
12174
+ };
12175
+ })
12176
+ );
11130
12177
  return {
11131
- id,
11132
- topic,
11133
- hook,
11134
- audience,
11135
- keyTakeaway,
11136
- talkingPoints,
11137
- platforms,
11138
- tags,
11139
- publishBy,
11140
- trendContext
12178
+ success: true,
12179
+ items: organizedItems
11141
12180
  };
11142
12181
  }
11143
- findDuplicateId(id) {
11144
- const normalizedId = id.trim().toLowerCase();
11145
- const existing = [...this.existingIdeas, ...this.generatedIdeas].find((idea) => idea.id.trim().toLowerCase() === normalizedId);
11146
- return existing?.id;
12182
+ upsertIdea(collection, nextIdea) {
12183
+ const existingIndex = collection.findIndex((idea) => idea.issueNumber === nextIdea.issueNumber);
12184
+ if (existingIndex === -1) {
12185
+ collection.push(nextIdea);
12186
+ return;
12187
+ }
12188
+ collection.splice(existingIndex, 1, nextIdea);
12189
+ }
12190
+ syncGeneratedIdea(nextIdea) {
12191
+ const existingIndex = this.generatedIdeas.findIndex((idea) => idea.issueNumber === nextIdea.issueNumber);
12192
+ if (existingIndex !== -1) {
12193
+ this.generatedIdeas.splice(existingIndex, 1, nextIdea);
12194
+ }
11147
12195
  }
11148
12196
  findDuplicateTopic(topic) {
11149
12197
  const normalizedTopic = topic.trim().toLowerCase();
@@ -11166,12 +12214,12 @@ async function generateIdeas(options = {}) {
11166
12214
  config2.BRAND_PATH = options.brandPath;
11167
12215
  }
11168
12216
  const brandContext = await loadBrandContext(options.brandPath);
11169
- const existingIdeas = await readIdeaBank(options.ideasDir);
11170
- const systemPrompt = buildSystemPrompt3(brandContext, existingIdeas, seedTopics, count);
12217
+ const existingIdeas = await listIdeas({ limit: DEFAULT_EXISTING_IDEA_LIMIT });
12218
+ const systemPrompt = buildSystemPrompt3(brandContext, existingIdeas, seedTopics, count, config2.IDEAS_REPO);
11171
12219
  const agent = new IdeationAgent(systemPrompt, {
11172
12220
  brandContext,
11173
12221
  existingIdeas,
11174
- ideasDir: options.ideasDir,
12222
+ ideaRepo: config2.IDEAS_REPO,
11175
12223
  targetCount: count
11176
12224
  });
11177
12225
  try {
@@ -11217,6 +12265,7 @@ function createScheduleAgent(...args) {
11217
12265
  }
11218
12266
 
11219
12267
  // src/L6-pipeline/pipeline.ts
12268
+ init_types();
11220
12269
  async function runStage(stageName, fn, stageResults) {
11221
12270
  const start = Date.now();
11222
12271
  try {
@@ -12104,7 +13153,7 @@ Type \x1B[33mexit\x1B[0m or \x1B[33mquit\x1B[0m to leave. Press Ctrl+C to stop.
12104
13153
 
12105
13154
  // src/L7-app/commands/ideate.ts
12106
13155
  init_environment();
12107
- init_ideaStore();
13156
+ init_ideaService();
12108
13157
 
12109
13158
  // src/L6-pipeline/ideation.ts
12110
13159
  function generateIdeas3(...args) {
@@ -12115,8 +13164,8 @@ function generateIdeas3(...args) {
12115
13164
  async function runIdeate(options = {}) {
12116
13165
  initConfig();
12117
13166
  if (options.list) {
12118
- const ideas2 = await readIdeaBank(options.output);
12119
- const filtered = options.status ? ideas2.filter((i) => i.status === options.status) : ideas2;
13167
+ const ideas2 = await listIdeas();
13168
+ const filtered = options.status ? ideas2.filter((idea) => idea.status === options.status) : ideas2;
12120
13169
  if (filtered.length === 0) {
12121
13170
  console.log("No ideas found.");
12122
13171
  if (options.status) {
@@ -12166,9 +13215,9 @@ ${filtered.length} idea(s) total`);
12166
13215
  console.log(` Status: ${idea.status}`);
12167
13216
  console.log("");
12168
13217
  }
12169
- console.log("Ideas saved to ./ideas/ directory.");
13218
+ console.log("Ideas saved to the GitHub-backed idea service.");
12170
13219
  console.log("Use `vidpipe ideate --list` to view all ideas.");
12171
- console.log("Use `vidpipe process video.mp4 --ideas <id1>,<id2>` to link ideas to a recording.");
13220
+ console.log("Use `vidpipe process video.mp4 --ideas <issueNumber1>,<issueNumber2>` to link ideas to a recording.");
12172
13221
  }
12173
13222
 
12174
13223
  // src/L1-infra/http/http.ts
@@ -12179,14 +13228,16 @@ import { Router } from "express";
12179
13228
  init_paths();
12180
13229
 
12181
13230
  // src/L7-app/review/routes.ts
12182
- init_ideaService();
13231
+ init_ideaService2();
13232
+ init_types();
12183
13233
  init_configLogger();
12184
13234
 
12185
13235
  // src/L7-app/review/approvalQueue.ts
12186
13236
  init_fileSystem();
12187
- init_ideaService();
13237
+ init_ideaService2();
12188
13238
 
12189
13239
  // src/L3-services/socialPosting/accountMapping.ts
13240
+ init_types();
12190
13241
  init_configLogger();
12191
13242
  init_fileSystem();
12192
13243
  init_paths();
@@ -12290,6 +13341,7 @@ async function getAccountId(platform) {
12290
13341
  }
12291
13342
 
12292
13343
  // src/L7-app/review/approvalQueue.ts
13344
+ init_types();
12293
13345
  init_configLogger();
12294
13346
  var queue = [];
12295
13347
  var processing = false;
@@ -12329,20 +13381,32 @@ async function processApprovalBatch(itemIds) {
12329
13381
  itemIds.map(async (id) => ({ id, item: await getItem(id) }))
12330
13382
  );
12331
13383
  const itemMap = new Map(loadedItems.map(({ id, item }) => [id, item]));
12332
- const enriched = await Promise.all(
12333
- loadedItems.map(async ({ id, item }) => {
12334
- if (!item?.metadata.ideaIds?.length) {
12335
- return { id, publishBy: null, hasIdeas: false };
13384
+ const allIdeaIds = /* @__PURE__ */ new Set();
13385
+ for (const { item } of loadedItems) {
13386
+ if (item?.metadata.ideaIds?.length) {
13387
+ for (const ideaId of item.metadata.ideaIds) {
13388
+ allIdeaIds.add(ideaId);
12336
13389
  }
12337
- try {
12338
- const ideas = await getIdeasByIds(item.metadata.ideaIds);
12339
- const dates = ideas.map((idea) => idea.publishBy).filter((publishBy) => Boolean(publishBy)).sort();
12340
- return { id, publishBy: dates[0] ?? null, hasIdeas: true };
12341
- } catch {
12342
- return { id, publishBy: null, hasIdeas: true };
13390
+ }
13391
+ }
13392
+ let ideaMap = /* @__PURE__ */ new Map();
13393
+ if (allIdeaIds.size > 0) {
13394
+ try {
13395
+ const allIdeas = await getIdeasByIds([...allIdeaIds]);
13396
+ for (const idea of allIdeas) {
13397
+ ideaMap.set(idea.id, idea);
13398
+ ideaMap.set(String(idea.issueNumber), idea);
12343
13399
  }
12344
- })
12345
- );
13400
+ } catch {
13401
+ }
13402
+ }
13403
+ const enriched = loadedItems.map(({ id, item }) => {
13404
+ if (!item?.metadata.ideaIds?.length) {
13405
+ return { id, publishBy: null, hasIdeas: false };
13406
+ }
13407
+ const dates = item.metadata.ideaIds.map((ideaId) => ideaMap.get(ideaId)?.publishBy).filter((publishBy) => Boolean(publishBy)).sort();
13408
+ return { id, publishBy: dates[0] ?? null, hasIdeas: true };
13409
+ });
12346
13410
  const now = Date.now();
12347
13411
  const sevenDays = 7 * 24 * 60 * 60 * 1e3;
12348
13412
  enriched.sort((a, b) => {
@@ -12480,7 +13544,31 @@ async function enrichQueueItem(item) {
12480
13544
  };
12481
13545
  }
12482
13546
  async function enrichQueueItems(items) {
12483
- return Promise.all(items.map((item) => enrichQueueItem(item)));
13547
+ const allIdeaIds = /* @__PURE__ */ new Set();
13548
+ for (const item of items) {
13549
+ if (item.metadata.ideaIds?.length) {
13550
+ for (const ideaId of item.metadata.ideaIds) {
13551
+ allIdeaIds.add(ideaId);
13552
+ }
13553
+ }
13554
+ }
13555
+ let publishByMap = /* @__PURE__ */ new Map();
13556
+ if (allIdeaIds.size > 0) {
13557
+ try {
13558
+ const ideas = await getIdeasByIds([...allIdeaIds]);
13559
+ for (const idea of ideas) {
13560
+ publishByMap.set(idea.id, idea.publishBy);
13561
+ publishByMap.set(String(idea.issueNumber), idea.publishBy);
13562
+ }
13563
+ } catch {
13564
+ }
13565
+ }
13566
+ return items.map((item) => {
13567
+ if (!item.metadata.ideaIds?.length) return { ...item };
13568
+ const dates = item.metadata.ideaIds.map((id) => publishByMap.get(id)).filter((publishBy) => Boolean(publishBy)).sort();
13569
+ const ideaPublishBy = dates[0];
13570
+ return ideaPublishBy ? { ...item, ideaPublishBy } : { ...item };
13571
+ });
12484
13572
  }
12485
13573
  async function enrichGroupedQueueItems(groups) {
12486
13574
  return Promise.all(groups.map(async (group) => ({
@@ -12806,7 +13894,7 @@ var defaultCmd = program.command("process", { isDefault: true }).argument("[vide
12806
13894
  logger_default.info(`Output dir: ${config2.OUTPUT_DIR}`);
12807
13895
  let ideas;
12808
13896
  if (opts.ideas) {
12809
- const { getIdeasByIds: getIdeasByIds2 } = await Promise.resolve().then(() => (init_ideaService(), ideaService_exports));
13897
+ const { getIdeasByIds: getIdeasByIds2 } = await Promise.resolve().then(() => (init_ideaService2(), ideaService_exports2));
12810
13898
  const ideaIds = opts.ideas.split(",").map((id) => id.trim()).filter(Boolean);
12811
13899
  try {
12812
13900
  ideas = await getIdeasByIds2(ideaIds);
@@ -12823,10 +13911,10 @@ var defaultCmd = program.command("process", { isDefault: true }).argument("[vide
12823
13911
  await processVideoSafe(resolvedPath, ideas);
12824
13912
  if (ideas && ideas.length > 0) {
12825
13913
  try {
12826
- const { markRecorded: markRecorded2 } = await Promise.resolve().then(() => (init_ideaService(), ideaService_exports));
13914
+ const { markRecorded: markRecorded3 } = await Promise.resolve().then(() => (init_ideaService(), ideaService_exports));
12827
13915
  const slug = resolvedPath.replace(/\\/g, "/").split("/").pop()?.replace(/\.(mp4|mov|webm|avi|mkv)$/i, "") || "";
12828
13916
  for (const idea of ideas) {
12829
- await markRecorded2(idea.id, slug);
13917
+ await markRecorded3(idea.issueNumber, slug);
12830
13918
  }
12831
13919
  logger_default.info(`Marked ${ideas.length} idea(s) as recorded`);
12832
13920
  } catch (err) {