npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-extension

Commands

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=research

CLI 通过 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 editor

space 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.pidserver.log;重复启动是幂等的。日常管理使用:

duobox server status
duobox server restart
duobox server stop

duobox 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 build

duobox 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>
  --force

The 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.json
  • package.json
  • main/index.ts

Optional files:

  • renderer/index.tsx
  • README.md
  • assets/**
  • 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/feishu

Validation requires:

  • manifest.json exists
  • package.json exists
  • all manifest fields conform to @duobox/extension-kit/manifest
  • paths stay inside the extension package
  • removed fields such as permissions and agentPlugin are rejected
  • package.json.version is a non-empty string

Successful output looks like:

Validated [email protected]

duobox extension build

Builds an extension into dist/.

duobox extension build

The build step:

  • removes the previous dist/
  • bundles main/index.ts to dist/main/index.js
  • bundles renderer/index.tsx to dist/renderer/index.js when present
  • extracts renderer slot contributions to dist/renderer/ui.json
  • builds manifest.agent.plugin when configured
  • builds any product packages under products/* that contain a package.json
  • creates a versioned zip archive under dist/
  • computes a sha256 for the archive
  • writes dist/duobox-extension.json with 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 pack

The output archive includes:

  • release manifest.json
  • package.json
  • README.md when present
  • assets/**
  • built products/* files
  • built dist/** files

Archive naming:

dist/<extension-id>-<package-version>.zip

GitHub Release descriptor

build and pack produce two assets for a GitHub Release:

dist/<extension-id>-<version>.zip
dist/duobox-extension.json

The 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/feishu

Extension 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.