agentleFS
vibeknowvibeknow/cli/AGENTS.md

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.

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
  1. Very specific:

    All user-visible strings go through internal/i18n.

    line 12
  2. Very specific:

    Run make test and make build.

    line 18
  3. Very specific:

    Never commit credentials, cache files (~/.cache/vibeknow/*), or dist/.

    line 19
  4. Distinctive:

    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.

    line 133
  5. Very specific:

    Only the tts part applies: an edit saved through /scene/edit takes its speech through the tts quota, while text generation and every image …

    line 161
  6. Very specific:

    Every run is addressed by a (task_id, session_id) pair.

    line 192
  7. Very specific:

    If the backend accepts the request but reports no progress within 60s, the CLI detaches anyway and says so on stderr.

    line 264
  8. Very specific:

    Default is text. There is no TTY-based auto-switching.

    line 283
  9. Distinctive:

    When the channel is on, the command skips the human prose it would otherwise write for the same event.

    line 318
  10. Very specific:

    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 …

    line 392
  11. Very specific:

    DELETE. --yes (or VIBEKNOW_ASSUME_YES=1) actually deletes.

    line 594

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
AGENTS.md

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) plus tests/integration/.
  • --output values: text (the default, whatever the terminal), json, ndjson.
  • Exit codes: see spec §5.4.

Before making changes

  • Run make test and make build.
  • Never commit credentials, cache files (~/.cache/vibeknow/*), or dist/.

Two-stage video workflow

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, 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 with reason: "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 follow next_actions[].command to plan the next step. --preview-dir also 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. --yes skips the confirmation prompt; VIBEKNOW_ASSUME_YES=1 works 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; --output means 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_limit and work_edit_busy exit 4: the same command is worth retrying after a wait.
  • insufficient_credits exits 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:

deliveryresend_safeBasis
submittedfalsethe backend has a work row for this session
not_submittedtruethe backend returns not_found — the work row is created at init, so its absence means nothing was billed
indeterminatefalsethe 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:

  1. --yes / VIBEKNOW_ASSUME_YES — the caller states it has authority.
  2. --confirm <action_id> — authority obtained through this gate.
  3. A terminal — ask, the way a person expects to be asked.
  4. 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:

TypeRaised byPayload
export_confirmationvk video export, vk create --exportsession_id, credits, operation
scene_edit_confirmationvk video editsession_id, scene_index, script_only, from, to
revoice_confirmationvk video revoicesession_id, source_revision, voice_id, has_avatar, operation
cover_confirmationvk video coversession_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 first
  • vk jobs get [task_id] — one run (default: the most recent)
  • vk jobs prune --terminal | --older-than 30d | --all — forget entries. A bare prune is 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:

  • --async returns 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 → a task.submitted event.
  • 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 wait before relying on it.
  • --async and --export cannot be combined and are rejected with exit 2. Export can only start once the preview is finished, which --async by 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 in gh and kubectl.
  • VIBEKNOW_OUTPUT sets 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.
  • ndjson is structured everywhere json is: a command with one result writes it as a single JSON line, and an error is the same envelope as under json, on one line of stderr. Twelve commands used to test for json alone and print their text table instead; TestOutputFormat_NoCommandBranchesOnJSONAlone guards the source.
  • stdout carries data, stderr carries everything else (progress, warnings, hints). vk doc upload x.pdf --output json | jq -r .doc_id works.

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 / =0 overrides.
  • Off in text (stderr is a person's progress display) and in ndjson (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_kind is cover_image or video_playback.
  • local_path is 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_warning and 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; default or no flag) — 灵活创作, generative video from the document. --density sparse|balanced|dense sets 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 into balanced without 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 N sets the page count (image-gen cost scales with it); --pages is rejected outside this mode. --theme-scene picks a scene of the --theme style and --ip-asset the 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 --aspect is given, unlike every other line. video_kind goes 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 why ua_<id> would fail too. Refused rather than corrected, so the value the caller sent is the value reported back.
  • vk theme list --mine lists only the user's own themes across every mode, each with the modes that 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; /themes does not mark them, so themecat.bookIntroOnly is 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 --mode values 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 or node.progress in 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-lock but 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 --pattern or --older-than (exit 2). No "delete all" shortcut.
  • Dry-run by default: prints matched kbs without issuing any DELETE. --yes (or VIBEKNOW_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.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.