cli
AGENTS.md10 starsChanged 3 months agoMITView on GitHub
This repo is a Cobra-based Go CLI with an npm distribution wrapper. The video pipeline has two stages — agents should understand both: 1. Preview (produced by vk create): a shareable HTML page. The share_url in the output is the primary artifact; it plays the finished video. 2. Export (optional, via vk video export): renders a downloadable MP4. Takes several minutes and extra credits. Commands: - vk create --from <src> — pipeline → preview → share_url. <src> is a path,…
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,439 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository3 other files it gives its agents.
- vibeknow-coreSkill
- vibeknow-createSkill
- vibeknow-docSkill
The file
We split this AGENTS.md into the rules it gives an agent
11 rules. About 0 are common, 2 are distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Very specific: line 12
All user-visible strings go through internal/i18n.
- Very specific: line 18
Run make test and make build.
- Very specific: line 19
Never commit credentials, cache files (~/.cache/vibeknow/*), or dist/.
- Distinctive: line 133
Not 2 either — the command line is fine, and sending an agent to look for an argument mistake it did not make wastes a turn.
- Very specific: line 161
Only the tts part applies: an edit saved through /scene/edit takes its speech through the tts quota, while text generation and every image …
- Very specific: line 192
Every run is addressed by a (task_id, session_id) pair.
- Very specific: line 264
If the backend accepts the request but reports no progress within 60s, the CLI detaches anyway and says so on stderr.
- Very specific: line 283
Default is text. There is no TTY-based auto-switching.
- Distinctive: line 318
When the channel is on, the command skips the human prose it would otherwise write for the same event.
- Very specific: line 392
Every run reports what it applied: a preset.applied event carrying the sorted list of keys that actually took effect, or one stderr line in …
- Very specific: line 594
DELETE. --yes (or VIBEKNOW_ASSUME_YES=1) actually deletes.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Agent guidance — vibeknow-cli repo
This repo is a Cobra-based Go CLI with an npm distribution wrapper.
Where to look
cmd/— Cobra command definitions, one file per command.internal/— Reusable framework packages (output, i18n, credential, config).docs/superpowers/specs/— Design documents;docs/superpowers/plans/— Implementation plans.skills/— AI agent Skills (empty in P0).
Conventions
- All user-visible strings go through
internal/i18n. - Tests live alongside source (
*_test.go) plustests/integration/. --outputvalues:text(the default, whatever the terminal),json,ndjson.- Exit codes: see spec §5.4.
Before making changes
- Run
make testandmake build. - Never commit credentials, cache files (
~/.cache/vibeknow/*), ordist/.
Two-stage video workflow
The video pipeline has two stages — agents should understand both:
- Preview (produced by
vk create): a shareable HTML page. Theshare_urlin the output is the primary artifact; it plays the finished video. - Export (optional, via
vk video export): renders a downloadable MP4. Takes several minutes and extra credits.
Commands:
vk create --from <src>— pipeline → preview →share_url.<src>is a path, a URL, a doc_id, or-to read the source text from stdin;--text <string>passes that text directly, for a passage pasted into a conversation rather than saved to a file.vk video wait [task_id] [--session-id <sess>] --for 90s --output json— watch the run for that long, then report the stage it reached and exit 6 withreason: "wait_budget_expired". For callers whose own timeout is shorter than a render; run it again to keep waiting.vk video status [task_id] [--session-id <sess>] --output json— full snapshot:{preview, export, next_actions}. Agents should follownext_actions[].commandto plan the next step.--preview-diralso writes whatever artifacts exist to disk, which is the only route to them for a caller that did not start the run.vk video export [task_id] [--session-id <sess>] --yes --output json— render MP4.--yesskips the confirmation prompt;VIBEKNOW_ASSUME_YES=1works at the environment level.vk video download [task_id] [--session-id <sess>] --dest out.mp4— pure download (requires export to have completed). The file path is--dest;--outputmeans format here as it does everywhere else.
--session-id is optional on all four: when omitted it is resolved from
the local run ledger (see below). An explicit value always wins.
Exit codes: 0 success, 2 user error, 3 auth, 4 retryable, 5
business failure, 6 not a terminal state, 7 partial success
(preview ready, export failed), 8 blocked on a user decision.
Exit 6 covers three situations and error.detail.reason tells them apart:
wait_budget_expired is a --for budget running out on a healthy run, and
the absence of a reason is either a stream that ended without a terminal
event (read resend_safe) or a paused task (vk video resume). None of the
three is ever answered with a second create.
The contract holds on every command, not just vk create:
- Anything wrong with the command line — unknown command, unknown or misspelled flag, missing required flag, stray positional argument, a bad enum value — exits 2. An unknown flag close to a real one names the real one in the hint.
- An expired or missing credential exits 3 on every command that talks to a backend. Re-authenticate and retry.
rate_limited,internal_error,concurrent_work_limitandwork_edit_busyexit 4: the same command is worth retrying after a wait.insufficient_creditsexits 5: retrying cannot help.- A paid action reached with no terminal attached and no prior authority exits 8: the decision was handed back, not made.
Nothing exits 0 with output the caller cannot act on. A malformed command never prints help to stdout and calls it success.
Exit 6 says whether a resend is safe. "State unknown" is true and
useless on its own: the caller's real question is whether re-running
recovers a lost run or pays for a second one. Before returning 6 the CLI
reads the work row and puts the verdict in error.detail:
delivery | resend_safe | Basis |
submitted | false | the backend has a work row for this session |
not_submitted | true | the backend returns not_found — the work row is created at init, so its absence means nothing was billed |
indeterminate | false | the probe itself failed; unknown is not permission |
indeterminate deliberately reports resend_safe: false. Waiting when a
run was already lost costs a delay; resending when it was not costs the
user's money.
Spend decisions (exit 8)
A confirmation prompt assumes someone is there to answer it. When an agent runs the CLI nobody is, and both available answers are bad: block forever, or spend the user's credits and mention it afterwards. The CLI used to do the second.
cmdutil.Gate resolves a paid action in this order:
--yes/VIBEKNOW_ASSUME_YES— the caller states it has authority.--confirm <action_id>— authority obtained through this gate.- A terminal — ask, the way a person expects to be asked.
- Otherwise — write the decision to stdout and exit 8.
The blocked payload carries pending_actions[] with an opaque action_id,
the payload being agreed to, options[].effect (resume / none), and
a resume_command spelled out in full.
action_id is an HMAC over the action type and the decision-relevant
payload, keyed by a per-installation secret in <config-dir>/action.key.
Because the payload is hashed and not merely displayed, a token stops
verifying when the terms change — consent is to a specific price for a
specific run, and a token that survived a price change would be consent to
a number the user never saw.
This is not a security boundary; anything running as the user can read the key. It is an evidence boundary: the token cannot be produced by reasoning about the conversation, so a model that has one went through the code path that disclosed the cost. The failure being prevented is confident invention, not a determined bypass.
Exit 8 rather than 0 (which is what Flova, the design this borrows from,
returns here) because our contract puts the outcome in the exit code
precisely so a small model does not have to parse anything. vk video
export exiting 0 states that the MP4 exists; at this boundary it does not.
Not 2 either — the command line is fine, and sending an agent to look for
an argument mistake it did not make wastes a turn.
Four boundaries exist, and a token verifies only against the one it was minted for:
| Type | Raised by | Payload |
export_confirmation | vk video export, vk create --export | session_id, credits, operation |
scene_edit_confirmation | vk video edit | session_id, scene_index, script_only, from, to |
revoice_confirmation | vk video revoice | session_id, source_revision, voice_id, has_avatar, operation |
cover_confirmation | vk video cover | session_id, title, subtitle, billed, operation |
Neither new boundary carries a credit count either. A re-voice is priced by
actual use (speech, and lip-sync when there is a presenter) and quoted by
nobody; a cover is one image generation whose unit price the backend does
not expose. source_revision is the backend's fingerprint of the work, so a
work edited after the block mints a different token.
scene_edit_confirmation carries no credit count, and that is the honest
answer rather than a gap: the price depends on how much text the model
writes and how long the speech runs, and the backend does not quote it in
advance. Naming the kinds of billed work is what the CLI can say without
inventing a figure — and inventing one would undermine the only thing the
gate rests on, which is that the user saw real terms.
It also carries, under context rather than payload, the work's free
post-edit quota (GET /scene/editQuota; vk video quota reads it alone).
Only the tts part applies: an edit saved through /scene/edit takes its
speech through the tts quota, while text generation and every image it
draws (a background on the standard line, illustrations on a canvas page)
are billed regardless. The layout and background quotas are reserved only
by the web editor's preview endpoints, which the CLI does not call, so the
gate shows tts alone and lists the rest under billed_outside_quota.
context is shown with the action but not hashed into the token: the
quota is moved by other edits, the web's included, and a consent that expired
whenever anyone used quota would expire for reasons unrelated to what was
agreed to.
It carries from as well as to for two reasons. The user is approving a
diff, so both halves have to be visible. And since the payload is hashed,
a shot that changed underneath between the block and the resume mints a
different token, so a stale confirmation cannot overwrite an edit nobody
asked to discard.
kb delete keeps the plain prompt: it is destructive but not billed, and
kb prune, the bulk path, is already dry-run by default.
Which commands adopt a failure as their exit code. A command that
blocks until a terminal state takes its exit code from that state:
vk create, vk video wait, and vk video export (without --async)
all do. A command that takes a single-shot reading exits 0 and reports
the state in its payload: vk video status, vk video export-status.
So a failed render is exit 7 from export and exit 0 +
export.status="failed" from export-status. Neither ever exits 0 with
no way to tell the outcome.
Run ledger (vk jobs)
Every run is addressed by a (task_id, session_id) pair. vk create
records that pair in <config-dir>/jobs.jsonl so a caller that lost it
— context trimmed, terminal closed, process restarted — can still reach
a run that is live and billing.
vk jobs list [--active] [--output json]— recorded runs, newest firstvk jobs get [task_id]— one run (default: the most recent)vk jobs prune --terminal | --older-than 30d | --all— forget entries. A barepruneis refused; it would drop the pointer to a live run.
The ledger is local and advisory — the backend owns run state. A missing
entry means "not recorded here", never "does not exist", and every video
subcommand still accepts an explicit --session-id. Ledger writes are
best-effort: a failure warns on stderr and never fails the run.
Because it is per-machine, "not recorded here" is a weak answer: an agent
that moved hosts, a rebuilt container, or a colleague picking the work up
all have a run reachable through the account and no local file mentioning
it. So resolveTarget falls back to the backend's most recent work when
the ledger has nothing, and says so on stderr — the caller must be able to
tell which run it got.
The fallback only covers the no-arguments case. A task_id cannot be
resolved remotely: the backend's work list is keyed by work_id and
session_id and carries no task_id, so vk video status 12345 with an
empty ledger exits 2 pointing at vk video list, rather than pretending to
look something up it cannot.
Re-voice and cover (vk video revoice, vk video cover)
Re-voice makes a new work. The backend copies the work into a new
session under the same task and re-narrates it; the source is untouched.
The CLI records the new pair in the ledger, which means vk video status
<task_id> without --session-id resolves to the new version afterwards —
the result names both so neither is lost.
Re-voice is idempotent by construction. The idempotency key is a hash
of (work, source_revision, voice), so the same request always sends the
same key and the backend hands back the run it already started. Before
creating anything the CLI looks for that run in /works/revoiceJobs:
running or finished, it attaches with no gate (nothing is spent); failed,
paused or out of credits, re-running retries it — a new spend, so it goes
through the gate. The job row, not the event stream, is what the command
waits on: it is what the backend retries from and where the failure reason
lives.
Cover uses the streaming endpoint. The synchronous one takes 30–60 s,
past the 30 s client timeout, and the backend completes and bills a
generation even after the client leaves — so a timeout there would invite a
second paid attempt. A stream that ends with no result exits 6 with
resend_safe: false; an explicit error frame means nothing was saved and
nothing billed. Generating a cover clears the work's exported MP4, reported
as export_cleared.
Async submission
vk create --async submits the run and returns without waiting for the
render. It does not skip starting the pipeline: it still opens the
generation request (that request, not task init, is what starts the
backend run), waits only until the backend emits its first event, and
then detaches. The run continues server-side; the CLI disconnecting
does not stop it.
Consequences worth planning around:
--asyncreturns in seconds, not instantly. If the backend rejects the run (bad input, no credits), you get the failure here, with the same exit code the synchronous path would have used — not a task_id for a run that was never going to happen.- On success it prints
task_id/session_id, honouring--output:json→{"task_id":…,"session_id":…},ndjson→ atask.submittedevent. - If the backend accepts the request but reports no progress within 60s,
the CLI detaches anyway and says so on stderr. Treat that as
unconfirmed and verify with
vk video waitbefore relying on it. --asyncand--exportcannot be combined and are rejected with exit 2. Export can only start once the preview is finished, which--asyncby definition does not wait for. Submit,vk video wait, then export.
vk video wait [task_id] reattaches to a running task and streams its
events. It exits 6, not 0, when the stream closes without a terminal
event — including the case where there is nothing to observe at all,
which means the task was never dispatched. An exit 0 from wait always
means the task genuinely finished.
Output format
--output text|json|ndjson is a persistent flag on every command, and
every command honours it — including doc upload, credits balance,
doctor, version, whoami, and the config/profile families.
- Default is
text. There is no TTY-based auto-switching; format is always explicit, as inghandkubectl. VIBEKNOW_OUTPUTsets the default for callers that do not want to thread the flag through every invocation. Precedence:--output>VIBEKNOW_OUTPUT>text.- An unrecognized value is a validation error (exit 2), not a silent fall-through to text.
ndjsonis structured everywherejsonis: a command with one result writes it as a single JSON line, and an error is the same envelope as underjson, on one line of stderr. Twelve commands used to test forjsonalone and print their text table instead;TestOutputFormat_NoCommandBranchesOnJSONAloneguards the source.- stdout carries data, stderr carries everything else (progress,
warnings, hints).
vk doc upload x.pdf --output json | jq -r .doc_idworks.
Structured progress on stderr
--output json used to be silent until the end, and --output ndjson put
the progress stream on stdout where it displaced the final answer — a
consumer had to read the whole stream and work out for itself which line
was terminal. Choosing between watching a run and parsing its result was
not a choice worth making.
Long-running commands now write progress to stderr as
vk_event={"schema_version":"1","ts":…,"type":…} lines while stdout keeps
carrying exactly one document. The prefix is what makes it possible for the
machine lines to share a stream with human text: a consumer picks them out
by string match instead of attempting a JSON parse of every line.
- On by default when
--output json;VIBEKNOW_EVENTS=1/=0overrides. - Off in
text(stderr is a person's progress display) and inndjson(the stdout stream is released; duplicating it would double every event). - The payload is
StreamEvent.NDJSONFields()— the same shape as the stdout stream, so a consumer needs one parser, not two. - When the channel is on, the command skips the human prose it would otherwise write for the same event. stderr never carries both renderings of one fact.
Preview delivery (--preview-dir)
share_url is a hosted page. An agent driving the CLI from a terminal
cannot open one, so the single output of a video tool worth looking at was
the one output an agent could not pass on.
--preview-dir <dir> on create, video wait and video export writes
the run's artifacts there and announces each as a resource_ready event on
the structured channel:
vk_event={"type":"resource_ready","asset_kind":"cover_image",
"session_id":"s_x","local_path":"/abs/path/s_x-cover_image.jpg","bytes":48210}asset_kindiscover_imageorvideo_playback.local_pathis absolute and the file is fully written before the event fires — download goes to a temp file in the same directory and is renamed last, so a reader never sees a partial one.- The source URL is never in the event. Those are signed; an agent that relays one has published a credential.
- Dedupe is by content hash against what is already at the destination, not by URL — the backend re-signs unchanged assets, so keying on the address would re-deliver the same still on every call. Because the comparison is against the file on disk, it survives across processes: re-running into the same directory is silent.
- Failures emit
resource_preview_warningand never fail the run. The video rendered; a missing thumbnail is not evidence about that.
Presets (--preset)
vk create takes 21 flags, and most of them describe a style rather than
a run — mode, aspect, theme, voice, language, bgm, avatar placement. A
preset is that combination in a version-controllable YAML file:
schema_version: "1"
name: brand-explainer
create:
mode: image
aspect: horizontal
language: zh-CN
bgm: true--preset <name> resolves under <config>/presets/; anything with a path
separator or a .yaml/.yml extension is a path. Keys are flag names;
underscores and dashes are interchangeable.
Two rules make it safe to run a file someone else wrote.
A preset only supplies defaults. Apply skips every flag cobra reports
as Changed, so the command line always wins — including an explicit
--bgm=false. A file can never contradict what the caller typed.
The option set is an allowlist. A preset cannot set --export, --yes
or --confirm: those authorize a charge, and consent that arrives inside a
forwarded file is not consent. It cannot set --from/--kb-id (one run's
input, not a style) or --async/--preview-dir/--output (one
invocation, not a style). Each refusal is an explicit exit 2 naming the key
and the reason — a yes: true that were silently dropped would read as
though it had worked, which is the failure mode this whole contract exists
to remove.
Expansion happens at the top of RunE, before the first upload, so every
rejection is free and everything downstream — validation included — sees
one flag set and cannot tell a preset value from a typed one. Values go
through pflag.Set, so pages: many fails with the same message the
command line gives.
Every run reports what it applied: a preset.applied event carrying the
sorted list of keys that actually took effect, or one stderr line in text
mode.
This is deliberately client-side only. It is not a place for per-node
instructions: the task init request has no field to carry them
(client/figlens/task.go:31-56), so a preset can express exactly what the
command line can express and nothing more.
Video kinds
vk create --mode <kind> picks which figlens pipeline runs. The names live
in one table, internal/modes, which vk theme list --mode reads too — the
two commands used to keep separate switches and disagreed about default.
freeform(default;defaultor no flag) — 灵活创作, generative video from the document.--density sparse|balanced|densesets how many scenes; it is refused on every other line, all of which ignore it, and locally validated because the backend turns an unknown tier intobalancedwithout saying so.--mode replica— PPT/PDF page-by-page reproduction, up to 100 pages. Suited for slide decks where the visual structure is the message.--mode image— one AI-generated illustration per narration page.--pages Nsets the page count (image-gen cost scales with it);--pagesis rejected outside this mode.--theme-scenepicks a scene of the--themestyle and--ip-assetthe character who appears (vk ip list); both are image-only and checked before upload. A scene only swaps in its own reference image, and most scenes have none yet — the backend then uses the style's image, by design — so help and docs promise no visible difference.--mode handdraw— hand-drawn animation. Runs its own graph (illustration → vectorization), so it is pipeline-only.--mode canvas— 画布创作 (stage_craft): components placed on a grid. Defaults to vertical when no--aspectis given, unlike every other line.video_kindgoes on the init request as well as the stream, and that matters here: init is what marks the work as a canvas work. Pipeline-only.
--images is a preference outside image mode. On freeform and canvas the
director is told to prefer the picked images, and the judge only reports
one it left out (beatline/judge.go, "只记录,不强塞") — forcing an image into
a scene designed without it reads worse than omitting it. Only image mode
assigns every picked image to a page. Docs and help say "feature", never
"mandatory".
Themes (--theme)
A theme belongs to a mode: the mode picks the catalog, --theme picks an
entry in it. 主题 / 风格 / 模板 in a user's request all mean this flag.
vk theme list --mode X shows everything mode X accepts — the user's own
themes first as ua_<id> (source: "custom"), then the official suite —
and vk create validates --theme against that same list (internal/themecat)
before the first upload. The check exists because the backend does not
refuse an id it does not recognise for the run's kind: on the standard line
it substitutes a fallback theme, and the canvas line ignores unknown ids by
design. That is how a user's custom theme passed as the bare number the web
displays came out in a different style, billed, with no error.
Two modes have a second level, carried inside each theme list entry so
there is still one place to look: canvas directions list their palettes as
variants (each a --theme value of its own), image styles list scenes
for --theme-scene. An image-mode IP character is a separate axis — who
appears, not how it is drawn — and has its own list, vk ip list. The
image2 asset type holds both styles and characters, so themecat keeps
only styles as themes and refuses a character's token pointing at
--ip-asset.
- A bare number is refused, but definitely: the hint names the user's theme
with that id and its
ua_<id>spelling, or the message says whyua_<id>would fail too. Refused rather than corrected, so the value the caller sent is the value reported back. vk theme list --minelists only the user's own themes across every mode, each with themodesthat accept it. Users name a theme, not the mode it was made for; without it an agent lists mode after mode.- An id from another mode's catalog is refused naming that mode.
- Four design-suite themes are reserved for a line the CLI does not expose
and get swapped in freeform — and so does a custom theme built on one,
since the backend selects through a custom theme's base;
/themesdoes not mark them, sothemecat.bookIntroOnlyis a copy of a server rule, and the error says so. - A catalog that cannot be fetched fails with that fetch's exit code. Unable to check is never treated as checked.
The result reports requested_theme, named as a request: the work detail
response carries no theme, so nothing can confirm what actually rendered.
Script lock
--script-lock uses the uploaded document text verbatim as the
narration instead of writing a script from it. It is orthogonal to
--mode — it rides on top of whichever line was picked, so
--mode image --script-lock illustrates the user's own script.
The doc must pass a quality preflight (length, characters, content)
which runs at task-init time, before any billing. Preflight failures
exit 2 with a clear message; agents should treat them as
user-input problems, not retries. The preflight only fires when the
kb/doc pair is known, so --script-lock requires --from <file|URL>
or --from <doc_id> --kb-id <kb>.
On --mode replica the lock means the speaker notes are the script,
page by page; the backend then requires the source to be a PPT file, since
a PDF has no notes.
--mode script is a deprecated alias for --script-lock and
prints a warning. It dates from when the backend expressed the lock
as a video_kind value rather than a separate boolean; the two axes
have since been split, and the alias now resolves to the boolean.
Modes combine freely with --aspect horizontal|vertical (or 16:9 /
9:16) and --bgm. All these flags are independent of --from,
--prompt, --voice, and --export.
Exit-code summary for new modes:
2—--mode <bad>,--aspect <bad>, or script preflight rejected the document.5— pipeline business failure (e.g., insufficient credits) — same as today.7— preview ok, MP4 export failed — same as today.
Engine selection
vk create --engine <name> picks which go-figlens engine generates the
video. Two engines exist on the backend, but only one is still offered on
the web:
--engine pipeline(default) — v=3, graph-based pipeline. Has rich SSE node events ([parse] prepare started,[outline] text_speech done, ...). All--modevalues supported.--engine agent— v=2, agent-driven flow. SSE events are free-form progress messages without a node graph; CLI shows them as[agent] <message>in text mode ornode.progressin NDJSON. The legacy 一键成片 engine: the web stopped offering it, so it prints a deprecation warning on stderr (behaviour and exit code unchanged) and may be retired. Supports--script-lockbut none of--mode replica|image|handdraw|canvas— each of those runs a dedicated graph the agent engine never dispatches to, so the CLI rejects the combination with exit 2 rather than letting the backend silently render an ordinary video instead.
The engine field in JSON snapshot output reflects which engine ran
("pipeline" / "agent"), letting agents verify routing.
Voice selection
vk voice list prints two identifiers per row. SPEECH_VOICE_ID is the
one the backend's TTS keys on; the leading # is a display index.
--voice accepts either — a # is translated to its speech_voice_id
before anything is uploaded, and a # that is not in the list exits 2
straight away rather than failing inside the TTS node after cover and
background images have already been billed.
Non-numeric values pass through unvalidated, because cloned voices do not appear in the template list.
Subtitle style
vk subtitle fonts and vk subtitle presets read two catalogs the backend
vendors from design-suite and treats as the single source — the web font
picker, the render-time whitelist and these endpoints all read the same file.
So a value present in the list is a value the backend accepts; there is no
approximation to hedge against. --subtitle-font and --subtitle-preset take
either the display # or the real value, the same bargain --voice makes.
Three things about the write path are easy to get wrong:
The style is stored wholesale. POST /works/subtitleStyle marshals what
it receives straight into the column, so a partial write clears everything it
omits. Always ParseSubtitleStyle the work's current style first, change what
was asked for, send the whole thing back.
A preset is a patch, and its zeroes are meaningful. SubtitleStylePatch
is built out of pointers for exactly one reason: the plated presets carry
"strokeWidth": 0 to switch off an outline the work may already have, while
saying nothing at all about font size. Decoded into value fields those two
cases collapse, and whichever way the merge then guesses, one family of
presets breaks. Keep the pointers.
Local range checks are not defensive symmetry. The backend clamps
bottomPercent to [0.02, 0.98] and strokeWidth to [0, 12] instead of
refusing them, and does not validate fontWeight at all. Clamping is worse
than refusing for a caller: the request succeeds having stored something else,
and nothing reports the substitution. The checks in validateSubtitleStyleFlags
run before any network call, so an impossible number costs nothing and is
named as the mistake it is.
The animation whitelist is the one piece of this with no endpoint behind it —
figlens.SubtitleAnimations is a copy of a server-side constant. A value
added server-side is unusable until that list catches up, which is why the
error says "not one this CLI recognises" rather than "invalid". If you touch
it, keep the wording honest about where the limit lives.
KB management
vk kb list / delete / prune manage vectoria knowledgebases. The
headline command is vk kb prune, which exists to clean up the kb
backlog that vk create accumulates (one new kb per invocation).
Safety contract for vk kb prune:
- Refuses to run without
--patternor--older-than(exit 2). No "delete all" shortcut. - Dry-run by default: prints matched kbs without issuing any
DELETE.
--yes(orVIBEKNOW_ASSUME_YES=1) actually deletes. - Idempotent: a 404 on DELETE counts as success (kb already gone).
- Partial failure: exit 7 if some succeed and some fail; exit 5 if all fail; exit 0 if all succeed or matched empty.
Recipes:
- Clean CLI's own orphans:
vk kb prune --pattern 'vibeknow-cli-*' --yes - Clean old kbs (last 30 days):
vk kb prune --older-than 30d --yes - Both:
vk kb prune --pattern '*' --older-than 90d --yes
Output via --output json for piping; default text mode is
human-friendly.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

