rushangle-cli 0.1.0 → 0.1.1
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 +2 -1
- package/package.json +1 -1
- package/src/commands/install.js +84 -58
package/README.md
CHANGED
|
@@ -16,7 +16,8 @@ rushangle whoami # 查看当前用户
|
|
|
16
16
|
rushangle publish # 发布技能到市场
|
|
17
17
|
rushangle list --type skills # 浏览技能列表
|
|
18
18
|
rushangle search <关键词> # 全局搜索
|
|
19
|
-
rushangle install <技能名> #
|
|
19
|
+
rushangle install <技能名> # 安装技能(含本地文件)
|
|
20
|
+
rushangle install <技能名> --register-only # 仅注册到服务器(适用于受限环境)
|
|
20
21
|
```
|
|
21
22
|
|
|
22
23
|
## 命令
|
package/package.json
CHANGED
package/src/commands/install.js
CHANGED
|
@@ -13,6 +13,7 @@ module.exports = new Command('install')
|
|
|
13
13
|
.argument('[skill]', '技能名称或 ID')
|
|
14
14
|
.option('-t, --type <type>', '类型: skill | mcp | code', 'skill')
|
|
15
15
|
.option('-g, --global', '全局安装(默认)', true)
|
|
16
|
+
.option('--register-only', '仅注册到服务器,不写入本地文件(适用于受限环境)')
|
|
16
17
|
.action(async (nameOrId, opts) => {
|
|
17
18
|
const token = auth.getToken();
|
|
18
19
|
if (!token) {
|
|
@@ -51,26 +52,35 @@ module.exports = new Command('install')
|
|
|
51
52
|
return;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
//
|
|
55
|
-
const installDir = path.join(INSTALL_DIR, 'skills', item.id);
|
|
56
|
-
fs.mkdirSync(installDir, { recursive: true });
|
|
57
|
-
|
|
58
|
-
// Write metadata
|
|
59
|
-
fs.writeFileSync(
|
|
60
|
-
path.join(installDir, 'rushangle.json'),
|
|
61
|
-
JSON.stringify({ type: 'skill', ...item, installedAt: new Date().toISOString() }, null, 2)
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
// Write readme if exists
|
|
65
|
-
if (item.readme) {
|
|
66
|
-
fs.writeFileSync(path.join(installDir, 'README.md'), item.readme);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Register install on server
|
|
55
|
+
// Register install on server FIRST (survives EPERM in sandbox)
|
|
70
56
|
await api.installSkill(item.id);
|
|
71
57
|
|
|
72
|
-
|
|
73
|
-
|
|
58
|
+
// Local file install (best-effort, skip in restricted environments)
|
|
59
|
+
if (!opts.registerOnly) {
|
|
60
|
+
try {
|
|
61
|
+
const installDir = path.join(INSTALL_DIR, 'skills', item.id);
|
|
62
|
+
fs.mkdirSync(installDir, { recursive: true });
|
|
63
|
+
fs.writeFileSync(
|
|
64
|
+
path.join(installDir, 'rushangle.json'),
|
|
65
|
+
JSON.stringify({ type: 'skill', ...item, installedAt: new Date().toISOString() }, null, 2)
|
|
66
|
+
);
|
|
67
|
+
if (item.readme) {
|
|
68
|
+
fs.writeFileSync(path.join(installDir, 'README.md'), item.readme);
|
|
69
|
+
}
|
|
70
|
+
console.log(chalk.green(`✓ 安装成功: ${item.name}@${item.version}`));
|
|
71
|
+
console.log(chalk.gray(` 本地目录: ${installDir}`));
|
|
72
|
+
} catch (fileErr) {
|
|
73
|
+
// EPERM or other file errors in sandbox — server registration already succeeded
|
|
74
|
+
console.log(chalk.green(`✓ 服务器注册成功: ${item.name}@${item.version}`));
|
|
75
|
+
console.log(chalk.yellow(` 本地文件写入跳过(${fileErr.code || fileErr.message})`));
|
|
76
|
+
if (fileErr.code === 'EPERM' || fileErr.code === 'EACCES') {
|
|
77
|
+
console.log(chalk.gray(` 提示: 使用 --register-only 跳过本地文件操作`));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
console.log(chalk.green(`✓ 服务器注册成功: ${item.name}@${item.version}`));
|
|
82
|
+
console.log(chalk.gray(` 已跳过本地文件写入(--register-only)`));
|
|
83
|
+
}
|
|
74
84
|
|
|
75
85
|
} else if (type === 'mcp') {
|
|
76
86
|
try {
|
|
@@ -90,34 +100,41 @@ module.exports = new Command('install')
|
|
|
90
100
|
return;
|
|
91
101
|
}
|
|
92
102
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
103
|
+
// Local file install (best-effort)
|
|
104
|
+
let showLocalPath = false;
|
|
105
|
+
let installDir = '';
|
|
106
|
+
if (!opts.registerOnly) {
|
|
107
|
+
try {
|
|
108
|
+
installDir = path.join(INSTALL_DIR, 'mcp', item.id);
|
|
109
|
+
fs.mkdirSync(installDir, { recursive: true });
|
|
110
|
+
if (item.code) {
|
|
111
|
+
const ext = item.language === 'python' ? '.py'
|
|
112
|
+
: item.language === 'javascript' ? '.js'
|
|
113
|
+
: item.language === 'typescript' ? '.ts'
|
|
114
|
+
: item.language === 'json' ? '.json'
|
|
115
|
+
: '.txt';
|
|
116
|
+
fs.writeFileSync(path.join(installDir, `server${ext}`), item.code);
|
|
117
|
+
}
|
|
118
|
+
if (item.config) {
|
|
119
|
+
const configStr = typeof item.config === 'string'
|
|
120
|
+
? item.config : JSON.stringify(item.config, null, 2);
|
|
121
|
+
fs.writeFileSync(path.join(installDir, 'mcp.json'), configStr);
|
|
122
|
+
}
|
|
123
|
+
fs.writeFileSync(
|
|
124
|
+
path.join(installDir, 'rushangle.json'),
|
|
125
|
+
JSON.stringify({ type: 'mcp', ...item, installedAt: new Date().toISOString() }, null, 2)
|
|
126
|
+
);
|
|
127
|
+
showLocalPath = true;
|
|
128
|
+
} catch (fileErr) {
|
|
129
|
+
console.log(chalk.yellow(` 本地文件写入跳过(${fileErr.code || fileErr.message})`));
|
|
130
|
+
if (fileErr.code === 'EPERM' || fileErr.code === 'EACCES') {
|
|
131
|
+
console.log(chalk.gray(` 提示: 使用 --register-only 跳过本地文件操作`));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
111
134
|
}
|
|
112
135
|
|
|
113
|
-
// Write metadata
|
|
114
|
-
fs.writeFileSync(
|
|
115
|
-
path.join(installDir, 'rushangle.json'),
|
|
116
|
-
JSON.stringify({ type: 'mcp', ...item, installedAt: new Date().toISOString() }, null, 2)
|
|
117
|
-
);
|
|
118
|
-
|
|
119
136
|
console.log(chalk.green(`✓ 安装成功: ${item.name}@${item.version}`));
|
|
120
|
-
console.log(chalk.gray(`
|
|
137
|
+
if (showLocalPath) console.log(chalk.gray(` 本地目录: ${installDir}`));
|
|
121
138
|
|
|
122
139
|
} else if (type === 'code') {
|
|
123
140
|
try {
|
|
@@ -137,25 +154,34 @@ module.exports = new Command('install')
|
|
|
137
154
|
return;
|
|
138
155
|
}
|
|
139
156
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
157
|
+
// Local file install (best-effort)
|
|
158
|
+
let showLocalPath = false;
|
|
159
|
+
let installDir = '';
|
|
160
|
+
if (!opts.registerOnly) {
|
|
161
|
+
try {
|
|
162
|
+
installDir = path.join(INSTALL_DIR, 'code', item.id);
|
|
163
|
+
fs.mkdirSync(installDir, { recursive: true });
|
|
164
|
+
if (item.code) {
|
|
165
|
+
const extMap = { python: '.py', javascript: '.js', typescript: '.ts', go: '.go', rust: '.rs', java: '.java', c: '.c', cpp: '.cpp', shell: '.sh', bash: '.sh', json: '.json', yaml: '.yml', toml: '.toml', sql: '.sql', ruby: '.rb', php: '.php', swift: '.swift', kotlin: '.kt', scala: '.scala', r: '.r' };
|
|
166
|
+
const ext = extMap[item.language] || `.${item.language}` || '.txt';
|
|
167
|
+
fs.writeFileSync(path.join(installDir, `snippet${ext}`), item.code);
|
|
168
|
+
}
|
|
169
|
+
fs.writeFileSync(
|
|
170
|
+
path.join(installDir, 'rushangle.json'),
|
|
171
|
+
JSON.stringify({ type: 'code', ...item, installedAt: new Date().toISOString() }, null, 2)
|
|
172
|
+
);
|
|
173
|
+
showLocalPath = true;
|
|
174
|
+
} catch (fileErr) {
|
|
175
|
+
console.log(chalk.yellow(` 本地文件写入跳过(${fileErr.code || fileErr.message})`));
|
|
176
|
+
if (fileErr.code === 'EPERM' || fileErr.code === 'EACCES') {
|
|
177
|
+
console.log(chalk.gray(` 提示: 使用 --register-only 跳过本地文件操作`));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
148
180
|
}
|
|
149
181
|
|
|
150
|
-
// Write metadata
|
|
151
|
-
fs.writeFileSync(
|
|
152
|
-
path.join(installDir, 'rushangle.json'),
|
|
153
|
-
JSON.stringify({ type: 'code', ...item, installedAt: new Date().toISOString() }, null, 2)
|
|
154
|
-
);
|
|
155
|
-
|
|
156
182
|
console.log(chalk.green(`✓ 安装成功: ${item.name}`));
|
|
157
183
|
console.log(chalk.gray(` 语言: ${item.language}`));
|
|
158
|
-
console.log(chalk.gray(`
|
|
184
|
+
if (showLocalPath) console.log(chalk.gray(` 本地目录: ${installDir}`));
|
|
159
185
|
|
|
160
186
|
} else {
|
|
161
187
|
console.log(chalk.red(`不支持的类型: ${type}`));
|