@gobing-ai/knowledge-kit 0.0.32 → 0.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/index.js +43047 -24724
  3. package/package.json +4 -4
  4. package/plugins/generations/content-gen/dist/index.js +10 -9
  5. package/plugins/generations/core-facts-gen/dist/index.js +13 -12
  6. package/plugins/generations/daily-article-gen/dist/index.js +16 -21
  7. package/plugins/generations/daily-article-gen/src/index.ts +8 -12
  8. package/plugins/generations/dailynews-gen/dist/index.js +23 -21
  9. package/plugins/generations/dailynews-gen/src/script-builder.ts +25 -26
  10. package/plugins/generations/episode-plan-gen/dist/index.js +58 -15
  11. package/plugins/generations/episode-plan-gen/src/index.ts +76 -6
  12. package/plugins/generations/gemini-tts/README.md +11 -0
  13. package/plugins/generations/gemini-tts/dist/index.js +10 -9
  14. package/plugins/generations/image-gen/dist/index.js +171 -153
  15. package/plugins/generations/image-gen/presets/formats/xhs-card.yaml +16 -14
  16. package/plugins/generations/image-gen/presets/styles/xhs-knowledge.yaml +2 -2
  17. package/plugins/generations/image-gen/src/compose/card.ts +17 -2
  18. package/plugins/generations/image-gen/src/compose/chromium.ts +60 -42
  19. package/plugins/generations/news-report-gen/dist/index.js +13 -12
  20. package/plugins/generations/voice-gen/dist/index.js +10 -9
  21. package/plugins/ingestions/aihot-ingest/dist/index.js +13 -12
  22. package/plugins/ingestions/horizon-ingest/dist/index.js +13 -12
  23. package/plugins/ingestions/karakeep-local/dist/index.js +13 -13
  24. package/plugins/ingestions/last30days-ingest/dist/index.js +13 -12
  25. package/plugins/ingestions/web-search/dist/index.js +11 -10
  26. package/plugins/kk/README.md +38 -0
  27. package/plugins/kk/decisions/extract/ai-news.yaml +27 -0
  28. package/plugins/kk/decisions/kk-items.yaml +179 -0
  29. package/plugins/kk/plugin.json +1 -1
  30. package/plugins/kk/schedules/cron.tmpl +15 -0
  31. package/plugins/kk/schedules/launchd.plist.tmpl +77 -0
  32. package/plugins/kk/scripts/daily-stages.ts +2023 -0
  33. package/plugins/kk/scripts/daily-voice-style.zh.md +15 -10
  34. package/plugins/kk/scripts/kk-workflow-stages.ts +253 -1368
  35. package/plugins/kk/skills/content-safety/SKILL.md +39 -8
  36. package/plugins/kk/skills/content-safety/references/platforms/apple-podcasts.md +2 -2
  37. package/plugins/kk/skills/web-automation/SKILL.md +100 -0
  38. package/plugins/kk/skills/web-automation/references/browser-launch.md +86 -0
  39. package/plugins/kk/workflows/kk-collect.yaml +256 -0
  40. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +9 -3
  41. package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +774 -0
  42. package/plugins/kk/workflows/kk-daily-safety.yaml +167 -0
  43. package/plugins/publishings/emdash-pub/dist/index.js +10 -9
  44. package/plugins/publishings/infoq-pub/dist/index.js +152 -59
  45. package/plugins/publishings/infoq-pub/plugin.json +1 -0
  46. package/plugins/publishings/infoq-pub/src/index.ts +167 -55
  47. package/plugins/publishings/infoq-pub/src/profile-check.ts +4 -2
  48. package/plugins/publishings/juejin-pub/dist/index.js +111 -30
  49. package/plugins/publishings/juejin-pub/plugin.json +1 -0
  50. package/plugins/publishings/juejin-pub/src/index.ts +49 -9
  51. package/plugins/publishings/medium-pub/dist/index.js +10 -9
  52. package/plugins/publishings/podcast-pub/dist/index.js +10 -9
  53. package/plugins/publishings/qiita-pub/dist/index.js +10 -9
  54. package/plugins/publishings/substack-pub/dist/index.js +105 -28
  55. package/plugins/publishings/substack-pub/plugin.json +1 -0
  56. package/plugins/publishings/substack-pub/src/index.ts +39 -8
  57. package/plugins/publishings/substack-pub/src/profile-check.ts +2 -0
  58. package/plugins/publishings/surfdash-pub/dist/index.js +10 -9
  59. package/plugins/publishings/wechatmp-pub/dist/index.js +115 -31
  60. package/plugins/publishings/wechatmp-pub/plugin.json +1 -0
  61. package/plugins/publishings/wechatmp-pub/src/index.ts +42 -9
  62. package/plugins/publishings/wechatmp-pub/src/profile-check.ts +84 -40
  63. package/plugins/publishings/x-pub/dist/index.js +114 -35
  64. package/plugins/publishings/x-pub/plugin.json +1 -0
  65. package/plugins/publishings/x-pub/src/index.ts +68 -14
  66. package/plugins/publishings/xhs-pub/dist/index.js +154 -59
  67. package/plugins/publishings/xhs-pub/plugin.json +1 -0
  68. package/plugins/publishings/xhs-pub/src/flow.ts +9 -3
  69. package/plugins/publishings/xhs-pub/src/index.ts +119 -9
  70. package/plugins/publishings/xhs-pub/src/page.ts +4 -17
  71. package/plugins/publishings/xhs-pub/src/profile-check.ts +28 -15
  72. package/plugins/publishings/zenn-pub/dist/index.js +10 -9
@@ -14671,7 +14671,7 @@ var init_zod = __esm(() => {
14671
14671
  init_external();
14672
14672
  });
14673
14673
 
14674
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/errors.js
14674
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.21/node_modules/@gobing-ai/ts-utils/dist/errors.js
14675
14675
  var ErrorCode;
14676
14676
  var init_errors3 = __esm(() => {
14677
14677
  ErrorCode = {
@@ -14682,7 +14682,7 @@ var init_errors3 = __esm(() => {
14682
14682
  };
14683
14683
  });
14684
14684
 
14685
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/api-response.js
14685
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.21/node_modules/@gobing-ai/ts-utils/dist/api-response.js
14686
14686
  var API_ERROR_CODES, ERROR_CODE_TO_HTTP, CLIENT_SAFE_CODES;
14687
14687
  var init_api_response = __esm(() => {
14688
14688
  init_errors3();
@@ -14705,10 +14705,10 @@ var init_api_response = __esm(() => {
14705
14705
  CLIENT_SAFE_CODES = new Set([ErrorCode.NotFound, ErrorCode.Validation, ErrorCode.Conflict]);
14706
14706
  });
14707
14707
 
14708
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/cursor.js
14708
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.21/node_modules/@gobing-ai/ts-utils/dist/cursor.js
14709
14709
  var init_cursor = () => {};
14710
14710
 
14711
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/env.js
14711
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.21/node_modules/@gobing-ai/ts-utils/dist/env.js
14712
14712
  function getEnvVar(name, fallback) {
14713
14713
  const raw = process.env[name];
14714
14714
  return raw === undefined ? fallback : raw;
@@ -14717,10 +14717,10 @@ function getEnvVars() {
14717
14717
  return process.env;
14718
14718
  }
14719
14719
 
14720
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/event-severity.js
14720
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.21/node_modules/@gobing-ai/ts-utils/dist/event-severity.js
14721
14721
  var init_event_severity = () => {};
14722
14722
 
14723
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/object.js
14723
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.21/node_modules/@gobing-ai/ts-utils/dist/object.js
14724
14724
  function isPlainObject2(value) {
14725
14725
  return typeof value === "object" && value !== null && !Array.isArray(value);
14726
14726
  }
@@ -14738,7 +14738,7 @@ function deepMerge(target, source) {
14738
14738
  return result;
14739
14739
  }
14740
14740
 
14741
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/output.js
14741
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.21/node_modules/@gobing-ai/ts-utils/dist/output.js
14742
14742
  function processStream(name) {
14743
14743
  const proc = globalThis.process;
14744
14744
  const stream = proc?.[name];
@@ -14756,7 +14756,7 @@ function echoError(message, target = defaultStderrTarget ?? processStream("stder
14756
14756
  }
14757
14757
  var defaultStderrTarget;
14758
14758
 
14759
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/index.js
14759
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.21/node_modules/@gobing-ai/ts-utils/dist/index.js
14760
14760
  var init_dist = __esm(() => {
14761
14761
  init_api_response();
14762
14762
  init_cursor();
@@ -21754,7 +21754,7 @@ var init_dist2 = __esm(() => {
21754
21754
  $visitAsync = visit.visitAsync;
21755
21755
  });
21756
21756
 
21757
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/config.js
21757
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.21+44edd5c4d6372504/node_modules/@gobing-ai/ts-runtime/dist/config.js
21758
21758
  function getProcessEnv() {
21759
21759
  return getEnvVars();
21760
21760
  }
@@ -21832,7 +21832,7 @@ var init_config = __esm(() => {
21832
21832
  };
21833
21833
  });
21834
21834
 
21835
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/file-system-node.js
21835
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.21+44edd5c4d6372504/node_modules/@gobing-ai/ts-runtime/dist/file-system-node.js
21836
21836
  import { appendFileSync, cpSync, createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "fs";
21837
21837
  import { dirname, resolve as resolvePath } from "path";
21838
21838
  function createNodeFileSystem(root, paths) {
@@ -21919,7 +21919,7 @@ function findProjectRoot(startDir) {
21919
21919
  }
21920
21920
  var init_file_system_node = () => {};
21921
21921
 
21922
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/db-errors.js
21922
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.21+44edd5c4d6372504/node_modules/@gobing-ai/ts-runtime/dist/db-errors.js
21923
21923
  var DbModuleNotInstalledError;
21924
21924
  var init_db_errors = __esm(() => {
21925
21925
  DbModuleNotInstalledError = class DbModuleNotInstalledError extends Error {
@@ -29130,7 +29130,7 @@ var init_execa = __esm(() => {
29130
29130
  } = getIpcExport());
29131
29131
  });
29132
29132
 
29133
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/process-group.js
29133
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.21+44edd5c4d6372504/node_modules/@gobing-ai/ts-runtime/dist/process-group.js
29134
29134
  function resolveDeadline(optionTimeout, defaultTimeout) {
29135
29135
  if (optionTimeout === null)
29136
29136
  return null;
@@ -29222,7 +29222,7 @@ function ownProcessGroupLifecycle(ownership) {
29222
29222
  }
29223
29223
  var MAX_TIMEOUT_MS = 2147483647, DEFAULT_KILL_GRACE_MS = 5000, GROUP_POLL_INTERVAL_MS = 25, GROUP_KILL_PATIENCE_MS = 2000;
29224
29224
 
29225
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/process-executor.js
29225
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.21+44edd5c4d6372504/node_modules/@gobing-ai/ts-runtime/dist/process-executor.js
29226
29226
  import { isatty } from "tty";
29227
29227
  function processEventSeverity(reason, exitCode) {
29228
29228
  if (reason === "error")
@@ -29594,7 +29594,7 @@ var init_process_executor = __esm(() => {
29594
29594
  init_config();
29595
29595
  });
29596
29596
 
29597
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/runtime-node-bun.js
29597
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.21+44edd5c4d6372504/node_modules/@gobing-ai/ts-runtime/dist/runtime-node-bun.js
29598
29598
  function getNodeFileSystem() {
29599
29599
  if (!_nodeFileSystem)
29600
29600
  _nodeFileSystem = createNodeFileSystem();
@@ -29742,15 +29742,16 @@ var PluginManifestSchema = exports_external.looseObject({
29742
29742
  name: exports_external.string().min(1),
29743
29743
  kind: PluginKindSchema,
29744
29744
  entry: exports_external.string().min(1),
29745
+ check: exports_external.string().min(1).optional(),
29745
29746
  version: exports_external.string().optional(),
29746
29747
  description: exports_external.string().optional(),
29747
29748
  operations: exports_external.record(exports_external.string(), PluginOperationSchema).optional()
29748
29749
  });
29749
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/index.js
29750
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.21+44edd5c4d6372504/node_modules/@gobing-ai/ts-runtime/dist/index.js
29750
29751
  init_file_system_node();
29751
29752
  init_config();
29752
29753
 
29753
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/path.js
29754
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.21+44edd5c4d6372504/node_modules/@gobing-ai/ts-runtime/dist/path.js
29754
29755
  function normalizeSeparators(path6) {
29755
29756
  return path6.replaceAll("\\", "/");
29756
29757
  }
@@ -29808,7 +29809,7 @@ function getProcessCwd() {
29808
29809
  return globalThis.process?.cwd?.() ?? "/";
29809
29810
  }
29810
29811
 
29811
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/index.js
29812
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.21+44edd5c4d6372504/node_modules/@gobing-ai/ts-runtime/dist/index.js
29812
29813
  init_runtime_node_bun();
29813
29814
 
29814
29815
  // ../../packages/kk-core/src/render-md.ts
@@ -29921,7 +29922,10 @@ async function trySelectors(page, selectors, options = {}) {
29921
29922
  return { found: false, selector: null };
29922
29923
  }
29923
29924
 
29924
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/errors.js
29925
+ // ../../packages/publish-harness/src/browser.ts
29926
+ import { resolve as resolve2 } from "path";
29927
+
29928
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.21+b6949ba92ac553f0/node_modules/@gobing-ai/ts-browser-automation/dist/errors.js
29925
29929
  class BrowserAutomationError extends Error {
29926
29930
  constructor(message, options) {
29927
29931
  super(message, options);
@@ -29970,10 +29974,10 @@ class OperationAbortedError extends BrowserAutomationError {
29970
29974
  this.name = "OperationAbortedError";
29971
29975
  }
29972
29976
  }
29973
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29977
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.21+b6949ba92ac553f0/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29974
29978
  import { mkdir } from "fs/promises";
29975
29979
 
29976
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/profile-dir.js
29980
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.21+b6949ba92ac553f0/node_modules/@gobing-ai/ts-browser-automation/dist/profile-dir.js
29977
29981
  function resolveProfileDir(profileDir) {
29978
29982
  if (profileDir.trim() === "") {
29979
29983
  throw new InvalidProfileDirError;
@@ -29985,7 +29989,7 @@ function resolveProfileDir(profileDir) {
29985
29989
  return resolved;
29986
29990
  }
29987
29991
 
29988
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29992
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.21+b6949ba92ac553f0/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29989
29993
  var PROFILE_DIR_MODE = 448;
29990
29994
  var BUSY_PROFILE_PATTERN = /singleton|already in use|in use by another/i;
29991
29995
  var CHROMIUM_MISSING_PATTERN = /playwright install|executable doesn't exist/i;
@@ -30036,7 +30040,7 @@ function createPersistentLauncher(options = {}) {
30036
30040
  };
30037
30041
  }
30038
30042
 
30039
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/operations.js
30043
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.21+b6949ba92ac553f0/node_modules/@gobing-ai/ts-browser-automation/dist/operations.js
30040
30044
  var DEFAULT_TIMEOUT_MS = 30000;
30041
30045
  var POLL_INTERVAL_MS = 250;
30042
30046
  var defaultLauncher;
@@ -30115,11 +30119,67 @@ async function pollPredicate(page, predicate, timeoutMs, signal) {
30115
30119
  function sleep(ms) {
30116
30120
  return new Promise((resolve2) => setTimeout(resolve2, ms));
30117
30121
  }
30122
+ // ../../packages/publish-harness/src/profile-dir.ts
30123
+ import { mkdir as mkdir2, stat } from "fs/promises";
30124
+ async function ensureProfileDir2(profileDir, headless) {
30125
+ const existing = await stat(profileDir).catch(() => null);
30126
+ if (existing) {
30127
+ if (!existing.isDirectory())
30128
+ throw new InvalidProfileDirError(`profileDir is not a directory: "${profileDir}".`);
30129
+ return;
30130
+ }
30131
+ if (headless)
30132
+ throw new BrowserProfileMissingError(profileDir);
30133
+ await mkdir2(profileDir, { recursive: true, mode: 448 });
30134
+ }
30135
+
30118
30136
  // ../../packages/publish-harness/src/browser.ts
30119
30137
  var DEFAULT_CHECK_TIMEOUT_MS = 30000;
30120
30138
  var DEFAULT_STEP_TIMEOUT_MS = 30000;
30121
30139
  var DEFAULT_POLL_MS = 500;
30122
30140
  var ATTACH_POLL_MS = 100;
30141
+ function createKkBrowserLauncher(loadPlaywright = () => import("playwright")) {
30142
+ return {
30143
+ async launch(request) {
30144
+ if (request.profileDir.trim() === "")
30145
+ throw new InvalidProfileDirError;
30146
+ const profileDir = resolve2(request.profileDir);
30147
+ await ensureProfileDir2(profileDir, request.headless);
30148
+ let playwright;
30149
+ try {
30150
+ playwright = await loadPlaywright();
30151
+ } catch (error51) {
30152
+ throw new PlaywrightUnavailableError('The "playwright" dependency is not installed. Run `bunx playwright install chromium`.', { cause: error51 });
30153
+ }
30154
+ const channel = process.env.KK_BROWSER_CHANNEL?.trim() || "chromium";
30155
+ let context2;
30156
+ try {
30157
+ context2 = await playwright.chromium.launchPersistentContext(profileDir, {
30158
+ headless: request.headless,
30159
+ channel,
30160
+ args: ["--disable-blink-features=AutomationControlled"],
30161
+ ignoreDefaultArgs: ["--enable-automation"]
30162
+ });
30163
+ } catch (error51) {
30164
+ const message = errorMessage2(error51);
30165
+ if (/singleton|already in use|in use by another/i.test(message)) {
30166
+ throw new BrowserProfileBusyError(profileDir, error51);
30167
+ }
30168
+ if (/playwright install|executable doesn't exist/i.test(message)) {
30169
+ throw new PlaywrightUnavailableError(`Browser channel '${channel}' is not installed. Run \`bunx playwright install chromium\` (or install Google Chrome for KK_BROWSER_CHANNEL=chrome).`, { cause: error51 });
30170
+ }
30171
+ throw error51;
30172
+ }
30173
+ await context2.addInitScript(() => {
30174
+ Object.defineProperty(navigator, "webdriver", { get: () => {
30175
+ return;
30176
+ } });
30177
+ });
30178
+ return context2;
30179
+ }
30180
+ };
30181
+ }
30182
+ var kkBrowserLauncher = createKkBrowserLauncher();
30123
30183
  function resolveChannelProfileDir(channel, override) {
30124
30184
  const tiers = [override, process.env[`${channel.toUpperCase()}_PROFILE_DIR`], process.env.KK_BROWSER_PROFILE_DIR];
30125
30185
  const chosen = tiers.find((tier) => tier !== undefined && tier.trim().length > 0);
@@ -30127,7 +30187,7 @@ function resolveChannelProfileDir(channel, override) {
30127
30187
  }
30128
30188
  async function checkChannelSession(config3, opts) {
30129
30189
  const timeoutMs = opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS;
30130
- const passes = opts.headedFallback === false ? [true] : [true, false];
30190
+ const passes = opts.headedOnly ? [false] : opts.headedFallback === false ? [true] : [true, false];
30131
30191
  let lastDetail = `${config3.channel} readiness was never probed`;
30132
30192
  for (const headless of passes) {
30133
30193
  try {
@@ -30137,12 +30197,12 @@ async function checkChannelSession(config3, opts) {
30137
30197
  url: config3.homeUrl,
30138
30198
  isAuthenticated: config3.isAuthenticated,
30139
30199
  timeoutMs,
30140
- launcher: opts.launcher
30200
+ launcher: opts.launcher ?? kkBrowserLauncher
30141
30201
  }, async () => true);
30142
30202
  return {
30143
30203
  status: "verified",
30144
30204
  profileDir: opts.profileDir,
30145
- detail: `authenticated ${config3.channel} session observed ${headless ? "headlessly" : "via headed fallback"} via profile ${opts.profileDir} (read-only)`
30205
+ detail: `authenticated ${config3.channel} session observed ${headless ? "headlessly" : "via a headed pass"} via profile ${opts.profileDir} (read-only)`
30146
30206
  };
30147
30207
  } catch (error51) {
30148
30208
  if (error51 instanceof BrowserProfileMissingError) {
@@ -30163,7 +30223,7 @@ async function waitUntil(predicate, timeoutMs, pollMs = DEFAULT_POLL_MS) {
30163
30223
  if (await predicate()) {
30164
30224
  return true;
30165
30225
  }
30166
- await new Promise((resolve2) => setTimeout(resolve2, pollMs));
30226
+ await new Promise((resolve3) => setTimeout(resolve3, pollMs));
30167
30227
  }
30168
30228
  return predicate();
30169
30229
  }
@@ -30275,9 +30335,13 @@ function oauth1Header(opts) {
30275
30335
  const header = Object.keys(params).sort().map((key) => `${percentEncode(key)}="${percentEncode(params[key] ?? "")}"`).join(", ");
30276
30336
  return `OAuth ${header}`;
30277
30337
  }
30338
+ var X_OPERATIONS = ["post", "article"];
30278
30339
  function resolveOperation(options, content) {
30279
30340
  const raw = options?.operation ?? content?.metadata?.operation ?? "post";
30280
- return raw === "article" ? "article" : "post";
30341
+ if (typeof raw !== "string" || !X_OPERATIONS.includes(raw)) {
30342
+ return { error: `unsupported operation '${String(raw)}' for x: expected one of ${X_OPERATIONS.join(", ")}` };
30343
+ }
30344
+ return { operation: raw };
30281
30345
  }
30282
30346
 
30283
30347
  class XPostApiTransport {
@@ -30361,7 +30425,8 @@ var ARTICLE_CONFIG = {
30361
30425
  channel: ARTICLE_CHANNEL,
30362
30426
  homeUrl: X_ARTICLES_URL,
30363
30427
  isAuthenticated: isArticleAuthenticated,
30364
- loginTimeoutMs: LOGIN_TIMEOUT_MS
30428
+ loginTimeoutMs: LOGIN_TIMEOUT_MS,
30429
+ checkHeadedOnly: true
30365
30430
  };
30366
30431
  function errorMessage3(error51) {
30367
30432
  return error51 instanceof Error ? error51.message : String(error51);
@@ -30395,7 +30460,7 @@ class XArticleBrowserTransport {
30395
30460
  stepTimeoutMs;
30396
30461
  constructor(driver = "script", deps = {}) {
30397
30462
  this.driver = driver;
30398
- this.launcher = deps.launcher;
30463
+ this.launcher = deps.launcher ?? kkBrowserLauncher;
30399
30464
  this.stepTimeoutMs = deps.stepTimeoutMs ?? STEP_TIMEOUT_MS;
30400
30465
  }
30401
30466
  async publish(payload) {
@@ -30442,7 +30507,11 @@ class XArticleBrowserTransport {
30442
30507
  label: "write control"
30443
30508
  });
30444
30509
  await page.locator(writeSelector).first().click();
30445
- const titleSelector = await resolveSelector(page, ARTICLE_SELECTORS.title, {
30510
+ const titleInput = ARTICLE_SELECTORS.title[0];
30511
+ if (await page.locator(titleInput).first().count() === 0) {
30512
+ await page.locator(ARTICLE_SELECTORS.title[1]).first().click({ timeout: this.stepTimeoutMs });
30513
+ }
30514
+ const titleSelector = await resolveSelector(page, [titleInput], {
30446
30515
  timeoutMs: this.stepTimeoutMs,
30447
30516
  label: "title input"
30448
30517
  });
@@ -30453,15 +30522,15 @@ class XArticleBrowserTransport {
30453
30522
  });
30454
30523
  await pasteHtml(page, bodySelector, markdownToHtml(content.body), { timeoutMs: this.stepTimeoutMs });
30455
30524
  if (coverPath !== undefined) {
30456
- const addPhotos = await trySelectors(page, ARTICLE_SELECTORS.addPhotos, { visible: false });
30457
- if (addPhotos.found && addPhotos.selector !== null) {
30458
- await page.locator(addPhotos.selector).first().click();
30459
- }
30460
30525
  const fileInputSelector = await resolveSelector(page, ARTICLE_SELECTORS.fileInput, {
30461
30526
  timeoutMs: this.stepTimeoutMs,
30462
30527
  label: "cover file input"
30463
30528
  });
30464
30529
  await uploadFile(page, fileInputSelector, coverPath, { timeoutMs: this.stepTimeoutMs });
30530
+ await page.locator('[role="dialog"] button:has-text("Apply") >> visible=true').first().click({ timeout: this.stepTimeoutMs });
30531
+ const attached = await expectOutcome(async () => await page.evaluate(coverAttachedScript()) === true ? "cover image rendered in composer" : null, { timeoutMs: this.stepTimeoutMs, label: "cover attachment" });
30532
+ if (!attached.ok)
30533
+ return fail(attached.error);
30465
30534
  }
30466
30535
  const draft = await expectOutcome(() => probeDraft(page), {
30467
30536
  timeoutMs: this.stepTimeoutMs,
@@ -30505,6 +30574,9 @@ async function probeDraft(page) {
30505
30574
  const url2 = page.url();
30506
30575
  return ARTICLE_EDIT_RE.test(url2) ? url2 : null;
30507
30576
  }
30577
+ function coverAttachedScript() {
30578
+ return `[...document.querySelectorAll('img[src*="twimg"]')].some((img) => !/profile_images/.test(img.src ?? ''))`;
30579
+ }
30508
30580
  async function probeLive(page) {
30509
30581
  const url2 = page.url();
30510
30582
  return !ARTICLE_EDIT_RE.test(url2) && LIVE_URL_ID_RE.test(url2) ? url2 : null;
@@ -30536,7 +30608,12 @@ async function processPublishIO(inputPath, outputPath, transport) {
30536
30608
  await writePublishResult(outputPath, fail(message));
30537
30609
  throw new Error(message);
30538
30610
  }
30539
- const operation = resolveOperation(options, content);
30611
+ const mode = resolveOperation(options, content);
30612
+ if (mode.error !== undefined) {
30613
+ await writePublishResult(outputPath, fail(mode.error));
30614
+ throw new Error(mode.error);
30615
+ }
30616
+ const operation = mode.operation;
30540
30617
  const resolved = transport ?? getPublishTransport(operation);
30541
30618
  const result = ResultSchema.parse(await resolved.publish({ content, options }));
30542
30619
  await writePublishResult(outputPath, result);
@@ -30567,11 +30644,13 @@ if (import.meta.main) {
30567
30644
  process.exit(await main2());
30568
30645
  }
30569
30646
  export {
30647
+ resolveOperation,
30570
30648
  processPublishIO,
30571
30649
  oauth1Header,
30572
30650
  main2 as main,
30573
30651
  isArticleAuthenticated,
30574
30652
  getPublishTransport,
30653
+ X_OPERATIONS,
30575
30654
  X_ARTICLES_URL,
30576
30655
  XPostApiTransport,
30577
30656
  XArticleBrowserTransport,
@@ -2,6 +2,7 @@
2
2
  "name": "x-pub",
3
3
  "kind": "publish",
4
4
  "entry": "./dist/index.js",
5
+ "check": "./src/profile-check.ts",
5
6
  "version": "1.0.0",
6
7
  "description": "post via official tweets API; article via script browser transport (ts-browser-automation)"
7
8
  }
@@ -14,6 +14,7 @@ import {
14
14
  type ChannelBrowserConfig,
15
15
  checkChannelSession,
16
16
  expectOutcome,
17
+ kkBrowserLauncher,
17
18
  markdownToHtml,
18
19
  pasteHtml,
19
20
  resolveChannelProfileDir,
@@ -81,9 +82,23 @@ export function oauth1Header(opts: {
81
82
  return `OAuth ${header}`;
82
83
  }
83
84
 
84
- function resolveOperation(options?: Record<string, unknown>, content?: Content): 'post' | 'article' {
85
+ /**
86
+ * Declared modes (ADR-029 dispatch): `post` (API v2 transport) and `article` (browser composer).
87
+ * An unknown operation is refused by name before any transport — and therefore before any
88
+ * browser — is resolved (task 0205 R2).
89
+ */
90
+ export const X_OPERATIONS = ['post', 'article'] as const;
91
+ export type XOperation = (typeof X_OPERATIONS)[number];
92
+
93
+ export function resolveOperation(
94
+ options?: Record<string, unknown>,
95
+ content?: Content,
96
+ ): { operation: XOperation; error?: undefined } | { operation?: undefined; error: string } {
85
97
  const raw = options?.operation ?? content?.metadata?.operation ?? 'post';
86
- return raw === 'article' ? 'article' : 'post';
98
+ if (typeof raw !== 'string' || !(X_OPERATIONS as readonly string[]).includes(raw)) {
99
+ return { error: `unsupported operation '${String(raw)}' for x: expected one of ${X_OPERATIONS.join(', ')}` };
100
+ }
101
+ return { operation: raw as XOperation };
87
102
  }
88
103
 
89
104
  export class XPostApiTransport implements PublishTransport {
@@ -156,8 +171,14 @@ export const LOGIN_URL_RE = /(\/i\/flow\/login|\/login)/;
156
171
 
157
172
  /**
158
173
  * Selectors seeded from the wt script (`x-article-playwright.ts:143-187`); each list is tried in
159
- * order and the operator dogfood (0194) re-measures them. The seed's `tweetButton`/`Tweet`
160
- * candidates are dropped: they belong to the tweet composer, and clicking one would post a tweet.
174
+ * order. Measured live 2026-10-07 (0194): `write` candidate 1 and body candidate 1 hit; the title
175
+ * is a two-step interaction — the `twitter-article-title` div is a collapsed display that mounts
176
+ * the real `textarea[placeholder="Add a title"]` on click (handled in `runArticleFlow`); the
177
+ * paste into the Draft.js container and the `/compose/articles/edit/<id>` signal were both
178
+ * verified. `addPhotos` (`button[aria-label*="Add photos" i]`, "Add photos or video") is present
179
+ * but is NOT clicked in the flow: opening the picker detaches the fileInput's handler and swallows
180
+ * the set file (see the cover leg in `runArticleFlow`). The seed's `tweetButton`/`Tweet` candidates are dropped: they belong to the tweet
181
+ * composer, and clicking one would post a tweet.
161
182
  */
162
183
  export const ARTICLE_SELECTORS = {
163
184
  write: ['[data-testid="empty_state_button_text"]', 'button:has-text("Write")', '[aria-label*="Write" i]'],
@@ -193,6 +214,11 @@ export const ARTICLE_CONFIG: ChannelBrowserConfig = {
193
214
  homeUrl: X_ARTICLES_URL,
194
215
  isAuthenticated: isArticleAuthenticated,
195
216
  loginTimeoutMs: LOGIN_TIMEOUT_MS,
217
+ // X login-walls headless article loads (measured 0216: the headless pass refuses even on a live
218
+ // profile), so the default headless-first + headed-fallback spends two deadlines per check and
219
+ // blows kk's 30s probe-kill (spawn SIGTERM → error instead of refused). One headed pass, like
220
+ // infoq's WAF, keeps the probe inside the budget.
221
+ checkHeadedOnly: true,
196
222
  };
197
223
 
198
224
  function errorMessage(error: unknown): string {
@@ -234,12 +260,12 @@ async function resolveCover(
234
260
  export class XArticleBrowserTransport implements PublishTransport {
235
261
  public readonly kind = 'browser';
236
262
  public readonly driver: BrowserDriver;
237
- private readonly launcher?: BrowserLauncher;
263
+ private readonly launcher: BrowserLauncher;
238
264
  private readonly stepTimeoutMs: number;
239
265
 
240
266
  constructor(driver: BrowserDriver = 'script', deps: { launcher?: BrowserLauncher; stepTimeoutMs?: number } = {}) {
241
267
  this.driver = driver;
242
- this.launcher = deps.launcher;
268
+ this.launcher = deps.launcher ?? kkBrowserLauncher;
243
269
  this.stepTimeoutMs = deps.stepTimeoutMs ?? STEP_TIMEOUT_MS;
244
270
  }
245
271
 
@@ -315,7 +341,15 @@ export class XArticleBrowserTransport implements PublishTransport {
315
341
  });
316
342
  await page.locator(writeSelector).first().click();
317
343
 
318
- const titleSelector = await resolveSelector(page, ARTICLE_SELECTORS.title, {
344
+ // X collapses the title into a display div (`[data-testid="twitter-article-title"]`, text
345
+ // `(Needs title)`); the real `textarea[placeholder="Add a title"]` is only mounted after
346
+ // that div is clicked (measured live 2026-10-07, 0194). So: fill the textarea when it is
347
+ // already attached, otherwise click the display div and fill the textarea it mounts.
348
+ const titleInput = ARTICLE_SELECTORS.title[0];
349
+ if ((await page.locator(titleInput).first().count()) === 0) {
350
+ await page.locator(ARTICLE_SELECTORS.title[1]).first().click({ timeout: this.stepTimeoutMs });
351
+ }
352
+ const titleSelector = await resolveSelector(page, [titleInput], {
319
353
  timeoutMs: this.stepTimeoutMs,
320
354
  label: 'title input',
321
355
  });
@@ -333,17 +367,27 @@ export class XArticleBrowserTransport implements PublishTransport {
333
367
  await pasteHtml(page, bodySelector, markdownToHtml(content.body), { timeoutMs: this.stepTimeoutMs });
334
368
 
335
369
  if (coverPath !== undefined) {
336
- // The photo picker is opened by a control when the composer renders it; the file input
337
- // itself is routinely hidden behind it, so it is found by attachment (R2).
338
- const addPhotos = await trySelectors(page, ARTICLE_SELECTORS.addPhotos, { visible: false });
339
- if (addPhotos.found && addPhotos.selector !== null) {
340
- await page.locator(addPhotos.selector).first().click();
341
- }
370
+ // The base `fileInput` handles a set file directly: X itself opens an "Edit media" crop
371
+ // dialog, and the cover attaches only when that dialog is confirmed with Apply.
372
+ // Clicking the add-photos picker first detaches that handler — a file set while the
373
+ // picker is open uploads nothing (measured live 2026-10-07, 0194). So: set the file,
374
+ // click Apply, and require the cover image to render (E91-D6: observed outcome only).
342
375
  const fileInputSelector = await resolveSelector(page, ARTICLE_SELECTORS.fileInput, {
343
376
  timeoutMs: this.stepTimeoutMs,
344
377
  label: 'cover file input',
345
378
  });
346
379
  await uploadFile(page, fileInputSelector, coverPath, { timeoutMs: this.stepTimeoutMs });
380
+ // The crop dialog renders two identical "Edit media" layers; click the visible Apply.
381
+ await page
382
+ .locator('[role="dialog"] button:has-text("Apply") >> visible=true')
383
+ .first()
384
+ .click({ timeout: this.stepTimeoutMs });
385
+ const attached = await expectOutcome(
386
+ async () =>
387
+ (await page.evaluate(coverAttachedScript())) === true ? 'cover image rendered in composer' : null,
388
+ { timeoutMs: this.stepTimeoutMs, label: 'cover attachment' },
389
+ );
390
+ if (!attached.ok) return fail(attached.error);
347
391
  }
348
392
 
349
393
  const draft = await expectOutcome(() => probeDraft(page), {
@@ -393,6 +437,11 @@ async function probeDraft(page: Page): Promise<string | null> {
393
437
  return ARTICLE_EDIT_RE.test(url) ? url : null;
394
438
  }
395
439
 
440
+ /** DOM probe script: true once a non-avatar twimg image — the just-attached cover — is rendered. */
441
+ function coverAttachedScript(): string {
442
+ return `[...document.querySelectorAll('img[src*="twimg"]')].some((img) => !/profile_images/.test(img.src ?? ''))`;
443
+ }
444
+
396
445
  /** Live evidence is URL-only too: a published article URL that left the `/edit/` composer. */
397
446
  async function probeLive(page: Page): Promise<string | null> {
398
447
  const url = page.url();
@@ -431,7 +480,12 @@ export async function processPublishIO(
431
480
  await writePublishResult(outputPath, fail(message));
432
481
  throw new Error(message);
433
482
  }
434
- const operation = resolveOperation(options, content);
483
+ const mode = resolveOperation(options, content);
484
+ if (mode.error !== undefined) {
485
+ await writePublishResult(outputPath, fail(mode.error));
486
+ throw new Error(mode.error);
487
+ }
488
+ const operation = mode.operation;
435
489
  const resolved = transport ?? getPublishTransport(operation);
436
490
  const result = ResultSchema.parse(await resolved.publish({ content, options }));
437
491
  await writePublishResult(outputPath, result);