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
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// commands/uninstall.js — Uninstall an asset
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const config = require('../config.js');
|
|
10
|
+
const { fish, ok, err, c } = require('../ui.js');
|
|
11
|
+
|
|
12
|
+
async function run(args) {
|
|
13
|
+
if (args.length === 0) {
|
|
14
|
+
err('Usage: openclawmp uninstall <type>/<slug>');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const spec = args[0];
|
|
19
|
+
const parts = spec.split('/');
|
|
20
|
+
const type = parts[0];
|
|
21
|
+
// Handle type/@author/slug or type/slug — take the last segment as slug
|
|
22
|
+
const slug = parts[parts.length - 1];
|
|
23
|
+
|
|
24
|
+
let targetDir;
|
|
25
|
+
try {
|
|
26
|
+
targetDir = path.join(config.installDirForType(type), slug);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
err(e.message);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!fs.existsSync(targetDir)) {
|
|
33
|
+
err(`${type}/${slug} is not installed`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fish(`Uninstalling ${type}/${slug}...`);
|
|
38
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
39
|
+
|
|
40
|
+
// Remove from lockfile — try multiple key formats
|
|
41
|
+
const lock = config.readLockfile();
|
|
42
|
+
const keysToRemove = Object.keys(lock.installed || {}).filter(k => {
|
|
43
|
+
// Match type/slug or type/@author/slug
|
|
44
|
+
return k === `${type}/${slug}` || k.startsWith(`${type}/`) && k.endsWith(`/${slug}`);
|
|
45
|
+
});
|
|
46
|
+
for (const key of keysToRemove) {
|
|
47
|
+
config.removeLockfile(key);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
ok(`Uninstalled ${type}/${slug}`);
|
|
51
|
+
console.log(` ${c('dim', `Removed: ${targetDir}`)}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = { run };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// commands/whoami.js — Show current user / device info
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const config = require('../config.js');
|
|
8
|
+
const { fish, info, warn, c, detail } = require('../ui.js');
|
|
9
|
+
|
|
10
|
+
async function run() {
|
|
11
|
+
console.log('');
|
|
12
|
+
fish('Who Am I');
|
|
13
|
+
console.log('');
|
|
14
|
+
|
|
15
|
+
// Device identity
|
|
16
|
+
const deviceId = config.getDeviceId();
|
|
17
|
+
if (deviceId) {
|
|
18
|
+
detail('Device ID', deviceId);
|
|
19
|
+
} else {
|
|
20
|
+
warn(`No device identity found (expected: ${config.DEVICE_JSON})`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Auth token
|
|
24
|
+
const token = config.getAuthToken();
|
|
25
|
+
if (token) {
|
|
26
|
+
detail('Auth Token', `${token.slice(0, 8)}...${token.slice(-4)} (configured)`);
|
|
27
|
+
} else {
|
|
28
|
+
detail('Auth Token', c('dim', 'not configured'));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// API base
|
|
32
|
+
detail('API Base', config.getApiBase());
|
|
33
|
+
|
|
34
|
+
// Config dir
|
|
35
|
+
detail('Config Dir', config.CONFIG_DIR);
|
|
36
|
+
|
|
37
|
+
// Install base
|
|
38
|
+
detail('Install Base', config.OPENCLAW_STATE_DIR);
|
|
39
|
+
|
|
40
|
+
// Lockfile stats
|
|
41
|
+
const lock = config.readLockfile();
|
|
42
|
+
const count = Object.keys(lock.installed || {}).length;
|
|
43
|
+
detail('Installed', `${count} asset(s)`);
|
|
44
|
+
|
|
45
|
+
console.log('');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { run };
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// config.js — Configuration management
|
|
3
|
+
//
|
|
4
|
+
// Config dir: ~/.openclawmp/
|
|
5
|
+
// Stores: auth tokens, preferences
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
|
|
14
|
+
const CONFIG_DIR = path.join(os.homedir(), '.openclawmp');
|
|
15
|
+
const AUTH_FILE = path.join(CONFIG_DIR, 'auth.json');
|
|
16
|
+
|
|
17
|
+
// Default API base — can be overridden by --api flag or OPENCLAWMP_API env
|
|
18
|
+
let API_BASE = 'https://openclawmp.cc';
|
|
19
|
+
|
|
20
|
+
// OpenClaw state directory (for lockfile, install dirs, device identity)
|
|
21
|
+
const OPENCLAW_STATE_DIR = process.env.OPENCLAW_STATE_DIR || path.join(os.homedir(), '.openclaw');
|
|
22
|
+
const LOCKFILE = path.join(OPENCLAW_STATE_DIR, 'seafood-lock.json');
|
|
23
|
+
const DEVICE_JSON = path.join(OPENCLAW_STATE_DIR, 'identity', 'device.json');
|
|
24
|
+
|
|
25
|
+
// Valid asset types and their install subdirectories
|
|
26
|
+
const ASSET_TYPES = {
|
|
27
|
+
skill: 'skills',
|
|
28
|
+
config: 'configs',
|
|
29
|
+
plugin: 'plugins',
|
|
30
|
+
trigger: 'triggers',
|
|
31
|
+
channel: 'channels',
|
|
32
|
+
template: 'templates',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Ensure config directory exists
|
|
37
|
+
*/
|
|
38
|
+
function ensureConfigDir() {
|
|
39
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
40
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get the install directory for a given asset type
|
|
46
|
+
*/
|
|
47
|
+
function installDirForType(type) {
|
|
48
|
+
const subdir = ASSET_TYPES[type];
|
|
49
|
+
if (!subdir) {
|
|
50
|
+
throw new Error(`Unknown asset type: ${type}. Valid types: ${Object.keys(ASSET_TYPES).join(', ')}`);
|
|
51
|
+
}
|
|
52
|
+
return path.join(OPENCLAW_STATE_DIR, subdir);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get/set API base URL
|
|
57
|
+
*/
|
|
58
|
+
function getApiBase() {
|
|
59
|
+
return API_BASE;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function setApiBase(url) {
|
|
63
|
+
// Strip trailing slash
|
|
64
|
+
API_BASE = url.replace(/\/+$/, '');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Read auth token
|
|
69
|
+
*/
|
|
70
|
+
function getAuthToken() {
|
|
71
|
+
ensureConfigDir();
|
|
72
|
+
if (!fs.existsSync(AUTH_FILE)) return null;
|
|
73
|
+
try {
|
|
74
|
+
const data = JSON.parse(fs.readFileSync(AUTH_FILE, 'utf-8'));
|
|
75
|
+
return data.token || null;
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Save auth token
|
|
83
|
+
*/
|
|
84
|
+
function saveAuthToken(token, extra = {}) {
|
|
85
|
+
ensureConfigDir();
|
|
86
|
+
const data = { token, savedAt: new Date().toISOString(), ...extra };
|
|
87
|
+
fs.writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2) + '\n');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Read the OpenClaw device ID
|
|
92
|
+
*/
|
|
93
|
+
function getDeviceId() {
|
|
94
|
+
if (!fs.existsSync(DEVICE_JSON)) return null;
|
|
95
|
+
try {
|
|
96
|
+
const data = JSON.parse(fs.readFileSync(DEVICE_JSON, 'utf-8'));
|
|
97
|
+
return data.deviceId || null;
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// === Lockfile operations ===
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Initialize lockfile if it doesn't exist
|
|
107
|
+
*/
|
|
108
|
+
function initLockfile() {
|
|
109
|
+
if (!fs.existsSync(LOCKFILE)) {
|
|
110
|
+
const dir = path.dirname(LOCKFILE);
|
|
111
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
112
|
+
fs.writeFileSync(LOCKFILE, JSON.stringify({ version: 1, installed: {} }, null, 2) + '\n');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Read the lockfile
|
|
118
|
+
*/
|
|
119
|
+
function readLockfile() {
|
|
120
|
+
initLockfile();
|
|
121
|
+
try {
|
|
122
|
+
return JSON.parse(fs.readFileSync(LOCKFILE, 'utf-8'));
|
|
123
|
+
} catch {
|
|
124
|
+
return { version: 1, installed: {} };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Update a lockfile entry
|
|
130
|
+
*/
|
|
131
|
+
function updateLockfile(key, version, location) {
|
|
132
|
+
const lock = readLockfile();
|
|
133
|
+
lock.installed[key] = {
|
|
134
|
+
version,
|
|
135
|
+
installedAt: new Date().toISOString(),
|
|
136
|
+
location,
|
|
137
|
+
};
|
|
138
|
+
fs.writeFileSync(LOCKFILE, JSON.stringify(lock, null, 2) + '\n');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Remove a lockfile entry
|
|
143
|
+
*/
|
|
144
|
+
function removeLockfile(key) {
|
|
145
|
+
const lock = readLockfile();
|
|
146
|
+
delete lock.installed[key];
|
|
147
|
+
fs.writeFileSync(LOCKFILE, JSON.stringify(lock, null, 2) + '\n');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
CONFIG_DIR,
|
|
152
|
+
OPENCLAW_STATE_DIR,
|
|
153
|
+
LOCKFILE,
|
|
154
|
+
DEVICE_JSON,
|
|
155
|
+
ASSET_TYPES,
|
|
156
|
+
ensureConfigDir,
|
|
157
|
+
installDirForType,
|
|
158
|
+
getApiBase,
|
|
159
|
+
setApiBase,
|
|
160
|
+
getAuthToken,
|
|
161
|
+
saveAuthToken,
|
|
162
|
+
getDeviceId,
|
|
163
|
+
initLockfile,
|
|
164
|
+
readLockfile,
|
|
165
|
+
updateLockfile,
|
|
166
|
+
removeLockfile,
|
|
167
|
+
};
|
package/lib/help.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// help.js — Help text output
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const { c } = require('./ui.js');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
|
10
|
+
|
|
11
|
+
function printHelp() {
|
|
12
|
+
console.log('');
|
|
13
|
+
console.log(` ${c('bold', '🐟 OpenClaw Marketplace')} v${pkg.version} — 水产市场命令行工具`);
|
|
14
|
+
console.log('');
|
|
15
|
+
console.log(' Usage: openclawmp <command> [args] [options]');
|
|
16
|
+
console.log('');
|
|
17
|
+
console.log(' Commands:');
|
|
18
|
+
console.log(' install <type>/@<author>/<slug> Install an asset from the market');
|
|
19
|
+
console.log(' uninstall <type>/<slug> Uninstall an asset');
|
|
20
|
+
console.log(' search <query> Search the market');
|
|
21
|
+
console.log(' list List installed assets');
|
|
22
|
+
console.log(' info <type>/<slug> View asset details');
|
|
23
|
+
console.log(' publish [path] Publish an asset to the market');
|
|
24
|
+
console.log(' login Show device authorization info');
|
|
25
|
+
console.log(' whoami Show current user / device info');
|
|
26
|
+
console.log(' help Show this help');
|
|
27
|
+
console.log('');
|
|
28
|
+
console.log(' Global options:');
|
|
29
|
+
console.log(' --api <url> Override API base URL');
|
|
30
|
+
console.log(' --version, -v Show version');
|
|
31
|
+
console.log(' --help, -h Show help');
|
|
32
|
+
console.log('');
|
|
33
|
+
console.log(` Asset types: ${c('dim', 'skill, config, plugin, trigger, channel, template')}`);
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log(' Examples:');
|
|
36
|
+
console.log(' openclawmp install trigger/@xiaoyue/fs-event-trigger');
|
|
37
|
+
console.log(' openclawmp install skill/@cybernova/web-search');
|
|
38
|
+
console.log(' openclawmp search "文件监控"');
|
|
39
|
+
console.log(' openclawmp list');
|
|
40
|
+
console.log('');
|
|
41
|
+
console.log(` Environment: ${c('dim', 'OPENCLAWMP_API — override API base URL')}`);
|
|
42
|
+
console.log('');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { printHelp };
|
package/lib/ui.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// ui.js — Colored output helpers (no external dependencies)
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
// ANSI color codes
|
|
8
|
+
const colors = {
|
|
9
|
+
red: '\x1b[0;31m',
|
|
10
|
+
green: '\x1b[0;32m',
|
|
11
|
+
yellow: '\x1b[1;33m',
|
|
12
|
+
blue: '\x1b[0;34m',
|
|
13
|
+
cyan: '\x1b[0;36m',
|
|
14
|
+
magenta: '\x1b[0;35m',
|
|
15
|
+
bold: '\x1b[1m',
|
|
16
|
+
dim: '\x1b[2m',
|
|
17
|
+
reset: '\x1b[0m',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Check if color output should be enabled
|
|
21
|
+
const useColor = process.env.NO_COLOR === undefined && process.stdout.isTTY !== false;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Wrap text with ANSI color codes (no-op if colors disabled)
|
|
25
|
+
*/
|
|
26
|
+
function c(colorName, text) {
|
|
27
|
+
if (!useColor) return text;
|
|
28
|
+
return `${colors[colorName] || ''}${text}${colors.reset}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Semantic log helpers (match the bash version)
|
|
32
|
+
function info(msg) { console.log(`${c('blue', 'ℹ')} ${msg}`); }
|
|
33
|
+
function ok(msg) { console.log(`${c('green', '✅')} ${msg}`); }
|
|
34
|
+
function warn(msg) { console.log(`${c('yellow', '⚠️')} ${msg}`); }
|
|
35
|
+
function err(msg) { console.error(`${c('red', '❌')} ${msg}`); }
|
|
36
|
+
function fish(msg) { console.log(`${c('cyan', '🐟')} ${msg}`); }
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Print a key-value detail line (indented)
|
|
40
|
+
*/
|
|
41
|
+
function detail(key, value) {
|
|
42
|
+
console.log(` ${c('dim', `${key}:`)} ${value}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Type icons for asset types
|
|
47
|
+
*/
|
|
48
|
+
const typeIcons = {
|
|
49
|
+
skill: '🧩',
|
|
50
|
+
config: '⚙️',
|
|
51
|
+
plugin: '🔌',
|
|
52
|
+
trigger: '⚡',
|
|
53
|
+
channel: '📡',
|
|
54
|
+
template: '📋',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function typeIcon(type) {
|
|
58
|
+
return typeIcons[type] || '📦';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Simple spinner for async operations
|
|
63
|
+
*/
|
|
64
|
+
class Spinner {
|
|
65
|
+
constructor(message) {
|
|
66
|
+
this.message = message;
|
|
67
|
+
this.frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
68
|
+
this.index = 0;
|
|
69
|
+
this.timer = null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
start() {
|
|
73
|
+
if (!useColor || !process.stderr.isTTY) {
|
|
74
|
+
process.stderr.write(` ${this.message}...\n`);
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
this.timer = setInterval(() => {
|
|
78
|
+
const frame = this.frames[this.index % this.frames.length];
|
|
79
|
+
process.stderr.write(`\r ${frame} ${this.message}`);
|
|
80
|
+
this.index++;
|
|
81
|
+
}, 80);
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
stop(finalMessage) {
|
|
86
|
+
if (this.timer) {
|
|
87
|
+
clearInterval(this.timer);
|
|
88
|
+
this.timer = null;
|
|
89
|
+
process.stderr.write('\r\x1b[K'); // clear line
|
|
90
|
+
}
|
|
91
|
+
if (finalMessage) {
|
|
92
|
+
console.log(` ${finalMessage}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
colors, c, useColor,
|
|
99
|
+
info, ok, warn, err, fish,
|
|
100
|
+
detail,
|
|
101
|
+
typeIcon, typeIcons,
|
|
102
|
+
Spinner,
|
|
103
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openclawmp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "🐟 OpenClaw Marketplace CLI — 水产市场命令行工具",
|
|
5
|
+
"bin": {
|
|
6
|
+
"openclawmp": "./bin/openclawmp.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"lib/",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"openclaw",
|
|
16
|
+
"marketplace",
|
|
17
|
+
"cli",
|
|
18
|
+
"agent",
|
|
19
|
+
"skill"
|
|
20
|
+
],
|
|
21
|
+
"author": "OpenClaw",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18.0.0"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/openclaw/openclawmp"
|
|
29
|
+
}
|
|
30
|
+
}
|