@gobing-ai/knowledge-kit 0.0.35 → 0.0.36
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/README.md +1 -1
- package/dist/board/collect/index.css +42 -0
- package/dist/board/collect/index.js +787 -0
- package/dist/board/daily-voice/index.css +13 -0
- package/dist/board/daily-voice/index.js +902 -0
- package/dist/board/itc/index.css +14 -0
- package/dist/board/itc/index.js +1026 -0
- package/dist/index.js +11768 -9249
- package/package.json +10 -9
- package/plugins/generations/content-gen/dist/index.js +1015 -10814
- package/plugins/generations/content-gen/package.json +1 -1
- package/plugins/generations/core-facts-gen/dist/index.js +1002 -10801
- package/plugins/generations/core-facts-gen/package.json +1 -1
- package/plugins/generations/daily-article-gen/dist/index.js +1004 -10803
- package/plugins/generations/daily-article-gen/package.json +1 -1
- package/plugins/generations/dailynews-gen/dist/index.js +1092 -10891
- package/plugins/generations/dailynews-gen/package.json +1 -1
- package/plugins/generations/episode-plan-gen/dist/index.js +1124 -10923
- package/plugins/generations/episode-plan-gen/package.json +1 -1
- package/plugins/generations/gemini-tts/dist/index.js +1104 -10903
- package/plugins/generations/gemini-tts/package.json +1 -1
- package/plugins/generations/image-gen/dist/index.js +1239 -11035
- package/plugins/generations/image-gen/package.json +1 -1
- package/plugins/generations/news-report-gen/dist/index.js +1048 -10847
- package/plugins/generations/news-report-gen/package.json +1 -1
- package/plugins/generations/voice-gen/dist/index.js +1062 -10861
- package/plugins/generations/voice-gen/package.json +1 -1
- package/plugins/generations/voxcpm-tts/README.md +72 -0
- package/plugins/generations/voxcpm-tts/dist/index.js +14006 -0
- package/plugins/generations/voxcpm-tts/package.json +16 -0
- package/plugins/generations/voxcpm-tts/plugin.json +6 -0
- package/plugins/generations/voxcpm-tts/profiles.json +10 -0
- package/plugins/generations/voxcpm-tts/pyproject.toml +9 -0
- package/plugins/generations/voxcpm-tts/python/worker.py +353 -0
- package/plugins/generations/voxcpm-tts/src/index.ts +42 -0
- package/plugins/generations/voxcpm-tts/src/voxcpm-client.ts +765 -0
- package/plugins/generations/voxcpm-tts/tsconfig.json +8 -0
- package/plugins/generations/voxcpm-tts/uv.lock +2571 -0
- package/plugins/ingestions/aihot-ingest/dist/index.js +1081 -10628
- package/plugins/ingestions/aihot-ingest/package.json +1 -1
- package/plugins/ingestions/horizon-ingest/dist/index.js +1013 -10812
- package/plugins/ingestions/horizon-ingest/package.json +1 -1
- package/plugins/ingestions/karakeep-local/dist/index.js +689 -10774
- package/plugins/ingestions/karakeep-local/package.json +1 -1
- package/plugins/ingestions/last30days-ingest/dist/index.js +1011 -10810
- package/plugins/ingestions/last30days-ingest/package.json +1 -1
- package/plugins/ingestions/web-search/dist/index.js +1250 -11033
- package/plugins/ingestions/web-search/package.json +1 -1
- package/plugins/kk/README.md +29 -2
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/schedules/cron.tmpl +9 -1
- package/plugins/kk/schedules/kk-serve.launchd.plist.tmpl +79 -0
- package/plugins/kk/schedules/launchd.plist.tmpl +11 -1
- package/plugins/kk/scripts/daily-stages.ts +428 -46
- package/plugins/kk/scripts/kk-workflow-stages.ts +76 -146
- package/plugins/kk/skills/publish/SKILL.md +8 -4
- package/plugins/kk/workflows/feature-verification.yaml +93 -0
- package/plugins/kk/workflows/judge-gated-publish-example.yaml +1 -0
- package/plugins/kk/workflows/kk-collect.yaml +43 -11
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +4 -2
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +86 -15
- package/plugins/kk/workflows/kk-daily-safety.yaml +15 -2
- package/plugins/kk/workflows/kk-itc.yaml +8 -1
- package/plugins/kk/workflows/kk-solo-podcast.yaml +2 -0
- package/plugins/publishings/emdash-pub/dist/index.js +987 -10814
- package/plugins/publishings/emdash-pub/package.json +1 -1
- package/plugins/publishings/infoq-pub/dist/index.js +1928 -11444
- package/plugins/publishings/infoq-pub/dist/profile-check.js +1677 -11199
- package/plugins/publishings/infoq-pub/package.json +1 -1
- package/plugins/publishings/infoq-pub/plugin.json +5 -1
- package/plugins/publishings/juejin-pub/dist/index.js +1940 -11456
- package/plugins/publishings/juejin-pub/dist/profile-check.js +1677 -11199
- package/plugins/publishings/juejin-pub/package.json +1 -1
- package/plugins/publishings/juejin-pub/plugin.json +5 -1
- package/plugins/publishings/medium-pub/dist/index.js +978 -10805
- package/plugins/publishings/medium-pub/package.json +1 -1
- package/plugins/publishings/podcast-pub/dist/index.js +1016 -10843
- package/plugins/publishings/podcast-pub/package.json +1 -1
- package/plugins/publishings/qiita-pub/dist/index.js +976 -10803
- package/plugins/publishings/qiita-pub/package.json +1 -1
- package/plugins/publishings/substack-pub/dist/index.js +1932 -11448
- package/plugins/publishings/substack-pub/dist/profile-check.js +1698 -11200
- package/plugins/publishings/substack-pub/package.json +1 -1
- package/plugins/publishings/substack-pub/plugin.json +4 -1
- package/plugins/publishings/substack-pub/src/profile-check.ts +36 -3
- package/plugins/publishings/surfdash-pub/dist/index.js +1151 -10834
- package/plugins/publishings/surfdash-pub/package.json +1 -1
- package/plugins/publishings/surfdash-pub/src/index.ts +137 -15
- package/plugins/publishings/surfdash-pub/src/patch.ts +144 -0
- package/plugins/publishings/threads-pub/dist/index.js +12394 -0
- package/plugins/publishings/threads-pub/package.json +17 -0
- package/plugins/publishings/threads-pub/plugin.json +7 -0
- package/plugins/publishings/threads-pub/src/index.ts +340 -0
- package/plugins/publishings/threads-pub/tsconfig.json +4 -0
- package/plugins/publishings/wechatmp-pub/dist/index.js +2508 -11695
- package/plugins/publishings/wechatmp-pub/dist/profile-check.js +1656 -11188
- package/plugins/publishings/wechatmp-pub/package.json +1 -1
- package/plugins/publishings/wechatmp-pub/plugin.json +5 -1
- package/plugins/publishings/wechatmp-pub/src/enrich.ts +244 -71
- package/plugins/publishings/wechatmp-pub/src/flow.ts +412 -96
- package/plugins/publishings/wechatmp-pub/src/index.ts +24 -3
- package/plugins/publishings/wechatmp-pub/src/page.ts +16 -0
- package/plugins/publishings/wechatmp-pub/src/profile-check.ts +40 -4
- package/plugins/publishings/weibo-pub/dist/index.js +21345 -0
- package/plugins/publishings/weibo-pub/dist/profile-check.js +20817 -0
- package/plugins/publishings/weibo-pub/package.json +19 -0
- package/plugins/publishings/weibo-pub/plugin.json +12 -0
- package/plugins/publishings/weibo-pub/src/index.ts +868 -0
- package/plugins/publishings/weibo-pub/src/profile-check.ts +20 -0
- package/plugins/publishings/weibo-pub/tsconfig.json +4 -0
- package/plugins/publishings/x-pub/dist/index.js +1948 -11440
- package/plugins/publishings/x-pub/dist/profile-check.js +1677 -11199
- package/plugins/publishings/x-pub/package.json +1 -1
- package/plugins/publishings/x-pub/plugin.json +5 -1
- package/plugins/publishings/xhs-pub/dist/index.js +1993 -11509
- package/plugins/publishings/xhs-pub/dist/profile-check.js +1697 -11212
- package/plugins/publishings/xhs-pub/package.json +1 -1
- package/plugins/publishings/xhs-pub/plugin.json +5 -1
- package/plugins/publishings/xhs-pub/src/profile-check.ts +72 -2
- package/plugins/publishings/zenn-pub/dist/index.js +988 -10815
- package/plugins/publishings/zenn-pub/package.json +1 -1
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
writeFileSync,
|
|
56
56
|
} from 'node:fs';
|
|
57
57
|
import { homedir } from 'node:os';
|
|
58
|
-
import { basename, dirname, join,
|
|
58
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
59
59
|
|
|
60
60
|
export const SCRIPTS_DIR = import.meta.dir;
|
|
61
61
|
export const VALIDATE_SIDECAR = 'validate-voicescript.ts';
|
|
@@ -1280,10 +1280,24 @@ function publishCompleteness(
|
|
|
1280
1280
|
detail: 'en/ja scaffolded, never synced',
|
|
1281
1281
|
});
|
|
1282
1282
|
} else if (syncResult.ok === true) {
|
|
1283
|
+
// A filled marker only proves the locale was SCAFFOLDED. The workflow's `translate` agent is
|
|
1284
|
+
// what publishes it (its prompt flips `draft: true` → `draft: false`), so a run whose
|
|
1285
|
+
// translation failed used to report `locales: done` while the site kept an unpublished
|
|
1286
|
+
// scaffold — the one channel whose loss nothing in the run's artifacts named. Read the draft
|
|
1287
|
+
// the marker points at: still `draft: true` means it was never translated.
|
|
1288
|
+
const untranslated = ['en', 'ja'].filter((locale) => {
|
|
1289
|
+
const marker = readFileSync(at(`20_translate-localize_${locale}-post-path.txt`), 'utf-8').trim();
|
|
1290
|
+
if (!isFile(marker)) return true;
|
|
1291
|
+
const head = readFileSync(marker, 'utf-8').split('---')[1] ?? '';
|
|
1292
|
+
return /^draft:\s*true\s*$/m.test(head);
|
|
1293
|
+
});
|
|
1283
1294
|
rows.push({
|
|
1284
1295
|
channel: 'locales',
|
|
1285
|
-
state: 'done',
|
|
1286
|
-
detail:
|
|
1296
|
+
state: untranslated.length === 0 ? 'done' : 'failed',
|
|
1297
|
+
detail:
|
|
1298
|
+
untranslated.length === 0
|
|
1299
|
+
? 'en/ja drafts scaffolded and translated'
|
|
1300
|
+
: `${untranslated.join(', ')} draft is still a scaffold (draft: true) — not translated, so it stays unpublished`,
|
|
1287
1301
|
});
|
|
1288
1302
|
} else {
|
|
1289
1303
|
rows.push({
|
|
@@ -1327,6 +1341,12 @@ function publishCompleteness(
|
|
|
1327
1341
|
function dailyPublishPartial(args: string[]): void {
|
|
1328
1342
|
const [workDir = '', runDate = ''] = args;
|
|
1329
1343
|
const result = join(workDir, '31-publish', `${runDate}_17_publish_result.json`);
|
|
1344
|
+
// The marker describes this run, so it belongs under that run's workDir — a cwd-relative path
|
|
1345
|
+
// litters whatever tree the caller stood in and hides the note from the episode it describes.
|
|
1346
|
+
const marker =
|
|
1347
|
+
workDir === ''
|
|
1348
|
+
? join('.spur', 'run', `publish-partial-${runDate}.txt`)
|
|
1349
|
+
: join(workDir, '.spur', 'run', `publish-partial-${runDate}.txt`);
|
|
1330
1350
|
timed(workDir, 'publish-partial', () => {
|
|
1331
1351
|
const failed = targetsOf(result)
|
|
1332
1352
|
.filter((entry) => entry.ok !== true || entry.status !== 'published')
|
|
@@ -1334,10 +1354,10 @@ function dailyPublishPartial(args: string[]): void {
|
|
|
1334
1354
|
.join(',');
|
|
1335
1355
|
const list = failed === '' ? '(unparsed)' : failed;
|
|
1336
1356
|
console.error(`WARNING: partial publish — failed targets: ${list}`);
|
|
1337
|
-
sted('mkdir',
|
|
1338
|
-
sted('write',
|
|
1357
|
+
sted('mkdir', dirname(marker), () => mkdirSync(dirname(marker), { recursive: true }));
|
|
1358
|
+
sted('write', marker, () =>
|
|
1339
1359
|
writeFileSync(
|
|
1340
|
-
|
|
1360
|
+
marker,
|
|
1341
1361
|
`${runDate} partial publish: failed targets: ${list} (result.json preserved for kk executor fan-out --retry-from)\n`,
|
|
1342
1362
|
),
|
|
1343
1363
|
);
|
|
@@ -1345,10 +1365,16 @@ function dailyPublishPartial(args: string[]): void {
|
|
|
1345
1365
|
}
|
|
1346
1366
|
|
|
1347
1367
|
/**
|
|
1348
|
-
*
|
|
1349
|
-
*
|
|
1350
|
-
*
|
|
1351
|
-
*
|
|
1368
|
+
* Post-publish surfdash healing — a thin delegation to the publisher that owns it (2026-10-12).
|
|
1369
|
+
*
|
|
1370
|
+
* The work itself (podcast cover URL into `image`/`og_image`, the per-locale podcast cross-link, the
|
|
1371
|
+
* local cover's `assets/` fallback, the EmDash re-sync that makes CMS cards carry a servable image,
|
|
1372
|
+
* and the pathspec commit + push) lives in the `surfdash-pub` `patch` operation now: it is the
|
|
1373
|
+
* publisher's job, and every path that publishes a post needs it. This stage only reads the run's
|
|
1374
|
+
* publish results into the operation's payload and reports its outcome.
|
|
1375
|
+
*
|
|
1376
|
+
* `KK_SURFDASH_PATCH_OFFLINE=1` keeps fixture tests hermetic — the plugin would otherwise write to,
|
|
1377
|
+
* commit and push a real checkout.
|
|
1352
1378
|
*/
|
|
1353
1379
|
function dailyPatchSurfdash(args: string[]): void {
|
|
1354
1380
|
const [workDir = '', runDate = '', surfdashRoot = '', pluginsPath = ''] = args;
|
|
@@ -1369,149 +1395,53 @@ function dailyPatchSurfdash(args: string[]): void {
|
|
|
1369
1395
|
const sd = targetOf(sdResult);
|
|
1370
1396
|
const pod = targetOf(podcastResult);
|
|
1371
1397
|
const postPath = (sd.metadata as Record<string, unknown> | undefined)?.postPath;
|
|
1372
|
-
if (typeof postPath !== 'string' || postPath === '')
|
|
1398
|
+
if (typeof postPath !== 'string' || postPath === '') {
|
|
1373
1399
|
failWith(1, 'patch-surfdash: surfdash result carries no postPath');
|
|
1374
|
-
// Refuse before anything is written: surfdash's deploy.yml publishes from main, so a commit
|
|
1375
|
-
// on any other branch is invisible to production. Placed here (after the input checks) so a
|
|
1376
|
-
// refusal leaves the working tree untouched.
|
|
1377
|
-
const head = spawnSync('git', ['-C', surfdashRoot, 'rev-parse', '--abbrev-ref', 'HEAD'])
|
|
1378
|
-
.stdout?.toString()
|
|
1379
|
-
.trim();
|
|
1380
|
-
if (head !== 'main')
|
|
1381
|
-
failWith(1, `patch-surfdash: refusing to commit on '${head}' — surfdash deploys from main`);
|
|
1382
|
-
// Paths this run actually wrote, relative to surfdashRoot. `git add`/`commit` take these as a
|
|
1383
|
-
// pathspec so the operator's unrelated staged work is never swept into the commit.
|
|
1384
|
-
const written: string[] = [];
|
|
1385
|
-
const slugDir = dirname(postPath);
|
|
1386
|
-
const showNotesUrl = typeof pod.url === 'string' ? pod.url : '';
|
|
1387
|
-
const coverUrl = ((pod.metadata as Record<string, unknown> | undefined)?.imageUrl as string | undefined) ?? '';
|
|
1388
|
-
const localCover = join(workDir, '23-cover', `${runDate}_07_cover_cover.png`);
|
|
1389
|
-
const dateIso = `${runDate.slice(0, 4)}-${runDate.slice(4, 6)}-${runDate.slice(6, 8)}`;
|
|
1390
|
-
const crossLink: Record<string, string> = {
|
|
1391
|
-
// Task 0189 R4: cross-links carry the column's episode-title pattern, not the retired
|
|
1392
|
-
// news-bulletin name (the VoiceScript episode title is episodeTitle(lead, date)).
|
|
1393
|
-
zh: `🎧 本期已同步制作为播客节目,[点击收听《每日AI乱炖 · ${dateIso}》](${showNotesUrl})。`,
|
|
1394
|
-
en: `🎧 This episode is also available as a podcast: [listen to 每日AI乱炖 · ${dateIso}](${showNotesUrl}).`,
|
|
1395
|
-
ja: `🎧 本エピソードはポッドキャストでも配信中:[每日AI乱炖 · ${dateIso} を聴く](${showNotesUrl})。`,
|
|
1396
|
-
};
|
|
1397
|
-
let changed = 0;
|
|
1398
|
-
for (const [locale, dir] of [
|
|
1399
|
-
// zh posts live under articles/zh/<slug>; en/ja under articles/<locale>/<slug> —
|
|
1400
|
-
// two dirnames up, not one (20260919: `articles/zh/en/...` never existed, so en/ja
|
|
1401
|
-
// were silently skipped by the not-published branch).
|
|
1402
|
-
['zh', slugDir],
|
|
1403
|
-
['en', join(dirname(dirname(slugDir)), 'en', basename(slugDir))],
|
|
1404
|
-
['ja', join(dirname(dirname(slugDir)), 'ja', basename(slugDir))],
|
|
1405
|
-
] as const) {
|
|
1406
|
-
const indexMd = join(dir, 'index.md');
|
|
1407
|
-
if (!existsSync(indexMd)) {
|
|
1408
|
-
console.error(`patch-surfdash: ${locale} post not published (${indexMd}) — skip`);
|
|
1409
|
-
continue;
|
|
1410
|
-
}
|
|
1411
|
-
let md = readFileSync(indexMd, 'utf-8');
|
|
1412
|
-
const fmEnd = md.indexOf('---', 3); // frontmatter is `---\n ... \n---\n`
|
|
1413
|
-
if (fmEnd < 0) failWith(1, `patch-surfdash: no frontmatter in ${indexMd}`);
|
|
1414
|
-
// Upsert a single-line `key: 'value'`; the regex consumes folded-scalar continuation
|
|
1415
|
-
// lines (`og_image: >-\n https://...`) so multi-line publish output is replaced whole.
|
|
1416
|
-
const upsertKey = (src: string, key: string, value: string): string => {
|
|
1417
|
-
// YAML single-quoted scalars escape `'` as `''`; the replacement is a function so
|
|
1418
|
-
// `$&`/`$1`/`$$` in the value are never expanded. Scope the match to the frontmatter
|
|
1419
|
-
// (recomputed per call, since each upsert shifts offsets) so a body line starting with
|
|
1420
|
-
// `image:` is not rewritten.
|
|
1421
|
-
const end = src.indexOf('\n---', 3);
|
|
1422
|
-
const fm = src.slice(0, end);
|
|
1423
|
-
const body = src.slice(end);
|
|
1424
|
-
const line = `${key}: '${value.replaceAll("'", "''")}'`;
|
|
1425
|
-
const re = new RegExp(`^${key}:.*(?:\\n[ \\t]+.*)*`, 'm');
|
|
1426
|
-
const next = re.test(fm)
|
|
1427
|
-
? fm.replace(re, () => line)
|
|
1428
|
-
: fm.replace(/^publishDate: .*$/m, (m0) => `${m0}\n${line}`);
|
|
1429
|
-
return next + body;
|
|
1430
|
-
};
|
|
1431
|
-
if (coverUrl !== '') {
|
|
1432
|
-
// 20260919 prod feedback: the podcast-hosted cover URL wins even when surfdash-pub
|
|
1433
|
-
// already wrote `image:` — its resolved og_image points at /content-posts/<slug>/
|
|
1434
|
-
// assets/cover.png, which the deployed worker does not serve (404).
|
|
1435
|
-
md = upsertKey(md, 'image', coverUrl);
|
|
1436
|
-
md = upsertKey(md, 'og_image', coverUrl);
|
|
1437
|
-
} else if (!/^image:/m.test(md.slice(0, fmEnd))) {
|
|
1438
|
-
// Fallback for runs without a podcast cover: copy the local cover instead.
|
|
1439
|
-
if (existsSync(localCover)) {
|
|
1440
|
-
const assets = join(dir, 'assets');
|
|
1441
|
-
mkdirSync(assets, { recursive: true });
|
|
1442
|
-
copyFileSync(localCover, join(assets, 'cover.png'));
|
|
1443
|
-
written.push(relative(surfdashRoot, join(assets, 'cover.png')));
|
|
1444
|
-
md = upsertKey(md, 'image', './assets/cover.png');
|
|
1445
|
-
md = upsertKey(md, 'og_image', './assets/cover.png');
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
if (showNotesUrl !== '' && !md.includes(showNotesUrl)) {
|
|
1449
|
-
md = `${md.replace(/\s*$/, '')}\n\n${crossLink[locale]}\n`;
|
|
1450
|
-
}
|
|
1451
|
-
if (md !== readFileSync(indexMd, 'utf-8')) {
|
|
1452
|
-
sted('write', indexMd, () => writeFileSync(indexMd, md));
|
|
1453
|
-
written.push(relative(surfdashRoot, indexMd));
|
|
1454
|
-
changed++;
|
|
1455
|
-
}
|
|
1456
1400
|
}
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
// rewrites files, so re-sync the EmDash rows from the patched files through the same
|
|
1461
|
-
// `kk executor` path as translate-sync (its plugin child gets EMDASH_API_TOKEN from kk
|
|
1462
|
-
// config; a direct orchestrator spawn gets 403 from prod dev-bypass). Runs before the
|
|
1463
|
-
// changed===0 early-return so re-runs heal rows an earlier commit left stale
|
|
1464
|
-
// (20260925/20260929). Fixture tests pass no plugins-path and must not shell out.
|
|
1465
|
-
if (pluginsPath === '') {
|
|
1466
|
-
// A missing plugins-path silently drops the CMS re-sync, and the CMS cards then keep the
|
|
1467
|
-
// pre-patch `./assets/cover.png` the deployed worker cannot serve (20261002 lost its
|
|
1468
|
-
// cover this way). Leave a marker for the classify step's completeness report.
|
|
1401
|
+
const resultPath = join(workDir, '31-publish', `${runDate}_23_patch-surfdash_sync-result.json`);
|
|
1402
|
+
if (process.env.KK_SURFDASH_PATCH_OFFLINE === '1') {
|
|
1403
|
+
// The marker keeps the completeness report's cms-resync row honest in offline runs.
|
|
1469
1404
|
const skipMarker = join(workDir, '31-publish', `${runDate}_23_patch-surfdash_emdash-resync.txt`);
|
|
1470
|
-
sted('write', skipMarker, () => writeRaw(skipMarker, 'skipped:
|
|
1471
|
-
console.error('patch-surfdash:
|
|
1472
|
-
} else {
|
|
1473
|
-
const syncIn = join(workDir, '31-publish', `${runDate}_23_patch-surfdash_sync.json`);
|
|
1474
|
-
const syncResult = join(workDir, '31-publish', `${runDate}_23_patch-surfdash_sync-result.json`);
|
|
1475
|
-
sted('write', syncIn, () =>
|
|
1476
|
-
writeJqText(syncIn, { body: '', options: { operation: 'sync', postPath: join(slugDir, 'index.md') } }),
|
|
1477
|
-
);
|
|
1478
|
-
const status = kk(
|
|
1479
|
-
['executor', 'run', 'surfdash-pub', '--in', syncIn, '--out', syncResult, ...pluginsArgs(pluginsPath)],
|
|
1480
|
-
{ unset: NESTED_SPUR_UNSET, soft: true },
|
|
1481
|
-
);
|
|
1482
|
-
const result = isFile(syncResult) ? readJson(syncResult) : null;
|
|
1483
|
-
const ok =
|
|
1484
|
-
status === 0 &&
|
|
1485
|
-
result !== null &&
|
|
1486
|
-
(result.ok === true || (result.targets as Array<{ ok?: boolean }> | undefined)?.[0]?.ok === true);
|
|
1487
|
-
if (!ok) {
|
|
1488
|
-
console.error(
|
|
1489
|
-
`patch-surfdash: WARNING: emdash re-sync failed (exit ${status}) — CMS cards keep the stale cover; fix: kk executor run surfdash-pub --in ${syncIn} --out ${syncResult} --plugins-path ${pluginsPath}`,
|
|
1490
|
-
);
|
|
1491
|
-
} else {
|
|
1492
|
-
console.error('patch-surfdash: emdash rows re-synced from patched files');
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
if (changed === 0) {
|
|
1496
|
-
console.error('patch-surfdash: nothing to change');
|
|
1405
|
+
sted('write', skipMarker, () => writeRaw(skipMarker, 'skipped: offline (KK_SURFDASH_PATCH_OFFLINE=1)\n'));
|
|
1406
|
+
console.error('patch-surfdash: offline — skip the plugin patch (KK_SURFDASH_PATCH_OFFLINE=1)');
|
|
1497
1407
|
return;
|
|
1498
1408
|
}
|
|
1499
|
-
const
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1409
|
+
const payload = join(workDir, '31-publish', `${runDate}_23_patch-surfdash_patch.json`);
|
|
1410
|
+
const podcastMeta = (pod.metadata ?? {}) as Record<string, unknown>;
|
|
1411
|
+
sted('write', payload, () =>
|
|
1412
|
+
writeJqText(payload, {
|
|
1413
|
+
body: '',
|
|
1414
|
+
options: {
|
|
1415
|
+
operation: 'patch',
|
|
1416
|
+
postPath,
|
|
1417
|
+
coverUrl: typeof podcastMeta.imageUrl === 'string' ? podcastMeta.imageUrl : '',
|
|
1418
|
+
showNotesUrl: typeof pod.url === 'string' ? pod.url : '',
|
|
1419
|
+
dateIso: `${runDate.slice(0, 4)}-${runDate.slice(4, 6)}-${runDate.slice(6, 8)}`,
|
|
1420
|
+
localCover: join(workDir, '23-cover', `${runDate}_07_cover_cover.png`),
|
|
1421
|
+
},
|
|
1422
|
+
}),
|
|
1510
1423
|
);
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1424
|
+
const status = kk(
|
|
1425
|
+
['executor', 'run', 'surfdash-pub', '--in', payload, '--out', resultPath, ...pluginsArgs(pluginsPath)],
|
|
1426
|
+
{ unset: NESTED_SPUR_UNSET, env: { SURFDASH_ROOT: surfdashRoot }, soft: true },
|
|
1514
1427
|
);
|
|
1428
|
+
const result = isFile(resultPath) ? readJson(resultPath) : null;
|
|
1429
|
+
const meta = (result?.metadata ?? {}) as Record<string, unknown>;
|
|
1430
|
+
if (result?.ok === true) {
|
|
1431
|
+
console.error(
|
|
1432
|
+
`patch-surfdash: ${String(result.message ?? 'patched')} — cms-resync ${String(meta.cmsResync ?? 'ok')}, commit ${String(meta.gitCommit ?? 'unchanged')}${Array.isArray(meta.absentLocales) && meta.absentLocales.length > 0 ? `, no post for ${(meta.absentLocales as string[]).join('/')}` : ''}`,
|
|
1433
|
+
);
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
// The posts are patched and committed; only the EmDash rows keep the unservable cover. That is
|
|
1437
|
+
// the soft channel the completeness report carries, so warn instead of failing the run.
|
|
1438
|
+
if (meta.cmsResync === 'failed') {
|
|
1439
|
+
console.error(
|
|
1440
|
+
`patch-surfdash: WARNING: cms re-sync failed — ${String(result?.error ?? `exit ${status}`)}; fix: kk executor run surfdash-pub --in ${payload} --out ${resultPath}${pluginsArgs(pluginsPath).join(' ')}`,
|
|
1441
|
+
);
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
failWith(1, `patch-surfdash: ${String(result?.error ?? `surfdash-pub patch exited ${status}`)}`);
|
|
1515
1445
|
});
|
|
1516
1446
|
}
|
|
1517
1447
|
|
|
@@ -24,10 +24,12 @@ not add a `kk publish` noun.
|
|
|
24
24
|
| medium | `medium-pub` | `file-cli` (token API) | `kk executor run medium-pub --in <content.json> --out <result.json>` |
|
|
25
25
|
| wechatmp | `wechatmp-pub` | `file-cli` (`draft/add`) | `kk executor run wechatmp-pub` (default operation `draft`) |
|
|
26
26
|
| x post | `x-pub` | `file-cli` (`POST /2/tweets`) | `kk executor run x-pub` (`options.operation` omitted / `post`) |
|
|
27
|
-
| x article | `x-pub` | `browser` / `
|
|
28
|
-
| juejin | `juejin-pub` | `browser` / `
|
|
29
|
-
| infoq | `infoq-pub` | `browser` / `
|
|
30
|
-
| substack | `substack-pub` | `browser` / `
|
|
27
|
+
| x article | `x-pub` | `browser` / `script` | `kk executor run x-pub --in <content.json> --out <result.json>` (`options.operation: article`); a missing or expired profile refuses (`ok: false`, naming `profile-check.ts login`) before any window opens |
|
|
28
|
+
| juejin | `juejin-pub` | `browser` / `script` | `kk executor run juejin-pub --in <content.json> --out <result.json>` |
|
|
29
|
+
| infoq | `infoq-pub` | `browser` / `script` | `kk executor run infoq-pub --in <content.json> --out <result.json>` |
|
|
30
|
+
| substack | `substack-pub` | `browser` / `script` | `kk executor run substack-pub --in <content.json> --out <result.json>` (needs `SUBSTACK_PUBLICATION_URL`) |
|
|
31
|
+
| threads | `threads-pub` | `file-cli` (Threads Graph, container → `threads_publish`) | `kk executor run threads-pub --in <content.json> --out <result.json>` — text only; needs `THREADS_ACCESS_TOKEN` + `THREADS_USER_ID`, no browser path |
|
|
32
|
+
| weibo | `weibo-pub` | `browser` / `script` | `kk executor run weibo-pub --in <content.json> --out <result.json>`; `options.operation` `article` (头条文章, draft via 保存草稿 by default) or `post` (home composer, which has **no draft** — it requires `metadata.publishStatus: public`) |
|
|
31
33
|
| xhs | `xhs-pub` | `browser` / `script` | `kk executor run xhs-pub --in <content.json> --out <result.json>` — its default `script` driver uploads the ordered card images with Playwright `setInputFiles`; the `agent` driver fails loud for this channel (ADR-024) |
|
|
32
34
|
|
|
33
35
|
Already-harvested file-cli channels (`qiita-pub`, `zenn-pub`, `surfdash-pub`,
|
|
@@ -99,5 +101,7 @@ the confirmed live status; otherwise fail naming the switch.
|
|
|
99
101
|
| medium-pub | `MEDIUM_INTEGRATION_TOKEN` |
|
|
100
102
|
| wechatmp-pub | `WECHAT_APP_ID`, `WECHAT_APP_SECRET` |
|
|
101
103
|
| x-pub `post` | `X_API_KEY`, `X_API_SECRET`, `X_ACCESS_TOKEN`, `X_ACCESS_TOKEN_SECRET` |
|
|
104
|
+
| threads-pub `post` | `THREADS_ACCESS_TOKEN`, `THREADS_USER_ID` |
|
|
105
|
+
| weibo-pub | none — its session lives in the browser profile (`WEIBO_PROFILE_DIR` overrides the dir; tier: `--profile` > `WEIBO_PROFILE_DIR` > `KK_BROWSER_PROFILE_DIR` > `~/.config/kk/browser-profiles/weibo`) |
|
|
102
106
|
|
|
103
107
|
Missing env → plugin exit 1, `Result.ok false`, error names the variable.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Feature-scoped verification pass (ADR-119, feature D62, task 0872; D63 task
|
|
2
|
+
# 0915 remediation folds the evidence contract into a workflow-owned script).
|
|
3
|
+
#
|
|
4
|
+
# Owns a lifecycle boundary no per-task graph owns: the repo-wide checks whose
|
|
5
|
+
# invariant spans the repository rather than a single task's diff. They run
|
|
6
|
+
# once per feature, deliberately the slow pass; `feature-lifecycle`'s verifying
|
|
7
|
+
# entry invokes this workflow (task 0880 caller wiring), and its verifying→done
|
|
8
|
+
# guard refuses to complete the feature until this pass records PASS (ADR-119
|
|
9
|
+
# consequence: a feature is not done until it passes).
|
|
10
|
+
#
|
|
11
|
+
# Run: spur workflow run feature-verification.yaml --vars '{"featureId":"D63"}'
|
|
12
|
+
#
|
|
13
|
+
# Shape: verify -> done | failed. The onEnter step runs the standard script
|
|
14
|
+
# `feature-verification-steps.ts` (no public CLI verb, D63 task 0915): it
|
|
15
|
+
# resolves this very definition for the verifier identity (name, source path,
|
|
16
|
+
# layer, definition digest), records the RUNNING receipt (run-scoped +
|
|
17
|
+
# feature-latest copies), registers the run-scoped receipt as a run artifact,
|
|
18
|
+
# runs the configured verification command via the safe launch splitter, and
|
|
19
|
+
# completes the receipt PASS/FAIL with the after-pass proof-input digest. A
|
|
20
|
+
# changed tree during the pass records FAIL.
|
|
21
|
+
#
|
|
22
|
+
# Reliability (aligned with task-pipeline / ADR-043): no agent.run nodes; the
|
|
23
|
+
# verification command comes from `vars.verificationCmd` below and is never
|
|
24
|
+
# interpolated through a shell. The always-exit-0 onEnter means missing,
|
|
25
|
+
# corrupt or FAIL evidence routes to `failed` via the guard (fail-closed),
|
|
26
|
+
# never a silent PASS; infrastructure errors exit nonzero and surface.
|
|
27
|
+
"$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
28
|
+
kind: state-machine
|
|
29
|
+
name: feature-verification
|
|
30
|
+
version: "1"
|
|
31
|
+
description: "Feature-scoped verification pass (ADR-119): runs the repo-wide check set once per feature and records a bound PASS/FAIL evidence receipt. Invoked by feature-lifecycle's verifying entry."
|
|
32
|
+
iterationBound: 2
|
|
33
|
+
initialState: verify
|
|
34
|
+
terminalStates:
|
|
35
|
+
- done
|
|
36
|
+
- failed
|
|
37
|
+
failureStates:
|
|
38
|
+
- failed
|
|
39
|
+
vars:
|
|
40
|
+
featureId: "X"
|
|
41
|
+
spurBin: "spur"
|
|
42
|
+
verificationCmd: "bun run spur-check"
|
|
43
|
+
__runId: ""
|
|
44
|
+
|
|
45
|
+
states:
|
|
46
|
+
- id: verify
|
|
47
|
+
description: >
|
|
48
|
+
Resolve this run's installed sp script root via Superskill, then run the
|
|
49
|
+
pinned installed verifier with Bun against the repo-wide check set. The
|
|
50
|
+
verifier records the bound evidence receipt (verifier identity,
|
|
51
|
+
checked-input digest, run identity) and the coarse status file. Both
|
|
52
|
+
actions fail closed — a locator/script failure, an unresolved or
|
|
53
|
+
non-installed root, an absent verifier, or a failing check yields a
|
|
54
|
+
non-PASS status — and always exit 0, so the transition guard routes
|
|
55
|
+
missing/corrupt/FAIL to `failed`.
|
|
56
|
+
onEnter:
|
|
57
|
+
- kind: shell
|
|
58
|
+
options:
|
|
59
|
+
command: >-
|
|
60
|
+
S="$(superskill script path sp script-root.mjs 2>/dev/null)" || S="";
|
|
61
|
+
if [ -n "$S" ] && [ -f "$S" ]; then node "$S" --run-id "$__runId" --spur-bin "$spurBin";
|
|
62
|
+
else echo "script-root failed closed — run 'superskill install sp --marketplace gobing-ai/spur'" >&2;
|
|
63
|
+
fi;
|
|
64
|
+
exit 0
|
|
65
|
+
- kind: shell
|
|
66
|
+
options:
|
|
67
|
+
command: >-
|
|
68
|
+
mkdir -p .spur/run;
|
|
69
|
+
DIR="$(jq -r 'select(.mode == "installed" and (.dir | type == "string") and .dir != "") | .dir' ".spur/run/$__runId-script-root.json" 2>/dev/null)" || DIR="";
|
|
70
|
+
[ -n "$DIR" ] && [ -f "$DIR/feature-verification-steps.mjs" ] && S="$DIR/feature-verification-steps.mjs";
|
|
71
|
+
[ -f "$S" ] && bun "$S" verify --feature-id "$featureId" --run-id "$__runId" --spur-bin "$spurBin" || printf 'FAIL feature-verification failed closed — unresolved root or verifier failure\n' > ".spur/run/$__runId-feature-verification.status";
|
|
72
|
+
exit 0
|
|
73
|
+
|
|
74
|
+
- id: done
|
|
75
|
+
description: Terminal — repo-wide checks passed and the receipt is bound.
|
|
76
|
+
|
|
77
|
+
- id: failed
|
|
78
|
+
description: Terminal — repo-wide checks failed or the evidence is missing/corrupt.
|
|
79
|
+
|
|
80
|
+
transitions:
|
|
81
|
+
- from: verify
|
|
82
|
+
to: done
|
|
83
|
+
description: Repo-wide checks passed — the script completed the receipt with PASS.
|
|
84
|
+
guard:
|
|
85
|
+
kind: shell
|
|
86
|
+
options:
|
|
87
|
+
command: 'test "$(cat .spur/run/$__runId-feature-verification.status 2>/dev/null)" = PASS'
|
|
88
|
+
- from: verify
|
|
89
|
+
to: failed
|
|
90
|
+
terminalReason: failed-check
|
|
91
|
+
description: Repo-wide checks failed (or the evidence is missing/corrupt) — stop at failed.
|
|
92
|
+
guard:
|
|
93
|
+
kind: always
|
|
@@ -91,6 +91,7 @@ transitions:
|
|
|
91
91
|
# FAIL, out-of-contract verdict, or NEEDS_REVISION with no retries left -> failed.
|
|
92
92
|
- from: judge
|
|
93
93
|
to: failed
|
|
94
|
+
terminalReason: failed-guard
|
|
94
95
|
description: "Judge verdict is FAIL, out-of-contract, or NEEDS_REVISION with retries exhausted -> block publish"
|
|
95
96
|
guard:
|
|
96
97
|
kind: shell
|
|
@@ -27,6 +27,10 @@ vars:
|
|
|
27
27
|
# builtin — a shell command may only reference declared vars). It is the lock owner and the
|
|
28
28
|
# `collect_runs` / ledger identity.
|
|
29
29
|
__runId: ""
|
|
30
|
+
# Definition digest of the executed workflow, injected by spur at launch (G8/0252 §9). The
|
|
31
|
+
# Board capture compares it with the public `spur workflow list --json` catalog and reads the
|
|
32
|
+
# topology only on exact agreement; a missing/mismatched digest omits topology with a diagnostic.
|
|
33
|
+
__definitionDigest: ""
|
|
30
34
|
# Per-source collection parameters, seeded into the plugin source configs by `lock`.
|
|
31
35
|
limit: "10"
|
|
32
36
|
last30days_topic: "AI news"
|
|
@@ -72,18 +76,34 @@ states:
|
|
|
72
76
|
# IS the lock contract, and the guard below reads it back:
|
|
73
77
|
# `{"acquired":true}` or `{"skipped":"locked","holder":<run>}` (R1).
|
|
74
78
|
command: >-
|
|
75
|
-
kk
|
|
79
|
+
kk lock acquire --name collect --run "${vars.__runId}" --ttl 21600000 > "${vars.work_dir}/lock.json"
|
|
76
80
|
|
|
77
81
|
- id: collect
|
|
78
82
|
description: >-
|
|
79
|
-
|
|
80
|
-
kk-daily-ai-voice's collect state)
|
|
83
|
+
Bind the run to its workspace for the Board (G8/0252 §9), then fan-in every ingestion source into
|
|
84
|
+
the run's blended Doc[] (same sources and timeout overrides as kk-daily-ai-voice's collect state)
|
|
85
|
+
and render the core-facts markdown with the existing stage (R3/R4).
|
|
81
86
|
onEnter:
|
|
82
87
|
- kind: shell
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
88
|
+
# Board capture (G8/0252 §9): revoke any previous workspace owner and bind THIS run to the
|
|
89
|
+
# workspace before any collection output exists. Revocation is mandatory — a refusal writes no
|
|
90
|
+
# board-capture.json, so the artifact guard below routes to `release-failed` and no result is
|
|
91
|
+
# ever stored over another owner's files. An optional capture failure after a successful
|
|
92
|
+
# revocation warns (captured:false) and only disables Board previews.
|
|
93
|
+
# onError: continue keeps the lock-releasing failure path intact instead of a hard abort.
|
|
94
|
+
options:
|
|
95
|
+
onError: continue
|
|
96
|
+
command: >-
|
|
97
|
+
kk board capture --workflow kk-collect --run "${vars.__runId}"
|
|
98
|
+
--work-dir "${vars.work_dir}" --stamp "${vars.stamp}"
|
|
99
|
+
--definition-digest "${vars.__definitionDigest}"
|
|
100
|
+
--setting "limit=${vars.limit}" --setting "horizon_hours=${vars.horizon_hours}"
|
|
101
|
+
> "${vars.work_dir}/board-capture.json"
|
|
102
|
+
- kind: shell
|
|
103
|
+
# onError: continue on both ingestion actions, with the artifact guard below as the positive
|
|
104
|
+
# success contract: a state-machine aborts the run on a failed action without taking a
|
|
105
|
+
# transition, so a hard failure here would leave the lock behind. The run still ends `failed`
|
|
106
|
+
# — through `release-failed`, which releases the lock first.
|
|
87
107
|
onError: continue
|
|
88
108
|
options:
|
|
89
109
|
# horizon-ingest's latency straddles the fan-in 30m per-source default (0098/0099), so it gets
|
|
@@ -153,7 +173,7 @@ states:
|
|
|
153
173
|
- kind: shell
|
|
154
174
|
options:
|
|
155
175
|
command: >-
|
|
156
|
-
kk
|
|
176
|
+
kk lock release --name collect --run "${vars.__runId}"
|
|
157
177
|
|
|
158
178
|
- id: release-failed
|
|
159
179
|
description: >-
|
|
@@ -164,7 +184,7 @@ states:
|
|
|
164
184
|
onError: continue
|
|
165
185
|
options:
|
|
166
186
|
command: >-
|
|
167
|
-
kk
|
|
187
|
+
kk lock release --name collect --run "${vars.__runId}"
|
|
168
188
|
|
|
169
189
|
- id: done
|
|
170
190
|
description: "Terminal — collection stored; the daily workflow reads it back through `kk store extract`"
|
|
@@ -192,13 +212,14 @@ transitions:
|
|
|
192
212
|
|
|
193
213
|
- from: collect
|
|
194
214
|
to: store
|
|
195
|
-
description: "Blended Doc[]
|
|
215
|
+
description: "Blended Doc[], core facts and Board capture recorded -> store it"
|
|
196
216
|
guard:
|
|
197
217
|
kind: shell
|
|
198
218
|
options:
|
|
199
219
|
command: >-
|
|
200
220
|
test -s "${vars.work_dir}/10-ingest/${vars.stamp}_02_collect_blended.json" &&
|
|
201
|
-
test -s "${vars.work_dir}/20-facts/${vars.stamp}_02_collect_core-facts.md"
|
|
221
|
+
test -s "${vars.work_dir}/20-facts/${vars.stamp}_02_collect_core-facts.md" &&
|
|
222
|
+
test -s "${vars.work_dir}/board-capture.json"
|
|
202
223
|
|
|
203
224
|
- from: collect
|
|
204
225
|
to: release-failed
|
|
@@ -232,6 +253,17 @@ transitions:
|
|
|
232
253
|
# refusal names the offending value on stderr before the transition falls through to
|
|
233
254
|
# `release-failed` — fail-closed, and never a deadlock (that state is `always`).
|
|
234
255
|
# Kept at 3 logical commands: spur's ADR-115 composition lint errors on a guard above 5.
|
|
256
|
+
# Task 0268 — degraded pass is a WARNING, not a gate. `kk store label` prints
|
|
257
|
+
# `degraded: true` and writes `kk.store.label.degraded: fallback: N/M consults (P%)` to
|
|
258
|
+
# stderr whenever the fallback share of its consults exceeds 0.5 — a majority of consults
|
|
259
|
+
# answered `source: default` (spur's `no-backend` degradation). Chosen at one-half because
|
|
260
|
+
# the 2026-10-12 incident measured `fallback: 22/26` (0.85) and `33/45` (0.73), well past
|
|
261
|
+
# it, while a healthy pass stays near zero; the threshold is the operator's signal in
|
|
262
|
+
# launchd.log/the ledger, not a transition. A degraded pass still prints `verdict: PASS`,
|
|
263
|
+
# so this guard releases the lock and the labels it DID write survive for the next tick —
|
|
264
|
+
# making the verdict non-`PASS` instead would route to `release-failed` and, with the
|
|
265
|
+
# label state's `onError: continue`, throw away a whole day's labels for a backend the
|
|
266
|
+
# next tick can reach. The fail-closed defaults themselves are unchanged (task 0268 R3).
|
|
235
267
|
command: >-
|
|
236
268
|
test "$(jq -r 'if (.verdict // "") == "PASS" then (.verdict + (if ((.confidence // "") | test("^(HIGH|MEDIUM|LOW)$")) then "" else "!" end)) else (.verdict // "") end' "${vars.work_dir}/label-result.txt" 2>/dev/null)" = PASS ||
|
|
237
269
|
{ echo "kk-collect.label.confidence-refused: verdict '$(jq -r '.verdict // "<none>"' "${vars.work_dir}/label-result.txt" 2>/dev/null)' carries confidence '$(jq -r '.confidence // "<absent>"' "${vars.work_dir}/label-result.txt" 2>/dev/null)', not HIGH|MEDIUM|LOW" >&2; false; }
|
|
@@ -21,7 +21,9 @@ vars:
|
|
|
21
21
|
limit: "10"
|
|
22
22
|
language: "zh"
|
|
23
23
|
voice_profile: "robin-news"
|
|
24
|
-
# Voice driver switch — voicebox (voice-gen) | ominivoice (omni-voice-gen) | gemini (gemini-tts)
|
|
24
|
+
# Voice driver switch — voicebox (voice-gen) | ominivoice (omni-voice-gen) | gemini (gemini-tts)
|
|
25
|
+
# | voxcpm (voxcpm-tts, opt-in; ADR-036). VoxCPM never probes Gemini and never falls back; its
|
|
26
|
+
# 5.6 字/秒 spoken-rate estimate is PROVISIONAL until 0258 measures a real render.
|
|
25
27
|
# 20260917 introduced the switch; 20260920 flipped default to voicebox after an operator A/B;
|
|
26
28
|
# 20260929 operator override: gemini-tts is the default again (verified live on this machine —
|
|
27
29
|
# robin-news replicated profile, GEMINI_API_KEY + GEMINI_TTS_MODEL via ~/.config/kk/config.yaml
|
|
@@ -400,7 +402,7 @@ states:
|
|
|
400
402
|
kk stage daily-wrap-docs "${vars.work_dir}" "${vars.run_date}" "${vars.voice_profile}"
|
|
401
403
|
|
|
402
404
|
- id: generate
|
|
403
|
-
description: "Invoke voice driver plugin (vdriver var: ominivoice -> omni-voice-gen, voicebox -> voice-gen, gemini -> gemini-tts) to generate audio"
|
|
405
|
+
description: "Invoke voice driver plugin (vdriver var: ominivoice -> omni-voice-gen, voicebox -> voice-gen, gemini -> gemini-tts, voxcpm -> voxcpm-tts) to generate audio"
|
|
404
406
|
onEnter:
|
|
405
407
|
- kind: shell
|
|
406
408
|
options:
|