@gobing-ai/knowledge-kit 0.1.10 → 0.1.12
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 +2478 -1737
- package/package.json +1 -1
- package/plugins/generations/image-gen/README.md +108 -0
- package/plugins/generations/image-gen/assets/sample/cover__clean-webapp-ui.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__cute.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__daily-news.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__editorial.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__fresh.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__minimalist.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__photorealistic.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__sketch.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__technical-diagram.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__vibrant.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__warm.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__xhs-knowledge.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__clean-webapp-ui.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__cute.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__daily-news.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__editorial.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__fresh.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__minimalist.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__photorealistic.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__sketch.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__technical-diagram.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__vibrant.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__warm.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__xhs-knowledge.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__clean-webapp-ui.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__cute.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__daily-news.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__editorial.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__fresh.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__minimalist.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__photorealistic.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__sketch.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__technical-diagram.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__vibrant.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__warm.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__xhs-knowledge.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__clean-webapp-ui.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__cute.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__daily-news.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__editorial.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__fresh.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__minimalist.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__photorealistic.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__sketch.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__technical-diagram.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__vibrant.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__warm.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__xhs-knowledge.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__clean-webapp-ui.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__cute.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__daily-news.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__editorial.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__fresh.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__minimalist.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__photorealistic.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__sketch.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__technical-diagram.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__vibrant.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__warm.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__xhs-knowledge.jpg +0 -0
- package/plugins/generations/image-gen/dist/index.js +102 -58
- package/plugins/generations/image-gen/scripts/compose-e2e-artifact.ts +5 -21
- package/plugins/generations/image-gen/scripts/gen-samples.ts +208 -0
- package/plugins/generations/image-gen/src/compose/chromium.ts +2 -3
- package/plugins/generations/image-gen/src/presets.ts +6 -3
- package/plugins/generations/image-gen/src/providers/errors.ts +16 -1
- package/plugins/generations/image-gen/src/providers/minimax.ts +46 -17
- package/plugins/ingestions/web-search/src/index.ts +1 -1
- package/plugins/kk/README.md +18 -0
- package/plugins/kk/commands/image-extract.md +1 -0
- package/plugins/kk/commands/image-generate.md +1 -0
- package/plugins/kk/commands/tell-me.md +1 -0
- package/plugins/kk/commands/workflow-run.md +7 -1
- package/plugins/kk/decisions/extract/it-topics.yaml +24 -0
- package/plugins/kk/decisions/kk-itc.yaml +6 -4
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/publish-presets/daily-ai-voice-migration.json +146 -0
- package/plugins/kk/publish-presets/daily-ai-voice.json +11 -3
- package/plugins/kk/schedules/kk-collect.grok-bot.md.tmpl +69 -0
- package/plugins/kk/schedules/kk-daily-ai-voice3.cron.tmpl +18 -0
- package/plugins/kk/schedules/kk-daily-publish3-cn-peak.cron.tmpl +15 -5
- package/plugins/kk/schedules/kk-daily-publish3-overseas.cron.tmpl +9 -4
- package/plugins/kk/scripts/daily-scheduled-run.ts +298 -0
- package/plugins/kk/scripts/daily-stages.ts +1403 -156
- package/plugins/kk/scripts/kk-workflow-stages.ts +266 -10
- package/plugins/kk/scripts/safety-artifact-check.ts +277 -0
- package/plugins/kk/scripts/voice3-publish-window.ts +202 -0
- package/plugins/kk/skills/article-adapt/SKILL.md +1 -0
- package/plugins/kk/skills/audio-authoring/SKILL.md +1 -0
- package/plugins/kk/skills/content-judge/SKILL.md +1 -0
- package/plugins/kk/skills/content-safety/SKILL.md +1 -0
- package/plugins/kk/skills/explain-things/SKILL.md +1 -0
- package/plugins/kk/skills/image-authoring/SKILL.md +1 -0
- package/plugins/kk/skills/itc-generating/SKILL.md +1 -0
- package/plugins/kk/skills/publish/SKILL.md +1 -0
- package/plugins/kk/skills/smart-seo/SKILL.md +1 -0
- package/plugins/kk/skills/storm-research/SKILL.md +1 -0
- package/plugins/kk/skills/taste-unslop/SKILL.md +1 -0
- package/plugins/kk/skills/topic/SKILL.md +1 -0
- package/plugins/kk/skills/web-automation/SKILL.md +1 -0
- package/plugins/kk/skills/xhs-images/SKILL.md +1 -0
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +177 -128
- package/plugins/kk/workflows/kk-daily-ai-voice3.yaml +161 -61
- package/plugins/kk/workflows/kk-daily-publish3.yaml +82 -26
- package/plugins/kk/workflows/kk-daily-safety.yaml +156 -69
- package/plugins/kk/workflows/kk-itc-author.yaml +75 -0
- package/plugins/kk/workflows/kk-itc-publish-tail.yaml +25 -3
- package/plugins/kk/workflows/kk-itc.yaml +36 -4
- package/plugins/publishings/bilibili-pub/dist/index.js +2 -1
- package/plugins/publishings/bilibili-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/cnblogs-pub/dist/index.js +2 -1
- package/plugins/publishings/cnblogs-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/csdn-pub/dist/index.js +2 -1
- package/plugins/publishings/csdn-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/infoq-pub/dist/index.js +2 -1
- package/plugins/publishings/infoq-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/jike-pub/dist/index.js +2 -1
- package/plugins/publishings/jike-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/juejin-pub/dist/index.js +2 -1
- package/plugins/publishings/juejin-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/linkedin-pub/dist/index.js +21333 -0
- package/plugins/publishings/linkedin-pub/dist/profile-check.js +21005 -0
- package/plugins/publishings/linkedin-pub/package.json +19 -0
- package/plugins/publishings/linkedin-pub/plugin.json +12 -0
- package/plugins/publishings/linkedin-pub/src/index.ts +705 -0
- package/plugins/publishings/linkedin-pub/src/profile-check.ts +17 -0
- package/plugins/publishings/linkedin-pub/tsconfig.json +4 -0
- package/plugins/publishings/podcast-pub/dist/index.js +46 -8
- package/plugins/publishings/podcast-pub/src/index.ts +62 -5
- package/plugins/publishings/shipinhao-pub/dist/index.js +2 -1
- package/plugins/publishings/shipinhao-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/sspai-pub/dist/index.js +2 -1
- package/plugins/publishings/sspai-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/substack-pub/dist/index.js +2 -1
- package/plugins/publishings/substack-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/surfdash-pub/dist/index.js +461 -53
- package/plugins/publishings/surfdash-pub/src/digest-check.ts +85 -19
- package/plugins/publishings/surfdash-pub/src/index.ts +592 -65
- package/plugins/publishings/threads-pub/dist/index.js +7 -0
- package/plugins/publishings/threads-pub/src/index.ts +12 -0
- package/plugins/publishings/wechatmp-pub/dist/index.js +936 -98
- package/plugins/publishings/wechatmp-pub/dist/profile-check.js +60 -3
- package/plugins/publishings/wechatmp-pub/src/flow.ts +1134 -86
- package/plugins/publishings/wechatmp-pub/src/index.ts +112 -30
- package/plugins/publishings/wechatmp-pub/src/page.ts +105 -0
- package/plugins/publishings/weibo-pub/dist/index.js +2 -1
- package/plugins/publishings/weibo-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/x-pub/dist/index.js +9 -1
- package/plugins/publishings/x-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/x-pub/src/index.ts +11 -0
- package/plugins/publishings/xhs-pub/dist/index.js +2 -1
- package/plugins/publishings/xhs-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +2 -1
- package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +2 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LinkedIn profile-session capability (0328 R2).
|
|
3
|
+
*
|
|
4
|
+
* `login` opens the feed in a headed browser and resolves only once LinkedIn's readiness predicate
|
|
5
|
+
* passes; `check` reopens the profile headlessly and reports `verified` or `refused` without
|
|
6
|
+
* posting. Arg parsing, profile precedence (`--profile` > `LINKEDIN_PROFILE_DIR` >
|
|
7
|
+
* `KK_BROWSER_PROFILE_DIR` > `~/.config/kk/browser-profiles/linkedin`, ADR-042) and the exit codes
|
|
8
|
+
* (0 verified, 1 refused, 2 usage error) all live in the shared `runProfileCli`, so the publish
|
|
9
|
+
* preflight (`kk plugin check linkedin-pub` and the publish-matrix probe) and this CLI apply one
|
|
10
|
+
* gate.
|
|
11
|
+
*/
|
|
12
|
+
import { runProfileCli } from '@gobing-ai/publish-harness/browser';
|
|
13
|
+
import { LINKEDIN_CONFIG } from './index';
|
|
14
|
+
|
|
15
|
+
if (import.meta.main) {
|
|
16
|
+
process.exit(await runProfileCli(LINKEDIN_CONFIG, Bun.argv.slice(2)));
|
|
17
|
+
}
|
|
@@ -11984,6 +11984,7 @@ function findProjectRoot(startDir) {
|
|
|
11984
11984
|
var init_file_system_node = () => {};
|
|
11985
11985
|
|
|
11986
11986
|
// ../../plugins/publishings/podcast-pub/src/index.ts
|
|
11987
|
+
import { createHash } from "crypto";
|
|
11987
11988
|
import { readFileSync as readFileSync3 } from "fs";
|
|
11988
11989
|
import { readFile } from "fs/promises";
|
|
11989
11990
|
import { basename, dirname as dirname2, extname as extname2 } from "path";
|
|
@@ -14334,26 +14335,53 @@ function shapedEpisode(episode, fields) {
|
|
|
14334
14335
|
link: fields.link
|
|
14335
14336
|
};
|
|
14336
14337
|
}
|
|
14337
|
-
|
|
14338
|
+
function canonicalJson(value) {
|
|
14339
|
+
if (value === null || value === undefined)
|
|
14340
|
+
return "null";
|
|
14341
|
+
if (typeof value === "string" || typeof value === "boolean")
|
|
14342
|
+
return JSON.stringify(value);
|
|
14343
|
+
if (typeof value === "number")
|
|
14344
|
+
return Number.isFinite(value) ? JSON.stringify(value) : "null";
|
|
14345
|
+
if (Array.isArray(value))
|
|
14346
|
+
return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
|
|
14347
|
+
if (typeof value === "object") {
|
|
14348
|
+
const entries = Object.entries(value).filter(([, entry]) => entry !== undefined).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
14349
|
+
return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(",")}}`;
|
|
14350
|
+
}
|
|
14351
|
+
return JSON.stringify(String(value));
|
|
14352
|
+
}
|
|
14353
|
+
function deterministicUuid(name) {
|
|
14354
|
+
const namespace = Buffer.from("6ba7b8119dad11d180b400c04fd430c8", "hex");
|
|
14355
|
+
const digest = createHash("sha1").update(namespace).update(name, "utf8").digest();
|
|
14356
|
+
digest[6] = (digest[6] ?? 0) & 15 | 80;
|
|
14357
|
+
digest[8] = (digest[8] ?? 0) & 63 | 128;
|
|
14358
|
+
const hex = digest.subarray(0, 16).toString("hex");
|
|
14359
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
14360
|
+
}
|
|
14361
|
+
function createItemIdempotencyKey(body, mediaIdentity) {
|
|
14362
|
+
return deterministicUuid(canonicalJson({ mediaIdentity, payload: body }));
|
|
14363
|
+
}
|
|
14364
|
+
async function createEpisodeItem(client, body, slug, warnings, mediaIdentity) {
|
|
14365
|
+
const create = (input) => client.createItem(input, createItemIdempotencyKey(input, mediaIdentity));
|
|
14338
14366
|
const withSlug = (itemSlug) => {
|
|
14339
14367
|
const micro = body._microfeed ?? {};
|
|
14340
14368
|
return { ...body, _microfeed: { ...micro, slug: itemSlug } };
|
|
14341
14369
|
};
|
|
14342
14370
|
if (!slug)
|
|
14343
|
-
return
|
|
14371
|
+
return create(body);
|
|
14344
14372
|
try {
|
|
14345
|
-
return await
|
|
14373
|
+
return await create(withSlug(slug));
|
|
14346
14374
|
} catch (error) {
|
|
14347
14375
|
if (!(error instanceof MicrofeedRequestError) || error.status !== 409)
|
|
14348
14376
|
throw error;
|
|
14349
14377
|
const retry = `${slug}-2`;
|
|
14350
14378
|
try {
|
|
14351
|
-
return await
|
|
14379
|
+
return await create(withSlug(retry));
|
|
14352
14380
|
} catch (retryError) {
|
|
14353
14381
|
if (!(retryError instanceof MicrofeedRequestError) || retryError.status !== 409)
|
|
14354
14382
|
throw retryError;
|
|
14355
14383
|
warn(`slug "${slug}" conflicted (HTTP 409); publishing without a custom slug`, warnings);
|
|
14356
|
-
return
|
|
14384
|
+
return create(body);
|
|
14357
14385
|
}
|
|
14358
14386
|
}
|
|
14359
14387
|
}
|
|
@@ -14458,6 +14486,14 @@ async function publishEpisode(client, payload, options) {
|
|
|
14458
14486
|
}
|
|
14459
14487
|
const seo = mergeSocial(fields.seo, social);
|
|
14460
14488
|
episode = shapedEpisode(episode, fields);
|
|
14489
|
+
let localAudioBytes;
|
|
14490
|
+
let mediaIdentity;
|
|
14491
|
+
if (episode.audio.kind === "url") {
|
|
14492
|
+
mediaIdentity = `audio-url:${episode.audio.url}\x00${episode.audio.mimeType}`;
|
|
14493
|
+
} else {
|
|
14494
|
+
localAudioBytes = new Uint8Array(await readFile(episode.audio.path));
|
|
14495
|
+
mediaIdentity = `audio-file-sha256:${createHash("sha256").update(localAudioBytes).digest("hex")}\x00${episode.audio.mimeType}`;
|
|
14496
|
+
}
|
|
14461
14497
|
if (episode.audio.kind === "url") {
|
|
14462
14498
|
const itemId = await createEpisodeItem(client, {
|
|
14463
14499
|
title: episode.title,
|
|
@@ -14465,7 +14501,7 @@ async function publishEpisode(client, payload, options) {
|
|
|
14465
14501
|
status: episode.status,
|
|
14466
14502
|
attachments: [audioAttachment(episode.audio, episode.audio.url, undefined, episode.durationSeconds)],
|
|
14467
14503
|
...episodeExtras(episode, seo)
|
|
14468
|
-
}, fields.slug, fields.warnings);
|
|
14504
|
+
}, fields.slug, fields.warnings, mediaIdentity);
|
|
14469
14505
|
let hostedItem;
|
|
14470
14506
|
try {
|
|
14471
14507
|
hostedItem = (await client.getItem(itemId)).items?.[0];
|
|
@@ -14485,8 +14521,10 @@ async function publishEpisode(client, payload, options) {
|
|
|
14485
14521
|
title: episode.title,
|
|
14486
14522
|
content_html: episode.contentHtml,
|
|
14487
14523
|
status: "unpublished"
|
|
14488
|
-
}, fields.slug, fields.warnings);
|
|
14489
|
-
const bytes =
|
|
14524
|
+
}, fields.slug, fields.warnings, mediaIdentity);
|
|
14525
|
+
const bytes = localAudioBytes;
|
|
14526
|
+
if (!bytes)
|
|
14527
|
+
return fail2("local audio identity was not prepared before item creation");
|
|
14490
14528
|
const prepared = await client.prepareUpload({
|
|
14491
14529
|
category: "audio",
|
|
14492
14530
|
full_local_file_path: episode.audio.path,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { readFileSync } from 'node:fs';
|
|
2
3
|
import { readFile } from 'node:fs/promises';
|
|
3
4
|
import { basename, dirname, extname } from 'node:path';
|
|
@@ -306,29 +307,72 @@ function shapedEpisode(episode: EpisodeMapping, fields: PreparedEpisodeFields):
|
|
|
306
307
|
};
|
|
307
308
|
}
|
|
308
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Canonical JSON for the create request fingerprint. The API's idempotency window is only 24h,
|
|
312
|
+
* so the key must be stable across a process restart while remaining different for a changed body.
|
|
313
|
+
* Object-key ordering is normalized because callers build the same logical payload in more than
|
|
314
|
+
* one branch (hosted audio and the draft half of local-audio publishing).
|
|
315
|
+
*/
|
|
316
|
+
function canonicalJson(value: unknown): string {
|
|
317
|
+
if (value === null || value === undefined) return 'null';
|
|
318
|
+
if (typeof value === 'string' || typeof value === 'boolean') return JSON.stringify(value);
|
|
319
|
+
if (typeof value === 'number') return Number.isFinite(value) ? JSON.stringify(value) : 'null';
|
|
320
|
+
if (Array.isArray(value)) return `[${value.map((entry) => canonicalJson(entry)).join(',')}]`;
|
|
321
|
+
if (typeof value === 'object') {
|
|
322
|
+
const entries = Object.entries(value as Record<string, unknown>)
|
|
323
|
+
.filter(([, entry]) => entry !== undefined)
|
|
324
|
+
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0));
|
|
325
|
+
return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(',')}}`;
|
|
326
|
+
}
|
|
327
|
+
return JSON.stringify(String(value));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** RFC 4122 UUIDv5-shaped key; microfeed validates Idempotency-Key as a UUID. */
|
|
331
|
+
function deterministicUuid(name: string): string {
|
|
332
|
+
// The URL namespace is only a stable namespace marker; the payload remains the name input.
|
|
333
|
+
const namespace = Buffer.from('6ba7b8119dad11d180b400c04fd430c8', 'hex');
|
|
334
|
+
const digest = createHash('sha1').update(namespace).update(name, 'utf8').digest();
|
|
335
|
+
digest[6] = ((digest[6] ?? 0) & 0x0f) | 0x50;
|
|
336
|
+
digest[8] = ((digest[8] ?? 0) & 0x3f) | 0x80;
|
|
337
|
+
const hex = digest.subarray(0, 16).toString('hex');
|
|
338
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Derives the caller-supplied create key from the exact request body and its primary media.
|
|
343
|
+
* Local audio uses content identity rather than its temporary path, so a resumed run can replay
|
|
344
|
+
* the same draft even when the workspace path changes.
|
|
345
|
+
*/
|
|
346
|
+
function createItemIdempotencyKey(body: MicrofeedItemInput, mediaIdentity: string): string {
|
|
347
|
+
return deterministicUuid(canonicalJson({ mediaIdentity, payload: body }));
|
|
348
|
+
}
|
|
349
|
+
|
|
309
350
|
/** First createItem carries the slug. A 409 retries once as `{slug}-2`, then publishes with no slug. */
|
|
310
351
|
async function createEpisodeItem(
|
|
311
352
|
client: MicrofeedClient,
|
|
312
353
|
body: MicrofeedItemInput,
|
|
313
354
|
slug: string | undefined,
|
|
314
355
|
warnings: string[],
|
|
356
|
+
mediaIdentity: string,
|
|
315
357
|
): Promise<string> {
|
|
358
|
+
const create = (input: MicrofeedItemInput): Promise<string> =>
|
|
359
|
+
client.createItem(input, createItemIdempotencyKey(input, mediaIdentity));
|
|
316
360
|
const withSlug = (itemSlug: string): MicrofeedItemInput => {
|
|
317
361
|
const micro = (body._microfeed as Record<string, unknown> | undefined) ?? {};
|
|
318
362
|
return { ...body, _microfeed: { ...micro, slug: itemSlug } };
|
|
319
363
|
};
|
|
320
|
-
if (!slug) return
|
|
364
|
+
if (!slug) return create(body);
|
|
321
365
|
try {
|
|
322
|
-
return await
|
|
366
|
+
return await create(withSlug(slug));
|
|
323
367
|
} catch (error: unknown) {
|
|
324
368
|
if (!(error instanceof MicrofeedRequestError) || error.status !== 409) throw error;
|
|
325
369
|
const retry = `${slug}-2`;
|
|
326
370
|
try {
|
|
327
|
-
return await
|
|
371
|
+
return await create(withSlug(retry));
|
|
328
372
|
} catch (retryError: unknown) {
|
|
329
373
|
if (!(retryError instanceof MicrofeedRequestError) || retryError.status !== 409) throw retryError;
|
|
330
374
|
warn(`slug "${slug}" conflicted (HTTP 409); publishing without a custom slug`, warnings);
|
|
331
|
-
return
|
|
375
|
+
return create(body);
|
|
332
376
|
}
|
|
333
377
|
}
|
|
334
378
|
}
|
|
@@ -464,6 +508,15 @@ async function publishEpisode(
|
|
|
464
508
|
const seo = mergeSocial(fields.seo, social);
|
|
465
509
|
episode = shapedEpisode(episode, fields);
|
|
466
510
|
|
|
511
|
+
let localAudioBytes: Uint8Array | undefined;
|
|
512
|
+
let mediaIdentity: string;
|
|
513
|
+
if (episode.audio.kind === 'url') {
|
|
514
|
+
mediaIdentity = `audio-url:${episode.audio.url}\0${episode.audio.mimeType}`;
|
|
515
|
+
} else {
|
|
516
|
+
localAudioBytes = new Uint8Array(await readFile(episode.audio.path));
|
|
517
|
+
mediaIdentity = `audio-file-sha256:${createHash('sha256').update(localAudioBytes).digest('hex')}\0${episode.audio.mimeType}`;
|
|
518
|
+
}
|
|
519
|
+
|
|
467
520
|
if (episode.audio.kind === 'url') {
|
|
468
521
|
const itemId = await createEpisodeItem(
|
|
469
522
|
client,
|
|
@@ -476,6 +529,7 @@ async function publishEpisode(
|
|
|
476
529
|
},
|
|
477
530
|
fields.slug,
|
|
478
531
|
fields.warnings,
|
|
532
|
+
mediaIdentity,
|
|
479
533
|
);
|
|
480
534
|
// Best-effort canonical-URL enrichment: the episode is already published, so a failed
|
|
481
535
|
// follow-up GET (or an unparsable response) falls back to publicItemUrl's own chain.
|
|
@@ -506,10 +560,13 @@ async function publishEpisode(
|
|
|
506
560
|
},
|
|
507
561
|
fields.slug,
|
|
508
562
|
fields.warnings,
|
|
563
|
+
mediaIdentity,
|
|
509
564
|
);
|
|
510
565
|
|
|
511
566
|
// Phase 2-3: presign against the draft, then PUT the raw bytes.
|
|
512
|
-
|
|
567
|
+
// The bytes were read before create so their digest can make a resumed create idempotent.
|
|
568
|
+
const bytes = localAudioBytes;
|
|
569
|
+
if (!bytes) return fail('local audio identity was not prepared before item creation');
|
|
513
570
|
const prepared = await client.prepareUpload({
|
|
514
571
|
category: 'audio',
|
|
515
572
|
full_local_file_path: episode.audio.path,
|
|
@@ -20440,9 +20440,11 @@ import { isAbsolute, join as join4, relative, resolve as resolve4, sep } from "p
|
|
|
20440
20440
|
// ../../packages/publish-harness/src/bot-fs.ts
|
|
20441
20441
|
import {
|
|
20442
20442
|
chmodSync,
|
|
20443
|
+
closeSync,
|
|
20443
20444
|
copyFileSync,
|
|
20444
20445
|
lstatSync,
|
|
20445
20446
|
mkdirSync as mkdirSync2,
|
|
20447
|
+
openSync,
|
|
20446
20448
|
readdirSync as readdirSync2,
|
|
20447
20449
|
realpathSync as realpathSync2,
|
|
20448
20450
|
rmdirSync
|
|
@@ -20687,7 +20689,6 @@ class BotError extends Error {
|
|
|
20687
20689
|
this.code = code;
|
|
20688
20690
|
}
|
|
20689
20691
|
}
|
|
20690
|
-
|
|
20691
20692
|
// ../../packages/publish-harness/src/profile-dir.ts
|
|
20692
20693
|
import { mkdir as mkdir2, stat } from "fs/promises";
|
|
20693
20694
|
async function ensureProfileDir2(profileDir, headless) {
|
|
@@ -19847,9 +19847,11 @@ import { isAbsolute, join as join3, relative, resolve as resolve3, sep } from "p
|
|
|
19847
19847
|
// ../../packages/publish-harness/src/bot-fs.ts
|
|
19848
19848
|
import {
|
|
19849
19849
|
chmodSync,
|
|
19850
|
+
closeSync,
|
|
19850
19851
|
copyFileSync,
|
|
19851
19852
|
lstatSync,
|
|
19852
19853
|
mkdirSync as mkdirSync2,
|
|
19854
|
+
openSync,
|
|
19853
19855
|
readdirSync as readdirSync2,
|
|
19854
19856
|
realpathSync as realpathSync2,
|
|
19855
19857
|
rmdirSync
|
|
@@ -20101,7 +20103,6 @@ class BotError extends Error {
|
|
|
20101
20103
|
this.code = code;
|
|
20102
20104
|
}
|
|
20103
20105
|
}
|
|
20104
|
-
|
|
20105
20106
|
// ../../packages/publish-harness/src/index.ts
|
|
20106
20107
|
import { homedir as homedir4 } from "os";
|
|
20107
20108
|
import { join as join4, resolve as resolve4 } from "path";
|
|
@@ -20464,9 +20464,11 @@ import { isAbsolute, join as join4, relative, resolve as resolve4, sep } from "p
|
|
|
20464
20464
|
// ../../packages/publish-harness/src/bot-fs.ts
|
|
20465
20465
|
import {
|
|
20466
20466
|
chmodSync,
|
|
20467
|
+
closeSync,
|
|
20467
20468
|
copyFileSync,
|
|
20468
20469
|
lstatSync,
|
|
20469
20470
|
mkdirSync as mkdirSync2,
|
|
20471
|
+
openSync,
|
|
20470
20472
|
readdirSync as readdirSync2,
|
|
20471
20473
|
realpathSync as realpathSync2,
|
|
20472
20474
|
rmdirSync
|
|
@@ -20711,7 +20713,6 @@ class BotError extends Error {
|
|
|
20711
20713
|
this.code = code;
|
|
20712
20714
|
}
|
|
20713
20715
|
}
|
|
20714
|
-
|
|
20715
20716
|
// ../../packages/publish-harness/src/profile-dir.ts
|
|
20716
20717
|
import { mkdir as mkdir2, stat } from "fs/promises";
|
|
20717
20718
|
async function ensureProfileDir2(profileDir, headless) {
|
|
@@ -19847,9 +19847,11 @@ import { isAbsolute, join as join3, relative, resolve as resolve3, sep } from "p
|
|
|
19847
19847
|
// ../../packages/publish-harness/src/bot-fs.ts
|
|
19848
19848
|
import {
|
|
19849
19849
|
chmodSync,
|
|
19850
|
+
closeSync,
|
|
19850
19851
|
copyFileSync,
|
|
19851
19852
|
lstatSync,
|
|
19852
19853
|
mkdirSync as mkdirSync2,
|
|
19854
|
+
openSync,
|
|
19853
19855
|
readdirSync as readdirSync2,
|
|
19854
19856
|
realpathSync as realpathSync2,
|
|
19855
19857
|
rmdirSync
|
|
@@ -20101,7 +20103,6 @@ class BotError extends Error {
|
|
|
20101
20103
|
this.code = code;
|
|
20102
20104
|
}
|
|
20103
20105
|
}
|
|
20104
|
-
|
|
20105
20106
|
// ../../packages/publish-harness/src/index.ts
|
|
20106
20107
|
import { homedir as homedir4 } from "os";
|
|
20107
20108
|
import { join as join4, resolve as resolve4 } from "path";
|
|
@@ -20458,9 +20458,11 @@ import { isAbsolute, join as join4, relative, resolve as resolve4, sep } from "p
|
|
|
20458
20458
|
// ../../packages/publish-harness/src/bot-fs.ts
|
|
20459
20459
|
import {
|
|
20460
20460
|
chmodSync,
|
|
20461
|
+
closeSync,
|
|
20461
20462
|
copyFileSync,
|
|
20462
20463
|
lstatSync,
|
|
20463
20464
|
mkdirSync as mkdirSync2,
|
|
20465
|
+
openSync,
|
|
20464
20466
|
readdirSync as readdirSync2,
|
|
20465
20467
|
realpathSync as realpathSync2,
|
|
20466
20468
|
rmdirSync
|
|
@@ -20705,7 +20707,6 @@ class BotError extends Error {
|
|
|
20705
20707
|
this.code = code;
|
|
20706
20708
|
}
|
|
20707
20709
|
}
|
|
20708
|
-
|
|
20709
20710
|
// ../../packages/publish-harness/src/profile-dir.ts
|
|
20710
20711
|
import { mkdir as mkdir2, stat } from "fs/promises";
|
|
20711
20712
|
async function ensureProfileDir2(profileDir, headless) {
|
|
@@ -20119,9 +20119,11 @@ import { isAbsolute, join as join3, relative, resolve as resolve3, sep } from "p
|
|
|
20119
20119
|
// ../../packages/publish-harness/src/bot-fs.ts
|
|
20120
20120
|
import {
|
|
20121
20121
|
chmodSync,
|
|
20122
|
+
closeSync,
|
|
20122
20123
|
copyFileSync,
|
|
20123
20124
|
lstatSync,
|
|
20124
20125
|
mkdirSync as mkdirSync2,
|
|
20126
|
+
openSync,
|
|
20125
20127
|
readdirSync as readdirSync2,
|
|
20126
20128
|
realpathSync as realpathSync2,
|
|
20127
20129
|
rmdirSync
|
|
@@ -20373,7 +20375,6 @@ class BotError extends Error {
|
|
|
20373
20375
|
this.code = code;
|
|
20374
20376
|
}
|
|
20375
20377
|
}
|
|
20376
|
-
|
|
20377
20378
|
// ../../packages/publish-harness/src/index.ts
|
|
20378
20379
|
import { homedir as homedir4 } from "os";
|
|
20379
20380
|
import { join as join4, resolve as resolve4 } from "path";
|