soulhubcli 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/dist/index.js ADDED
@@ -0,0 +1,1245 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command as Command8 } from "commander";
5
+
6
+ // src/commands/search.ts
7
+ import { Command } from "commander";
8
+ import chalk from "chalk";
9
+
10
+ // src/utils.ts
11
+ import fs from "fs";
12
+ import path from "path";
13
+ import { execSync } from "child_process";
14
+ import yaml from "js-yaml";
15
+ var DEFAULT_REGISTRY_URL = "http://soulhub.store";
16
+ function getRegistryUrl() {
17
+ return process.env.SOULHUB_REGISTRY_URL || DEFAULT_REGISTRY_URL;
18
+ }
19
+ async function fetchIndex() {
20
+ const url = `${getRegistryUrl()}/index.json`;
21
+ const response = await fetch(url);
22
+ if (!response.ok) {
23
+ throw new Error(`Failed to fetch registry index: ${response.statusText}`);
24
+ }
25
+ return await response.json();
26
+ }
27
+ async function fetchAgentFile(agentName, fileName) {
28
+ const url = `${getRegistryUrl()}/agents/${agentName}/${fileName}`;
29
+ const response = await fetch(url);
30
+ if (!response.ok) {
31
+ throw new Error(
32
+ `Failed to fetch ${fileName} for ${agentName}: ${response.statusText}`
33
+ );
34
+ }
35
+ return await response.text();
36
+ }
37
+ async function fetchRecipeFile(recipeName, fileName) {
38
+ const url = `${getRegistryUrl()}/recipes/${recipeName}/${fileName}`;
39
+ const response = await fetch(url);
40
+ if (!response.ok) {
41
+ throw new Error(
42
+ `Failed to fetch ${fileName} for recipe ${recipeName}: ${response.statusText}`
43
+ );
44
+ }
45
+ return await response.text();
46
+ }
47
+ function findOpenClawDir(customDir) {
48
+ if (customDir) {
49
+ const resolved = path.resolve(customDir);
50
+ if (fs.existsSync(resolved)) {
51
+ return resolved;
52
+ }
53
+ return resolved;
54
+ }
55
+ const envHome = process.env.OPENCLAW_HOME;
56
+ if (envHome) {
57
+ const resolved = path.resolve(envHome);
58
+ if (fs.existsSync(resolved)) {
59
+ return resolved;
60
+ }
61
+ return resolved;
62
+ }
63
+ const candidates = [
64
+ path.join(process.env.HOME || "~", ".openclaw"),
65
+ path.join(process.cwd(), ".openclaw")
66
+ ];
67
+ for (const candidate of candidates) {
68
+ if (fs.existsSync(candidate)) {
69
+ return candidate;
70
+ }
71
+ }
72
+ return null;
73
+ }
74
+ function getConfigPath() {
75
+ const home = process.env.HOME || "~";
76
+ return path.join(home, ".soulhub", "config.json");
77
+ }
78
+ function loadConfig() {
79
+ const configPath = getConfigPath();
80
+ if (fs.existsSync(configPath)) {
81
+ return JSON.parse(fs.readFileSync(configPath, "utf-8"));
82
+ }
83
+ return {
84
+ installed: [],
85
+ registryUrl: DEFAULT_REGISTRY_URL
86
+ };
87
+ }
88
+ function saveConfig(config) {
89
+ const configPath = getConfigPath();
90
+ const configDir = path.dirname(configPath);
91
+ if (!fs.existsSync(configDir)) {
92
+ fs.mkdirSync(configDir, { recursive: true });
93
+ }
94
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
95
+ }
96
+ function recordInstall(name, version, workspace) {
97
+ const config = loadConfig();
98
+ config.installed = config.installed.filter((a) => a.name !== name);
99
+ config.installed.push({
100
+ name,
101
+ version,
102
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
103
+ workspace
104
+ });
105
+ saveConfig(config);
106
+ }
107
+ function removeInstallRecord(name) {
108
+ const config = loadConfig();
109
+ config.installed = config.installed.filter((a) => a.name !== name);
110
+ saveConfig(config);
111
+ }
112
+ function getWorkspaceDir(clawDir, agentName) {
113
+ return path.join(clawDir, `workspace-${agentName}`);
114
+ }
115
+ function getMainWorkspaceDir(clawDir) {
116
+ return path.join(clawDir, "workspace");
117
+ }
118
+ function checkMainAgentExists(clawDir) {
119
+ const workspaceDir = getMainWorkspaceDir(clawDir);
120
+ if (!fs.existsSync(workspaceDir)) {
121
+ return { exists: false, hasContent: false, workspaceDir };
122
+ }
123
+ const entries = fs.readdirSync(workspaceDir);
124
+ const hasIdentity = entries.includes("IDENTITY.md");
125
+ const hasSoul = entries.includes("SOUL.md");
126
+ return {
127
+ exists: true,
128
+ hasContent: hasIdentity || hasSoul,
129
+ workspaceDir
130
+ };
131
+ }
132
+ function getOpenClawConfigPath(clawDir) {
133
+ return path.join(clawDir, "openclaw.json");
134
+ }
135
+ function readOpenClawConfig(clawDir) {
136
+ const configPath = getOpenClawConfigPath(clawDir);
137
+ if (!fs.existsSync(configPath)) {
138
+ return null;
139
+ }
140
+ try {
141
+ return JSON.parse(fs.readFileSync(configPath, "utf-8"));
142
+ } catch {
143
+ return null;
144
+ }
145
+ }
146
+ function writeOpenClawConfig(clawDir, config) {
147
+ const configPath = getOpenClawConfigPath(clawDir);
148
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
149
+ }
150
+ function updateOpenClawConfig(clawDir, updater) {
151
+ const config = readOpenClawConfig(clawDir);
152
+ if (!config) {
153
+ return false;
154
+ }
155
+ const updated = updater(config);
156
+ writeOpenClawConfig(clawDir, updated);
157
+ return true;
158
+ }
159
+ function configureMultiAgentCommunication(clawDir, dispatcherId, workerIds) {
160
+ return updateOpenClawConfig(clawDir, (config) => {
161
+ if (!config.agents) config.agents = {};
162
+ if (!config.agents.list) config.agents.list = [];
163
+ const dispatcherAgent = config.agents.list.find((a) => a.id === dispatcherId);
164
+ if (dispatcherAgent) {
165
+ dispatcherAgent.subagents = {
166
+ ...dispatcherAgent.subagents,
167
+ allowAgents: workerIds
168
+ };
169
+ }
170
+ if (!config.tools) config.tools = {};
171
+ config.tools.sessions = {
172
+ ...config.tools.sessions,
173
+ visibility: "all"
174
+ };
175
+ const allAgentIds = [dispatcherId, ...workerIds];
176
+ config.tools.agentToAgent = {
177
+ enabled: true,
178
+ allow: allAgentIds
179
+ };
180
+ return config;
181
+ });
182
+ }
183
+ function addAgentToOpenClawConfig(clawDir, agentId, agentName, isMain) {
184
+ return updateOpenClawConfig(clawDir, (config) => {
185
+ if (!config.agents) config.agents = {};
186
+ if (!config.agents.list) config.agents.list = [];
187
+ const existing = config.agents.list.find((a) => a.id === agentId);
188
+ if (existing) {
189
+ existing.name = agentName;
190
+ return config;
191
+ }
192
+ if (isMain) {
193
+ config.agents.list.push({
194
+ id: agentId,
195
+ name: agentName
196
+ });
197
+ } else {
198
+ config.agents.list.push({
199
+ id: agentId,
200
+ name: agentName,
201
+ workspace: path.join(clawDir, `workspace-${agentId}`),
202
+ agentDir: path.join(clawDir, `agents/${agentId}/agent`)
203
+ });
204
+ }
205
+ return config;
206
+ });
207
+ }
208
+ function readSoulHubPackage(dir) {
209
+ const yamlPath = path.join(dir, "soulhub.yaml");
210
+ if (!fs.existsSync(yamlPath)) {
211
+ return null;
212
+ }
213
+ try {
214
+ return yaml.load(fs.readFileSync(yamlPath, "utf-8"));
215
+ } catch {
216
+ return null;
217
+ }
218
+ }
219
+ function detectPackageKind(dir) {
220
+ const pkg = readSoulHubPackage(dir);
221
+ if (pkg) {
222
+ return pkg.kind;
223
+ }
224
+ if (fs.existsSync(path.join(dir, "IDENTITY.md"))) {
225
+ return "agent";
226
+ }
227
+ return "unknown";
228
+ }
229
+ function checkOpenClawInstalled(customDir) {
230
+ const clawDir = findOpenClawDir(customDir);
231
+ if (clawDir) {
232
+ return {
233
+ installed: true,
234
+ clawDir,
235
+ message: `OpenClaw detected at: ${clawDir}`
236
+ };
237
+ }
238
+ try {
239
+ execSync("which openclaw 2>/dev/null || where openclaw 2>nul", {
240
+ stdio: "pipe"
241
+ });
242
+ return {
243
+ installed: true,
244
+ clawDir: null,
245
+ message: "OpenClaw command found in PATH, but workspace directory not detected."
246
+ };
247
+ } catch {
248
+ }
249
+ return {
250
+ installed: false,
251
+ clawDir: null,
252
+ message: "OpenClaw is not installed. Please install OpenClaw first, use --claw-dir to specify OpenClaw directory, or set OPENCLAW_HOME environment variable."
253
+ };
254
+ }
255
+ function backupAgentWorkspace(workspaceDir) {
256
+ if (!fs.existsSync(workspaceDir)) {
257
+ return null;
258
+ }
259
+ const entries = fs.readdirSync(workspaceDir);
260
+ if (entries.length === 0) {
261
+ return null;
262
+ }
263
+ const clawDir = path.dirname(workspaceDir);
264
+ const backupBaseDir = path.join(clawDir, "agentbackup");
265
+ if (!fs.existsSync(backupBaseDir)) {
266
+ fs.mkdirSync(backupBaseDir, { recursive: true });
267
+ }
268
+ const dirName = path.basename(workspaceDir);
269
+ let backupDir = path.join(backupBaseDir, dirName);
270
+ if (fs.existsSync(backupDir)) {
271
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
272
+ backupDir = path.join(backupBaseDir, `${dirName}-${timestamp}`);
273
+ }
274
+ fs.cpSync(workspaceDir, backupDir, { recursive: true });
275
+ return backupDir;
276
+ }
277
+ var CATEGORY_LABELS = {
278
+ "self-media": "Self Media",
279
+ development: "Development",
280
+ operations: "Operations",
281
+ support: "Support",
282
+ education: "Education",
283
+ dispatcher: "Dispatcher"
284
+ };
285
+ function registerAgentToOpenClaw(agentName, workspaceDir, _clawDir) {
286
+ const agentId = agentName.toLowerCase().replace(/[\s_]+/g, "-");
287
+ try {
288
+ execSync(
289
+ `openclaw agents add "${agentId}" --workspace "${workspaceDir}" --non-interactive --json`,
290
+ { stdio: "pipe", timeout: 15e3 }
291
+ );
292
+ return {
293
+ success: true,
294
+ message: `Agent "${agentId}" registered via OpenClaw CLI.`
295
+ };
296
+ } catch (cliError) {
297
+ const stderr = cliError && typeof cliError === "object" && "stderr" in cliError ? String(cliError.stderr) : "";
298
+ if (stderr.includes("already exists")) {
299
+ return {
300
+ success: true,
301
+ message: `Agent "${agentId}" already registered in OpenClaw.`
302
+ };
303
+ }
304
+ const isCommandNotFound = cliError && typeof cliError === "object" && "code" in cliError && cliError.code === "ENOENT" || stderr.includes("not found") || stderr.includes("not recognized");
305
+ if (isCommandNotFound) {
306
+ return {
307
+ success: false,
308
+ message: "OpenClaw CLI not found. Please install OpenClaw first: https://github.com/anthropics/openclaw"
309
+ };
310
+ }
311
+ const errMsg = cliError instanceof Error ? cliError.message : String(cliError);
312
+ return {
313
+ success: false,
314
+ message: `openclaw agents add failed: ${stderr || errMsg}`
315
+ };
316
+ }
317
+ }
318
+ function restartOpenClawGateway() {
319
+ try {
320
+ execSync("openclaw gateway restart", {
321
+ stdio: "pipe",
322
+ timeout: 3e4
323
+ // 30 秒超时
324
+ });
325
+ return {
326
+ success: true,
327
+ message: "OpenClaw Gateway restarted successfully."
328
+ };
329
+ } catch (error) {
330
+ const stderr = error && typeof error === "object" && "stderr" in error ? String(error.stderr).trim() : "";
331
+ const errMsg = stderr || (error instanceof Error ? error.message : String(error));
332
+ return {
333
+ success: false,
334
+ message: errMsg
335
+ };
336
+ }
337
+ }
338
+
339
+ // src/commands/search.ts
340
+ var searchCommand = new Command("search").description("Search for agent templates in the SoulHub registry").argument("[query]", "Search query (matches name, description, tags)").option("-c, --category <category>", "Filter by category").option("-l, --limit <number>", "Max results to show", "20").action(async (query, options) => {
341
+ try {
342
+ const index = await fetchIndex();
343
+ let agents = index.agents;
344
+ if (options.category) {
345
+ agents = agents.filter(
346
+ (a) => a.category === options.category
347
+ );
348
+ }
349
+ if (query) {
350
+ const q = query.toLowerCase();
351
+ agents = agents.filter(
352
+ (a) => a.name.toLowerCase().includes(q) || a.displayName.toLowerCase().includes(q) || a.description.toLowerCase().includes(q) || a.tags.some((t) => t.toLowerCase().includes(q))
353
+ );
354
+ }
355
+ const limit = parseInt(options.limit, 10);
356
+ const shown = agents.slice(0, limit);
357
+ if (shown.length === 0) {
358
+ console.log(chalk.yellow("No agents found matching your query."));
359
+ if (query) {
360
+ console.log(
361
+ chalk.dim(` Try: soulhub search (without query to list all)`)
362
+ );
363
+ }
364
+ return;
365
+ }
366
+ console.log(
367
+ chalk.bold(`
368
+ Found ${agents.length} agent(s):
369
+ `)
370
+ );
371
+ for (const agent of shown) {
372
+ const category = CATEGORY_LABELS[agent.category] || agent.category;
373
+ console.log(
374
+ ` ${chalk.cyan.bold(agent.name)} ${chalk.dim(`v${agent.version}`)}`
375
+ );
376
+ console.log(
377
+ ` ${agent.displayName} - ${agent.description}`
378
+ );
379
+ console.log(
380
+ ` ${chalk.dim(`[${category}]`)} ${chalk.dim(agent.tags.join(", "))}`
381
+ );
382
+ console.log();
383
+ }
384
+ if (agents.length > limit) {
385
+ console.log(
386
+ chalk.dim(
387
+ ` ... and ${agents.length - limit} more. Use --limit to show more.`
388
+ )
389
+ );
390
+ }
391
+ console.log(
392
+ chalk.dim(` Install: soulhub install <name>`)
393
+ );
394
+ console.log();
395
+ } catch (error) {
396
+ console.error(
397
+ chalk.red(`Error: ${error instanceof Error ? error.message : error}`)
398
+ );
399
+ process.exit(1);
400
+ }
401
+ });
402
+
403
+ // src/commands/info.ts
404
+ import { Command as Command2 } from "commander";
405
+ import chalk2 from "chalk";
406
+ var infoCommand = new Command2("info").description("View detailed information about an agent template").argument("<name>", "Agent name").option("--identity", "Show IDENTITY.md content").option("--soul", "Show SOUL.md content").action(async (name, options) => {
407
+ try {
408
+ const index = await fetchIndex();
409
+ const agent = index.agents.find((a) => a.name === name);
410
+ if (!agent) {
411
+ console.error(chalk2.red(`Agent "${name}" not found.`));
412
+ console.log(
413
+ chalk2.dim(` Use 'soulhub search' to find available agents.`)
414
+ );
415
+ process.exit(1);
416
+ }
417
+ const category = CATEGORY_LABELS[agent.category] || agent.category;
418
+ console.log();
419
+ console.log(chalk2.bold.cyan(` ${agent.displayName}`));
420
+ console.log(chalk2.dim(` ${agent.name} v${agent.version}`));
421
+ console.log();
422
+ console.log(` ${agent.description}`);
423
+ console.log();
424
+ console.log(
425
+ ` ${chalk2.dim("Category:")} ${category}`
426
+ );
427
+ console.log(
428
+ ` ${chalk2.dim("Author:")} ${agent.author}`
429
+ );
430
+ console.log(
431
+ ` ${chalk2.dim("Tags:")} ${agent.tags.join(", ")}`
432
+ );
433
+ console.log(
434
+ ` ${chalk2.dim("Min Claw:")} ${agent.minClawVersion}`
435
+ );
436
+ console.log(
437
+ ` ${chalk2.dim("Downloads:")} ${agent.downloads}`
438
+ );
439
+ console.log();
440
+ console.log(chalk2.dim(" Files:"));
441
+ for (const [fileName, size] of Object.entries(agent.files)) {
442
+ const sizeStr = size > 1024 ? `${(size / 1024).toFixed(1)} KB` : `${size} B`;
443
+ console.log(` ${fileName} ${chalk2.dim(`(${sizeStr})`)}`);
444
+ }
445
+ if (options.identity) {
446
+ console.log();
447
+ console.log(chalk2.bold(" \u2500\u2500 IDENTITY.md \u2500\u2500"));
448
+ console.log();
449
+ const content = await fetchAgentFile(name, "IDENTITY.md");
450
+ console.log(
451
+ content.split("\n").map((l) => ` ${l}`).join("\n")
452
+ );
453
+ }
454
+ if (options.soul) {
455
+ console.log();
456
+ console.log(chalk2.bold(" \u2500\u2500 SOUL.md \u2500\u2500"));
457
+ console.log();
458
+ const content = await fetchAgentFile(name, "SOUL.md");
459
+ console.log(
460
+ content.split("\n").map((l) => ` ${l}`).join("\n")
461
+ );
462
+ }
463
+ console.log();
464
+ console.log(
465
+ chalk2.dim(` Install: soulhub install ${name}`)
466
+ );
467
+ console.log();
468
+ } catch (error) {
469
+ console.error(
470
+ chalk2.red(`Error: ${error instanceof Error ? error.message : error}`)
471
+ );
472
+ process.exit(1);
473
+ }
474
+ });
475
+
476
+ // src/commands/install.ts
477
+ import { Command as Command3 } from "commander";
478
+ import chalk3 from "chalk";
479
+ import ora from "ora";
480
+ import fs2 from "fs";
481
+ import path2 from "path";
482
+ import yaml2 from "js-yaml";
483
+ 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
+ "--dir <path>",
485
+ "Target directory (defaults to OpenClaw workspace)"
486
+ ).option(
487
+ "--claw-dir <path>",
488
+ "OpenClaw installation directory (overrides OPENCLAW_HOME env var, defaults to ~/.openclaw)"
489
+ ).action(async (name, options) => {
490
+ try {
491
+ if (options.from) {
492
+ await installFromSource(options.from, options.dir, options.clawDir);
493
+ } else if (name) {
494
+ await installFromRegistry(name, options.dir, options.clawDir);
495
+ } else {
496
+ console.error(chalk3.red("Please specify an agent or team name, or use --from to install from a local source."));
497
+ console.log(chalk3.dim(" Examples:"));
498
+ console.log(chalk3.dim(" soulhub install writer-wechat # \u4ECE registry \u5B89\u88C5\u5355 agent"));
499
+ console.log(chalk3.dim(" soulhub install dev-squad # \u4ECE registry \u5B89\u88C5\u56E2\u961F"));
500
+ console.log(chalk3.dim(" soulhub install --from ./agent-team/ # \u4ECE\u672C\u5730\u76EE\u5F55\u5B89\u88C5"));
501
+ process.exit(1);
502
+ }
503
+ } catch (error) {
504
+ console.error(
505
+ chalk3.red(`Error: ${error instanceof Error ? error.message : error}`)
506
+ );
507
+ process.exit(1);
508
+ }
509
+ });
510
+ async function installFromRegistry(name, targetDir, clawDir) {
511
+ const spinner = ora(`Checking registry for ${chalk3.cyan(name)}...`).start();
512
+ const index = await fetchIndex();
513
+ const agent = index.agents.find((a) => a.name === name);
514
+ const recipe = index.recipes.find((r) => r.name === name);
515
+ if (agent && !recipe) {
516
+ spinner.stop();
517
+ await installSingleAgent(name, targetDir, clawDir);
518
+ } else if (recipe) {
519
+ spinner.stop();
520
+ await installRecipeFromRegistry(name, recipe, targetDir, clawDir);
521
+ } else {
522
+ spinner.fail(`"${name}" not found in registry.`);
523
+ console.log(chalk3.dim(" Use 'soulhub search' to find available agents and teams."));
524
+ }
525
+ }
526
+ async function installSingleAgent(name, targetDir, clawDir) {
527
+ const spinner = ora(`Checking environment...`).start();
528
+ if (!targetDir) {
529
+ const clawCheck = checkOpenClawInstalled(clawDir);
530
+ if (!clawCheck.installed) {
531
+ spinner.fail("OpenClaw is not installed.");
532
+ printOpenClawInstallHelp();
533
+ return;
534
+ }
535
+ spinner.text = chalk3.dim(`OpenClaw detected: ${clawCheck.clawDir || "via PATH"}`);
536
+ }
537
+ spinner.text = `Fetching agent ${chalk3.cyan(name)}...`;
538
+ const index = await fetchIndex();
539
+ const agent = index.agents.find((a) => a.name === name);
540
+ if (!agent) {
541
+ spinner.fail(`Agent "${name}" not found in registry.`);
542
+ console.log(chalk3.dim(" Use 'soulhub search' to find available agents."));
543
+ return;
544
+ }
545
+ let workspaceDir;
546
+ if (targetDir) {
547
+ workspaceDir = path2.resolve(targetDir);
548
+ } else {
549
+ const resolvedClawDir = findOpenClawDir(clawDir);
550
+ if (!resolvedClawDir) {
551
+ spinner.fail("OpenClaw workspace directory not found.");
552
+ printOpenClawInstallHelp();
553
+ return;
554
+ }
555
+ workspaceDir = getMainWorkspaceDir(resolvedClawDir);
556
+ }
557
+ if (!targetDir) {
558
+ const resolvedClawDir = findOpenClawDir(clawDir);
559
+ const mainCheck = checkMainAgentExists(resolvedClawDir);
560
+ if (mainCheck.hasContent) {
561
+ spinner.warn(
562
+ `Existing main agent detected. Backing up workspace...`
563
+ );
564
+ const backupDir = backupAgentWorkspace(workspaceDir);
565
+ if (backupDir) {
566
+ console.log(chalk3.yellow(` \u26A0 Existing main agent backed up to: ${backupDir}`));
567
+ }
568
+ }
569
+ } else {
570
+ const backupDir = backupAgentWorkspace(workspaceDir);
571
+ if (backupDir) {
572
+ console.log(chalk3.yellow(` \u26A0 Existing agent backed up to: ${backupDir}`));
573
+ }
574
+ }
575
+ if (!fs2.existsSync(workspaceDir)) {
576
+ fs2.mkdirSync(workspaceDir, { recursive: true });
577
+ }
578
+ if (!targetDir) {
579
+ spinner.text = `Registering ${chalk3.cyan(agent.displayName)} as main agent...`;
580
+ const resolvedClawDir = findOpenClawDir(clawDir);
581
+ addAgentToOpenClawConfig(resolvedClawDir, "main", name, true);
582
+ spinner.text = chalk3.dim(`Main agent registered in openclaw.json`);
583
+ }
584
+ spinner.text = `Downloading ${chalk3.cyan(agent.displayName)} soul files...`;
585
+ await downloadAgentFiles(name, workspaceDir, spinner);
586
+ await saveAgentManifest(name, agent, workspaceDir);
587
+ recordInstall(name, agent.version, workspaceDir);
588
+ spinner.succeed(
589
+ `${chalk3.cyan.bold(agent.displayName)} installed as main agent!`
590
+ );
591
+ console.log();
592
+ console.log(` ${chalk3.dim("Location:")} ${workspaceDir}`);
593
+ console.log(` ${chalk3.dim("Version:")} ${agent.version}`);
594
+ console.log(` ${chalk3.dim("Type:")} ${chalk3.blue("Single Agent (Main)")}`);
595
+ if (!targetDir) {
596
+ await tryRestartGateway();
597
+ }
598
+ console.log();
599
+ }
600
+ async function installRecipeFromRegistry(name, recipe, targetDir, clawDir) {
601
+ const spinner = ora(`Installing team ${chalk3.cyan(recipe.displayName)}...`).start();
602
+ if (!targetDir) {
603
+ const clawCheck = checkOpenClawInstalled(clawDir);
604
+ if (!clawCheck.installed) {
605
+ spinner.fail("OpenClaw is not installed.");
606
+ printOpenClawInstallHelp();
607
+ return;
608
+ }
609
+ }
610
+ const resolvedClawDir = targetDir ? path2.resolve(targetDir) : findOpenClawDir(clawDir);
611
+ if (!resolvedClawDir) {
612
+ spinner.fail("OpenClaw workspace directory not found.");
613
+ printOpenClawInstallHelp();
614
+ return;
615
+ }
616
+ spinner.text = `Fetching team configuration...`;
617
+ let pkg;
618
+ try {
619
+ const soulhubYamlContent = await fetchRecipeFile(name, "soulhub.yaml");
620
+ pkg = yaml2.load(soulhubYamlContent);
621
+ } catch {
622
+ spinner.fail(`Failed to fetch soulhub.yaml for recipe "${name}". Recipe packages must include a soulhub.yaml file.`);
623
+ return;
624
+ }
625
+ if (pkg.dispatcher) {
626
+ spinner.text = `Installing dispatcher ${chalk3.blue(pkg.dispatcher.name)}...`;
627
+ await installDispatcher(pkg.dispatcher, resolvedClawDir, clawDir, targetDir, spinner);
628
+ }
629
+ const index = await fetchIndex();
630
+ const workerIds = [];
631
+ for (const worker of pkg.agents || []) {
632
+ spinner.text = `Installing worker ${chalk3.cyan(worker.name)}...`;
633
+ const agentName = worker.dir || worker.name;
634
+ const agentId = worker.name;
635
+ const workerDir = targetDir ? path2.join(resolvedClawDir, `workspace-${agentId}`) : getWorkspaceDir(resolvedClawDir, agentId);
636
+ const backupDir = backupAgentWorkspace(workerDir);
637
+ if (backupDir) {
638
+ console.log(chalk3.yellow(` \u26A0 Existing ${agentId} backed up to: ${backupDir}`));
639
+ }
640
+ if (!targetDir) {
641
+ const regResult = registerAgentToOpenClaw(agentId, workerDir, clawDir);
642
+ if (!regResult.success) {
643
+ console.log(chalk3.yellow(` \u26A0 Failed to register ${agentId}: ${regResult.message}`));
644
+ continue;
645
+ }
646
+ } else {
647
+ fs2.mkdirSync(workerDir, { recursive: true });
648
+ }
649
+ await downloadAgentFiles(agentName, workerDir, spinner);
650
+ const agentInfo = index.agents.find((a) => a.name === agentName);
651
+ if (agentInfo) {
652
+ await saveAgentManifest(agentName, agentInfo, workerDir);
653
+ }
654
+ recordInstall(agentId, recipe.version || "1.0.0", workerDir);
655
+ workerIds.push(agentId);
656
+ }
657
+ if (!targetDir) {
658
+ spinner.text = "Configuring multi-agent communication...";
659
+ const dispatcherId = "main";
660
+ configureMultiAgentCommunication(resolvedClawDir, dispatcherId, workerIds);
661
+ }
662
+ spinner.succeed(
663
+ `Team ${chalk3.cyan.bold(recipe.displayName)} installed! (1 dispatcher + ${workerIds.length} workers)`
664
+ );
665
+ printTeamSummary(pkg, workerIds);
666
+ if (!targetDir) {
667
+ await tryRestartGateway();
668
+ }
669
+ }
670
+ async function installFromSource(source, targetDir, clawDir) {
671
+ const spinner = ora("Analyzing package...").start();
672
+ let packageDir;
673
+ let tempDir = null;
674
+ if (source.startsWith("http://") || source.startsWith("https://")) {
675
+ spinner.text = "Downloading package...";
676
+ const response = await fetch(source);
677
+ if (!response.ok) {
678
+ spinner.fail(`Failed to download: ${response.statusText}`);
679
+ return;
680
+ }
681
+ const contentType = response.headers.get("content-type") || "";
682
+ if (contentType.includes("zip") || source.endsWith(".zip")) {
683
+ const JSZip = (await import("jszip")).default;
684
+ const arrayBuffer = await response.arrayBuffer();
685
+ const zip = await JSZip.loadAsync(arrayBuffer);
686
+ tempDir = path2.join(process.env.HOME || "/tmp", ".soulhub", "tmp", `pkg-${Date.now()}`);
687
+ fs2.mkdirSync(tempDir, { recursive: true });
688
+ await extractZipToDir(zip, tempDir);
689
+ packageDir = tempDir;
690
+ } else {
691
+ spinner.fail("Unsupported URL content type. Expected ZIP file.");
692
+ return;
693
+ }
694
+ } else if (source.endsWith(".zip")) {
695
+ spinner.text = "Extracting ZIP file...";
696
+ const JSZip = (await import("jszip")).default;
697
+ const zipData = fs2.readFileSync(path2.resolve(source));
698
+ const zip = await JSZip.loadAsync(zipData);
699
+ tempDir = path2.join(process.env.HOME || "/tmp", ".soulhub", "tmp", `pkg-${Date.now()}`);
700
+ fs2.mkdirSync(tempDir, { recursive: true });
701
+ await extractZipToDir(zip, tempDir);
702
+ packageDir = tempDir;
703
+ } else {
704
+ packageDir = path2.resolve(source);
705
+ if (!fs2.existsSync(packageDir)) {
706
+ spinner.fail(`Directory not found: ${packageDir}`);
707
+ return;
708
+ }
709
+ }
710
+ const kind = detectPackageKind(packageDir);
711
+ spinner.text = `Detected package type: ${chalk3.blue(kind)}`;
712
+ if (kind === "agent") {
713
+ spinner.stop();
714
+ await installSingleAgentFromDir(packageDir, targetDir, clawDir);
715
+ } else if (kind === "team") {
716
+ spinner.stop();
717
+ await installTeamFromDir(packageDir, targetDir, clawDir);
718
+ } else {
719
+ spinner.fail("Cannot determine package type. Expected soulhub.yaml or IDENTITY.md.");
720
+ }
721
+ if (tempDir && fs2.existsSync(tempDir)) {
722
+ fs2.rmSync(tempDir, { recursive: true, force: true });
723
+ }
724
+ }
725
+ async function installSingleAgentFromDir(packageDir, targetDir, clawDir) {
726
+ const spinner = ora("Installing single agent...").start();
727
+ const pkg = readSoulHubPackage(packageDir);
728
+ const agentName = pkg?.name || path2.basename(packageDir);
729
+ if (!targetDir) {
730
+ const clawCheck = checkOpenClawInstalled(clawDir);
731
+ if (!clawCheck.installed) {
732
+ spinner.fail("OpenClaw is not installed.");
733
+ printOpenClawInstallHelp();
734
+ return;
735
+ }
736
+ }
737
+ let workspaceDir;
738
+ if (targetDir) {
739
+ workspaceDir = path2.resolve(targetDir);
740
+ } else {
741
+ const resolvedClawDir = findOpenClawDir(clawDir);
742
+ if (!resolvedClawDir) {
743
+ spinner.fail("OpenClaw workspace directory not found.");
744
+ return;
745
+ }
746
+ workspaceDir = getMainWorkspaceDir(resolvedClawDir);
747
+ }
748
+ if (!targetDir) {
749
+ const resolvedClawDir = findOpenClawDir(clawDir);
750
+ const mainCheck = checkMainAgentExists(resolvedClawDir);
751
+ if (mainCheck.hasContent) {
752
+ spinner.warn("Existing main agent detected. Backing up...");
753
+ const backupDir = backupAgentWorkspace(workspaceDir);
754
+ if (backupDir) {
755
+ console.log(chalk3.yellow(` \u26A0 Existing main agent backed up to: ${backupDir}`));
756
+ }
757
+ }
758
+ }
759
+ if (!fs2.existsSync(workspaceDir)) {
760
+ fs2.mkdirSync(workspaceDir, { recursive: true });
761
+ }
762
+ if (!targetDir) {
763
+ spinner.text = `Registering ${chalk3.cyan(agentName)} as main agent...`;
764
+ const resolvedClawDir = findOpenClawDir(clawDir);
765
+ addAgentToOpenClawConfig(resolvedClawDir, "main", agentName, true);
766
+ }
767
+ spinner.text = `Copying soul files...`;
768
+ copyAgentFilesFromDir(packageDir, workspaceDir);
769
+ recordInstall(agentName, pkg?.version || "local", workspaceDir);
770
+ spinner.succeed(`${chalk3.cyan.bold(agentName)} installed as main agent!`);
771
+ console.log();
772
+ console.log(` ${chalk3.dim("Location:")} ${workspaceDir}`);
773
+ console.log(` ${chalk3.dim("Source:")} ${packageDir}`);
774
+ console.log(` ${chalk3.dim("Type:")} ${chalk3.blue("Single Agent (Main)")}`);
775
+ if (!targetDir) {
776
+ await tryRestartGateway();
777
+ }
778
+ console.log();
779
+ }
780
+ async function installTeamFromDir(packageDir, targetDir, clawDir) {
781
+ const spinner = ora("Installing team...").start();
782
+ const pkg = readSoulHubPackage(packageDir);
783
+ if (!pkg || pkg.kind !== "team") {
784
+ spinner.fail("Invalid team package. Missing soulhub.yaml.");
785
+ return;
786
+ }
787
+ if (!targetDir) {
788
+ const clawCheck = checkOpenClawInstalled(clawDir);
789
+ if (!clawCheck.installed) {
790
+ spinner.fail("OpenClaw is not installed.");
791
+ printOpenClawInstallHelp();
792
+ return;
793
+ }
794
+ }
795
+ const resolvedClawDir = targetDir ? path2.resolve(targetDir) : findOpenClawDir(clawDir);
796
+ if (!resolvedClawDir) {
797
+ spinner.fail("OpenClaw workspace directory not found.");
798
+ return;
799
+ }
800
+ if (pkg.dispatcher) {
801
+ spinner.text = `Installing dispatcher ${chalk3.blue(pkg.dispatcher.name)}...`;
802
+ const mainWorkspace = targetDir ? path2.join(resolvedClawDir, "workspace") : getMainWorkspaceDir(resolvedClawDir);
803
+ if (!targetDir) {
804
+ const mainCheck = checkMainAgentExists(resolvedClawDir);
805
+ if (mainCheck.hasContent) {
806
+ spinner.warn("Existing main agent detected. Backing up...");
807
+ const backupDir = backupAgentWorkspace(mainWorkspace);
808
+ if (backupDir) {
809
+ console.log(chalk3.yellow(` \u26A0 Existing main agent backed up to: ${backupDir}`));
810
+ }
811
+ }
812
+ }
813
+ if (!fs2.existsSync(mainWorkspace)) {
814
+ fs2.mkdirSync(mainWorkspace, { recursive: true });
815
+ }
816
+ if (!targetDir) {
817
+ addAgentToOpenClawConfig(resolvedClawDir, "main", pkg.dispatcher.name, true);
818
+ }
819
+ const dispatcherSourceDir = path2.join(packageDir, pkg.dispatcher.dir);
820
+ if (fs2.existsSync(dispatcherSourceDir)) {
821
+ copyAgentFilesFromDir(dispatcherSourceDir, mainWorkspace);
822
+ }
823
+ recordInstall("dispatcher", pkg.version || "local", mainWorkspace);
824
+ }
825
+ const workerIds = [];
826
+ for (const worker of pkg.agents || []) {
827
+ const agentId = worker.name;
828
+ const agentDir = worker.dir || worker.name;
829
+ spinner.text = `Installing worker ${chalk3.cyan(agentId)}...`;
830
+ const workerWorkspace = targetDir ? path2.join(resolvedClawDir, `workspace-${agentId}`) : getWorkspaceDir(resolvedClawDir, agentId);
831
+ const backupDir = backupAgentWorkspace(workerWorkspace);
832
+ if (backupDir) {
833
+ console.log(chalk3.yellow(` \u26A0 Existing ${agentId} backed up to: ${backupDir}`));
834
+ }
835
+ if (!targetDir) {
836
+ const regResult = registerAgentToOpenClaw(agentId, workerWorkspace, clawDir);
837
+ if (!regResult.success) {
838
+ console.log(chalk3.yellow(` \u26A0 Failed to register ${agentId}: ${regResult.message}`));
839
+ continue;
840
+ }
841
+ } else {
842
+ fs2.mkdirSync(workerWorkspace, { recursive: true });
843
+ }
844
+ const workerSourceDir = path2.join(packageDir, agentDir);
845
+ if (fs2.existsSync(workerSourceDir)) {
846
+ copyAgentFilesFromDir(workerSourceDir, workerWorkspace);
847
+ }
848
+ recordInstall(agentId, pkg.version || "local", workerWorkspace);
849
+ workerIds.push(agentId);
850
+ }
851
+ if (!targetDir && workerIds.length > 0) {
852
+ spinner.text = "Configuring multi-agent communication...";
853
+ configureMultiAgentCommunication(resolvedClawDir, "main", workerIds);
854
+ }
855
+ spinner.succeed(
856
+ `Team ${chalk3.cyan.bold(pkg.name)} installed! (${pkg.dispatcher ? "1 dispatcher + " : ""}${workerIds.length} workers)`
857
+ );
858
+ printTeamSummary(pkg, workerIds);
859
+ if (!targetDir) {
860
+ await tryRestartGateway();
861
+ }
862
+ }
863
+ async function downloadAgentFiles(agentName, workspaceDir, spinner) {
864
+ fs2.mkdirSync(workspaceDir, { recursive: true });
865
+ const coreFiles = ["IDENTITY.md", "SOUL.md"];
866
+ for (const fileName of coreFiles) {
867
+ const content = await fetchAgentFile(agentName, fileName);
868
+ fs2.writeFileSync(path2.join(workspaceDir, fileName), content);
869
+ spinner.text = `Downloaded ${fileName}`;
870
+ }
871
+ const optionalFiles = ["USER.md.template", "TOOLS.md.template"];
872
+ for (const fileName of optionalFiles) {
873
+ try {
874
+ const content = await fetchAgentFile(agentName, fileName);
875
+ const actualName = fileName.replace(".template", "");
876
+ fs2.writeFileSync(path2.join(workspaceDir, actualName), content);
877
+ } catch {
878
+ }
879
+ }
880
+ }
881
+ async function saveAgentManifest(agentName, agent, workspaceDir) {
882
+ try {
883
+ const manifestContent = await fetchAgentFile(agentName, "manifest.yaml");
884
+ fs2.writeFileSync(path2.join(workspaceDir, "manifest.yaml"), manifestContent);
885
+ } catch {
886
+ const manifest = {
887
+ name: agent.name,
888
+ displayName: agent.displayName,
889
+ description: agent.description,
890
+ category: agent.category,
891
+ tags: agent.tags,
892
+ version: agent.version,
893
+ author: agent.author
894
+ };
895
+ fs2.writeFileSync(path2.join(workspaceDir, "manifest.yaml"), yaml2.dump(manifest));
896
+ }
897
+ }
898
+ function copyAgentFilesFromDir(sourceDir, targetDir) {
899
+ const filesToCopy = ["IDENTITY.md", "SOUL.md", "USER.md", "TOOLS.md", "AGENTS.md", "HEARTBEAT.md"];
900
+ for (const fileName of filesToCopy) {
901
+ const sourcePath = path2.join(sourceDir, fileName);
902
+ if (fs2.existsSync(sourcePath)) {
903
+ fs2.mkdirSync(targetDir, { recursive: true });
904
+ fs2.copyFileSync(sourcePath, path2.join(targetDir, fileName));
905
+ }
906
+ }
907
+ }
908
+ async function installDispatcher(dispatcher, resolvedClawDir, clawDir, targetDir, spinner) {
909
+ const mainWorkspace = targetDir ? path2.join(resolvedClawDir, "workspace") : getMainWorkspaceDir(resolvedClawDir);
910
+ if (!targetDir) {
911
+ const mainCheck = checkMainAgentExists(resolvedClawDir);
912
+ if (mainCheck.hasContent) {
913
+ if (spinner) spinner.warn("Existing main agent detected. Backing up...");
914
+ const backupDir = backupAgentWorkspace(mainWorkspace);
915
+ if (backupDir) {
916
+ console.log(chalk3.yellow(` \u26A0 Existing main agent backed up to: ${backupDir}`));
917
+ }
918
+ }
919
+ }
920
+ if (!fs2.existsSync(mainWorkspace)) {
921
+ fs2.mkdirSync(mainWorkspace, { recursive: true });
922
+ }
923
+ if (!targetDir) {
924
+ addAgentToOpenClawConfig(resolvedClawDir, "main", dispatcher.name, true);
925
+ }
926
+ const templateName = dispatcher.dir || dispatcher.name;
927
+ if (spinner) spinner.text = `Downloading dispatcher files...`;
928
+ await downloadAgentFiles(templateName, mainWorkspace, spinner || ora());
929
+ recordInstall("dispatcher", "1.0.0", mainWorkspace);
930
+ }
931
+ async function extractZipToDir(zip, targetDir) {
932
+ const entries = Object.entries(zip.files);
933
+ for (const [relativePath, file] of entries) {
934
+ const fullPath = path2.join(targetDir, relativePath);
935
+ if (file.dir) {
936
+ fs2.mkdirSync(fullPath, { recursive: true });
937
+ } else {
938
+ fs2.mkdirSync(path2.dirname(fullPath), { recursive: true });
939
+ const content = await file.async("nodebuffer");
940
+ fs2.writeFileSync(fullPath, content);
941
+ }
942
+ }
943
+ }
944
+ function printOpenClawInstallHelp() {
945
+ console.log(chalk3.dim(" Please install OpenClaw first, or use one of the following options:"));
946
+ console.log(chalk3.dim(" --claw-dir <path> Specify OpenClaw installation directory"));
947
+ console.log(chalk3.dim(" --dir <path> Specify agent target directory directly"));
948
+ console.log(chalk3.dim(" OPENCLAW_HOME=<path> Set environment variable"));
949
+ console.log(chalk3.dim(" Visit: https://github.com/anthropics/openclaw for installation instructions."));
950
+ }
951
+ function printTeamSummary(pkg, workerIds) {
952
+ console.log();
953
+ if (pkg.dispatcher) {
954
+ console.log(` ${chalk3.blue("\u26A1 [Dispatcher]")} ${chalk3.cyan(pkg.dispatcher.name)} \u2192 ${chalk3.dim("workspace/")}`);
955
+ }
956
+ for (const id of workerIds) {
957
+ console.log(` ${chalk3.dim(" \u2713 [Worker]")} ${chalk3.cyan(id)} \u2192 ${chalk3.dim(`workspace-${id}/`)}`);
958
+ }
959
+ console.log();
960
+ console.log(` ${chalk3.dim("Type:")} ${chalk3.blue("Multi-Agent Team")}`);
961
+ if (pkg.routing && pkg.routing.length > 0) {
962
+ console.log(` ${chalk3.dim("Routing:")} ${pkg.routing.length} rules configured`);
963
+ }
964
+ console.log();
965
+ }
966
+ async function tryRestartGateway() {
967
+ const restartSpinner = ora("Restarting OpenClaw Gateway...").start();
968
+ const result = restartOpenClawGateway();
969
+ if (result.success) {
970
+ restartSpinner.succeed("OpenClaw Gateway restarted successfully.");
971
+ } else {
972
+ restartSpinner.warn("Failed to restart OpenClaw Gateway.");
973
+ console.log(chalk3.yellow(` Reason: ${result.message}`));
974
+ console.log(chalk3.dim(" Please restart manually:"));
975
+ console.log(chalk3.dim(" openclaw gateway restart"));
976
+ }
977
+ }
978
+
979
+ // src/commands/list.ts
980
+ import { Command as Command4 } from "commander";
981
+ import chalk4 from "chalk";
982
+ var listCommand = new Command4("list").description("List installed agent templates").alias("ls").action(async () => {
983
+ try {
984
+ const config = loadConfig();
985
+ if (config.installed.length === 0) {
986
+ console.log(chalk4.yellow("\n No agents installed yet.\n"));
987
+ console.log(
988
+ chalk4.dim(" Install one: soulhub install <name>")
989
+ );
990
+ console.log(
991
+ chalk4.dim(" Browse all: soulhub search\n")
992
+ );
993
+ return;
994
+ }
995
+ console.log(
996
+ chalk4.bold(`
997
+ Installed agents (${config.installed.length}):
998
+ `)
999
+ );
1000
+ for (const agent of config.installed) {
1001
+ const date = new Date(agent.installedAt).toLocaleDateString();
1002
+ console.log(
1003
+ ` ${chalk4.cyan.bold(agent.name)} ${chalk4.dim(`v${agent.version}`)}`
1004
+ );
1005
+ console.log(
1006
+ ` ${chalk4.dim("Installed:")} ${date} ${chalk4.dim("Location:")} ${agent.workspace}`
1007
+ );
1008
+ console.log();
1009
+ }
1010
+ } catch (error) {
1011
+ console.error(
1012
+ chalk4.red(`Error: ${error instanceof Error ? error.message : error}`)
1013
+ );
1014
+ process.exit(1);
1015
+ }
1016
+ });
1017
+
1018
+ // src/commands/update.ts
1019
+ import { Command as Command5 } from "commander";
1020
+ import chalk5 from "chalk";
1021
+ import ora2 from "ora";
1022
+ import fs3 from "fs";
1023
+ import path3 from "path";
1024
+ 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
+ try {
1026
+ const config = loadConfig();
1027
+ if (config.installed.length === 0) {
1028
+ console.log(chalk5.yellow("\n No agents installed.\n"));
1029
+ return;
1030
+ }
1031
+ const spinner = ora2("Checking for updates...").start();
1032
+ const index = await fetchIndex();
1033
+ const toUpdate = name ? config.installed.filter((a) => a.name === name) : config.installed;
1034
+ if (toUpdate.length === 0) {
1035
+ spinner.fail(`Agent "${name}" is not installed.`);
1036
+ return;
1037
+ }
1038
+ let updated = 0;
1039
+ for (const installed of toUpdate) {
1040
+ const remote = index.agents.find(
1041
+ (a) => a.name === installed.name
1042
+ );
1043
+ if (!remote) {
1044
+ spinner.text = `${installed.name}: not found in registry, skipping`;
1045
+ continue;
1046
+ }
1047
+ if (remote.version === installed.version) {
1048
+ continue;
1049
+ }
1050
+ spinner.text = `Updating ${chalk5.cyan(installed.name)} (${installed.version} \u2192 ${remote.version})...`;
1051
+ const workspaceDir = installed.workspace;
1052
+ if (!fs3.existsSync(workspaceDir)) {
1053
+ fs3.mkdirSync(workspaceDir, { recursive: true });
1054
+ }
1055
+ for (const fileName of ["IDENTITY.md", "SOUL.md", "manifest.yaml"]) {
1056
+ try {
1057
+ const content = await fetchAgentFile(
1058
+ installed.name,
1059
+ fileName
1060
+ );
1061
+ fs3.writeFileSync(
1062
+ path3.join(workspaceDir, fileName),
1063
+ content
1064
+ );
1065
+ } catch {
1066
+ }
1067
+ }
1068
+ installed.version = remote.version;
1069
+ installed.installedAt = (/* @__PURE__ */ new Date()).toISOString();
1070
+ updated++;
1071
+ }
1072
+ saveConfig(config);
1073
+ if (updated === 0) {
1074
+ spinner.succeed("All agents are up to date.");
1075
+ } else {
1076
+ spinner.succeed(`Updated ${updated} agent(s).`);
1077
+ }
1078
+ console.log();
1079
+ } catch (error) {
1080
+ console.error(
1081
+ chalk5.red(`Error: ${error instanceof Error ? error.message : error}`)
1082
+ );
1083
+ process.exit(1);
1084
+ }
1085
+ });
1086
+
1087
+ // src/commands/uninstall.ts
1088
+ import { Command as Command6 } from "commander";
1089
+ import chalk6 from "chalk";
1090
+ import ora3 from "ora";
1091
+ import fs4 from "fs";
1092
+ 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
+ try {
1094
+ const config = loadConfig();
1095
+ const installed = config.installed.find((a) => a.name === name);
1096
+ if (!installed) {
1097
+ console.error(
1098
+ chalk6.red(`
1099
+ Agent "${name}" is not installed.
1100
+ `)
1101
+ );
1102
+ console.log(
1103
+ chalk6.dim(" Use 'soulhub list' to see installed agents.")
1104
+ );
1105
+ process.exit(1);
1106
+ }
1107
+ const spinner = ora3(
1108
+ `Uninstalling ${chalk6.cyan(name)}...`
1109
+ ).start();
1110
+ if (!options.keepFiles && fs4.existsSync(installed.workspace)) {
1111
+ fs4.rmSync(installed.workspace, { recursive: true, force: true });
1112
+ spinner.text = `Removed workspace: ${installed.workspace}`;
1113
+ }
1114
+ removeInstallRecord(name);
1115
+ spinner.succeed(
1116
+ `${chalk6.cyan.bold(name)} uninstalled.`
1117
+ );
1118
+ if (options.keepFiles) {
1119
+ console.log(
1120
+ chalk6.dim(` Files kept at: ${installed.workspace}`)
1121
+ );
1122
+ }
1123
+ console.log();
1124
+ } catch (error) {
1125
+ console.error(
1126
+ chalk6.red(`Error: ${error instanceof Error ? error.message : error}`)
1127
+ );
1128
+ process.exit(1);
1129
+ }
1130
+ });
1131
+
1132
+ // src/commands/publish.ts
1133
+ import { Command as Command7 } from "commander";
1134
+ import chalk7 from "chalk";
1135
+ import ora4 from "ora";
1136
+ import fs5 from "fs";
1137
+ import path4 from "path";
1138
+ import yaml3 from "js-yaml";
1139
+ var publishCommand = new Command7("publish").description("Publish an agent template to the SoulHub community").argument(
1140
+ "[directory]",
1141
+ "Agent workspace directory (defaults to current directory)"
1142
+ ).action(async (directory) => {
1143
+ try {
1144
+ const dir = directory ? path4.resolve(directory) : process.cwd();
1145
+ const spinner = ora4("Validating agent template...").start();
1146
+ const requiredFiles = ["manifest.yaml", "IDENTITY.md", "SOUL.md"];
1147
+ const missing = requiredFiles.filter(
1148
+ (f) => !fs5.existsSync(path4.join(dir, f))
1149
+ );
1150
+ if (missing.length > 0) {
1151
+ spinner.fail("Missing required files:");
1152
+ for (const f of missing) {
1153
+ console.log(chalk7.red(` - ${f}`));
1154
+ }
1155
+ console.log();
1156
+ console.log(chalk7.dim(" Required files: manifest.yaml, IDENTITY.md, SOUL.md"));
1157
+ console.log(
1158
+ chalk7.dim(
1159
+ " See: https://soulhub.dev/docs/contributing"
1160
+ )
1161
+ );
1162
+ return;
1163
+ }
1164
+ const manifestContent = fs5.readFileSync(
1165
+ path4.join(dir, "manifest.yaml"),
1166
+ "utf-8"
1167
+ );
1168
+ const manifest = yaml3.load(manifestContent);
1169
+ const requiredFields = [
1170
+ "name",
1171
+ "displayName",
1172
+ "description",
1173
+ "category",
1174
+ "version",
1175
+ "author"
1176
+ ];
1177
+ const missingFields = requiredFields.filter(
1178
+ (f) => !manifest[f]
1179
+ );
1180
+ if (missingFields.length > 0) {
1181
+ spinner.fail("Missing required fields in manifest.yaml:");
1182
+ for (const f of missingFields) {
1183
+ console.log(chalk7.red(` - ${f}`));
1184
+ }
1185
+ return;
1186
+ }
1187
+ const validCategories = [
1188
+ "self-media",
1189
+ "development",
1190
+ "operations",
1191
+ "support",
1192
+ "education",
1193
+ "dispatcher"
1194
+ ];
1195
+ if (!validCategories.includes(manifest.category)) {
1196
+ spinner.fail(
1197
+ `Invalid category: ${manifest.category}. Must be one of: ${validCategories.join(", ")}`
1198
+ );
1199
+ return;
1200
+ }
1201
+ spinner.succeed("Template validation passed!");
1202
+ console.log();
1203
+ console.log(chalk7.bold(` ${manifest.displayName}`));
1204
+ console.log(
1205
+ chalk7.dim(` ${manifest.name} v${manifest.version}`)
1206
+ );
1207
+ console.log(` ${manifest.description}`);
1208
+ console.log();
1209
+ console.log(chalk7.bold(" Next steps to publish:"));
1210
+ console.log();
1211
+ console.log(
1212
+ ` 1. Fork ${chalk7.cyan("github.com/soulhub-community/soulhub")}`
1213
+ );
1214
+ console.log(
1215
+ ` 2. Copy your agent directory to ${chalk7.cyan(`registry/agents/${manifest.name}/`)}`
1216
+ );
1217
+ console.log(
1218
+ ` 3. Submit a Pull Request`
1219
+ );
1220
+ console.log();
1221
+ console.log(
1222
+ chalk7.dim(
1223
+ " Your template will be reviewed and added to the community registry."
1224
+ )
1225
+ );
1226
+ console.log();
1227
+ } catch (error) {
1228
+ console.error(
1229
+ chalk7.red(`Error: ${error instanceof Error ? error.message : error}`)
1230
+ );
1231
+ process.exit(1);
1232
+ }
1233
+ });
1234
+
1235
+ // src/index.ts
1236
+ var program = new Command8();
1237
+ program.name("soulhub").description("SoulHub CLI - Install and manage AI agent persona templates").version("0.1.0");
1238
+ program.addCommand(searchCommand);
1239
+ program.addCommand(infoCommand);
1240
+ program.addCommand(installCommand);
1241
+ program.addCommand(listCommand);
1242
+ program.addCommand(updateCommand);
1243
+ program.addCommand(uninstallCommand);
1244
+ program.addCommand(publishCommand);
1245
+ program.parse();