openclawmp 0.1.6 → 1.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,176 +4,194 @@
4
4
 
5
5
  A command-line client for the [OpenClaw Marketplace](https://openclawmp.cc), allowing you to search, install, publish, and manage agent assets (skills, plugins, triggers, channels, and more).
6
6
 
7
- ## Installation
7
+ ## 安装
8
8
 
9
9
  ```bash
10
10
  npm install -g openclawmp
11
11
  ```
12
12
 
13
- Requires **Node.js 18+** (uses built-in `fetch`).
13
+ 需要 Node.js `>=18.0.0`。项目使用 Node 内置 `fetch`,无额外运行时依赖。
14
14
 
15
- ## Quick Start
15
+ ## 快速开始
16
16
 
17
17
  ```bash
18
- # Search for assets
19
- openclawmp search "web search"
18
+ # 搜索市场资产
19
+ openclawmp search 天气
20
20
 
21
- # Install a skill
22
- openclawmp install skill/@cybernova/web-search
21
+ # 查看资产详情
22
+ openclawmp info 7c19dc4c3244418096f1dcb59c93f795
23
23
 
24
- # List installed assets
24
+ # 安装资产
25
+ openclawmp install skill/7c19dc4c3244418096f1dcb59c93f795
26
+
27
+ # 安装指定版本
28
+ openclawmp install skill/7c19dc4c3244418096f1dcb59c93f795@1.0.3
29
+
30
+ # 列出本地已安装资产
25
31
  openclawmp list
26
32
 
27
- # View asset details
28
- openclawmp info skill/web-search
33
+ # 卸载本地资产
34
+ openclawmp uninstall skill/demo-skill
29
35
 
30
- # Publish your own skill
31
- openclawmp publish ./my-skill
36
+ # 发布本地资产
37
+ openclawmp publish ./my-skill --type skill --version 1.0.0 --yes
32
38
  ```
33
39
 
34
- ## Commands
40
+ ## 功能概览
35
41
 
36
- ### `openclawmp search <query>`
42
+ - `search`:搜索市场资产,按类型、作者、安装量、标签和简介格式化输出。
43
+ - `info`:查看资产详情与版本信息。
44
+ - `install`:根据 `assetId` 和可选 `semver` 下载归档并安装到本地 OpenClaw 目录。
45
+ - `uninstall`:删除本地安装目录,并清理对应锁文件记录。
46
+ - `list`:扫描本地已安装资产,结合 lockfile 和安装元数据展示结果。
47
+ - `publish`:打包本地目录并上传发布,串联 `createUpload`、`signUploadPartUrl`、`completeUpload`、`createAssetSemver`。
37
48
 
38
- Search the marketplace for assets.
49
+ ## 支持的资产类型
39
50
 
40
- ```bash
41
- openclawmp search "文件监控"
42
- openclawmp search weather
43
- ```
51
+ - `skill`
52
+ - `experience`
53
+ - `plugin`
54
+ - `trigger`
55
+ - `channel`
44
56
 
45
- ### `openclawmp install <type>/@<author>/<slug>`
57
+ ## 运行要求
46
58
 
47
- Install an asset from the marketplace.
59
+ - Node.js `>=18.0.0`
60
+ - 零运行时依赖
48
61
 
49
- ```bash
50
- # Full format with author scope
51
- openclawmp install trigger/@xiaoyue/fs-event-trigger
52
- openclawmp install skill/@cybernova/web-search
62
+ ## 凭证
63
+
64
+ 默认读取:
53
65
 
54
- # Legacy format (no author)
55
- openclawmp install skill/web-search
66
+ - macOS / Linux: `~/.openclaw/hub-credentials.json`
67
+ - Windows: `%USERPROFILE%\\.openclaw\\hub-credentials.json`
56
68
 
57
- # Force overwrite existing
58
- openclawmp install skill/@cybernova/web-search --force
69
+ 文件最少需要:
70
+
71
+ ```json
72
+ {
73
+ "token": "your-credential-token"
74
+ }
59
75
  ```
60
76
 
61
- **Supported asset types:** `skill`, `config`, `plugin`, `trigger`, `channel`, `template`
77
+ 请求头约定:
62
78
 
63
- ### `openclawmp list`
79
+ - `Authorization: Bearer <credential token>`:受保护接口必需;`search` 可匿名调用
80
+ - `X-Request-ID`:自动生成,也可通过 `--request-id` 显式指定
81
+ - `Cli-Version`:自动带当前 package 版本
64
82
 
65
- List all assets installed via the marketplace.
83
+ ## 常用示例
66
84
 
67
85
  ```bash
68
- openclawmp list
86
+ npm install -g openclawmp
87
+ openclawmp publish /path/to/asset
69
88
  ```
70
89
 
71
- ### `openclawmp uninstall <type>/<slug>`
72
-
73
- Remove an installed asset.
90
+ 示例:
74
91
 
75
92
  ```bash
76
- openclawmp uninstall skill/web-search
77
- openclawmp uninstall trigger/fs-event-trigger
93
+ openclawmp publish ./my-skill \
94
+ --type skill \
95
+ --category productivity \
96
+ --tags agent,automation \
97
+ --version 1.0.0 \
98
+ --yes
78
99
  ```
79
100
 
80
- ### `openclawmp info <type>/<slug>`
81
-
82
- View detailed information about an asset from the registry.
101
+ 更新已有资产版本:
83
102
 
84
103
  ```bash
85
- openclawmp info skill/web-search
86
- openclawmp info trigger/fs-event-trigger
104
+ openclawmp publish ./my-skill --version 1.0.1 --yes
87
105
  ```
88
106
 
89
- ### `openclawmp publish [path]`
107
+ 如果目录根部已存在 `.assetid`,会自动把该值透传为 `assetId`;也可显式传入 `--asset-id <id>`。
90
108
 
91
- Publish a local asset directory to the marketplace. Defaults to current directory.
109
+ 只做本地校验和打包:
92
110
 
93
111
  ```bash
94
- # Publish current directory
95
- openclawmp publish
96
-
97
- # Publish a specific directory
98
- openclawmp publish ./my-skill
99
-
100
- # Skip confirmation prompt
101
- openclawmp publish ./my-skill --yes
112
+ openclawmp publish ./my-skill --dry-run
102
113
  ```
103
114
 
104
- The command will auto-detect the asset type from:
105
- 1. `SKILL.md` frontmatter (for skills)
106
- 2. `openclaw.plugin.json` (for plugins/channels)
107
- 3. `package.json` (fallback)
108
- 4. `README.md` (fallback)
109
-
110
- ### `openclawmp login`
111
-
112
- Show device authorization information. Your OpenClaw device identity is used for publishing.
115
+ 安装示例:
113
116
 
114
117
  ```bash
115
- openclawmp login
118
+ openclawmp install skill/7c19dc4c3244418096f1dcb59c93f795
119
+ openclawmp install skill/7c19dc4c3244418096f1dcb59c93f795@1.0.3
116
120
  ```
117
121
 
118
- ### `openclawmp whoami`
119
-
120
- Show current user/device info and configuration status.
122
+ 卸载示例:
121
123
 
122
124
  ```bash
123
- openclawmp whoami
125
+ openclawmp uninstall skill/7c19dc4c3244418096f1dcb59c93f795
126
+ openclawmp uninstall skill/demo-skill
124
127
  ```
125
128
 
126
- ## Global Options
127
-
128
- | Option | Description |
129
- |--------|-------------|
130
- | `--api <url>` | Override the API base URL |
131
- | `--version`, `-v` | Show version |
132
- | `--help`, `-h` | Show help |
133
-
134
- ## Environment Variables
135
-
136
- | Variable | Description |
137
- |----------|-------------|
138
- | `OPENCLAWMP_API` | Override the default API base URL (`https://openclawmp.cc`) |
139
- | `OPENCLAW_STATE_DIR` | Override the OpenClaw state directory (default: `~/.openclaw`) |
140
- | `NO_COLOR` | Disable colored output |
141
-
142
- ## Configuration
143
-
144
- Configuration files are stored in `~/.openclawmp/`:
145
-
146
- - `auth.json` — Authentication token
147
-
148
- Install metadata is tracked in `~/.openclaw/seafood-lock.json` (shared with the OpenClaw ecosystem).
149
-
150
- ## Asset Types
151
-
152
- | Type | Icon | Description |
153
- |------|------|-------------|
154
- | `skill` | 🧩 | Agent skills and capabilities |
155
- | `config` | ⚙️ | Configuration presets |
156
- | `plugin` | 🔌 | Gateway plugins |
157
- | `trigger` | ⚡ | Event triggers |
158
- | `channel` | 📡 | Communication channels |
159
- | `template` | 📋 | Project templates |
160
-
161
- ## Development
129
+ 搜索示例:
162
130
 
163
131
  ```bash
164
- # Clone and run locally
165
- git clone https://github.com/openclaw/openclawmp.git
166
- cd openclawmp
167
-
168
- # Run directly
169
- node bin/openclawmp.js --help
170
- node bin/openclawmp.js search weather
171
-
172
- # Link globally for testing
173
- npm link
174
- openclawmp --help
132
+ openclawmp search 天气 --page-size 10
175
133
  ```
176
134
 
177
- ## License
178
-
179
- MIT
135
+ ## 命令结构
136
+
137
+ 入口采用懒加载:
138
+
139
+ - `publish`
140
+ - `search`
141
+ - `install`
142
+ - `uninstall`
143
+ - `list`
144
+
145
+ 其中当前可用的是 `publish`、`search`、`install`、`uninstall`、`list`。
146
+
147
+ ## 发布命令参数
148
+
149
+ - `--type <skill|experience|plugin|trigger|channel>`
150
+ - `--asset-id <id>`:未传时会尝试读取目录根部 `.assetid`
151
+ - `--name <slug>`
152
+ - `--display-name <name>`
153
+ - `--description <text>`
154
+ - `--version <semver>`
155
+ - `--category <category>`
156
+ - `--tags <a,b,c>`
157
+ - `--tag <value>`:可重复
158
+ - `--long-description <text>`
159
+ - `--base-url <url>`
160
+ - `--token-file <path>`
161
+ - `--part-size-mb <number>`
162
+ - `--ttl-seconds <number>`
163
+ - `--request-id <id>`
164
+ - `--dry-run`
165
+ - `--yes, -y`
166
+ - `--verbose`
167
+
168
+ ## 目录发布打包规则
169
+
170
+ - 目录发布会默认跳过 `.git`、`node_modules`、`__MACOSX`、`.DS_Store`、`Thumbs.db`
171
+ - 根目录可通过 `.openclawmpignore` 补充忽略规则,也会读取根目录 `.gitignore` / `.npmignore` 的常见规则
172
+ - 子路径遇到无权限读取的目录或文件时会自动跳过;使用 `--verbose` 可查看具体跳过项
173
+
174
+ ## Metadata 提取规则
175
+
176
+ - 优先级:CLI 显式参数 > `.metadata.json` > `README.md` > `package.json` > `openclaw.plugin.json` > `SKILL.md`
177
+ - `.metadata.json` 存在时会优先使用,适合显式固定 `type`、`name`、`displayName`、`description`、`version`、`category`、`tags`、`longDescription`
178
+ - `SKILL.md` 的 `name`、`display-name`、`description`、`version`、`tags` 只从 frontmatter(`---` 到 `---`)提取,不会把正文内容混进 `description`
179
+ - `README.md` 支持 YAML frontmatter 和前置 `key: value` 形式字段提取 `name`、`display-name`、`description`、`version`、`type`、`category`、`tags`
180
+ - `trigger` / `experience` 会把 README 标题后的第一段作为描述兜底
181
+ - 发布必填规则:`assetType`、`name`、`displayName`、`semver`
182
+ - `version` 必须是严格的 `x.x.x` 形式,例如 `1.2.1`、`1.1.10`
183
+ - `assetId` 为可选字段;有就透传,没有就省略,由后端判断是新建还是已有资产发新版
184
+ - `objectId` 由上传完成后自动生成,登录态始终必需
185
+ - 调用发布接口前会打印 `CreateAssetSemver` 请求预览;真正发网请求前会打印对应 request body
186
+
187
+ ## 默认配置来源
188
+
189
+ `baseUrl` 优先级:
190
+
191
+ 1. `--base-url`
192
+ 2. `OPENCLAWMP_API_BASE_URL`
193
+ 3. `OPENCLAWMP_BASE_URL`
194
+ 4. `API_BASE_URL`
195
+ 5. `NEXT_PUBLIC_API_BASE_URL`
196
+ 6. 仓库根目录 `.env.local` / `.env` / `.env.prod`
197
+ 7. `https://seafood.c.stepfun-inc.net`
package/bin/openclawmp.js CHANGED
@@ -1,85 +1,83 @@
1
1
  #!/usr/bin/env node
2
- // ============================================================================
3
- // 🐟 OpenClaw Marketplace CLI (openclawmp)
4
- //
5
- // Pure Node.js rewrite of seafood-market.sh
6
- // Zero external runtime dependencies
7
- // ============================================================================
8
2
 
9
- 'use strict';
3
+ const path = require("node:path");
10
4
 
11
- const path = require('path');
12
- const libDir = path.join(__dirname, '..', 'lib');
13
- const { parseArgs } = require(path.join(libDir, 'cli-parser.js'));
14
- const { printHelp } = require(path.join(libDir, 'help.js'));
5
+ const pkg = require(path.join(__dirname, "..", "package.json"));
6
+ const { parseCommandLine } = require("../lib/cli-parser");
7
+ const { printGlobalHelp } = require("../lib/help");
8
+ const { maybeNotifyAboutUpdates } = require("../lib/update-notifier");
9
+ const { formatUserFacingError } = require("../lib/utils");
15
10
 
16
- // Command handlers (lazy-loaded)
17
- const cmdDir = path.join(libDir, 'commands');
18
- const commands = {
19
- install: () => require(path.join(cmdDir, 'install.js')),
20
- uninstall: () => require(path.join(cmdDir, 'uninstall.js')),
21
- search: () => require(path.join(cmdDir, 'search.js')),
22
- list: () => require(path.join(cmdDir, 'list.js')),
23
- info: () => require(path.join(cmdDir, 'info.js')),
24
- publish: () => require(path.join(cmdDir, 'publish.js')),
25
- login: () => require(path.join(cmdDir, 'login.js')),
26
- authorize: () => require(path.join(cmdDir, 'login.js')), // alias
27
- whoami: () => require(path.join(cmdDir, 'whoami.js')),
28
- star: () => ({ run: (a, f) => require(path.join(cmdDir, 'star.js')).runStar(a, f) }),
29
- unstar: () => ({ run: (a, f) => require(path.join(cmdDir, 'star.js')).runUnstar(a, f) }),
30
- comment: () => ({ run: (a, f) => require(path.join(cmdDir, 'comment.js')).runComment(a, f) }),
31
- comments: () => ({ run: (a, f) => require(path.join(cmdDir, 'comment.js')).runComments(a, f) }),
32
- issue: () => ({ run: (a, f) => require(path.join(cmdDir, 'issue.js')).runIssue(a, f) }),
33
- issues: () => ({ run: (a, f) => require(path.join(cmdDir, 'issue.js')).runIssues(a, f) }),
34
- 'delete-account': () => require(path.join(cmdDir, 'delete-account.js')),
35
- unbind: () => require(path.join(cmdDir, 'unbind.js')),
36
- help: () => ({ run: () => printHelp() }),
11
+ const COMMAND_LOADERS = {
12
+ help: function loadHelp() {
13
+ return require("../lib/commands/help");
14
+ },
15
+ login: function loadLogin() {
16
+ return require("../lib/commands/login");
17
+ },
18
+ publish: function loadPublish() {
19
+ return require("../lib/commands/publish");
20
+ },
21
+ search: function loadSearch() {
22
+ return require("../lib/commands/search");
23
+ },
24
+ info: function loadInfo() {
25
+ return require("../lib/commands/info");
26
+ },
27
+ install: function loadInstall() {
28
+ return require("../lib/commands/install");
29
+ },
30
+ uninstall: function loadUninstall() {
31
+ return require("../lib/commands/uninstall");
32
+ },
33
+ list: function loadList() {
34
+ return require("../lib/commands/list");
35
+ }
37
36
  };
38
37
 
39
38
  async function main() {
40
- const { command, args, flags } = parseArgs(process.argv.slice(2));
39
+ const parsed = parseCommandLine(process.argv.slice(2));
41
40
 
42
- // Handle version flag
43
- if (flags.version || flags.v) {
44
- const pkg = require(path.join(__dirname, '..', 'package.json'));
41
+ if (parsed.version) {
45
42
  console.log(pkg.version);
46
- process.exit(0);
43
+ return;
47
44
  }
48
45
 
49
- // Handle help flag or no command
50
- if (flags.help || flags.h || command === 'help' || !command) {
51
- printHelp();
52
- process.exit(0);
46
+ if (!parsed.command) {
47
+ printGlobalHelp();
48
+ return;
53
49
  }
54
50
 
55
- // Resolve command
56
- const loader = commands[command];
57
- if (!loader) {
58
- const ui = require(path.join(libDir, 'ui.js'));
59
- ui.err(`Unknown command: ${command}`);
60
- console.log('');
61
- printHelp();
62
- process.exit(1);
51
+ const loadCommand = COMMAND_LOADERS[parsed.command];
52
+ if (!loadCommand) {
53
+ throw new Error("未知命令: " + parsed.command);
63
54
  }
64
55
 
65
- // Override API base if --api flag or env var provided
66
- if (flags.api || process.env.OPENCLAWMP_API) {
67
- const config = require(path.join(libDir, 'config.js'));
68
- config.setApiBase(flags.api || process.env.OPENCLAWMP_API);
69
- }
56
+ const commandModule = loadCommand();
70
57
 
71
- try {
72
- const mod = loader();
73
- await mod.run(args, flags);
74
- } catch (e) {
75
- const ui = require(path.join(libDir, 'ui.js'));
76
- if (e.code === 'ENOTFOUND' || e.code === 'ECONNREFUSED') {
77
- ui.err(`Cannot reach API server. Check your connection or use --api to set a custom endpoint.`);
78
- } else {
79
- ui.err(e.message || String(e));
58
+ if (parsed.help) {
59
+ if (typeof commandModule.printHelp === "function") {
60
+ commandModule.printHelp();
61
+ return;
80
62
  }
81
- process.exit(1);
63
+ printGlobalHelp();
64
+ return;
82
65
  }
66
+
67
+ await commandModule.run({
68
+ argv: parsed.argv,
69
+ command: parsed.command,
70
+ cliVersion: pkg.version
71
+ });
72
+
73
+ await maybeNotifyAboutUpdates({
74
+ command: parsed.command,
75
+ packageName: pkg.name,
76
+ currentVersion: pkg.version
77
+ });
83
78
  }
84
79
 
85
- main();
80
+ main().catch(function onError(error) {
81
+ console.error(formatUserFacingError(error, { title: "执行失败" }));
82
+ process.exitCode = 1;
83
+ });