@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/dist/index.js +43047 -24724
- package/package.json +4 -4
- package/plugins/generations/content-gen/dist/index.js +10 -9
- package/plugins/generations/core-facts-gen/dist/index.js +13 -12
- package/plugins/generations/daily-article-gen/dist/index.js +16 -21
- package/plugins/generations/daily-article-gen/src/index.ts +8 -12
- package/plugins/generations/dailynews-gen/dist/index.js +23 -21
- package/plugins/generations/dailynews-gen/src/script-builder.ts +25 -26
- package/plugins/generations/episode-plan-gen/dist/index.js +58 -15
- package/plugins/generations/episode-plan-gen/src/index.ts +76 -6
- package/plugins/generations/gemini-tts/README.md +11 -0
- package/plugins/generations/gemini-tts/dist/index.js +10 -9
- package/plugins/generations/image-gen/dist/index.js +171 -153
- package/plugins/generations/image-gen/presets/formats/xhs-card.yaml +16 -14
- package/plugins/generations/image-gen/presets/styles/xhs-knowledge.yaml +2 -2
- package/plugins/generations/image-gen/src/compose/card.ts +17 -2
- package/plugins/generations/image-gen/src/compose/chromium.ts +60 -42
- package/plugins/generations/news-report-gen/dist/index.js +13 -12
- package/plugins/generations/voice-gen/dist/index.js +10 -9
- package/plugins/ingestions/aihot-ingest/dist/index.js +13 -12
- package/plugins/ingestions/horizon-ingest/dist/index.js +13 -12
- package/plugins/ingestions/karakeep-local/dist/index.js +13 -13
- package/plugins/ingestions/last30days-ingest/dist/index.js +13 -12
- package/plugins/ingestions/web-search/dist/index.js +11 -10
- package/plugins/kk/README.md +38 -0
- package/plugins/kk/decisions/extract/ai-news.yaml +27 -0
- package/plugins/kk/decisions/kk-items.yaml +179 -0
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/schedules/cron.tmpl +15 -0
- package/plugins/kk/schedules/launchd.plist.tmpl +77 -0
- package/plugins/kk/scripts/daily-stages.ts +2023 -0
- package/plugins/kk/scripts/daily-voice-style.zh.md +15 -10
- package/plugins/kk/scripts/kk-workflow-stages.ts +253 -1368
- package/plugins/kk/skills/content-safety/SKILL.md +39 -8
- package/plugins/kk/skills/content-safety/references/platforms/apple-podcasts.md +2 -2
- package/plugins/kk/skills/web-automation/SKILL.md +100 -0
- package/plugins/kk/skills/web-automation/references/browser-launch.md +86 -0
- package/plugins/kk/workflows/kk-collect.yaml +256 -0
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +9 -3
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +774 -0
- package/plugins/kk/workflows/kk-daily-safety.yaml +167 -0
- package/plugins/publishings/emdash-pub/dist/index.js +10 -9
- package/plugins/publishings/infoq-pub/dist/index.js +152 -59
- package/plugins/publishings/infoq-pub/plugin.json +1 -0
- package/plugins/publishings/infoq-pub/src/index.ts +167 -55
- package/plugins/publishings/infoq-pub/src/profile-check.ts +4 -2
- package/plugins/publishings/juejin-pub/dist/index.js +111 -30
- package/plugins/publishings/juejin-pub/plugin.json +1 -0
- package/plugins/publishings/juejin-pub/src/index.ts +49 -9
- package/plugins/publishings/medium-pub/dist/index.js +10 -9
- package/plugins/publishings/podcast-pub/dist/index.js +10 -9
- package/plugins/publishings/qiita-pub/dist/index.js +10 -9
- package/plugins/publishings/substack-pub/dist/index.js +105 -28
- package/plugins/publishings/substack-pub/plugin.json +1 -0
- package/plugins/publishings/substack-pub/src/index.ts +39 -8
- package/plugins/publishings/substack-pub/src/profile-check.ts +2 -0
- package/plugins/publishings/surfdash-pub/dist/index.js +10 -9
- package/plugins/publishings/wechatmp-pub/dist/index.js +115 -31
- package/plugins/publishings/wechatmp-pub/plugin.json +1 -0
- package/plugins/publishings/wechatmp-pub/src/index.ts +42 -9
- package/plugins/publishings/wechatmp-pub/src/profile-check.ts +84 -40
- package/plugins/publishings/x-pub/dist/index.js +114 -35
- package/plugins/publishings/x-pub/plugin.json +1 -0
- package/plugins/publishings/x-pub/src/index.ts +68 -14
- package/plugins/publishings/xhs-pub/dist/index.js +154 -59
- package/plugins/publishings/xhs-pub/plugin.json +1 -0
- package/plugins/publishings/xhs-pub/src/flow.ts +9 -3
- package/plugins/publishings/xhs-pub/src/index.ts +119 -9
- package/plugins/publishings/xhs-pub/src/page.ts +4 -17
- package/plugins/publishings/xhs-pub/src/profile-check.ts +28 -15
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
// ../../
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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((
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
160
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
|
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);
|