@gobing-ai/knowledge-kit 0.0.32 → 0.0.33

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 (65) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/index.js +32320 -15021
  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 +14 -13
  7. package/plugins/generations/daily-article-gen/src/index.ts +1 -1
  8. package/plugins/generations/dailynews-gen/dist/index.js +10 -9
  9. package/plugins/generations/episode-plan-gen/dist/index.js +58 -15
  10. package/plugins/generations/episode-plan-gen/src/index.ts +76 -6
  11. package/plugins/generations/gemini-tts/README.md +11 -0
  12. package/plugins/generations/gemini-tts/dist/index.js +10 -9
  13. package/plugins/generations/image-gen/dist/index.js +154 -151
  14. package/plugins/generations/image-gen/src/compose/chromium.ts +60 -42
  15. package/plugins/generations/news-report-gen/dist/index.js +13 -12
  16. package/plugins/generations/voice-gen/dist/index.js +10 -9
  17. package/plugins/ingestions/aihot-ingest/dist/index.js +13 -12
  18. package/plugins/ingestions/horizon-ingest/dist/index.js +13 -12
  19. package/plugins/ingestions/karakeep-local/dist/index.js +13 -13
  20. package/plugins/ingestions/last30days-ingest/dist/index.js +13 -12
  21. package/plugins/ingestions/web-search/dist/index.js +11 -10
  22. package/plugins/kk/README.md +38 -0
  23. package/plugins/kk/decisions/extract/ai-news.yaml +21 -0
  24. package/plugins/kk/decisions/kk-items.yaml +113 -0
  25. package/plugins/kk/plugin.json +1 -1
  26. package/plugins/kk/schedules/cron.tmpl +15 -0
  27. package/plugins/kk/schedules/launchd.plist.tmpl +77 -0
  28. package/plugins/kk/scripts/daily-stages.ts +1533 -0
  29. package/plugins/kk/scripts/kk-workflow-stages.ts +247 -1365
  30. package/plugins/kk/skills/content-safety/SKILL.md +38 -7
  31. package/plugins/kk/skills/web-automation/SKILL.md +100 -0
  32. package/plugins/kk/skills/web-automation/references/browser-launch.md +86 -0
  33. package/plugins/kk/workflows/kk-collect.yaml +258 -0
  34. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +9 -3
  35. package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +655 -0
  36. package/plugins/kk/workflows/kk-daily-safety.yaml +170 -0
  37. package/plugins/publishings/emdash-pub/dist/index.js +10 -9
  38. package/plugins/publishings/infoq-pub/dist/index.js +152 -59
  39. package/plugins/publishings/infoq-pub/plugin.json +1 -0
  40. package/plugins/publishings/infoq-pub/src/index.ts +167 -55
  41. package/plugins/publishings/infoq-pub/src/profile-check.ts +4 -2
  42. package/plugins/publishings/juejin-pub/dist/index.js +111 -30
  43. package/plugins/publishings/juejin-pub/plugin.json +1 -0
  44. package/plugins/publishings/juejin-pub/src/index.ts +49 -9
  45. package/plugins/publishings/medium-pub/dist/index.js +10 -9
  46. package/plugins/publishings/podcast-pub/dist/index.js +10 -9
  47. package/plugins/publishings/qiita-pub/dist/index.js +10 -9
  48. package/plugins/publishings/substack-pub/dist/index.js +105 -28
  49. package/plugins/publishings/substack-pub/plugin.json +1 -0
  50. package/plugins/publishings/substack-pub/src/index.ts +39 -8
  51. package/plugins/publishings/substack-pub/src/profile-check.ts +2 -0
  52. package/plugins/publishings/surfdash-pub/dist/index.js +10 -9
  53. package/plugins/publishings/wechatmp-pub/dist/index.js +115 -31
  54. package/plugins/publishings/wechatmp-pub/plugin.json +1 -0
  55. package/plugins/publishings/wechatmp-pub/src/index.ts +42 -9
  56. package/plugins/publishings/wechatmp-pub/src/profile-check.ts +84 -40
  57. package/plugins/publishings/x-pub/dist/index.js +114 -35
  58. package/plugins/publishings/x-pub/plugin.json +1 -0
  59. package/plugins/publishings/x-pub/src/index.ts +68 -14
  60. package/plugins/publishings/xhs-pub/dist/index.js +100 -54
  61. package/plugins/publishings/xhs-pub/plugin.json +1 -0
  62. package/plugins/publishings/xhs-pub/src/index.ts +8 -4
  63. package/plugins/publishings/xhs-pub/src/page.ts +4 -17
  64. package/plugins/publishings/xhs-pub/src/profile-check.ts +28 -15
  65. 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.18/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.18/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.18/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.18/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.18/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.18/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.18/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.18/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.18+ea28d47622d670ff/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.18+ea28d47622d670ff/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.18+ea28d47622d670ff/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.18+ea28d47622d670ff/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.18+ea28d47622d670ff/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.18+ea28d47622d670ff/node_modules/@gobing-ai/ts-runtime/dist/runtime-node-bun.js
29598
29598
  function getNodeFileSystem() {
29599
29599
  if (!_nodeFileSystem)
29600
29600
  _nodeFileSystem = createNodeFileSystem();
@@ -29740,15 +29740,16 @@ var PluginManifestSchema = exports_external.looseObject({
29740
29740
  name: exports_external.string().min(1),
29741
29741
  kind: PluginKindSchema,
29742
29742
  entry: exports_external.string().min(1),
29743
+ check: exports_external.string().min(1).optional(),
29743
29744
  version: exports_external.string().optional(),
29744
29745
  description: exports_external.string().optional(),
29745
29746
  operations: exports_external.record(exports_external.string(), PluginOperationSchema).optional()
29746
29747
  });
29747
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/index.js
29748
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.18+ea28d47622d670ff/node_modules/@gobing-ai/ts-runtime/dist/index.js
29748
29749
  init_file_system_node();
29749
29750
  init_config();
29750
29751
 
29751
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/path.js
29752
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.18+ea28d47622d670ff/node_modules/@gobing-ai/ts-runtime/dist/path.js
29752
29753
  function normalizeSeparators(path6) {
29753
29754
  return path6.replaceAll("\\", "/");
29754
29755
  }
@@ -29806,7 +29807,7 @@ function getProcessCwd() {
29806
29807
  return globalThis.process?.cwd?.() ?? "/";
29807
29808
  }
29808
29809
 
29809
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/index.js
29810
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.18+ea28d47622d670ff/node_modules/@gobing-ai/ts-runtime/dist/index.js
29810
29811
  init_runtime_node_bun();
29811
29812
 
29812
29813
  // ../../packages/kk-core/src/render-md.ts
@@ -29919,7 +29920,10 @@ async function trySelectors(page, selectors, options = {}) {
29919
29920
  return { found: false, selector: null };
29920
29921
  }
29921
29922
 
29922
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/errors.js
29923
+ // ../../packages/publish-harness/src/browser.ts
29924
+ import { resolve as resolve2 } from "path";
29925
+
29926
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.18+1f5782d4d327a230/node_modules/@gobing-ai/ts-browser-automation/dist/errors.js
29923
29927
  class BrowserAutomationError extends Error {
29924
29928
  constructor(message, options) {
29925
29929
  super(message, options);
@@ -29954,10 +29958,10 @@ class PlaywrightUnavailableError extends BrowserAutomationError {
29954
29958
  this.name = "PlaywrightUnavailableError";
29955
29959
  }
29956
29960
  }
29957
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29961
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.18+1f5782d4d327a230/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29958
29962
  import { mkdir } from "fs/promises";
29959
29963
 
29960
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/profile-dir.js
29964
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.18+1f5782d4d327a230/node_modules/@gobing-ai/ts-browser-automation/dist/profile-dir.js
29961
29965
  function resolveProfileDir(profileDir) {
29962
29966
  if (profileDir.trim() === "") {
29963
29967
  throw new InvalidProfileDirError;
@@ -29969,7 +29973,7 @@ function resolveProfileDir(profileDir) {
29969
29973
  return resolved;
29970
29974
  }
29971
29975
 
29972
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29976
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.18+1f5782d4d327a230/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29973
29977
  var PROFILE_DIR_MODE = 448;
29974
29978
  var BUSY_PROFILE_PATTERN = /singleton|already in use|in use by another/i;
29975
29979
  var CHROMIUM_MISSING_PATTERN = /playwright install|executable doesn't exist/i;
@@ -30020,7 +30024,7 @@ function createPersistentLauncher(options = {}) {
30020
30024
  };
30021
30025
  }
30022
30026
 
30023
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/operations.js
30027
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.18+1f5782d4d327a230/node_modules/@gobing-ai/ts-browser-automation/dist/operations.js
30024
30028
  var defaultLauncher;
30025
30029
  function getDefaultLauncher() {
30026
30030
  if (defaultLauncher === undefined)
@@ -30033,17 +30037,73 @@ async function openPersistentBrowser(options) {
30033
30037
  headless: options.headless
30034
30038
  });
30035
30039
  }
30040
+ // ../../packages/publish-harness/src/profile-dir.ts
30041
+ import { mkdir as mkdir2, stat } from "fs/promises";
30042
+ async function ensureProfileDir2(profileDir, headless) {
30043
+ const existing = await stat(profileDir).catch(() => null);
30044
+ if (existing) {
30045
+ if (!existing.isDirectory())
30046
+ throw new InvalidProfileDirError(`profileDir is not a directory: "${profileDir}".`);
30047
+ return;
30048
+ }
30049
+ if (headless)
30050
+ throw new BrowserProfileMissingError(profileDir);
30051
+ await mkdir2(profileDir, { recursive: true, mode: 448 });
30052
+ }
30053
+
30036
30054
  // ../../packages/publish-harness/src/browser.ts
30037
30055
  var DEFAULT_STEP_TIMEOUT_MS = 30000;
30038
30056
  var DEFAULT_POLL_MS = 500;
30039
30057
  var ATTACH_POLL_MS = 100;
30058
+ function createKkBrowserLauncher(loadPlaywright = () => import("playwright")) {
30059
+ return {
30060
+ async launch(request) {
30061
+ if (request.profileDir.trim() === "")
30062
+ throw new InvalidProfileDirError;
30063
+ const profileDir = resolve2(request.profileDir);
30064
+ await ensureProfileDir2(profileDir, request.headless);
30065
+ let playwright;
30066
+ try {
30067
+ playwright = await loadPlaywright();
30068
+ } catch (error51) {
30069
+ throw new PlaywrightUnavailableError('The "playwright" dependency is not installed. Run `bunx playwright install chromium`.', { cause: error51 });
30070
+ }
30071
+ const channel = process.env.KK_BROWSER_CHANNEL?.trim() || "chromium";
30072
+ let context2;
30073
+ try {
30074
+ context2 = await playwright.chromium.launchPersistentContext(profileDir, {
30075
+ headless: request.headless,
30076
+ channel,
30077
+ args: ["--disable-blink-features=AutomationControlled"],
30078
+ ignoreDefaultArgs: ["--enable-automation"]
30079
+ });
30080
+ } catch (error51) {
30081
+ const message = errorMessage2(error51);
30082
+ if (/singleton|already in use|in use by another/i.test(message)) {
30083
+ throw new BrowserProfileBusyError(profileDir, error51);
30084
+ }
30085
+ if (/playwright install|executable doesn't exist/i.test(message)) {
30086
+ 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 });
30087
+ }
30088
+ throw error51;
30089
+ }
30090
+ await context2.addInitScript(() => {
30091
+ Object.defineProperty(navigator, "webdriver", { get: () => {
30092
+ return;
30093
+ } });
30094
+ });
30095
+ return context2;
30096
+ }
30097
+ };
30098
+ }
30099
+ var kkBrowserLauncher = createKkBrowserLauncher();
30040
30100
  async function waitUntil(predicate, timeoutMs, pollMs = DEFAULT_POLL_MS) {
30041
30101
  const deadline = Date.now() + timeoutMs;
30042
30102
  while (Date.now() < deadline) {
30043
30103
  if (await predicate()) {
30044
30104
  return true;
30045
30105
  }
30046
- await new Promise((resolve2) => setTimeout(resolve2, pollMs));
30106
+ await new Promise((resolve3) => setTimeout(resolve3, pollMs));
30047
30107
  }
30048
30108
  return predicate();
30049
30109
  }
@@ -30121,6 +30181,9 @@ async function resolveSelector(page, selectors, opts = {}) {
30121
30181
  }
30122
30182
  return selector;
30123
30183
  }
30184
+ function errorMessage2(error51) {
30185
+ return error51 instanceof Error ? error51.message : String(error51);
30186
+ }
30124
30187
  function pasteScript(selector, html) {
30125
30188
  const text = html.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
30126
30189
  return `(() => {
@@ -30736,7 +30799,7 @@ function fail(error51) {
30736
30799
  return ResultSchema.parse({ ok: false, target: TARGET, error: error51 });
30737
30800
  }
30738
30801
  function sleep(ms) {
30739
- return new Promise((resolve2) => setTimeout(resolve2, ms));
30802
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
30740
30803
  }
30741
30804
  function titleOf(content) {
30742
30805
  return (content.title ?? "").trim();
@@ -31218,22 +31281,42 @@ function isWechatLoggedIn(page) {
31218
31281
  function storageStatePath(profileDir) {
31219
31282
  return `${profileDir}/storage-state.json`;
31220
31283
  }
31221
- async function checkWechatSession(profileDir) {
31222
- const { page, context: context2 } = await openWithState(profileDir, true);
31284
+ async function checkWechatSession(profileDir, opts = {}) {
31285
+ const { page, context: context2 } = await openWithState(profileDir, opts.headless ?? true);
31223
31286
  try {
31224
- await pollLogin(page);
31287
+ await pollLogin(page, opts.timeoutMs);
31225
31288
  } finally {
31226
31289
  await context2.close();
31227
31290
  }
31228
31291
  }
31229
- async function pollLogin(page) {
31230
- const ready = await waitUntil(() => Promise.resolve(isConsoleReady(page)), CONSOLE_READY_TIMEOUT_MS, 1000);
31292
+ async function pollLogin(page, timeoutMs) {
31293
+ const deadline = timeoutMs ?? CONSOLE_READY_TIMEOUT_MS;
31294
+ const ready = await waitUntil(() => Promise.resolve(isConsoleReady(page)), deadline, 1000);
31231
31295
  if (ready)
31232
31296
  return;
31233
- throw new Error(`Authentication was not confirmed within ${CONSOLE_READY_TIMEOUT_MS}ms \u2014 the saved session state has ` + "likely expired: re-run `bun src/profile-check.ts login` to sign in again.");
31297
+ throw new Error(`Authentication was not confirmed within ${deadline}ms \u2014 the saved session state has ` + "likely expired: re-run `bun src/profile-check.ts login` to sign in again.");
31298
+ }
31299
+ async function openWechatLogin(opts) {
31300
+ const timeoutMs = opts.timeoutMs ?? 180000;
31301
+ const context2 = await openPersistentBrowser({
31302
+ profileDir: opts.profileDir,
31303
+ headless: false,
31304
+ launcher: kkBrowserLauncher
31305
+ });
31306
+ try {
31307
+ const page = context2.pages()[0] ?? await context2.newPage();
31308
+ await page.goto(CONSOLE_URL);
31309
+ const ready = await waitUntil(() => Promise.resolve(page.url().includes(CONSOLE_HOME_FRAGMENT)), timeoutMs, 1000);
31310
+ if (!ready)
31311
+ throw new Error(`QR scan not completed within ${timeoutMs / 1000}s`);
31312
+ await page.waitForTimeout(1e4);
31313
+ await context2.storageState({ path: storageStatePath(opts.profileDir) });
31314
+ } finally {
31315
+ await context2.close();
31316
+ }
31234
31317
  }
31235
31318
  async function openWithState(profileDir, headless) {
31236
- const context2 = await openPersistentBrowser({ profileDir, headless });
31319
+ const context2 = await openPersistentBrowser({ profileDir, headless, launcher: kkBrowserLauncher });
31237
31320
  try {
31238
31321
  const stateFile = storageStatePath(profileDir);
31239
31322
  let state;
@@ -31390,6 +31473,7 @@ export {
31390
31473
  storageStatePath,
31391
31474
  runMode,
31392
31475
  processPublishIO,
31476
+ openWechatLogin,
31393
31477
  markdownToWechatHtml,
31394
31478
  main2 as main,
31395
31479
  isWechatLoggedIn,
@@ -2,6 +2,7 @@
2
2
  "name": "wechatmp-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": "Publish plugin for the WeChat MP console via ts-browser-automation: draft-box first (ADR-022), headed publish behind a session preflight"
7
8
  }
@@ -6,7 +6,7 @@ import {
6
6
  type PublishTransportPayload,
7
7
  resolveBrowserProfileDir,
8
8
  } from '@gobing-ai/publish-harness';
9
- import { waitUntil } from '@gobing-ai/publish-harness/browser';
9
+ import { kkBrowserLauncher, waitUntil } from '@gobing-ai/publish-harness/browser';
10
10
  import { BrowserProfileMissingError, openPersistentBrowser } from '@gobing-ai/ts-browser-automation';
11
11
  import { createNodeFileSystem } from '@gobing-ai/ts-runtime';
12
12
  import { echoError } from '@gobing-ai/ts-utils';
@@ -74,28 +74,61 @@ export function storageStatePath(profileDir: string): string {
74
74
  return `${profileDir}/storage-state.json`;
75
75
  }
76
76
 
77
- /** Headless session preflight: throws (incl. BrowserProfileMissingError) when the profile cannot drive the console. */
78
- export async function checkWechatSession(profileDir: string): Promise<void> {
79
- const { page, context } = await openWithState(profileDir, true);
77
+ /** Headless session preflight: throws (incl. BrowserProfileMissingError) when the profile cannot drive the console.
78
+ * 0216: `headless: false` probes headed only and `timeoutMs` overrides the ready deadline. */
79
+ export async function checkWechatSession(
80
+ profileDir: string,
81
+ opts: { headless?: boolean; timeoutMs?: number } = {},
82
+ ): Promise<void> {
83
+ const { page, context } = await openWithState(profileDir, opts.headless ?? true);
80
84
  try {
81
- await pollLogin(page);
85
+ await pollLogin(page, opts.timeoutMs);
82
86
  } finally {
83
87
  await context.close();
84
88
  }
85
89
  }
86
90
 
87
- async function pollLogin(page: Page): Promise<void> {
88
- const ready = await waitUntil(() => Promise.resolve(isConsoleReady(page)), CONSOLE_READY_TIMEOUT_MS, 1000);
91
+ async function pollLogin(page: Page, timeoutMs?: number): Promise<void> {
92
+ const deadline = timeoutMs ?? CONSOLE_READY_TIMEOUT_MS;
93
+ const ready = await waitUntil(() => Promise.resolve(isConsoleReady(page)), deadline, 1000);
89
94
  if (ready) return;
90
95
  throw new Error(
91
- `Authentication was not confirmed within ${CONSOLE_READY_TIMEOUT_MS}ms — the saved session state has ` +
96
+ `Authentication was not confirmed within ${deadline}ms — the saved session state has ` +
92
97
  'likely expired: re-run `bun src/profile-check.ts login` to sign in again.',
93
98
  );
94
99
  }
95
100
 
101
+ /** Headed one-time login: open the console, wait for the QR scan to land on `/cgi-bin/home`, let
102
+ * the console finish its post-auth Set-Cookies, then capture the durable storageState file —
103
+ * mp.weixin.qq.com grant cookies do not survive a Chromium restart, so the saved state (not the
104
+ * profile cookie store) is the session artifact the publish flow re-injects. Shared by the CLI
105
+ * (`profile-check login`, 0216) so the publish flow and the probe use one login path. */
106
+ export async function openWechatLogin(opts: { profileDir: string; timeoutMs?: number }): Promise<void> {
107
+ const timeoutMs = opts.timeoutMs ?? 180_000;
108
+ const context = await openPersistentBrowser({
109
+ profileDir: opts.profileDir,
110
+ headless: false,
111
+ launcher: kkBrowserLauncher,
112
+ });
113
+ try {
114
+ const page = context.pages()[0] ?? (await context.newPage());
115
+ await page.goto(CONSOLE_URL);
116
+ const ready = await waitUntil(
117
+ () => Promise.resolve(page.url().includes(CONSOLE_HOME_FRAGMENT)),
118
+ timeoutMs,
119
+ 1000,
120
+ );
121
+ if (!ready) throw new Error(`QR scan not completed within ${timeoutMs / 1000}s`);
122
+ await page.waitForTimeout(10_000); // settle: let the console finish its post-auth Set-Cookies
123
+ await context.storageState({ path: storageStatePath(opts.profileDir) });
124
+ } finally {
125
+ await context.close();
126
+ }
127
+ }
128
+
96
129
  /** Launch, inject the saved login cookies, and land on the console. Caller owns closing the context. */
97
130
  async function openWithState(profileDir: string, headless: boolean): Promise<{ page: Page; context: BrowserContext }> {
98
- const context = await openPersistentBrowser({ profileDir, headless });
131
+ const context = await openPersistentBrowser({ profileDir, headless, launcher: kkBrowserLauncher });
99
132
  try {
100
133
  const stateFile = storageStatePath(profileDir);
101
134
  let state:
@@ -1,48 +1,92 @@
1
- import { resolveBrowserProfileDir } from '@gobing-ai/publish-harness';
2
- import { waitUntil } from '@gobing-ai/publish-harness/browser';
3
- import { openPersistentBrowser } from '@gobing-ai/ts-browser-automation';
4
- import { echo } from '@gobing-ai/ts-utils';
5
- import { checkWechatSession, storageStatePath } from './index';
1
+ /**
2
+ * WeChat MP profile-session capability (0216, ADR-032 probe contract).
3
+ *
4
+ * `login` opens the console headed and resolves once the QR scan lands on `/cgi-bin/home`, then
5
+ * captures the durable `storageState` file — mp.weixin.qq.com grant cookies do not survive a
6
+ * Chromium restart, so the saved state (not the profile cookie store) is the session artifact.
7
+ * `check` reopens the profile with that state re-injected and reports `verified` or `refused`
8
+ * as the LAST line on stdout, one JSON object — `kk plugin check` parses it. Exit codes:
9
+ * 0 verified, 1 refused, 2 usage error. No cookie, token or storageState value is ever printed;
10
+ * the profile directory is the only detail that leaves the process.
11
+ */
12
+ import { parseArgs } from 'node:util';
13
+ import { resolveChannelProfileDir } from '@gobing-ai/publish-harness/browser';
14
+ import { echo, echoError } from '@gobing-ai/ts-utils';
15
+ import { CHANNEL, checkWechatSession, openWechatLogin } from './index';
6
16
 
7
- const CONSOLE_URL = 'https://mp.weixin.qq.com/';
17
+ const USAGE = 'usage: wechatmp profile-check [login|check] [--profile <dir>] [--timeout <ms>] [--headed]';
8
18
  const LOGIN_TIMEOUT_MS = 180_000;
9
- const LOGGED_IN_FRAGMENT = '/cgi-bin/home';
10
19
 
11
- const profileDir = resolveBrowserProfileDir('wechatmp', process.env.WECHATMP_PROFILE_DIR);
12
- const verb = process.argv[2] ?? 'check';
20
+ interface WechatVerdict {
21
+ status: 'verified' | 'refused';
22
+ profileDir: string;
23
+ detail: string;
24
+ }
25
+
26
+ export async function main(argv: string[] = process.argv.slice(2)): Promise<number> {
27
+ let command: string;
28
+ let profileDir: string;
29
+ let timeoutMs: number | undefined;
30
+ let headed = false;
31
+ try {
32
+ const { positionals, values } = parseArgs({
33
+ args: argv,
34
+ allowPositionals: true,
35
+ options: {
36
+ profile: { type: 'string' },
37
+ timeout: { type: 'string' },
38
+ headed: { type: 'boolean' },
39
+ },
40
+ });
41
+ command = positionals[0] ?? 'check';
42
+ if (command !== 'login' && command !== 'check') {
43
+ throw new Error(`unknown command '${command}'`);
44
+ }
45
+ headed = values.headed === true;
46
+ if (values.timeout !== undefined) {
47
+ timeoutMs = Number(values.timeout);
48
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
49
+ throw new Error(`--timeout must be a non-negative number of ms, got '${values.timeout}'`);
50
+ }
51
+ }
52
+ profileDir = resolveChannelProfileDir(CHANNEL, values.profile);
53
+ } catch (error) {
54
+ const message = error instanceof Error ? error.message : String(error);
55
+ echoError(`wechatmp profile-check: ${message} — ${USAGE}`);
56
+ echo(JSON.stringify({ status: 'error', detail: message }));
57
+ return 2;
58
+ }
13
59
 
14
- try {
15
- if (verb === 'login') {
16
- echo(
17
- `wechatmp login: opening the console in a headed browser — scan the QR once (timeout ${LOGIN_TIMEOUT_MS / 1000}s)`,
18
- );
19
- const context = await openPersistentBrowser({ profileDir, headless: false });
60
+ if (command === 'login') {
20
61
  try {
21
- const page = context.pages()[0] ?? (await context.newPage());
22
- await page.goto(CONSOLE_URL);
23
- const ready = await waitUntil(
24
- () => Promise.resolve(page.url().includes(LOGGED_IN_FRAGMENT)),
25
- LOGIN_TIMEOUT_MS,
26
- 1000,
27
- );
28
- if (!ready) throw new Error(`QR scan not completed within ${LOGIN_TIMEOUT_MS / 1000}s`);
29
- await page.waitForTimeout(10_000); // settle: let the console finish its post-auth Set-Cookies
30
- // The Chromium profile store drops the grant cookies on restart even though they carry
31
- // multi-day expiries — the durable session artifact is the captured storageState file,
32
- // which the publish flow re-injects into every launch.
33
- await context.storageState({ path: storageStatePath(profileDir) });
34
- echo(`wechatmp login: session saved in ${storageStatePath(profileDir)}`);
35
- } finally {
36
- await context.close();
62
+ await openWechatLogin({ profileDir, timeoutMs: timeoutMs ?? LOGIN_TIMEOUT_MS });
63
+ echo(`wechatmp profile login verified: ${profileDir}`);
64
+ return 0;
65
+ } catch (error) {
66
+ echoError(`wechatmp profile login failed: ${error instanceof Error ? error.message : String(error)}`);
67
+ return 1;
37
68
  }
38
- } else if (verb === 'check') {
39
- await checkWechatSession(profileDir);
40
- echo(`wechatmp session: OK (${profileDir})`);
41
- } else {
42
- echo('usage: bun src/profile-check.ts login|check');
43
- process.exit(1);
44
69
  }
45
- } catch (error: unknown) {
46
- echo(`wechatmp ${verb}: failed — ${error instanceof Error ? error.message : String(error)}`);
47
- process.exit(1);
70
+
71
+ let verdict: WechatVerdict;
72
+ try {
73
+ await checkWechatSession(profileDir, { headless: !headed, timeoutMs });
74
+ verdict = {
75
+ status: 'verified',
76
+ profileDir,
77
+ detail: `authenticated wechatmp console session observed ${headed ? 'via a headed pass' : 'headlessly'} via profile ${profileDir} (read-only)`,
78
+ };
79
+ } catch (error) {
80
+ verdict = {
81
+ status: 'refused',
82
+ profileDir,
83
+ detail: `wechatmp readiness refused: ${error instanceof Error ? error.message : String(error)}`,
84
+ };
85
+ }
86
+ echo(JSON.stringify(verdict));
87
+ return verdict.status === 'verified' ? 0 : 1;
88
+ }
89
+
90
+ if (import.meta.main) {
91
+ process.exit(await main());
48
92
  }