# Lingji Cut is a desktop pipeline whose CLI cannot run without the desktop

> An Apache-2.0 Electron workbench for turning source material into a finished vertical video, with AI writing, TTS, subtitles, generated cards, a Remotion export and publishing to five Chinese platforms. The interesting design decisions are all in the plumbing: the CLI talks to the running app, the build obfuscates itself, and the installer mirrors come from China by default.

**yoqu/lingji-cut** — Open-source video creation tool

- Repository: https://github.com/yoqu/lingji-cut
- Stars: 412 · Forks: 89
- Language: TypeScript
- License: Apache-2.0
- Published: 2026-09-20 · Updated: 2026-09-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/yoqu-lingji-cut

## The lingji command is a client of the running desktop, not a replacement for it

The headless CLI is the part of Lingji Cut most likely to be misread as a standalone tool. It is not. It communicates with the desktop application over an MCP service address, so the app has to be running, or the address has to be supplied with the --server flag.

Its subcommands are named after pipeline stages, and the asynchronous ones block on a flag rather than returning a handle:

```bash
lingji project current                    # 显示应用当前活动项目
lingji project list                       # 列出最近项目
lingji audio gen [--project <p>] --wait   # 生成口播音频 (TTS)
lingji subtitle analyze --wait            # 字幕分析 + 卡片生成
lingji cards list|show|update|regenerate|regen-media|convert|delete
lingji cover prompt|image|gen --wait      # 封面提示词 / 出图 / 一次性
lingji export [--out <file>] --wait       # 导出 MP4
lingji task status|list|cancel|wait <id>  # 任务查询与控制
```

Two global switches exist: --json for machine-readable output and --server <url> to override the MCP service address. The task subcommand is the escape hatch, since it can query, list, cancel or wait on a job id.

The practical consequence for automation is that you are scripting a running Electron window, not a daemon.

## The global command is lingji while the linked package is lingjijianying

Installation runs a build and then a link:

```bash
npm run install:cli
```

That script is defined as npm run build:cli && npm link, where build:cli bundles cli/src/index.ts into dist-cli/lingji.mjs with esbuild. Because it is a link rather than a published install, the global entry is a symlink into this checkout.

The naming is where confusion starts. The package is called lingjijianying and the productName is 灵机剪影, while the bin entry is named lingji. So the command you type is lingji and the package npm unlinks is lingjijianying, which is exactly what the uninstall script does:

```bash
npm run uninstall:cli
```

Removal is npm unlink -g lingjijianying. Trying to unlink lingji will not find anything.

Two further details matter day to day. The link lives under the current Node version's global bin, so switching Node versions with nvm means running npm run install:cli again. And because it is a symlink, editing the CLI source only needs npm run build:cli, with no second link step.

## The production build obfuscates its own output

The build script is not a plain bundle. It is three chained steps:

```bash
npm run build
```

which resolves to a clean step that removes dist and dist-electron, then electron-vite build, then node scripts/obfuscate-build.cjs. The obfuscation stage is why javascript-obfuscator appears as a pinned dependency, and it runs after the bundler, so what lands in dist-electron is deliberately hard to read.

That is a deliberate choice for a product with paying users rather than a library, and it does have a cost for anyone auditing or debugging a build. Reading the shipped main, preload and renderer output is no longer the fastest way to understand a behaviour, so the source has to be the reference.

The dev path is unaffected. npm run dev runs an Electron binary pre-check first and then the Windows UTF-8 helper, with no obfuscation in the chain, which keeps the edit-reload loop readable.

## Only the Windows packaging path pre-checks the Electron binary

There is a script called ensure:electron that runs node scripts/ensure-electron-binary.cjs, and its placement is inconsistent across the three entry points that could reasonably use it.

The Windows distribution chain includes it. So does the development command, which pairs it with dev-windows-utf8.cjs. The macOS chain does not:

- dist:win runs npm run ensure:electron first, then build, build:cli, bundle:remotion and package:win.
- dist:mac starts directly at npm run build and then runs build:cli, bundle:remotion and package:mac.

So a macOS packager is the one path that reaches the bundler without the Electron binary being verified first. Both chains do bundle Remotion, which is the step that gathers Chrome Headless Shell and ffmpeg, and that is the heaviest download in either path.

A dmg script also exists, as dmg:mac and dist:dmg:mac, which chain make-dmg-mac.cjs after the app package. More on that below, because the README and the scripts disagree about it.

## The project .npmrc sends every install to npmmirror, and npm 11 complains about it

The repository ships a project-level .npmrc that points npm, Electron and Node native module downloads at the npmmirror mirrors, described as being for domestic network conditions. It is not a per-user preference; it is committed, so it applies to everyone who clones the project.

The file uses the electron_mirror key, and npm 11 emits an Unknown project config "electron_mirror" warning for it. The README is direct that this warning usually does not mean the install failed, which is an admission that the config key is ahead of the npm version rather than an oversight in the mirror URL.

If the Electron download is being ignored by a local npm configuration, the documented override is two environment variables:

```bash
export ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/"
export npm_config_disturl="https://npmmirror.com/mirrors/node/"
npm install
```

A PowerShell equivalent is given for Windows, setting the same two values through $env. In both cases the point is to restate the mirror explicitly at the point of use, which is the shape a global user-level configuration would normally take.

## The macOS build is an unsigned local .app, while a dmg script sits in package.json

The packaging section is short and unusually candid. Output lands under release/ as 灵机剪影-darwin-arm64 and 灵机剪影-darwin-x64, each holding a 灵机剪影.app, and the same paragraph states that the current packaged output is a local .app with no formal signing, no notarization and no DMG or PKG distribution wired up.

That sentence has a direct consequence on any machine but the developer's. An unsigned, unnotarized application bundle is quarantined by macOS on first launch, so whoever receives it has to clear Gatekeeper by hand or build it themselves.

It also sits awkwardly beside package.json, which defines dmg:mac as node scripts/make-dmg-mac.cjs and dist:dmg:mac as npm run dist:mac && npm run dmg:mac. A DMG path exists in the scripts and does not exist in the distribution story, and nothing in the visible text says whether the unsigned .app is produced by that path or whether signing was simply deferred.

If you need something you can hand to a non-developer, that gap is the first thing to close.

## Project state is a folder of plain files, and older layouts migrate on load

Nothing lives in a database. The application writes into a directory the user picks, and the layout is documented file by file: project.json holding the timeline, aiAnalysis and script sections, original.md for raw material or a transcript, script.md for the spoken script, podcast-audio.mp3 for generated audio, podcast-subtitles.srt for subtitles, podcast-subtitles.original.srt as the initial TTS subtitle backup, covers/ for cover candidates, ai-cards/ for generated visual cards, imports/ for anything brought in from outside, and configs/prompts/ for project-level prompt overrides.

Older layouts still open. timeline.json, ai-analysis.json and script-state.json from previous versions are migrated into project.json when an old project is loaded, so an archive from an earlier release is still readable.

The companion claim is that the repository never needs to hold a real API key. Credentials are configured in the application settings screen instead, and the .env.example reflects that: it contains MAIN_VITE_DEBUG_MODE=false and MAIN_VITE_LOG_LEVEL=info, and nothing else. The warning is explicit that API keys, session IDs, cookies and access tokens should stay out of source, tests, docs and screenshots.

## Publishing holds five platforms' login state on the same local disk

The publishing tab covers more than an export button. It carries a multi-aspect cover workbench at 16:9, 4:3 and 3:4, metadata for title, description and tags, an AI recommendation for Bilibili categories, and destinations across Bilibili, WeChat Channels, Douyin, Kuaishou and Xiaohongshu.

Underneath all of that sits an account section that manages the login state for those same five platforms. That is the part worth understanding before installing it: a tool that publishes on your behalf has to hold the credentials that let it act on your behalf, and here they live on the same local disk as the project files.

That arrangement is consistent with the local-first design and with the instruction not to commit cookies or tokens, but it means the disk holding your project folder is also the disk holding sessions for five accounts.

The other AI-facing surface points the same way. An MCP server named lingji-editor exposes lingji_* tools to Claude Code, Codex and Gemini over a file-first contract, so an external agent edits the project files directly rather than driving the interface.

## Conclusion

This fits someone producing spoken vertical video on a schedule who wants one application to hold the script, the audio, the subtitles, the cards and the export, and who is willing to run an Electron app plus a Chrome extension. It does not fit a headless or server environment, because the command line tool is a client of the running desktop rather than a replacement for it. Two things to check before committing. First, the packaged macOS build is an unsigned local .app with no notarization and no DMG, so Gatekeeper will get in the way on someone else's machine. Second, the project-level .npmrc redirects every download to npmmirror, which needs overriding outside China. The last push is dated 2026-08-19, so check that before assuming the release tags track the code.

## FAQ

### Does the Lingji Cut CLI need the desktop app running?

Yes. The headless lingji command talks to the desktop application over an MCP service address, so the app must be running, or an address has to be passed with --server. Subcommands such as audio gen, subtitle analyze, cover gen and export take a --wait flag to block on the task.

### How do I install and remove the Lingji Cut CLI?

npm run install:cli builds the CLI into dist-cli/lingji.mjs with esbuild and then runs npm link. Removal is npm run uninstall:cli, which runs npm unlink -g lingjijianying, so the package name to remove is lingjijianying even though the command is lingji.

### What export formats and engine does Lingji Cut use?

Export goes through Remotion 4, which bundles Chrome Headless Shell and ffmpeg, and produces H.264 MP4. The editor previews through the same compiled artefact that the export uses, with timeline seeking and export progress shown in the app.

### Which AI providers can Lingji Cut be configured with?

LLM providers include OpenAI-compatible endpoints, Gemini and LM Studio. Image generation covers Jimeng, OpenAI Image, MiniMax, Doubao, Imagen, Tongyi Wanxiang and custom providers, video generation has its own provider list, and TTS supports MiniMax and Xiaomi MiMo including cloned voices. Different prompt kinds can be bound to different providers, with card.image and card.video named as examples.

### Where does Lingji Cut store credentials and project data?

AI provider credentials are configured in the application settings screen rather than in a repository .env, and the .env.example holds only MAIN_VITE_DEBUG_MODE and MAIN_VITE_LOG_LEVEL. Project data is written as plain files into a directory the user chooses, including project.json, script.md and an ai-cards/ folder.

### Is the Lingji Cut macOS build signed and notarized?

No. The packaged output is a local .app under release/ for darwin-arm64 and darwin-x64, with no formal signing, notarization, or DMG and PKG distribution in place. A dmg:mac script exists in package.json even though distribution is described as not yet wired up.

## Sources

- [Issues](https://github.com/yoqu/lingji-cut/issues)
- [License: Apache-2.0](https://github.com/yoqu/lingji-cut/blob/main/LICENSE)
- [README](https://github.com/yoqu/lingji-cut/blob/main/README.md)
- [Releases](https://github.com/yoqu/lingji-cut/releases)
- [yoqu/lingji-cut on GitHub](https://github.com/yoqu/lingji-cut)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/yoqu-lingji-cut
