imaclawhub 1.0.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 +97 -0
- package/cli.js +26 -0
- package/index.js +105 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# imaclawhub
|
|
2
|
+
|
|
3
|
+
通过 ensure 服务获取技能压缩包并解压到本地。默认解压到**与执行命令所在目录平级**的文件夹(即当前目录的上一级下的 `<slug>`)。
|
|
4
|
+
|
|
5
|
+
## 前置条件
|
|
6
|
+
|
|
7
|
+
- 默认使用 ensure 服务 `https://imahub.imaclaw.ai`(暴露 `/skills/ensure` 接口)。也可通过环境变量 `IMACLAWHUB_BASE_URL` 或 API 选项指定其他地址。
|
|
8
|
+
|
|
9
|
+
## 安装
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install imaclawhub
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 安装库之后,如何用它「安装技能」
|
|
16
|
+
|
|
17
|
+
安装好 `imaclawhub` 后,用下面任一方式把某个技能拉到本地并解压(默认解压到与当前目录平级的 `<slug>` 文件夹)。
|
|
18
|
+
|
|
19
|
+
### 方式一:命令行(推荐)
|
|
20
|
+
|
|
21
|
+
在项目目录或任意目录执行:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# 把 <技能名称或slug> 对应的技能解压到当前目录平级
|
|
25
|
+
npx imaclawhub <技能名称或slug>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
示例:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx imaclawhub my-skill
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
若已全局安装(`npm install -g imaclawhub`),可直接:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
imaclawhub my-skill
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
指定 ensure 服务地址(环境变量):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
IMACLAWHUB_BASE_URL=https://其他地址 npx imaclawhub my-skill
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
执行成功后,技能会解压到**与当前目录平级**的、以该技能 slug 命名的文件夹(例如在 `~/projects/my-app` 下执行则解压到 `~/projects/my-skill`)。
|
|
47
|
+
|
|
48
|
+
### 方式二:在代码里调用
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
import { ensureToDesktop } from 'imaclawhub';
|
|
52
|
+
|
|
53
|
+
const result = await ensureToDesktop('my-skill');
|
|
54
|
+
if (result.ok) {
|
|
55
|
+
console.log('已解压到:', result.path); // 如 /Users/你/projects/my-skill(与当前目录平级)
|
|
56
|
+
} else {
|
|
57
|
+
console.error('失败:', result.error);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 使用说明(详细)
|
|
62
|
+
|
|
63
|
+
### 作为库使用
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
import { ensureToDesktop, ensureSkill, getDesktopPath } from 'imaclawhub';
|
|
67
|
+
|
|
68
|
+
// 一键:调用 ensure 并解压(默认解压到与当前目录平级)
|
|
69
|
+
const result = await ensureToDesktop('my-skill');
|
|
70
|
+
if (result.ok) {
|
|
71
|
+
console.log('解压路径:', result.path);
|
|
72
|
+
} else {
|
|
73
|
+
console.error(result.error);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 解压到桌面:传入 outputDir 为桌面路径
|
|
77
|
+
const result2 = await ensureToDesktop('my-skill', { outputDir: getDesktopPath() });
|
|
78
|
+
|
|
79
|
+
// 仅调用 ensure 获取信息(含 downloadUrl)
|
|
80
|
+
const info = await ensureSkill('my-skill', { baseUrl: 'https://imahub.imaclaw.ai' });
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
选项:
|
|
84
|
+
|
|
85
|
+
- `ensureToDesktop(skillName, options)`
|
|
86
|
+
- `options.baseUrl`: ensure 服务地址,默认 `https://imahub.imaclaw.ai`
|
|
87
|
+
- `options.outputDir`: 解压目标父目录,默认与当前目录平级(`process.cwd()` 的上一级)
|
|
88
|
+
- `options.subdir`: 解压后的文件夹名,默认使用接口返回的 `slug`
|
|
89
|
+
|
|
90
|
+
## 发布到 npm
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm login
|
|
94
|
+
npm publish
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
如需发布为 scoped 包(如 `@your-org/imaclawhub`),在 `package.json` 中把 `name` 改为 `@your-org/imaclawhub` 后执行 `npm publish --access public`。
|
package/cli.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { ensureToDesktop } from './index.js';
|
|
4
|
+
|
|
5
|
+
const skillName = process.argv[2];
|
|
6
|
+
if (!skillName) {
|
|
7
|
+
console.error('用法: imaclawhub <技能名称或slug>');
|
|
8
|
+
console.error('示例: imaclawhub my-skill');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const baseUrl = process.env.IMACLAWHUB_BASE_URL || 'https://imahub.imaclaw.ai';
|
|
13
|
+
|
|
14
|
+
ensureToDesktop(skillName, { baseUrl })
|
|
15
|
+
.then((result) => {
|
|
16
|
+
if (result.ok) {
|
|
17
|
+
console.log('解压成功:', result.path);
|
|
18
|
+
} else {
|
|
19
|
+
console.error('失败:', result.error);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
.catch((err) => {
|
|
24
|
+
console.error(err.message || err);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
});
|
package/index.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { homedir, platform } from 'os';
|
|
3
|
+
import { mkdir } from 'fs/promises';
|
|
4
|
+
import { cwd } from 'process';
|
|
5
|
+
import AdmZip from 'adm-zip';
|
|
6
|
+
|
|
7
|
+
const DEFAULT_BASE_URL = 'https://imahub.imaclaw.ai';
|
|
8
|
+
/**
|
|
9
|
+
* 获取桌面目录路径(跨平台)
|
|
10
|
+
* @returns {string}
|
|
11
|
+
*/
|
|
12
|
+
export function getDesktopPath() {
|
|
13
|
+
const home = homedir();
|
|
14
|
+
if (platform() === 'win32') {
|
|
15
|
+
return process.env.USERPROFILE ? join(process.env.USERPROFILE, 'Desktop') : join(home, 'Desktop');
|
|
16
|
+
}
|
|
17
|
+
return join(home, 'Desktop');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 调用 ensure 接口获取技能信息(含下载 URL)
|
|
22
|
+
* @param {string} skillName - 技能名称或 slug
|
|
23
|
+
* @param {{ baseUrl?: string }} options
|
|
24
|
+
* @returns {Promise<{ found: boolean, downloadUrl?: string, slug?: string, name?: string, error?: string }>}
|
|
25
|
+
*/
|
|
26
|
+
export async function ensureSkill(skillName, options = {}) {
|
|
27
|
+
const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, '');
|
|
28
|
+
const name = String(skillName).trim();
|
|
29
|
+
if (!name) {
|
|
30
|
+
return { found: false, error: '缺少技能名称' };
|
|
31
|
+
}
|
|
32
|
+
const url = `${baseUrl}/skills/ensure?name=${encodeURIComponent(name)}`;
|
|
33
|
+
const res = await fetch(url);
|
|
34
|
+
const data = await res.json().catch(() => ({}));
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
return {
|
|
37
|
+
found: false,
|
|
38
|
+
error: data.error || `HTTP ${res.status}`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return data;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 从 URL 下载 zip 并解压到指定目录
|
|
46
|
+
* @param {string} zipUrl - zip 下载地址
|
|
47
|
+
* @param {string} outDir - 解压目标目录
|
|
48
|
+
* @returns {Promise<{ ok: boolean, path?: string, error?: string }>}
|
|
49
|
+
*/
|
|
50
|
+
export async function downloadAndExtractZip(zipUrl, outDir) {
|
|
51
|
+
const res = await fetch(zipUrl, { redirect: 'follow' });
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
return { ok: false, error: `下载失败: HTTP ${res.status}` };
|
|
54
|
+
}
|
|
55
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
56
|
+
const zip = new AdmZip(buf);
|
|
57
|
+
await mkdir(outDir, { recursive: true });
|
|
58
|
+
zip.extractAllTo(outDir, true);
|
|
59
|
+
return { ok: true, path: outDir };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 通过 ensure 接口获取技能 zip URL,下载并解压到指定目录
|
|
64
|
+
* 默认解压到「执行命令所在目录」的平级目录(即上级目录下的 <slug> 文件夹)
|
|
65
|
+
* @param {string} skillName - 技能名称或 slug
|
|
66
|
+
* @param {{ baseUrl?: string, outputDir?: string, subdir?: string }} options
|
|
67
|
+
* - baseUrl: ensure 服务地址,默认 https://imahub.imaclaw.ai
|
|
68
|
+
* - outputDir: 解压目标父目录,默认 process.cwd() 的上一级(与当前目录平级)
|
|
69
|
+
* - subdir: 解压后的文件夹名,默认使用 slug
|
|
70
|
+
* @returns {Promise<{ ok: boolean, path?: string, slug?: string, error?: string }>}
|
|
71
|
+
*/
|
|
72
|
+
export async function ensureToDesktop(skillName, options = {}) {
|
|
73
|
+
const baseUrl = options.baseUrl || DEFAULT_BASE_URL;
|
|
74
|
+
const ensureResult = await ensureSkill(skillName, { baseUrl });
|
|
75
|
+
if (!ensureResult.found || ensureResult.error) {
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
error: ensureResult.error || '未找到该技能',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const downloadUrl = ensureResult.downloadUrl;
|
|
82
|
+
if (!downloadUrl) {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
error: 'ensure 接口未返回 downloadUrl',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// 默认:与执行命令的目录平级(上级目录下的 <slug>)
|
|
89
|
+
const baseDir = options.outputDir ?? join(cwd(), '..');
|
|
90
|
+
const subdir = options.subdir ?? options.desktopSubdir ?? ensureResult.slug ?? skillName.trim();
|
|
91
|
+
const outDir = join(baseDir, subdir);
|
|
92
|
+
const extractResult = await downloadAndExtractZip(downloadUrl, outDir);
|
|
93
|
+
if (!extractResult.ok) {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
error: extractResult.error,
|
|
97
|
+
slug: ensureResult.slug,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
ok: true,
|
|
102
|
+
path: extractResult.path,
|
|
103
|
+
slug: ensureResult.slug,
|
|
104
|
+
};
|
|
105
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "imaclawhub",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Fetch skill from local ensure API and extract to Desktop",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"imaclawhub": "cli.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"index.js",
|
|
15
|
+
"cli.js",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"clawhub",
|
|
21
|
+
"skills",
|
|
22
|
+
"imaclawhub"
|
|
23
|
+
],
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"registry": "https://registry.npmjs.org/"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18.0.0"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"adm-zip": "^0.5.16"
|
|
33
|
+
}
|
|
34
|
+
}
|