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 +21 -0
- package/README.md +179 -0
- package/bin/openclawmp.js +77 -0
- package/lib/api.js +162 -0
- package/lib/auth.js +42 -0
- package/lib/cli-parser.js +61 -0
- package/lib/commands/info.js +56 -0
- package/lib/commands/install.js +270 -0
- package/lib/commands/list.js +36 -0
- package/lib/commands/login.js +37 -0
- package/lib/commands/publish.js +298 -0
- package/lib/commands/search.js +49 -0
- package/lib/commands/uninstall.js +54 -0
- package/lib/commands/whoami.js +48 -0
- package/lib/config.js +167 -0
- package/lib/help.js +45 -0
- package/lib/ui.js +103 -0
- package/package.json +30 -0
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 };
|