imahub-test 1.0.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.
Files changed (4) hide show
  1. package/README.md +80 -0
  2. package/cli.js +142 -0
  3. package/index.js +304 -0
  4. package/package.json +41 -0
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # imahub
2
+
3
+ 通过 ensure 服务获取技能列表并解压到本地。默认安装到本机 openclaw 的 skills 目录(`~/.openclaw/skills/<名称>`),可通过环境变量 `IMAHUB_OUTPUT_DIR` 设置安装目录。使用默认安装目录时会先检查本机是否已安装 openclaw(存在 `~/.openclaw` 目录),未安装则提示先安装 openclaw 并中断;支持 Mac / Linux / Windows。
4
+
5
+ ## 全局安装
6
+
7
+ ```bash
8
+ npm install imahub -g
9
+ ```
10
+
11
+ ## 使用
12
+
13
+ 全局安装后可直接执行
14
+ ```bash
15
+ imahub install <技能名称>
16
+ ```
17
+
18
+ 查看版本号
19
+ ```bash
20
+ imahub -v
21
+ ```
22
+ ## 或者直接使用npx
23
+
24
+ ```bash
25
+ npx imahub install <技能名称>
26
+ ```
27
+
28
+ 会请求 ensure 接口获取匹配列表,列出序号后输入数字选择并下载。示例:`npx imahub install claw`。
29
+
30
+ **设置安装目录**(可选项,一般不需要设置)
31
+ 已安装 openclaw 时,技能会默认安装到 `~/.openclaw/skills`,无需额外配置。仅在需要安装到其他目录(如未安装 openclaw 或自定义路径)时再设置:
32
+
33
+ - 命令:`imahub set dir '<路径>'`,会写入 `~/.imahub/config.json`,之后安装**直接**使用该目录。
34
+ ```bash
35
+ imahub set dir /path/to/my/skills
36
+ imahub set dir "/path/with spaces"
37
+ ```
38
+ - 清空已设定目录:`imahub set dir --clear`,恢复为默认(需已安装 openclaw)。
39
+ - 或环境变量:`IMAHUB_OUTPUT_DIR`(优先级高于 set dir 配置)。
40
+ 优先级:环境变量 > `imahub set dir` 配置 > `~/.openclaw/skills`。直接运行 `imahub` 不加参数可查看当前安装目录。
41
+
42
+ **检查 openclaw 是否已安装**:`imahub check openclaw`,会输出「已安装」或「未安装」及默认技能目录。
43
+
44
+ ## 发布脚本(测试包 / 正式包)
45
+
46
+ 已内置两套发布命令,分别管理包名、请求地址、版本号:
47
+
48
+ - 测试包:`imahub-test`
49
+ - 正式包:`imahub`
50
+
51
+ 配置文件:
52
+
53
+ - `release.config.json`:管理包名、请求地址、registry、npm tag
54
+ - `release.versions.json`:分别维护 `test` / `prod` 版本号
55
+
56
+ 命令:
57
+
58
+ ```bash
59
+ # 测试包发布(默认 patch 递增)
60
+ npm run publish:test
61
+
62
+ # 正式包发布(默认 patch 递增)
63
+ npm run publish:prod
64
+
65
+ # 指定版本递增类型
66
+ npm run publish:test -- minor
67
+ npm run publish:prod -- major
68
+
69
+ # 直接指定目标版本
70
+ npm run publish:test -- 0.2.0
71
+ npm run publish:prod -- 1.1.0
72
+
73
+ # 仅校验,不真正发布
74
+ npm run publish:test -- --dry-run
75
+ ```
76
+
77
+ 说明:
78
+
79
+ - 测试包默认使用 `beta` tag,正式包使用 `latest` tag。
80
+ - 发布前脚本会自动更新 `package.json` 的 `name/version`,并同步替换 `index.js` 中的 `DEFAULT_BASE_URL`。
package/cli.js ADDED
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync } from 'fs';
4
+ import {
5
+ ensureSkillList,
6
+ ensureToDesktop,
7
+ getOpenClawSkillsDir,
8
+ getConfiguredOutputDir,
9
+ setConfiguredOutputDir,
10
+ clearConfiguredOutputDir,
11
+ isOpenClawInstalled,
12
+ } from './index.js';
13
+
14
+ const CLI_NAME = 'imahub';
15
+ const CLI_VERSION = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf-8')).version;
16
+ const OUTPUT_DIR_ENV = 'IMAHUB_OUTPUT_DIR';
17
+ const LEGACY_OUTPUT_DIR_ENV = 'IMACLAWHUB_OUTPUT_DIR';
18
+
19
+ // 安装目录优先级:环境变量 > imahub set dir 配置 > ~/.openclaw/skills
20
+ const outputDir =
21
+ process.env[OUTPUT_DIR_ENV] ||
22
+ process.env[LEGACY_OUTPUT_DIR_ENV] ||
23
+ getConfiguredOutputDir() ||
24
+ getOpenClawSkillsDir();
25
+
26
+ function printUsage() {
27
+ console.error(`用法: ${CLI_NAME} install <技能名称或关键词>`);
28
+ console.error(` ${CLI_NAME} check openclaw # 检查是否已安装 openclaw`);
29
+ console.error(` ${CLI_NAME} set dir <路径> # 设定安装目录(已设置则直接安装到该目录)`);
30
+ console.error(` ${CLI_NAME} set dir --clear # 清空已设定的安装目录`);
31
+ console.error(` ${CLI_NAME} -v # 查看版本号`);
32
+ console.error(`示例: ${CLI_NAME} install my-skill 或 ${CLI_NAME} install "AI 衣橱搭配"`);
33
+ console.error(
34
+ '安装目录: ' +
35
+ outputDir +
36
+ (isOpenClawInstalled()
37
+ ? ' (已检测到 openclaw)'
38
+ : ` (未检测到 openclaw,请先安装或 ${CLI_NAME} set dir <路径>)`)
39
+ );
40
+ }
41
+
42
+ const args = process.argv.slice(2);
43
+ const arg0 = args[0];
44
+ const arg1 = args[1];
45
+
46
+ if (arg0 === 'version' || arg0 === '--version' || arg0 === '-v') {
47
+ console.log(CLI_VERSION);
48
+ process.exit(0);
49
+ } else if (!arg0 || arg0 === 'help' || arg0 === '--help' || arg0 === '-h') {
50
+ printUsage();
51
+ process.exit(arg0 ? 0 : 1);
52
+ } else if (arg0 === 'check' && arg1 === 'openclaw') {
53
+ const installed = isOpenClawInstalled();
54
+ const skillsDir = getOpenClawSkillsDir();
55
+ if (installed) {
56
+ console.log('openclaw: 已安装');
57
+ console.log('默认技能目录: ' + skillsDir);
58
+ } else {
59
+ console.log('openclaw: 未安装');
60
+ console.log(`期望目录: ${skillsDir}(请先安装 openclaw 或使用 ${CLI_NAME} set dir <路径> 指定安装目录)`);
61
+ }
62
+ process.exit(installed ? 0 : 1);
63
+ } else if (arg0 === 'set' && arg1 === 'dir') {
64
+ const dir = args.length > 2 ? args.slice(2).join(' ').trim() : '';
65
+ if (dir === '--clear' || dir === '--unset') {
66
+ clearConfiguredOutputDir()
67
+ .then(() => {
68
+ console.log('已清空安装目录设置,将使用默认目录(需已安装 openclaw)。');
69
+ process.exit(0);
70
+ })
71
+ .catch((err) => {
72
+ console.error('清空失败:', err.message || err);
73
+ process.exit(1);
74
+ });
75
+ // flow stops here (async)
76
+ } else if (!dir) {
77
+ console.error(`用法: ${CLI_NAME} set dir <安装目录路径>`);
78
+ console.error(` ${CLI_NAME} set dir --clear # 清空已设定的安装目录`);
79
+ console.error(`示例: ${CLI_NAME} set dir /path/to/skills 或 ${CLI_NAME} set dir "C:\\My Skills"`);
80
+ process.exit(1);
81
+ } else {
82
+ setConfiguredOutputDir(dir)
83
+ .then(() => {
84
+ console.log('已设置安装目录: ' + dir);
85
+ process.exit(0);
86
+ })
87
+ .catch((err) => {
88
+ console.error('设置失败:', err.message || err);
89
+ process.exit(1);
90
+ });
91
+ }
92
+ } else {
93
+ // 主用法:imahub install "AI 衣橱搭配",兼容旧写法:imahub "AI 衣橱搭配"
94
+ const skillArgs = arg0 === 'install' ? args.slice(1) : args;
95
+ const skillName = skillArgs.join(' ').trim();
96
+ if (!skillName) {
97
+ printUsage();
98
+ process.exit(1);
99
+ }
100
+
101
+ function run() {
102
+ const options = { outputDir };
103
+ ensureToDesktop(skillName, options)
104
+ .then((result) => {
105
+ if (result.ok) {
106
+ console.log('安装成功');
107
+ } else {
108
+ console.error('失败:', result.error);
109
+ process.exit(1);
110
+ }
111
+ })
112
+ .catch((err) => {
113
+ console.error(err.message || err);
114
+ process.exit(1);
115
+ });
116
+ }
117
+
118
+ (async () => {
119
+ const usingDefaultOpenClawDir =
120
+ !process.env[OUTPUT_DIR_ENV] &&
121
+ !process.env[LEGACY_OUTPUT_DIR_ENV] &&
122
+ !getConfiguredOutputDir();
123
+ if (usingDefaultOpenClawDir && !isOpenClawInstalled()) {
124
+ console.error(`未检测到 openclaw,请先安装 openclaw 或使用 ${CLI_NAME} set dir <路径> 指定安装目录。`);
125
+ process.exit(1);
126
+ }
127
+ const { list, error } = await ensureSkillList(skillName);
128
+ if (error) {
129
+ console.error('失败:', error);
130
+ process.exit(1);
131
+ }
132
+ const downloadable = list.filter((i) => i.downloadUrl && !i.error);
133
+ if (downloadable.length === 0) {
134
+ console.error(list.length ? '列表中暂无可用下载链接' : '未找到匹配的技能');
135
+ process.exit(1);
136
+ }
137
+ run();
138
+ })().catch((err) => {
139
+ console.error(err.message || err);
140
+ process.exit(1);
141
+ });
142
+ }
package/index.js ADDED
@@ -0,0 +1,304 @@
1
+ import { join } from 'path';
2
+ import { homedir, platform } from 'os';
3
+ import { existsSync, readFileSync } from 'fs';
4
+ import { mkdir, writeFile, unlink } from 'fs/promises';
5
+ import AdmZip from 'adm-zip';
6
+
7
+ // const DEFAULT_BASE_URL = 'http://localhost:8848';
8
+ const DEFAULT_BASE_URL = 'https://qa-imahub.imaclaw.ai';
9
+ const CONFIG_FILENAME = 'config.json';
10
+
11
+ function normalizeListItem(item = {}) {
12
+ const skill = item.skill && typeof item.skill === 'object' ? item.skill : item;
13
+ const latestVersion =
14
+ (item.latestVersion && typeof item.latestVersion === 'object' && item.latestVersion) ||
15
+ (item.version && typeof item.version === 'object' && item.version) ||
16
+ (skill.latestVersion && typeof skill.latestVersion === 'object' && skill.latestVersion) ||
17
+ null;
18
+
19
+ return {
20
+ name: item.displayName || skill.displayName || item.name || skill.name || skill.slug || item.slug || '',
21
+ slug: skill.slug || item.slug || '',
22
+ version:
23
+ (typeof latestVersion?.version === 'string' && latestVersion.version.trim()) ||
24
+ (typeof item.version === 'string' && item.version.trim()) ||
25
+ '',
26
+ downloadUrl: item.downloadUrl || skill.downloadUrl || skill.url || item.url || '',
27
+ error: item.error || skill.error || '',
28
+ };
29
+ }
30
+
31
+ function normalizeDetailItem(data = {}, requestedSlug = '') {
32
+ const skill = data.skill && typeof data.skill === 'object' ? data.skill : {};
33
+ const latestVersion = data.latestVersion && typeof data.latestVersion === 'object' ? data.latestVersion : {};
34
+
35
+ return {
36
+ name: skill.displayName || data.displayName || requestedSlug,
37
+ slug: skill.slug || data.resolvedSlug || requestedSlug,
38
+ version: (typeof latestVersion.version === 'string' && latestVersion.version.trim()) || '',
39
+ downloadUrl: data.downloadUrl || '',
40
+ error: data.error || '',
41
+ };
42
+ }
43
+
44
+ async function trackSkillInstallEvent(item = {}, source = 'imahub-cli') {
45
+ const slug = String(item?.slug || '').trim();
46
+ if (!slug) return;
47
+
48
+ const baseUrl = DEFAULT_BASE_URL.replace(/\/$/, '');
49
+ const url = `${baseUrl}/api/v1/skills/track/install`;
50
+ const version = typeof item?.version === 'string' ? item.version.trim() : '';
51
+
52
+ try {
53
+ await fetch(url, {
54
+ method: 'POST',
55
+ headers: { 'content-type': 'application/json' },
56
+ body: JSON.stringify({
57
+ slug,
58
+ source,
59
+ ...(version ? { version } : {}),
60
+ }),
61
+ });
62
+ } catch {
63
+ // 埋点失败不影响安装主流程
64
+ }
65
+ }
66
+
67
+ function pickAutoDownloadItem(skillName, list = []) {
68
+ if (!Array.isArray(list) || list.length === 0) return null;
69
+ if (list.length === 1) return list[0];
70
+
71
+ const query = String(skillName || '').trim().toLowerCase();
72
+ if (!query) return null;
73
+
74
+ const exactMatches = list.filter((item) => {
75
+ const slug = String(item.slug || '').trim().toLowerCase();
76
+ const name = String(item.name || '').trim().toLowerCase();
77
+ return slug === query || name === query;
78
+ });
79
+ if (exactMatches.length === 1) return exactMatches[0];
80
+
81
+ return list[0];
82
+ }
83
+
84
+ /**
85
+ * 获取 openclaw 根目录(skills 的父目录)
86
+ * Mac / Linux: ~/.openclaw,Windows: %USERPROFILE%\.openclaw
87
+ * @returns {string}
88
+ */
89
+ function getOpenClawBaseDir() {
90
+ const home = homedir();
91
+ if (platform() === 'win32') {
92
+ const base = process.env.USERPROFILE || home;
93
+ return join(base, '.openclaw');
94
+ }
95
+ return join(home, '.openclaw');
96
+ }
97
+
98
+ /**
99
+ * 检查当前电脑是否已存在 openclaw 目录(即是否“已安装” openclaw 环境)
100
+ * @returns {boolean}
101
+ */
102
+ export function isOpenClawInstalled() {
103
+ return existsSync(getOpenClawBaseDir());
104
+ }
105
+
106
+ /**
107
+ * 获取本机 openclaw 的 skills 目录(默认安装目录)
108
+ * Mac / Linux: ~/.openclaw/skills,Windows: %USERPROFILE%\.openclaw\skills
109
+ * @returns {string}
110
+ */
111
+ export function getOpenClawSkillsDir() {
112
+ return join(getOpenClawBaseDir(), 'skills');
113
+ }
114
+
115
+ /**
116
+ * 确保 openclaw skills 目录存在;若不存在则创建并返回目录路径
117
+ * @returns {Promise<string>}
118
+ */
119
+ export async function ensureOpenClawSkillsDir() {
120
+ const dir = getOpenClawSkillsDir();
121
+ await mkdir(dir, { recursive: true });
122
+ return dir;
123
+ }
124
+
125
+ /** imahub 配置目录:~/.imahub(兼容读取旧的 ~/.imaclawhub) */
126
+ function getImahubConfigDir() {
127
+ const home = homedir();
128
+ if (platform() === 'win32') {
129
+ const base = process.env.USERPROFILE || home;
130
+ return join(base, '.imahub');
131
+ }
132
+ return join(home, '.imahub');
133
+ }
134
+
135
+ function getLegacyImaclawhubConfigDir() {
136
+ const home = homedir();
137
+ if (platform() === 'win32') {
138
+ const base = process.env.USERPROFILE || home;
139
+ return join(base, '.imaclawhub');
140
+ }
141
+ return join(home, '.imaclawhub');
142
+ }
143
+
144
+ /**
145
+ * 读取通过 imahub set dir 设置的安装目录,未设置则返回 null
146
+ * 优先读取 ~/.imahub/config.json,兼容回退 ~/.imaclawhub/config.json
147
+ * @returns {string | null}
148
+ */
149
+ export function getConfiguredOutputDir() {
150
+ try {
151
+ const configPaths = [
152
+ join(getImahubConfigDir(), CONFIG_FILENAME),
153
+ join(getLegacyImaclawhubConfigDir(), CONFIG_FILENAME),
154
+ ];
155
+ for (const configPath of configPaths) {
156
+ if (!existsSync(configPath)) continue;
157
+ const raw = readFileSync(configPath, 'utf-8');
158
+ const data = JSON.parse(raw);
159
+ const dir = data?.outputDir;
160
+ if (typeof dir === 'string' && dir.trim()) {
161
+ return dir.trim();
162
+ }
163
+ }
164
+ return null;
165
+ } catch {
166
+ return null;
167
+ }
168
+ }
169
+
170
+ /**
171
+ * 保存安装目录(供 imahub set dir 使用)
172
+ * @param {string} dir - 绝对或相对路径
173
+ * @returns {Promise<void>}
174
+ */
175
+ export async function setConfiguredOutputDir(dir) {
176
+ const path = dir.trim();
177
+ if (!path) throw new Error('安装目录不能为空');
178
+ const configDir = getImahubConfigDir();
179
+ await mkdir(configDir, { recursive: true });
180
+ const configPath = join(configDir, CONFIG_FILENAME);
181
+ await writeFile(configPath, JSON.stringify({ outputDir: path }, null, 2), 'utf-8');
182
+ }
183
+
184
+ /**
185
+ * 清空已设置的安装目录(删除 ~/.imahub/config.json,并兼容清理旧的 ~/.imaclawhub/config.json)
186
+ * @returns {Promise<void>}
187
+ */
188
+ export async function clearConfiguredOutputDir() {
189
+ const configPaths = [
190
+ join(getImahubConfigDir(), CONFIG_FILENAME),
191
+ join(getLegacyImaclawhubConfigDir(), CONFIG_FILENAME),
192
+ ];
193
+ for (const configPath of configPaths) {
194
+ if (existsSync(configPath)) {
195
+ await unlink(configPath);
196
+ }
197
+ }
198
+ }
199
+
200
+ /**
201
+ * 通过 slug 精准详情接口获取技能下载信息
202
+ * @param {string} skillName - 技能 slug
203
+ * @returns {Promise<{ list: Array<{ name: string, slug: string, version?: string, downloadUrl?: string, error?: string }>, error?: string }>}
204
+ */
205
+ export async function ensureSkillList(skillName) {
206
+ const baseUrl = DEFAULT_BASE_URL.replace(/\/$/, '');
207
+ const slug = String(skillName).replace(/\s+/g, ' ').trim();
208
+ if (!slug) {
209
+ return { list: [], error: '缺少技能 slug' };
210
+ }
211
+
212
+ try {
213
+ const detailUrl = `${baseUrl}/api/v1/skills/${encodeURIComponent(slug)}`;
214
+ const res = await fetch(detailUrl);
215
+ const data = await res.json().catch(() => ({}));
216
+ if (res.ok) {
217
+ const item = normalizeDetailItem(data, slug);
218
+ if (item.slug && item.downloadUrl) {
219
+ return { list: [item] };
220
+ }
221
+ }
222
+ } catch {
223
+ // 详情接口异常时回退旧接口,避免完全不可用
224
+ }
225
+
226
+ const legacyUrl = `${baseUrl}/skills/ensure?name=${encodeURIComponent(slug)}`;
227
+ const legacyRes = await fetch(legacyUrl);
228
+ const legacyData = await legacyRes.json().catch(() => ({}));
229
+ if (!legacyRes.ok) {
230
+ return { list: [], error: legacyData.error || `HTTP ${legacyRes.status}` };
231
+ }
232
+ // 支持接口直接返回数组,或 { list } / { items }
233
+ const rawList = Array.isArray(legacyData) ? legacyData : (legacyData.list || legacyData.items || []);
234
+ return { list: rawList.map((item) => normalizeListItem(item)).filter((item) => item.slug || item.downloadUrl) };
235
+ }
236
+
237
+ /**
238
+ * 从 URL 下载 zip 并解压到指定目录
239
+ * @param {string} zipUrl - zip 下载地址
240
+ * @param {string} outDir - 解压目标目录
241
+ * @returns {Promise<{ ok: boolean, path?: string, error?: string }>}
242
+ */
243
+ export async function downloadAndExtractZip(zipUrl, outDir) {
244
+ const res = await fetch(zipUrl, { redirect: 'follow' });
245
+ if (!res.ok) {
246
+ return { ok: false, error: `下载失败: HTTP ${res.status}` };
247
+ }
248
+ const buf = Buffer.from(await res.arrayBuffer());
249
+ const zip = new AdmZip(buf);
250
+ await mkdir(outDir, { recursive: true });
251
+ zip.extractAllTo(outDir, true);
252
+ return { ok: true, path: outDir };
253
+ }
254
+
255
+ /**
256
+ * 通过 ensure 接口获取技能列表,自动选择结果并下载解压到指定目录
257
+ * 默认解压到本机 openclaw 的 skills 目录(~/.openclaw/skills/<名称>)
258
+ * @param {string} skillName - 技能名称或关键词
259
+ * @param {{ outputDir?: string, subdir?: string, selectedIndex?: number, item?: { slug: string, name?: string, downloadUrl?: string } }} options
260
+ * - outputDir: 解压目标父目录,默认 getOpenClawSkillsDir()(~/.openclaw/skills)
261
+ * - subdir: 解压后的文件夹名,默认使用输入的名称(如「AI 衣橱搭配」),为空则用 slug
262
+ * - selectedIndex: 兼容旧参数,传入时按指定序号下载
263
+ * - item: 兼容旧参数,传入时直接使用该项下载
264
+ * @returns {Promise<{ ok: boolean, path?: string, slug?: string, list?: Array, error?: string }>}
265
+ */
266
+ export async function ensureToDesktop(skillName, options = {}) {
267
+ // 每次安装都重新拉取列表
268
+ const result = await ensureSkillList(skillName);
269
+ if (result.error) return { ok: false, error: result.error };
270
+ const list = result.list;
271
+ const downloadable = list.filter((i) => i.downloadUrl && !i.error);
272
+ if (downloadable.length === 0) {
273
+ return {
274
+ ok: false,
275
+ error: list.length ? '列表中暂无可用下载链接' : '未找到匹配的技能',
276
+ list,
277
+ };
278
+ }
279
+
280
+ let item = options.item;
281
+ if (!item && options.selectedIndex != null) {
282
+ const idx = Number(options.selectedIndex);
283
+ if (idx >= 0 && idx < downloadable.length) item = downloadable[idx];
284
+ }
285
+ if (!item) {
286
+ item = pickAutoDownloadItem(skillName, downloadable);
287
+ }
288
+
289
+ const downloadUrl = item.downloadUrl;
290
+ if (!downloadUrl) {
291
+ return { ok: false, error: item.error || '该项无下载链接', slug: item.slug };
292
+ }
293
+
294
+ const baseDir = options.outputDir ?? getOpenClawSkillsDir();
295
+ // 优先使用用户输入的名称作为文件夹名,如 imahub install 'AI 衣橱搭配' → 解压到 .../AI 衣橱搭配
296
+ const subdir = options.subdir ?? (skillName.replace(/\s+/g, ' ').trim() || item.slug);
297
+ const outDir = join(baseDir, subdir);
298
+ const extractResult = await downloadAndExtractZip(downloadUrl, outDir);
299
+ if (!extractResult.ok) {
300
+ return { ok: false, error: extractResult.error, slug: item.slug };
301
+ }
302
+ await trackSkillInstallEvent(item);
303
+ return { ok: true, path: extractResult.path, slug: item.slug };
304
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "imahub-test",
3
+ "version": "1.0.1",
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
+ "imahub": "cli.js",
12
+ "imaclawhub": "cli.js"
13
+ },
14
+ "files": [
15
+ "index.js",
16
+ "cli.js",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "dev": "node cli.js",
21
+ "start": "node cli.js",
22
+ "publish:test": "node scripts/publish.mjs test",
23
+ "publish:prod": "node scripts/publish.mjs prod"
24
+ },
25
+ "keywords": [
26
+ "clawhub",
27
+ "skills",
28
+ "imahub",
29
+ "imaclawhub"
30
+ ],
31
+ "license": "MIT",
32
+ "publishConfig": {
33
+ "registry": "https://registry.npmjs.org/"
34
+ },
35
+ "engines": {
36
+ "node": ">=18.0.0"
37
+ },
38
+ "dependencies": {
39
+ "adm-zip": "^0.5.16"
40
+ }
41
+ }