@gobing-ai/knowledge-kit 0.0.36 → 0.1.0

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 (159) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/board/collect/index.css +654 -16
  3. package/dist/board/collect/index.js +191 -64
  4. package/dist/board/daily-voice/index.css +679 -12
  5. package/dist/board/daily-voice/index.js +185 -66
  6. package/dist/board/itc/index.css +679 -13
  7. package/dist/board/itc/index.js +188 -66
  8. package/dist/index.js +2878 -152
  9. package/package.json +3 -2
  10. package/plugins/generations/content-gen/dist/index.js +158 -12
  11. package/plugins/generations/core-facts-gen/dist/index.js +158 -12
  12. package/plugins/generations/daily-article-gen/dist/index.js +158 -12
  13. package/plugins/generations/dailynews-gen/dist/index.js +180 -12
  14. package/plugins/generations/dailynews-gen/src/script-plan.ts +32 -0
  15. package/plugins/generations/episode-plan-gen/dist/index.js +158 -12
  16. package/plugins/generations/gemini-tts/README.md +4 -3
  17. package/plugins/generations/gemini-tts/dist/index.js +158 -12
  18. package/plugins/generations/image-gen/dist/index.js +158 -12
  19. package/plugins/generations/news-report-gen/dist/index.js +158 -12
  20. package/plugins/generations/omni-voice-gen/README.md +15 -4
  21. package/plugins/generations/omni-voice-gen/profiles.json +1 -2
  22. package/plugins/generations/voice-gen/dist/index.js +158 -12
  23. package/plugins/generations/voxcpm-tts/README.md +21 -1
  24. package/plugins/generations/voxcpm-tts/dist/index.js +173 -13
  25. package/plugins/generations/voxcpm-tts/scripts/audit-speaker-consistency.py +109 -0
  26. package/plugins/generations/voxcpm-tts/src/voxcpm-client.ts +28 -1
  27. package/plugins/ingestions/aihot-ingest/dist/index.js +158 -12
  28. package/plugins/ingestions/horizon-ingest/dist/index.js +158 -12
  29. package/plugins/ingestions/last30days-ingest/dist/index.js +158 -12
  30. package/plugins/ingestions/web-search/dist/index.js +158 -12
  31. package/plugins/kk/commands/workflow-run.md +56 -18
  32. package/plugins/kk/decisions/kk-itc.yaml +46 -0
  33. package/plugins/kk/decisions/kk-seo-keyword.yaml +57 -0
  34. package/plugins/kk/decisions/kk-seo-page.yaml +230 -0
  35. package/plugins/kk/decisions/kk-seo-pair.yaml +21 -0
  36. package/plugins/kk/decisions/kk-seo-site.yaml +90 -0
  37. package/plugins/kk/plugin.json +1 -1
  38. package/plugins/kk/publish-presets/chinese-article.json +14 -0
  39. package/plugins/kk/publish-presets/technical-article.json +14 -0
  40. package/plugins/kk/references/smart-seo-attribution.md +85 -0
  41. package/plugins/kk/scripts/daily-stages.ts +42 -2
  42. package/plugins/kk/scripts/daily-voice-style.zh.md +1 -1
  43. package/plugins/kk/scripts/itc-stages.ts +25 -118
  44. package/plugins/kk/scripts/kk-workflow-stages.ts +38 -1
  45. package/plugins/kk/scripts/seo-decisions.ts +477 -0
  46. package/plugins/kk/scripts/seo-enrich.ts +728 -0
  47. package/plugins/kk/scripts/seo-facts.ts +610 -0
  48. package/plugins/kk/scripts/seo-fetch.ts +766 -0
  49. package/plugins/kk/scripts/seo-report.ts +460 -0
  50. package/plugins/kk/scripts/seo-rules.ts +1882 -0
  51. package/plugins/kk/scripts/seo-stages.ts +1044 -0
  52. package/plugins/kk/skills/article-adapt/SKILL.md +2 -2
  53. package/plugins/kk/skills/smart-seo/SKILL.md +183 -0
  54. package/plugins/kk/skills/smart-seo/references/method.md +126 -0
  55. package/plugins/kk/skills/smart-seo/references/narrative.md +125 -0
  56. package/plugins/kk/skills/smart-seo/references/operations.md +202 -0
  57. package/plugins/kk/skills/smart-seo/references/seo-geo-sources.md +90 -0
  58. package/plugins/kk/workflows/feature-verification.yaml +28 -24
  59. package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +86 -18
  60. package/plugins/kk/workflows/kk-itc-author.yaml +406 -0
  61. package/plugins/kk/workflows/kk-itc.yaml +208 -682
  62. package/plugins/kk/workflows/kk-smart-seo.yaml +251 -0
  63. package/plugins/publishings/bilibili-pub/dist/index.js +21782 -0
  64. package/plugins/publishings/bilibili-pub/dist/profile-check.js +20410 -0
  65. package/plugins/publishings/bilibili-pub/package.json +20 -0
  66. package/plugins/publishings/bilibili-pub/plugin.json +12 -0
  67. package/plugins/publishings/bilibili-pub/src/flow.ts +1296 -0
  68. package/plugins/publishings/bilibili-pub/src/index.ts +139 -0
  69. package/plugins/publishings/bilibili-pub/src/profile-check.ts +19 -0
  70. package/plugins/publishings/bilibili-pub/src/selectors.ts +222 -0
  71. package/plugins/publishings/bilibili-pub/tsconfig.json +4 -0
  72. package/plugins/publishings/cnblogs-pub/dist/index.js +21383 -0
  73. package/plugins/publishings/cnblogs-pub/dist/profile-check.js +20412 -0
  74. package/plugins/publishings/cnblogs-pub/package.json +19 -0
  75. package/plugins/publishings/cnblogs-pub/plugin.json +12 -0
  76. package/plugins/publishings/cnblogs-pub/src/flow.ts +708 -0
  77. package/plugins/publishings/cnblogs-pub/src/index.ts +172 -0
  78. package/plugins/publishings/cnblogs-pub/src/profile-check.ts +24 -0
  79. package/plugins/publishings/cnblogs-pub/src/selectors.ts +104 -0
  80. package/plugins/publishings/cnblogs-pub/tsconfig.json +4 -0
  81. package/plugins/publishings/csdn-pub/dist/index.js +21552 -0
  82. package/plugins/publishings/csdn-pub/dist/profile-check.js +20963 -0
  83. package/plugins/publishings/csdn-pub/package.json +19 -0
  84. package/plugins/publishings/csdn-pub/plugin.json +12 -0
  85. package/plugins/publishings/csdn-pub/src/index.ts +1178 -0
  86. package/plugins/publishings/csdn-pub/src/profile-check.ts +23 -0
  87. package/plugins/publishings/csdn-pub/tsconfig.json +4 -0
  88. package/plugins/publishings/emdash-pub/dist/index.js +158 -12
  89. package/plugins/publishings/emdash-pub/plugin.json +22 -1
  90. package/plugins/publishings/infoq-pub/dist/index.js +160 -12
  91. package/plugins/publishings/infoq-pub/dist/profile-check.js +160 -12
  92. package/plugins/publishings/infoq-pub/plugin.json +21 -0
  93. package/plugins/publishings/jike-pub/dist/index.js +21379 -0
  94. package/plugins/publishings/jike-pub/dist/profile-check.js +20951 -0
  95. package/plugins/publishings/jike-pub/package.json +19 -0
  96. package/plugins/publishings/jike-pub/plugin.json +12 -0
  97. package/plugins/publishings/jike-pub/src/index.ts +800 -0
  98. package/plugins/publishings/jike-pub/src/profile-check.ts +21 -0
  99. package/plugins/publishings/jike-pub/tsconfig.json +4 -0
  100. package/plugins/publishings/juejin-pub/dist/index.js +160 -12
  101. package/plugins/publishings/juejin-pub/dist/profile-check.js +160 -12
  102. package/plugins/publishings/juejin-pub/plugin.json +21 -0
  103. package/plugins/publishings/medium-pub/dist/index.js +158 -12
  104. package/plugins/publishings/medium-pub/plugin.json +22 -1
  105. package/plugins/publishings/podcast-pub/dist/index.js +158 -12
  106. package/plugins/publishings/qiita-pub/dist/index.js +158 -12
  107. package/plugins/publishings/qiita-pub/plugin.json +24 -1
  108. package/plugins/publishings/shipinhao-pub/dist/index.js +21715 -0
  109. package/plugins/publishings/shipinhao-pub/dist/profile-check.js +20431 -0
  110. package/plugins/publishings/shipinhao-pub/package.json +19 -0
  111. package/plugins/publishings/shipinhao-pub/plugin.json +12 -0
  112. package/plugins/publishings/shipinhao-pub/src/flow.ts +1323 -0
  113. package/plugins/publishings/shipinhao-pub/src/index.ts +173 -0
  114. package/plugins/publishings/shipinhao-pub/src/profile-check.ts +25 -0
  115. package/plugins/publishings/shipinhao-pub/src/selectors.ts +241 -0
  116. package/plugins/publishings/shipinhao-pub/tsconfig.json +4 -0
  117. package/plugins/publishings/sspai-pub/dist/index.js +21371 -0
  118. package/plugins/publishings/sspai-pub/dist/profile-check.js +20414 -0
  119. package/plugins/publishings/sspai-pub/package.json +19 -0
  120. package/plugins/publishings/sspai-pub/plugin.json +12 -0
  121. package/plugins/publishings/sspai-pub/src/flow.ts +624 -0
  122. package/plugins/publishings/sspai-pub/src/index.ts +167 -0
  123. package/plugins/publishings/sspai-pub/src/profile-check.ts +25 -0
  124. package/plugins/publishings/sspai-pub/src/selectors.ts +122 -0
  125. package/plugins/publishings/sspai-pub/tsconfig.json +4 -0
  126. package/plugins/publishings/substack-pub/dist/index.js +160 -12
  127. package/plugins/publishings/substack-pub/dist/profile-check.js +160 -12
  128. package/plugins/publishings/substack-pub/plugin.json +21 -0
  129. package/plugins/publishings/surfdash-pub/dist/index.js +158 -12
  130. package/plugins/publishings/surfdash-pub/plugin.json +22 -1
  131. package/plugins/publishings/threads-pub/dist/index.js +158 -12
  132. package/plugins/publishings/wechatmp-pub/dist/index.js +1669 -502
  133. package/plugins/publishings/wechatmp-pub/dist/profile-check.js +204 -21
  134. package/plugins/publishings/wechatmp-pub/plugin.json +32 -0
  135. package/plugins/publishings/wechatmp-pub/src/cgi-draft.ts +540 -0
  136. package/plugins/publishings/wechatmp-pub/src/cgi-media.ts +380 -0
  137. package/plugins/publishings/wechatmp-pub/src/cgi.ts +485 -0
  138. package/plugins/publishings/wechatmp-pub/src/flow.ts +104 -0
  139. package/plugins/publishings/weibo-pub/dist/index.js +160 -12
  140. package/plugins/publishings/weibo-pub/dist/profile-check.js +160 -12
  141. package/plugins/publishings/weibo-pub/plugin.json +35 -0
  142. package/plugins/publishings/x-pub/dist/index.js +160 -12
  143. package/plugins/publishings/x-pub/dist/profile-check.js +160 -12
  144. package/plugins/publishings/x-pub/plugin.json +40 -0
  145. package/plugins/publishings/xhs-pub/dist/index.js +160 -12
  146. package/plugins/publishings/xhs-pub/dist/profile-check.js +160 -12
  147. package/plugins/publishings/xhs-pub/plugin.json +37 -0
  148. package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +21491 -0
  149. package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +20417 -0
  150. package/plugins/publishings/xiaoyuzhou-pub/package.json +19 -0
  151. package/plugins/publishings/xiaoyuzhou-pub/plugin.json +12 -0
  152. package/plugins/publishings/xiaoyuzhou-pub/src/flow.ts +826 -0
  153. package/plugins/publishings/xiaoyuzhou-pub/src/index.ts +174 -0
  154. package/plugins/publishings/xiaoyuzhou-pub/src/profile-check.ts +26 -0
  155. package/plugins/publishings/xiaoyuzhou-pub/src/selectors.ts +125 -0
  156. package/plugins/publishings/xiaoyuzhou-pub/tsconfig.json +4 -0
  157. package/plugins/publishings/zenn-pub/dist/index.js +158 -12
  158. package/plugins/publishings/zenn-pub/plugin.json +22 -1
  159. package/plugins/generations/omni-voice-gen/voices/robin-news.wav +0 -0
@@ -0,0 +1,172 @@
1
+ /**
2
+ * cnblogs-pub — cnblogs (www.cnblogs.com) Markdown-article publisher plugin surface (task 0293).
3
+ *
4
+ * This module is the manifest-facing plugin surface: the declared operation list, the pre-browser
5
+ * refusal ladder (operation → driver → publishStatus → payload → unsupported fields → category
6
+ * types → local Markdown images — all refused before any browser opens), `getPublishTransport`,
7
+ * the `--in/--out` IO contract and the CLI `main`. The selectors live in `src/selectors.ts`, the
8
+ * browser transport and flow in `src/flow.ts`, and the operator session CLI in
9
+ * `src/profile-check.ts`.
10
+ *
11
+ * Evidence: the recovered extension adapter
12
+ * `.spur/memory/evidence/publish-ready-0288-0297-20261014-01/source/platforms/cnblogs.js` pins the
13
+ * field semantics cited below (fill cnblogs.js:20-32, register map cnblogs.js:35-37) but is a
14
+ * fill-only adapter — no save/publish protocol, no receipt. Its upload.cnblogs.com XSRF image
15
+ * endpoint (cnblogs.js:5-18) and its replace-with-uploaded-URL image pass (cnblogs.js:24) are
16
+ * deliberately NOT ported: requested cover/tags/images refuse, non-HTTPS Markdown image refs
17
+ * refuse before submit without echoing the path, and existing HTTPS image URLs stay literal.
18
+ *
19
+ * publishStatus contract (task 0293 Design freeze): absent defaults to 'draft', 'draft' saves
20
+ * through the exact-visible 保存草稿|存为草稿 control, 'public' activates 发布|发布博文 — both
21
+ * provisional, bounded, fail closed — and any other value refuses before the browser.
22
+ */
23
+ import { dirname } from 'node:path';
24
+ import { parseArgs } from 'node:util';
25
+ import { type Content, ContentSchema, type Result, ResultSchema } from '@gobing-ai/kk-core';
26
+ import { type BrowserDriver, FakeBrowserTransport, type PublishTransport } from '@gobing-ai/publish-harness';
27
+ import { createNodeFileSystem } from '@gobing-ai/ts-runtime';
28
+ import { echoError } from '@gobing-ai/ts-utils';
29
+ import { bounded, boundedDetail, CnblogsBrowserTransport, countNonHttpsImageRefs, errorMessage, fail } from './flow';
30
+ import { CNBLOGS_TARGET } from './selectors';
31
+
32
+ /** Declared operations (ADR-029 dispatch): the source handles article only (cnblogs.js:21). */
33
+ export const CNBLOGS_OPERATIONS = ['article'] as const;
34
+ export type CnblogsOperation = (typeof CNBLOGS_OPERATIONS)[number];
35
+
36
+ /**
37
+ * Resolves the requested operation. An undeclared value is refused by name, before any transport —
38
+ * and therefore before any browser — is resolved (the source handles article only, cnblogs.js:21).
39
+ * @param options - Per-call publish options (`operation`).
40
+ * @param content - Content whose `metadata.operation` is the fallback source.
41
+ * @returns The resolved operation, or the refusal message.
42
+ */
43
+ export function resolveOperation(
44
+ options?: Record<string, unknown>,
45
+ content?: Content,
46
+ ): { operation: CnblogsOperation; error?: undefined } | { operation?: undefined; error: string } {
47
+ const raw = options?.operation ?? content?.metadata?.operation ?? 'article';
48
+ if (typeof raw !== 'string' || !(CNBLOGS_OPERATIONS as readonly string[]).includes(raw)) {
49
+ return {
50
+ error: `unsupported operation '${bounded(raw)}' for ${CNBLOGS_TARGET}: expected one of ${CNBLOGS_OPERATIONS.join(', ')}`,
51
+ };
52
+ }
53
+ return { operation: raw as CnblogsOperation };
54
+ }
55
+
56
+ /**
57
+ * Resolves the driver from `options.driver ?? 'script'`. This channel ships one driver and no
58
+ * `agent` path, so anything else — including a wrong type — is refused by name before a transport
59
+ * is built.
60
+ * @param options - Per-call publish options (`driver`).
61
+ * @returns The resolved driver, or the refusal message.
62
+ */
63
+ function resolveDriver(
64
+ options?: Record<string, unknown>,
65
+ ): { driver: BrowserDriver; error?: undefined } | { driver?: undefined; error: string } {
66
+ const raw = options?.driver ?? 'script';
67
+ if (raw !== 'script') {
68
+ return {
69
+ error: `unsupported driver '${bounded(raw)}' for ${CNBLOGS_TARGET}: expected one of script (the cnblogs-pub browser flow has no agent path)`,
70
+ };
71
+ }
72
+ return { driver: 'script' };
73
+ }
74
+
75
+ /**
76
+ * Resolves the channel transport: the publish-harness fake under
77
+ * `KNOWLEDGE_KIT_PUBLISH_TRANSPORT=fake` (offline, no profile, no browser), otherwise the real
78
+ * cnblogs browser transport (`src/flow.ts`).
79
+ * @param operation - Resolved operation (`article`).
80
+ * @param driver - Resolved driver; only `script` is declared.
81
+ * @returns The publish transport for this channel.
82
+ */
83
+ export function getPublishTransport(
84
+ operation: CnblogsOperation = 'article',
85
+ driver: BrowserDriver = 'script',
86
+ ): PublishTransport {
87
+ if (process.env.KNOWLEDGE_KIT_PUBLISH_TRANSPORT === 'fake') return new FakeBrowserTransport('script');
88
+ return new CnblogsBrowserTransport(operation, driver);
89
+ }
90
+
91
+ /** Writes the Result to `--out`, creating its directory when the caller named a nested path. */
92
+ async function writePublishResult(outputPath: string, result: Result): Promise<void> {
93
+ const outDir = dirname(outputPath);
94
+ if (outDir && outDir !== '.') await createNodeFileSystem().ensureDir(outDir);
95
+ await createNodeFileSystem().writeFile(outputPath, JSON.stringify(result, null, 2));
96
+ }
97
+
98
+ /**
99
+ * The plugin's `--in/--out` contract: validate the Content, resolve **and refuse** the operation
100
+ * and the driver before any transport exists, then publish through the injected or configured
101
+ * transport (whose own ladder refuses status/payload/unsupported-field/category/image problems —
102
+ * also all pre-browser).
103
+ * @param inputPath - Path to the Content JSON.
104
+ * @param outputPath - Path the Result JSON is written to, on success and on refusal.
105
+ * @param transport - Optional transport injection for tests and offline runs.
106
+ */
107
+ export async function processPublishIO(
108
+ inputPath: string,
109
+ outputPath: string,
110
+ transport?: PublishTransport,
111
+ ): Promise<void> {
112
+ await createNodeFileSystem().deleteFile(outputPath);
113
+ let content: Content;
114
+ let options: Record<string, unknown> | undefined;
115
+ try {
116
+ const raw = JSON.parse(await createNodeFileSystem().readFile(inputPath)) as Record<string, unknown>;
117
+ content = ContentSchema.parse(raw);
118
+ if (raw.options !== undefined && typeof raw.options === 'object' && raw.options !== null) {
119
+ options = raw.options as Record<string, unknown>;
120
+ }
121
+ } catch (error: unknown) {
122
+ const message = boundedDetail(`Invalid Content input: ${errorMessage(error)}`);
123
+ await writePublishResult(outputPath, fail(CNBLOGS_TARGET, message));
124
+ throw new Error(message);
125
+ }
126
+ const mode = resolveOperation(options, content);
127
+ if (mode.error !== undefined) {
128
+ await writePublishResult(outputPath, fail(CNBLOGS_TARGET, mode.error));
129
+ throw new Error(mode.error);
130
+ }
131
+ const driverMode = resolveDriver(options);
132
+ if (driverMode.error !== undefined) {
133
+ await writePublishResult(outputPath, fail(CNBLOGS_TARGET, driverMode.error));
134
+ throw new Error(driverMode.error);
135
+ }
136
+ const resolved = transport ?? getPublishTransport(mode.operation, driverMode.driver);
137
+ const result = ResultSchema.parse(await resolved.publish({ content, options }));
138
+ await writePublishResult(outputPath, result);
139
+ if (!result.ok) throw new Error(result.error ?? 'Publish failed');
140
+ }
141
+
142
+ /**
143
+ * CLI entry point: `--in <content.json> --out <result.json>`.
144
+ * @returns Process exit code (0 published, 1 refused or invalid arguments).
145
+ */
146
+ export async function main(): Promise<number> {
147
+ let values: { in?: string; out?: string };
148
+ try {
149
+ ({ values } = parseArgs({ options: { in: { type: 'string' }, out: { type: 'string' } } }));
150
+ } catch (error: unknown) {
151
+ echoError(`Invalid arguments: ${errorMessage(error)}`);
152
+ return 1;
153
+ }
154
+ if (!values.in || !values.out) {
155
+ echoError('Missing required arguments: --in and --out');
156
+ return 1;
157
+ }
158
+ try {
159
+ await processPublishIO(values.in, values.out);
160
+ return 0;
161
+ } catch (error: unknown) {
162
+ echoError(`cnblogs-pub failed: ${boundedDetail(errorMessage(error))}`);
163
+ return 1;
164
+ }
165
+ }
166
+
167
+ if (import.meta.main) {
168
+ process.exit(await main());
169
+ }
170
+
171
+ /** Re-exported so the local Markdown-image rule stays observable from the plugin surface. */
172
+ export { countNonHttpsImageRefs };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * cnblogs profile-session capability (task 0293, Chunk A).
3
+ *
4
+ * `login` opens the cnblogs post-edit entry (https://i.cnblogs.com/posts/edit, provisional — see
5
+ * `src/selectors.ts`) in a headed browser and resolves only once the site readiness predicate
6
+ * passes; `check` reopens the profile (headlessly, unless the operator passes `--headed` or the
7
+ * config sets `checkHeadedOnly`) and reports `verified` or `refused`; `open` just brings the
8
+ * window up. Arg parsing, profile precedence (`--profile` > `CNBLOGS_PROFILE_DIR` >
9
+ * `KK_BROWSER_PROFILE_DIR` > `~/.config/kk/browser-profiles/cnblogs`) and the exit codes
10
+ * (0 verified, 1 refused, 2 usage error) all live in the shared `runProfileCli`, so the publish
11
+ * preflight and this CLI apply one gate.
12
+ *
13
+ * The readiness predicate observes the site-owned Markdown editor (the grounded
14
+ * `input#post-title`, cnblogs.js:22/25/36, see `src/selectors.ts`); the login-redirect regex is
15
+ * provisional (see `src/selectors.ts`). A selector that never attaches is a refusal
16
+ * (`bun plugins/publishings/cnblogs-pub/src/profile-check.ts login` is the recovery), never
17
+ * silent success.
18
+ */
19
+ import { runProfileCli } from '@gobing-ai/publish-harness/browser';
20
+ import { CNBLOGS_CONFIG } from './selectors';
21
+
22
+ if (import.meta.main) {
23
+ process.exit(await runProfileCli(CNBLOGS_CONFIG, Bun.argv.slice(2)));
24
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * cnblogs-pub selectors — every selector/URL/regex constant this channel resolves (task 0293).
3
+ *
4
+ * Grounding: the recovered extension adapter
5
+ * `.spur/memory/evidence/publish-ready-0288-0297-20261014-01/source/platforms/cnblogs.js` pins the
6
+ * three editor controls in both its fill (cnblogs.js:22-23, 25-26, 30) and its register map
7
+ * (cnblogs.js:35-37): `input#post-title`, `textarea#md-editor`, optional `textarea#summary`.
8
+ * Anything that evidence does not pin is **Provisional** and fails closed when absent — a missing
9
+ * control is a loud typed refusal, never a silent no-op or a broad fallback.
10
+ */
11
+ import { trySelectors } from '@gobing-ai/publish-harness';
12
+ import type { ChannelBrowserConfig } from '@gobing-ai/publish-harness/browser';
13
+ import type { IsAuthenticatedPredicate } from '@gobing-ai/ts-browser-automation';
14
+
15
+ /** Site-owned target slug; also the profile channel key (`CNBLOGS_PROFILE_DIR`). */
16
+ export const CNBLOGS_TARGET = 'cnblogs';
17
+
18
+ /**
19
+ * Post-edit entry where the article flow composes. **Provisional**: cnblogs.js runs inside an
20
+ * already-authenticated editor tab and never navigates (cnblogs.js fills in place only).
21
+ */
22
+ export const CNBLOGS_EDIT_URL = 'https://i.cnblogs.com/posts/edit';
23
+
24
+ /**
25
+ * Login/passport redirect marker. **Provisional**: cnblogs.js never observes a redirect URL;
26
+ * account.cnblogs.com/signin is cnblogs' known SSO entry. The readiness predicate below carries
27
+ * the real gate.
28
+ */
29
+ export const CNBLOGS_LOGIN_URL_RE = /(account\.cnblogs\.com\/signin|\/signin)/;
30
+
31
+ /**
32
+ * Article-editor selectors. The first three keys are **Grounded** (cnblogs.js:22-23, 25-26, 30
33
+ * and the cnblogs.js:35-37 register map); `categoryPanel`, `draftSave` and `publishButton` are
34
+ * **Provisional** semantic candidates from the task 0293 Design freeze — exact visible
35
+ * 保存草稿|存为草稿 and 发布|发布博文, bounded ordered resolution, no broad primary-button
36
+ * fallback, fail closed when absent.
37
+ */
38
+ export const CNBLOGS_ARTICLE_SELECTORS = {
39
+ /** Title input. **Grounded** (cnblogs.js:22, 25, 36). */
40
+ titleInput: ['input#post-title'],
41
+ /** Markdown body textarea. **Grounded** (cnblogs.js:23, 26, 36); value + input events, never innerHTML. */
42
+ bodyEditor: ['textarea#md-editor'],
43
+ /** Optional digest/summary textarea. **Grounded** (cnblogs.js:30, 36); filled only when a digest is requested. */
44
+ summaryInput: ['textarea#summary'],
45
+ /**
46
+ * Category panel holding the account's existing categories as label-associated checkboxes.
47
+ * **Provisional** panel node (cnblogs.js never touches categories); the checkbox/label
48
+ * matching inside it is structural, not guessed.
49
+ */
50
+ categoryPanel: ['#cateOwner', '#categories'],
51
+ /**
52
+ * The exact-visible draft-save control(s). **Provisional** (task 0293 Design freeze);
53
+ * `text-is` exact match, most specific wording first, no other fallback.
54
+ */
55
+ draftSave: ['button:text-is("保存草稿")', 'button:text-is("存为草稿")'],
56
+ /**
57
+ * The exact-visible publish control(s). **Provisional** (task 0293 Design freeze); 发布博文
58
+ * first so the specific control wins over the bare 发布 wording — never a primary-button or
59
+ * submit fallback.
60
+ */
61
+ publishButton: ['button:text-is("发布博文")', 'button:text-is("发布")'],
62
+ } as const;
63
+
64
+ /** Keys of the selector map, for typed probes. */
65
+ export type CnblogsArticleSelectorKey = keyof typeof CNBLOGS_ARTICLE_SELECTORS;
66
+
67
+ /**
68
+ * Persisted draft/post receipt URL shapes. **Provisional**: cnblogs.js is a fill-only adapter and
69
+ * never reads a receipt; these are the edit-URL-gaining-`postId` and the public
70
+ * `/p/<id>.html` shapes the task 0293 Design freeze names. Only shapes in this list count as
71
+ * receipts, and only when they were not on screen before the save (fresh-signal rule).
72
+ */
73
+ export const CNBLOGS_RECEIPT_URL_RES = [
74
+ /i\.cnblogs\.com\/posts\/edit\?[^\s#]*postId=\d+/,
75
+ /cnblogs\.com\/[^/\s]+\/p\/\d+\.html/,
76
+ ] as const;
77
+
78
+ /** Login deadline for the operator CLI and the session gate (shared harness default). */
79
+ export const CNBLOGS_LOGIN_TIMEOUT_MS = 180_000;
80
+
81
+ /**
82
+ * Cnblogs-owned readiness predicate: no login redirect and the site-owned Markdown editor
83
+ * reachable (the grounded title input). Shared by the publish preflight and the operator profile
84
+ * CLI, so both apply one gate. The login-redirect regex is provisional (see
85
+ * `CNBLOGS_LOGIN_URL_RE`); the title-input probe carries the real gate.
86
+ */
87
+ export const isCnblogsAuthenticated: IsAuthenticatedPredicate = async (page) => {
88
+ if (CNBLOGS_LOGIN_URL_RE.test(page.url())) return false;
89
+ return (await trySelectors(page, CNBLOGS_ARTICLE_SELECTORS.titleInput, { visible: false })).found;
90
+ };
91
+
92
+ /**
93
+ * Site-owned config for the shared session check and operator CLI (ADR-032). The check opens the
94
+ * post-edit entry (see `CNBLOGS_EDIT_URL`). The publish preflight never forwards
95
+ * `checkHeadedOnly`: it is the operator CLI's setting, and a refusal must never escalate to a
96
+ * headed window on its own.
97
+ */
98
+ export const CNBLOGS_CONFIG: ChannelBrowserConfig = {
99
+ channel: CNBLOGS_TARGET,
100
+ homeUrl: CNBLOGS_EDIT_URL,
101
+ isAuthenticated: isCnblogsAuthenticated,
102
+ loginTimeoutMs: CNBLOGS_LOGIN_TIMEOUT_MS,
103
+ checkHeadedOnly: false,
104
+ };
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "../../../tooling/typescript/base.json",
3
+ "include": ["src", "tests"]
4
+ }