@302ai/media-studio-cli
v0.1.0
Published
CLI for AI Media Studio — let external agents drive the core media-studio features
Maintainers
Readme
@302ai/media-studio-cli
CLI for AI Media Studio — drive media-studio chat from any terminal or external agent.
Install
npm install -g @302ai/media-studio-cli
# requires Node.js >= 22No stable release exists yet.
latestcurrently points at a prerelease. Staging- and Local-server visibility is gated on the package version carrying a prerelease tag — not on the dist-tag — so Staging and Local appear inmsc loginand in--regionwhichever way you install. A future stable release will hide them from both channels.
If you installed via bun and get
command not found: bun's global bin dir is not on PATH by default. Run once:echo 'export PATH="$HOME/.bun/bin:$PATH"' >> ~/.zshrc && source ~/.zshrc
Usage
msc login # authenticate in a browser
msc guide # the full agent-facing manual
msc whoami # show current auth status
msc chat "hi" # one chat turn (multi-turn by default)
msc sessions # list sessions from the server
msc skills # list the skill catalog you can reference
msc logout # clear stored credentialsmedia-studio-cli is the long alias for msc — the same program, both
installed. The docs, the help output, and every error message use the short
name:
msc chat "画一只猫" -r # -r/--show-reasoning = 展示模型推理过程
msc chat "hi" --json # machine-readable output
msc chat "hi" --session <id> # 切换到指定会话并设为默认
msc sessions # 列出所有会话(标记当前默认 `(current)`)
msc sessions --json # 机器可读的原始 JSON 数组
msc sessions use <id> # 纯本地切换默认会话(零请求、不校验)
msc skills # 列出技能库(--plaza 浏览技能广场)
msc skills add <slug> # 从广场安装技能(如 brainstorming,见 --plaza --json 的 slug)
msc skills rm <command> # 从技能库移除(-y/--yes 跳过确认)
msc skills enable <command> # 启用 / disable 停用
msc --help # all flagsSession deletion is not available in the CLI. To delete a session, open the corresponding session page in the Media Studio web app and delete it manually.
msc --help carries an agent-oriented quickstart — the auth prerequisite, the
three canonical calls, and the --json contract. msc guide prints the full
manual: every command, both JSON schemas, the nextStep values, exit codes,
stream discipline, and skill semantics. Point an agent at those two instead of
duplicating them here.
Headless environments cannot complete browser sign-in, so msc login refuses
immediately instead of waiting out the callback deadline: CI (CI set), and
Linux with neither DISPLAY nor WAYLAND_DISPLAY. Sign in from a machine
whose browser can reach the loopback callback.
Multi-turn conversation
chat is multi-turn by default — no flag needed. The CLI keeps a single
lastSessionId pointer in config.json (under the config dir):
- First-ever chat provisions a session via the same prewarm flow the web
app uses (source
cli), printing stage progress (creating / sandbox / skills / finishing), then chats. The pointer is written the moment the session is durably persisted upstream — an interrupted provision never loses a live session. - Later chats continue that session — upstream remembers the conversation by session id, so no history is resent.
- If the session was deleted elsewhere, the CLI transparently provisions a fresh one and continues — you are never stranded.
--session <id>switches to a specific session (including web-created ones) and makes it the default, without provisioning.
Manual proof of continuity:
msc chat "记住数字42"
msc chat "刚才的数字是几?" # answers 42Point at a different server via msc login --region default|cn|local (the
server you log into is saved and reused; local — like staging — is a
prerelease-only region, see the note at the top).
Configuration and storage
The CLI stores credentials and local settings in:
- Default:
~/.config/media-studio-cli/ - XDG standard:
$XDG_CONFIG_HOME/media-studio-cli/ - Override:
MEDIA_STUDIO_CONFIG_DIR=/path/to/dir
Key files:
auth.json: stores active platform credentials and user session metadata (permissions0600)config.json: stores CLI client settings, includinglastSessionIdpointer
Skills
Discover what your account can reference, then reference it — the reference must name a skill that exists in your catalog:
msc skills # list your library
msc skills --json # raw catalog array (see fields below)
msc skills --plaza # browse the Skill Plaza with your library
# state merged in (built-ins excluded)
msc skills --plaza --json
msc skills add <slug> # install a plaza entry by slug
msc skills rm <command> # remove a library skill by command
msc skills enable <command> # flip a library skill's enabled flag
msc skills disable <command>add takes the entry's plaza slug (brainstorming, shown in
msc skills --plaza --json as slug); the library mutations take the
command string (brainstorming and /brainstorming are the same entry) —
the same string the prompt uses to reference the skill.
--json is the intended output for agents: the catalog is the input to a
decision the calling program makes itself. Both descriptionEn/descriptionZh
are passed through untouched — upstream does not guarantee the En slot is
English, so the CLI picks nothing. The human rendering lists names only (the
command string is available in --json) with the full descriptionEn on a
second line, and marks [on] enabled / [off] disabled / [-] not installed
in the plaza view.
Shared API endpoints: mutations use the exact same endpoints as the web app
(POST /api/skills for installs, DELETE /api/skills?skillId= for removal,
POST /api/skills/enabled for enable/disable). The CLI resolves the slug/command
to the entry/id before calling the endpoint. rm requires --yes outside a
terminal. Built-in entries are rejected (422): they sync into your library from
the backend. Managing skills (upload, cover) stays in the Media Studio web app.
Then reference a skill in the prompt and the CLI installs it into the session sandbox on demand:
msc chat "/always-hello 不管我说什么" # leading slash command
msc chat "%%/always-hello%%不管我说什么" # web composer marker, paste-compatibleBoth spellings resolve to the same thing. The upstream installs a skill only
from the turn's skill_urls field, so the CLI looks the reference up in your
catalog (GET /api/skills) and sends the matching package URL — prompt text
alone triggers nothing.
- A bare
/commandcounts only at the start of the prompt, so mid-sentence slashes stay prose (check /etc/hostsis not a skill reference). The%%…%%marker is also recognized anywhere — a web prompt pasted verbatim keeps its token — but the skill activates upstream only when the marker leads the message, so lead the prompt with it. The leading token can be any bare slug, single-segment included —/tmp what is thisresolves/tmp— and a reference that matches nothing fails the turn with the catalog listed, so a typo never silently degrades to a skill-less answer. - The catalog is fetched only when the prompt references a skill; skill-free turns cost no extra request.
- Unknown skills fail the turn with a non-zero exit before any session is
provisioned. A disabled skill is auto-enabled first — the same ADR-0001
behavior as the web's "Use in Chat" — because the upstream treats
enabledas a hard precondition for invocation and would otherwise download the package and silently never run it. The enable flips the skill'senabledflag on your account (a persistent change), is reported asAuto-enabled for this turn: …, and a failed enable aborts the turn.
Attachments
Attach local files to a chat turn using @path anywhere in the prompt:
msc chat "看看 @cover.png 这张图" # bare path (ends at whitespace)
msc chat "总结 @/abs/path/notes.pdf" # absolute path
msc chat '对比 @"my docs/report.docx" 内容' # quoted path with spacesThe CLI mirrors the web composer's upload flow:
- Validates each referenced file locally (exists, readable, supported extension, under 50 MB) before any network call.
- Uploads each file via the web app's
POST /api/chat/attachmentsendpoint with a terminal spinner (Uploading attachment 1/2: cover.png). - Rewrites the prompt to inline the remote URL using the web's
&&<url>&&marker, which the upstream model and web session history natively understand. - Up to 5 unique attachments per turn (same as the web composer); referencing the same file twice uploads it once.
- An unattachable reference (missing file, unsupported format) fails the turn before any session is provisioned.
- Prose mentions (
@team 辛苦了, email addresses) stay literal text — only tokens with a file extension or path slash are treated as attachments. @pathis local-filesystem-only:@https://example.com/a.pngfails withno such file. There is no@urlform — remote URLs are not attachments.
In --json mode the uploaded attachments are surfaced under the attachments array:
[{ name, type, size, remoteUrl }].
Staging server (expired TLS certificate)
The staging server's certificate is expired (ops declines renewal), so the
CLI skips TLS certificate verification only while pointed at the staging
host; the server choice itself is the opt-in, and the default / CN hosts
always verify. The interactive msc login region list shows each server's
URL and marks Staging with the auto-off behavior; msc login --region
staging opts in.
Stable (production) builds hide Staging and Local from the msc login region
menu and --region, but a persisted auth.json from a prerelease login keeps
pointing at that server — and a staging one still auto-bypasses TLS
verification. This is intentional (the login persists across upgrades), not an
oversight.
The whitelist is the only way the bypass turns on — no environment variable can extend it to another server, so production can never skip verification. To force verification back on while pointed at staging (once ops renews the certificate, or to debug its chain):
MEDIA_STUDIO_INSECURE_TLS=0 msc chat "hi" # also accepts "false"Plain-http:// servers skip the bypass entirely (nothing to verify). The
bypass prints nothing: the staging choice is already stated in the msc login
menu and here, so no warning repeats on every command (--help and --version
included, whose output scripts and agents parse).
TLS-intercepting proxies
If browser SSO login works but every API call fails with fetch failed, a
TLS-intercepting proxy (Clash/FlClash TUN mode, corporate MITM) is breaking
certificate verification (unable to verify the first certificate).
Node/Bun do not use the OS keychain, so a proxy CA trusted by your browser
is not trusted by the CLI. Either trust the proxy CA explicitly:
export NODE_EXTRA_CA_CERTS=/path/to/proxy-ca.crtor skip verification for the CLI only (no certificate checks). The staging host
already does this automatically, so this is only needed for other servers — and
it is now the only way to bypass a non-staging host, since no msc flag or env
var does so. Note it is process-global: every host the CLI talks to stops being
verified.
NODE_TLS_REJECT_UNAUTHORIZED=0 msc sessionsDevelopment
bun run dev # run from TS source (bun), no build step
bun run build # bundled JS entry -> dist/ (the npm artifact)
bun run build:binary # standalone single-file binary -> bin/Testing (before shipping any change)
Test layers from fast to faithful. Minimum bar: layer 2 passes; before an actual publish also run layer 4.
1. Dev iteration (fastest)
bun run dev -- chat "hi" # runs TS source directly, no packaging2. Automated external-consumer check (required)
# from repo root
bun run package:cli:checkBuilds, type-checks, packs the real tarball, installs it into a temp
directory outside the repo, and executes the installed binaries
(--version / --help assertions). Catches packaging-only breakage
(missing shebang, lost exec bit, stale bin target) that in-repo runs never see.
3. Manual user-install simulation
cd packages/media-studio-cli
bun pm pack --destination /tmp/cli-test
# option A: clean local consumer
mkdir /tmp/cli-consumer && cd /tmp/cli-consumer
bun add /tmp/cli-test/302ai-media-studio-cli-0.1.0.tgz
./node_modules/.bin/msc --version
# option B: global install (also verifies the short name doesn't collide)
bun add -g /tmp/cli-test/302ai-media-studio-cli-0.1.0.tgz
msc --version
# cleanup afterwards — one command removes BOTH bin names
bun remove -g @302ai/media-studio-cli4. End-to-end against a real server
# terminal 1: local gateway (CLI defaults to localhost:3000)
bun run dev
# terminal 2:
msc login # browser sign-in
msc whoami # verify auth state
msc chat "画一只猫" -r # real streaming turn
msc chat "hi" --json
# Multi-turn continuity acceptance recipe:
msc chat "记住数字42"
msc chat "刚才的数字是几?" # answers 42
# Session management verification:
msc sessions # shows session list with (current) marker
msc chat "hi" --session <id> # switch to specific session
msc sessions use <id> # switch default pointer locallyWithout the local server, point at production with
msc login --region default.
Notes learned the hard way
bun pm packdoes NOT run publish lifecycle scripts; the smoke script builds explicitly first. Don't assume pack produces a fresh dist.- Two
binentries pointing at the SAME file makebun pm packemit duplicate tarball entries — that's whymsctargets the tinydist/msc.jsshim instead ofdist/index.js. Keep bin targets unique. bun publishrequires a modern bun (lifecycle scripts); the repo standard publish gate isbun run package:cli:checkfirst.
License
Apache-2.0
