peak6-x-publishing-plugin 0.2.1 → 0.2.3

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/worker.js CHANGED
@@ -965,10 +965,10 @@ async function setTokens(ctx, handle, tokens) {
965
965
  tokens
966
966
  );
967
967
  }
968
- async function refreshTokens(ctx, config, handle) {
968
+ async function refreshTokens(ctx, config2, handle) {
969
969
  const current = await getTokens(ctx, handle);
970
- const clientId = await ctx.secrets.resolve(config.oauth_client_id_ref);
971
- const clientSecret = await ctx.secrets.resolve(config.oauth_client_secret_ref);
970
+ const clientId = await ctx.secrets.resolve(config2.oauth_client_id_ref);
971
+ const clientSecret = await ctx.secrets.resolve(config2.oauth_client_secret_ref);
972
972
  const resp = await ctx.http.fetch("https://api.x.com/2/oauth2/token", {
973
973
  method: "POST",
974
974
  headers: {
@@ -993,10 +993,10 @@ async function refreshTokens(ctx, config, handle) {
993
993
  await setTokens(ctx, handle, newTokens);
994
994
  return newTokens;
995
995
  }
996
- async function getValidAccessToken(ctx, config, handle) {
996
+ async function getValidAccessToken(ctx, config2, handle) {
997
997
  const tokens = await getTokens(ctx, handle);
998
998
  if (tokens.expires_at - Date.now() < REFRESH_BUFFER_MS) {
999
- const refreshed = await refreshTokens(ctx, config, handle);
999
+ const refreshed = await refreshTokens(ctx, config2, handle);
1000
1000
  return refreshed.access_token;
1001
1001
  }
1002
1002
  return tokens.access_token;
@@ -1132,7 +1132,7 @@ async function updateRateLimits(ctx, handle, endpoint, headers) {
1132
1132
  }
1133
1133
 
1134
1134
  // src/pipeline/approval-gate.ts
1135
- async function checkApproval(ctx, config, contentType, draftId, approvalModes) {
1135
+ async function checkApproval(ctx, config2, contentType, draftId, approvalModes) {
1136
1136
  const modes = approvalModes || DEFAULT_APPROVAL_MODES;
1137
1137
  const mode = modes[contentType];
1138
1138
  if (mode === "none") {
@@ -1144,14 +1144,14 @@ async function checkApproval(ctx, config, contentType, draftId, approvalModes) {
1144
1144
  }
1145
1145
  if (!draftId) {
1146
1146
  const issue = await ctx.issues.create({
1147
- companyId: config.company_id,
1147
+ companyId: config2.company_id,
1148
1148
  title: `Approval required for ${contentType}`,
1149
1149
  description: `Content type "${contentType}" requires approval but no draft was provided for review.
1150
1150
 
1151
1151
  Create a draft first, then have it approved before publishing.`
1152
1152
  });
1153
- await ctx.issues.update(issue.id, { status: "todo" }, config.company_id);
1154
- await ctx.events.emit(EVENT_NAMES.approvalRequired, config.company_id, {
1153
+ await ctx.issues.update(issue.id, { status: "todo" }, config2.company_id);
1154
+ await ctx.events.emit(EVENT_NAMES.approvalRequired, config2.company_id, {
1155
1155
  content_type: contentType,
1156
1156
  reason: "No draft provided for required approval"
1157
1157
  });
@@ -1169,7 +1169,7 @@ Create a draft first, then have it approved before publishing.`
1169
1169
  if (draft.status !== "in_review") {
1170
1170
  draft.status = "in_review";
1171
1171
  const issue = await ctx.issues.create({
1172
- companyId: config.company_id,
1172
+ companyId: config2.company_id,
1173
1173
  title: `Review draft: ${draft.text.slice(0, 60)}`,
1174
1174
  description: `Draft ID: ${draftId}
1175
1175
  Format: ${draft.format}
@@ -1177,7 +1177,7 @@ Format: ${draft.format}
1177
1177
  Text:
1178
1178
  ${draft.text}${draft.thread_tweets ? "\n\nThread:\n" + draft.thread_tweets.join("\n---\n") : ""}`
1179
1179
  });
1180
- await ctx.issues.update(issue.id, { status: "todo" }, config.company_id);
1180
+ await ctx.issues.update(issue.id, { status: "todo" }, config2.company_id);
1181
1181
  draft.review_issue_id = issue.id;
1182
1182
  await ctx.entities.upsert({
1183
1183
  entityType: ENTITY_TYPES.draft,
@@ -1186,7 +1186,7 @@ ${draft.text}${draft.thread_tweets ? "\n\nThread:\n" + draft.thread_tweets.join(
1186
1186
  title: entity.title ?? draft.text.slice(0, 60),
1187
1187
  data: draft
1188
1188
  });
1189
- await ctx.events.emit(EVENT_NAMES.approvalRequired, config.company_id, {
1189
+ await ctx.events.emit(EVENT_NAMES.approvalRequired, config2.company_id, {
1190
1190
  draft_id: draftId,
1191
1191
  content_type: contentType,
1192
1192
  reason: "Draft submitted for review"
@@ -1197,7 +1197,7 @@ ${draft.text}${draft.thread_tweets ? "\n\nThread:\n" + draft.thread_tweets.join(
1197
1197
 
1198
1198
  // src/pipeline/thread-publisher.ts
1199
1199
  var MAX_RETRIES = 3;
1200
- async function publishThread(ctx, token, tweets, metadata, companyId) {
1200
+ async function publishThread(ctx, token, handle, tweets, metadata, companyId) {
1201
1201
  const tweetIds = [];
1202
1202
  let replyTo;
1203
1203
  for (let i = 0; i < tweets.length; i++) {
@@ -1209,11 +1209,12 @@ async function publishThread(ctx, token, tweets, metadata, companyId) {
1209
1209
  text,
1210
1210
  reply_to: replyTo
1211
1211
  });
1212
- await updateRateLimits(ctx, "post_tweets", result.rateLimit);
1212
+ await updateRateLimits(ctx, handle, "post_tweets", result.rateLimit);
1213
1213
  tweetIds.push(result.data.id);
1214
1214
  replyTo = result.data.id;
1215
1215
  const published = {
1216
1216
  tweet_id: result.data.id,
1217
+ account: handle,
1217
1218
  text: result.data.text,
1218
1219
  published_at: (/* @__PURE__ */ new Date()).toISOString(),
1219
1220
  format: "thread",
@@ -1263,13 +1264,13 @@ async function getConfig(ctx) {
1263
1264
  const raw = await ctx.config.get();
1264
1265
  return { ...DEFAULT_CONFIG, ...raw };
1265
1266
  }
1266
- function resolveAccount(config, handle) {
1267
- const accounts = config.accounts || {};
1267
+ function resolveAccount(config2, handle) {
1268
+ const accounts = config2.accounts || {};
1268
1269
  const handles = Object.keys(accounts);
1269
1270
  if (handles.length === 0) {
1270
1271
  return { ok: false, error: "No accounts configured." };
1271
1272
  }
1272
- const target = handle || config.default_account || handles[0];
1273
+ const target = handle || config2.default_account || handles[0];
1273
1274
  const account = accounts[target];
1274
1275
  if (!account) {
1275
1276
  return { ok: false, error: `Account @${target} not found in config. Available: ${handles.join(", ")}` };
@@ -1284,8 +1285,8 @@ function findEntityById(entities, id) {
1284
1285
  async function handleSetupOauth(ctx, params) {
1285
1286
  const handle = params.account;
1286
1287
  if (!handle) return { error: "account param is required (X handle to store tokens for)." };
1287
- const config = await getConfig(ctx);
1288
- const resolved = resolveAccount(config, handle);
1288
+ const config2 = await getConfig(ctx);
1289
+ const resolved = resolveAccount(config2, handle);
1289
1290
  if (!resolved.ok) return { error: resolved.error };
1290
1291
  const accessToken = params.access_token;
1291
1292
  const refreshToken = params.refresh_token;
@@ -1303,12 +1304,13 @@ async function handleDraftPost(ctx, params) {
1303
1304
  if (text.length > 280) {
1304
1305
  return { error: `Tweet text exceeds 280 characters (${text.length}).` };
1305
1306
  }
1306
- const config = await getConfig(ctx);
1307
+ const config2 = await getConfig(ctx);
1307
1308
  const format = params.format || "single";
1308
1309
  const metadata = params.metadata || {};
1309
1310
  const draft = {
1310
1311
  text,
1311
1312
  format,
1313
+ account: params.account || config2.default_account || void 0,
1312
1314
  reply_to_tweet_id: params.reply_to_tweet_id,
1313
1315
  quote_tweet_id: params.quote_tweet_id,
1314
1316
  schedule_at: params.schedule_at,
@@ -1321,7 +1323,7 @@ async function handleDraftPost(ctx, params) {
1321
1323
  title: text.slice(0, 60),
1322
1324
  data: draft
1323
1325
  });
1324
- await ctx.events.emit(EVENT_NAMES.draftCreated, config.company_id, {
1326
+ await ctx.events.emit(EVENT_NAMES.draftCreated, config2.company_id, {
1325
1327
  draft_id: entity.id,
1326
1328
  format,
1327
1329
  metadata
@@ -1338,11 +1340,12 @@ async function handleDraftThread(ctx, params) {
1338
1340
  return { error: `Tweet ${i + 1} exceeds 280 characters (${threadTweets[i].length}).` };
1339
1341
  }
1340
1342
  }
1341
- const config = await getConfig(ctx);
1343
+ const config2 = await getConfig(ctx);
1342
1344
  const metadata = params.metadata || {};
1343
1345
  const draft = {
1344
1346
  text: threadTweets[0],
1345
1347
  format: "thread",
1348
+ account: params.account || config2.default_account || void 0,
1346
1349
  thread_tweets: threadTweets,
1347
1350
  schedule_at: params.schedule_at,
1348
1351
  status: "draft",
@@ -1354,7 +1357,7 @@ async function handleDraftThread(ctx, params) {
1354
1357
  title: `Thread: ${threadTweets[0].slice(0, 50)}`,
1355
1358
  data: draft
1356
1359
  });
1357
- await ctx.events.emit(EVENT_NAMES.draftCreated, config.company_id, {
1360
+ await ctx.events.emit(EVENT_NAMES.draftCreated, config2.company_id, {
1358
1361
  draft_id: entity.id,
1359
1362
  format: "thread",
1360
1363
  metadata
@@ -1382,8 +1385,8 @@ async function handleUpdateDraft(ctx, params) {
1382
1385
  return { content: JSON.stringify({ draft_id: draftId, status: "draft", updated: true }) };
1383
1386
  }
1384
1387
  async function handlePublishPost(ctx, params) {
1385
- const config = await getConfig(ctx);
1386
- const resolved = resolveAccount(config, params.account);
1388
+ const config2 = await getConfig(ctx);
1389
+ const resolved = resolveAccount(config2, params.account);
1387
1390
  if (!resolved.ok) return { error: resolved.error };
1388
1391
  const { handle, account } = resolved;
1389
1392
  let text = params.text;
@@ -1397,13 +1400,14 @@ async function handlePublishPost(ctx, params) {
1397
1400
  metadata = { ...match.data.metadata, ...metadata };
1398
1401
  }
1399
1402
  if (!text) return { error: "No text provided and no draft_id." };
1400
- const approval = await checkApproval(ctx, config, "posts", draftId, account.approval_modes);
1403
+ const approval = await checkApproval(ctx, config2, "posts", draftId, account.approval_modes);
1401
1404
  if (!approval.allowed) return { error: `Blocked: ${approval.reason}` };
1402
- const token = await getValidAccessToken(ctx, config, handle);
1405
+ const token = await getValidAccessToken(ctx, config2, handle);
1403
1406
  const result = await createTweet(ctx.http, token, { text });
1404
1407
  await updateRateLimits(ctx, handle, "post_tweets", result.rateLimit);
1405
1408
  const published = {
1406
1409
  tweet_id: result.data.id,
1410
+ account: handle,
1407
1411
  text: result.data.text,
1408
1412
  published_at: (/* @__PURE__ */ new Date()).toISOString(),
1409
1413
  draft_id: draftId,
@@ -1417,7 +1421,7 @@ async function handlePublishPost(ctx, params) {
1417
1421
  title: text.slice(0, 60),
1418
1422
  data: published
1419
1423
  });
1420
- await ctx.events.emit(EVENT_NAMES.postPublished, config.company_id, {
1424
+ await ctx.events.emit(EVENT_NAMES.postPublished, config2.company_id, {
1421
1425
  tweet_id: result.data.id,
1422
1426
  text,
1423
1427
  format: "single",
@@ -1426,20 +1430,21 @@ async function handlePublishPost(ctx, params) {
1426
1430
  return { content: JSON.stringify({ tweet_id: result.data.id, text: result.data.text, status: "published" }) };
1427
1431
  }
1428
1432
  async function handleReplyToTweet(ctx, params) {
1429
- const config = await getConfig(ctx);
1430
- const resolved = resolveAccount(config, params.account);
1433
+ const config2 = await getConfig(ctx);
1434
+ const resolved = resolveAccount(config2, params.account);
1431
1435
  if (!resolved.ok) return { error: resolved.error };
1432
1436
  const { handle, account } = resolved;
1433
1437
  const tweetId = params.tweet_id;
1434
1438
  const text = params.text;
1435
1439
  const metadata = params.metadata || {};
1436
- const approval = await checkApproval(ctx, config, "replies", params.draft_id, account.approval_modes);
1440
+ const approval = await checkApproval(ctx, config2, "replies", params.draft_id, account.approval_modes);
1437
1441
  if (!approval.allowed) return { error: `Blocked: ${approval.reason}` };
1438
- const token = await getValidAccessToken(ctx, config, handle);
1442
+ const token = await getValidAccessToken(ctx, config2, handle);
1439
1443
  const result = await createTweet(ctx.http, token, { text, reply_to: tweetId });
1440
1444
  await updateRateLimits(ctx, handle, "post_tweets", result.rateLimit);
1441
1445
  const published = {
1442
1446
  tweet_id: result.data.id,
1447
+ account: handle,
1443
1448
  text: result.data.text,
1444
1449
  published_at: (/* @__PURE__ */ new Date()).toISOString(),
1445
1450
  draft_id: params.draft_id,
@@ -1453,7 +1458,7 @@ async function handleReplyToTweet(ctx, params) {
1453
1458
  title: `Reply: ${text.slice(0, 50)}`,
1454
1459
  data: published
1455
1460
  });
1456
- await ctx.events.emit(EVENT_NAMES.postPublished, config.company_id, {
1461
+ await ctx.events.emit(EVENT_NAMES.postPublished, config2.company_id, {
1457
1462
  tweet_id: result.data.id,
1458
1463
  text,
1459
1464
  format: "reply",
@@ -1462,20 +1467,21 @@ async function handleReplyToTweet(ctx, params) {
1462
1467
  return { content: JSON.stringify({ tweet_id: result.data.id, text: result.data.text, status: "published", reply_to: tweetId }) };
1463
1468
  }
1464
1469
  async function handleQuoteTweet(ctx, params) {
1465
- const config = await getConfig(ctx);
1466
- const resolved = resolveAccount(config, params.account);
1470
+ const config2 = await getConfig(ctx);
1471
+ const resolved = resolveAccount(config2, params.account);
1467
1472
  if (!resolved.ok) return { error: resolved.error };
1468
1473
  const { handle, account } = resolved;
1469
1474
  const tweetId = params.tweet_id;
1470
1475
  const text = params.text;
1471
1476
  const metadata = params.metadata || {};
1472
- const approval = await checkApproval(ctx, config, "quotes", params.draft_id, account.approval_modes);
1477
+ const approval = await checkApproval(ctx, config2, "quotes", params.draft_id, account.approval_modes);
1473
1478
  if (!approval.allowed) return { error: `Blocked: ${approval.reason}` };
1474
- const token = await getValidAccessToken(ctx, config, handle);
1479
+ const token = await getValidAccessToken(ctx, config2, handle);
1475
1480
  const result = await createTweet(ctx.http, token, { text, quote_tweet_id: tweetId });
1476
1481
  await updateRateLimits(ctx, handle, "post_tweets", result.rateLimit);
1477
1482
  const published = {
1478
1483
  tweet_id: result.data.id,
1484
+ account: handle,
1479
1485
  text: result.data.text,
1480
1486
  published_at: (/* @__PURE__ */ new Date()).toISOString(),
1481
1487
  draft_id: params.draft_id,
@@ -1489,7 +1495,7 @@ async function handleQuoteTweet(ctx, params) {
1489
1495
  title: `Quote: ${text.slice(0, 50)}`,
1490
1496
  data: published
1491
1497
  });
1492
- await ctx.events.emit(EVENT_NAMES.postPublished, config.company_id, {
1498
+ await ctx.events.emit(EVENT_NAMES.postPublished, config2.company_id, {
1493
1499
  tweet_id: result.data.id,
1494
1500
  text,
1495
1501
  format: "quote",
@@ -1498,18 +1504,19 @@ async function handleQuoteTweet(ctx, params) {
1498
1504
  return { content: JSON.stringify({ tweet_id: result.data.id, text: result.data.text, status: "published", quoted: tweetId }) };
1499
1505
  }
1500
1506
  async function handleRepost(ctx, params) {
1501
- const config = await getConfig(ctx);
1502
- const resolved = resolveAccount(config, params.account);
1507
+ const config2 = await getConfig(ctx);
1508
+ const resolved = resolveAccount(config2, params.account);
1503
1509
  if (!resolved.ok) return { error: resolved.error };
1504
1510
  const { handle, account } = resolved;
1505
1511
  const tweetId = params.tweet_id;
1506
- const approval = await checkApproval(ctx, config, "reposts", params.draft_id, account.approval_modes);
1512
+ const approval = await checkApproval(ctx, config2, "reposts", params.draft_id, account.approval_modes);
1507
1513
  if (!approval.allowed) return { error: `Blocked: ${approval.reason}` };
1508
- const token = await getValidAccessToken(ctx, config, handle);
1514
+ const token = await getValidAccessToken(ctx, config2, handle);
1509
1515
  const result = await repost(ctx.http, token, account.x_user_id, tweetId);
1510
1516
  await updateRateLimits(ctx, handle, "retweets", result.rateLimit);
1511
1517
  const published = {
1512
1518
  tweet_id: tweetId,
1519
+ account: handle,
1513
1520
  text: "",
1514
1521
  published_at: (/* @__PURE__ */ new Date()).toISOString(),
1515
1522
  draft_id: params.draft_id,
@@ -1523,7 +1530,7 @@ async function handleRepost(ctx, params) {
1523
1530
  title: `Repost: ${tweetId}`,
1524
1531
  data: published
1525
1532
  });
1526
- await ctx.events.emit(EVENT_NAMES.postPublished, config.company_id, {
1533
+ await ctx.events.emit(EVENT_NAMES.postPublished, config2.company_id, {
1527
1534
  tweet_id: tweetId,
1528
1535
  text: "",
1529
1536
  format: "repost",
@@ -1554,6 +1561,7 @@ async function handleSchedulePost(ctx, params) {
1554
1561
  const draft = {
1555
1562
  text,
1556
1563
  format: "single",
1564
+ account: params.account || config.default_account || void 0,
1557
1565
  schedule_at: scheduleAt,
1558
1566
  status: "draft",
1559
1567
  metadata
@@ -1567,8 +1575,8 @@ async function handleSchedulePost(ctx, params) {
1567
1575
  return { content: JSON.stringify({ draft_id: entity.id, schedule_at: scheduleAt, status: "scheduled" }) };
1568
1576
  }
1569
1577
  async function handlePublishThread(ctx, params) {
1570
- const config = await getConfig(ctx);
1571
- const resolved = resolveAccount(config, params.account);
1578
+ const config2 = await getConfig(ctx);
1579
+ const resolved = resolveAccount(config2, params.account);
1572
1580
  if (!resolved.ok) return { error: resolved.error };
1573
1581
  const { handle, account } = resolved;
1574
1582
  let threadTweets = params.thread_tweets;
@@ -1584,17 +1592,17 @@ async function handlePublishThread(ctx, params) {
1584
1592
  if (!threadTweets || threadTweets.length < 2) {
1585
1593
  return { error: "Thread must have at least 2 tweets." };
1586
1594
  }
1587
- const approval = await checkApproval(ctx, config, "posts", draftId, account.approval_modes);
1595
+ const approval = await checkApproval(ctx, config2, "posts", draftId, account.approval_modes);
1588
1596
  if (!approval.allowed) return { error: `Blocked: ${approval.reason}` };
1589
- const token = await getValidAccessToken(ctx, config, handle);
1590
- const result = await publishThread(ctx, token, threadTweets, metadata, config.company_id);
1591
- await ctx.events.emit(EVENT_NAMES.threadPublished, config.company_id, {
1597
+ const token = await getValidAccessToken(ctx, config2, handle);
1598
+ const result = await publishThread(ctx, token, handle, threadTweets, metadata, config2.company_id);
1599
+ await ctx.events.emit(EVENT_NAMES.threadPublished, config2.company_id, {
1592
1600
  tweet_ids: result.tweetIds,
1593
1601
  thread_length: threadTweets.length,
1594
1602
  partial: result.partial
1595
1603
  });
1596
1604
  for (const tweetId of result.tweetIds) {
1597
- await ctx.events.emit(EVENT_NAMES.postPublished, config.company_id, {
1605
+ await ctx.events.emit(EVENT_NAMES.postPublished, config2.company_id, {
1598
1606
  tweet_id: tweetId,
1599
1607
  text: "",
1600
1608
  format: "thread",
@@ -1640,8 +1648,8 @@ async function handleGetPostMetrics(ctx, params) {
1640
1648
  }) };
1641
1649
  }
1642
1650
  async function handleGetAccountStatus(ctx, params) {
1643
- const config = await getConfig(ctx);
1644
- const accounts = config.accounts || {};
1651
+ const config2 = await getConfig(ctx);
1652
+ const accounts = config2.accounts || {};
1645
1653
  const handles = params.account ? [params.account] : Object.keys(accounts);
1646
1654
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1647
1655
  const entities = await ctx.entities.list({ entityType: ENTITY_TYPES.publishedPost, limit: 500 });
@@ -1667,29 +1675,29 @@ async function handleGetAccountStatus(ctx, params) {
1667
1675
  }
1668
1676
  const todayCount = entities.filter((e) => {
1669
1677
  const data = e.data;
1670
- return data.published_at.startsWith(today);
1678
+ return data.published_at.startsWith(today) && (!data.account || data.account === handle);
1671
1679
  }).length;
1672
1680
  statuses.push({
1673
1681
  x_handle: handle,
1674
1682
  role: accounts[handle]?.role || "unknown",
1675
1683
  token_health: tokenHealth,
1676
- daily_posts: { count: todayCount, limit: config.daily_post_limit },
1684
+ daily_posts: { count: todayCount, limit: config2.daily_post_limit },
1677
1685
  rate_limits: rateLimits
1678
1686
  });
1679
1687
  }
1680
1688
  return { content: JSON.stringify(handles.length === 1 ? statuses[0] : statuses) };
1681
1689
  }
1682
1690
  async function handleTokenRefresh(ctx, _job) {
1683
- const config = await getConfig(ctx);
1684
- const handles = Object.keys(config.accounts || {});
1691
+ const config2 = await getConfig(ctx);
1692
+ const handles = Object.keys(config2.accounts || {});
1685
1693
  for (const handle of handles) {
1686
1694
  try {
1687
- await refreshTokens(ctx, config, handle);
1695
+ await refreshTokens(ctx, config2, handle);
1688
1696
  ctx.logger.info(`Token refresh succeeded for @${handle}`);
1689
1697
  } catch (err) {
1690
1698
  ctx.logger.error(`Token refresh failed for @${handle}`, { error: String(err) });
1691
1699
  const issue = await ctx.issues.create({
1692
- companyId: config.company_id,
1700
+ companyId: config2.company_id,
1693
1701
  title: `OAuth token refresh failed for @${handle}`,
1694
1702
  description: `Token refresh failed for @${handle} at ${(/* @__PURE__ */ new Date()).toISOString()}.
1695
1703
 
@@ -1697,18 +1705,12 @@ Error: ${String(err)}
1697
1705
 
1698
1706
  Manual re-auth may be required via setup-oauth tool.`
1699
1707
  });
1700
- await ctx.issues.update(issue.id, { status: "todo" }, config.company_id);
1708
+ await ctx.issues.update(issue.id, { status: "todo" }, config2.company_id);
1701
1709
  }
1702
1710
  }
1703
1711
  }
1704
1712
  async function handlePublishScheduled(ctx, _job) {
1705
- const config = await getConfig(ctx);
1706
- const resolved = resolveAccount(config);
1707
- if (!resolved.ok) {
1708
- ctx.logger.error(resolved.error);
1709
- return;
1710
- }
1711
- const { handle } = resolved;
1713
+ const config2 = await getConfig(ctx);
1712
1714
  const now = (/* @__PURE__ */ new Date()).toISOString();
1713
1715
  const entities = await ctx.entities.list({ entityType: ENTITY_TYPES.draft, limit: 100 });
1714
1716
  const due = entities.filter((e) => {
@@ -1717,15 +1719,21 @@ async function handlePublishScheduled(ctx, _job) {
1717
1719
  });
1718
1720
  for (const entity of due) {
1719
1721
  const draft = entity.data;
1720
- const approval = await checkApproval(ctx, config, "scheduled", entity.id, resolved.account.approval_modes);
1722
+ const resolved = resolveAccount(config2, draft.account);
1723
+ if (!resolved.ok) {
1724
+ ctx.logger.error(`Scheduled draft ${entity.id}: ${resolved.error}`);
1725
+ continue;
1726
+ }
1727
+ const { handle, account } = resolved;
1728
+ const approval = await checkApproval(ctx, config2, "scheduled", entity.id, account.approval_modes);
1721
1729
  if (!approval.allowed) {
1722
1730
  ctx.logger.info(`Scheduled draft ${entity.id} blocked: ${approval.reason}`);
1723
1731
  continue;
1724
1732
  }
1725
1733
  try {
1726
- const token = await getValidAccessToken(ctx, config, handle);
1734
+ const token = await getValidAccessToken(ctx, config2, handle);
1727
1735
  if (draft.format === "thread" && draft.thread_tweets) {
1728
- await publishThread(ctx, token, draft.thread_tweets, draft.metadata, config.company_id);
1736
+ await publishThread(ctx, token, handle, draft.thread_tweets, draft.metadata, config2.company_id);
1729
1737
  } else {
1730
1738
  const result = await createTweet(ctx.http, token, {
1731
1739
  text: draft.text,
@@ -1740,6 +1748,7 @@ async function handlePublishScheduled(ctx, _job) {
1740
1748
  title: draft.text.slice(0, 60),
1741
1749
  data: {
1742
1750
  tweet_id: result.data.id,
1751
+ account: handle,
1743
1752
  text: result.data.text,
1744
1753
  published_at: (/* @__PURE__ */ new Date()).toISOString(),
1745
1754
  draft_id: entity.id,
@@ -1747,7 +1756,7 @@ async function handlePublishScheduled(ctx, _job) {
1747
1756
  metadata: draft.metadata
1748
1757
  }
1749
1758
  });
1750
- await ctx.events.emit(EVENT_NAMES.postPublished, config.company_id, {
1759
+ await ctx.events.emit(EVENT_NAMES.postPublished, config2.company_id, {
1751
1760
  tweet_id: result.data.id,
1752
1761
  text: draft.text,
1753
1762
  format: draft.format,
@@ -1769,14 +1778,14 @@ async function handlePublishScheduled(ctx, _job) {
1769
1778
  }
1770
1779
  }
1771
1780
  async function handleMetricsCapture(ctx, _job) {
1772
- const config = await getConfig(ctx);
1773
- const resolved = resolveAccount(config);
1781
+ const config2 = await getConfig(ctx);
1782
+ const resolved = resolveAccount(config2);
1774
1783
  if (!resolved.ok) {
1775
1784
  ctx.logger.error(resolved.error);
1776
1785
  return;
1777
1786
  }
1778
1787
  const { handle } = resolved;
1779
- const lookbackMs = config.metrics_capture_lookback_days * 864e5;
1788
+ const lookbackMs = config2.metrics_capture_lookback_days * 864e5;
1780
1789
  const cutoff = new Date(Date.now() - lookbackMs).toISOString();
1781
1790
  const entities = await ctx.entities.list({ entityType: ENTITY_TYPES.publishedPost, limit: 500 });
1782
1791
  const recent = entities.filter((e) => {
@@ -1785,7 +1794,7 @@ async function handleMetricsCapture(ctx, _job) {
1785
1794
  });
1786
1795
  if (recent.length === 0) return;
1787
1796
  const tweetIds = recent.map((e) => e.data.tweet_id);
1788
- const token = await getValidAccessToken(ctx, config, handle);
1797
+ const token = await getValidAccessToken(ctx, config2, handle);
1789
1798
  const batchSize = 100;
1790
1799
  for (let i = 0; i < tweetIds.length; i += batchSize) {
1791
1800
  const batch = tweetIds.slice(i, i + batchSize);
@@ -1812,9 +1821,9 @@ async function handleMetricsCapture(ctx, _job) {
1812
1821
  };
1813
1822
  const totalEngagement = newMetrics.likes + newMetrics.retweets + newMetrics.replies;
1814
1823
  const prevTotal = data.metrics ? data.metrics.likes + data.metrics.retweets + data.metrics.replies : 0;
1815
- for (const milestone of config.engagement_milestones) {
1824
+ for (const milestone of config2.engagement_milestones) {
1816
1825
  if (totalEngagement >= milestone && prevTotal < milestone) {
1817
- await ctx.events.emit(EVENT_NAMES.postEngagementMilestone, config.company_id, {
1826
+ await ctx.events.emit(EVENT_NAMES.postEngagementMilestone, config2.company_id, {
1818
1827
  tweet_id: tweet.id,
1819
1828
  milestone,
1820
1829
  current_metrics: newMetrics
@@ -1935,8 +1944,8 @@ var plugin = definePlugin({
1935
1944
  (params) => handleGetAccountStatus(ctx, p(params))
1936
1945
  );
1937
1946
  ctx.data.register("dashboard-summary", async () => {
1938
- const config = await getConfig(ctx);
1939
- const accounts = config.accounts || {};
1947
+ const config2 = await getConfig(ctx);
1948
+ const accounts = config2.accounts || {};
1940
1949
  const handles = Object.keys(accounts);
1941
1950
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1942
1951
  const published = await ctx.entities.list({ entityType: ENTITY_TYPES.publishedPost, limit: 100 });
@@ -1962,7 +1971,7 @@ var plugin = definePlugin({
1962
1971
  const recentPosts = published.map((e) => e.data).sort((a, b) => a.published_at > b.published_at ? -1 : 1).slice(0, 5);
1963
1972
  return {
1964
1973
  today_post_count: todayPosts.length,
1965
- daily_limit: config.daily_post_limit,
1974
+ daily_limit: config2.daily_post_limit,
1966
1975
  pending_drafts: pendingDrafts.length,
1967
1976
  token_health: tokenHealth,
1968
1977
  accounts: accountHealth,
@@ -1970,8 +1979,8 @@ var plugin = definePlugin({
1970
1979
  };
1971
1980
  });
1972
1981
  ctx.data.register("plugin-config", async () => {
1973
- const config = await getConfig(ctx);
1974
- return config;
1982
+ const config2 = await getConfig(ctx);
1983
+ return config2;
1975
1984
  });
1976
1985
  ctx.data.register("content-queue", async () => {
1977
1986
  const allDrafts = await ctx.entities.list({ entityType: ENTITY_TYPES.draft, limit: 100 });
@@ -1991,8 +2000,8 @@ var plugin = definePlugin({
1991
2000
  },
1992
2001
  async onHealth() {
1993
2002
  if (!pluginCtx) return { status: "error", message: "Plugin not initialized" };
1994
- const config = await getConfig(pluginCtx);
1995
- const accounts = config.accounts || {};
2003
+ const config2 = await getConfig(pluginCtx);
2004
+ const accounts = config2.accounts || {};
1996
2005
  const handles = Object.keys(accounts);
1997
2006
  if (handles.length === 0) {
1998
2007
  return { status: "error", message: "No accounts configured" };
@@ -2033,22 +2042,26 @@ var plugin = definePlugin({
2033
2042
  const message = issues.length > 0 ? issues.join("; ") : `All ${handles.length} account(s) operational`;
2034
2043
  return { status, message, details: { accounts: accountDetails } };
2035
2044
  },
2036
- async onValidateConfig(config) {
2045
+ async onValidateConfig(config2) {
2037
2046
  const warnings = [];
2038
2047
  const errors = [];
2039
- const accounts = config.accounts || {};
2048
+ const accounts = config2.accounts || {};
2040
2049
  const handles = Object.keys(accounts);
2041
2050
  if (handles.length === 0) {
2042
2051
  errors.push("At least one account is required in accounts map");
2043
2052
  }
2053
+ const defaultAcct = config2.default_account;
2054
+ if (defaultAcct && handles.length > 0 && !accounts[defaultAcct]) {
2055
+ errors.push(`default_account "${defaultAcct}" is not in the accounts map. Available: ${handles.join(", ")}`);
2056
+ }
2044
2057
  for (const handle of handles) {
2045
2058
  const acct = accounts[handle];
2046
2059
  if (!acct.x_handle) errors.push(`Account ${handle}: x_handle is required`);
2047
2060
  if (!acct.x_user_id) errors.push(`Account ${handle}: x_user_id is required`);
2048
2061
  }
2049
2062
  if (pluginCtx) {
2050
- const clientIdRef = config.oauth_client_id_ref || DEFAULT_CONFIG.oauth_client_id_ref;
2051
- const clientSecretRef = config.oauth_client_secret_ref || DEFAULT_CONFIG.oauth_client_secret_ref;
2063
+ const clientIdRef = config2.oauth_client_id_ref || DEFAULT_CONFIG.oauth_client_id_ref;
2064
+ const clientSecretRef = config2.oauth_client_secret_ref || DEFAULT_CONFIG.oauth_client_secret_ref;
2052
2065
  try {
2053
2066
  await pluginCtx.secrets.resolve(clientIdRef);
2054
2067
  } catch {