vidpipe 1.3.5 → 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.
- package/dist/cli.js +1429 -285
- package/dist/cli.js.map +1 -1
- 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/
|
|
1191
|
-
|
|
1192
|
-
|
|
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
|
-
|
|
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);
|
|
1196
1256
|
}
|
|
1197
|
-
function
|
|
1198
|
-
|
|
1199
|
-
|
|
1257
|
+
function normalizeLabels(labels) {
|
|
1258
|
+
return Array.from(new Set(labels.map((label) => label.trim()).filter((label) => label.length > 0)));
|
|
1259
|
+
}
|
|
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
|
|
1270
|
+
return clientInstance;
|
|
1202
1271
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
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
|
|
1207
|
-
return
|
|
1547
|
+
function sanitizeMultilineValue(value) {
|
|
1548
|
+
return (value ?? "").trim();
|
|
1208
1549
|
}
|
|
1209
|
-
function
|
|
1210
|
-
return
|
|
1550
|
+
function normalizeTag(tag) {
|
|
1551
|
+
return tag.trim().toLowerCase().replace(/\s+/g, "-");
|
|
1211
1552
|
}
|
|
1212
|
-
function
|
|
1213
|
-
return
|
|
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
|
|
1557
|
+
return platformValues.has(value);
|
|
1217
1558
|
}
|
|
1218
|
-
function
|
|
1219
|
-
return
|
|
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
|
|
1225
|
-
return
|
|
1562
|
+
function uniquePlatforms(platforms) {
|
|
1563
|
+
return Array.from(new Set(platforms));
|
|
1226
1564
|
}
|
|
1227
|
-
function
|
|
1228
|
-
|
|
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
|
-
|
|
1231
|
-
const
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1246
|
-
const
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
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
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
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
|
+
};
|
|
1255
1671
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
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");
|
|
1703
|
+
}
|
|
1704
|
+
function parseIdeaComment(commentBody) {
|
|
1705
|
+
const markerIndex = commentBody.indexOf(COMMENT_MARKER);
|
|
1706
|
+
if (markerIndex === -1) {
|
|
1259
1707
|
return null;
|
|
1260
1708
|
}
|
|
1261
|
-
const
|
|
1262
|
-
|
|
1263
|
-
|
|
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;
|
|
1714
|
+
}
|
|
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;
|
|
1264
1742
|
}
|
|
1265
|
-
return
|
|
1743
|
+
return null;
|
|
1266
1744
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
if (!await fileExists(ideasDir)) {
|
|
1745
|
+
function buildLabelFilters(filters) {
|
|
1746
|
+
if (!filters) {
|
|
1270
1747
|
return [];
|
|
1271
1748
|
}
|
|
1272
|
-
const
|
|
1273
|
-
|
|
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
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
async function getReadyIdeas() {
|
|
2011
|
+
return listIdeas({ status: "ready" });
|
|
1274
2012
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
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
|
-
|
|
2023
|
+
init_types();
|
|
2024
|
+
init_githubClient();
|
|
2025
|
+
init_environment();
|
|
1280
2026
|
init_configLogger();
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
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
|
|
1292
|
-
__export(
|
|
2038
|
+
var ideaService_exports2 = {};
|
|
2039
|
+
__export(ideaService_exports2, {
|
|
1293
2040
|
getIdeasByIds: () => getIdeasByIds,
|
|
1294
|
-
getReadyIdeas: () =>
|
|
1295
|
-
markPublished: () =>
|
|
1296
|
-
markRecorded: () =>
|
|
2041
|
+
getReadyIdeas: () => getReadyIdeas2,
|
|
2042
|
+
markPublished: () => markPublished2,
|
|
2043
|
+
markRecorded: () => markRecorded2,
|
|
1297
2044
|
matchIdeasToTranscript: () => matchIdeasToTranscript
|
|
1298
2045
|
});
|
|
1299
|
-
|
|
1300
|
-
return
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
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;
|
|
1309
2071
|
}
|
|
1310
|
-
async function
|
|
1311
|
-
const ideas = await
|
|
1312
|
-
|
|
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
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
async function getReadyIdeas2(_dir) {
|
|
2085
|
+
return getReadyIdeas();
|
|
1313
2086
|
}
|
|
1314
|
-
async function
|
|
1315
|
-
const idea = await
|
|
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.
|
|
1320
|
-
idea.sourceVideoSlug = videoSlug;
|
|
1321
|
-
await writeIdea(idea, dir);
|
|
2092
|
+
await markRecorded(idea.issueNumber, videoSlug);
|
|
1322
2093
|
}
|
|
1323
|
-
async function
|
|
1324
|
-
const idea = await
|
|
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
|
-
|
|
1329
|
-
idea.status = "published";
|
|
1330
|
-
await writeIdea(idea, dir);
|
|
2099
|
+
await markPublished(idea.issueNumber, record);
|
|
1331
2100
|
}
|
|
1332
|
-
async function matchIdeasToTranscript(transcript, ideas,
|
|
2101
|
+
async function matchIdeasToTranscript(transcript, ideas, _dir) {
|
|
1333
2102
|
try {
|
|
1334
|
-
const readyIdeas = (ideas ?? await
|
|
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 =
|
|
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
|
|
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
|
|
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,6 +2207,7 @@ init_environment();
|
|
|
1440
2207
|
init_paths();
|
|
1441
2208
|
init_fileSystem();
|
|
1442
2209
|
init_configLogger();
|
|
2210
|
+
init_types();
|
|
1443
2211
|
var FileWatcher = class _FileWatcher extends EventEmitter {
|
|
1444
2212
|
watchFolder;
|
|
1445
2213
|
watcher = null;
|
|
@@ -1469,8 +2237,8 @@ var FileWatcher = class _FileWatcher extends EventEmitter {
|
|
|
1469
2237
|
}
|
|
1470
2238
|
}
|
|
1471
2239
|
async handleDetectedFile(filePath) {
|
|
1472
|
-
if (extname(filePath).toLowerCase()
|
|
1473
|
-
logger_default.debug(`[watcher] Ignoring
|
|
2240
|
+
if (!isSupportedVideoExtension(extname(filePath).toLowerCase())) {
|
|
2241
|
+
logger_default.debug(`[watcher] Ignoring unsupported file: ${filePath}`);
|
|
1474
2242
|
return;
|
|
1475
2243
|
}
|
|
1476
2244
|
let fileSize;
|
|
@@ -1505,7 +2273,7 @@ var FileWatcher = class _FileWatcher extends EventEmitter {
|
|
|
1505
2273
|
throw err;
|
|
1506
2274
|
}
|
|
1507
2275
|
for (const file of files) {
|
|
1508
|
-
if (extname(file).toLowerCase()
|
|
2276
|
+
if (isSupportedVideoExtension(extname(file).toLowerCase())) {
|
|
1509
2277
|
const filePath = join(this.watchFolder, file);
|
|
1510
2278
|
this.handleDetectedFile(filePath).catch(
|
|
1511
2279
|
(err) => logger_default.error(`Error processing ${filePath}: ${err instanceof Error ? err.message : String(err)}`)
|
|
@@ -1535,7 +2303,7 @@ var FileWatcher = class _FileWatcher extends EventEmitter {
|
|
|
1535
2303
|
});
|
|
1536
2304
|
this.watcher.on("change", (filePath) => {
|
|
1537
2305
|
logger_default.debug(`[watcher] 'change' event: ${filePath}`);
|
|
1538
|
-
if (extname(filePath).toLowerCase()
|
|
2306
|
+
if (!isSupportedVideoExtension(extname(filePath).toLowerCase())) return;
|
|
1539
2307
|
logger_default.info(`Change detected on video file: ${filePath}`);
|
|
1540
2308
|
this.handleDetectedFile(filePath).catch(
|
|
1541
2309
|
(err) => logger_default.error(`Error processing ${filePath}: ${err instanceof Error ? err.message : String(err)}`)
|
|
@@ -1556,7 +2324,7 @@ var FileWatcher = class _FileWatcher extends EventEmitter {
|
|
|
1556
2324
|
this.scanExistingFiles();
|
|
1557
2325
|
}
|
|
1558
2326
|
});
|
|
1559
|
-
logger_default.info(`Watching for new
|
|
2327
|
+
logger_default.info(`Watching for new video files in: ${this.watchFolder}`);
|
|
1560
2328
|
}
|
|
1561
2329
|
stop() {
|
|
1562
2330
|
if (this.watcher) {
|
|
@@ -2880,6 +3648,41 @@ async function compositeOverlays(videoPath, overlays, outputPath, videoWidth, vi
|
|
|
2880
3648
|
});
|
|
2881
3649
|
}
|
|
2882
3650
|
|
|
3651
|
+
// src/L2-clients/ffmpeg/transcoding.ts
|
|
3652
|
+
init_fileSystem();
|
|
3653
|
+
init_paths();
|
|
3654
|
+
init_configLogger();
|
|
3655
|
+
function transcodeToMp4(inputPath, outputPath) {
|
|
3656
|
+
const outputDir = dirname(outputPath);
|
|
3657
|
+
return new Promise((resolve3, reject) => {
|
|
3658
|
+
ensureDirectory(outputDir).then(() => {
|
|
3659
|
+
logger_default.info(`Transcoding to MP4: ${inputPath} \u2192 ${outputPath}`);
|
|
3660
|
+
createFFmpeg(inputPath).outputOptions([
|
|
3661
|
+
"-c:v",
|
|
3662
|
+
"libx264",
|
|
3663
|
+
"-preset",
|
|
3664
|
+
"ultrafast",
|
|
3665
|
+
"-crf",
|
|
3666
|
+
"23",
|
|
3667
|
+
"-threads",
|
|
3668
|
+
"4",
|
|
3669
|
+
"-c:a",
|
|
3670
|
+
"aac",
|
|
3671
|
+
"-b:a",
|
|
3672
|
+
"128k",
|
|
3673
|
+
"-movflags",
|
|
3674
|
+
"+faststart"
|
|
3675
|
+
]).output(outputPath).on("end", () => {
|
|
3676
|
+
logger_default.info(`Transcoding complete: ${outputPath}`);
|
|
3677
|
+
resolve3(outputPath);
|
|
3678
|
+
}).on("error", (err) => {
|
|
3679
|
+
logger_default.error(`Transcoding failed: ${err.message}`);
|
|
3680
|
+
reject(new Error(`Transcoding to MP4 failed: ${err.message}`));
|
|
3681
|
+
}).run();
|
|
3682
|
+
}).catch(reject);
|
|
3683
|
+
});
|
|
3684
|
+
}
|
|
3685
|
+
|
|
2883
3686
|
// src/L3-services/videoOperations/videoOperations.ts
|
|
2884
3687
|
function ffprobe2(...args) {
|
|
2885
3688
|
return ffprobe(...args);
|
|
@@ -2917,6 +3720,9 @@ function getVideoResolution2(...args) {
|
|
|
2917
3720
|
function compositeOverlays2(...args) {
|
|
2918
3721
|
return compositeOverlays(...args);
|
|
2919
3722
|
}
|
|
3723
|
+
function transcodeToMp42(...args) {
|
|
3724
|
+
return transcodeToMp4(...args);
|
|
3725
|
+
}
|
|
2920
3726
|
|
|
2921
3727
|
// src/L0-pure/captions/captionGenerator.ts
|
|
2922
3728
|
function pad(n, width) {
|
|
@@ -4367,46 +5173,7 @@ var SocialPostAsset = class extends TextAsset {
|
|
|
4367
5173
|
// src/L5-assets/ShortVideoAsset.ts
|
|
4368
5174
|
init_paths();
|
|
4369
5175
|
init_fileSystem();
|
|
4370
|
-
|
|
4371
|
-
// src/L0-pure/types/index.ts
|
|
4372
|
-
var Platform = /* @__PURE__ */ ((Platform2) => {
|
|
4373
|
-
Platform2["TikTok"] = "tiktok";
|
|
4374
|
-
Platform2["YouTube"] = "youtube";
|
|
4375
|
-
Platform2["Instagram"] = "instagram";
|
|
4376
|
-
Platform2["LinkedIn"] = "linkedin";
|
|
4377
|
-
Platform2["X"] = "x";
|
|
4378
|
-
return Platform2;
|
|
4379
|
-
})(Platform || {});
|
|
4380
|
-
var PLATFORM_CHAR_LIMITS = {
|
|
4381
|
-
tiktok: 2200,
|
|
4382
|
-
youtube: 5e3,
|
|
4383
|
-
instagram: 2200,
|
|
4384
|
-
linkedin: 3e3,
|
|
4385
|
-
twitter: 280
|
|
4386
|
-
};
|
|
4387
|
-
function toLatePlatform(platform) {
|
|
4388
|
-
return platform === "x" /* X */ ? "twitter" : platform;
|
|
4389
|
-
}
|
|
4390
|
-
function fromLatePlatform(latePlatform) {
|
|
4391
|
-
const normalized = normalizePlatformString(latePlatform);
|
|
4392
|
-
if (normalized === "twitter") {
|
|
4393
|
-
return "x" /* X */;
|
|
4394
|
-
}
|
|
4395
|
-
const platformValues = Object.values(Platform);
|
|
4396
|
-
if (platformValues.includes(normalized)) {
|
|
4397
|
-
return normalized;
|
|
4398
|
-
}
|
|
4399
|
-
throw new Error(`Unsupported platform from Late API: ${latePlatform}`);
|
|
4400
|
-
}
|
|
4401
|
-
function normalizePlatformString(raw) {
|
|
4402
|
-
const lower = raw.toLowerCase().trim();
|
|
4403
|
-
if (lower === "x" || lower === "x (twitter)" || lower === "x/twitter") {
|
|
4404
|
-
return "twitter";
|
|
4405
|
-
}
|
|
4406
|
-
return lower;
|
|
4407
|
-
}
|
|
4408
|
-
|
|
4409
|
-
// src/L5-assets/ShortVideoAsset.ts
|
|
5176
|
+
init_types();
|
|
4410
5177
|
var ShortVideoAsset = class extends VideoAsset {
|
|
4411
5178
|
/** Reference to the source video this short was extracted from */
|
|
4412
5179
|
parent;
|
|
@@ -4539,6 +5306,7 @@ var ShortVideoAsset = class extends VideoAsset {
|
|
|
4539
5306
|
// src/L5-assets/MediumClipAsset.ts
|
|
4540
5307
|
init_paths();
|
|
4541
5308
|
init_fileSystem();
|
|
5309
|
+
init_types();
|
|
4542
5310
|
var MediumClipAsset = class extends VideoAsset {
|
|
4543
5311
|
/** Parent video this clip was extracted from */
|
|
4544
5312
|
parent;
|
|
@@ -6738,6 +7506,7 @@ init_fileSystem();
|
|
|
6738
7506
|
init_paths();
|
|
6739
7507
|
init_configLogger();
|
|
6740
7508
|
init_environment();
|
|
7509
|
+
init_types();
|
|
6741
7510
|
var SYSTEM_PROMPT5 = `You are a viral social-media content strategist.
|
|
6742
7511
|
Given a video transcript and summary you MUST generate one post for each of the 5 platforms listed below.
|
|
6743
7512
|
Each post must match the platform's tone, format, and constraints exactly.
|
|
@@ -7249,8 +8018,11 @@ async function commitAndPush(videoSlug, message) {
|
|
|
7249
8018
|
init_fileSystem();
|
|
7250
8019
|
init_paths();
|
|
7251
8020
|
init_configLogger();
|
|
8021
|
+
init_types();
|
|
8022
|
+
init_types();
|
|
7252
8023
|
|
|
7253
8024
|
// src/L3-services/socialPosting/platformContentStrategy.ts
|
|
8025
|
+
init_types();
|
|
7254
8026
|
var CONTENT_MATRIX = {
|
|
7255
8027
|
["youtube" /* YouTube */]: {
|
|
7256
8028
|
video: { captions: true, variantKey: null },
|
|
@@ -7283,6 +8055,7 @@ function platformAcceptsMedia(platform, clipType) {
|
|
|
7283
8055
|
}
|
|
7284
8056
|
|
|
7285
8057
|
// src/L3-services/postStore/postStore.ts
|
|
8058
|
+
init_types();
|
|
7286
8059
|
init_environment();
|
|
7287
8060
|
init_configLogger();
|
|
7288
8061
|
init_fileSystem();
|
|
@@ -7491,14 +8264,40 @@ async function approveItem(id, publishData) {
|
|
|
7491
8264
|
item.metadata.reviewedAt = now;
|
|
7492
8265
|
if (item.metadata.ideaIds && item.metadata.ideaIds.length > 0) {
|
|
7493
8266
|
try {
|
|
7494
|
-
const { markPublished:
|
|
7495
|
-
|
|
7496
|
-
|
|
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, {
|
|
7497
8295
|
clipType: item.metadata.clipType,
|
|
7498
8296
|
platform: fromLatePlatform(item.metadata.platform),
|
|
7499
8297
|
queueItemId: id,
|
|
7500
8298
|
publishedAt: now,
|
|
7501
|
-
|
|
8299
|
+
latePostId: item.metadata.latePostId ?? "",
|
|
8300
|
+
lateUrl: item.metadata.publishedUrl || (item.metadata.latePostId ? `https://app.late.co/dashboard/post/${item.metadata.latePostId}` : "")
|
|
7502
8301
|
});
|
|
7503
8302
|
}
|
|
7504
8303
|
} catch (err) {
|
|
@@ -8085,6 +8884,7 @@ async function enhanceVideo(videoPath, transcript, video) {
|
|
|
8085
8884
|
// src/L5-assets/MainVideoAsset.ts
|
|
8086
8885
|
init_environment();
|
|
8087
8886
|
init_configLogger();
|
|
8887
|
+
init_types();
|
|
8088
8888
|
var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
|
|
8089
8889
|
sourcePath;
|
|
8090
8890
|
videoDir;
|
|
@@ -8231,26 +9031,40 @@ var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
|
|
|
8231
9031
|
await ensureDirectory(socialPostsDir);
|
|
8232
9032
|
const destFilename = `${slug}.mp4`;
|
|
8233
9033
|
const destPath = join(videoDir, destFilename);
|
|
8234
|
-
|
|
9034
|
+
const sourceExt = extname(sourcePath).toLowerCase();
|
|
9035
|
+
const needsTranscode = sourceExt !== ".mp4";
|
|
9036
|
+
let needsIngest = true;
|
|
8235
9037
|
try {
|
|
8236
9038
|
const destStats = await getFileStats(destPath);
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
9039
|
+
if (needsTranscode) {
|
|
9040
|
+
if (destStats.size > 1024) {
|
|
9041
|
+
logger_default.info(`Transcoded MP4 already exists (${(destStats.size / 1024 / 1024).toFixed(1)} MB), skipping transcode`);
|
|
9042
|
+
needsIngest = false;
|
|
9043
|
+
}
|
|
9044
|
+
} else {
|
|
9045
|
+
const srcStats = await getFileStats(sourcePath);
|
|
9046
|
+
if (destStats.size === srcStats.size) {
|
|
9047
|
+
logger_default.info(`Video already copied (same size), skipping copy`);
|
|
9048
|
+
needsIngest = false;
|
|
9049
|
+
}
|
|
8241
9050
|
}
|
|
8242
9051
|
} catch {
|
|
8243
9052
|
}
|
|
8244
|
-
if (
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
9053
|
+
if (needsIngest) {
|
|
9054
|
+
if (needsTranscode) {
|
|
9055
|
+
await transcodeToMp42(sourcePath, destPath);
|
|
9056
|
+
logger_default.info(`Transcoded video to ${destPath}`);
|
|
9057
|
+
} else {
|
|
9058
|
+
await new Promise((resolve3, reject) => {
|
|
9059
|
+
const readStream = openReadStream(sourcePath);
|
|
9060
|
+
const writeStream = openWriteStream(destPath);
|
|
9061
|
+
readStream.on("error", reject);
|
|
9062
|
+
writeStream.on("error", reject);
|
|
9063
|
+
writeStream.on("finish", resolve3);
|
|
9064
|
+
readStream.pipe(writeStream);
|
|
9065
|
+
});
|
|
9066
|
+
logger_default.info(`Copied video to ${destPath}`);
|
|
9067
|
+
}
|
|
8254
9068
|
}
|
|
8255
9069
|
const asset = new _MainVideoAsset(sourcePath, videoDir, slug);
|
|
8256
9070
|
try {
|
|
@@ -8957,7 +9771,7 @@ var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
|
|
|
8957
9771
|
*/
|
|
8958
9772
|
async buildPublishQueueData(shorts, mediumClips, socialPosts, captionedVideoPath) {
|
|
8959
9773
|
const video = await this.toVideoFile();
|
|
8960
|
-
const ideaIds = this._ideas.length > 0 ? this._ideas.map((idea) => idea.
|
|
9774
|
+
const ideaIds = this._ideas.length > 0 ? this._ideas.map((idea) => String(idea.issueNumber)) : void 0;
|
|
8961
9775
|
return buildPublishQueue2(video, shorts, mediumClips, socialPosts, captionedVideoPath, ideaIds);
|
|
8962
9776
|
}
|
|
8963
9777
|
/**
|
|
@@ -10677,17 +11491,26 @@ var ScheduleAgent = class extends BaseAgent {
|
|
|
10677
11491
|
init_fileSystem();
|
|
10678
11492
|
init_environment();
|
|
10679
11493
|
init_modelConfig();
|
|
10680
|
-
init_ideaStore();
|
|
10681
11494
|
init_configLogger();
|
|
11495
|
+
init_ideaService();
|
|
10682
11496
|
init_providerFactory();
|
|
11497
|
+
init_types();
|
|
10683
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.
|
|
10684
11499
|
|
|
10685
11500
|
## CRITICAL: Research Before Creating
|
|
10686
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.
|
|
10687
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
|
+
|
|
10688
11511
|
## Your Research Process
|
|
10689
11512
|
1. Load the brand context (get_brand_context) to understand the creator's voice, expertise, and content pillars.
|
|
10690
|
-
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.
|
|
10691
11514
|
3. **RESEARCH PHASE** \u2014 This is the most important step. Use the available MCP tools:
|
|
10692
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.
|
|
10693
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.
|
|
@@ -10713,15 +11536,27 @@ Every idea must:
|
|
|
10713
11536
|
- Written (LinkedIn, X/Twitter): Thought leadership, hot takes, thread-worthy
|
|
10714
11537
|
|
|
10715
11538
|
Generate 3-5 high-quality ideas. Quality over quantity. Every idea must be backed by research.`;
|
|
10716
|
-
var SUPPORTED_PLATFORMS = [
|
|
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"];
|
|
10717
11548
|
var MIN_IDEA_COUNT = 3;
|
|
10718
11549
|
var MAX_IDEA_COUNT = 5;
|
|
10719
|
-
|
|
11550
|
+
var DEFAULT_EXISTING_IDEA_LIMIT = 50;
|
|
11551
|
+
function isRecord(value) {
|
|
10720
11552
|
return typeof value === "object" && value !== null;
|
|
10721
11553
|
}
|
|
10722
|
-
function
|
|
11554
|
+
function isStringArray(value) {
|
|
10723
11555
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
10724
11556
|
}
|
|
11557
|
+
function hasField(source, field) {
|
|
11558
|
+
return Object.prototype.hasOwnProperty.call(source, field);
|
|
11559
|
+
}
|
|
10725
11560
|
function normalizeCount(count) {
|
|
10726
11561
|
if (typeof count !== "number" || Number.isNaN(count)) {
|
|
10727
11562
|
return MIN_IDEA_COUNT;
|
|
@@ -10734,7 +11569,7 @@ function normalizeSeedTopics(seedTopics) {
|
|
|
10734
11569
|
}
|
|
10735
11570
|
function extractStringArrayField(source, field) {
|
|
10736
11571
|
const value = source[field];
|
|
10737
|
-
return
|
|
11572
|
+
return isStringArray(value) ? value : [];
|
|
10738
11573
|
}
|
|
10739
11574
|
function extractContentPillars(brand) {
|
|
10740
11575
|
const raw = brand.contentPillars;
|
|
@@ -10746,7 +11581,7 @@ function extractContentPillars(brand) {
|
|
|
10746
11581
|
const pillar2 = entry.trim();
|
|
10747
11582
|
return pillar2 ? [{ pillar: pillar2 }] : [];
|
|
10748
11583
|
}
|
|
10749
|
-
if (!
|
|
11584
|
+
if (!isRecord(entry)) {
|
|
10750
11585
|
return [];
|
|
10751
11586
|
}
|
|
10752
11587
|
const pillar = typeof entry.pillar === "string" ? entry.pillar.trim() : "";
|
|
@@ -10755,21 +11590,22 @@ function extractContentPillars(brand) {
|
|
|
10755
11590
|
}
|
|
10756
11591
|
const description = typeof entry.description === "string" ? entry.description.trim() : void 0;
|
|
10757
11592
|
const frequency = typeof entry.frequency === "string" ? entry.frequency.trim() : void 0;
|
|
10758
|
-
const formats =
|
|
11593
|
+
const formats = isStringArray(entry.formats) ? entry.formats.map((format) => format.trim()).filter((format) => format.length > 0) : void 0;
|
|
10759
11594
|
return [{ pillar, description, frequency, formats }];
|
|
10760
11595
|
});
|
|
10761
11596
|
}
|
|
10762
11597
|
function summarizeExistingIdeas(ideas) {
|
|
10763
11598
|
if (ideas.length === 0) {
|
|
10764
|
-
return "No existing ideas found in the
|
|
11599
|
+
return "No existing GitHub issue ideas found in the repository.";
|
|
10765
11600
|
}
|
|
10766
|
-
return ideas.slice(0, 25).map((idea) => `-
|
|
11601
|
+
return ideas.slice(0, 25).map((idea) => `- #${idea.issueNumber}: ${idea.topic} [${idea.status}] (${idea.issueUrl})`).join("\n");
|
|
10767
11602
|
}
|
|
10768
11603
|
function buildPlatformGuidance() {
|
|
10769
11604
|
return [
|
|
10770
11605
|
`Allowed platforms for create_idea: ${SUPPORTED_PLATFORMS.join(", ")}`,
|
|
10771
11606
|
`Create between ${MIN_IDEA_COUNT} and ${MAX_IDEA_COUNT} ideas unless the user explicitly requests fewer within that range.`,
|
|
10772
|
-
"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."
|
|
10773
11609
|
].join("\n");
|
|
10774
11610
|
}
|
|
10775
11611
|
function buildBrandPromptSection(brand) {
|
|
@@ -10803,20 +11639,33 @@ function buildBrandPromptSection(brand) {
|
|
|
10803
11639
|
lines.push("Content pillars:");
|
|
10804
11640
|
lines.push(
|
|
10805
11641
|
...contentPillars.map((pillar) => {
|
|
10806
|
-
const details = [
|
|
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(" | ");
|
|
10807
11647
|
return details ? `- ${pillar.pillar}: ${details}` : `- ${pillar.pillar}`;
|
|
10808
11648
|
})
|
|
10809
11649
|
);
|
|
10810
11650
|
}
|
|
10811
11651
|
return lines.join("\n");
|
|
10812
11652
|
}
|
|
10813
|
-
function
|
|
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) {
|
|
10814
11661
|
const promptSections = [
|
|
10815
11662
|
BASE_SYSTEM_PROMPT,
|
|
10816
11663
|
"",
|
|
11664
|
+
buildIdeaRepoPromptSection(ideaRepo),
|
|
11665
|
+
"",
|
|
10817
11666
|
buildBrandPromptSection(brand),
|
|
10818
11667
|
"",
|
|
10819
|
-
"## Existing Idea
|
|
11668
|
+
"## Existing Idea Issues",
|
|
10820
11669
|
summarizeExistingIdeas(existingIdeas),
|
|
10821
11670
|
"",
|
|
10822
11671
|
"## Planning Constraints",
|
|
@@ -10832,7 +11681,7 @@ function buildUserMessage(count, seedTopics, hasMcpServers) {
|
|
|
10832
11681
|
const focusText = seedTopics.length > 0 ? `Focus areas: ${seedTopics.join(", ")}` : "Focus areas: choose the strongest timely opportunities from the creator context and current trends.";
|
|
10833
11682
|
const steps = [
|
|
10834
11683
|
"1. Call get_brand_context to load the creator profile.",
|
|
10835
|
-
"2. Call get_past_ideas to
|
|
11684
|
+
"2. Call get_past_ideas (and search_ideas if needed) to inspect existing GitHub issue ideas before proposing anything new."
|
|
10836
11685
|
];
|
|
10837
11686
|
if (hasMcpServers) {
|
|
10838
11687
|
steps.push(
|
|
@@ -10842,12 +11691,14 @@ function buildUserMessage(count, seedTopics, hasMcpServers) {
|
|
|
10842
11691
|
" - Use perplexity-search to get current analysis on promising topics.",
|
|
10843
11692
|
" Do at least 2-3 research queries. Each idea you create MUST reference specific findings from this research in its trendContext field.",
|
|
10844
11693
|
`4. Call create_idea for each of the ${count} ideas, grounding each in your research findings.`,
|
|
10845
|
-
"5.
|
|
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."
|
|
10846
11696
|
);
|
|
10847
11697
|
} else {
|
|
10848
11698
|
steps.push(
|
|
10849
11699
|
`3. Call create_idea for each of the ${count} ideas.`,
|
|
10850
|
-
"4.
|
|
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."
|
|
10851
11702
|
);
|
|
10852
11703
|
}
|
|
10853
11704
|
return [
|
|
@@ -10864,50 +11715,181 @@ async function loadBrandContext(brandPath) {
|
|
|
10864
11715
|
}
|
|
10865
11716
|
return readJsonFile(brandPath);
|
|
10866
11717
|
}
|
|
10867
|
-
function
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
|
|
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`);
|
|
10872
11725
|
}
|
|
10873
11726
|
return normalized;
|
|
10874
11727
|
}
|
|
10875
|
-
function
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
11728
|
+
function normalizeOptionalString(value, field) {
|
|
11729
|
+
if (value === void 0) {
|
|
11730
|
+
return void 0;
|
|
11731
|
+
}
|
|
11732
|
+
if (typeof value !== "string") {
|
|
11733
|
+
throw new Error(`Invalid ${field}: expected string`);
|
|
11734
|
+
}
|
|
11735
|
+
const normalized = value.trim();
|
|
11736
|
+
return normalized || void 0;
|
|
11737
|
+
}
|
|
11738
|
+
function normalizeStringList(value, field) {
|
|
11739
|
+
if (!isStringArray(value)) {
|
|
11740
|
+
throw new Error(`Invalid ${field}: expected string[]`);
|
|
10879
11741
|
}
|
|
10880
|
-
return
|
|
11742
|
+
return value.map((item) => item.trim()).filter((item) => item.length > 0);
|
|
10881
11743
|
}
|
|
10882
|
-
function
|
|
10883
|
-
|
|
10884
|
-
|
|
10885
|
-
if (args.hook.trim().length > 80) {
|
|
10886
|
-
throw new Error(`Idea hook must be 80 characters or fewer: ${args.id}`);
|
|
11744
|
+
function normalizeIssueNumber(value) {
|
|
11745
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
11746
|
+
throw new Error("Invalid issueNumber: expected a positive integer");
|
|
10887
11747
|
}
|
|
11748
|
+
return value;
|
|
11749
|
+
}
|
|
11750
|
+
function normalizePublishBy(value, field = "publishBy") {
|
|
11751
|
+
const publishBy = normalizeRequiredString(value, field);
|
|
10888
11752
|
if (Number.isNaN(new Date(publishBy).getTime())) {
|
|
10889
|
-
throw new Error(`Invalid
|
|
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}`);
|
|
10890
11776
|
}
|
|
10891
11777
|
return {
|
|
10892
|
-
|
|
10893
|
-
|
|
10894
|
-
|
|
10895
|
-
|
|
10896
|
-
|
|
10897
|
-
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"),
|
|
10898
11783
|
platforms: normalizePlatforms(args.platforms),
|
|
10899
|
-
|
|
10900
|
-
|
|
10901
|
-
trendContext: args.trendContext
|
|
10902
|
-
createdAt: now,
|
|
10903
|
-
updatedAt: now,
|
|
10904
|
-
publishBy
|
|
11784
|
+
tags: normalizeStringList(args.tags, "tags"),
|
|
11785
|
+
publishBy: normalizePublishBy(args.publishBy),
|
|
11786
|
+
trendContext: normalizeOptionalString(args.trendContext, "trendContext")
|
|
10905
11787
|
};
|
|
10906
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)
|
|
11860
|
+
};
|
|
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
|
+
}
|
|
10907
11889
|
var IdeationAgent = class extends BaseAgent {
|
|
10908
11890
|
brandContext;
|
|
10909
11891
|
existingIdeas;
|
|
10910
|
-
|
|
11892
|
+
ideaRepo;
|
|
10911
11893
|
targetCount;
|
|
10912
11894
|
generatedIdeas = [];
|
|
10913
11895
|
finalized = false;
|
|
@@ -10915,7 +11897,7 @@ var IdeationAgent = class extends BaseAgent {
|
|
|
10915
11897
|
super("IdeationAgent", systemPrompt, getProvider2(), model ?? getModelForAgent("IdeationAgent"));
|
|
10916
11898
|
this.brandContext = context.brandContext;
|
|
10917
11899
|
this.existingIdeas = [...context.existingIdeas];
|
|
10918
|
-
this.
|
|
11900
|
+
this.ideaRepo = context.ideaRepo;
|
|
10919
11901
|
this.targetCount = context.targetCount;
|
|
10920
11902
|
}
|
|
10921
11903
|
resetForRetry() {
|
|
@@ -10966,20 +11948,25 @@ var IdeationAgent = class extends BaseAgent {
|
|
|
10966
11948
|
},
|
|
10967
11949
|
{
|
|
10968
11950
|
name: "get_past_ideas",
|
|
10969
|
-
description: "
|
|
11951
|
+
description: "List GitHub-backed ideas with optional status, platform, tag, priority, or limit filters.",
|
|
10970
11952
|
parameters: {
|
|
10971
11953
|
type: "object",
|
|
10972
|
-
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
|
+
}
|
|
10973
11961
|
},
|
|
10974
11962
|
handler: async (args) => this.handleToolCall("get_past_ideas", args)
|
|
10975
11963
|
},
|
|
10976
11964
|
{
|
|
10977
11965
|
name: "create_idea",
|
|
10978
|
-
description:
|
|
11966
|
+
description: `Create a new draft GitHub Issue in ${this.ideaRepo} using the full idea schema.`,
|
|
10979
11967
|
parameters: {
|
|
10980
11968
|
type: "object",
|
|
10981
11969
|
properties: {
|
|
10982
|
-
id: { type: "string", description: "Kebab-case idea identifier" },
|
|
10983
11970
|
topic: { type: "string", description: "Main topic or title" },
|
|
10984
11971
|
hook: { type: "string", description: "Attention-grabbing hook (80 chars max)" },
|
|
10985
11972
|
audience: { type: "string", description: "Target audience" },
|
|
@@ -11011,10 +11998,98 @@ var IdeationAgent = class extends BaseAgent {
|
|
|
11011
11998
|
description: "Why this idea is timely right now"
|
|
11012
11999
|
}
|
|
11013
12000
|
},
|
|
11014
|
-
required: ["
|
|
12001
|
+
required: ["topic", "hook", "audience", "keyTakeaway", "talkingPoints", "platforms", "tags", "publishBy"]
|
|
11015
12002
|
},
|
|
11016
12003
|
handler: async (args) => this.handleToolCall("create_idea", args)
|
|
11017
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
|
+
},
|
|
11018
12093
|
{
|
|
11019
12094
|
name: "finalize_ideas",
|
|
11020
12095
|
description: "Signal that idea generation is complete.",
|
|
@@ -11030,16 +12105,18 @@ var IdeationAgent = class extends BaseAgent {
|
|
|
11030
12105
|
switch (toolName) {
|
|
11031
12106
|
case "get_brand_context":
|
|
11032
12107
|
return this.brandContext ?? await Promise.resolve(getBrandConfig());
|
|
11033
|
-
case "get_past_ideas":
|
|
11034
|
-
|
|
11035
|
-
return ideas.map((idea) => ({
|
|
11036
|
-
id: idea.id,
|
|
11037
|
-
topic: idea.topic,
|
|
11038
|
-
status: idea.status
|
|
11039
|
-
}));
|
|
11040
|
-
}
|
|
12108
|
+
case "get_past_ideas":
|
|
12109
|
+
return await listIdeas(parseIdeaFilters(args));
|
|
11041
12110
|
case "create_idea":
|
|
11042
|
-
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);
|
|
11043
12120
|
case "finalize_ideas":
|
|
11044
12121
|
this.finalized = true;
|
|
11045
12122
|
return { success: true, count: this.generatedIdeas.length };
|
|
@@ -11051,43 +12128,70 @@ var IdeationAgent = class extends BaseAgent {
|
|
|
11051
12128
|
if (this.generatedIdeas.length >= this.targetCount) {
|
|
11052
12129
|
throw new Error(`Target idea count already reached (${this.targetCount})`);
|
|
11053
12130
|
}
|
|
11054
|
-
const
|
|
11055
|
-
const
|
|
11056
|
-
const duplicateTopic = this.findDuplicateTopic(idea.topic);
|
|
12131
|
+
const input = parseCreateIdeaInput(args);
|
|
12132
|
+
const duplicateTopic = this.findDuplicateTopic(input.topic);
|
|
11057
12133
|
if (duplicateTopic) {
|
|
11058
12134
|
throw new Error(`Duplicate idea topic detected: ${duplicateTopic}`);
|
|
11059
12135
|
}
|
|
11060
|
-
const
|
|
11061
|
-
|
|
11062
|
-
|
|
11063
|
-
}
|
|
11064
|
-
await writeIdea(idea, this.ideasDir);
|
|
11065
|
-
this.generatedIdeas.push(idea);
|
|
11066
|
-
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}`);
|
|
11067
12140
|
return { success: true, idea };
|
|
11068
12141
|
}
|
|
11069
|
-
|
|
11070
|
-
const
|
|
11071
|
-
|
|
11072
|
-
|
|
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}`);
|
|
11073
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
|
+
);
|
|
11074
12177
|
return {
|
|
11075
|
-
|
|
11076
|
-
|
|
11077
|
-
hook,
|
|
11078
|
-
audience,
|
|
11079
|
-
keyTakeaway,
|
|
11080
|
-
talkingPoints,
|
|
11081
|
-
platforms,
|
|
11082
|
-
tags,
|
|
11083
|
-
publishBy,
|
|
11084
|
-
trendContext
|
|
12178
|
+
success: true,
|
|
12179
|
+
items: organizedItems
|
|
11085
12180
|
};
|
|
11086
12181
|
}
|
|
11087
|
-
|
|
11088
|
-
const
|
|
11089
|
-
|
|
11090
|
-
|
|
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
|
+
}
|
|
11091
12195
|
}
|
|
11092
12196
|
findDuplicateTopic(topic) {
|
|
11093
12197
|
const normalizedTopic = topic.trim().toLowerCase();
|
|
@@ -11110,12 +12214,12 @@ async function generateIdeas(options = {}) {
|
|
|
11110
12214
|
config2.BRAND_PATH = options.brandPath;
|
|
11111
12215
|
}
|
|
11112
12216
|
const brandContext = await loadBrandContext(options.brandPath);
|
|
11113
|
-
const existingIdeas = await
|
|
11114
|
-
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);
|
|
11115
12219
|
const agent = new IdeationAgent(systemPrompt, {
|
|
11116
12220
|
brandContext,
|
|
11117
12221
|
existingIdeas,
|
|
11118
|
-
|
|
12222
|
+
ideaRepo: config2.IDEAS_REPO,
|
|
11119
12223
|
targetCount: count
|
|
11120
12224
|
});
|
|
11121
12225
|
try {
|
|
@@ -11161,6 +12265,7 @@ function createScheduleAgent(...args) {
|
|
|
11161
12265
|
}
|
|
11162
12266
|
|
|
11163
12267
|
// src/L6-pipeline/pipeline.ts
|
|
12268
|
+
init_types();
|
|
11164
12269
|
async function runStage(stageName, fn, stageResults) {
|
|
11165
12270
|
const start = Date.now();
|
|
11166
12271
|
try {
|
|
@@ -12048,7 +13153,7 @@ Type \x1B[33mexit\x1B[0m or \x1B[33mquit\x1B[0m to leave. Press Ctrl+C to stop.
|
|
|
12048
13153
|
|
|
12049
13154
|
// src/L7-app/commands/ideate.ts
|
|
12050
13155
|
init_environment();
|
|
12051
|
-
|
|
13156
|
+
init_ideaService();
|
|
12052
13157
|
|
|
12053
13158
|
// src/L6-pipeline/ideation.ts
|
|
12054
13159
|
function generateIdeas3(...args) {
|
|
@@ -12059,8 +13164,8 @@ function generateIdeas3(...args) {
|
|
|
12059
13164
|
async function runIdeate(options = {}) {
|
|
12060
13165
|
initConfig();
|
|
12061
13166
|
if (options.list) {
|
|
12062
|
-
const ideas2 = await
|
|
12063
|
-
const filtered = options.status ? ideas2.filter((
|
|
13167
|
+
const ideas2 = await listIdeas();
|
|
13168
|
+
const filtered = options.status ? ideas2.filter((idea) => idea.status === options.status) : ideas2;
|
|
12064
13169
|
if (filtered.length === 0) {
|
|
12065
13170
|
console.log("No ideas found.");
|
|
12066
13171
|
if (options.status) {
|
|
@@ -12110,9 +13215,9 @@ ${filtered.length} idea(s) total`);
|
|
|
12110
13215
|
console.log(` Status: ${idea.status}`);
|
|
12111
13216
|
console.log("");
|
|
12112
13217
|
}
|
|
12113
|
-
console.log("Ideas saved to
|
|
13218
|
+
console.log("Ideas saved to the GitHub-backed idea service.");
|
|
12114
13219
|
console.log("Use `vidpipe ideate --list` to view all ideas.");
|
|
12115
|
-
console.log("Use `vidpipe process video.mp4 --ideas <
|
|
13220
|
+
console.log("Use `vidpipe process video.mp4 --ideas <issueNumber1>,<issueNumber2>` to link ideas to a recording.");
|
|
12116
13221
|
}
|
|
12117
13222
|
|
|
12118
13223
|
// src/L1-infra/http/http.ts
|
|
@@ -12123,14 +13228,16 @@ import { Router } from "express";
|
|
|
12123
13228
|
init_paths();
|
|
12124
13229
|
|
|
12125
13230
|
// src/L7-app/review/routes.ts
|
|
12126
|
-
|
|
13231
|
+
init_ideaService2();
|
|
13232
|
+
init_types();
|
|
12127
13233
|
init_configLogger();
|
|
12128
13234
|
|
|
12129
13235
|
// src/L7-app/review/approvalQueue.ts
|
|
12130
13236
|
init_fileSystem();
|
|
12131
|
-
|
|
13237
|
+
init_ideaService2();
|
|
12132
13238
|
|
|
12133
13239
|
// src/L3-services/socialPosting/accountMapping.ts
|
|
13240
|
+
init_types();
|
|
12134
13241
|
init_configLogger();
|
|
12135
13242
|
init_fileSystem();
|
|
12136
13243
|
init_paths();
|
|
@@ -12234,6 +13341,7 @@ async function getAccountId(platform) {
|
|
|
12234
13341
|
}
|
|
12235
13342
|
|
|
12236
13343
|
// src/L7-app/review/approvalQueue.ts
|
|
13344
|
+
init_types();
|
|
12237
13345
|
init_configLogger();
|
|
12238
13346
|
var queue = [];
|
|
12239
13347
|
var processing = false;
|
|
@@ -12273,20 +13381,32 @@ async function processApprovalBatch(itemIds) {
|
|
|
12273
13381
|
itemIds.map(async (id) => ({ id, item: await getItem(id) }))
|
|
12274
13382
|
);
|
|
12275
13383
|
const itemMap = new Map(loadedItems.map(({ id, item }) => [id, item]));
|
|
12276
|
-
const
|
|
12277
|
-
|
|
12278
|
-
|
|
12279
|
-
|
|
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);
|
|
12280
13389
|
}
|
|
12281
|
-
|
|
12282
|
-
|
|
12283
|
-
|
|
12284
|
-
|
|
12285
|
-
|
|
12286
|
-
|
|
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);
|
|
12287
13399
|
}
|
|
12288
|
-
}
|
|
12289
|
-
|
|
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
|
+
});
|
|
12290
13410
|
const now = Date.now();
|
|
12291
13411
|
const sevenDays = 7 * 24 * 60 * 60 * 1e3;
|
|
12292
13412
|
enriched.sort((a, b) => {
|
|
@@ -12424,7 +13544,31 @@ async function enrichQueueItem(item) {
|
|
|
12424
13544
|
};
|
|
12425
13545
|
}
|
|
12426
13546
|
async function enrichQueueItems(items) {
|
|
12427
|
-
|
|
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
|
+
});
|
|
12428
13572
|
}
|
|
12429
13573
|
async function enrichGroupedQueueItems(groups) {
|
|
12430
13574
|
return Promise.all(groups.map(async (group) => ({
|
|
@@ -12750,7 +13894,7 @@ var defaultCmd = program.command("process", { isDefault: true }).argument("[vide
|
|
|
12750
13894
|
logger_default.info(`Output dir: ${config2.OUTPUT_DIR}`);
|
|
12751
13895
|
let ideas;
|
|
12752
13896
|
if (opts.ideas) {
|
|
12753
|
-
const { getIdeasByIds: getIdeasByIds2 } = await Promise.resolve().then(() => (
|
|
13897
|
+
const { getIdeasByIds: getIdeasByIds2 } = await Promise.resolve().then(() => (init_ideaService2(), ideaService_exports2));
|
|
12754
13898
|
const ideaIds = opts.ideas.split(",").map((id) => id.trim()).filter(Boolean);
|
|
12755
13899
|
try {
|
|
12756
13900
|
ideas = await getIdeasByIds2(ideaIds);
|
|
@@ -12767,10 +13911,10 @@ var defaultCmd = program.command("process", { isDefault: true }).argument("[vide
|
|
|
12767
13911
|
await processVideoSafe(resolvedPath, ideas);
|
|
12768
13912
|
if (ideas && ideas.length > 0) {
|
|
12769
13913
|
try {
|
|
12770
|
-
const { markRecorded:
|
|
13914
|
+
const { markRecorded: markRecorded3 } = await Promise.resolve().then(() => (init_ideaService(), ideaService_exports));
|
|
12771
13915
|
const slug = resolvedPath.replace(/\\/g, "/").split("/").pop()?.replace(/\.(mp4|mov|webm|avi|mkv)$/i, "") || "";
|
|
12772
13916
|
for (const idea of ideas) {
|
|
12773
|
-
await
|
|
13917
|
+
await markRecorded3(idea.issueNumber, slug);
|
|
12774
13918
|
}
|
|
12775
13919
|
logger_default.info(`Marked ${ideas.length} idea(s) as recorded`);
|
|
12776
13920
|
} catch (err) {
|