@duobox/cli
v0.2.4
Published
CLI for Duobox Remote Space administration and extension development.
Readme
@duobox/cli
CLI for Duobox Remote Space administration and extension development.
The npm package is scoped as @duobox/cli, but the installed command is duobox.
pnpm dlx @duobox/cli extension init my-extensionCommands
duobox login [--federation <url>] [--provider <id>]
duobox users
duobox users use <user-id>
duobox logout [user-id]
duobox server init
duobox server start [--foreground]
duobox server stop [--force]
duobox server restart [--force]
duobox server status
duobox server organization list
duobox server organization link <organization-id-or-key>
duobox server organization access list <organization-id>
duobox server organization access set <organization-id> <user-id> --role editor|viewer
duobox server organization access remove <organization-id> <user-id>
duobox space create [directory] [--organization <id-or-key>] [--name <name>]
duobox space list [--organization <id-or-key>]
duobox space clone <space-id> [directory] [--organization <id-or-key>]
duobox space settings [directory]
duobox extension init [directory] [options]
duobox extension validate [directory]
duobox extension build [directory]
duobox extension pack [directory][directory] defaults to the current working directory.
Remote Space
Remote Space 沿用 Duobox 的 Downcity Federation 登录。App 与 CLI 共用 ~/.duobox/app.db 中的账户和当前用户。Token 直接保存在权限为 0600 的数据库中,不创建第二套 CLI credential。
升级自旧版 Electron safeStorage 账户结构时,旧账户会被清空而不会迁移 Token;用户需要重新登录一次。
duobox login
duobox users
duobox users use <user-id>
export DUOBOX_ORGANIZATION=researchCLI 通过 Federation organizations/my 发现 Organization 的 server_url。当当前账户只有一个 active Organization 时可以省略 --organization;有多个时使用 Organization ID 或 key 选择:
duobox space list --organization "$DUOBOX_ORGANIZATION"
duobox space clone space_... ./my-space --organization "$DUOBOX_ORGANIZATION"Organization Membership 在 Federation 中管理。Owner 和 Admin 自动拥有 Duobox manager;Member 默认是 viewer,可以由服务器管理员授予独立的 editor ACL:
duobox server organization access set <organization-id> <user-id> --role editorspace create 会初始化或复用当前 Git repository,维护 Duobox 的 .gitignore 区块,解除 .downcity/ 与 .duobox/ 的 Git 跟踪但保留本地文件,拒绝同步已被 Git 跟踪的 .env 文件,创建 checkpoint commit,然后把 main 推送到 Remote Space。连接信息保存在 Git remote 中,不创建 .duobox/space.json。
Phase 1 允许普通图片进入 Git,但拒绝超过 20 MiB 的单文件;视频和大型附件需要等待后续 Git LFS 支持。
Local Server
本机首次启动时运行交互式初始化:
duobox server init
duobox server start配置默认写入 ~/.duobox/server.env,文件权限为 0600。初始化过程会询问 Federation URL、Bureau Token、City ID、监听地址、公开地址和数据目录;Bureau Token 不回显。运行时显式设置的同名环境变量优先于配置文件。
server start 在后台启动并等待健康检查通过,不占用当前终端。PID 和日志分别保存在数据目录的 server.pid、server.log;重复启动是幂等的。日常管理使用:
duobox server status
duobox server restart
duobox server stopduobox server start --foreground 使用前台运行模式,适合开发调试,或交给 systemd、Docker、Dokploy 管理。进程无法通过健康检查确认时,stop 会拒绝杀死 PID;确认状态文件可信后可使用 stop --force。
Server 管理命令使用同一份配置:
duobox server organization list
duobox server organization link <organization-id-or-key>
duobox server organization access list <organization-id>
duobox server organization access set <organization-id> <user-id> --role editor
duobox server organization access remove <organization-id> <user-id>organization link 使用当前 Federation 用户调用 organizations/server/update。当前用户必须属于 Server 配置的同一 Federation 和 City,并拥有更新 Organization Server URL 的权限。
详细配置见 @duobox/server README。
Quick start
pnpm dlx @duobox/cli extension init my-extension \
--id com.example.my-extension \
--name "My Extension"
cd my-extension
pnpm install
pnpm run validate
pnpm run typecheck
pnpm run buildduobox extension init
Creates a runnable extension project with a manifest, main action, renderer UI, TypeScript config, icon, README, and local CLI scripts.
duobox extension init [directory]
--id <reverse-domain-id>
--name <display-name>
--description <description>
--forceThe directory defaults to the current directory. A non-empty directory is
rejected unless --force is passed. --force overwrites template-owned files
but does not delete unrelated files.
Expected extension layout
my-extension/
manifest.json
package.json
README.md
tsconfig.json
main/
index.ts
renderer/
index.tsx
assets/
products/Required files:
manifest.jsonpackage.jsonmain/index.ts
Optional files:
renderer/index.tsxREADME.mdassets/**products/*
The generated manifest.json references the public JSON Schema shipped by the
SDK:
{
"$schema": "./node_modules/@duobox/extension-kit/manifest.schema.json"
}duobox extension validate
Checks the package shape and the same manifest contract used by the Duobox app.
duobox extension validate
duobox extension validate ./extensions/feishuValidation requires:
manifest.jsonexistspackage.jsonexists- all manifest fields conform to
@duobox/extension-kit/manifest - paths stay inside the extension package
- removed fields such as
permissionsandagentPluginare rejected package.json.versionis a non-empty string
Successful output looks like:
Validated [email protected]duobox extension build
Builds an extension into dist/.
duobox extension buildThe build step:
- removes the previous
dist/ - bundles
main/index.tstodist/main/index.js - bundles
renderer/index.tsxtodist/renderer/index.jswhen present - extracts renderer slot contributions to
dist/renderer/ui.json - builds
manifest.agent.pluginwhen configured - builds any product packages under
products/*that contain apackage.json - creates a versioned zip archive under
dist/ - computes a sha256 for the archive
- writes
dist/duobox-extension.jsonwith the GitHub Release descriptor contract
The renderer build treats React as a Duobox runtime import, so extension renderer bundles do not vendor React.
duobox extension pack
pack currently runs the same build pipeline as build.
duobox extension packThe output archive includes:
- release
manifest.json package.jsonREADME.mdwhen presentassets/**- built
products/*files - built
dist/**files
Archive naming:
dist/<extension-id>-<package-version>.zipGitHub Release descriptor
build and pack produce two assets for a GitHub Release:
dist/<extension-id>-<version>.zip
dist/duobox-extension.jsonThe descriptor includes the full packaged manifest snapshot and archive sha256/size. Upload both files to a public GitHub Release using the exact archive name. Duobox App Main resolves the public repository and latest stable Release directly; the CLI does not upload to Pages, R2, or the official catalog.
Renderer UI extraction
When renderer/index.tsx exists, the CLI imports the renderer in a build-time collector and executes the default renderer function:
import { defineRenderer } from "@duobox/extension-kit/react";
import { PublishDialog } from "./PublishDialog";
export default defineRenderer(function render({ ui }) {
ui.doc.menu.export({ title: "Publish" }).dialog(PublishDialog);
ui.command.palette({ title: "Publish" }).run("feishu.publish");
});That produces dist/renderer/ui.json:
{
"contributions": [
{
"where": "doc.menu.export",
"how": "dialog",
"id": "publish",
"title": "Publish",
"component": "PublishDialog"
},
{
"where": "command.palette",
"how": "run",
"id": "publish",
"title": "Publish",
"action": "feishu.publish"
}
]
}Components passed to slots must be exported from the renderer entry module so the CLI can serialize them by export name.
Manifest handling
The CLI reads manifest.json and package.json.
package.json.version is the release version. During packing, the CLI writes a release manifest whose entries point at built files:
{
"main": "./dist/main/index.js",
"renderer": "./dist/renderer/index.js"
}Do not rely on a version field inside manifest.json; use package.json.version.
Local workspace development
Inside the Duobox monorepo, official extensions can depend on workspace packages:
{
"devDependencies": {
"@duobox/extension-kit": "workspace:*",
"@duobox/cli": "workspace:*"
}
}Extension scripts can still call the installed bin name:
{
"scripts": {
"validate": "duobox extension validate",
"build": "duobox extension build",
"pack": "duobox extension pack"
}
}Troubleshooting
manifest.json not found
Run the command from the extension root or pass the directory explicitly:
duobox extension validate ./extensions/feishuExtension renderer must export default render function
renderer/index.tsx must default-export a function created with defineRenderer.
component must be exported from renderer entry
Export the component from renderer/index.tsx or pass a supported string component reference.
duobox-extension.json is missing
Run duobox extension build or duobox extension pack from the extension root and upload the generated descriptor together with the archive.
