openclawmp 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenClaw
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,179 @@
1
+ # 🐟 openclawmp
2
+
3
+ **OpenClaw Marketplace CLI** — 水产市场命令行工具
4
+
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
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install -g openclawmp
11
+ ```
12
+
13
+ Requires **Node.js 18+** (uses built-in `fetch`).
14
+
15
+ ## Quick Start
16
+
17
+ ```bash
18
+ # Search for assets
19
+ openclawmp search "web search"
20
+
21
+ # Install a skill
22
+ openclawmp install skill/@cybernova/web-search
23
+
24
+ # List installed assets
25
+ openclawmp list
26
+
27
+ # View asset details
28
+ openclawmp info skill/web-search
29
+
30
+ # Publish your own skill
31
+ openclawmp publish ./my-skill
32
+ ```
33
+
34
+ ## Commands
35
+
36
+ ### `openclawmp search <query>`
37
+
38
+ Search the marketplace for assets.
39
+
40
+ ```bash
41
+ openclawmp search "文件监控"
42
+ openclawmp search weather
43
+ ```
44
+
45
+ ### `openclawmp install <type>/@<author>/<slug>`
46
+
47
+ Install an asset from the marketplace.
48
+
49
+ ```bash
50
+ # Full format with author scope
51
+ openclawmp install trigger/@xiaoyue/fs-event-trigger
52
+ openclawmp install skill/@cybernova/web-search
53
+
54
+ # Legacy format (no author)
55
+ openclawmp install skill/web-search
56
+
57
+ # Force overwrite existing
58
+ openclawmp install skill/@cybernova/web-search --force
59
+ ```
60
+
61
+ **Supported asset types:** `skill`, `config`, `plugin`, `trigger`, `channel`, `template`
62
+
63
+ ### `openclawmp list`
64
+
65
+ List all assets installed via the marketplace.
66
+
67
+ ```bash
68
+ openclawmp list
69
+ ```
70
+
71
+ ### `openclawmp uninstall <type>/<slug>`
72
+
73
+ Remove an installed asset.
74
+
75
+ ```bash
76
+ openclawmp uninstall skill/web-search
77
+ openclawmp uninstall trigger/fs-event-trigger
78
+ ```
79
+
80
+ ### `openclawmp info <type>/<slug>`
81
+
82
+ View detailed information about an asset from the registry.
83
+
84
+ ```bash
85
+ openclawmp info skill/web-search
86
+ openclawmp info trigger/fs-event-trigger
87
+ ```
88
+
89
+ ### `openclawmp publish [path]`
90
+
91
+ Publish a local asset directory to the marketplace. Defaults to current directory.
92
+
93
+ ```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
102
+ ```
103
+
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.
113
+
114
+ ```bash
115
+ openclawmp login
116
+ ```
117
+
118
+ ### `openclawmp whoami`
119
+
120
+ Show current user/device info and configuration status.
121
+
122
+ ```bash
123
+ openclawmp whoami
124
+ ```
125
+
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
162
+
163
+ ```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
175
+ ```
176
+
177
+ ## License
178
+
179
+ MIT
@@ -0,0 +1,77 @@
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
+
9
+ 'use strict';
10
+
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'));
15
+
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
+ help: () => ({ run: () => printHelp() }),
29
+ };
30
+
31
+ async function main() {
32
+ const { command, args, flags } = parseArgs(process.argv.slice(2));
33
+
34
+ // Handle version flag
35
+ if (flags.version || flags.v) {
36
+ const pkg = require(path.join(__dirname, '..', 'package.json'));
37
+ console.log(pkg.version);
38
+ process.exit(0);
39
+ }
40
+
41
+ // Handle help flag or no command
42
+ if (flags.help || flags.h || command === 'help' || !command) {
43
+ printHelp();
44
+ process.exit(0);
45
+ }
46
+
47
+ // Resolve command
48
+ const loader = commands[command];
49
+ if (!loader) {
50
+ const ui = require(path.join(libDir, 'ui.js'));
51
+ ui.err(`Unknown command: ${command}`);
52
+ console.log('');
53
+ printHelp();
54
+ process.exit(1);
55
+ }
56
+
57
+ // Override API base if --api flag or env var provided
58
+ if (flags.api || process.env.OPENCLAWMP_API) {
59
+ const config = require(path.join(libDir, 'config.js'));
60
+ config.setApiBase(flags.api || process.env.OPENCLAWMP_API);
61
+ }
62
+
63
+ try {
64
+ const mod = loader();
65
+ await mod.run(args, flags);
66
+ } catch (e) {
67
+ const ui = require(path.join(libDir, 'ui.js'));
68
+ if (e.code === 'ENOTFOUND' || e.code === 'ECONNREFUSED') {
69
+ ui.err(`Cannot reach API server. Check your connection or use --api to set a custom endpoint.`);
70
+ } else {
71
+ ui.err(e.message || String(e));
72
+ }
73
+ process.exit(1);
74
+ }
75
+ }
76
+
77
+ main();
package/lib/api.js ADDED
@@ -0,0 +1,162 @@
1
+ // ============================================================================
2
+ // api.js — HTTP request helpers for the OpenClaw Marketplace API
3
+ //
4
+ // Uses Node.js built-in fetch (available since Node 18)
5
+ // ============================================================================
6
+
7
+ 'use strict';
8
+
9
+ const config = require('./config.js');
10
+
11
+ /**
12
+ * Build standard auth headers (token + device ID)
13
+ */
14
+ function authHeaders() {
15
+ const headers = {};
16
+ const token = config.getAuthToken();
17
+ if (token) headers['Authorization'] = `Bearer ${token}`;
18
+ const deviceId = config.getDeviceId();
19
+ if (deviceId) headers['X-Device-ID'] = deviceId;
20
+ return headers;
21
+ }
22
+
23
+ /**
24
+ * Make a GET request to the API
25
+ * @param {string} apiPath - API path (e.g., '/api/assets')
26
+ * @param {object} [params] - Query parameters
27
+ * @returns {Promise<object>} Parsed JSON response
28
+ */
29
+ async function get(apiPath, params = {}) {
30
+ const url = new URL(apiPath, config.getApiBase());
31
+ for (const [k, v] of Object.entries(params)) {
32
+ if (v !== undefined && v !== null && v !== '') {
33
+ url.searchParams.set(k, String(v));
34
+ }
35
+ }
36
+
37
+ const res = await fetch(url.toString(), { headers: authHeaders() });
38
+ if (!res.ok) {
39
+ const body = await res.text().catch(() => '');
40
+ throw new Error(`API error ${res.status}: ${body || res.statusText}`);
41
+ }
42
+ return res.json();
43
+ }
44
+
45
+ /**
46
+ * Make a POST request with JSON body
47
+ * @param {string} apiPath
48
+ * @param {object} body
49
+ * @param {object} [extraHeaders]
50
+ * @returns {Promise<{status: number, data: object}>}
51
+ */
52
+ async function post(apiPath, body, extraHeaders = {}) {
53
+ const url = new URL(apiPath, config.getApiBase());
54
+
55
+ const headers = {
56
+ 'Content-Type': 'application/json',
57
+ ...authHeaders(),
58
+ ...extraHeaders,
59
+ };
60
+
61
+ const res = await fetch(url.toString(), {
62
+ method: 'POST',
63
+ headers,
64
+ body: JSON.stringify(body),
65
+ });
66
+ const data = await res.json().catch(() => ({}));
67
+ return { status: res.status, data };
68
+ }
69
+
70
+ /**
71
+ * POST multipart form data (for publish with file upload)
72
+ * Node 18+ supports FormData natively in fetch
73
+ * @param {string} apiPath
74
+ * @param {FormData} formData
75
+ * @param {object} [extraHeaders]
76
+ * @returns {Promise<{status: number, data: object}>}
77
+ */
78
+ async function postMultipart(apiPath, formData, extraHeaders = {}) {
79
+ const url = new URL(apiPath, config.getApiBase());
80
+
81
+ const headers = {
82
+ ...authHeaders(),
83
+ ...extraHeaders,
84
+ };
85
+
86
+ const res = await fetch(url.toString(), {
87
+ method: 'POST',
88
+ headers,
89
+ body: formData,
90
+ });
91
+ const data = await res.json().catch(() => ({}));
92
+ return { status: res.status, data };
93
+ }
94
+
95
+ /**
96
+ * Download a file (returns Buffer or null if 404)
97
+ * @param {string} apiPath
98
+ * @returns {Promise<Buffer|null>}
99
+ */
100
+ async function download(apiPath) {
101
+ const url = new URL(apiPath, config.getApiBase());
102
+
103
+ const res = await fetch(url.toString(), { headers: authHeaders() });
104
+ if (!res.ok) return null;
105
+ const arrayBuffer = await res.arrayBuffer();
106
+ return Buffer.from(arrayBuffer);
107
+ }
108
+
109
+ /**
110
+ * Search assets
111
+ * @param {string} query
112
+ * @param {object} [opts] - { type, limit }
113
+ * @returns {Promise<object>}
114
+ */
115
+ async function searchAssets(query, opts = {}) {
116
+ const params = { q: query, limit: opts.limit || 20 };
117
+ if (opts.type) params.type = opts.type;
118
+ return get('/api/assets', params);
119
+ }
120
+
121
+ /**
122
+ * Find an asset by type and slug (with optional author filter)
123
+ * @param {string} type
124
+ * @param {string} slug
125
+ * @param {string} [authorFilter]
126
+ * @returns {Promise<object|null>}
127
+ */
128
+ async function findAsset(type, slug, authorFilter) {
129
+ const result = await get('/api/assets', { q: slug, limit: 50 });
130
+ const assets = result?.data?.assets || [];
131
+
132
+ // Exact match on type + name
133
+ let matches = assets.filter(a => a.type === type && a.name === slug);
134
+ if (authorFilter) {
135
+ const authorMatches = matches.filter(a => (a.author?.id || '') === authorFilter);
136
+ if (authorMatches.length > 0) matches = authorMatches;
137
+ }
138
+
139
+ // Fallback: partial match
140
+ if (matches.length === 0) {
141
+ matches = assets.filter(a => a.type === type && a.name.includes(slug));
142
+ if (authorFilter) {
143
+ const authorMatches = matches.filter(a => (a.author?.id || '') === authorFilter);
144
+ if (authorMatches.length > 0) matches = authorMatches;
145
+ }
146
+ }
147
+
148
+ if (matches.length === 0) return null;
149
+
150
+ // Prefer the one with an author ID
151
+ matches.sort((a, b) => (b.author?.id || '').localeCompare(a.author?.id || ''));
152
+ return matches[0];
153
+ }
154
+
155
+ module.exports = {
156
+ get,
157
+ post,
158
+ postMultipart,
159
+ download,
160
+ searchAssets,
161
+ findAsset,
162
+ };
package/lib/auth.js ADDED
@@ -0,0 +1,42 @@
1
+ // ============================================================================
2
+ // auth.js — Authentication / token management
3
+ // ============================================================================
4
+
5
+ 'use strict';
6
+
7
+ const config = require('./config.js');
8
+
9
+ /**
10
+ * Check if the user is authenticated
11
+ */
12
+ function isAuthenticated() {
13
+ return config.getAuthToken() !== null;
14
+ }
15
+
16
+ /**
17
+ * Get the current device ID
18
+ */
19
+ function getDeviceId() {
20
+ return config.getDeviceId();
21
+ }
22
+
23
+ /**
24
+ * Get auth token
25
+ */
26
+ function getToken() {
27
+ return config.getAuthToken();
28
+ }
29
+
30
+ /**
31
+ * Save auth token
32
+ */
33
+ function saveToken(token, extra = {}) {
34
+ config.saveAuthToken(token, extra);
35
+ }
36
+
37
+ module.exports = {
38
+ isAuthenticated,
39
+ getDeviceId,
40
+ getToken,
41
+ saveToken,
42
+ };
@@ -0,0 +1,61 @@
1
+ // ============================================================================
2
+ // cli-parser.js — Lightweight argument parser (no dependencies)
3
+ //
4
+ // Parses: command, positional args, and --flag / --key=value / --key value
5
+ // ============================================================================
6
+
7
+ 'use strict';
8
+
9
+ /**
10
+ * Parse process.argv-style arguments
11
+ * @param {string[]} argv - Arguments (without node and script path)
12
+ * @returns {{ command: string|null, args: string[], flags: object }}
13
+ */
14
+ function parseArgs(argv) {
15
+ const flags = {};
16
+ const positional = [];
17
+
18
+ let i = 0;
19
+ while (i < argv.length) {
20
+ const arg = argv[i];
21
+
22
+ if (arg === '--') {
23
+ // Everything after -- is positional
24
+ positional.push(...argv.slice(i + 1));
25
+ break;
26
+ }
27
+
28
+ if (arg.startsWith('--')) {
29
+ const eqIndex = arg.indexOf('=');
30
+ if (eqIndex !== -1) {
31
+ // --key=value
32
+ const key = arg.slice(2, eqIndex);
33
+ flags[key] = arg.slice(eqIndex + 1);
34
+ } else {
35
+ const key = arg.slice(2);
36
+ // Check if next arg is a value (not a flag)
37
+ if (i + 1 < argv.length && !argv[i + 1].startsWith('-')) {
38
+ flags[key] = argv[i + 1];
39
+ i++;
40
+ } else {
41
+ flags[key] = true;
42
+ }
43
+ }
44
+ } else if (arg.startsWith('-') && arg.length === 2) {
45
+ // Short flag: -h, -v, etc.
46
+ const key = arg.slice(1);
47
+ flags[key] = true;
48
+ } else {
49
+ positional.push(arg);
50
+ }
51
+
52
+ i++;
53
+ }
54
+
55
+ const command = positional[0] || null;
56
+ const args = positional.slice(1);
57
+
58
+ return { command, args, flags };
59
+ }
60
+
61
+ module.exports = { parseArgs };
@@ -0,0 +1,56 @@
1
+ // ============================================================================
2
+ // commands/info.js — View asset details from the registry
3
+ // ============================================================================
4
+
5
+ 'use strict';
6
+
7
+ const api = require('../api.js');
8
+ const config = require('../config.js');
9
+ const { info, err, c } = require('../ui.js');
10
+
11
+ async function run(args) {
12
+ if (args.length === 0) {
13
+ err('Usage: openclawmp info <type>/<slug>');
14
+ process.exit(1);
15
+ }
16
+
17
+ const spec = args[0];
18
+ const parts = spec.split('/');
19
+ const type = parts[0];
20
+ const slug = parts[parts.length - 1];
21
+
22
+ info(`Looking up ${type}/${slug}...`);
23
+
24
+ const asset = await api.findAsset(type, slug);
25
+ if (!asset) {
26
+ err(`Not found: ${type}/${slug}`);
27
+ process.exit(1);
28
+ }
29
+
30
+ const authorName = asset.author?.name || 'unknown';
31
+ const authorId = asset.author?.id || '';
32
+ const tags = (asset.tags || []).join(', ');
33
+
34
+ console.log('');
35
+ console.log(` 🐟 ${c('bold', asset.displayName || asset.name)}`);
36
+ console.log(` ${'─'.repeat(40)}`);
37
+ console.log(` Type: ${asset.type}`);
38
+ console.log(` Package: ${asset.name}`);
39
+ console.log(` Version: ${asset.version}`);
40
+ console.log(` Author: ${c('cyan', authorName)} ${c('dim', `(${authorId})`)}`);
41
+ console.log(` Score: ${asset.hubScore || 0}`);
42
+ console.log(` Downloads: ${asset.downloads || 0}`);
43
+ if (tags) {
44
+ console.log(` Tags: ${tags}`);
45
+ }
46
+ if (asset.description) {
47
+ console.log('');
48
+ console.log(` ${asset.description}`);
49
+ }
50
+ console.log('');
51
+ console.log(` Install: openclawmp install ${asset.type}/@${authorId}/${asset.name}`);
52
+ console.log(` Registry: ${config.getApiBase()}/asset/${asset.id}`);
53
+ console.log('');
54
+ }
55
+
56
+ module.exports = { run };