@gobing-ai/knowledge-kit 0.0.11 → 0.0.13
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/dist/index.js +451 -152
- package/package.json +1 -1
- package/plugins/generations/content-gen/dist/index.js +22167 -0
- package/plugins/generations/content-gen/plugin.json +1 -1
- package/plugins/generations/content-gen/src/storm.ts +99 -50
- package/plugins/generations/core-facts-gen/dist/index.js +22048 -0
- package/plugins/generations/core-facts-gen/package.json +17 -0
- package/plugins/generations/core-facts-gen/plugin.json +7 -0
- package/plugins/generations/core-facts-gen/src/index.ts +116 -0
- package/plugins/generations/core-facts-gen/tsconfig.json +4 -0
- package/plugins/generations/daily-article-gen/dist/index.js +22022 -0
- package/plugins/generations/daily-article-gen/package.json +17 -0
- package/plugins/generations/daily-article-gen/plugin.json +7 -0
- package/plugins/generations/daily-article-gen/src/index.ts +91 -0
- package/plugins/generations/daily-article-gen/tsconfig.json +4 -0
- package/plugins/generations/dailynews-gen/dist/index.js +22324 -0
- package/plugins/generations/dailynews-gen/plugin.json +1 -1
- package/plugins/generations/dailynews-gen/src/index.ts +11 -0
- package/plugins/generations/dailynews-gen/src/script-builder.ts +1 -1
- package/plugins/generations/episode-plan-gen/dist/index.js +22479 -0
- package/plugins/generations/episode-plan-gen/package.json +17 -0
- package/plugins/generations/episode-plan-gen/plugin.json +7 -0
- package/plugins/generations/episode-plan-gen/src/index.ts +726 -0
- package/plugins/generations/episode-plan-gen/tsconfig.json +4 -0
- package/plugins/generations/image-gen/config.example.yaml +75 -0
- package/plugins/generations/image-gen/dist/index.js +22619 -0
- package/plugins/generations/image-gen/package.json +17 -0
- package/plugins/generations/image-gen/plugin.json +7 -0
- package/plugins/generations/image-gen/presets/formats/cover.yaml +57 -0
- package/plugins/generations/image-gen/presets/formats/free.yaml +46 -0
- package/plugins/generations/image-gen/presets/formats/illustration.yaml +48 -0
- package/plugins/generations/image-gen/presets/styles/clean-webapp-ui.yaml +28 -0
- package/plugins/generations/image-gen/presets/styles/cute.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/editorial.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/fresh.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/minimalist.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/photorealistic.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/sketch.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/technical-diagram.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/vibrant.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/warm.yaml +3 -0
- package/plugins/generations/image-gen/src/bytes.ts +19 -0
- package/plugins/generations/image-gen/src/index.ts +319 -0
- package/plugins/generations/image-gen/src/job.ts +143 -0
- package/plugins/generations/image-gen/src/paths.ts +31 -0
- package/plugins/generations/image-gen/src/presets.ts +344 -0
- package/plugins/generations/image-gen/src/providers/seedream.ts +128 -0
- package/plugins/generations/image-gen/src/providers/types.ts +285 -0
- package/plugins/generations/image-gen/tsconfig.json +8 -0
- package/plugins/generations/voice-gen/dist/index.js +23027 -0
- package/plugins/generations/voice-gen/plugin.json +1 -1
- package/plugins/generations/voice-gen/src/index.ts +102 -11
- package/plugins/generations/voice-gen/src/qc.ts +154 -9
- package/plugins/ingestions/aihot-ingest/dist/index.js +22358 -0
- package/plugins/ingestions/aihot-ingest/plugin.json +2 -2
- package/plugins/ingestions/aihot-ingest/src/index.ts +72 -13
- package/plugins/ingestions/aihot-ingest/src/mapper.ts +1 -0
- package/plugins/ingestions/aihot-ingest/src/rss.ts +151 -0
- package/plugins/ingestions/horizon-ingest/dist/index.js +22105 -0
- package/plugins/ingestions/horizon-ingest/package.json +17 -0
- package/plugins/ingestions/horizon-ingest/plugin.json +7 -0
- package/plugins/ingestions/horizon-ingest/src/index.ts +205 -0
- package/plugins/ingestions/horizon-ingest/tsconfig.json +4 -0
- package/plugins/ingestions/karakeep-local/dist/index.js +24204 -0
- package/plugins/ingestions/karakeep-local/plugin.json +1 -1
- package/plugins/ingestions/last30days-ingest/dist/index.js +22050 -0
- package/plugins/ingestions/last30days-ingest/package.json +17 -0
- package/plugins/ingestions/last30days-ingest/plugin.json +7 -0
- package/plugins/ingestions/last30days-ingest/src/index.ts +148 -0
- package/plugins/ingestions/last30days-ingest/tsconfig.json +4 -0
- package/plugins/ingestions/web-search/dist/index.js +24379 -0
- package/plugins/ingestions/web-search/plugin.json +1 -1
- package/plugins/kk/commands/image-generate.md +15 -0
- package/plugins/kk/config.example.yaml +80 -0
- package/plugins/kk/skills/image-authoring/SKILL.md +250 -0
- package/plugins/kk/skills/image-authoring/references/illustration-positions.md +87 -0
- package/plugins/kk/skills/image-authoring/references/migrating-from-wt.md +31 -0
- package/plugins/kk/skills/image-authoring/references/providers.md +52 -0
- package/plugins/kk/skills/image-authoring/references/style-extraction.md +134 -0
- package/plugins/kk/skills/taste-unslop/SKILL.md +12 -6
- package/plugins/kk/skills/taste-unslop/references/pattern-guide.md +128 -48
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +432 -19
- package/plugins/publishings/emdash-pub/dist/index.js +22243 -0
- package/plugins/publishings/emdash-pub/plugin.json +1 -1
- package/plugins/publishings/podcast-pub/dist/index.js +22596 -0
- package/plugins/publishings/podcast-pub/package.json +17 -0
- package/plugins/publishings/podcast-pub/plugin.json +13 -0
- package/plugins/publishings/podcast-pub/src/index.ts +538 -0
- package/plugins/publishings/podcast-pub/src/map.ts +165 -0
- package/plugins/publishings/podcast-pub/src/microfeed-client.ts +196 -0
- package/plugins/publishings/podcast-pub/src/show-notes.ts +132 -0
- package/plugins/publishings/podcast-pub/tsconfig.json +4 -0
- package/plugins/publishings/qiita-pub/dist/index.js +22081 -0
- package/plugins/publishings/qiita-pub/plugin.json +1 -1
- package/plugins/publishings/surfdash-pub/dist/index.js +22233 -0
- package/plugins/publishings/surfdash-pub/plugin.json +1 -1
- package/plugins/publishings/surfdash-pub/src/index.ts +328 -62
- package/plugins/publishings/zenn-pub/dist/index.js +22122 -0
- package/plugins/publishings/zenn-pub/plugin.json +1 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: image-generate
|
|
3
|
+
description: Generate images from a prompt or an article — covers, illustrations, or free-form — using named format and style presets.
|
|
4
|
+
argument-hint: "[prompt] [--format <name>] [--style <name>] [--content <file>] [--ref <path>] [--var K=V] [--dry-run] [--list-presets]"
|
|
5
|
+
allowed-tools: ["Skill"]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# image-generate
|
|
9
|
+
|
|
10
|
+
Thin wrapper for the `image-authoring` skill — all preset resolution, preference precedence,
|
|
11
|
+
confirmation and delegation logic lives in the skill. Forward `$ARGUMENTS` unchanged.
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
Skill(skill="kk:image-authoring", args="$ARGUMENTS")
|
|
15
|
+
```
|
|
@@ -22,6 +22,73 @@ plugins:
|
|
|
22
22
|
VOICEBOX_POLL_MS: "1000" # GET /history/{id} interval
|
|
23
23
|
VOICEBOX_MAX_CHUNK_CHARS: "800" # Voicebox auto-chunk size (100–5000)
|
|
24
24
|
VOICEBOX_CROSSFADE_MS: "50" # Voicebox chunk crossfade (0–500)
|
|
25
|
+
|
|
26
|
+
# image-gen (task 0103/0104): full credential inventory, placeholder values only —
|
|
27
|
+
# source of truth: plugins/generations/image-gen/config.example.yaml
|
|
28
|
+
image-gen:
|
|
29
|
+
env:
|
|
30
|
+
# -- google (Gemini multimodal + Imagen; GOOGLE_* and GEMINI_* are fallback pairs) --
|
|
31
|
+
GOOGLE_API_KEY: "" # or GEMINI_API_KEY — either one enables google
|
|
32
|
+
GEMINI_API_KEY: ""
|
|
33
|
+
GOOGLE_IMAGE_MODEL: "" # optional — default gemini-3-pro-image
|
|
34
|
+
GOOGLE_BASE_URL: "" # optional — default https://generativelanguage.googleapis.com
|
|
35
|
+
# -- openai (gpt-image generations/edits; OPENAI_IMAGE_USE_CHAT is a debug escape hatch) --
|
|
36
|
+
OPENAI_API_KEY: "" # platform.openai.com — required for openai
|
|
37
|
+
OPENAI_IMAGE_MODEL: "" # optional — default gpt-image-2
|
|
38
|
+
OPENAI_BASE_URL: "" # optional — default https://api.openai.com/v1
|
|
39
|
+
OPENAI_IMAGE_USE_CHAT: "" # optional — "true" routes through chat completions
|
|
40
|
+
# -- openrouter (multi-host gateway) --
|
|
41
|
+
OPENROUTER_API_KEY: "" # openrouter.ai/settings/keys — required for openrouter
|
|
42
|
+
OPENROUTER_IMAGE_MODEL: "" # optional — default google/gemini-3.1-flash-image
|
|
43
|
+
OPENROUTER_BASE_URL: "" # optional — default https://openrouter.ai/api/v1
|
|
44
|
+
OPENROUTER_HTTP_REFERER: "" # optional — attribution header
|
|
45
|
+
OPENROUTER_TITLE: "" # optional — attribution header
|
|
46
|
+
# -- dashscope (Alibaba; refs only on wan2.7 models) --
|
|
47
|
+
DASHSCOPE_API_KEY: "" # required for dashscope
|
|
48
|
+
DASHSCOPE_IMAGE_MODEL: "" # optional — default qwen-image-2.0-pro
|
|
49
|
+
DASHSCOPE_BASE_URL: "" # optional — default https://dashscope.aliyuncs.com
|
|
50
|
+
# -- zai (Z.AI / bigmodel.cn; refs unsupported) --
|
|
51
|
+
ZAI_API_KEY: "" # or BIGMODEL_API_KEY — either one enables zai
|
|
52
|
+
BIGMODEL_API_KEY: ""
|
|
53
|
+
ZAI_IMAGE_MODEL: "" # optional — default glm-image
|
|
54
|
+
BIGMODEL_IMAGE_MODEL: ""
|
|
55
|
+
ZAI_BASE_URL: "" # optional — default https://api.z.ai/api/paas/v4
|
|
56
|
+
BIGMODEL_BASE_URL: ""
|
|
57
|
+
# -- minimax (image-01; refs via subject_reference, jpg/png <10MB) --
|
|
58
|
+
MINIMAX_API_KEY: "" # required for minimax
|
|
59
|
+
MINIMAX_IMAGE_MODEL: "" # optional — default image-01
|
|
60
|
+
MINIMAX_BASE_URL: "" # optional — default https://api.minimaxi.com
|
|
61
|
+
# -- replicate (hosted model surface; prediction polling) --
|
|
62
|
+
REPLICATE_API_TOKEN: "" # replicate.com/account/api-tokens — required for replicate
|
|
63
|
+
REPLICATE_IMAGE_MODEL: "" # optional — default google/nano-banana-2
|
|
64
|
+
REPLICATE_BASE_URL: "" # optional — default https://api.replicate.com
|
|
65
|
+
# -- jimeng (Volcengine; BOTH key halves required, HMAC-signed) --
|
|
66
|
+
JIMENG_ACCESS_KEY_ID: "" # required for jimeng (with the secret key)
|
|
67
|
+
JIMENG_SECRET_ACCESS_KEY: "" # required for jimeng (with the access key)
|
|
68
|
+
JIMENG_IMAGE_MODEL: "" # optional — default jimeng_t2i_v40
|
|
69
|
+
JIMENG_REGION: "" # optional — default cn-north-1
|
|
70
|
+
JIMENG_BASE_URL: "" # optional — default https://visual.volcengineapi.com
|
|
71
|
+
# -- seedream (ByteDance Ark; key is ARK_API_KEY, not SEEDREAM_*) --
|
|
72
|
+
ARK_API_KEY: "" # required for seedream
|
|
73
|
+
SEEDREAM_IMAGE_MODEL: "" # optional — default doubao-seedream-5-0-260128
|
|
74
|
+
SEEDREAM_BASE_URL: "" # optional — default https://ark.cn-beijing.volces.com/api/v3
|
|
75
|
+
# -- azure (Azure OpenAI; model is addressed by deployment name) --
|
|
76
|
+
AZURE_OPENAI_API_KEY: "" # required for azure
|
|
77
|
+
AZURE_OPENAI_BASE_URL: "" # required resource endpoint, e.g. https://<res>.openai.azure.com
|
|
78
|
+
AZURE_OPENAI_DEPLOYMENT: "" # deployment hosting a gpt-image model
|
|
79
|
+
AZURE_OPENAI_IMAGE_MODEL: "" # optional — default gpt-image-2
|
|
80
|
+
AZURE_API_VERSION: "" # optional — default 2025-04-01-preview
|
|
81
|
+
# -- agnes (OpenAI-compatible images endpoint) --
|
|
82
|
+
AGNES_API_KEY: "" # required for agnes
|
|
83
|
+
AGNES_IMAGE_MODEL: "" # optional — default agnes-image-2.1-flash
|
|
84
|
+
AGNES_BASE_URL: "" # optional — default https://apihub.agnes-ai.com/v1
|
|
85
|
+
# -- codex-cli (subprocess; NEVER auto-selected — pin explicitly) --
|
|
86
|
+
BAOYU_CODEX_IMAGEGEN_BIN: "" # optional — wrapper entrypoint override
|
|
87
|
+
BAOYU_CODEX_IMAGEGEN_TIMEOUT_MS: "" # optional — per-call timeout (ms)
|
|
88
|
+
BAOYU_CODEX_IMAGEGEN_RETRIES: "" # optional — retry count
|
|
89
|
+
# -- 0104 R6 runtime seams (behavior, not credentials; placeholder values only) --
|
|
90
|
+
KK_IMAGE_MAX_IMAGES: "" # optional — per-run budget cap; default 4 (OPEN for the operator to set)
|
|
91
|
+
KK_IMAGE_DRY_RUN: "" # optional — "1" composes every prompt, selects the provider, spends nothing
|
|
25
92
|
publishings:
|
|
26
93
|
qiita-pub:
|
|
27
94
|
env:
|
|
@@ -32,3 +99,16 @@ plugins:
|
|
|
32
99
|
surfdash-pub:
|
|
33
100
|
env:
|
|
34
101
|
POSTSURFING_BIN: "" # optional — delete if unused; defaults to `postsurfing` on PATH
|
|
102
|
+
|
|
103
|
+
# Image-authoring preferences (task 0106) — read by the kk:image-authoring skill,
|
|
104
|
+
# not by the plugin; the plugin sees only the resolved winners in its --in envelope.
|
|
105
|
+
# Precedence: arguments > environment (KK_IMAGE_*) > this block > compiled defaults.
|
|
106
|
+
# Credentials never live here — see plugins.generations.image-gen.env above.
|
|
107
|
+
image:
|
|
108
|
+
format: free # default --format
|
|
109
|
+
style: ~ # ~ = use the format's defaultStyle
|
|
110
|
+
provider: ~ # ~ = auto (0103 AUTO_ORDER)
|
|
111
|
+
model: ~
|
|
112
|
+
auto_confirm: false # baoyu's quick_mode, renamed to say what it does
|
|
113
|
+
max_images: 4 # mirrors KK_IMAGE_MAX_IMAGES (0104)
|
|
114
|
+
preset_path: ~ # extra roots, ':'-separated → KK_IMAGE_PRESET_PATH
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: image-authoring
|
|
3
|
+
description: >-
|
|
4
|
+
Use the image-authoring skill to generate images — "generate an image",
|
|
5
|
+
"create a cover for this article", "illustrate this article", "make a hero
|
|
6
|
+
image", "extract a style from this reference image", "clone this look",
|
|
7
|
+
"list the image formats and styles". Three operations — generate,
|
|
8
|
+
extract-style, list-presets — over named format and style presets, with a
|
|
9
|
+
confirmation gate before any paid generation.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# image-authoring — image generation agent surface (generate | extract-style | list-presets)
|
|
13
|
+
|
|
14
|
+
## Purpose
|
|
15
|
+
|
|
16
|
+
Turn an operator request into **generated or derived image artefacts** by driving the
|
|
17
|
+
deterministic `kk:image-gen` generator plugin. One fat skill with three operations —
|
|
18
|
+
`generate` (default), `extract-style`, `list-presets` — sharing one invocation contract,
|
|
19
|
+
`--in` envelope, preference chain and confirmation policy. The job is not an operation:
|
|
20
|
+
it is **data** — each format preset declares its own `job` field
|
|
21
|
+
(0102), and `--format` selects the work. This is the `audio-authoring` shape applied to
|
|
22
|
+
a job axis that lives in YAML.
|
|
23
|
+
|
|
24
|
+
**The judgment line — the plugin executes a decided plan; this skill decides it.** The
|
|
25
|
+
plugin (`plugins/generations/image-gen/`) is deterministic: preset resolution and
|
|
26
|
+
shadowing, prompt composition, provider selection and the API call, artefact naming and
|
|
27
|
+
writing, and markdown insertion at a given anchor. Everything needing reading, taste or
|
|
28
|
+
ambiguity resolution is **this skill's**: deriving `--var` values from an article (title,
|
|
29
|
+
subtitle, topics), style auto-detection, illustration **position detection**, caption and
|
|
30
|
+
alt-text authoring, style extraction from reference images, preference precedence
|
|
31
|
+
resolution, and spend approval. The skill's single output to the engine is the `--in`
|
|
32
|
+
envelope; nothing model-shaped ever crosses it.
|
|
33
|
+
|
|
34
|
+
## When to use
|
|
35
|
+
|
|
36
|
+
- A user asks for an image: a free-form picture from a prompt, a cover for an article,
|
|
37
|
+
or illustrations inserted into an article body.
|
|
38
|
+
- A user hands over reference images wanting the look captured as a reusable style
|
|
39
|
+
preset, or asks what formats and styles exist.
|
|
40
|
+
- A workflow `agent.run` step needs one image job non-interactively (Confirmation
|
|
41
|
+
policy — the node must set `KK_NONINTERACTIVE=1`).
|
|
42
|
+
|
|
43
|
+
## Do not use this skill for
|
|
44
|
+
|
|
45
|
+
- Speech or spoken-audio authoring (that is `kk:audio-authoring`).
|
|
46
|
+
- Hand-written programmatic generation: call `kk executor run image-gen` directly with
|
|
47
|
+
your own `--in` envelope — this skill's value is the deciding, not the spawn.
|
|
48
|
+
- Editing an existing image (image-to-image): regenerate with a tweaked prompt instead.
|
|
49
|
+
- A coordinator subagent over this skill: none is wanted; `/kk:image-generate` is the
|
|
50
|
+
whole entry surface.
|
|
51
|
+
|
|
52
|
+
## Operations
|
|
53
|
+
|
|
54
|
+
### generate (default)
|
|
55
|
+
|
|
56
|
+
Sequence: **resolve preferences** (Preference chain) → **resolve the format** →
|
|
57
|
+
**validate arguments** → **decide the work** → **confirm** (Confirmation policy) →
|
|
58
|
+
**preview** (`KK_IMAGE_DRY_RUN=1`) → **paid run** → **report**.
|
|
59
|
+
|
|
60
|
+
1. Resolve the format preset from `--format` and read its `job` field — that is the
|
|
61
|
+
branch. Never hardcode format names: an operator-authored format must work with
|
|
62
|
+
zero edits here (its `job` field is the only fact the skill needs).
|
|
63
|
+
2. If the `job` value is one of the branches below, proceed. Anything else: **stop,
|
|
64
|
+
name the unimplemented branch and the format**, and never fall back to `free`.
|
|
65
|
+
- `free` — the positional prompt is the subject; no `--content` needed.
|
|
66
|
+
- `cover` — read `--content` and derive the format's template variables from the
|
|
67
|
+
article (title, subtitle, topics) — model work, this skill's — into `vars`.
|
|
68
|
+
- `illustration` — detect insertion positions and author captions per
|
|
69
|
+
[references/illustration-positions.md](references/illustration-positions.md);
|
|
70
|
+
the chosen anchors cross as `metadata.positions`, one image per anchor.
|
|
71
|
+
3. Resolve the style: `--style`, else the preference `image.style`, else the format's
|
|
72
|
+
`defaultStyle`. Without an explicit choice the skill may propose an auto-detected
|
|
73
|
+
style — taste, this skill's — shown in the preview for approval.
|
|
74
|
+
4. Resolve the provider and model per Preference chain; the credential and
|
|
75
|
+
reference-image facts per provider live in
|
|
76
|
+
[references/providers.md](references/providers.md).
|
|
77
|
+
5. Write the `--in` envelope (Artifact contract), then follow the Confirmation policy.
|
|
78
|
+
6. Report the `--out` path, every artefact, and for illustration the captions.
|
|
79
|
+
`--dry-run` stops after the preview and reports the composed prompt.
|
|
80
|
+
|
|
81
|
+
### extract-style
|
|
82
|
+
|
|
83
|
+
Requires `--ref` (one to five images). **Authoritative procedure:
|
|
84
|
+
[references/style-extraction.md](references/style-extraction.md)** — read it first; it
|
|
85
|
+
owns the schema-fill rules, the multi-image merge rules, the cap-5 refusal wording,
|
|
86
|
+
where the preset is written (user root, shadowing by design), and the validation step:
|
|
87
|
+
read the images with your own vision, write `~/.config/kk/image-presets/styles/<name>.yaml`
|
|
88
|
+
filling exactly the `StylePreset` fields, then validate with a `KK_IMAGE_DRY_RUN=1` run
|
|
89
|
+
whose `style` is the new preset's name. Extraction never writes a `formats/*.yaml` —
|
|
90
|
+
a format is not recoverable from a picture.
|
|
91
|
+
|
|
92
|
+
### list-presets
|
|
93
|
+
|
|
94
|
+
Enumerate the **real** preset roots the 0102 loader scans — builtin
|
|
95
|
+
(`plugins/generations/image-gen/presets/`), then `~/.config/kk/image-presets/`, then
|
|
96
|
+
`$KNOWLEDGE_KIT_PROJECT_ROOT/.kk/image-presets/`, then each `:`-separated
|
|
97
|
+
`KK_IMAGE_PRESET_PATH` entry — printing every format and style with its **origin
|
|
98
|
+
root**, plus (formats) `job`, `aspectRatio`, `defaultStyle`. Surface the loader's
|
|
99
|
+
diagnostics verbatim: `shadowed` names **both** paths (losing shipped file, winning
|
|
100
|
+
user file); `invalid` names the file and the schema violation. **Never read a shipped
|
|
101
|
+
markdown gallery** — no gallery exists; the roots are the one source of truth. This
|
|
102
|
+
output is also the text an unknown `--format`/`--style` refusal quotes.
|
|
103
|
+
|
|
104
|
+
## Invocation contract
|
|
105
|
+
|
|
106
|
+
`$ARGUMENTS` (or the Skill call args) parse as:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
[prompt] [--format <name>] [--style <name>] [--var KEY=VALUE]... [--content <file>]
|
|
110
|
+
[--ref <path>]... [--count <n>] [--provider <name>] [--model <id>]
|
|
111
|
+
[--out <dir|file>] [--dry-run] [--list-presets] [--extract-style <name>]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| Token | Meaning | Default |
|
|
115
|
+
| --- | --- | --- |
|
|
116
|
+
| `prompt` | free-form subject text | required for `free`; optional otherwise |
|
|
117
|
+
| `--format <name>` | format preset; its `job` field selects the branch | `image.format`, else `free` |
|
|
118
|
+
| `--style <name>` | style preset | format's `defaultStyle` |
|
|
119
|
+
| `--var KEY=VALUE` | template variable (repeatable) | preset `variables` defaults |
|
|
120
|
+
| `--content <file>` | markdown source for `cover` / `illustration` | none |
|
|
121
|
+
| `--ref <path>` | reference image (repeatable, **max 5**) | none |
|
|
122
|
+
| `--count <n>` | images to produce — **`illustration` job only** | number of `positions` anchors, else 1 |
|
|
123
|
+
| `--provider <name>` | 0103 provider name | auto (`AUTO_ORDER`) |
|
|
124
|
+
| `--model <id>` | provider model id | provider default |
|
|
125
|
+
| `--out <dir\|file>` | the plugin's `--out`; see Artifact contract | temp dir under the works dir |
|
|
126
|
+
| `--dry-run` | compose and price, generate nothing | off |
|
|
127
|
+
| `--list-presets` | → the `list-presets` operation | — |
|
|
128
|
+
| `--extract-style <name>` | → the `extract-style` operation; **requires `--ref`** | — |
|
|
129
|
+
|
|
130
|
+
**Validation is this skill's, before any plugin spawn**, each refusal naming the
|
|
131
|
+
offending argument: unknown `--format` or `--style` (quote what `list-presets` would
|
|
132
|
+
show); `--count` on a non-`illustration` job; more than five `--ref` paths;
|
|
133
|
+
`--extract-style` without `--ref`; a `--content` file that does not exist. Flags the
|
|
134
|
+
wt plugin retired are **answered, not ignored** — state the replacement and stop; the
|
|
135
|
+
table lives in
|
|
136
|
+
[references/migrating-from-wt.md](references/migrating-from-wt.md)
|
|
137
|
+
(`--template` → `--format`, `--resolution WxH` → the format's `aspectRatio`,
|
|
138
|
+
`--backend` → `--provider`, `--list-templates` → `--list-presets`).
|
|
139
|
+
|
|
140
|
+
## Artifact contract
|
|
141
|
+
|
|
142
|
+
The skill writes exactly one input file — the `--in` JSON: a `Doc[]` whose **first
|
|
143
|
+
element** carries the whole job spec at `metadata.imageGen` (strict schema; a typo'd
|
|
144
|
+
key fails loud), plus `metadata.positions` for the illustration job:
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
[{
|
|
148
|
+
"id": "doc-1", "title": "Article title", "body": "<article text>",
|
|
149
|
+
"metadata": {
|
|
150
|
+
"imageGen": {
|
|
151
|
+
"job": "cover", "format": "cover", "style": "vibrant",
|
|
152
|
+
"provider": "seedream", "model": "doubao-seedream-5-0-260128",
|
|
153
|
+
"vars": { "title": "Article title", "subtitle": "Derived subtitle" },
|
|
154
|
+
"maxImages": 4, "outputStem": "article-title"
|
|
155
|
+
},
|
|
156
|
+
"positions": ["## A heading that occurs exactly once in the body"]
|
|
157
|
+
}
|
|
158
|
+
}]
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
| Path | Rule |
|
|
162
|
+
| --- | --- |
|
|
163
|
+
| `--out` ending `.json` | used verbatim as the plugin's `--out` |
|
|
164
|
+
| `--out` anything else | treated as a directory; the plugin's `--out` becomes `<dir>/content.json` |
|
|
165
|
+
| generated images | **siblings** of `--out` (0104), named from the format's `outputFilename` |
|
|
166
|
+
| output `metadata.images[]` | one entry per image: absolute `path`, `format`, `style`, `provider`, `model`, the exact `prompt`, `bytes` |
|
|
167
|
+
| output `metadata.captions[]` | illustration only: `{index, path, caption, altText, anchor}` |
|
|
168
|
+
|
|
169
|
+
`metadata.imageGen` accepts only: `job`, `format`, `style`, `vars`, `extra`, `provider`,
|
|
170
|
+
`model`, `referenceImages`, `count`, `maxImages`, `seed`, `outputStem`. **No credential
|
|
171
|
+
is ever written into this file** — see Preference chain.
|
|
172
|
+
|
|
173
|
+
## Preference chain
|
|
174
|
+
|
|
175
|
+
Every preference resolves through the repo's frozen order — **arguments >
|
|
176
|
+
environment > `$KK_CONFIG` > compiled defaults** — and the winners are written into
|
|
177
|
+
`metadata.imageGen`:
|
|
178
|
+
|
|
179
|
+
| Key | 1. argument | 2. environment | 3. `$KK_CONFIG` `image:` | 4. compiled default |
|
|
180
|
+
| --- | --- | --- | --- | --- |
|
|
181
|
+
| format | `--format` | — | `image.format` | `free` |
|
|
182
|
+
| style | `--style` | — | `image.style` | the format's `defaultStyle` |
|
|
183
|
+
| provider | `--provider` | `IMAGE_GEN_PROVIDER` | `image.provider` | auto (`AUTO_ORDER`) |
|
|
184
|
+
| model | `--model` | — | `image.model` | provider default |
|
|
185
|
+
| auto-confirm | — | `KK_IMAGE_AUTO_CONFIRM=1` | `image.auto_confirm` | `false` |
|
|
186
|
+
| max images | — | `KK_IMAGE_MAX_IMAGES` | `image.max_images` | `4` |
|
|
187
|
+
| preset roots | — | `KK_IMAGE_PRESET_PATH` | `image.preset_path` | none |
|
|
188
|
+
|
|
189
|
+
- `$KK_CONFIG` defaults to `~/.config/kk/config.yaml`. The environment **wins over the
|
|
190
|
+
config file**: an exported `KK_IMAGE_MAX_IMAGES` beats `image.max_images`.
|
|
191
|
+
- Loading is **non-blocking**: a missing or malformed `image:` block earns **one
|
|
192
|
+
notice line naming the block**, then the compiled defaults. Every key has a working
|
|
193
|
+
default; refusing to draw over a missing optional config is a worse failure than
|
|
194
|
+
drawing with defaults. (A malformed *preset* is a different matter and fails loud.)
|
|
195
|
+
- **No `EXTEND.md`** — do not read, create, or mention one: baoyu's ranks file above
|
|
196
|
+
environment, the opposite of this chain, and one product gets one precedence rule.
|
|
197
|
+
- **Credentials are not in this chain.** `plugins.generations.image-gen.env` values
|
|
198
|
+
reach the plugin only through `invoke.ts`'s process environment at spawn. They never
|
|
199
|
+
enter this skill's context, the `--in` file, or any report.
|
|
200
|
+
|
|
201
|
+
## Confirmation policy
|
|
202
|
+
|
|
203
|
+
The skill is in exactly one of two contexts, signalled **explicitly**:
|
|
204
|
+
`KK_NONINTERACTIVE=1` means non-interactive. Never infer from a TTY probe — a wrong
|
|
205
|
+
guess is either a hang or an unapproved spend.
|
|
206
|
+
|
|
207
|
+
**Interactive** (default). Before any paid run: execute once with `KK_IMAGE_DRY_RUN=1`,
|
|
208
|
+
then present the **spend preview** — resolved format and style, provider and model,
|
|
209
|
+
image count, and the **composed prompt taken from that run's `Content.body`** (the real
|
|
210
|
+
string the provider will receive, not a reconstruction) — and ask **once** for
|
|
211
|
+
confirmation (`AskUserQuestion` where available). `--dry-run` stops here and reports.
|
|
212
|
+
Declining ends the run with no plugin spawn beyond the dry run. A modification request
|
|
213
|
+
re-runs the preview; the loop is bounded at **3 revision rounds**, after which state
|
|
214
|
+
the bound and ask the operator to restate the request — never a paid round.
|
|
215
|
+
|
|
216
|
+
**Non-interactive** (`KK_NONINTERACTIVE=1`, e.g. a `spur workflow` `agent.run` step).
|
|
217
|
+
**Never prompt — never emit a question.** Proceed only if `image.auto_confirm: true`
|
|
218
|
+
or `KK_IMAGE_AUTO_CONFIRM=1`; otherwise **fail immediately, naming both switches**.
|
|
219
|
+
Hanging is unrecoverable, and spending unapproved money is worse than a failed node.
|
|
220
|
+
A workflow node invoking this skill **must set `KK_NONINTERACTIVE=1`** (and choose its
|
|
221
|
+
auto-confirm switch) — that is the caller's contract.
|
|
222
|
+
|
|
223
|
+
The cap is the backstop under both paths: `maxImages` rides the envelope and the
|
|
224
|
+
plugin refuses before the first provider call (0104 R6). Confirmation is a courtesy;
|
|
225
|
+
the cap is the control.
|
|
226
|
+
|
|
227
|
+
## Failure and recovery
|
|
228
|
+
|
|
229
|
+
- **Unknown format/style** — refuse, listing what `list-presets` shows.
|
|
230
|
+
- **Unimplemented `job` branch** — stop, naming the branch and format; never fall back
|
|
231
|
+
to `free`.
|
|
232
|
+
- **Malformed preset** — the 0102 loader's `invalid` diagnostic names the file and the
|
|
233
|
+
schema violation; the dry run fails loud before any spend. Fix or delete the file.
|
|
234
|
+
- **Provider cannot accept `--ref`** — when `supportsReferenceImages(model)` is false,
|
|
235
|
+
say so and offer `extract-style`; never silently drop a reference.
|
|
236
|
+
- **Declined confirmation** — end cleanly; only the dry run ran, nothing was spent.
|
|
237
|
+
- **Failed run** — the plugin deletes `--out` and every artefact of that run before
|
|
238
|
+
rethrowing; report the error verbatim and stop.
|
|
239
|
+
- **No credential anywhere** — name the provider and the env var it needs
|
|
240
|
+
(references/providers.md); never solicit a secret in chat.
|
|
241
|
+
|
|
242
|
+
## References
|
|
243
|
+
|
|
244
|
+
- [references/style-extraction.md](references/style-extraction.md) — `extract-style` (0105): schema-fill rules, merge rules, cap, fidelity check.
|
|
245
|
+
- [references/illustration-positions.md](references/illustration-positions.md) —
|
|
246
|
+
position detection and caption authoring for `illustration`, with the wt example.
|
|
247
|
+
- [references/providers.md](references/providers.md) — the operator digest of 0103's
|
|
248
|
+
provider inventory: env vars, default models, reference capability, auto order.
|
|
249
|
+
- [references/migrating-from-wt.md](references/migrating-from-wt.md) — the retired wt
|
|
250
|
+
flags and their frozen replacements.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Illustration positions and captions
|
|
2
|
+
|
|
3
|
+
Loaded by the image-authoring skill when the resolved format's `job` is
|
|
4
|
+
`illustration`. Position detection, concept prompts and caption authoring are
|
|
5
|
+
**model work** — this file's — while the plugin does the deterministic half:
|
|
6
|
+
composing one prompt per anchor (the anchor rides the format's `concept`
|
|
7
|
+
template variable), generating, and inserting the markdown at the anchor. The
|
|
8
|
+
skill's entire output across this boundary is `metadata.positions` in the
|
|
9
|
+
`--in` envelope.
|
|
10
|
+
|
|
11
|
+
## The anchor contract
|
|
12
|
+
|
|
13
|
+
- `metadata.positions` is an **array of anchor strings**, one per illustration.
|
|
14
|
+
- Each anchor must occur **exactly once** in the doc `body` — the plugin
|
|
15
|
+
inserts at that occurrence and fails loud if it is absent or ambiguous.
|
|
16
|
+
Headings and distinctive sentences are safe anchors; short common phrases
|
|
17
|
+
are not.
|
|
18
|
+
- The array length is the image count: `--count` must equal it, and each
|
|
19
|
+
anchor is fed as image *i*'s `concept` var (`plugins/generations/image-gen/src/index.ts`
|
|
20
|
+
composes one prompt per anchor in array order).
|
|
21
|
+
- The budget still applies: the default cap is 4 images (`maxImages`); a
|
|
22
|
+
longer article does not silently spend past it.
|
|
23
|
+
|
|
24
|
+
## Detection procedure
|
|
25
|
+
|
|
26
|
+
Read the article once, end to end, then list **candidate** positions before
|
|
27
|
+
selecting. For each candidate record: line/section, **type**, a 0–12 **score**,
|
|
28
|
+
and a one-line rationale. The wt taxonomy, kept because it works:
|
|
29
|
+
|
|
30
|
+
| Type | Looks like | Typical score driver |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| abstract concept | jargon-dense section with no visual ("microservices architecture", "service discovery") | technical-jargon density |
|
|
33
|
+
| information dense | wide tables, long parameter lists | structure that a diagram would organise |
|
|
34
|
+
| emotional transition | major section break, shift in narrative register | flow improvement |
|
|
35
|
+
|
|
36
|
+
Selection rules (the wt defaults, kept): prefer higher score; keep at least
|
|
37
|
+
~20 lines between chosen positions; respect the operator's `--count` as the
|
|
38
|
+
maximum; spread across types rather than clustering on one. Fewer, better
|
|
39
|
+
placements beat filling the quota.
|
|
40
|
+
|
|
41
|
+
For each **selected** position, author in the same pass:
|
|
42
|
+
|
|
43
|
+
1. **Anchor** — the exactly-once string from the body.
|
|
44
|
+
2. **Concept** — a one-sentence visual brief; the plugin feeds it to the
|
|
45
|
+
format's `concept` variable, so write it as an image subject, not prose
|
|
46
|
+
about the article.
|
|
47
|
+
3. **Caption and alt text** — the caption states what the image adds; the alt
|
|
48
|
+
text describes the image for a screen reader (complete sentence, no "image
|
|
49
|
+
of"). These are reported to the operator against each artefact path.
|
|
50
|
+
4. **Style** — default to the resolved style; `technical-diagram` suits
|
|
51
|
+
concepts and dense reference content, `sketch` suits data relationships,
|
|
52
|
+
`minimalist` suits transitions. A proposal, shown in the preview — the
|
|
53
|
+
operator confirms the whole spend once.
|
|
54
|
+
|
|
55
|
+
## Worked example (from the wt plugin, the prior art)
|
|
56
|
+
|
|
57
|
+
Input: `article-before.md`, a 1,850-word microservices guide with no images.
|
|
58
|
+
Detection found **8 candidates, selected 5** (two dropped for sitting too
|
|
59
|
+
close to a higher-scoring neighbour, one for low score):
|
|
60
|
+
|
|
61
|
+
| # | Line | Type | Score | Selected |
|
|
62
|
+
| --- | --- | --- | --- | --- |
|
|
63
|
+
| 1 | 23 | abstract concept ("What Are Microservices?") | 12 | yes |
|
|
64
|
+
| 2 | 45 | abstract concept (service discovery) | 11 | yes |
|
|
65
|
+
| 3 | 62 | information dense (7-column API table) | 10 | yes |
|
|
66
|
+
| 4 | 79 | abstract concept (communication patterns) | 10 | no — too close to #3 |
|
|
67
|
+
| 5 | 92 | information dense (configuration list) | 11 | yes |
|
|
68
|
+
| 6 | 112 | emotional transition ("Best Practices") | 7 | yes |
|
|
69
|
+
| 7 | 128 | abstract concept | 8 | no — below the line |
|
|
70
|
+
|
|
71
|
+
The chosen anchors became `metadata.positions`; image *i*'s anchor was fed as
|
|
72
|
+
its `concept` — e.g. position 1's concept, "Microservices architecture pattern
|
|
73
|
+
with API gateway", produced a technical-diagram prompt, and the caption
|
|
74
|
+
reported back as alt text: "Diagram showing microservices architecture with
|
|
75
|
+
API gateway, independent services with databases, and message queue
|
|
76
|
+
communication". The output side is `article-after.md` — the same article with
|
|
77
|
+
`` references at the anchors — plus the captions payload the
|
|
78
|
+
plugin emits at `metadata.captions[]` (`{index, path, caption, altText,
|
|
79
|
+
anchor}`), which is what the skill reports to the operator.
|
|
80
|
+
|
|
81
|
+
## What this file never does
|
|
82
|
+
|
|
83
|
+
- No detection inside the plugin — the plugin never reads for meaning; it
|
|
84
|
+
only inserts where told.
|
|
85
|
+
- No image selection after the fact — positions are decided **before** the
|
|
86
|
+
confirmation preview, so the operator approves the whole spend knowingly.
|
|
87
|
+
- No caption writing inside the plugin — captions are the skill's voice.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Migrating from the wt image commands
|
|
2
|
+
|
|
3
|
+
For operators arriving from the `wt` plugin (`/wt:image-generate` and its
|
|
4
|
+
three skills). The kk surface is **one command** (`/kk:image-generate`)
|
|
5
|
+
driving **one skill** (`kk:image-authoring`). Flag by flag:
|
|
6
|
+
|
|
7
|
+
| wt flag | kk replacement | Why |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `--template <name>` | `--format <name>` | Templates are formats now — data presets (0102). wt's `default` → `free`; `cover` → `cover`; `illustrator` → `illustration`. A custom template like `cover-xhs` becomes a YAML file in `~/.config/kk/image-presets/formats/` — no skill or plugin edit needed. |
|
|
10
|
+
| `--resolution WxH` | *(dropped)* | The format's `aspectRatio` owns dimensions (0102 R1) — a portable ratio beats per-call pixels across twelve providers. A different shape means a different or overridden format. |
|
|
11
|
+
| `--backend <name>` | `--provider <name>` | 0103's inventory replaces wt's three backends: `gemini` → `google`; `nano_banana` → `replicate`; `huggingface` has no kk equivalent. Omitting `--provider` auto-selects (see providers.md). |
|
|
12
|
+
| `--list-templates` | `--list-presets` | Lists formats **and** styles from the real preset roots, with origin and shadowing diagnostics. |
|
|
13
|
+
| `--output <path>` | `--out <dir\|file>` | A path ending `.json` is used verbatim; anything else is a directory, and generated images land as its siblings. |
|
|
14
|
+
|
|
15
|
+
Kept, unchanged in meaning: the positional prompt, `--style`,
|
|
16
|
+
`--var KEY=VALUE`, `--content <file>`.
|
|
17
|
+
|
|
18
|
+
Also different, by design:
|
|
19
|
+
|
|
20
|
+
- The command validates nothing — the skill does. Thinner command, same
|
|
21
|
+
refusals, one place that knows the rules.
|
|
22
|
+
- The three wt skills' model work (content analysis, style auto-detection,
|
|
23
|
+
position detection) lives inside this one skill as named capabilities, not
|
|
24
|
+
three skills.
|
|
25
|
+
- The `wt:image-generator` coordinator subagent is deliberately **not
|
|
26
|
+
ported** — a forwarder over a single skill is an indirection with nothing
|
|
27
|
+
in it.
|
|
28
|
+
- Spend control: kk previews the real prompt via a `KK_IMAGE_DRY_RUN=1` run
|
|
29
|
+
and asks once before the paid run; workflow nodes must set
|
|
30
|
+
`KK_NONINTERACTIVE=1`. The wt `--resolution`-style per-call budget has no
|
|
31
|
+
equivalent — `maxImages` (env/config) is the cap.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Provider facts for image generation
|
|
2
|
+
|
|
3
|
+
The operator-facing digest of 0103's provider inventory. Source of truth:
|
|
4
|
+
`plugins/generations/image-gen/src/providers/types.ts` (the frozen registry) —
|
|
5
|
+
if this file and the registry ever disagree, the registry wins and this file
|
|
6
|
+
gets fixed. Credential *values* live only in
|
|
7
|
+
`plugins.generations.image-gen.env` (`plugins/kk/config.example.yaml`); they
|
|
8
|
+
are read by `invoke.ts` at spawn, never by this skill.
|
|
9
|
+
|
|
10
|
+
## The rules
|
|
11
|
+
|
|
12
|
+
- **Readiness is env presence.** A provider is available when its env var(s)
|
|
13
|
+
are non-empty. A stale key still passes readiness and surfaces as the
|
|
14
|
+
adapter's own auth error at generate time.
|
|
15
|
+
- **Auto-selection** walks `AUTO_ORDER` — google, openai, replicate,
|
|
16
|
+
openrouter, dashscope, seedream, zai, minimax, jimeng, azure, agnes — and
|
|
17
|
+
takes the first available. `IMAGE_GEN_PROVIDER` names a provider explicitly;
|
|
18
|
+
`--provider` outranks it.
|
|
19
|
+
- **`codex-cli` is never auto-selected.** Its readiness (the `codex` CLI on
|
|
20
|
+
PATH with an active login) is not env-expressible; pin it explicitly.
|
|
21
|
+
- **Reference images** ride `--ref` only when `supportsReferenceImages(model)`
|
|
22
|
+
is true for the resolved model — a substring test over the registry's
|
|
23
|
+
`refModels` fragments (no `refModels` = every model; empty list = none).
|
|
24
|
+
When it is false, say so and offer `extract-style`; never drop the
|
|
25
|
+
reference silently.
|
|
26
|
+
|
|
27
|
+
## The inventory
|
|
28
|
+
|
|
29
|
+
| Provider | Env var(s) that enable it | Default model | Reference images |
|
|
30
|
+
| --- | --- | --- | --- |
|
|
31
|
+
| agnes | `AGNES_API_KEY` | `agnes-image-2.1-flash` | yes |
|
|
32
|
+
| azure | `AZURE_OPENAI_API_KEY` (+ `AZURE_OPENAI_BASE_URL`, `AZURE_OPENAI_DEPLOYMENT` to run) | `gpt-image-2` | yes (no `refModels` declared → every model passes `supportsReferenceImages`) |
|
|
33
|
+
| codex-cli | not env-expressible — `codex` CLI + login | `codex-image-gen` | never auto-selected |
|
|
34
|
+
| dashscope | `DASHSCOPE_API_KEY` | `qwen-image-2.0-pro` | only `wan2.7` models |
|
|
35
|
+
| google | `GOOGLE_API_KEY` or `GEMINI_API_KEY` | `gemini-3-pro-image` | the gemini image models (`gemini-3-pro-image`, `*-flash-image`, previews) |
|
|
36
|
+
| jimeng | `JIMENG_ACCESS_KEY_ID` **and** `JIMENG_SECRET_ACCESS_KEY` (both halves) | `jimeng_t2i_v40` | yes |
|
|
37
|
+
| minimax | `MINIMAX_API_KEY` | `image-01` | yes (jpg/png <10MB, subject reference) |
|
|
38
|
+
| openai | `OPENAI_API_KEY` | `gpt-image-2` | `gpt-image` models |
|
|
39
|
+
| openrouter | `OPENROUTER_API_KEY` | `google/gemini-3.1-flash-image` | yes (no `refModels` declared → treated as supporting; real capability depends on the routed model) |
|
|
40
|
+
| replicate | `REPLICATE_API_TOKEN` | `google/nano-banana-2` | yes |
|
|
41
|
+
| seedream | `ARK_API_KEY` (not `SEEDREAM_*`) | `doubao-seedream-5-0-260128` | `doubao-seedream-5-0` / `4-5-` / `4-0-` models |
|
|
42
|
+
| zai | `ZAI_API_KEY` or `BIGMODEL_API_KEY` | `glm-image` | **none** |
|
|
43
|
+
|
|
44
|
+
## Deciding with references in hand
|
|
45
|
+
|
|
46
|
+
1. Resolve the provider per Preference chain (argument → `IMAGE_GEN_PROVIDER`
|
|
47
|
+
→ config → `AUTO_ORDER`).
|
|
48
|
+
2. Check `supportsReferenceImages` for the resolved model. False → the
|
|
49
|
+
pass-through path is unavailable: state it and offer style extraction
|
|
50
|
+
(0105), which works with every provider.
|
|
51
|
+
3. Name the chosen provider and model in the spend preview — the operator
|
|
52
|
+
approves the pair, not just the prompt.
|