Add @vivideo/cli (npm) to public_registry.json — an AI video generation
CLI for creating avatar and text-to-video content from the terminal,
scripts, CI, or agents.
Co-authored-by: Cursor <cursoragent@cursor.com>
These four CLI modules define their entry point but never invoke it under an
`if __name__ == "__main__":` guard, so `python -m cli_anything.<pkg>.<pkg>_cli`
exits 0 without producing any output. 63 of the 68 CLI modules in the repo
already carry the guard; this brings the remaining ones in line.
Each guard calls the same callable the console script points at, so behaviour
matches an installed run:
- eth2-quickstart -> `main()`
- mubu -> `raise SystemExit(entrypoint())`
- siyuan -> `cli()`
- zotero -> `raise SystemExit(entrypoint())`
`entrypoint()` returns an exit code in mubu and zotero, which `SystemExit`
propagates the same way setuptools' console-script wrapper does.
Refs #436
Claude-Session: https://claude.ai/code/session_01ChPxA9ksnNqr83nLbfMhwu
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`main()` called `cli()` without any error handling, so a failure to reach the
AdGuardHome API surfaced as a chained urllib3/requests/click traceback. In
`--json` mode nothing at all was written to stdout, leaving an agent with no
parsable result.
The Docker hint inside that error message also mapped the host port to itself
(`-p 3001:3001`), which cannot reach a fresh container: the image serves its web
interface on container port 3000, as the harness's own E2E fixture and README
already assume.
- catch RuntimeError and requests exceptions in `main()`: emit
`{"error": ...}` on stdout with `--json`, the plain message on stderr
otherwise, exit 1 in both cases
- map the Docker hint to container port 3000 and mention that the wizard must
keep the web interface there
- cover both paths with tests: JSON shape and exit status on an unreachable
host, plain-message path on stderr, and the port mapping in the hint
Fixes#437
Claude-Session: https://claude.ai/code/session_01ChPxA9ksnNqr83nLbfMhwu
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`cli_anything/adguardhome/adguardhome_cli.py` defined `main()` but never
invoked it under an `if __name__ == "__main__":` guard, so running the module
with `python -m cli_anything.adguardhome.adguardhome_cli` exited 0 without any
output.
That is the exact module path the `_resolve_cli()` helper in
`tests/test_full_e2e.py` falls back to when the console script is not on PATH,
so five of the seven subprocess tests failed on a plain checkout while two
others — which asserted only the exit code — stayed green.
- add the guard to `adguardhome_cli.py`
- assert command-specific stdout in `test_rewrite_help` and `test_blocking_help`,
matching their sibling tests, so the pair can detect an inert entry point
- bump the harness version to 1.0.1 in `setup.py` and `registry.json`
Fixes#436
Claude-Session: https://claude.ai/code/session_01ChPxA9ksnNqr83nLbfMhwu
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fast scrolling on iPhone could still kill the tab after the GIF/video and
content-visibility work. Two remaining causes only real iOS exhibits:
- iOS Safari fires a resize when its toolbar collapses mid-scroll, and the
handler rebuilt both masonry grids (teardown + re-append of 101 cards)
during the fling. Masonry columns depend only on width, so the handler
now bails when the width is unchanged.
- Phones laid out all 79 harness cards (~28k px of live DOM) up front —
content-visibility skips paint but the initial masonry measurement still
forces layout of every card, and a fling still rasterizes the whole run.
Single-column layouts now render the deck in batches of 20 that grow via
a sentinel IntersectionObserver (1600px margin) as the user approaches;
the mobile deck placeholder shrinks to match. Multi-column desktop
masonry still renders the full set so shortest-column balancing is exact.
jumpToTool materializes remaining batches before scrolling to a target
card; search/filter/sort reset to the first batch.
Chromium renderer RSS (390x844@3x fling scenario): load 297MB -> 171MB,
peak 807MB -> 380MB vs the original page. Full cross-engine suite passes
(WebKit + Chromium, mobile + desktop): batch growth while scrolling,
reset-on-search, jumpToTool into late batches, footer reachable at true
bottom, video rings, flip lifecycle, no overflow, zero page errors.
Fast scrolling to the bottom stats killed the tab on iPhone (Safari and
Chrome, both WebKit). Two causes, both memory:
- The demo strip embedded animated GIFs that decode to ~730MB of RGBA
frames on index alone (~1.26GB on demos.html). Replaced with H.264
<video muted loop playsinline preload=none> + poster JPEGs
(hardware-decoded, 8-48x smaller files). An IntersectionObserver
plays/pauses within 200px of the viewport and beyond 1200px drops the
src entirely so the media pipeline is fully released.
- A fling rasterized the whole ~32k-px page of gradient cards at once.
Below-fold sections (demos strip, matrices, deck, footer; demo rows on
demos.html) now use content-visibility:auto with contain-intrinsic-size
placeholders (plain-length fallback first for Safari 15.4-16, which
lacks the auto keyword).
Measured (Chromium renderer RSS, 390x844@3x, repeated fling to bottom,
3 runs each): index peak 807MB -> 420MB and memory is released after
scrolling (settles 350MB) instead of pinned; demos.html 631MB -> 222MB.
Functional suite passes in WebKit + Chromium, mobile + desktop: masonry
integrity under render-skip, video play/pause/teardown/resume rings,
deck switch, search, flip lifecycle, no horizontal overflow, zero page
errors.
- Gate per-card backdrop-filter blur to desktop pointers; touch devices use
the solid glass fallback (dozens of blur layers blew iOS Safari's GPU budget)
- Flatten card flip 3D contexts by default; enable per-card only on hover or
during the flip transition
- Replace filter:blur(100px) ambient blobs with pre-softened gradients and
hide the blend-mode grain overlay on touch devices
- Render decks once: fetch matrix registry with the other registries, defer
the offscreen public deck + shelf, start counters after first paint
- Fix 627px-wide layout blowout on 390px phones (min-width guards on the
empower and demo grids); let cards use the full phone width
PR #415 refreshed the model catalog and updated the generated mirror
skills/cli-anything-minimax/SKILL.md, but left the canonical source
description describing only MiniMax-M3 / speech-2.8-hd. The root-skills
validator flags the mirror as containing content absent from its source,
failing "Check Root Skills" on main. Update the source description to match
so the mirror is a faithful sync (sync_root_skills.py now reports no drift).
* registry: meerk40t v1.2.0, move to design category
* registry: meerk40t to 3d category (CAM/G-code neighbours)
* readme: add Fabrication & Machine Control category
* copy: describe only released 1.2.0 capabilities
* registry: meerk40t v1.3.0 with released hardware-control commands
* fix(comfyui): keep --json stdout parseable
`workflow validate --json` printed the JSON document and then appended a
human-readable summary line, so `json.loads()` on the output failed with
"Extra data". The harness's own test already caught this
(test_workflow_validate_json_output), leaving the suite at 59 passed /
1 failed.
Route human-facing text through an `echo_human()` helper that no-ops in
JSON mode, and send the `queue clear` confirmation prompt to stderr in
JSON mode so it cannot corrupt stdout either. Human-mode output is
unchanged.
Adds regression tests for the invalid-workflow branch, for the human
summary still being printed without --json, and for the confirm prompt
staying off stdout. README test counts updated (70 -> 73).
Fixes#401
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(comfyui): read real streams for the JSON-purity prompt test
`CliRunner` emulates a terminal by echoing the answer typed at a prompt
onto stdout. On click < 8.2 the echo lands there even when the prompt
itself was routed to stderr, so with click 8.1.8 the runner produced
`stdout == ' n\n{"error": "", "type": "Abort"}\n'` and the assertion
`json.loads(result.stdout)` failed — the new test was red under the
unbounded `click>=8.0.0` dependency. Click 8.2+ sends the echo to the
prompt's own stream, which is why the test passed on 8.4.2.
Drop the `split_runner` fixture and run the CLI in a real subprocess
instead, so the test reads the streams a caller actually sees. Answering
"n" aborts before any request, so no ComfyUI server is needed.
Verified on click 8.1.8 and 8.4.2: 63 unit tests pass on both, and both
fail this test again if the `err=_json_output` fix is reverted.
Addresses Codex review feedback on #414.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: PAN CHAO <panchao@PANdeMacBook-Pro.local>
* fix(obs-studio): reject non-finite geometry in position/size/crop
validate_position accepted NaN coordinates into project JSON. size/crop
converted NaN via int() with a cryptic error. Reject non-finite values
up front, matching validate_range.
* test(obs-studio): drop unused math import in geometry nan tests
* fix(obs-studio): wire finite geometry checks into source transforms
validate_position/size/crop were unused while add_source and
transform_source accepted NaN into project JSON. Call the validators
from those paths and cover the live transform entrypoint.
* fix(obs-studio): validate finite geometry without float-rounding ints
* test(obs-studio): preserve large integers in geometry validators
* fix(obs-studio): preserve decimal integer geometry
* test(obs-studio): document integer string regressions
* fix(obs-studio): validate integer geometry and make source transform validation atomic
NaN bypassed min/max checks because NaN comparisons are always false,
so invalid opacity/volume values could be stored. Reject non-finite
floats before the range compare.
* docs(browser): document split-and-check wrapper call sequences
Update HARNESS.md so absolute/rooted fs and act wrappers describe the
actual multi-call anchor → op → restore pattern instead of a single
multi-line domshell_execute string.
Fixes#328
* docs(browser): correct rooted wrapper sequences
Use repr() (via f-string !r) for path embedding instead of raw-string
r'...' prefixes. Raw strings are fragile with trailing backslashes and
quotes in paths; repr() produces a correctly-escaped Python literal
for any path.
Affects three sites in bpy_gen.py:
- HDRI image path in _gen_world_settings
- Output filepath in _gen_render_output
- 'Render complete:' print statement in _gen_render_output
Fixes#343
* fix(n8n): send only the fields the workflow API accepts
n8n's workflow schema sets additionalProperties:false and marks active, tags,
meta, isArchived, triggerCount and the id/timestamp fields readOnly. The payload
reduction in _clean_for_api was a blacklist of five fields, which is incomplete
against that schema: on n8n 2.16.1 a GET returns 20 keys, five are removed, and
eight of the remaining fifteen are rejected. Every command that builds a payload
from a fetched workflow therefore fails with
400 - request/body must NOT have additional properties
Separate the two reductions the helper was serving. Writes now keep only the
schema's non-readOnly properties; backup-all and diff keep the previous
behaviour under _strip_server_fields, because a backup has to record active and
tags and a diff has to compare them - exactly what the API refuses. versions
diff had a fifth inline copy of the blacklist, now routed through the same
helper.
nodeGroups and parentFolderId are in the writable set although they only exist
from n8n 2.33.3, so an edit made against a current instance does not silently
delete a workflow's node groups.
Two further layers are handled:
- Nulls are dropped. description is a plain string in the schema, so a workflow
without one reads back as null and sending it returns
"request/body/description must be string". Omitting a field leaves the stored
value untouched, so dropping is the safe direction.
- The nested settings object is reduced too. It is additionalProperties:false as
well and n8n's own editor writes binaryMode into it, so every workflow created
in the UI was rejected even with a clean top level.
Adds tests/test_api_payload_fields.py, which declares the accepted set itself
rather than importing it, so it asserts the API contract instead of tracking
what the module currently sends. Five of its cases fail on main.
* fix(n8n): route import, restore-all and template deploy through the same payload helper
These three built their request bodies with their own inline copy of the field
blacklist and then set data["active"] = False. active is readOnly on the
workflow endpoint, so all three failed unconditionally with 400, independently
of the field-count problem:
workflow import <file> 400
workflow backup-all -> workflow restore-all 400, printed as
"OK: Restored 0 workflows (1 failed)"
template deploy <id> 400
Route them through _clean_for_api instead. Workflows still land inactive, since
active cannot be set through this endpoint at all.
template deploy additionally needs settings defaulted: n8n.io templates
frequently omit it while the API requires it, which surfaced as
"400 - request/body must have required property 'settings'".
* fix(n8n): keep nulls for nullable properties, and always supply settings
Addresses the two review comments on this PR.
Dropping every null was wrong for versions rollback. staticData, pinData and
parentFolderId are nullable in the schema, and for them null is meaningful: it
clears the stored value, while omitting the key leaves it alone. So a snapshot
taken before any pinned data existed could not clear pinned data added later,
and the command reported a successful rollback regardless. The non-nullable
properties still have their nulls dropped, since echoing back the null a GET
returned is what draws "request/body/description must be string".
Verified against a live instance: a PUT with pinData: null empties it, and
rollback from a snapshot that predates the pinned data now restores the empty
state.
workflow import and workflow restore-all also did not guarantee the required
settings object, so a handwritten or older export lacking it still failed with
"must have required property 'settings'". Rather than repeat the default at
each call site, _clean_for_api now always supplies it, and the copy in
template_deploy is dropped.
* fix(n8n): restore tag assignments after recreating or rewinding a workflow
tags is readOnly on the workflow body, so reducing the payload correctly means
import, restore-all and versions rollback all leave the tags behind - and do it
silently, because the create or update itself succeeds. A restored workflow was
counted as restored while dropping out of every tag-filtered search.
Put them back through update_workflow_tags, which the harness already wraps.
Passing None (an older export with no tags key) leaves the current assignment
alone; an empty list clears it, so rewinding to a snapshot taken before any tag
existed removes them, matching the null-clears semantics used for pinData.
Tag ids are per-instance, so restoring into a different n8n answers 404 Some
tags not found. That is reported as a warning instead of raised: the workflow is
already created at that point, and failing the whole restore over its tags would
lose more than it protects.
Also completes the payload tests per review: parentFolderId is now in the
fixture and asserted both as writable and as null-preserving, and `shared` is
pinned explicitly - it is the one field that does not draw a 400 (the API
answers 200 and ignores it), so only an assertion catches it being forwarded.
* fix(n8n): keep export local-shaped, and reduce every write at the call to the API
Reducing `export` to the writable set was a regression of my own making: the
file it writes lost its tags, so `export` -> `import` restored a workflow
without them, while `backup-all` -> `restore-all` kept them. Two paths meant to
do the same thing behaved differently.
Split it along one rule instead. Local artefacts keep the full picture, and
every body that goes to the API is reduced at the call:
local form (_strip_server_fields): export, backup-all, diff, versions diff
wire form (_clean_for_api): create, update, import, restore-all,
template deploy, autofix, patch, rollback
`workflow create` and `workflow update` now reduce their input too, which is
what makes a file straight out of `export` usable in both. Hand-written JSON is
unaffected in practice: the fields this removes are exactly the ones that would
have produced a 400. `create` also reattaches tags from the file, the same way
`import` does.
Verified against a live instance: an exported workflow keeps its tags, imports
with tags and pinData intact, and the same file can be fed back through
`workflow update` without a 400.
* fix(n8n): degrade instead of failing on cross-version writes and unusable tags
Three failure modes where a write that had already partly succeeded was reported
as a plain failure, or silently did the wrong thing.
Cross-version imports: `nodeGroups` and `parentFolderId` exist only from n8n
2.33.3, so a file exported from a newer instance was rejected wholesale by an
older one. The target version is not cheap to query, so the write now reacts to
the rejection - retry once without those properties and say so. Losing a canvas
grouping beats losing the import. Every workflow create/update goes through the
one wrapper, `scaffold --deploy` included.
Tags with no id: filtering ids out of a non-empty list yields an empty list, and
sending that cleared every tag on the workflow - the opposite of what an
unreadable record should do. It now warns and leaves the assignment alone. An
explicitly empty list still clears, since that is a real state.
Tag request failures: only HTTPError was caught, so a timeout or dropped
connection escaped and made the caller count an already-created workflow as
failed - and a retry of `restore-all` would then duplicate it. Catches
RequestException instead.
Verified against a live n8n 2.16.1, which is old enough to reject both newer
properties: a file carrying them imports with
"WARN: Target n8n rejected nodeGroups, parentFolderId - retrying without", and a
file whose tags carry no ids imports with the assignment left untouched.
* fix(n8n): state what the retry knows, not what it assumes
The fallback warning claimed the target had rejected those specific properties,
but the retry fires on any 400 that carries them - it is a guess, not a
diagnosis. If the 400 came from something else the retry fails and the real
error surfaces, so the message should not have asserted a cause.
before: Target n8n rejected nodeGroups, parentFolderId - retrying without
after: Write rejected with 400 - retrying without nodeGroups, parentFolderId
* fix(n8n): carry the cross-version fallback into settings, and keep diagnostics off stdout
The nested settings object gained five properties in n8n 2.33.3 - binaryMode,
credentialResolverId, customTelemetryTags, redactionPolicy, timeSavedMode - and
it is additionalProperties:false in its own right. Two consequences, both fixed
here:
Writing to a current instance was resetting them, because the whitelist was
taken from the 2.16.1 schema. binaryMode matters most: the editor writes it into
every workflow it creates, so every edit was quietly changing it.
The cross-version retry only stripped top-level properties, so a payload whose
only incompatibility was in settings got no retry, and one that had both failed
again on the second request. It now reduces settings the same way and names what
it dropped as `settings.<key>`.
The trade is one failed request against silently changing a setting: on an older
instance a workflow carrying binaryMode now costs a 400 and a retry rather than
being stripped up front.
Diagnostics from these fallbacks go to stderr. warn() prints to stdout, which
would corrupt the documented --json output exactly when a fallback fires;
repl_skin.py is meant to stay a verbatim copy of the plugin's, so this goes
around it instead of changing it.
Verified against a live n8n 2.16.1: a file carrying nodeGroups and
settings.binaryMode imports with "Write rejected with 400 - retrying without
nodeGroups, settings.binaryMode" on stderr.
* fix(n8n): retry only on unknown-property errors, not on any 400
The compatibility retry fired on any 400 whose payload happened to carry a
newer-schema field. A rejected *value* - a malformed nodeGroups object, an
unsupported settings value - produces a 400 too, and the retry would then drop
the property and succeed, reporting success while silently discarding
configuration the caller asked for. That is the failure mode this PR exists to
remove, reintroduced in the fallback itself.
n8n's validator separates the two cases, so the retry now requires the first:
unknown key request/body must NOT have additional properties
request/body/settings must NOT have additional properties
bad value request/body/description must be string
request/body/settings/executionTimeout must be number
request/body/settings/callerPolicy must be equal to one of ...
An unreadable error body counts as no evidence, and does not retry either.
Verified against a live n8n 2.16.1: a payload with an unknown property still
degrades and imports; one that also carries an invalid settings value surfaces
`request/body/settings/callerPolicy must be equal to one of the allowed values`
and creates nothing. A payload whose only problem is a bad value never reaches
the retry at all, covered by unit tests since it needs a newer instance to
reproduce end to end.
* fix(n8n): match the rejected path, not just the wording
The previous predicate accepted any message ending in "must NOT have additional
properties", but the validator uses that same wording for an unexpected member
nested anywhere:
retryable request/body must NOT have additional properties
request/body/settings must NOT have additional properties
malformed request/body/nodes/0 must NOT have additional properties
request/body/nodeGroups/0 must NOT have additional properties
The last one is the dangerous case: a grouping whose item carries an unexpected
member would have been read as a version mismatch, and dropping the entire
grouping could then succeed - turning invalid input into a success that quietly
lost the grouping. Only the two paths the retry actually reduces now qualify.
Verified against a live n8n 2.16.1 that the deep form is real - a node with an
extra member reports request/body/nodes/0 - and that a payload combining it with
an unknown top-level property now surfaces that error and creates nothing, while
a plain version mismatch still degrades and imports. The nodeGroups/0 case needs
an instance that defines nodeGroups, so it is covered by unit tests.
* fix(n8n): treat malformed tags and settings as input, not as something to swallow
Two shape assumptions, both of which turned bad input into the wrong outcome.
A scalar `tags` value raised TypeError on the comprehension, which happens after
the workflow has been created - so the caller reported a failure for something
that exists, and a retry of restore-all would duplicate it. That is the partial
success this helper already guarded against for request failures, just reached
by a different route. A non-list is now reported as an unreadable record and the
assignment is left alone.
A non-object `settings` was silently replaced with `{}`. The value is invalid,
but that is the server's verdict to give by name, not something to quietly
substitute - the caller would otherwise see a success with their input gone. It
is now passed through; only a missing one is defaulted.
Verified against a live instance: importing a workflow with "tags": 1 creates it
and reports `Tags ... are not a list — left unchanged`, and one with
"settings": "nope" fails with `request/body/settings must be object` and creates
nothing.
---------
Co-authored-by: richardhxwang <225731730+richardhxwang@users.noreply.github.com>
In the RIFF/WAV format, 8-bit PCM samples are unsigned (0..255, silence
== 128), while 16/24/32-bit are signed. Both samples_to_wav_bytes() and
write_wav() packed 8-bit with a signed byte ("<b"), so every 8-bit file
was written with the wrong encoding. This module's own read_wav() decodes
8-bit as unsigned ((val - 128) / 128), and so does every standard player,
so 8-bit output was misdecoded: e.g. silence (0.0) round-tripped to full
scale (-1.0). The wav-8 export preset selects this path.
Pack 8-bit as unsigned ("<B", int_val + 128); int_val is already clamped
to [-128, 127]. Add an 8-bit write/read round-trip regression test
(TestWavIO only covered 16/24-bit).
* fix(browser): bare cat for root/empty path in fs cat
Preserve no-arg `fs cat` when the session cwd is `/` by translating the
empty absolute path into anchor + bare `cat` + restore, instead of
sending `cat ''` to DOMShell.
Also treat relative empty path as bare `cat` on the current cursor.
Fixes#330
* fix(browser): read current node for empty cat paths
validate_root_skills.py compared source and mirror with a symmetric `!=`,
so it detected divergence but never direction, and printed the same advice
either way: "Run sync_root_skills.py and commit."
That advice is only correct when the harness source moved ahead. When the
edit is in the generated skills/ mirror -- as in #360 -- sync regenerates
the mirror from the source and silently deletes the change. The validator
then passes, so CI turns green and certifies the loss.
Detect the direction. When the mirror holds lines the source lacks, name
them, point at the source file, and never suggest the destructive command.
Regenerable drift keeps the old advice. Also make sync_root_skills.py
report each file it creates or overwrites instead of writing in silence.
Verified: in-sync, mirror-edited, source-edited, missing-mirror, and
idempotent no-op.
#360 added the "TTS options" section to skills/cli-anything-minimax/SKILL.md,
which is a generated mirror produced by .github/scripts/sync_root_skills.py
from minimax/agent-harness/cli_anything/minimax/skills/SKILL.md. The source
was never updated, so validate_root_skills.py failed on main.
Add the section to the canonical source. The mirror is unchanged, so the
documentation added in #360 is preserved verbatim. Note that running
sync_root_skills.py alone -- as the validator's error message suggests --
would have deleted those lines instead.
* # feat(minimax tts): expose all voice/audio parameters on the tts subcommand
## Summary
The MiniMax TTS backend (`cli_anything/minimax/utils/minimax_backend.py`)
already supported a full set of voice and audio parameters, but the CLI
exposed only `--text`, `--model`, `--voice`, and `--output`. Every other
field was hardcoded, so users had to fork the harness to change speech
speed, volume, pitch, sample rate, bitrate, audio format, or channel
layout.
This change promotes all seven hardcoded parameters to first-class CLI
options on the `tts` subcommand, with click-level range and choice
validation, and adds a regression test module.
## What changed
### `cli_anything/minimax/utils/minimax_backend.py`
- `tts_synthesize(...)` gains 7 new parameters: `speed`, `vol`, `pitch`,
`sample_rate`, `bitrate`, `audio_format`, `channel`.
- The hardcoded `voice_setting` and `audio_setting` blocks now read from
the new parameters. Defaults match the previous hardcoded values, so
the change is fully backward compatible at the API level.
### `cli_anything/minimax/minimax_cli.py`
- The `tts` Click command gains 7 new options:
- `--speed` (FloatRange 0.5..2.0, default 1.0)
- `--vol` (FloatRange 0.0..10.0, default 1.0)
- `--pitch` (IntRange -12..12, default 0)
- `--sample-rate` (Choice: 8000/16000/22050/24000/32000/44100, default 32000)
- `--bitrate` (Choice: 32000/64000/128000/256000, default 128000)
- `--format` (Choice: mp3/pcm/flac, default mp3)
- `--channel` (Choice: 1/2, default 1)
- The `tts_synthesize(...)` call is updated to forward the new options.
### `cli_anything/minimax/tests/test_tts_extended.py` (new)
- 4 mock-based tests:
- `test_tts_default_voice_audio_settings` — guards backward-compatible defaults
- `test_tts_custom_voice_setting` — speed / vol / pitch propagation
- `test_tts_custom_audio_setting` — sample_rate / bitrate / format / channel
- `test_tts_voice_id_propagates` — regression guard for voice + speed combo
## Verification
```bash
# Apply patches (from repo root)
patch -p0 < pr-minimax-tts/01-backend.patch
patch -p0 < pr-minimax-tts/02-cli.patch
cp pr-minimax-tts/03-tests-test_tts_extended.py \
cli_anything/minimax/tests/test_tts_extended.py
# Run the new tests
cd minimax/agent-harness
PYTHONPATH=. python3 -m pytest cli_anything/minimax/tests/test_tts_extended.py -v
# 4 passed
# Inspect the new surface
PYTHONPATH=. python3 -m cli_anything.minimax.minimax_cli tts --help
```
## Backward compatibility
- API call signature gains keyword-only-ish params with default values
identical to the previous hardcoded values, so any existing caller of
`tts_synthesize(api_key, text, model, voice, output_path)` keeps
working unchanged.
- CLI behavior is unchanged when none of the new options are passed.
## Related
- Skill surfaces this command under `cli-anything-minimax tts` —
`skills/cli-anything-minimax/SKILL.md` should mention the new flags
in a follow-up doc pass.
* docs(minimax): document new tts options in README and SKILL.md
Companion to #360. Surfaces the new --speed/--vol/--pitch/--sample-rate/
--bitrate/--format/--channel flags in both:
- minimax/agent-harness/cli_anything/minimax/README.md (TTS options table)
- skills/cli-anything-minimax/SKILL.md (TTS options block)
No code change; just user-facing doc sync.
---------
Co-authored-by: hhdhh <hhdhh@users.noreply.github.com>
Standalone CLI-Anything harness for the Palmier Pro local MCP video editor
(http://127.0.0.1:19789/mcp). Wraps all 29 MCP tools with a Click command tree;
stdlib streamable-HTTP transport (no Node/mcpc), offline pytest suite, SKILL.md.
Repo: https://github.com/workcr/palmier-cli-anything
CLI harness for Chrome DevTools Protocol browser automation.
Connects to user's existing Chrome — inherits cookies/sessions.
No Chrome extension, no npm dependencies, zero browser-side setup.
Part of cdp-agent-kit: github.com/Uname58/cdp-agent-kit
`-e`/`--editable` consumes a path argument in `pip install`, but was not
listed in `PIP_OPTIONS_WITH_VALUES`. As a result `_extract_pypi_package`
returned the editable path (e.g. `.` or `./local/pkg`) instead of the
real package, causing a wasted/incorrect PyPI lookup.
Add `-e` and `--editable` to the option set so the path argument is
skipped, matching how the other value-taking pip options are handled.
Add regression tests for `pip install -e ./local/pkg py4csr` and
`pip install -e .`.
* fix(3mf): make inspect→resize work for non-default axes and concentric holes
`inspect -a 2` could find a cylindrical hole, but `resize` then failed with
"Unknown hole_ids: []"; and even when a hole was found its diameter was wrong
and resize moved 0 vertices. Three root causes:
1. resize_holes() re-ran hole detection with hard-coded defaults and the
resize CLI exposed no detection options, so hole_ids produced by a
non-default `inspect` (e.g. --axis 2, --min-confidence) never lined up.
Thread InspectParams through resize_holes() and mirror inspect's
--planes/--min-diameter/--min-confidence/--axis options on `resize`.
2. _group_circles() clustered by centre only, so a hole's inner circle and
the body's outer contour (both centred on the axis) merged and their radii
averaged into a meaningless diameter. Cluster on (centre, radius).
3. A hole's axial extent came from the inset cross-section planes, so a
through hole's rim vertices fell outside the wall-vertex band and resize
moved nothing. Measure the extent from the actual wall vertices.
Add regression tests for concentric grouping, true inner diameter, through-hole
axial extent, and axis-param parity in resize_holes. All 122 tests pass.
* fix(3mf): bound wall-vertex extent to the detected hole span
Address review: _wall_axial_extent scanned every vertex by radius, so a
coaxial same-radius feature elsewhere along the axis could stretch
axis_min/axis_max past the inspected hole and make resize move unrelated
vertices. Restrict the wall-vertex search to this group's detected span,
extended by one sampling inset (shared backend.PLANE_INSET_FRACTION) and
clamped to the mesh bounds, so through-hole rims are still recovered.
Adds a regression test.
* fix(3mf): reject outer contours so only interior holes are reported
Address review: splitting circle groups by radius surfaced a round body's
outer perimeter (annulus section) as its own high-confidence group, so inspect
listed the part exterior as a resizable hole. Emit a hole only when the mesh has
material radially outside the circle within its axial band -- true for an
interior hole, false for the outer boundary, a boss, or a bare cylinder. The
washer test now asserts the Ø40 outer wall is rejected; adds a solid-cylinder
rejection test.
* fix(3mf): classify holes by enclosure at both axial ends
Address review: the "material outside the radius" check kept a solid boss that
stands on a wider base, because the base rim vertices sit in the boss's axial
band and are farther out than the boss radius. Require body material radially
beyond the circle near BOTH ends of the extent instead: an interior hole is
enclosed at both faces (or, for a blind hole, by its floor), while a boss is
enclosed only at its base. Outer boundaries and bare cylinders remain rejected.
Adds a boss-on-plate rejection test.
* fix(3mf): classify holes by wall-normal orientation (keep blind holes)
Address review: the both-ends enclosure check discarded blind pockets, whose
closed-floor end has no vertices beyond the hole radius. Replace it with a
surface-normal test: a hole's cylindrical wall faces the void so its normals
point toward the axis, while an outer boundary, a bare cylinder, or a solid
boss faces outward. This keeps through AND blind holes and still rejects those
exterior contours, using the mesh's own face normals (no new dependency). If
winding is inconsistent or the wall can't be sampled, keep the candidate rather
than drop a real hole. Adds a blind-hole retention test.
* Fix Python 3.11 compatibility issue in cli_hub/preview.py
* fix(cli-hub): remove backslash from f-string expression for pre-3.12
The prior change swapped the inner double quotes for single quotes, but
pre-3.12 (before PEP 701) prohibits a backslash ANYWHERE in the
expression part of an f-string — the escaped quote left a backslash in
place, so preview.py still raised SyntaxError on Python 3.10/3.11.
Hoist the fallback markup into an empty_message variable so the f-string
expression contains no backslash. This also restores the original
double-quoted HTML (class="artifact-file"); the rendered output is now
byte-for-byte identical to origin/main.
* chore(cli-hub): bump version to 0.4.1
Ships the pre-3.12 f-string fix in preview.py to PyPI. Without a version
bump publish-cli-hub.yml sees 0.4.0 already on PyPI and skips, so users on
Python 3.10/3.11 would keep installing the broken release.
---------
Co-authored-by: yuhao <itsyuhao@icloud.com>
The cli-matrix command family (#355) shipped to the repo but never reached
PyPI: version stayed at 0.3.0, which was already published, so the publish
workflow skipped it. Bump to 0.4.0 in both setup.py and __init__.py to trigger
a fresh release carrying matrix.py / matrix_skill.py.
Also instrument the matrix surface under the existing unified PostHog
mechanism (flat event names, breakdown by properties — same convention as
cli-install / cli-launch):
- matrix-install: install / dry_run / skill_only / error, with scope +
installed/skipped/failed counts (headline conversion signal)
- matrix-preflight: capability-coverage check with covered/capabilities/gaps
- matrix-discover: list / search / can / recipes, with query + result count
- matrix-info: info / doctor inspects of a single matrix
All events honor CLI_HUB_NO_ANALYTICS and the provider switch. 163 tests pass.
* feat: CLI-Matrix with multi-approach stages, skill discovery, and matrix search
Introduce CLI-Matrix — curated multi-CLI workflow matrices that agents can
install in one command. The video-creation matrix bundles 11 CLIs across 8
production stages (AI video gen, capture, audio, voice/TTS, music, NLE
editing, captions, thumbnails).
Each stage now exposes a goal, alternative approaches (Python libs, cloud
APIs, native commands), and skill_search_hints that encourage agents to
dynamically discover relevant skills via `npx skills search` rather than
relying on hard-coded tool lists.
Key changes:
- matrix_registry.json: extended stage schema with goal, alternatives,
skill_search_hints fields
- cli-hub matrix list/search/info/install commands
- matrix_skill.py: renders dynamic SKILL.md with stage tooling overview,
install status, and aggregated discovery commands
- Fixed brittle parents[2] repo root detection with git-based lookup
- 85 tests passing (10 new)
* update cli-matrix
* feat(cli-matrix): eco-first capability-based matrix, v2 schema + S2-S5 SKILLs
- Add docs/cli-matrix/matrix_registry.schema.md describing v2 capability-based
registry shape (capabilities[], providers with kind/requires/cost/quality/
offline, recipes[], known_gaps[], decision rubric, suggest-to-user template).
- Rewrite cli-hub-matrix/video-creation/SKILL.md and matrix_registry.json (S1)
around capabilities + providers + recipes instead of linear stages.
- Rename Vn -> Sn across cli-matrix-plan.md and test fixtures.
- Reorder scenarios by current completeness; rewrite S2 knowledge-research,
S3 3d-cad, S4 game-development, S5 image-design in v2 capability form with
full SKILL.md files.
- Add docs/cli-matrix/test-plans/video-creation.md with 13 long realistic
end-to-end tasks as checkable todo lists, each exercising 5-9 capabilities.
- Move cli-matrix-plan.md and matrix_registry.schema.md under docs/cli-matrix/.
* Document preview protocol and Audacity autosave
Add the preview bundle protocol plan, record video matrix review evidence, and make one-shot Audacity project mutations persist to disk with E2E coverage.
* Update CLI matrix skill registry and video workflow
* chore(git): always ignore docs/* — working documents stay local
* feat(cli-matrix): video-creation skill WIP — sound design, source triage, render doctor, NLE refs, video_doctor script
- SKILL.md: adds sound.design capability, bundled video_doctor.py provider,
recipe updates, and links to five new reference modules
- new references: art-direction-review, nle-shotcut-kdenlive, render-doctor,
sound-design, source-triage; captions and story-structure-audio updated
- scripts/video_doctor.py: bundled probe/diagnose helper
* fix(cli-hub): preflight detects packages by import name or PyPI dist name (P1-3)
_package_available() now tries find_spec as-is, dash->underscore
normalized, then importlib.metadata dist lookup (PEP 503), so registry
entries like edge-tts are detected when installed. All lookup failures
degrade to unavailable instead of crashing preflight. Adds 6 tests.
* feat(cli-hub): distribute matrix skill content to installed skills, wheels, and Pages (P1-4)
- matrix install renders to ~/.cli-hub/matrix/<name>/SKILL.md and copies
references/ and scripts/ beside it (pycache excluded, idempotent
reinstall purges stale files); legacy flat <name>.SKILL.md still read
- content lookup chain: repo checkout -> bundled cli_hub/_matrix_data
(vendored into sdist/wheel by setup.py build hooks + MANIFEST.in) ->
published Pages URL -> stub
- new 'matrix install --skill-only' renders skill + assets without
installing CLIs
- deploy-pages.yml: copy cli-hub-matrix/ into the site after the Jekyll
build (served verbatim at /matrix/<name>/); triggers remain main-only
- 12 new tests in tests/test_matrix_skill_dist.py (142 total pass)
* feat(cli-matrix): register S2-S5 matrices and resync video-creation registry (P1-1, P1-2)
- add knowledge-research (S2, 12 caps), 3d-cad (S3, 12), game-development
(S4, 10), image-design (S5, 9) derived from their SKILL.md drafts; full
v2 shape with capabilities, providers, recipes, known_gaps; clis lists
cross-checked against registry.json (unresolvable tools represented as
public-cli/native/python/api/agent-skill providers instead)
- video-creation: add sound.design capability (5 providers, wired into 5
recipes), register scripts/video_doctor.py as bundled-script provider
under quality.review, cite the 5 new reference modules in provider
notes, refresh description
- python provider package strings use import names (cv2, edge_tts,
ffmpeg, skimage, ...) so preflight detection is robust to dist-name
variants like opencv-python-headless
- fix homepage URLs to docs/cli-matrix/cli-matrix-plan.md; bump
meta.updated to 2026-06-11
* feat(cli-hub): ship the unified Gallery design as the production homepage
Replace docs/hub/index.html with the finalized "Gallery / R2 Flip" main page
(Steel Sky palette default, Newsreader serif hero title, liquid-glass flip
cards, JS masonry catalog, and the unified Matrices layer with bidirectional
stitching). Production-indexable robots meta retained.
Stop tracking docs/cli-matrix/* — the CLI-Matrix working docs stay local and
confidential; add an explicit /docs/cli-matrix/ ignore rule.
* feat: CLI-Matrix command family + Hub docs/demos pages and responsive nav
---------
* test: add regression tests for review blockers
- backup-clean --json actually deletes old backups
- flatten copies motion Sound assets
- save_model uses atomic write (tempfile + os.replace)
- auto_backup deduplicates on content, not wall-clock time
- save_model cleans up temp files on error
* test: use CliRunner in backup-clean regression test
The previous test deleted files manually, which would pass even if the
CLI regressed. Now it invokes 'backup-clean --keep 2 --json' through
CliRunner and asserts both disk state and JSON output.
---------
Co-authored-by: zyzly0705 <zyzly0705@users.noreply.github.com>