soulhubcli 1.0.4 → 1.0.6
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/dist/index.js +492 -117
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,26 +8,158 @@ import { Command } from "commander";
|
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
|
|
10
10
|
// src/utils.ts
|
|
11
|
-
import
|
|
12
|
-
import
|
|
11
|
+
import fs2 from "fs";
|
|
12
|
+
import path2 from "path";
|
|
13
13
|
import { execSync } from "child_process";
|
|
14
14
|
import yaml from "js-yaml";
|
|
15
|
+
|
|
16
|
+
// src/logger.ts
|
|
17
|
+
import fs from "fs";
|
|
18
|
+
import path from "path";
|
|
19
|
+
var LEVEL_PRIORITY = {
|
|
20
|
+
debug: 0,
|
|
21
|
+
info: 1,
|
|
22
|
+
warn: 2,
|
|
23
|
+
error: 3
|
|
24
|
+
};
|
|
25
|
+
var LOG_RETENTION_DAYS = 7;
|
|
26
|
+
var Logger = class {
|
|
27
|
+
logDir;
|
|
28
|
+
verbose = false;
|
|
29
|
+
initialized = false;
|
|
30
|
+
constructor() {
|
|
31
|
+
const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
|
|
32
|
+
this.logDir = path.join(home, ".soulhub", "logs");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 初始化日志系统
|
|
36
|
+
* @param verbose 是否开启 debug 级别输出到终端
|
|
37
|
+
*/
|
|
38
|
+
init(verbose = false) {
|
|
39
|
+
this.verbose = verbose;
|
|
40
|
+
if (!fs.existsSync(this.logDir)) {
|
|
41
|
+
fs.mkdirSync(this.logDir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
this.initialized = true;
|
|
44
|
+
this.cleanOldLogs();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 获取当前日志文件路径(按日期)
|
|
48
|
+
*/
|
|
49
|
+
getLogFilePath() {
|
|
50
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
51
|
+
return path.join(this.logDir, `soulhub-${date}.log`);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 格式化日志行
|
|
55
|
+
*/
|
|
56
|
+
formatLine(level, message, meta) {
|
|
57
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
58
|
+
const levelTag = level.toUpperCase().padEnd(5);
|
|
59
|
+
let line = `[${timestamp}] ${levelTag} ${message}`;
|
|
60
|
+
if (meta && Object.keys(meta).length > 0) {
|
|
61
|
+
line += ` | ${JSON.stringify(meta)}`;
|
|
62
|
+
}
|
|
63
|
+
return line;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 写入日志到文件
|
|
67
|
+
*/
|
|
68
|
+
write(level, message, meta) {
|
|
69
|
+
if (!this.initialized) {
|
|
70
|
+
try {
|
|
71
|
+
this.init(this.verbose);
|
|
72
|
+
} catch {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const line = this.formatLine(level, message, meta);
|
|
77
|
+
try {
|
|
78
|
+
fs.appendFileSync(this.getLogFilePath(), line + "\n");
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
if (this.verbose && LEVEL_PRIORITY[level] === LEVEL_PRIORITY.debug) {
|
|
82
|
+
console.log(` ${line}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 清理超过保留天数的旧日志
|
|
87
|
+
*/
|
|
88
|
+
cleanOldLogs() {
|
|
89
|
+
try {
|
|
90
|
+
const files = fs.readdirSync(this.logDir);
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
const maxAge = LOG_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
93
|
+
for (const file of files) {
|
|
94
|
+
if (!file.startsWith("soulhub-") || !file.endsWith(".log")) continue;
|
|
95
|
+
const filePath = path.join(this.logDir, file);
|
|
96
|
+
const stat = fs.statSync(filePath);
|
|
97
|
+
if (now - stat.mtimeMs > maxAge) {
|
|
98
|
+
fs.unlinkSync(filePath);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// ==========================================
|
|
105
|
+
// 公开的日志方法
|
|
106
|
+
// ==========================================
|
|
107
|
+
debug(message, meta) {
|
|
108
|
+
this.write("debug", message, meta);
|
|
109
|
+
}
|
|
110
|
+
info(message, meta) {
|
|
111
|
+
this.write("info", message, meta);
|
|
112
|
+
}
|
|
113
|
+
warn(message, meta) {
|
|
114
|
+
this.write("warn", message, meta);
|
|
115
|
+
}
|
|
116
|
+
error(message, meta) {
|
|
117
|
+
this.write("error", message, meta);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 记录错误对象(自动提取 message 和 stack)
|
|
121
|
+
*/
|
|
122
|
+
errorObj(message, err) {
|
|
123
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
124
|
+
const stack = err instanceof Error ? err.stack : void 0;
|
|
125
|
+
this.write("error", message, { error: errMsg, stack });
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 获取日志目录路径(供用户查看)
|
|
129
|
+
*/
|
|
130
|
+
getLogDir() {
|
|
131
|
+
return this.logDir;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 获取今天的日志文件路径
|
|
135
|
+
*/
|
|
136
|
+
getTodayLogFile() {
|
|
137
|
+
return this.getLogFilePath();
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
var logger = new Logger();
|
|
141
|
+
|
|
142
|
+
// src/utils.ts
|
|
15
143
|
var DEFAULT_REGISTRY_URL = "http://soulhub.store";
|
|
16
144
|
function getRegistryUrl() {
|
|
17
145
|
return process.env.SOULHUB_REGISTRY_URL || DEFAULT_REGISTRY_URL;
|
|
18
146
|
}
|
|
19
147
|
async function fetchIndex() {
|
|
20
148
|
const url = `${getRegistryUrl()}/index.json`;
|
|
149
|
+
logger.debug(`Fetching registry index`, { url });
|
|
21
150
|
const response = await fetch(url);
|
|
22
151
|
if (!response.ok) {
|
|
152
|
+
logger.error(`Failed to fetch registry index`, { url, status: response.status, statusText: response.statusText });
|
|
23
153
|
throw new Error(`Failed to fetch registry index: ${response.statusText}`);
|
|
24
154
|
}
|
|
25
155
|
return await response.json();
|
|
26
156
|
}
|
|
27
157
|
async function fetchAgentFile(agentName, fileName) {
|
|
28
158
|
const url = `${getRegistryUrl()}/agents/${agentName}/${fileName}`;
|
|
159
|
+
logger.debug(`Fetching agent file`, { agentName, fileName, url });
|
|
29
160
|
const response = await fetch(url);
|
|
30
161
|
if (!response.ok) {
|
|
162
|
+
logger.error(`Failed to fetch agent file`, { agentName, fileName, url, status: response.status });
|
|
31
163
|
throw new Error(
|
|
32
164
|
`Failed to fetch ${fileName} for ${agentName}: ${response.statusText}`
|
|
33
165
|
);
|
|
@@ -36,8 +168,10 @@ async function fetchAgentFile(agentName, fileName) {
|
|
|
36
168
|
}
|
|
37
169
|
async function fetchRecipeFile(recipeName, fileName) {
|
|
38
170
|
const url = `${getRegistryUrl()}/recipes/${recipeName}/${fileName}`;
|
|
171
|
+
logger.debug(`Fetching recipe file`, { recipeName, fileName, url });
|
|
39
172
|
const response = await fetch(url);
|
|
40
173
|
if (!response.ok) {
|
|
174
|
+
logger.error(`Failed to fetch recipe file`, { recipeName, fileName, url, status: response.status });
|
|
41
175
|
throw new Error(
|
|
42
176
|
`Failed to fetch ${fileName} for recipe ${recipeName}: ${response.statusText}`
|
|
43
177
|
);
|
|
@@ -46,26 +180,26 @@ async function fetchRecipeFile(recipeName, fileName) {
|
|
|
46
180
|
}
|
|
47
181
|
function findOpenClawDir(customDir) {
|
|
48
182
|
if (customDir) {
|
|
49
|
-
const resolved =
|
|
50
|
-
if (
|
|
183
|
+
const resolved = path2.resolve(customDir);
|
|
184
|
+
if (fs2.existsSync(resolved)) {
|
|
51
185
|
return resolved;
|
|
52
186
|
}
|
|
53
187
|
return resolved;
|
|
54
188
|
}
|
|
55
189
|
const envHome = process.env.OPENCLAW_HOME;
|
|
56
190
|
if (envHome) {
|
|
57
|
-
const resolved =
|
|
58
|
-
if (
|
|
191
|
+
const resolved = path2.resolve(envHome);
|
|
192
|
+
if (fs2.existsSync(resolved)) {
|
|
59
193
|
return resolved;
|
|
60
194
|
}
|
|
61
195
|
return resolved;
|
|
62
196
|
}
|
|
63
197
|
const candidates = [
|
|
64
|
-
|
|
65
|
-
|
|
198
|
+
path2.join(process.env.HOME || "~", ".openclaw"),
|
|
199
|
+
path2.join(process.cwd(), ".openclaw")
|
|
66
200
|
];
|
|
67
201
|
for (const candidate of candidates) {
|
|
68
|
-
if (
|
|
202
|
+
if (fs2.existsSync(candidate)) {
|
|
69
203
|
return candidate;
|
|
70
204
|
}
|
|
71
205
|
}
|
|
@@ -73,12 +207,12 @@ function findOpenClawDir(customDir) {
|
|
|
73
207
|
}
|
|
74
208
|
function getConfigPath() {
|
|
75
209
|
const home = process.env.HOME || "~";
|
|
76
|
-
return
|
|
210
|
+
return path2.join(home, ".soulhub", "config.json");
|
|
77
211
|
}
|
|
78
212
|
function loadConfig() {
|
|
79
213
|
const configPath = getConfigPath();
|
|
80
|
-
if (
|
|
81
|
-
return JSON.parse(
|
|
214
|
+
if (fs2.existsSync(configPath)) {
|
|
215
|
+
return JSON.parse(fs2.readFileSync(configPath, "utf-8"));
|
|
82
216
|
}
|
|
83
217
|
return {
|
|
84
218
|
installed: [],
|
|
@@ -87,11 +221,11 @@ function loadConfig() {
|
|
|
87
221
|
}
|
|
88
222
|
function saveConfig(config) {
|
|
89
223
|
const configPath = getConfigPath();
|
|
90
|
-
const configDir =
|
|
91
|
-
if (!
|
|
92
|
-
|
|
224
|
+
const configDir = path2.dirname(configPath);
|
|
225
|
+
if (!fs2.existsSync(configDir)) {
|
|
226
|
+
fs2.mkdirSync(configDir, { recursive: true });
|
|
93
227
|
}
|
|
94
|
-
|
|
228
|
+
fs2.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
95
229
|
}
|
|
96
230
|
function recordInstall(name, version, workspace) {
|
|
97
231
|
const config = loadConfig();
|
|
@@ -103,6 +237,7 @@ function recordInstall(name, version, workspace) {
|
|
|
103
237
|
workspace
|
|
104
238
|
});
|
|
105
239
|
saveConfig(config);
|
|
240
|
+
logger.debug(`Recorded install`, { name, version, workspace });
|
|
106
241
|
}
|
|
107
242
|
function removeInstallRecord(name) {
|
|
108
243
|
const config = loadConfig();
|
|
@@ -110,17 +245,17 @@ function removeInstallRecord(name) {
|
|
|
110
245
|
saveConfig(config);
|
|
111
246
|
}
|
|
112
247
|
function getWorkspaceDir(clawDir, agentName) {
|
|
113
|
-
return
|
|
248
|
+
return path2.join(clawDir, `workspace-${agentName}`);
|
|
114
249
|
}
|
|
115
250
|
function getMainWorkspaceDir(clawDir) {
|
|
116
|
-
return
|
|
251
|
+
return path2.join(clawDir, "workspace");
|
|
117
252
|
}
|
|
118
253
|
function checkMainAgentExists(clawDir) {
|
|
119
254
|
const workspaceDir = getMainWorkspaceDir(clawDir);
|
|
120
|
-
if (!
|
|
255
|
+
if (!fs2.existsSync(workspaceDir)) {
|
|
121
256
|
return { exists: false, hasContent: false, workspaceDir };
|
|
122
257
|
}
|
|
123
|
-
const entries =
|
|
258
|
+
const entries = fs2.readdirSync(workspaceDir);
|
|
124
259
|
const hasIdentity = entries.includes("IDENTITY.md");
|
|
125
260
|
const hasSoul = entries.includes("SOUL.md");
|
|
126
261
|
return {
|
|
@@ -130,22 +265,22 @@ function checkMainAgentExists(clawDir) {
|
|
|
130
265
|
};
|
|
131
266
|
}
|
|
132
267
|
function getOpenClawConfigPath(clawDir) {
|
|
133
|
-
return
|
|
268
|
+
return path2.join(clawDir, "openclaw.json");
|
|
134
269
|
}
|
|
135
270
|
function readOpenClawConfig(clawDir) {
|
|
136
271
|
const configPath = getOpenClawConfigPath(clawDir);
|
|
137
|
-
if (!
|
|
272
|
+
if (!fs2.existsSync(configPath)) {
|
|
138
273
|
return null;
|
|
139
274
|
}
|
|
140
275
|
try {
|
|
141
|
-
return JSON.parse(
|
|
276
|
+
return JSON.parse(fs2.readFileSync(configPath, "utf-8"));
|
|
142
277
|
} catch {
|
|
143
278
|
return null;
|
|
144
279
|
}
|
|
145
280
|
}
|
|
146
281
|
function writeOpenClawConfig(clawDir, config) {
|
|
147
282
|
const configPath = getOpenClawConfigPath(clawDir);
|
|
148
|
-
|
|
283
|
+
fs2.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
149
284
|
}
|
|
150
285
|
function updateOpenClawConfig(clawDir, updater) {
|
|
151
286
|
const config = readOpenClawConfig(clawDir);
|
|
@@ -198,20 +333,20 @@ function addAgentToOpenClawConfig(clawDir, agentId, agentName, isMain) {
|
|
|
198
333
|
config.agents.list.push({
|
|
199
334
|
id: agentId,
|
|
200
335
|
name: agentName,
|
|
201
|
-
workspace:
|
|
202
|
-
agentDir:
|
|
336
|
+
workspace: path2.join(clawDir, `workspace-${agentId}`),
|
|
337
|
+
agentDir: path2.join(clawDir, `agents/${agentId}/agent`)
|
|
203
338
|
});
|
|
204
339
|
}
|
|
205
340
|
return config;
|
|
206
341
|
});
|
|
207
342
|
}
|
|
208
343
|
function readSoulHubPackage(dir) {
|
|
209
|
-
const yamlPath =
|
|
210
|
-
if (!
|
|
344
|
+
const yamlPath = path2.join(dir, "soulhub.yaml");
|
|
345
|
+
if (!fs2.existsSync(yamlPath)) {
|
|
211
346
|
return null;
|
|
212
347
|
}
|
|
213
348
|
try {
|
|
214
|
-
return yaml.load(
|
|
349
|
+
return yaml.load(fs2.readFileSync(yamlPath, "utf-8"));
|
|
215
350
|
} catch {
|
|
216
351
|
return null;
|
|
217
352
|
}
|
|
@@ -221,7 +356,7 @@ function detectPackageKind(dir) {
|
|
|
221
356
|
if (pkg) {
|
|
222
357
|
return pkg.kind;
|
|
223
358
|
}
|
|
224
|
-
if (
|
|
359
|
+
if (fs2.existsSync(path2.join(dir, "IDENTITY.md"))) {
|
|
225
360
|
return "agent";
|
|
226
361
|
}
|
|
227
362
|
return "unknown";
|
|
@@ -253,27 +388,136 @@ function checkOpenClawInstalled(customDir) {
|
|
|
253
388
|
};
|
|
254
389
|
}
|
|
255
390
|
function backupAgentWorkspace(workspaceDir) {
|
|
256
|
-
if (!
|
|
391
|
+
if (!fs2.existsSync(workspaceDir)) {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
const entries = fs2.readdirSync(workspaceDir);
|
|
395
|
+
if (entries.length === 0) {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
const clawDir = path2.dirname(workspaceDir);
|
|
399
|
+
const backupBaseDir = path2.join(clawDir, "agentbackup");
|
|
400
|
+
if (!fs2.existsSync(backupBaseDir)) {
|
|
401
|
+
fs2.mkdirSync(backupBaseDir, { recursive: true });
|
|
402
|
+
}
|
|
403
|
+
const dirName = path2.basename(workspaceDir);
|
|
404
|
+
let backupDir = path2.join(backupBaseDir, dirName);
|
|
405
|
+
if (fs2.existsSync(backupDir)) {
|
|
406
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
407
|
+
backupDir = path2.join(backupBaseDir, `${dirName}-${timestamp}`);
|
|
408
|
+
}
|
|
409
|
+
fs2.cpSync(workspaceDir, backupDir, { recursive: true });
|
|
410
|
+
logger.info(`Workspace backed up`, { from: workspaceDir, to: backupDir });
|
|
411
|
+
return backupDir;
|
|
412
|
+
}
|
|
413
|
+
function moveBackupAgentWorkspace(workspaceDir) {
|
|
414
|
+
if (!fs2.existsSync(workspaceDir)) {
|
|
257
415
|
return null;
|
|
258
416
|
}
|
|
259
|
-
const entries =
|
|
417
|
+
const entries = fs2.readdirSync(workspaceDir);
|
|
260
418
|
if (entries.length === 0) {
|
|
261
419
|
return null;
|
|
262
420
|
}
|
|
263
|
-
const clawDir =
|
|
264
|
-
const backupBaseDir =
|
|
265
|
-
if (!
|
|
266
|
-
|
|
421
|
+
const clawDir = path2.dirname(workspaceDir);
|
|
422
|
+
const backupBaseDir = path2.join(clawDir, "agentbackup");
|
|
423
|
+
if (!fs2.existsSync(backupBaseDir)) {
|
|
424
|
+
fs2.mkdirSync(backupBaseDir, { recursive: true });
|
|
267
425
|
}
|
|
268
|
-
const dirName =
|
|
269
|
-
let backupDir =
|
|
270
|
-
if (
|
|
426
|
+
const dirName = path2.basename(workspaceDir);
|
|
427
|
+
let backupDir = path2.join(backupBaseDir, dirName);
|
|
428
|
+
if (fs2.existsSync(backupDir)) {
|
|
271
429
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
272
|
-
backupDir =
|
|
430
|
+
backupDir = path2.join(backupBaseDir, `${dirName}-${timestamp}`);
|
|
273
431
|
}
|
|
274
|
-
|
|
432
|
+
fs2.renameSync(workspaceDir, backupDir);
|
|
433
|
+
logger.info(`Workspace moved to backup`, { from: workspaceDir, to: backupDir });
|
|
275
434
|
return backupDir;
|
|
276
435
|
}
|
|
436
|
+
function backupAllWorkerWorkspaces(clawDir) {
|
|
437
|
+
const results = [];
|
|
438
|
+
const workerDirs = listAgentWorkspaces(clawDir);
|
|
439
|
+
for (const dirName of workerDirs) {
|
|
440
|
+
const fullPath = path2.join(clawDir, dirName);
|
|
441
|
+
const backupDir = moveBackupAgentWorkspace(fullPath);
|
|
442
|
+
if (backupDir) {
|
|
443
|
+
results.push({ name: dirName, backupDir });
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return results;
|
|
447
|
+
}
|
|
448
|
+
function listAgentWorkspaces(clawDir) {
|
|
449
|
+
if (!fs2.existsSync(clawDir)) {
|
|
450
|
+
return [];
|
|
451
|
+
}
|
|
452
|
+
return fs2.readdirSync(clawDir).filter((entry) => {
|
|
453
|
+
const fullPath = path2.join(clawDir, entry);
|
|
454
|
+
return fs2.statSync(fullPath).isDirectory() && entry.startsWith("workspace-");
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
function getBackupManifestPath() {
|
|
458
|
+
const home = process.env.HOME || "~";
|
|
459
|
+
return path2.join(home, ".soulhub", "backups.json");
|
|
460
|
+
}
|
|
461
|
+
function loadBackupManifest() {
|
|
462
|
+
const manifestPath = getBackupManifestPath();
|
|
463
|
+
if (fs2.existsSync(manifestPath)) {
|
|
464
|
+
try {
|
|
465
|
+
return JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
|
|
466
|
+
} catch {
|
|
467
|
+
return { records: [] };
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return { records: [] };
|
|
471
|
+
}
|
|
472
|
+
function saveBackupManifest(manifest) {
|
|
473
|
+
const manifestPath = getBackupManifestPath();
|
|
474
|
+
const manifestDir = path2.dirname(manifestPath);
|
|
475
|
+
if (!fs2.existsSync(manifestDir)) {
|
|
476
|
+
fs2.mkdirSync(manifestDir, { recursive: true });
|
|
477
|
+
}
|
|
478
|
+
manifest.records = manifest.records.slice(0, 50);
|
|
479
|
+
fs2.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
480
|
+
}
|
|
481
|
+
function generateBackupId() {
|
|
482
|
+
const now = /* @__PURE__ */ new Date();
|
|
483
|
+
const ts = now.toISOString().replace(/[:.\-T]/g, "").slice(0, 14);
|
|
484
|
+
const rand = Math.random().toString(36).slice(2, 6);
|
|
485
|
+
return `${ts}-${rand}`;
|
|
486
|
+
}
|
|
487
|
+
function createBackupRecord(installType, packageName, clawDir) {
|
|
488
|
+
let openclawJsonSnapshot = null;
|
|
489
|
+
const configPath = path2.join(clawDir, "openclaw.json");
|
|
490
|
+
if (fs2.existsSync(configPath)) {
|
|
491
|
+
try {
|
|
492
|
+
openclawJsonSnapshot = fs2.readFileSync(configPath, "utf-8");
|
|
493
|
+
} catch {
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return {
|
|
497
|
+
id: generateBackupId(),
|
|
498
|
+
installType,
|
|
499
|
+
packageName,
|
|
500
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
501
|
+
clawDir,
|
|
502
|
+
openclawJsonSnapshot,
|
|
503
|
+
items: [],
|
|
504
|
+
installedWorkerIds: [],
|
|
505
|
+
installedMainAgent: null
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
function addBackupItem(record, item) {
|
|
509
|
+
record.items.push(item);
|
|
510
|
+
}
|
|
511
|
+
function commitBackupRecord(record) {
|
|
512
|
+
if (record.items.length === 0 && record.installedWorkerIds.length === 0 && !record.installedMainAgent) {
|
|
513
|
+
logger.debug("No backup items to record, skipping.");
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const manifest = loadBackupManifest();
|
|
517
|
+
manifest.records.unshift(record);
|
|
518
|
+
saveBackupManifest(manifest);
|
|
519
|
+
logger.info(`Backup record saved`, { id: record.id, items: record.items.length, workers: record.installedWorkerIds.length });
|
|
520
|
+
}
|
|
277
521
|
var CATEGORY_LABELS = {
|
|
278
522
|
"self-media": "Self Media",
|
|
279
523
|
development: "Development",
|
|
@@ -284,6 +528,7 @@ var CATEGORY_LABELS = {
|
|
|
284
528
|
};
|
|
285
529
|
function registerAgentToOpenClaw(agentName, workspaceDir, _clawDir) {
|
|
286
530
|
const agentId = agentName.toLowerCase().replace(/[\s_]+/g, "-");
|
|
531
|
+
logger.debug(`Registering agent to OpenClaw`, { agentId, workspaceDir });
|
|
287
532
|
try {
|
|
288
533
|
execSync(
|
|
289
534
|
`openclaw agents add "${agentId}" --workspace "${workspaceDir}" --non-interactive --json`,
|
|
@@ -303,12 +548,14 @@ function registerAgentToOpenClaw(agentName, workspaceDir, _clawDir) {
|
|
|
303
548
|
}
|
|
304
549
|
const isCommandNotFound = cliError && typeof cliError === "object" && "code" in cliError && cliError.code === "ENOENT" || stderr.includes("not found") || stderr.includes("not recognized");
|
|
305
550
|
if (isCommandNotFound) {
|
|
551
|
+
logger.error(`OpenClaw CLI not found`);
|
|
306
552
|
return {
|
|
307
553
|
success: false,
|
|
308
554
|
message: "OpenClaw CLI not found. Please install OpenClaw first: https://github.com/anthropics/openclaw"
|
|
309
555
|
};
|
|
310
556
|
}
|
|
311
557
|
const errMsg = cliError instanceof Error ? cliError.message : String(cliError);
|
|
558
|
+
logger.error(`openclaw agents add failed`, { agentId, stderr, error: errMsg });
|
|
312
559
|
return {
|
|
313
560
|
success: false,
|
|
314
561
|
message: `openclaw agents add failed: ${stderr || errMsg}`
|
|
@@ -316,6 +563,7 @@ function registerAgentToOpenClaw(agentName, workspaceDir, _clawDir) {
|
|
|
316
563
|
}
|
|
317
564
|
}
|
|
318
565
|
function restartOpenClawGateway() {
|
|
566
|
+
logger.debug(`Restarting OpenClaw Gateway`);
|
|
319
567
|
try {
|
|
320
568
|
execSync("openclaw gateway restart", {
|
|
321
569
|
stdio: "pipe",
|
|
@@ -393,9 +641,11 @@ var searchCommand = new Command("search").description("Search for agent template
|
|
|
393
641
|
);
|
|
394
642
|
console.log();
|
|
395
643
|
} catch (error) {
|
|
644
|
+
logger.errorObj("Search command failed", error);
|
|
396
645
|
console.error(
|
|
397
646
|
chalk.red(`Error: ${error instanceof Error ? error.message : error}`)
|
|
398
647
|
);
|
|
648
|
+
console.error(chalk.dim(` See logs: ${logger.getTodayLogFile()}`));
|
|
399
649
|
process.exit(1);
|
|
400
650
|
}
|
|
401
651
|
});
|
|
@@ -466,9 +716,11 @@ var infoCommand = new Command2("info").description("View detailed information ab
|
|
|
466
716
|
);
|
|
467
717
|
console.log();
|
|
468
718
|
} catch (error) {
|
|
719
|
+
logger.errorObj("Info command failed", error);
|
|
469
720
|
console.error(
|
|
470
721
|
chalk2.red(`Error: ${error instanceof Error ? error.message : error}`)
|
|
471
722
|
);
|
|
723
|
+
console.error(chalk2.dim(` See logs: ${logger.getTodayLogFile()}`));
|
|
472
724
|
process.exit(1);
|
|
473
725
|
}
|
|
474
726
|
});
|
|
@@ -477,8 +729,8 @@ var infoCommand = new Command2("info").description("View detailed information ab
|
|
|
477
729
|
import { Command as Command3 } from "commander";
|
|
478
730
|
import chalk3 from "chalk";
|
|
479
731
|
import ora from "ora";
|
|
480
|
-
import
|
|
481
|
-
import
|
|
732
|
+
import fs3 from "fs";
|
|
733
|
+
import path3 from "path";
|
|
482
734
|
import yaml2 from "js-yaml";
|
|
483
735
|
var installCommand = new Command3("install").description("Install an agent or team from the SoulHub registry").argument("[name]", "Agent or team name to install").option("--from <source>", "Install from a local directory, ZIP file, or URL").option(
|
|
484
736
|
"--dir <path>",
|
|
@@ -501,9 +753,11 @@ var installCommand = new Command3("install").description("Install an agent or te
|
|
|
501
753
|
process.exit(1);
|
|
502
754
|
}
|
|
503
755
|
} catch (error) {
|
|
756
|
+
logger.errorObj("Install command failed", error);
|
|
504
757
|
console.error(
|
|
505
758
|
chalk3.red(`Error: ${error instanceof Error ? error.message : error}`)
|
|
506
759
|
);
|
|
760
|
+
console.error(chalk3.dim(` See logs: ${logger.getTodayLogFile()}`));
|
|
507
761
|
process.exit(1);
|
|
508
762
|
}
|
|
509
763
|
});
|
|
@@ -514,11 +768,14 @@ async function installFromRegistry(name, targetDir, clawDir) {
|
|
|
514
768
|
const recipe = index.recipes.find((r) => r.name === name);
|
|
515
769
|
if (agent && !recipe) {
|
|
516
770
|
spinner.stop();
|
|
771
|
+
logger.info(`Installing single agent from registry: ${name}`);
|
|
517
772
|
await installSingleAgent(name, targetDir, clawDir);
|
|
518
773
|
} else if (recipe) {
|
|
519
774
|
spinner.stop();
|
|
775
|
+
logger.info(`Installing team recipe from registry: ${name}`);
|
|
520
776
|
await installRecipeFromRegistry(name, recipe, targetDir, clawDir);
|
|
521
777
|
} else {
|
|
778
|
+
logger.warn(`"${name}" not found in registry`);
|
|
522
779
|
spinner.fail(`"${name}" not found in registry.`);
|
|
523
780
|
console.log(chalk3.dim(" Use 'soulhub search' to find available agents and teams."));
|
|
524
781
|
}
|
|
@@ -544,7 +801,7 @@ async function installSingleAgent(name, targetDir, clawDir) {
|
|
|
544
801
|
}
|
|
545
802
|
let workspaceDir;
|
|
546
803
|
if (targetDir) {
|
|
547
|
-
workspaceDir =
|
|
804
|
+
workspaceDir = path3.resolve(targetDir);
|
|
548
805
|
} else {
|
|
549
806
|
const resolvedClawDir = findOpenClawDir(clawDir);
|
|
550
807
|
if (!resolvedClawDir) {
|
|
@@ -554,9 +811,10 @@ async function installSingleAgent(name, targetDir, clawDir) {
|
|
|
554
811
|
}
|
|
555
812
|
workspaceDir = getMainWorkspaceDir(resolvedClawDir);
|
|
556
813
|
}
|
|
814
|
+
const resolvedClawDirForBackup = findOpenClawDir(clawDir);
|
|
815
|
+
const backupRecord = !targetDir ? createBackupRecord("single-agent", name, resolvedClawDirForBackup) : null;
|
|
557
816
|
if (!targetDir) {
|
|
558
|
-
const
|
|
559
|
-
const mainCheck = checkMainAgentExists(resolvedClawDir);
|
|
817
|
+
const mainCheck = checkMainAgentExists(resolvedClawDirForBackup);
|
|
560
818
|
if (mainCheck.hasContent) {
|
|
561
819
|
spinner.warn(
|
|
562
820
|
`Existing main agent detected. Backing up workspace...`
|
|
@@ -564,6 +822,13 @@ async function installSingleAgent(name, targetDir, clawDir) {
|
|
|
564
822
|
const backupDir = backupAgentWorkspace(workspaceDir);
|
|
565
823
|
if (backupDir) {
|
|
566
824
|
console.log(chalk3.yellow(` \u26A0 Existing main agent backed up to: ${backupDir}`));
|
|
825
|
+
addBackupItem(backupRecord, {
|
|
826
|
+
originalPath: workspaceDir,
|
|
827
|
+
backupPath: backupDir,
|
|
828
|
+
method: "cp",
|
|
829
|
+
role: "main",
|
|
830
|
+
agentId: "main"
|
|
831
|
+
});
|
|
567
832
|
}
|
|
568
833
|
}
|
|
569
834
|
} else {
|
|
@@ -572,8 +837,8 @@ async function installSingleAgent(name, targetDir, clawDir) {
|
|
|
572
837
|
console.log(chalk3.yellow(` \u26A0 Existing agent backed up to: ${backupDir}`));
|
|
573
838
|
}
|
|
574
839
|
}
|
|
575
|
-
if (!
|
|
576
|
-
|
|
840
|
+
if (!fs3.existsSync(workspaceDir)) {
|
|
841
|
+
fs3.mkdirSync(workspaceDir, { recursive: true });
|
|
577
842
|
}
|
|
578
843
|
if (!targetDir) {
|
|
579
844
|
spinner.text = `Registering ${chalk3.cyan(agent.displayName)} as main agent...`;
|
|
@@ -585,6 +850,11 @@ async function installSingleAgent(name, targetDir, clawDir) {
|
|
|
585
850
|
await downloadAgentFiles(name, workspaceDir, spinner);
|
|
586
851
|
await saveAgentManifest(name, agent, workspaceDir);
|
|
587
852
|
recordInstall(name, agent.version, workspaceDir);
|
|
853
|
+
if (backupRecord) {
|
|
854
|
+
backupRecord.installedMainAgent = name;
|
|
855
|
+
commitBackupRecord(backupRecord);
|
|
856
|
+
}
|
|
857
|
+
logger.info(`Single agent installed: ${name}`, { version: agent.version, workspace: workspaceDir });
|
|
588
858
|
spinner.succeed(
|
|
589
859
|
`${chalk3.cyan.bold(agent.displayName)} installed as main agent!`
|
|
590
860
|
);
|
|
@@ -607,7 +877,7 @@ async function installRecipeFromRegistry(name, recipe, targetDir, clawDir) {
|
|
|
607
877
|
return;
|
|
608
878
|
}
|
|
609
879
|
}
|
|
610
|
-
const resolvedClawDir = targetDir ?
|
|
880
|
+
const resolvedClawDir = targetDir ? path3.resolve(targetDir) : findOpenClawDir(clawDir);
|
|
611
881
|
if (!resolvedClawDir) {
|
|
612
882
|
spinner.fail("OpenClaw workspace directory not found.");
|
|
613
883
|
printOpenClawInstallHelp();
|
|
@@ -622,9 +892,29 @@ async function installRecipeFromRegistry(name, recipe, targetDir, clawDir) {
|
|
|
622
892
|
spinner.fail(`Failed to fetch soulhub.yaml for recipe "${name}". Recipe packages must include a soulhub.yaml file.`);
|
|
623
893
|
return;
|
|
624
894
|
}
|
|
895
|
+
const recipeBackupRecord = !targetDir ? createBackupRecord("team-registry", name, resolvedClawDir) : null;
|
|
896
|
+
if (!targetDir) {
|
|
897
|
+
spinner.text = "Backing up existing worker agents...";
|
|
898
|
+
const backupResults = backupAllWorkerWorkspaces(resolvedClawDir);
|
|
899
|
+
for (const { name: dirName, backupDir } of backupResults) {
|
|
900
|
+
logger.info(`Existing worker backed up (mv)`, { dirName, backupDir });
|
|
901
|
+
console.log(chalk3.yellow(` \u26A0 Existing ${dirName} moved to: ${backupDir}`));
|
|
902
|
+
const agentId = dirName.replace(/^workspace-/, "");
|
|
903
|
+
addBackupItem(recipeBackupRecord, {
|
|
904
|
+
originalPath: path3.join(resolvedClawDir, dirName),
|
|
905
|
+
backupPath: backupDir,
|
|
906
|
+
method: "mv",
|
|
907
|
+
role: "worker",
|
|
908
|
+
agentId
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
if (backupResults.length > 0) {
|
|
912
|
+
console.log(chalk3.dim(` ${backupResults.length} existing worker(s) backed up.`));
|
|
913
|
+
}
|
|
914
|
+
}
|
|
625
915
|
if (pkg.dispatcher) {
|
|
626
916
|
spinner.text = `Installing dispatcher ${chalk3.blue(pkg.dispatcher.name)}...`;
|
|
627
|
-
await installDispatcher(pkg.dispatcher, resolvedClawDir, clawDir, targetDir, spinner);
|
|
917
|
+
await installDispatcher(pkg.dispatcher, resolvedClawDir, clawDir, targetDir, spinner, recipeBackupRecord);
|
|
628
918
|
}
|
|
629
919
|
const index = await fetchIndex();
|
|
630
920
|
const workerIds = [];
|
|
@@ -632,11 +922,7 @@ async function installRecipeFromRegistry(name, recipe, targetDir, clawDir) {
|
|
|
632
922
|
spinner.text = `Installing worker ${chalk3.cyan(worker.name)}...`;
|
|
633
923
|
const agentName = worker.dir || worker.name;
|
|
634
924
|
const agentId = worker.name;
|
|
635
|
-
const workerDir = targetDir ?
|
|
636
|
-
const backupDir = backupAgentWorkspace(workerDir);
|
|
637
|
-
if (backupDir) {
|
|
638
|
-
console.log(chalk3.yellow(` \u26A0 Existing ${agentId} backed up to: ${backupDir}`));
|
|
639
|
-
}
|
|
925
|
+
const workerDir = targetDir ? path3.join(resolvedClawDir, `workspace-${agentId}`) : getWorkspaceDir(resolvedClawDir, agentId);
|
|
640
926
|
if (!targetDir) {
|
|
641
927
|
const regResult = registerAgentToOpenClaw(agentId, workerDir, clawDir);
|
|
642
928
|
if (!regResult.success) {
|
|
@@ -644,7 +930,7 @@ async function installRecipeFromRegistry(name, recipe, targetDir, clawDir) {
|
|
|
644
930
|
continue;
|
|
645
931
|
}
|
|
646
932
|
} else {
|
|
647
|
-
|
|
933
|
+
fs3.mkdirSync(workerDir, { recursive: true });
|
|
648
934
|
}
|
|
649
935
|
await downloadAgentFiles(agentName, workerDir, spinner);
|
|
650
936
|
const agentInfo = index.agents.find((a) => a.name === agentName);
|
|
@@ -659,6 +945,12 @@ async function installRecipeFromRegistry(name, recipe, targetDir, clawDir) {
|
|
|
659
945
|
const dispatcherId = "main";
|
|
660
946
|
configureMultiAgentCommunication(resolvedClawDir, dispatcherId, workerIds);
|
|
661
947
|
}
|
|
948
|
+
if (recipeBackupRecord) {
|
|
949
|
+
recipeBackupRecord.installedWorkerIds = workerIds;
|
|
950
|
+
recipeBackupRecord.installedMainAgent = pkg.dispatcher?.name || null;
|
|
951
|
+
commitBackupRecord(recipeBackupRecord);
|
|
952
|
+
}
|
|
953
|
+
logger.info(`Team installed from registry: ${name}`, { dispatcher: pkg.dispatcher?.name, workers: workerIds });
|
|
662
954
|
spinner.succeed(
|
|
663
955
|
`Team ${chalk3.cyan.bold(recipe.displayName)} installed! (1 dispatcher + ${workerIds.length} workers)`
|
|
664
956
|
);
|
|
@@ -672,42 +964,52 @@ async function installFromSource(source, targetDir, clawDir) {
|
|
|
672
964
|
let packageDir;
|
|
673
965
|
let tempDir = null;
|
|
674
966
|
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
967
|
+
logger.info(`Downloading package from URL: ${source}`);
|
|
675
968
|
spinner.text = "Downloading package...";
|
|
676
|
-
const response = await fetch(source
|
|
969
|
+
const response = await fetch(source, {
|
|
970
|
+
headers: {
|
|
971
|
+
"User-Agent": "soulhub-cli",
|
|
972
|
+
"Accept": "application/zip, application/octet-stream"
|
|
973
|
+
}
|
|
974
|
+
});
|
|
677
975
|
if (!response.ok) {
|
|
976
|
+
logger.error(`Download failed`, { url: source, status: response.status, statusText: response.statusText });
|
|
678
977
|
spinner.fail(`Failed to download: ${response.statusText}`);
|
|
679
978
|
return;
|
|
680
979
|
}
|
|
681
980
|
const contentType = response.headers.get("content-type") || "";
|
|
981
|
+
logger.debug(`Response content-type: ${contentType}`);
|
|
682
982
|
if (contentType.includes("zip") || source.endsWith(".zip")) {
|
|
683
983
|
const JSZip = (await import("jszip")).default;
|
|
684
984
|
const arrayBuffer = await response.arrayBuffer();
|
|
685
985
|
const zip = await JSZip.loadAsync(arrayBuffer);
|
|
686
|
-
tempDir =
|
|
687
|
-
|
|
986
|
+
tempDir = path3.join(process.env.HOME || "/tmp", ".soulhub", "tmp", `pkg-${Date.now()}`);
|
|
987
|
+
fs3.mkdirSync(tempDir, { recursive: true });
|
|
688
988
|
await extractZipToDir(zip, tempDir);
|
|
689
989
|
packageDir = tempDir;
|
|
690
990
|
} else {
|
|
991
|
+
logger.error(`Unsupported content type from URL`, { url: source, contentType });
|
|
691
992
|
spinner.fail("Unsupported URL content type. Expected ZIP file.");
|
|
692
993
|
return;
|
|
693
994
|
}
|
|
694
995
|
} else if (source.endsWith(".zip")) {
|
|
695
996
|
spinner.text = "Extracting ZIP file...";
|
|
696
997
|
const JSZip = (await import("jszip")).default;
|
|
697
|
-
const zipData =
|
|
998
|
+
const zipData = fs3.readFileSync(path3.resolve(source));
|
|
698
999
|
const zip = await JSZip.loadAsync(zipData);
|
|
699
|
-
tempDir =
|
|
700
|
-
|
|
1000
|
+
tempDir = path3.join(process.env.HOME || "/tmp", ".soulhub", "tmp", `pkg-${Date.now()}`);
|
|
1001
|
+
fs3.mkdirSync(tempDir, { recursive: true });
|
|
701
1002
|
await extractZipToDir(zip, tempDir);
|
|
702
1003
|
packageDir = tempDir;
|
|
703
1004
|
} else {
|
|
704
|
-
packageDir =
|
|
705
|
-
if (!
|
|
1005
|
+
packageDir = path3.resolve(source);
|
|
1006
|
+
if (!fs3.existsSync(packageDir)) {
|
|
706
1007
|
spinner.fail(`Directory not found: ${packageDir}`);
|
|
707
1008
|
return;
|
|
708
1009
|
}
|
|
709
1010
|
}
|
|
710
1011
|
const kind = detectPackageKind(packageDir);
|
|
1012
|
+
logger.info(`Detected package type: ${kind}`, { packageDir });
|
|
711
1013
|
spinner.text = `Detected package type: ${chalk3.blue(kind)}`;
|
|
712
1014
|
if (kind === "agent") {
|
|
713
1015
|
spinner.stop();
|
|
@@ -716,16 +1018,17 @@ async function installFromSource(source, targetDir, clawDir) {
|
|
|
716
1018
|
spinner.stop();
|
|
717
1019
|
await installTeamFromDir(packageDir, targetDir, clawDir);
|
|
718
1020
|
} else {
|
|
1021
|
+
logger.error(`Cannot determine package type`, { packageDir, files: fs3.existsSync(packageDir) ? fs3.readdirSync(packageDir) : [] });
|
|
719
1022
|
spinner.fail("Cannot determine package type. Expected soulhub.yaml or IDENTITY.md.");
|
|
720
1023
|
}
|
|
721
|
-
if (tempDir &&
|
|
722
|
-
|
|
1024
|
+
if (tempDir && fs3.existsSync(tempDir)) {
|
|
1025
|
+
fs3.rmSync(tempDir, { recursive: true, force: true });
|
|
723
1026
|
}
|
|
724
1027
|
}
|
|
725
1028
|
async function installSingleAgentFromDir(packageDir, targetDir, clawDir) {
|
|
726
1029
|
const spinner = ora("Installing single agent...").start();
|
|
727
1030
|
const pkg = readSoulHubPackage(packageDir);
|
|
728
|
-
const agentName = pkg?.name ||
|
|
1031
|
+
const agentName = pkg?.name || path3.basename(packageDir);
|
|
729
1032
|
if (!targetDir) {
|
|
730
1033
|
const clawCheck = checkOpenClawInstalled(clawDir);
|
|
731
1034
|
if (!clawCheck.installed) {
|
|
@@ -736,7 +1039,7 @@ async function installSingleAgentFromDir(packageDir, targetDir, clawDir) {
|
|
|
736
1039
|
}
|
|
737
1040
|
let workspaceDir;
|
|
738
1041
|
if (targetDir) {
|
|
739
|
-
workspaceDir =
|
|
1042
|
+
workspaceDir = path3.resolve(targetDir);
|
|
740
1043
|
} else {
|
|
741
1044
|
const resolvedClawDir = findOpenClawDir(clawDir);
|
|
742
1045
|
if (!resolvedClawDir) {
|
|
@@ -745,6 +1048,7 @@ async function installSingleAgentFromDir(packageDir, targetDir, clawDir) {
|
|
|
745
1048
|
}
|
|
746
1049
|
workspaceDir = getMainWorkspaceDir(resolvedClawDir);
|
|
747
1050
|
}
|
|
1051
|
+
const localBackupRecord = !targetDir ? createBackupRecord("single-agent-local", agentName, findOpenClawDir(clawDir)) : null;
|
|
748
1052
|
if (!targetDir) {
|
|
749
1053
|
const resolvedClawDir = findOpenClawDir(clawDir);
|
|
750
1054
|
const mainCheck = checkMainAgentExists(resolvedClawDir);
|
|
@@ -753,11 +1057,18 @@ async function installSingleAgentFromDir(packageDir, targetDir, clawDir) {
|
|
|
753
1057
|
const backupDir = backupAgentWorkspace(workspaceDir);
|
|
754
1058
|
if (backupDir) {
|
|
755
1059
|
console.log(chalk3.yellow(` \u26A0 Existing main agent backed up to: ${backupDir}`));
|
|
1060
|
+
addBackupItem(localBackupRecord, {
|
|
1061
|
+
originalPath: workspaceDir,
|
|
1062
|
+
backupPath: backupDir,
|
|
1063
|
+
method: "cp",
|
|
1064
|
+
role: "main",
|
|
1065
|
+
agentId: "main"
|
|
1066
|
+
});
|
|
756
1067
|
}
|
|
757
1068
|
}
|
|
758
1069
|
}
|
|
759
|
-
if (!
|
|
760
|
-
|
|
1070
|
+
if (!fs3.existsSync(workspaceDir)) {
|
|
1071
|
+
fs3.mkdirSync(workspaceDir, { recursive: true });
|
|
761
1072
|
}
|
|
762
1073
|
if (!targetDir) {
|
|
763
1074
|
spinner.text = `Registering ${chalk3.cyan(agentName)} as main agent...`;
|
|
@@ -767,6 +1078,11 @@ async function installSingleAgentFromDir(packageDir, targetDir, clawDir) {
|
|
|
767
1078
|
spinner.text = `Copying soul files...`;
|
|
768
1079
|
copyAgentFilesFromDir(packageDir, workspaceDir);
|
|
769
1080
|
recordInstall(agentName, pkg?.version || "local", workspaceDir);
|
|
1081
|
+
if (localBackupRecord) {
|
|
1082
|
+
localBackupRecord.installedMainAgent = agentName;
|
|
1083
|
+
commitBackupRecord(localBackupRecord);
|
|
1084
|
+
}
|
|
1085
|
+
logger.info(`Single agent installed from dir: ${agentName}`, { source: packageDir, workspace: workspaceDir });
|
|
770
1086
|
spinner.succeed(`${chalk3.cyan.bold(agentName)} installed as main agent!`);
|
|
771
1087
|
console.log();
|
|
772
1088
|
console.log(` ${chalk3.dim("Location:")} ${workspaceDir}`);
|
|
@@ -792,14 +1108,34 @@ async function installTeamFromDir(packageDir, targetDir, clawDir) {
|
|
|
792
1108
|
return;
|
|
793
1109
|
}
|
|
794
1110
|
}
|
|
795
|
-
const resolvedClawDir = targetDir ?
|
|
1111
|
+
const resolvedClawDir = targetDir ? path3.resolve(targetDir) : findOpenClawDir(clawDir);
|
|
796
1112
|
if (!resolvedClawDir) {
|
|
797
1113
|
spinner.fail("OpenClaw workspace directory not found.");
|
|
798
1114
|
return;
|
|
799
1115
|
}
|
|
1116
|
+
const teamBackupRecord = !targetDir ? createBackupRecord("team-local", pkg.name, resolvedClawDir) : null;
|
|
1117
|
+
if (!targetDir) {
|
|
1118
|
+
spinner.text = "Backing up existing worker agents...";
|
|
1119
|
+
const backupResults = backupAllWorkerWorkspaces(resolvedClawDir);
|
|
1120
|
+
for (const { name: dirName, backupDir } of backupResults) {
|
|
1121
|
+
logger.info(`Existing worker backed up (mv)`, { dirName, backupDir });
|
|
1122
|
+
console.log(chalk3.yellow(` \u26A0 Existing ${dirName} moved to: ${backupDir}`));
|
|
1123
|
+
const agentId = dirName.replace(/^workspace-/, "");
|
|
1124
|
+
addBackupItem(teamBackupRecord, {
|
|
1125
|
+
originalPath: path3.join(resolvedClawDir, dirName),
|
|
1126
|
+
backupPath: backupDir,
|
|
1127
|
+
method: "mv",
|
|
1128
|
+
role: "worker",
|
|
1129
|
+
agentId
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
if (backupResults.length > 0) {
|
|
1133
|
+
console.log(chalk3.dim(` ${backupResults.length} existing worker(s) backed up.`));
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
800
1136
|
if (pkg.dispatcher) {
|
|
801
1137
|
spinner.text = `Installing dispatcher ${chalk3.blue(pkg.dispatcher.name)}...`;
|
|
802
|
-
const mainWorkspace = targetDir ?
|
|
1138
|
+
const mainWorkspace = targetDir ? path3.join(resolvedClawDir, "workspace") : getMainWorkspaceDir(resolvedClawDir);
|
|
803
1139
|
if (!targetDir) {
|
|
804
1140
|
const mainCheck = checkMainAgentExists(resolvedClawDir);
|
|
805
1141
|
if (mainCheck.hasContent) {
|
|
@@ -807,17 +1143,26 @@ async function installTeamFromDir(packageDir, targetDir, clawDir) {
|
|
|
807
1143
|
const backupDir = backupAgentWorkspace(mainWorkspace);
|
|
808
1144
|
if (backupDir) {
|
|
809
1145
|
console.log(chalk3.yellow(` \u26A0 Existing main agent backed up to: ${backupDir}`));
|
|
1146
|
+
if (teamBackupRecord) {
|
|
1147
|
+
addBackupItem(teamBackupRecord, {
|
|
1148
|
+
originalPath: mainWorkspace,
|
|
1149
|
+
backupPath: backupDir,
|
|
1150
|
+
method: "cp",
|
|
1151
|
+
role: "main",
|
|
1152
|
+
agentId: "main"
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
810
1155
|
}
|
|
811
1156
|
}
|
|
812
1157
|
}
|
|
813
|
-
if (!
|
|
814
|
-
|
|
1158
|
+
if (!fs3.existsSync(mainWorkspace)) {
|
|
1159
|
+
fs3.mkdirSync(mainWorkspace, { recursive: true });
|
|
815
1160
|
}
|
|
816
1161
|
if (!targetDir) {
|
|
817
1162
|
addAgentToOpenClawConfig(resolvedClawDir, "main", pkg.dispatcher.name, true);
|
|
818
1163
|
}
|
|
819
|
-
const dispatcherSourceDir =
|
|
820
|
-
if (
|
|
1164
|
+
const dispatcherSourceDir = path3.join(packageDir, pkg.dispatcher.dir);
|
|
1165
|
+
if (fs3.existsSync(dispatcherSourceDir)) {
|
|
821
1166
|
copyAgentFilesFromDir(dispatcherSourceDir, mainWorkspace);
|
|
822
1167
|
}
|
|
823
1168
|
recordInstall("dispatcher", pkg.version || "local", mainWorkspace);
|
|
@@ -827,11 +1172,7 @@ async function installTeamFromDir(packageDir, targetDir, clawDir) {
|
|
|
827
1172
|
const agentId = worker.name;
|
|
828
1173
|
const agentDir = worker.dir || worker.name;
|
|
829
1174
|
spinner.text = `Installing worker ${chalk3.cyan(agentId)}...`;
|
|
830
|
-
const workerWorkspace = targetDir ?
|
|
831
|
-
const backupDir = backupAgentWorkspace(workerWorkspace);
|
|
832
|
-
if (backupDir) {
|
|
833
|
-
console.log(chalk3.yellow(` \u26A0 Existing ${agentId} backed up to: ${backupDir}`));
|
|
834
|
-
}
|
|
1175
|
+
const workerWorkspace = targetDir ? path3.join(resolvedClawDir, `workspace-${agentId}`) : getWorkspaceDir(resolvedClawDir, agentId);
|
|
835
1176
|
if (!targetDir) {
|
|
836
1177
|
const regResult = registerAgentToOpenClaw(agentId, workerWorkspace, clawDir);
|
|
837
1178
|
if (!regResult.success) {
|
|
@@ -839,10 +1180,10 @@ async function installTeamFromDir(packageDir, targetDir, clawDir) {
|
|
|
839
1180
|
continue;
|
|
840
1181
|
}
|
|
841
1182
|
} else {
|
|
842
|
-
|
|
1183
|
+
fs3.mkdirSync(workerWorkspace, { recursive: true });
|
|
843
1184
|
}
|
|
844
|
-
const workerSourceDir =
|
|
845
|
-
if (
|
|
1185
|
+
const workerSourceDir = path3.join(packageDir, agentDir);
|
|
1186
|
+
if (fs3.existsSync(workerSourceDir)) {
|
|
846
1187
|
copyAgentFilesFromDir(workerSourceDir, workerWorkspace);
|
|
847
1188
|
}
|
|
848
1189
|
recordInstall(agentId, pkg.version || "local", workerWorkspace);
|
|
@@ -852,6 +1193,12 @@ async function installTeamFromDir(packageDir, targetDir, clawDir) {
|
|
|
852
1193
|
spinner.text = "Configuring multi-agent communication...";
|
|
853
1194
|
configureMultiAgentCommunication(resolvedClawDir, "main", workerIds);
|
|
854
1195
|
}
|
|
1196
|
+
if (teamBackupRecord) {
|
|
1197
|
+
teamBackupRecord.installedWorkerIds = workerIds;
|
|
1198
|
+
teamBackupRecord.installedMainAgent = pkg.dispatcher?.name || null;
|
|
1199
|
+
commitBackupRecord(teamBackupRecord);
|
|
1200
|
+
}
|
|
1201
|
+
logger.info(`Team installed from dir: ${pkg.name}`, { dispatcher: pkg.dispatcher?.name, workers: workerIds, source: packageDir });
|
|
855
1202
|
spinner.succeed(
|
|
856
1203
|
`Team ${chalk3.cyan.bold(pkg.name)} installed! (${pkg.dispatcher ? "1 dispatcher + " : ""}${workerIds.length} workers)`
|
|
857
1204
|
);
|
|
@@ -861,11 +1208,11 @@ async function installTeamFromDir(packageDir, targetDir, clawDir) {
|
|
|
861
1208
|
}
|
|
862
1209
|
}
|
|
863
1210
|
async function downloadAgentFiles(agentName, workspaceDir, spinner) {
|
|
864
|
-
|
|
1211
|
+
fs3.mkdirSync(workspaceDir, { recursive: true });
|
|
865
1212
|
const coreFiles = ["IDENTITY.md", "SOUL.md"];
|
|
866
1213
|
for (const fileName of coreFiles) {
|
|
867
1214
|
const content = await fetchAgentFile(agentName, fileName);
|
|
868
|
-
|
|
1215
|
+
fs3.writeFileSync(path3.join(workspaceDir, fileName), content);
|
|
869
1216
|
spinner.text = `Downloaded ${fileName}`;
|
|
870
1217
|
}
|
|
871
1218
|
const optionalFiles = ["USER.md.template", "TOOLS.md.template"];
|
|
@@ -873,7 +1220,7 @@ async function downloadAgentFiles(agentName, workspaceDir, spinner) {
|
|
|
873
1220
|
try {
|
|
874
1221
|
const content = await fetchAgentFile(agentName, fileName);
|
|
875
1222
|
const actualName = fileName.replace(".template", "");
|
|
876
|
-
|
|
1223
|
+
fs3.writeFileSync(path3.join(workspaceDir, actualName), content);
|
|
877
1224
|
} catch {
|
|
878
1225
|
}
|
|
879
1226
|
}
|
|
@@ -881,7 +1228,7 @@ async function downloadAgentFiles(agentName, workspaceDir, spinner) {
|
|
|
881
1228
|
async function saveAgentManifest(agentName, agent, workspaceDir) {
|
|
882
1229
|
try {
|
|
883
1230
|
const manifestContent = await fetchAgentFile(agentName, "manifest.yaml");
|
|
884
|
-
|
|
1231
|
+
fs3.writeFileSync(path3.join(workspaceDir, "manifest.yaml"), manifestContent);
|
|
885
1232
|
} catch {
|
|
886
1233
|
const manifest = {
|
|
887
1234
|
name: agent.name,
|
|
@@ -892,21 +1239,21 @@ async function saveAgentManifest(agentName, agent, workspaceDir) {
|
|
|
892
1239
|
version: agent.version,
|
|
893
1240
|
author: agent.author
|
|
894
1241
|
};
|
|
895
|
-
|
|
1242
|
+
fs3.writeFileSync(path3.join(workspaceDir, "manifest.yaml"), yaml2.dump(manifest));
|
|
896
1243
|
}
|
|
897
1244
|
}
|
|
898
1245
|
function copyAgentFilesFromDir(sourceDir, targetDir) {
|
|
899
1246
|
const filesToCopy = ["IDENTITY.md", "SOUL.md", "USER.md", "TOOLS.md", "AGENTS.md", "HEARTBEAT.md"];
|
|
900
1247
|
for (const fileName of filesToCopy) {
|
|
901
|
-
const sourcePath =
|
|
902
|
-
if (
|
|
903
|
-
|
|
904
|
-
|
|
1248
|
+
const sourcePath = path3.join(sourceDir, fileName);
|
|
1249
|
+
if (fs3.existsSync(sourcePath)) {
|
|
1250
|
+
fs3.mkdirSync(targetDir, { recursive: true });
|
|
1251
|
+
fs3.copyFileSync(sourcePath, path3.join(targetDir, fileName));
|
|
905
1252
|
}
|
|
906
1253
|
}
|
|
907
1254
|
}
|
|
908
|
-
async function installDispatcher(dispatcher, resolvedClawDir, clawDir, targetDir, spinner) {
|
|
909
|
-
const mainWorkspace = targetDir ?
|
|
1255
|
+
async function installDispatcher(dispatcher, resolvedClawDir, clawDir, targetDir, spinner, backupRecord) {
|
|
1256
|
+
const mainWorkspace = targetDir ? path3.join(resolvedClawDir, "workspace") : getMainWorkspaceDir(resolvedClawDir);
|
|
910
1257
|
if (!targetDir) {
|
|
911
1258
|
const mainCheck = checkMainAgentExists(resolvedClawDir);
|
|
912
1259
|
if (mainCheck.hasContent) {
|
|
@@ -914,11 +1261,20 @@ async function installDispatcher(dispatcher, resolvedClawDir, clawDir, targetDir
|
|
|
914
1261
|
const backupDir = backupAgentWorkspace(mainWorkspace);
|
|
915
1262
|
if (backupDir) {
|
|
916
1263
|
console.log(chalk3.yellow(` \u26A0 Existing main agent backed up to: ${backupDir}`));
|
|
1264
|
+
if (backupRecord) {
|
|
1265
|
+
addBackupItem(backupRecord, {
|
|
1266
|
+
originalPath: mainWorkspace,
|
|
1267
|
+
backupPath: backupDir,
|
|
1268
|
+
method: "cp",
|
|
1269
|
+
role: "main",
|
|
1270
|
+
agentId: "main"
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
917
1273
|
}
|
|
918
1274
|
}
|
|
919
1275
|
}
|
|
920
|
-
if (!
|
|
921
|
-
|
|
1276
|
+
if (!fs3.existsSync(mainWorkspace)) {
|
|
1277
|
+
fs3.mkdirSync(mainWorkspace, { recursive: true });
|
|
922
1278
|
}
|
|
923
1279
|
if (!targetDir) {
|
|
924
1280
|
addAgentToOpenClawConfig(resolvedClawDir, "main", dispatcher.name, true);
|
|
@@ -931,13 +1287,13 @@ async function installDispatcher(dispatcher, resolvedClawDir, clawDir, targetDir
|
|
|
931
1287
|
async function extractZipToDir(zip, targetDir) {
|
|
932
1288
|
const entries = Object.entries(zip.files);
|
|
933
1289
|
for (const [relativePath, file] of entries) {
|
|
934
|
-
const fullPath =
|
|
1290
|
+
const fullPath = path3.join(targetDir, relativePath);
|
|
935
1291
|
if (file.dir) {
|
|
936
|
-
|
|
1292
|
+
fs3.mkdirSync(fullPath, { recursive: true });
|
|
937
1293
|
} else {
|
|
938
|
-
|
|
1294
|
+
fs3.mkdirSync(path3.dirname(fullPath), { recursive: true });
|
|
939
1295
|
const content = await file.async("nodebuffer");
|
|
940
|
-
|
|
1296
|
+
fs3.writeFileSync(fullPath, content);
|
|
941
1297
|
}
|
|
942
1298
|
}
|
|
943
1299
|
}
|
|
@@ -1008,9 +1364,11 @@ var listCommand = new Command4("list").description("List installed agent templat
|
|
|
1008
1364
|
console.log();
|
|
1009
1365
|
}
|
|
1010
1366
|
} catch (error) {
|
|
1367
|
+
logger.errorObj("List command failed", error);
|
|
1011
1368
|
console.error(
|
|
1012
1369
|
chalk4.red(`Error: ${error instanceof Error ? error.message : error}`)
|
|
1013
1370
|
);
|
|
1371
|
+
console.error(chalk4.dim(` See logs: ${logger.getTodayLogFile()}`));
|
|
1014
1372
|
process.exit(1);
|
|
1015
1373
|
}
|
|
1016
1374
|
});
|
|
@@ -1019,8 +1377,8 @@ var listCommand = new Command4("list").description("List installed agent templat
|
|
|
1019
1377
|
import { Command as Command5 } from "commander";
|
|
1020
1378
|
import chalk5 from "chalk";
|
|
1021
1379
|
import ora2 from "ora";
|
|
1022
|
-
import
|
|
1023
|
-
import
|
|
1380
|
+
import fs4 from "fs";
|
|
1381
|
+
import path4 from "path";
|
|
1024
1382
|
var updateCommand = new Command5("update").description("Update installed agent templates to latest versions").argument("[name]", "Agent name to update (updates all if omitted)").action(async (name) => {
|
|
1025
1383
|
try {
|
|
1026
1384
|
const config = loadConfig();
|
|
@@ -1049,8 +1407,8 @@ var updateCommand = new Command5("update").description("Update installed agent t
|
|
|
1049
1407
|
}
|
|
1050
1408
|
spinner.text = `Updating ${chalk5.cyan(installed.name)} (${installed.version} \u2192 ${remote.version})...`;
|
|
1051
1409
|
const workspaceDir = installed.workspace;
|
|
1052
|
-
if (!
|
|
1053
|
-
|
|
1410
|
+
if (!fs4.existsSync(workspaceDir)) {
|
|
1411
|
+
fs4.mkdirSync(workspaceDir, { recursive: true });
|
|
1054
1412
|
}
|
|
1055
1413
|
for (const fileName of ["IDENTITY.md", "SOUL.md", "manifest.yaml"]) {
|
|
1056
1414
|
try {
|
|
@@ -1058,8 +1416,8 @@ var updateCommand = new Command5("update").description("Update installed agent t
|
|
|
1058
1416
|
installed.name,
|
|
1059
1417
|
fileName
|
|
1060
1418
|
);
|
|
1061
|
-
|
|
1062
|
-
|
|
1419
|
+
fs4.writeFileSync(
|
|
1420
|
+
path4.join(workspaceDir, fileName),
|
|
1063
1421
|
content
|
|
1064
1422
|
);
|
|
1065
1423
|
} catch {
|
|
@@ -1073,13 +1431,16 @@ var updateCommand = new Command5("update").description("Update installed agent t
|
|
|
1073
1431
|
if (updated === 0) {
|
|
1074
1432
|
spinner.succeed("All agents are up to date.");
|
|
1075
1433
|
} else {
|
|
1434
|
+
logger.info(`Updated ${updated} agent(s)`);
|
|
1076
1435
|
spinner.succeed(`Updated ${updated} agent(s).`);
|
|
1077
1436
|
}
|
|
1078
1437
|
console.log();
|
|
1079
1438
|
} catch (error) {
|
|
1439
|
+
logger.errorObj("Update command failed", error);
|
|
1080
1440
|
console.error(
|
|
1081
1441
|
chalk5.red(`Error: ${error instanceof Error ? error.message : error}`)
|
|
1082
1442
|
);
|
|
1443
|
+
console.error(chalk5.dim(` See logs: ${logger.getTodayLogFile()}`));
|
|
1083
1444
|
process.exit(1);
|
|
1084
1445
|
}
|
|
1085
1446
|
});
|
|
@@ -1088,7 +1449,7 @@ var updateCommand = new Command5("update").description("Update installed agent t
|
|
|
1088
1449
|
import { Command as Command6 } from "commander";
|
|
1089
1450
|
import chalk6 from "chalk";
|
|
1090
1451
|
import ora3 from "ora";
|
|
1091
|
-
import
|
|
1452
|
+
import fs5 from "fs";
|
|
1092
1453
|
var uninstallCommand = new Command6("uninstall").description("Uninstall an agent template").alias("rm").argument("<name>", "Agent name to uninstall").option("--keep-files", "Remove from registry but keep workspace files").action(async (name, options) => {
|
|
1093
1454
|
try {
|
|
1094
1455
|
const config = loadConfig();
|
|
@@ -1107,11 +1468,12 @@ var uninstallCommand = new Command6("uninstall").description("Uninstall an agent
|
|
|
1107
1468
|
const spinner = ora3(
|
|
1108
1469
|
`Uninstalling ${chalk6.cyan(name)}...`
|
|
1109
1470
|
).start();
|
|
1110
|
-
if (!options.keepFiles &&
|
|
1111
|
-
|
|
1471
|
+
if (!options.keepFiles && fs5.existsSync(installed.workspace)) {
|
|
1472
|
+
fs5.rmSync(installed.workspace, { recursive: true, force: true });
|
|
1112
1473
|
spinner.text = `Removed workspace: ${installed.workspace}`;
|
|
1113
1474
|
}
|
|
1114
1475
|
removeInstallRecord(name);
|
|
1476
|
+
logger.info(`Agent uninstalled: ${name}`, { workspace: installed.workspace, keepFiles: !!options.keepFiles });
|
|
1115
1477
|
spinner.succeed(
|
|
1116
1478
|
`${chalk6.cyan.bold(name)} uninstalled.`
|
|
1117
1479
|
);
|
|
@@ -1122,9 +1484,11 @@ var uninstallCommand = new Command6("uninstall").description("Uninstall an agent
|
|
|
1122
1484
|
}
|
|
1123
1485
|
console.log();
|
|
1124
1486
|
} catch (error) {
|
|
1487
|
+
logger.errorObj("Uninstall command failed", error);
|
|
1125
1488
|
console.error(
|
|
1126
1489
|
chalk6.red(`Error: ${error instanceof Error ? error.message : error}`)
|
|
1127
1490
|
);
|
|
1491
|
+
console.error(chalk6.dim(` See logs: ${logger.getTodayLogFile()}`));
|
|
1128
1492
|
process.exit(1);
|
|
1129
1493
|
}
|
|
1130
1494
|
});
|
|
@@ -1133,19 +1497,19 @@ var uninstallCommand = new Command6("uninstall").description("Uninstall an agent
|
|
|
1133
1497
|
import { Command as Command7 } from "commander";
|
|
1134
1498
|
import chalk7 from "chalk";
|
|
1135
1499
|
import ora4 from "ora";
|
|
1136
|
-
import
|
|
1137
|
-
import
|
|
1500
|
+
import fs6 from "fs";
|
|
1501
|
+
import path5 from "path";
|
|
1138
1502
|
import yaml3 from "js-yaml";
|
|
1139
1503
|
var publishCommand = new Command7("publish").description("Publish an agent template to the SoulHub community").argument(
|
|
1140
1504
|
"[directory]",
|
|
1141
1505
|
"Agent workspace directory (defaults to current directory)"
|
|
1142
1506
|
).action(async (directory) => {
|
|
1143
1507
|
try {
|
|
1144
|
-
const dir = directory ?
|
|
1508
|
+
const dir = directory ? path5.resolve(directory) : process.cwd();
|
|
1145
1509
|
const spinner = ora4("Validating agent template...").start();
|
|
1146
1510
|
const requiredFiles = ["manifest.yaml", "IDENTITY.md", "SOUL.md"];
|
|
1147
1511
|
const missing = requiredFiles.filter(
|
|
1148
|
-
(f) => !
|
|
1512
|
+
(f) => !fs6.existsSync(path5.join(dir, f))
|
|
1149
1513
|
);
|
|
1150
1514
|
if (missing.length > 0) {
|
|
1151
1515
|
spinner.fail("Missing required files:");
|
|
@@ -1161,8 +1525,8 @@ var publishCommand = new Command7("publish").description("Publish an agent templ
|
|
|
1161
1525
|
);
|
|
1162
1526
|
return;
|
|
1163
1527
|
}
|
|
1164
|
-
const manifestContent =
|
|
1165
|
-
|
|
1528
|
+
const manifestContent = fs6.readFileSync(
|
|
1529
|
+
path5.join(dir, "manifest.yaml"),
|
|
1166
1530
|
"utf-8"
|
|
1167
1531
|
);
|
|
1168
1532
|
const manifest = yaml3.load(manifestContent);
|
|
@@ -1225,16 +1589,27 @@ var publishCommand = new Command7("publish").description("Publish an agent templ
|
|
|
1225
1589
|
);
|
|
1226
1590
|
console.log();
|
|
1227
1591
|
} catch (error) {
|
|
1592
|
+
logger.errorObj("Publish command failed", error);
|
|
1228
1593
|
console.error(
|
|
1229
1594
|
chalk7.red(`Error: ${error instanceof Error ? error.message : error}`)
|
|
1230
1595
|
);
|
|
1596
|
+
console.error(chalk7.dim(` See logs: ${logger.getTodayLogFile()}`));
|
|
1231
1597
|
process.exit(1);
|
|
1232
1598
|
}
|
|
1233
1599
|
});
|
|
1234
1600
|
|
|
1235
1601
|
// src/index.ts
|
|
1236
1602
|
var program = new Command8();
|
|
1237
|
-
program.name("soulhub").description("SoulHub CLI - Install and manage AI agent persona templates").version("0.1.0")
|
|
1603
|
+
program.name("soulhub").description("SoulHub CLI - Install and manage AI agent persona templates").version("0.1.0").option("--verbose", "Enable verbose debug logging").hook("preAction", () => {
|
|
1604
|
+
const opts = program.opts();
|
|
1605
|
+
const verbose = opts.verbose || process.env.SOULHUB_DEBUG === "1";
|
|
1606
|
+
logger.init(verbose);
|
|
1607
|
+
logger.info("CLI started", {
|
|
1608
|
+
args: process.argv.slice(2),
|
|
1609
|
+
version: "0.1.0",
|
|
1610
|
+
node: process.version
|
|
1611
|
+
});
|
|
1612
|
+
});
|
|
1238
1613
|
program.addCommand(searchCommand);
|
|
1239
1614
|
program.addCommand(infoCommand);
|
|
1240
1615
|
program.addCommand(installCommand);
|