@gobing-ai/knowledge-kit 0.0.14 → 0.0.15
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/package.json +1 -1
- package/plugins/generations/omni-voice-gen/README.md +9 -3
- package/plugins/generations/omni-voice-gen/profiles.json +5 -1
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/backend.py +12 -1
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/pipeline.py +18 -2
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/profiles.py +8 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/voicescript.py +0 -12
- package/plugins/generations/omni-voice-gen/voices/robin-news.wav +0 -0
- package/plugins/kk/commands/workflow-run.md +54 -22
- package/plugins/kk/config.example.yaml +5 -0
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/scripts/kk-workflow-stages.ts +1226 -0
- package/plugins/kk/{workflows → scripts}/validate-voicescript.ts +4 -4
- package/plugins/kk/{workflows → scripts}/wrap-voicescript-doc.ts +7 -6
- package/plugins/kk/skills/audio-authoring/SKILL.md +11 -12
- package/plugins/kk/skills/itc-generating/SKILL.md +2 -2
- package/plugins/kk/skills/itc-generating/references/generic-craft.md +1 -1
- package/plugins/kk/skills/itc-generating/references/platform-english.md +1 -1
- package/plugins/kk/skills/itc-generating/references/platform-wechat.md +1 -1
- package/plugins/kk/skills/storm-research/SKILL.md +5 -5
- package/plugins/kk/skills/topic/SKILL.md +7 -7
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +149 -163
- package/plugins/kk/workflows/kk-itc.yaml +12 -29
- package/plugins/kk/workflows/kk-solo-podcast.yaml +72 -131
- package/plugins/kk/workflows/kk-storm-research.yaml +12 -62
package/package.json
CHANGED
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
OmniVoice-backed voice generator plugin for [knowledge-kit](../../../README.md).
|
|
4
4
|
Drop-in replacement for `voice-gen` — same `kk executor run` contract (`--in Doc[] --out Content`),
|
|
5
|
-
same audio metadata shape, same QC pipeline.
|
|
5
|
+
same audio metadata shape, same QC pipeline. `kk-daily-ai-voice` selects it by default via the
|
|
6
|
+
`voice_generator` var; fall back to the Voicebox backend with:
|
|
6
7
|
|
|
7
8
|
```bash
|
|
8
9
|
spur workflow run plugins/kk/workflows/kk-daily-ai-voice.yaml \
|
|
9
|
-
--vars '{"voice_generator":"
|
|
10
|
+
--vars '{"voice_generator":"voice-gen"}' # other vars per your kk config
|
|
10
11
|
```
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
> **Voice identity:** the shipped `robin-news` profile is an instruct placeholder. For a real
|
|
14
|
+
> operator voice, record a 3–10 s clip into a private registry (`OMNIVOICE_PROFILE_REGISTRY`)
|
|
15
|
+
> and it supersedes the committed entry.
|
|
13
16
|
|
|
14
17
|
## Setup
|
|
15
18
|
|
|
@@ -23,6 +26,9 @@ make sync # runs `uv sync` — creates .venv, installs omnivoice from P
|
|
|
23
26
|
> (~2–4 GB depending on device). The Whisper ASR model used for transcription-fidelity QC loads
|
|
24
27
|
> lazily on the first QC transcription (`OMNIVOICE_ASR_MODEL` overrides it). Subsequent runs
|
|
25
28
|
> use the HF cache. `OMNIVOICE_MODEL` overrides the model id or points to a local checkout.
|
|
29
|
+
> If the cached-model revalidation hang blocks a run (observed on flaky networks), set
|
|
30
|
+
> `HF_HUB_OFFLINE=1` once the model is cached — the plugin only needs the hub for the
|
|
31
|
+
> first pull.
|
|
26
32
|
> GPU (`cuda`) is auto-selected when available; MPS (Apple Silicon) and CPU are fallbacks.
|
|
27
33
|
> Set `OMNIVOICE_DEVICE` to override (e.g. `cpu` for CI).
|
|
28
34
|
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
+
"robin-news": {
|
|
3
|
+
"ref_audio": "voices/robin-news.wav",
|
|
4
|
+
"ref_text": "本届赛事由北京市人民政府、中央广播电视总台、世界机器人合作组织、亚太机器人世界杯国际理事会联合主办。来自六大洲 16 个国家的 666 支队伍、2056 台机器人参赛,队伍数量较首届增长 138%,机器人"
|
|
5
|
+
},
|
|
2
6
|
"sample-clone": {
|
|
3
7
|
"ref_audio": "voices/sample-ref.wav",
|
|
4
8
|
"ref_text": "Replace with the transcript of your reference clip."
|
|
@@ -7,6 +11,6 @@
|
|
|
7
11
|
"prompt": "voices/sample-prompt.pt"
|
|
8
12
|
},
|
|
9
13
|
"sample-narrator": {
|
|
10
|
-
"instruct": "
|
|
14
|
+
"instruct": "male, middle-aged, low pitch"
|
|
11
15
|
}
|
|
12
16
|
}
|
|
@@ -19,6 +19,15 @@ from .profiles import ProfileEntry
|
|
|
19
19
|
|
|
20
20
|
DEFAULT_MODEL = "k2-fsa/OmniVoice"
|
|
21
21
|
SAMPLE_RATE = 24000 # OmniVoice output rate; ASR transcribe is fixed at 24000 (task R4)
|
|
22
|
+
_ASR_CHUNK_SECONDS = 28.0 # ponytail: Whisper errors on >30s inputs; fixed chunks, silence-aware split if boundary words hurt fidelity
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _chunk_for_asr(audio: np.ndarray) -> list[np.ndarray]:
|
|
26
|
+
"""Split a waveform into <=30s slices the upstream Whisper pipe accepts."""
|
|
27
|
+
chunk = int(_ASR_CHUNK_SECONDS * SAMPLE_RATE)
|
|
28
|
+
if len(audio) <= chunk:
|
|
29
|
+
return [audio]
|
|
30
|
+
return [audio[i : i + chunk] for i in range(0, len(audio), chunk)]
|
|
22
31
|
|
|
23
32
|
|
|
24
33
|
class SegmentSpec(TypedDict):
|
|
@@ -147,4 +156,6 @@ class OmnivoiceBackend:
|
|
|
147
156
|
if not self._asr_loaded:
|
|
148
157
|
self._model.load_asr_model(model_name=self._asr_model)
|
|
149
158
|
self._asr_loaded = True
|
|
150
|
-
|
|
159
|
+
audio = _wav_to_float(wav)
|
|
160
|
+
parts = [str(self._model.transcribe((part, SAMPLE_RATE))) for part in _chunk_for_asr(audio)]
|
|
161
|
+
return " ".join(part for part in parts if part)
|
|
@@ -39,7 +39,6 @@ from .voicescript import (
|
|
|
39
39
|
WarnSink,
|
|
40
40
|
merge_voice_scripts,
|
|
41
41
|
parse_doc_to_voicescript,
|
|
42
|
-
resolve_instruct,
|
|
43
42
|
validate_voice_script,
|
|
44
43
|
)
|
|
45
44
|
|
|
@@ -51,6 +50,10 @@ def _log(msg: str) -> None:
|
|
|
51
50
|
print(f"omni-voice-gen: {msg}", file=sys.stderr)
|
|
52
51
|
|
|
53
52
|
|
|
53
|
+
# Emotions already warned about this run (one line per distinct value, not per segment).
|
|
54
|
+
_ignored_emotions: set[str] = set()
|
|
55
|
+
|
|
56
|
+
|
|
54
57
|
def _delete(*paths: str) -> None:
|
|
55
58
|
for path in paths:
|
|
56
59
|
Path(path).unlink(missing_ok=True)
|
|
@@ -188,11 +191,24 @@ def _render(
|
|
|
188
191
|
speaker = (script.speakers or {}).get(segment.speaker) if segment.speaker else None
|
|
189
192
|
profile = resolve_profile_target(segment, speaker, script, env_default, docs)
|
|
190
193
|
emotion = _first(segment.emotion, getattr(speaker, "emotion", None), script.default_emotion)
|
|
194
|
+
# OmniVoice instruct accepts a fixed vocabulary (accent/age/pitch) with no emotion items,
|
|
195
|
+
# so voicegen-era free-text emotion is unrepresentable: drop it loudly and let the
|
|
196
|
+
# resolved profile's instruct carry the voice. An explicit segment/speaker instruct is
|
|
197
|
+
# operator-authored whitelist text and passes through untouched.
|
|
198
|
+
segment_instruct = _first(segment.instruct, getattr(speaker, "instruct", None))
|
|
199
|
+
if segment_instruct is None and emotion is not None:
|
|
200
|
+
key = emotion.strip().lower()
|
|
201
|
+
if key not in _ignored_emotions:
|
|
202
|
+
_ignored_emotions.add(key)
|
|
203
|
+
_log(
|
|
204
|
+
f"ignoring emotion {emotion!r}: OmniVoice instruct vocabulary has no emotion items — "
|
|
205
|
+
"voice comes from the resolved profile"
|
|
206
|
+
)
|
|
191
207
|
spec: SegmentSpec = {
|
|
192
208
|
"text": segment.text,
|
|
193
209
|
"language": _first(segment.language, getattr(speaker, "language", None), script.language, "en"),
|
|
194
210
|
"profile": resolve_profile(profile, registry),
|
|
195
|
-
"instruct":
|
|
211
|
+
"instruct": segment_instruct,
|
|
196
212
|
"seed": None if segment.seed is None else int(segment.seed),
|
|
197
213
|
"speed": None,
|
|
198
214
|
}
|
|
@@ -83,6 +83,14 @@ def load_registry(path: Path | None = None) -> ProfileRegistry:
|
|
|
83
83
|
f"profile registry at {path} must be a JSON object mapping profile names to entries"
|
|
84
84
|
)
|
|
85
85
|
entries = {name: _parse_entry(name, spec) for name, spec in data.items()}
|
|
86
|
+
# Resolve relative asset paths against the registry's directory so backend passes
|
|
87
|
+
# absolute paths to the engine regardless of process CWD.
|
|
88
|
+
base = path.resolve().parent
|
|
89
|
+
for entry in entries.values():
|
|
90
|
+
if entry.ref_audio and not Path(entry.ref_audio).is_absolute():
|
|
91
|
+
entry.ref_audio = str((base / entry.ref_audio).resolve())
|
|
92
|
+
if entry.prompt and not Path(entry.prompt).is_absolute():
|
|
93
|
+
entry.prompt = str((base / entry.prompt).resolve())
|
|
86
94
|
lowered = [key.lower() for key in entries]
|
|
87
95
|
if len(set(lowered)) != len(lowered):
|
|
88
96
|
dupes = sorted({key for key in entries if lowered.count(key.lower()) > 1})
|
|
@@ -338,15 +338,3 @@ def validate_voice_script(script: VoiceScript, sink: WarnSink) -> None:
|
|
|
338
338
|
raise ValueError("seed must be a non-negative integer")
|
|
339
339
|
|
|
340
340
|
_collect_ignored(segment, f"segments[{idx}]", sink)
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
def resolve_instruct(instruct: str | None = None, emotion: str | None = None) -> str | None:
|
|
344
|
-
if instruct and emotion:
|
|
345
|
-
if emotion.lower() in instruct.lower():
|
|
346
|
-
return instruct
|
|
347
|
-
return f"{instruct}. Emotion: {emotion}"
|
|
348
|
-
if instruct:
|
|
349
|
-
return instruct
|
|
350
|
-
if emotion:
|
|
351
|
-
return f"Speak in a {emotion} tone."
|
|
352
|
-
return None
|
|
Binary file
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
name: workflow-run
|
|
3
3
|
description: >-
|
|
4
4
|
Run STORM daily research, IT content authoring, or solo-podcast end to end.
|
|
5
|
-
Resolves the workflow YAML from the user override, KK_WORKFLOWS_SOURCE,
|
|
6
|
-
|
|
5
|
+
Resolves the workflow YAML from the user override, KK_WORKFLOWS_SOURCE, bun/npm
|
|
6
|
+
global installs, the kk package root, or a repo checkout — runs in place from any cwd;
|
|
7
7
|
creates the config with defaults if missing; computes the workspace; shells
|
|
8
8
|
spur workflow run against the resolved YAML.
|
|
9
9
|
argument-hint: "[name] <topic|--in file> [--dir <path>] [--playbook generic|english|wechat] [--research] [--judge] [--outline <a|b|c>] [--writer itc-generating|topic] [--duration <min>] [--language <code>] [--script-approved] [--fixture] [--force]"
|
|
@@ -13,8 +13,9 @@ Thin runner around the **storm-research**, **itc-generating** / **topic**, and *
|
|
|
13
13
|
capabilities. Read `plugins/kk/skills/storm-research/SKILL.md` or
|
|
14
14
|
`plugins/kk/skills/itc-generating/SKILL.md` for craft; solo-podcast craft lives in the
|
|
15
15
|
`audio-authoring` skill. This command owns the run procedure: config create,
|
|
16
|
-
workflow YAML resolution (user override → `$KK_WORKFLOWS_SOURCE` → **
|
|
17
|
-
root** → repo checkout, runs in place from any cwd), workspace
|
|
16
|
+
workflow YAML resolution (user override → `$KK_WORKFLOWS_SOURCE` → **bun/npm global
|
|
17
|
+
install** → **installed package root** → repo checkout, runs in place from any cwd), workspace
|
|
18
|
+
computation, and the
|
|
18
19
|
`spur workflow run` of the resolved YAML. Design-time SSOT remains
|
|
19
20
|
`plugins/kk/workflows/` in the package/repo. No new `kk` CLI noun (ADR-011) — everything here
|
|
20
21
|
is shell + the workflow.
|
|
@@ -30,12 +31,13 @@ Parse per the frozen rule:
|
|
|
30
31
|
`kk-storm-research` (default), `kk-itc`, `kk-solo-podcast`. Any other name after successful copy → exit 1 listing
|
|
31
32
|
`kk-storm-research`, `kk-itc`, `kk-solo-podcast`.
|
|
32
33
|
- `--in <file>` → file mode. `--force` → overwrite an existing dest YAML / existing `brief.md`.
|
|
33
|
-
- Profile `kk-storm-research`: `--
|
|
34
|
-
|
|
34
|
+
- Profile `kk-storm-research`: `--dir <path>` (default `$works_dir/kk-storm-research/<topic-id>`),
|
|
35
|
+
`--fixture` → force `fixture: "true"`.
|
|
36
|
+
- Profile `kk-itc`: `--dir <path>` (default `$works_dir/kk-itc/<kebab>`), `--playbook generic|english|wechat` (default `generic`),
|
|
35
37
|
`--research` (`true`|`false`, default `false`), `--judge` (`true`|`false`, default `false`),
|
|
36
38
|
`--outline <a|b|c>` (default empty), `--writer itc-generating|topic` (default `itc-generating`).
|
|
37
39
|
`--fixture` passed with `kk-itc` or `kk-solo-podcast` → exit 1 (`fixture is storm-only`).
|
|
38
|
-
- Profile `kk-solo-podcast`: `--dir <path>` (default `$works_dir/<
|
|
40
|
+
- Profile `kk-solo-podcast`: `--dir <path>` (default `$works_dir/kk-solo-podcast/<date>`), `--outline <a|b|c>`
|
|
39
41
|
(default empty), `--duration <min>` (default `8`), `--language <code>` (default `en`),
|
|
40
42
|
`--script-approved` (sets `script_approved=true`; default off). `--fixture` is storm-only.
|
|
41
43
|
- **XOR:** exactly one of `<topic>` / `--in <file>`. Both set or both empty → exit 1, stderr
|
|
@@ -90,6 +92,19 @@ while [ -L "$kbin" ]; do
|
|
|
90
92
|
case "$link" in /*) kbin="$link";; *) kbin="$(dirname "$kbin")/$link";; esac
|
|
91
93
|
done
|
|
92
94
|
pkg=""; [ -n "$kbin" ] && pkg="$(cd "$(dirname "$kbin")/.." 2>/dev/null && pwd || true)"
|
|
95
|
+
# Global-install roots — the common no-config case (package installed with `bun install -g`
|
|
96
|
+
# or `npm i -g` and no `workflows_dir` written yet). Each probe is guarded, so a missing
|
|
97
|
+
# package manager never breaks the chain.
|
|
98
|
+
bun_global_workflows=""
|
|
99
|
+
if command -v bun >/dev/null 2>&1; then
|
|
100
|
+
bg="$(dirname "$(bun pm bin -g 2>/dev/null)")/install/global/node_modules/@gobing-ai/knowledge-kit/plugins/kk/workflows"
|
|
101
|
+
[ -d "$bg" ] && bun_global_workflows="$bg"
|
|
102
|
+
fi
|
|
103
|
+
npm_global_workflows=""
|
|
104
|
+
if command -v npm >/dev/null 2>&1; then
|
|
105
|
+
ng="$(dirname "$(npm root -g 2>/dev/null)")/node_modules/@gobing-ai/knowledge-kit/plugins/kk/workflows"
|
|
106
|
+
[ -d "$ng" ] && npm_global_workflows="$ng"
|
|
107
|
+
fi
|
|
93
108
|
```
|
|
94
109
|
|
|
95
110
|
Resolution order — first hit wins:
|
|
@@ -97,26 +112,31 @@ Resolution order — first hit wins:
|
|
|
97
112
|
1. **User override** `$workflows_dir/$NAME.yaml` (default `~/.config/kk/workflows/`) —
|
|
98
113
|
when it exists it always wins, and nothing is copied.
|
|
99
114
|
2. `$KK_WORKFLOWS_SOURCE/$NAME.yaml`
|
|
100
|
-
3. `$
|
|
101
|
-
4.
|
|
115
|
+
3. **bun global install** — `$(dirname $(bun pm bin -g))/install/global/node_modules/@gobing-ai/knowledge-kit/plugins/kk/workflows/$NAME.yaml`
|
|
116
|
+
4. **npm global install** — `$(dirname $(npm root -g))/node_modules/@gobing-ai/knowledge-kit/plugins/kk/workflows/$NAME.yaml`
|
|
117
|
+
5. `$pkg/plugins/kk/workflows/$NAME.yaml` — the installed package copy (resolved from the `kk` binary)
|
|
118
|
+
6. `plugins/kk/workflows/$NAME.yaml` — repo checkout (development)
|
|
119
|
+
|
|
120
|
+
A corrupt `config.yaml` already exited 1 in step 2; none of these probes runs before that check,
|
|
121
|
+
so no fallback masks a config error.
|
|
102
122
|
|
|
103
123
|
```bash
|
|
104
124
|
dest=""
|
|
105
125
|
if [ -f "$workflows_dir/$NAME.yaml" ]; then
|
|
106
126
|
dest="$workflows_dir/$NAME.yaml"
|
|
107
127
|
else
|
|
108
|
-
for d in "$KK_WORKFLOWS_SOURCE" ${pkg:+"$pkg/plugins/kk/workflows"} "plugins/kk/workflows"; do
|
|
128
|
+
for d in "$KK_WORKFLOWS_SOURCE" "$bun_global_workflows" "$npm_global_workflows" ${pkg:+"$pkg/plugins/kk/workflows"} "plugins/kk/workflows"; do
|
|
109
129
|
[ -n "$d" ] && [ -f "$d/$NAME.yaml" ] && { dest="$d/$NAME.yaml"; break; }
|
|
110
130
|
done
|
|
111
|
-
[ -z "$dest" ] && { echo "no source workflow for $NAME (searched $workflows_dir, KK_WORKFLOWS_SOURCE, kk package root, plugins/kk/workflows)" >&2; exit 1; }
|
|
131
|
+
[ -z "$dest" ] && { echo "no source workflow for $NAME (searched $workflows_dir, KK_WORKFLOWS_SOURCE, bun/npm global installs, kk package root, plugins/kk/workflows)" >&2; exit 1; }
|
|
112
132
|
fi
|
|
113
133
|
# --force: refresh the user override from the best available source, then run the override.
|
|
114
134
|
if [ "$FORCE" = true ]; then
|
|
115
135
|
src=""
|
|
116
|
-
for d in "$KK_WORKFLOWS_SOURCE" ${pkg:+"$pkg/plugins/kk/workflows"} "plugins/kk/workflows"; do
|
|
136
|
+
for d in "$KK_WORKFLOWS_SOURCE" "$bun_global_workflows" "$npm_global_workflows" ${pkg:+"$pkg/plugins/kk/workflows"} "plugins/kk/workflows"; do
|
|
117
137
|
[ -n "$d" ] && [ -f "$d/$NAME.yaml" ] && { src="$d/$NAME.yaml"; break; }
|
|
118
138
|
done
|
|
119
|
-
[ -z "$src" ] && { echo "no source workflow for $NAME to force-install (searched KK_WORKFLOWS_SOURCE, kk package root, plugins/kk/workflows)" >&2; exit 1; }
|
|
139
|
+
[ -z "$src" ] && { echo "no source workflow for $NAME to force-install (searched KK_WORKFLOWS_SOURCE, bun/npm global installs, kk package root, plugins/kk/workflows)" >&2; exit 1; }
|
|
120
140
|
install -d "$workflows_dir" && cp "$src" "$workflows_dir/$NAME.yaml"
|
|
121
141
|
dest="$workflows_dir/$NAME.yaml"
|
|
122
142
|
fi
|
|
@@ -126,9 +146,11 @@ fi
|
|
|
126
146
|
`--force` to place a fresh copy there) — that override wins on every later run.
|
|
127
147
|
- No auto-copy, no drift warnings: the package copy is versioned with the CLI and
|
|
128
148
|
never goes stale; overrides are explicit.
|
|
129
|
-
- Sidecars (`
|
|
130
|
-
|
|
131
|
-
root,
|
|
149
|
+
- Sidecars (`plugins/kk/scripts/kk-workflow-stages.ts`, plus the
|
|
150
|
+
`validate-voicescript.ts` / `wrap-voicescript-doc.ts` it delegates to) are **not copied** —
|
|
151
|
+
the workflow resolves the stage runner from the package root, and the runner resolves each
|
|
152
|
+
sidecar via the explicit var, `$KK_WORKFLOWS_DIR`, the package copy, then
|
|
153
|
+
`$HOME/.config/kk/workflows`.
|
|
132
154
|
|
|
133
155
|
## 4. Compute workspace
|
|
134
156
|
|
|
@@ -143,7 +165,7 @@ slug=$(printf '%s' "$normalized" | tr -cs 'a-z0-9' '-' | sed 's/^-*//; s/-*$//'
|
|
|
143
165
|
[ -z "$slug" ] && slug="topic"
|
|
144
166
|
hash=$(printf '%s' "$normalized" | shasum -a 256 | cut -c1-8)
|
|
145
167
|
topic_id="${slug}-${hash}"
|
|
146
|
-
work_dir="$works_dir/$topic_id"
|
|
168
|
+
work_dir="${DIR:-$works_dir/kk-storm-research/$topic_id}"
|
|
147
169
|
```
|
|
148
170
|
|
|
149
171
|
- `raw` derives empty (no H1 and empty basename) → exit 1 with the derived value.
|
|
@@ -157,15 +179,24 @@ mkdir -p "$work_dir"
|
|
|
157
179
|
|
|
158
180
|
`raw` = `TOPIC` or file-mode first ATX H1 else basename without extension.
|
|
159
181
|
`kebab` = lower-case, `[^a-z0-9]+` → `-`, trimmed dashes (no length cap, no sha256 hash).
|
|
160
|
-
`work_dir="${DIR
|
|
182
|
+
`work_dir="${DIR:-$works_dir/kk-itc/$kebab}"`. Empty derived `kebab` → exit 1. An explicit
|
|
183
|
+
`--dir` still wins, and the default never writes into the caller's cwd.
|
|
161
184
|
|
|
162
|
-
### Profile `kk-solo-podcast`
|
|
185
|
+
### Profile `kk-solo-podcast`
|
|
163
186
|
|
|
164
187
|
`date` = today's date in `YYYY-MM-DD` (`$(date +%Y-%m-%d)`).
|
|
165
188
|
`raw` = `TOPIC` or file-mode first ATX H1 else basename without extension.
|
|
166
189
|
`kebab` = lower-case, `[^a-z0-9]+` → `-`, trimmed dashes (no length cap, no sha256 hash).
|
|
167
190
|
`work_dir="${DIR:-$works_dir/kk-solo-podcast/$date}"`.
|
|
168
191
|
|
|
192
|
+
### Profile `kk-daily-ai-voice`
|
|
193
|
+
|
|
194
|
+
Same `date` / `raw` / `kebab` derivation as `kk-solo-podcast`.
|
|
195
|
+
`work_dir="${DIR:-$works_dir/kk-daily-ai-voice/$date}"`.
|
|
196
|
+
|
|
197
|
+
Every profile's default puts its workspace in **its own** `$works_dir/<workflow-name>/` namespace;
|
|
198
|
+
an explicit `--dir` overrides all four defaults.
|
|
199
|
+
|
|
169
200
|
## 5. Run the workflow
|
|
170
201
|
|
|
171
202
|
All values are strings.
|
|
@@ -216,9 +247,10 @@ spur workflow run "$dest" --vars \
|
|
|
216
247
|
and `$work_dir/content.json` (machine contract). Skim `content.md` citations against `docs.json`
|
|
217
248
|
ids before presenting.
|
|
218
249
|
- **`kk-itc`**: print resolved `work_dir`, then `$work_dir/brief.md`, `$work_dir/2-outline/outline-approved.md`,
|
|
219
|
-
and `$work_dir/3-draft
|
|
220
|
-
- **`kk-solo-podcast`**: print resolved `work_dir`, then `$work_dir/
|
|
221
|
-
`$work_dir/
|
|
250
|
+
and `$work_dir/3-draft-draft-article.md`. If `judge=true`, also print the verdict path.
|
|
251
|
+
- **`kk-solo-podcast`**: print resolved `work_dir`, then `$work_dir/1-briefing-briefing.md`,
|
|
252
|
+
`$work_dir/3-script-voicescript.yaml`, `$work_dir/4-audio/content.json`, and
|
|
253
|
+
`$work_dir/4-audio/content.wav`.
|
|
222
254
|
|
|
223
255
|
If the run ended `failed`, show the failing state (`spur workflow trace <run-id> --json`) and the
|
|
224
256
|
fail-loud recovery line.
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# kk operator config. `works_dir` is the run-workspace root — each workflow run defaults
|
|
2
|
+
# to <works_dir>/<workflow-name>/<run-id> unless --dir is given (existing `.works/`-era
|
|
3
|
+
# trees are not migrated). `workflows_dir` is the user-override root for workflow YAMLs;
|
|
4
|
+
# when a workflow is not found there, /workflow-run falls back to KK_WORKFLOWS_SOURCE,
|
|
5
|
+
# bun/npm global installs, the kk package root, then the repo checkout.
|
|
1
6
|
works_dir: "~/.config/kk/works"
|
|
2
7
|
workflows_dir: "~/.config/kk/workflows"
|
|
3
8
|
defaults:
|
package/plugins/kk/plugin.json
CHANGED