lua-cli 3.0.3 → 3.1.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +8 -1
  2. package/dist/api/job.api.service.d.ts +3 -1
  3. package/dist/api/job.api.service.js +7 -2
  4. package/dist/api/logs.api.service.d.ts +11 -13
  5. package/dist/api/logs.api.service.js +20 -17
  6. package/dist/api/marketplace.api.service.d.ts +25 -0
  7. package/dist/api/marketplace.api.service.js +105 -0
  8. package/dist/api/user.data.api.service.js +7 -2
  9. package/dist/cli/command-definitions.d.ts +6 -0
  10. package/dist/cli/command-definitions.js +25 -1
  11. package/dist/commands/env.d.ts +11 -0
  12. package/dist/commands/env.js +1 -79
  13. package/dist/commands/index.d.ts +1 -0
  14. package/dist/commands/index.js +1 -0
  15. package/dist/commands/logs.js +128 -68
  16. package/dist/commands/marketplace.d.ts +12 -0
  17. package/dist/commands/marketplace.js +1593 -0
  18. package/dist/commands/persona.js +9 -4
  19. package/dist/common/job.instance.js +1 -3
  20. package/dist/common/user.instance.d.ts +5 -2
  21. package/dist/common/user.instance.js +55 -21
  22. package/dist/config/constants.d.ts +1 -1
  23. package/dist/config/constants.js +5 -1
  24. package/dist/index.js +6 -4
  25. package/dist/interfaces/index.d.ts +5 -4
  26. package/dist/interfaces/marketplace.d.ts +99 -0
  27. package/dist/interfaces/marketplace.js +2 -0
  28. package/dist/interfaces/user.d.ts +19 -0
  29. package/dist/interfaces/user.js +1 -0
  30. package/dist/types/compile.types.d.ts +48 -41
  31. package/dist/types/compile.types.js +1 -0
  32. package/dist/utils/env-loader.utils.d.ts +9 -0
  33. package/dist/utils/env-loader.utils.js +88 -0
  34. package/dist/utils/job-management.d.ts +3 -3
  35. package/dist/utils/postprocessor-management.d.ts +3 -3
  36. package/dist/utils/preprocessor-management.d.ts +3 -3
  37. package/dist/utils/semver.d.ts +27 -0
  38. package/dist/utils/semver.js +75 -0
  39. package/dist/utils/skill-management.d.ts +4 -4
  40. package/dist/utils/webhook-management.d.ts +3 -3
  41. package/dist/web/app.js +3 -3
  42. package/package.json +1 -1
  43. package/template/README.md +13 -0
@@ -0,0 +1,1593 @@
1
+ /**
2
+ * Marketplace Command
3
+ * A command to interact with the Lua Marketplace.
4
+ */
5
+ import { loadApiKey, checkApiKey } from "../services/auth.js";
6
+ import { withErrorHandling, writeProgress, writeSuccess, writeInfo, } from "../utils/cli.js";
7
+ import { safePrompt } from "../utils/prompt-handler.js";
8
+ import { MarketplaceApiService } from "../api/marketplace.api.service.js";
9
+ import { readSkillConfig } from "../utils/files.js";
10
+ import { validateConfig, validateAgentConfig } from "../utils/dev-helpers.js";
11
+ import { BASE_URLS } from "../config/constants.js";
12
+ import SkillApi from "../api/skills.api.service.js";
13
+ import DeveloperApi from "../api/developer.api.service.js";
14
+ import { loadSandboxEnvVariables } from "../utils/env-loader.utils.js";
15
+ import inquirer from "inquirer";
16
+ import { compareVersions } from "../utils/semver.js";
17
+ /**
18
+ * Main marketplace command - interactive menu.
19
+ *
20
+ * Features:
21
+ * - Role selection (Creator or Installer)
22
+ * - Creator: List, publish, update, unlist skills, manage versions
23
+ * - Installer: Browse, search, install, and manage marketplace skills
24
+ *
25
+ * @param role - Optional role argument ('create', 'creator', 'install', or 'installer')
26
+ * @returns Promise that resolves when command completes.
27
+ */
28
+ export async function marketplaceCommand(role) {
29
+ return withErrorHandling(async () => {
30
+ // Step 1: Load and validate configuration
31
+ const config = readSkillConfig();
32
+ try {
33
+ validateConfig(config);
34
+ validateAgentConfig(config);
35
+ }
36
+ catch (error) {
37
+ console.error(`\n❌ Invalid lua.skill.yaml configuration: ${error.message}\n`);
38
+ process.exit(1);
39
+ }
40
+ // Step 2: Authenticate
41
+ const apiKey = await loadApiKey();
42
+ if (!apiKey) {
43
+ console.error("❌ No API key found. Please run 'lua auth configure' to set up your API key.");
44
+ process.exit(1);
45
+ }
46
+ await checkApiKey(apiKey);
47
+ writeProgress("✅ Authenticated");
48
+ const marketplaceApi = new MarketplaceApiService(apiKey);
49
+ let selectedRole = null;
50
+ // Step 3: Check if role was provided as argument
51
+ if (role) {
52
+ const normalizedRole = role.toLowerCase();
53
+ if (normalizedRole === "create" || normalizedRole === "creator") {
54
+ selectedRole = "creator";
55
+ }
56
+ else if (normalizedRole === "install" ||
57
+ normalizedRole === "installer") {
58
+ selectedRole = "installer";
59
+ }
60
+ else {
61
+ console.error(`❌ Invalid role: "${role}". Must be "create" or "install".`);
62
+ console.log("\nUsage:");
63
+ console.log(" lua marketplace - Interactive selection");
64
+ console.log(" lua marketplace create - Creator actions (publish & manage)");
65
+ console.log(" lua marketplace install - Installer actions (browse & install)");
66
+ process.exit(1);
67
+ }
68
+ }
69
+ // If role was provided as argument, go directly to that flow
70
+ if (selectedRole) {
71
+ if (selectedRole === "creator") {
72
+ await handleCreatorActions(marketplaceApi, config, apiKey);
73
+ }
74
+ else {
75
+ await handleInstallerActions(marketplaceApi, config, apiKey);
76
+ }
77
+ return;
78
+ }
79
+ // Otherwise, show interactive selection
80
+ let exit = false;
81
+ while (!exit) {
82
+ const roleAnswer = await safePrompt([
83
+ {
84
+ type: "list",
85
+ name: "role",
86
+ message: "What would you like to do?",
87
+ choices: [
88
+ {
89
+ name: "As a Creator (Publish & Manage your skills)",
90
+ value: "creator",
91
+ },
92
+ {
93
+ name: "As an Installer (Find & Install skills)",
94
+ value: "installer",
95
+ },
96
+ { name: "Exit", value: "exit" },
97
+ ],
98
+ },
99
+ ]);
100
+ if (!roleAnswer || roleAnswer.role === "exit") {
101
+ exit = true;
102
+ console.log("\n👋 Goodbye!\n");
103
+ continue;
104
+ }
105
+ if (roleAnswer.role === "creator") {
106
+ await handleCreatorActions(marketplaceApi, config, apiKey);
107
+ }
108
+ else if (roleAnswer.role === "installer") {
109
+ await handleInstallerActions(marketplaceApi, config, apiKey);
110
+ }
111
+ }
112
+ }, "marketplace");
113
+ }
114
+ async function handleCreatorActions(marketplaceApi, config, apiKey) {
115
+ let back = false;
116
+ while (!back) {
117
+ const creatorAnswer = await safePrompt([
118
+ {
119
+ type: "list",
120
+ name: "action",
121
+ message: "Creator Menu:",
122
+ choices: [
123
+ { name: "List a new skill on the Marketplace", value: "list" },
124
+ { name: "Publish a new version of a skill", value: "publish" },
125
+ { name: "Update metadata for a listed skill", value: "update" },
126
+ { name: "Unlist a skill from the Marketplace", value: "unlist" },
127
+ { name: "Unpublish a skill version", value: "unpublish" },
128
+ { name: "View my listed skills", value: "view" },
129
+ { name: "Back", value: "back" },
130
+ ],
131
+ },
132
+ ]);
133
+ if (!creatorAnswer || creatorAnswer.action === "back") {
134
+ back = true;
135
+ continue;
136
+ }
137
+ switch (creatorAnswer.action) {
138
+ case "list":
139
+ await listSkillOnMarketplace(marketplaceApi, config, apiKey);
140
+ continue;
141
+ case "publish":
142
+ await publishSkillVersion(marketplaceApi, config, apiKey);
143
+ continue;
144
+ case "update":
145
+ await updateSkillMetadata(marketplaceApi, config);
146
+ continue;
147
+ case "unlist":
148
+ await unlistSkillFromMarketplace(marketplaceApi);
149
+ continue;
150
+ case "unpublish":
151
+ await unpublishSkillVersion(marketplaceApi);
152
+ continue;
153
+ case "view":
154
+ await viewMyListedSkills(marketplaceApi);
155
+ continue;
156
+ // Other cases will be added here
157
+ default:
158
+ console.log(`\nAction '${creatorAnswer.action}' is not implemented yet.\n`);
159
+ await safePrompt([
160
+ {
161
+ type: "input",
162
+ name: "continue",
163
+ message: "Press Enter to continue...",
164
+ },
165
+ ]);
166
+ continue;
167
+ }
168
+ }
169
+ }
170
+ async function listSkillOnMarketplace(marketplaceApi, config, apiKey) {
171
+ // 1. Fetch agent skills
172
+ const agentId = config.agent?.agentId;
173
+ if (!agentId) {
174
+ console.error("\n❌ Agent ID not found in configuration.");
175
+ return;
176
+ }
177
+ const skillApi = new SkillApi(BASE_URLS.API, apiKey, agentId);
178
+ writeProgress("🔄 Loading your agent's skills...\n");
179
+ const agentSkillsResponse = await skillApi.getSkills();
180
+ if (!agentSkillsResponse.success || !agentSkillsResponse.data) {
181
+ console.error(`\n❌ Failed to fetch agent skills: ${agentSkillsResponse.message}\n`);
182
+ return;
183
+ }
184
+ const skills = agentSkillsResponse.data.skills || [];
185
+ if (skills.length === 0) {
186
+ console.log("\nℹ️ No skills found on this agent.");
187
+ console.log("💡 Push a skill first using 'lua push skill'.\n");
188
+ return;
189
+ }
190
+ // 2. Prompt user to select a skill to list
191
+ const skillAnswer = await safePrompt([
192
+ {
193
+ type: "list",
194
+ name: "skill",
195
+ message: "Which skill would you like to list on the marketplace?",
196
+ choices: skills.map((s) => ({
197
+ name: `${s.name}`,
198
+ value: s,
199
+ })),
200
+ },
201
+ ]);
202
+ if (!skillAnswer)
203
+ return;
204
+ const skillToList = skillAnswer.skill;
205
+ // 2.5 Check if skill already exists (for re-listing)
206
+ writeProgress("\n🔄 Checking existing marketplace listings...");
207
+ let defaultDisplayName;
208
+ try {
209
+ const creatorSkills = await marketplaceApi.getCreatorSkills();
210
+ const existingSkill = creatorSkills?.find((s) => s.sourceSkillId === skillToList.id || s.name === skillToList.name);
211
+ if (existingSkill) {
212
+ if (existingSkill.listed) {
213
+ writeInfo(`\nℹ️ Skill "${skillToList.name}" is already listed on the marketplace as "${existingSkill.displayName}".\n`);
214
+ return;
215
+ }
216
+ writeInfo(`\nℹ️ Skill "${skillToList.name}" was previously listed as "${existingSkill.displayName}". You are re-listing it.`);
217
+ defaultDisplayName = existingSkill.displayName;
218
+ }
219
+ }
220
+ catch (error) {
221
+ // Ignore error, treat as new listing
222
+ }
223
+ // 3. Prompt for required metadata
224
+ const metadata = await safePrompt([
225
+ {
226
+ type: "input",
227
+ name: "displayName",
228
+ message: "Enter a display name for the marketplace:",
229
+ default: defaultDisplayName,
230
+ validate: (input) => input && input.trim().length > 0
231
+ ? true
232
+ : "Display name cannot be empty.",
233
+ },
234
+ ]);
235
+ if (!metadata)
236
+ return;
237
+ // 5. Construct payload and list the skill
238
+ const payload = {
239
+ skillId: skillToList.id,
240
+ displayName: metadata.displayName,
241
+ // TODO: Add remaining metadata from user
242
+ };
243
+ try {
244
+ writeProgress("\nListing skill on the marketplace...");
245
+ const marketplaceSkill = await marketplaceApi.listSkill(payload);
246
+ writeSuccess(`\n✅ Skill "${marketplaceSkill.displayName}" listed successfully!`);
247
+ writeInfo(`Marketplace Skill ID: ${marketplaceSkill.id}`);
248
+ writeInfo("💡 You can now publish a version to make it installable.\n");
249
+ }
250
+ catch (error) {
251
+ console.error(`\n❌ Error listing skill: ${error.message}\n`);
252
+ }
253
+ }
254
+ async function publishSkillVersion(marketplaceApi, config, apiKey) {
255
+ try {
256
+ // 1. Fetch creator's listed marketplace skills
257
+ writeProgress("🔄 Loading your marketplace skills...\n");
258
+ const creatorSkillsResponse = await marketplaceApi.getCreatorSkills();
259
+ // Only show listed skills for publishing versions
260
+ const listedMarketplaceSkills = (creatorSkillsResponse || []).filter((s) => s.listed);
261
+ if (listedMarketplaceSkills.length === 0) {
262
+ writeInfo("\nℹ️ You haven't listed any skills on the marketplace yet.");
263
+ writeInfo("💡 Please list a skill first using the 'List a new skill' option.\n");
264
+ return;
265
+ }
266
+ // 2. Prompt user to select a listed marketplace skill
267
+ const marketplaceSkillAnswer = await safePrompt([
268
+ {
269
+ type: "list",
270
+ name: "marketplaceSkill",
271
+ message: "Which skill do you want to publish a new version for?",
272
+ choices: listedMarketplaceSkills.map((s) => ({
273
+ name: s.displayName,
274
+ value: s,
275
+ })),
276
+ },
277
+ ]);
278
+ if (!marketplaceSkillAnswer)
279
+ return;
280
+ const selectedMarketplaceSkill = marketplaceSkillAnswer.marketplaceSkill;
281
+ // 3. Fetch agent skills to find the corresponding source skill
282
+ const agentId = config.agent?.agentId;
283
+ if (!agentId) {
284
+ console.error("\n❌ Agent ID not found in configuration.");
285
+ return;
286
+ }
287
+ const skillApi = new SkillApi(BASE_URLS.API, apiKey, agentId);
288
+ writeProgress("🔄 Loading your agent's skills...\n");
289
+ const agentSkillsResponse = await skillApi.getSkills();
290
+ if (!agentSkillsResponse.success || !agentSkillsResponse.data) {
291
+ throw new Error(`Failed to fetch agent skills: ${agentSkillsResponse.message}`);
292
+ }
293
+ const agentSkills = agentSkillsResponse.data.skills || [];
294
+ // Try to find by sourceSkillId first, then by name
295
+ let sourceSkill = agentSkills.find((s) => s.id === selectedMarketplaceSkill.sourceSkillId);
296
+ if (!sourceSkill) {
297
+ // Fallback: try matching by name
298
+ sourceSkill = agentSkills.find((s) => s.name === selectedMarketplaceSkill.name);
299
+ }
300
+ if (!sourceSkill) {
301
+ console.error(`\n❌ Could not find the source skill on your agent.`);
302
+ writeInfo(`💡 The skill "${selectedMarketplaceSkill.name}" (ID: ${selectedMarketplaceSkill.sourceSkillId}) is missing from agent ${agentId}.`);
303
+ return;
304
+ }
305
+ // 4. Fetch all available versions for that source skill
306
+ writeProgress(`🔄 Loading versions for ${selectedMarketplaceSkill.displayName}...\n`);
307
+ const agentSkillVersionsResponse = await skillApi.getSkillVersions(sourceSkill.id);
308
+ if (!agentSkillVersionsResponse.success) {
309
+ throw new Error(`Failed to fetch skill versions: ${agentSkillVersionsResponse.message}`);
310
+ }
311
+ const availableAgentVersions = agentSkillVersionsResponse.data?.versions || [];
312
+ if (availableAgentVersions.length === 0) {
313
+ writeInfo(`\nℹ️ No versions found for ${selectedMarketplaceSkill.displayName}.`);
314
+ writeInfo("💡 Push a version first using 'lua push skill'.\n");
315
+ return;
316
+ }
317
+ // 4.5 Fetch existing marketplace versions to check status
318
+ writeProgress(`\n🔄 Checking marketplace version status...`);
319
+ const marketplaceVersions = await marketplaceApi.getSkillVersions(selectedMarketplaceSkill.id);
320
+ const getVersionStatus = (version) => {
321
+ const mpVersion = marketplaceVersions?.find((v) => v.version === version);
322
+ if (!mpVersion)
323
+ return { status: "new", label: "📤 Publish" };
324
+ if (mpVersion.published)
325
+ return { status: "published", label: "✅ Live" };
326
+ return { status: "unpublished", label: "🔄 Republish" };
327
+ };
328
+ // 5. Prompt user to select an agent version to publish
329
+ const agentVersionAnswer = await safePrompt([
330
+ {
331
+ type: "list",
332
+ name: "agentVersion",
333
+ message: "Select a version to publish:",
334
+ choices: availableAgentVersions
335
+ .sort((a, b) => compareVersions(b.version, a.version)) // Sort descending
336
+ .map((v) => {
337
+ const { status, label } = getVersionStatus(v.version);
338
+ return {
339
+ name: `${label} - v${v.version} (Created: ${new Date(v.createdDate).toLocaleString()})`,
340
+ value: { ...v, _status: status },
341
+ disabled: status === "published" ? "Already published" : false,
342
+ };
343
+ }),
344
+ },
345
+ ]);
346
+ if (!agentVersionAnswer)
347
+ return;
348
+ const agentVersionToPublish = agentVersionAnswer.agentVersion;
349
+ if (agentVersionToPublish._status === "unpublished") {
350
+ writeInfo(`\nℹ️ You are republishing v${agentVersionToPublish.version}. This will make it live again and update its details.\n`);
351
+ }
352
+ // 6. Prompt for an optional changelog
353
+ const existingChangelog = agentVersionToPublish._status === "unpublished"
354
+ ? marketplaceVersions?.find((v) => v.version === agentVersionToPublish.version)?.changelog
355
+ : undefined;
356
+ const changelogAnswer = await safePrompt([
357
+ {
358
+ type: "input",
359
+ name: "changelog",
360
+ message: "Enter a changelog for this version (optional):",
361
+ default: existingChangelog,
362
+ },
363
+ ]);
364
+ // 7. Configure environment variables for this version
365
+ let initialEnvVars;
366
+ if (agentVersionToPublish._status === "unpublished") {
367
+ // Republishing: Use THIS version's existing metadata
368
+ initialEnvVars = marketplaceVersions?.find((v) => v.version === agentVersionToPublish.version)?.envVarsMetadata;
369
+ }
370
+ else {
371
+ // New Version: Default to the LATEST existing version's metadata (if any)
372
+ const latestVersion = marketplaceVersions?.sort((a, b) => compareVersions(b.version, a.version))[0];
373
+ if (latestVersion) {
374
+ initialEnvVars = latestVersion.envVarsMetadata;
375
+ }
376
+ }
377
+ const envVars = await handleEnvVarConfiguration(initialEnvVars);
378
+ if (envVars === null) {
379
+ console.log("\nCancelled publishing process.\n");
380
+ return;
381
+ }
382
+ // 8. Publish the version to the marketplace
383
+ const payload = {
384
+ versionId: agentVersionToPublish.id,
385
+ changelog: changelogAnswer?.changelog || undefined,
386
+ envVars: Object.keys(envVars).length > 0 ? envVars : undefined,
387
+ };
388
+ writeProgress(`\n Publishing version to the marketplace...`);
389
+ const publishedVersion = await marketplaceApi.publishVersion(selectedMarketplaceSkill.id, payload);
390
+ writeSuccess(`\n✅ Version ${publishedVersion.version} of "${selectedMarketplaceSkill.displayName}" has been submitted for review!`);
391
+ writeInfo("💡 You will be notified once the review is complete.\n");
392
+ }
393
+ catch (error) {
394
+ console.error(`\n❌ Error publishing skill version: ${error.message}\n`);
395
+ }
396
+ }
397
+ async function updateSkillMetadata(marketplaceApi, config) {
398
+ try {
399
+ // 1. Fetch creator's listed marketplace skills
400
+ writeProgress("🔄 Loading your marketplace skills...\n");
401
+ const listedMarketplaceSkills = await marketplaceApi.getCreatorSkills();
402
+ if (!listedMarketplaceSkills || listedMarketplaceSkills.length === 0) {
403
+ writeInfo("\nℹ️ You haven't listed any skills on the marketplace yet.");
404
+ writeInfo("💡 Please list a skill first using the 'List a new skill' option.\n");
405
+ return;
406
+ }
407
+ // 2. Prompt user to select a marketplace skill to update
408
+ const marketplaceSkillAnswer = await safePrompt([
409
+ {
410
+ type: "list",
411
+ name: "marketplaceSkill",
412
+ message: "Which skill would you like to update?",
413
+ choices: listedMarketplaceSkills.map((s) => ({
414
+ name: s.displayName,
415
+ value: s,
416
+ })),
417
+ },
418
+ ]);
419
+ if (!marketplaceSkillAnswer)
420
+ return;
421
+ const marketplaceSkillToUpdate = marketplaceSkillAnswer.marketplaceSkill;
422
+ let back = false;
423
+ while (!back) {
424
+ const actionAnswer = await safePrompt([
425
+ {
426
+ type: "list",
427
+ name: "action",
428
+ message: `What would you like to update for "${marketplaceSkillToUpdate.displayName}"?`,
429
+ choices: [
430
+ { name: "Display Name", value: "displayName" },
431
+ { name: "Finish Updating", value: "back" },
432
+ ],
433
+ },
434
+ ]);
435
+ const action = actionAnswer?.action || "back";
436
+ if (action === "back") {
437
+ back = true;
438
+ continue;
439
+ }
440
+ if (action === "displayName") {
441
+ await updateDisplayName(marketplaceApi, marketplaceSkillToUpdate);
442
+ }
443
+ }
444
+ // Final success message can be removed as each sub-action gives its own feedback.
445
+ }
446
+ catch (error) {
447
+ console.error(`\n❌ Error updating skill metadata: ${error.message}\n`);
448
+ }
449
+ }
450
+ async function updateDisplayName(marketplaceApi, marketplaceSkill) {
451
+ const newDisplayNameAnswer = await safePrompt([
452
+ {
453
+ type: "input",
454
+ name: "newDisplayName",
455
+ message: `Enter the new display name for "${marketplaceSkill.displayName}":`,
456
+ default: marketplaceSkill.displayName,
457
+ validate: (input) => input && input.trim().length > 0
458
+ ? true
459
+ : "Display name cannot be empty.",
460
+ },
461
+ ]);
462
+ if (!newDisplayNameAnswer)
463
+ return;
464
+ const newDisplayName = newDisplayNameAnswer.newDisplayName;
465
+ const payload = { displayName: newDisplayName };
466
+ writeProgress(`\nUpdating display name...`);
467
+ await marketplaceApi.updateSkill(marketplaceSkill.id, payload);
468
+ writeSuccess(`\n✅ Display name updated successfully to "${newDisplayName}"!\n`);
469
+ marketplaceSkill.displayName = newDisplayName; // Update in-memory object for sub-menu display
470
+ }
471
+ /**
472
+ * Unlist a skill from the marketplace
473
+ */
474
+ async function unlistSkillFromMarketplace(marketplaceApi) {
475
+ try {
476
+ // 1. Fetch creator's listed marketplace skills
477
+ writeProgress("🔄 Loading your marketplace skills...\n");
478
+ const listedMarketplaceSkills = await marketplaceApi.getCreatorSkills();
479
+ if (!listedMarketplaceSkills || listedMarketplaceSkills.length === 0) {
480
+ writeInfo("\nℹ️ You haven't listed any skills on the marketplace yet.");
481
+ writeInfo("💡 Please list a skill first using the 'List a new skill' option.\n");
482
+ return;
483
+ }
484
+ // Filter to only show listed skills
485
+ const activeListedSkills = listedMarketplaceSkills.filter((s) => s.listed);
486
+ if (activeListedSkills.length === 0) {
487
+ writeInfo("\nℹ️ You don't have any active listings on the marketplace.");
488
+ writeInfo("💡 All your skills are already unlisted.\n");
489
+ return;
490
+ }
491
+ // 2. Prompt user to select a marketplace skill to unlist
492
+ const marketplaceSkillAnswer = await safePrompt([
493
+ {
494
+ type: "list",
495
+ name: "marketplaceSkill",
496
+ message: "Which skill would you like to unlist from the marketplace?",
497
+ choices: activeListedSkills.map((s) => ({
498
+ name: `${s.displayName} (${s.name})`,
499
+ value: s,
500
+ })),
501
+ },
502
+ ]);
503
+ if (!marketplaceSkillAnswer)
504
+ return;
505
+ const marketplaceSkillToUnlist = marketplaceSkillAnswer.marketplaceSkill;
506
+ // 3. Confirm the action
507
+ const confirmAnswer = await safePrompt([
508
+ {
509
+ type: "confirm",
510
+ name: "confirm",
511
+ message: `⚠️ Are you sure you want to unlist "${marketplaceSkillToUnlist.displayName}"? This will unpublish all versions and remove it from the marketplace.`,
512
+ default: false,
513
+ },
514
+ ]);
515
+ if (!confirmAnswer || !confirmAnswer.confirm) {
516
+ writeInfo("\n❌ Unlist cancelled.\n");
517
+ return;
518
+ }
519
+ // 4. Unlist the skill
520
+ writeProgress("\n🔄 Unlisting skill from marketplace...");
521
+ await marketplaceApi.unlistSkill(marketplaceSkillToUnlist.id);
522
+ writeSuccess(`\n✅ "${marketplaceSkillToUnlist.displayName}" has been unlisted from the marketplace.`);
523
+ writeInfo("💡 You can re-list it anytime using the 'List a new skill' option.\n");
524
+ }
525
+ catch (error) {
526
+ console.error(`\n❌ Error unlisting skill: ${error.message}\n`);
527
+ }
528
+ }
529
+ /**
530
+ * Unpublish a specific version of a marketplace skill
531
+ */
532
+ async function unpublishSkillVersion(marketplaceApi) {
533
+ try {
534
+ // 1. Fetch creator's listed marketplace skills
535
+ writeProgress("🔄 Loading your marketplace skills...\n");
536
+ const listedMarketplaceSkills = await marketplaceApi.getCreatorSkills();
537
+ if (!listedMarketplaceSkills || listedMarketplaceSkills.length === 0) {
538
+ writeInfo("\nℹ️ You haven't listed any skills on the marketplace yet.");
539
+ writeInfo("💡 Please list a skill first using the 'List a new skill' option.\n");
540
+ return;
541
+ }
542
+ // 2. Prompt user to select a marketplace skill
543
+ const marketplaceSkillAnswer = await safePrompt([
544
+ {
545
+ type: "list",
546
+ name: "marketplaceSkill",
547
+ message: "Which skill has the version you want to unpublish?",
548
+ choices: listedMarketplaceSkills.map((s) => ({
549
+ name: `${s.displayName} (${s.name})`,
550
+ value: s,
551
+ })),
552
+ },
553
+ ]);
554
+ if (!marketplaceSkillAnswer)
555
+ return;
556
+ const selectedMarketplaceSkill = marketplaceSkillAnswer.marketplaceSkill;
557
+ // 3. Fetch versions for the selected skill
558
+ writeProgress(`\n🔄 Loading versions for ${selectedMarketplaceSkill.displayName}...\n`);
559
+ const versions = await marketplaceApi.getSkillVersions(selectedMarketplaceSkill.id);
560
+ const publishedVersions = (versions || []).filter((v) => v.published);
561
+ if (publishedVersions.length === 0) {
562
+ writeInfo(`\nℹ️ No published versions found for "${selectedMarketplaceSkill.displayName}".`);
563
+ writeInfo("💡 All versions are already unpublished.\n");
564
+ return;
565
+ }
566
+ // 4. Prompt user to select a version to unpublish
567
+ const versionAnswer = await safePrompt([
568
+ {
569
+ type: "list",
570
+ name: "version",
571
+ message: "Which version would you like to unpublish?",
572
+ choices: publishedVersions
573
+ .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
574
+ .map((v) => ({
575
+ name: `v${v.version} - ${new Date(v.createdAt).toLocaleDateString()}`,
576
+ value: v,
577
+ })),
578
+ },
579
+ ]);
580
+ if (!versionAnswer)
581
+ return;
582
+ const versionToUnpublish = versionAnswer.version;
583
+ // 5. Confirm the action
584
+ const confirmAnswer = await safePrompt([
585
+ {
586
+ type: "confirm",
587
+ name: "confirm",
588
+ message: `⚠️ Are you sure you want to unpublish version ${versionToUnpublish.version} of "${selectedMarketplaceSkill.displayName}"?`,
589
+ default: false,
590
+ },
591
+ ]);
592
+ if (!confirmAnswer || !confirmAnswer.confirm) {
593
+ writeInfo("\n❌ Unpublish cancelled.\n");
594
+ return;
595
+ }
596
+ // 6. Unpublish the version
597
+ writeProgress("\n🔄 Unpublishing version...");
598
+ await marketplaceApi.unpublishVersion(selectedMarketplaceSkill.id, versionToUnpublish.id);
599
+ writeSuccess(`\n✅ Version ${versionToUnpublish.version} of "${selectedMarketplaceSkill.displayName}" has been unpublished.`);
600
+ writeInfo("💡 Users can no longer install this version. You can publish it again anytime.\n");
601
+ }
602
+ catch (error) {
603
+ console.error(`\n❌ Error unpublishing version: ${error.message}\n`);
604
+ }
605
+ }
606
+ /**
607
+ * View all listed skills created by the user
608
+ */
609
+ async function viewMyListedSkills(marketplaceApi) {
610
+ try {
611
+ writeProgress("🔄 Loading your marketplace skills...\n");
612
+ const listedMarketplaceSkills = await marketplaceApi.getCreatorSkills();
613
+ if (!listedMarketplaceSkills || listedMarketplaceSkills.length === 0) {
614
+ writeInfo("\n📦 You haven't listed any skills on the marketplace yet.");
615
+ writeInfo("💡 Use 'List a new skill' to get started.\n");
616
+ return;
617
+ }
618
+ // Display summary
619
+ console.log(`\n📊 You have ${listedMarketplaceSkills.length} skill(s) on the marketplace:\n`);
620
+ // Display each skill
621
+ for (const skill of listedMarketplaceSkills) {
622
+ const statusIcon = skill.listed ? "✅" : "❌";
623
+ const statusText = skill.listed ? "Listed" : "Unlisted";
624
+ console.log(`${statusIcon} ${skill.displayName} (${skill.name})`);
625
+ console.log(` Status: ${statusText}`);
626
+ if (skill.description) {
627
+ console.log(` Description: ${skill.description}`);
628
+ }
629
+ if (skill.category) {
630
+ console.log(` Category: ${skill.category}`);
631
+ }
632
+ if (skill.tags && skill.tags.length > 0) {
633
+ console.log(` Tags: ${skill.tags.join(", ")}`);
634
+ }
635
+ if (skill.verified)
636
+ console.log(` ✨ Verified Creator`);
637
+ if (skill.featured)
638
+ console.log(` ⭐ Featured Skill`);
639
+ if (skill.createdAt) {
640
+ console.log(` Created: ${new Date(skill.createdAt).toLocaleDateString()}`);
641
+ }
642
+ if (skill.updatedAt) {
643
+ console.log(` Last Updated: ${new Date(skill.updatedAt).toLocaleDateString()}`);
644
+ }
645
+ // Show version info if available
646
+ if (skill.versions && skill.versions.length > 0) {
647
+ const publishedVersions = skill.versions.filter((v) => v.published);
648
+ const totalVersions = skill.versions.length;
649
+ console.log(` Versions: ${publishedVersions.length} published / ${totalVersions} total`);
650
+ if (publishedVersions.length > 0) {
651
+ const latestVersion = publishedVersions.sort((a, b) => compareVersions(b.version, a.version))[0];
652
+ console.log(` Latest Published: v${latestVersion.version}`);
653
+ }
654
+ }
655
+ // Show install count if available
656
+ if (typeof skill.installCount !== "undefined") {
657
+ console.log(` Installs: ${skill.installCount}`);
658
+ }
659
+ // Show environment variables from latest version
660
+ const latestVersion = skill.versions && skill.versions.length > 0
661
+ ? skill.versions.sort((a, b) => compareVersions(b.version, a.version))[0]
662
+ : null;
663
+ if (latestVersion &&
664
+ latestVersion.envVarsMetadata &&
665
+ Object.keys(latestVersion.envVarsMetadata).length > 0) {
666
+ const envKeys = Object.keys(latestVersion.envVarsMetadata).join(", ");
667
+ console.log(` Environment Variables (latest version): ${envKeys} (${Object.keys(latestVersion.envVarsMetadata).length} configured)`);
668
+ }
669
+ console.log(""); // Empty line between skills
670
+ }
671
+ // Prompt to continue
672
+ await safePrompt([
673
+ {
674
+ type: "input",
675
+ name: "continue",
676
+ message: "Press Enter to continue...",
677
+ },
678
+ ]);
679
+ }
680
+ catch (error) {
681
+ console.error(`\n❌ Error loading your skills: ${error.message}\n`);
682
+ }
683
+ }
684
+ /**
685
+ * Handle env var configuration
686
+ * Builds an environment variable configuration object interactively.
687
+ * Used for both listing new skills and updating existing ones.
688
+ */
689
+ async function handleEnvVarConfiguration(initialEnvVars) {
690
+ const detectedKeys = loadSandboxEnvVariables().map((v) => v.key);
691
+ let envVars = initialEnvVars
692
+ ? { ...initialEnvVars }
693
+ : {};
694
+ let continueManaging = true;
695
+ while (continueManaging) {
696
+ const keys = Object.keys(envVars);
697
+ const statusText = keys.length > 0
698
+ ? `Current: ${keys.length} configured (${keys.join(", ")})`
699
+ : "Current: None configured";
700
+ const actionAnswer = await safePrompt([
701
+ {
702
+ type: "list",
703
+ name: "action",
704
+ message: `Manage environment variables (${statusText}):`,
705
+ choices: [
706
+ { name: "Add a new variable", value: "add" },
707
+ { name: "Edit an existing variable", value: "edit" },
708
+ { name: "Delete a variable", value: "delete" },
709
+ new inquirer.Separator(),
710
+ { name: "Finish and continue", value: "done" },
711
+ ],
712
+ },
713
+ ]);
714
+ if (!actionAnswer || actionAnswer.action === "done") {
715
+ continueManaging = false;
716
+ continue;
717
+ }
718
+ const { action } = actionAnswer;
719
+ switch (action) {
720
+ case "add":
721
+ await handleEnvVarAction("add", envVars, detectedKeys);
722
+ break;
723
+ case "edit":
724
+ await handleEnvVarAction("edit", envVars, detectedKeys);
725
+ break;
726
+ case "delete":
727
+ await handleEnvVarAction("delete", envVars, detectedKeys);
728
+ break;
729
+ }
730
+ }
731
+ return envVars;
732
+ }
733
+ async function handleEnvVarAction(action, envVars, detectedKeys) {
734
+ const keys = Object.keys(envVars);
735
+ if (action === "add") {
736
+ const unconfiguredKeys = detectedKeys.filter((k) => !envVars[k]);
737
+ const choices = [
738
+ ...unconfiguredKeys.map((key) => ({
739
+ name: `From .env file: ${key}`,
740
+ value: key,
741
+ })),
742
+ new inquirer.Separator(),
743
+ { name: "Manually enter a variable name", value: "manual" },
744
+ { name: "Back", value: "back" },
745
+ ];
746
+ const varNameAnswer = await safePrompt([
747
+ {
748
+ type: "list",
749
+ name: "varName",
750
+ message: "How would you like to add a variable?",
751
+ choices,
752
+ },
753
+ ]);
754
+ if (!varNameAnswer || varNameAnswer.varName === "back")
755
+ return;
756
+ let keyToAdd = varNameAnswer.varName;
757
+ if (keyToAdd === "manual") {
758
+ const manualKeyAnswer = await safePrompt([
759
+ {
760
+ type: "input",
761
+ name: "manualKey",
762
+ message: "Enter the new variable name:",
763
+ validate: (input) => input?.trim().length > 0 ? true : "Name cannot be empty.",
764
+ },
765
+ ]);
766
+ if (!manualKeyAnswer)
767
+ return;
768
+ keyToAdd = manualKeyAnswer.manualKey;
769
+ }
770
+ await configureEnvVar(keyToAdd, envVars);
771
+ }
772
+ else if (action === "edit") {
773
+ if (keys.length === 0) {
774
+ writeInfo("\nℹ️ No variables configured yet. Please add one first.\n");
775
+ return;
776
+ }
777
+ const keyAnswer = await safePrompt([
778
+ {
779
+ type: "list",
780
+ name: "key",
781
+ message: "Which variable do you want to edit?",
782
+ choices: [
783
+ ...keys,
784
+ new inquirer.Separator(),
785
+ { name: "Back", value: "back" },
786
+ ],
787
+ },
788
+ ]);
789
+ if (!keyAnswer || keyAnswer.key === "back")
790
+ return;
791
+ await configureEnvVar(keyAnswer.key, envVars);
792
+ }
793
+ else if (action === "delete") {
794
+ if (keys.length === 0) {
795
+ writeInfo("\nℹ️ No variables configured yet.\n");
796
+ return;
797
+ }
798
+ const keyAnswer = await safePrompt([
799
+ {
800
+ type: "list",
801
+ name: "key",
802
+ message: "Which variable do you want to delete?",
803
+ choices: [
804
+ ...keys,
805
+ new inquirer.Separator(),
806
+ { name: "Back", value: "back" },
807
+ ],
808
+ },
809
+ ]);
810
+ if (!keyAnswer || keyAnswer.key === "back")
811
+ return;
812
+ delete envVars[keyAnswer.key];
813
+ writeSuccess(`\n✅ Variable "${keyAnswer.key}" has been removed.\n`);
814
+ }
815
+ }
816
+ async function configureEnvVar(varName, envVars) {
817
+ const existing = envVars[varName] || {};
818
+ const metadata = await safePrompt([
819
+ {
820
+ type: "input",
821
+ name: "description",
822
+ message: `Description for ${varName}:`,
823
+ default: existing.description,
824
+ validate: (input) => input?.trim().length > 0 ? true : "Description cannot be empty.",
825
+ },
826
+ {
827
+ type: "confirm",
828
+ name: "required",
829
+ message: "Is this variable required?",
830
+ default: existing.required !== undefined ? existing.required : true,
831
+ },
832
+ {
833
+ type: "input",
834
+ name: "example",
835
+ message: "Example value (optional):",
836
+ default: existing.example,
837
+ },
838
+ ]);
839
+ if (!metadata)
840
+ return;
841
+ envVars[varName] = {
842
+ description: metadata.description,
843
+ required: metadata.required,
844
+ example: metadata.example || undefined,
845
+ };
846
+ writeSuccess(`\n✅ Configured ${varName}.\n`);
847
+ }
848
+ async function handleInstallerActions(marketplaceApi, config, apiKey) {
849
+ let back = false;
850
+ while (!back) {
851
+ const installerAnswer = await safePrompt([
852
+ {
853
+ type: "list",
854
+ name: "action",
855
+ message: "Installer Menu:",
856
+ choices: [
857
+ { name: "Browse & Search for skills", value: "search" },
858
+ { name: "Install a skill from the Marketplace", value: "install" },
859
+ { name: "Update an installed skill", value: "update" },
860
+ { name: "Uninstall a skill", value: "uninstall" },
861
+ { name: "List my currently installed skills", value: "installed" },
862
+ { name: "Back", value: "back" },
863
+ ],
864
+ },
865
+ ]);
866
+ if (!installerAnswer || installerAnswer.action === "back") {
867
+ back = true;
868
+ continue;
869
+ }
870
+ switch (installerAnswer.action) {
871
+ case "search":
872
+ await searchMarketplaceSkills(marketplaceApi);
873
+ continue;
874
+ case "install":
875
+ await installMarketplaceSkill(marketplaceApi, config, apiKey);
876
+ continue;
877
+ case "update":
878
+ await updateInstalledSkill(marketplaceApi, config, apiKey);
879
+ continue;
880
+ case "uninstall":
881
+ await uninstallMarketplaceSkill(marketplaceApi, config);
882
+ continue;
883
+ case "installed":
884
+ await listInstalledSkills(marketplaceApi, config);
885
+ continue;
886
+ // Other cases will be added here
887
+ default:
888
+ console.log(`\nAction '${installerAnswer.action}' is not implemented yet.\n`);
889
+ await safePrompt([
890
+ {
891
+ type: "input",
892
+ name: "continue",
893
+ message: "Press Enter to continue...",
894
+ },
895
+ ]);
896
+ continue;
897
+ }
898
+ }
899
+ }
900
+ /**
901
+ * Browse and select a skill from marketplace (with optional search)
902
+ * Returns the selected skill or null if cancelled
903
+ */
904
+ async function browseAndSelectSkill(marketplaceApi, purpose, publishedVersionsOnly) {
905
+ try {
906
+ // 1. Prompt for search query (optional)
907
+ const searchAnswer = await safePrompt([
908
+ {
909
+ type: "input",
910
+ name: "query",
911
+ message: "Search for skills (leave empty to browse all):",
912
+ },
913
+ ]);
914
+ if (searchAnswer === null)
915
+ return null; // User cancelled
916
+ const searchQuery = searchAnswer?.query?.trim() || "";
917
+ // 2. Search/browse marketplace skills
918
+ writeProgress(searchQuery
919
+ ? `\n🔍 Searching for "${searchQuery}"...`
920
+ : "\n🔍 Loading marketplace skills...");
921
+ const filters = {};
922
+ if (searchQuery)
923
+ filters.search = searchQuery;
924
+ if (publishedVersionsOnly !== undefined)
925
+ filters.publishedVersionsOnly = publishedVersionsOnly;
926
+ const searchResults = await marketplaceApi.searchSkills(Object.keys(filters).length > 0 ? filters : undefined);
927
+ const skills = searchResults || [];
928
+ if (skills.length === 0) {
929
+ if (searchQuery) {
930
+ writeInfo(`\n📦 No skills found matching "${searchQuery}".`);
931
+ }
932
+ else {
933
+ writeInfo("\n📦 No skills available on the marketplace yet.");
934
+ }
935
+ writeInfo("💡 Be the first to publish a skill!\n");
936
+ return null;
937
+ }
938
+ // 3. Display results
939
+ console.log(`\n📊 Found ${skills.length} skill${skills.length === 1 ? "" : "s"}:\n`);
940
+ // 4. Prompt user to select a skill
941
+ const message = purpose || "Select a skill (or press ESC to go back):";
942
+ const skillAnswer = await safePrompt([
943
+ {
944
+ type: "list",
945
+ name: "skill",
946
+ message,
947
+ choices: [
948
+ ...skills.map((s) => ({
949
+ name: `${s.displayName} - ${s.description || "No description"}`,
950
+ value: s,
951
+ })),
952
+ new inquirer.Separator(),
953
+ { name: "← Back to menu", value: null },
954
+ ],
955
+ },
956
+ ]);
957
+ if (!skillAnswer || !skillAnswer.skill)
958
+ return null;
959
+ return skillAnswer.skill;
960
+ }
961
+ catch (error) {
962
+ console.error(`\n❌ Error browsing marketplace: ${error.message}\n`);
963
+ return null;
964
+ }
965
+ }
966
+ /**
967
+ * Browse and search for marketplace skills (view details)
968
+ */
969
+ async function searchMarketplaceSkills(marketplaceApi) {
970
+ const selectedSkill = await browseAndSelectSkill(marketplaceApi, "Select a skill to view details:");
971
+ if (selectedSkill) {
972
+ await showSkillDetails(marketplaceApi, selectedSkill);
973
+ }
974
+ }
975
+ /**
976
+ * Show detailed information about a marketplace skill
977
+ */
978
+ async function showSkillDetails(marketplaceApi, skill) {
979
+ try {
980
+ // Fetch full details
981
+ writeProgress("\n🔄 Loading skill details...\n");
982
+ const skillDetails = await marketplaceApi.getSkillById(skill.id);
983
+ // Display detailed information
984
+ console.log(`\n${"=".repeat(60)}`);
985
+ console.log(`📦 ${skillDetails.displayName}`);
986
+ console.log(`${"=".repeat(60)}\n`);
987
+ console.log(`Name: ${skillDetails.name}`);
988
+ console.log(`Created: ${new Date(skillDetails.createdAt).toLocaleDateString()}`);
989
+ console.log(`Last Updated: ${new Date(skillDetails.updatedAt).toLocaleDateString()}`);
990
+ if (skillDetails.description) {
991
+ console.log(`\nDescription: ${skillDetails.description}`);
992
+ }
993
+ if (skillDetails.longDescription) {
994
+ console.log(`\nDetails:\n${skillDetails.longDescription}`);
995
+ }
996
+ if (skillDetails.category) {
997
+ console.log(`\nCategory: ${skillDetails.category}`);
998
+ }
999
+ if (skillDetails.tags && skillDetails.tags.length > 0) {
1000
+ console.log(`Tags: ${skillDetails.tags.join(", ")}`);
1001
+ }
1002
+ // Show install count
1003
+ if (typeof skillDetails.installCount !== "undefined") {
1004
+ console.log(`Installs: ${skillDetails.installCount}`);
1005
+ }
1006
+ // Show verified/featured badges
1007
+ if (skillDetails.verified) {
1008
+ console.log(`✅ Verified`);
1009
+ }
1010
+ if (skillDetails.featured) {
1011
+ console.log(`⭐ Featured`);
1012
+ }
1013
+ // Show versions
1014
+ if (skillDetails.versions && skillDetails.versions.length > 0) {
1015
+ const publishedVersions = skillDetails.versions.filter((v) => v.published);
1016
+ console.log(`\nVersions: ${publishedVersions.length} published / ${skillDetails.versions.length} total`);
1017
+ if (publishedVersions.length > 0) {
1018
+ const latestVersion = publishedVersions.sort((a, b) => compareVersions(b.version, a.version))[0];
1019
+ console.log(`Latest: v${latestVersion.version}`);
1020
+ if (latestVersion.tools && latestVersion.tools.length > 0) {
1021
+ console.log(`\nTools (v${latestVersion.version}):`);
1022
+ latestVersion.tools.forEach((tool) => {
1023
+ console.log(` • ${tool.name || tool.functionName || "Unnamed Tool"}`);
1024
+ if (tool.description)
1025
+ console.log(` ${tool.description}`);
1026
+ });
1027
+ }
1028
+ }
1029
+ }
1030
+ // Show environment variables from latest version
1031
+ const latestVersion = skillDetails.versions && skillDetails.versions.length > 0
1032
+ ? skillDetails.versions.sort((a, b) => compareVersions(b.version, a.version))[0]
1033
+ : null;
1034
+ if (latestVersion &&
1035
+ latestVersion.envVarsMetadata &&
1036
+ Object.keys(latestVersion.envVarsMetadata).length > 0) {
1037
+ console.log(`\nEnvironment Variables (v${latestVersion.version}):`);
1038
+ for (const [key, meta] of Object.entries(latestVersion.envVarsMetadata)) {
1039
+ const varMeta = meta;
1040
+ const required = varMeta.required ? " (required)" : " (optional)";
1041
+ console.log(` • ${key}${required}`);
1042
+ if (varMeta.description) {
1043
+ console.log(` ${varMeta.description}`);
1044
+ }
1045
+ }
1046
+ }
1047
+ // Show repository and documentation links
1048
+ if (skillDetails.repositoryUrl) {
1049
+ console.log(`\nRepository: ${skillDetails.repositoryUrl}`);
1050
+ }
1051
+ if (skillDetails.documentationUrl) {
1052
+ console.log(`Documentation: ${skillDetails.documentationUrl}`);
1053
+ }
1054
+ console.log(`\n${"=".repeat(60)}\n`);
1055
+ // Prompt to continue
1056
+ await safePrompt([
1057
+ {
1058
+ type: "input",
1059
+ name: "continue",
1060
+ message: "Press Enter to continue...",
1061
+ },
1062
+ ]);
1063
+ }
1064
+ catch (error) {
1065
+ console.error(`\n❌ Error loading skill details: ${error.message}\n`);
1066
+ }
1067
+ }
1068
+ /**
1069
+ * Install a marketplace skill on the agent
1070
+ */
1071
+ async function installMarketplaceSkill(marketplaceApi, config, apiKey) {
1072
+ try {
1073
+ const agentId = config.agent?.agentId;
1074
+ if (!agentId) {
1075
+ console.error("\n❌ No agent ID found in configuration.\n");
1076
+ return;
1077
+ }
1078
+ // 1. Choose how to select the skill
1079
+ const selectionMethodAnswer = await safePrompt([
1080
+ {
1081
+ type: "list",
1082
+ name: "method",
1083
+ message: "How would you like to find the skill?",
1084
+ choices: [
1085
+ { name: "Browse and select from marketplace", value: "browse" },
1086
+ { name: "Enter skill name and creator ID directly", value: "direct" },
1087
+ { name: "Cancel", value: "cancel" },
1088
+ ],
1089
+ },
1090
+ ]);
1091
+ if (!selectionMethodAnswer || selectionMethodAnswer.method === "cancel") {
1092
+ return;
1093
+ }
1094
+ let selectedMarketplaceSkill = null;
1095
+ if (selectionMethodAnswer.method === "browse") {
1096
+ // Browse and select - only show skills with published versions
1097
+ selectedMarketplaceSkill = await browseAndSelectSkill(marketplaceApi, undefined, true);
1098
+ if (!selectedMarketplaceSkill)
1099
+ return; // User cancelled or no skills found
1100
+ }
1101
+ else {
1102
+ // Direct entry
1103
+ const directAnswer = await safePrompt([
1104
+ {
1105
+ type: "input",
1106
+ name: "name",
1107
+ message: "Enter the skill name:",
1108
+ validate: (input) => input.trim().length > 0 ? true : "Skill name is required",
1109
+ },
1110
+ {
1111
+ type: "input",
1112
+ name: "creatorId",
1113
+ message: "Enter the creator ID:",
1114
+ validate: (input) => input.trim().length > 0 ? true : "Creator ID is required",
1115
+ },
1116
+ ]);
1117
+ if (!directAnswer)
1118
+ return;
1119
+ // Search for the skill by exact name and creator using API filters
1120
+ writeProgress("\n🔍 Looking up skill...");
1121
+ const searchResults = await marketplaceApi.searchSkills({
1122
+ name: directAnswer.name,
1123
+ creatorId: directAnswer.creatorId,
1124
+ publishedVersionsOnly: true,
1125
+ });
1126
+ const skills = searchResults || [];
1127
+ if (skills.length === 0) {
1128
+ console.error(`\n❌ Skill "${directAnswer.name}" by creator "${directAnswer.creatorId}" not found on the marketplace.\n`);
1129
+ return;
1130
+ }
1131
+ // Should only have one result with exact name + creatorId match
1132
+ selectedMarketplaceSkill = skills[0];
1133
+ }
1134
+ // Check if the skill is already installed on this agent (as a marketplace skill)
1135
+ try {
1136
+ const installedSkills = await marketplaceApi.getInstalledSkills(agentId);
1137
+ const alreadyInstalled = installedSkills?.find((s) => s.marketplaceSkillId === selectedMarketplaceSkill.id);
1138
+ if (alreadyInstalled) {
1139
+ console.error(`\n❌ The skill "${selectedMarketplaceSkill.displayName}" is already installed on this agent.`);
1140
+ writeInfo("💡 Use 'Update an installed skill' to change versions or configuration.\n");
1141
+ return;
1142
+ }
1143
+ }
1144
+ catch (e) {
1145
+ // Silent catch - if we can't check, let the backend handle the error later
1146
+ }
1147
+ try {
1148
+ const skillApi = new SkillApi(BASE_URLS.API, apiKey, agentId);
1149
+ const agentSkillsResponse = await skillApi.getSkills();
1150
+ if (agentSkillsResponse.success && agentSkillsResponse.data?.skills) {
1151
+ const agentSkills = agentSkillsResponse.data.skills;
1152
+ const existingSourceSkill = agentSkills.find((s) => s.id === selectedMarketplaceSkill.sourceSkillId);
1153
+ if (existingSourceSkill) {
1154
+ console.error(`\n❌ You cannot install "${selectedMarketplaceSkill.displayName}" because the source skill already exists on this agent.`);
1155
+ writeInfo("💡 You already have the original skill on this agent.\n");
1156
+ return;
1157
+ }
1158
+ }
1159
+ }
1160
+ catch (e) {
1161
+ // Silent catch
1162
+ }
1163
+ // 2. Fetch full skill details including versions
1164
+ writeProgress(`\n🔄 Loading details for ${selectedMarketplaceSkill.displayName}...`);
1165
+ const skillDetails = await marketplaceApi.getSkillById(selectedMarketplaceSkill.id);
1166
+ // 3. Get published versions
1167
+ const versions = await marketplaceApi.getSkillVersions(selectedMarketplaceSkill.id);
1168
+ const publishedVersions = (versions || []).filter((v) => v.published);
1169
+ if (publishedVersions.length === 0) {
1170
+ writeInfo(`\n❌ No published versions available for "${skillDetails.displayName}".`);
1171
+ writeInfo("💡 Please contact the skill creator to publish a version.\n");
1172
+ return;
1173
+ }
1174
+ // 4. Select version to install
1175
+ const versionAnswer = await safePrompt([
1176
+ {
1177
+ type: "list",
1178
+ name: "version",
1179
+ message: "Select a version to install:",
1180
+ choices: publishedVersions
1181
+ .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
1182
+ .map((v) => ({
1183
+ name: `v${v.version} - ${new Date(v.createdAt).toLocaleDateString()}${v.changelog ? ` - ${v.changelog}` : ""}`,
1184
+ value: v,
1185
+ })),
1186
+ },
1187
+ ]);
1188
+ if (!versionAnswer)
1189
+ return;
1190
+ const selectedVersion = versionAnswer.version;
1191
+ // 5. Configure environment variables
1192
+ const envVarsConfig = {};
1193
+ const versionEnvVars = selectedVersion.envVarsMetadata || {};
1194
+ if (Object.keys(versionEnvVars).length > 0) {
1195
+ writeInfo("\n📝 Configure environment variables:\n");
1196
+ for (const [key, meta] of Object.entries(versionEnvVars)) {
1197
+ const varMeta = meta;
1198
+ const isRequired = varMeta.required;
1199
+ const description = varMeta.description || "No description";
1200
+ const example = varMeta.example ? ` (e.g., ${varMeta.example})` : "";
1201
+ console.log(`\n${key}${isRequired ? " (required)" : " (optional)"}`);
1202
+ console.log(` ${description}${example}`);
1203
+ const valueAnswer = await safePrompt([
1204
+ {
1205
+ type: "input",
1206
+ name: "value",
1207
+ message: `Enter value for ${key}:`,
1208
+ validate: (input) => {
1209
+ if (isRequired && !input.trim()) {
1210
+ return `${key} is required`;
1211
+ }
1212
+ return true;
1213
+ },
1214
+ },
1215
+ ]);
1216
+ if (valueAnswer === null) {
1217
+ writeInfo("\n❌ Installation cancelled.\n");
1218
+ return;
1219
+ }
1220
+ if (valueAnswer.value && valueAnswer.value.trim()) {
1221
+ envVarsConfig[key] = valueAnswer.value.trim();
1222
+ }
1223
+ }
1224
+ }
1225
+ // 6. Confirm installation
1226
+ console.log("\n📋 Installation Summary:");
1227
+ console.log(` Skill: ${skillDetails.displayName}`);
1228
+ console.log(` Version: v${selectedVersion.version}`);
1229
+ console.log(` Agent: ${agentId}`);
1230
+ if (Object.keys(envVarsConfig).length > 0) {
1231
+ console.log(` Environment Variables: ${Object.keys(envVarsConfig).length} configured`);
1232
+ }
1233
+ const confirmAnswer = await safePrompt([
1234
+ {
1235
+ type: "confirm",
1236
+ name: "confirm",
1237
+ message: "Proceed with installation?",
1238
+ default: true,
1239
+ },
1240
+ ]);
1241
+ if (!confirmAnswer || !confirmAnswer.confirm) {
1242
+ writeInfo("\n❌ Installation cancelled.\n");
1243
+ return;
1244
+ }
1245
+ // 7. Install the skill
1246
+ writeProgress("\n🔄 Installing skill...");
1247
+ const installPayload = {
1248
+ versionId: selectedVersion.id,
1249
+ envVars: Object.keys(envVarsConfig).length > 0 ? envVarsConfig : undefined,
1250
+ };
1251
+ await marketplaceApi.installSkill(selectedMarketplaceSkill.id, agentId, installPayload);
1252
+ writeSuccess(`\n✅ "${skillDetails.displayName}" v${selectedVersion.version} has been installed successfully!`);
1253
+ writeInfo(`💡 The skill is now available on agent ${agentId}.\n`);
1254
+ }
1255
+ catch (error) {
1256
+ console.error(`\n❌ Error installing skill: ${error.message}\n`);
1257
+ }
1258
+ }
1259
+ /**
1260
+ * Update an installed marketplace skill (version and/or env vars)
1261
+ */
1262
+ async function updateInstalledSkill(marketplaceApi, config, apiKey) {
1263
+ try {
1264
+ const agentId = config.agent?.agentId;
1265
+ if (!agentId) {
1266
+ console.error("\n❌ No agent ID found in configuration.\n");
1267
+ return;
1268
+ }
1269
+ // 1. Fetch installed skills
1270
+ writeProgress(`\n🔄 Loading installed marketplace skills...`);
1271
+ const installedSkills = await marketplaceApi.getInstalledSkills(agentId);
1272
+ if (!installedSkills || installedSkills.length === 0) {
1273
+ writeInfo("\n📦 No marketplace skills installed on this agent yet.");
1274
+ writeInfo("💡 Use 'Install a skill' to get started.\n");
1275
+ return;
1276
+ }
1277
+ // 2. Select which installed skill to update
1278
+ const skillAnswer = await safePrompt([
1279
+ {
1280
+ type: "list",
1281
+ name: "skill",
1282
+ message: "Which installed skill would you like to update?",
1283
+ choices: installedSkills.map((s) => ({
1284
+ name: `${s.title || s.name} (currently v${s.activeVersionId || "unknown"})`,
1285
+ value: s,
1286
+ })),
1287
+ },
1288
+ ]);
1289
+ if (!skillAnswer)
1290
+ return;
1291
+ const installedSkill = skillAnswer.skill;
1292
+ // 3. Choose what to update
1293
+ const updateChoiceAnswer = await safePrompt([
1294
+ {
1295
+ type: "list",
1296
+ name: "choice",
1297
+ message: "What would you like to update?",
1298
+ choices: [
1299
+ { name: "Upgrade to a different version", value: "version" },
1300
+ { name: "Update environment variables", value: "envVars" },
1301
+ {
1302
+ name: "Update both version and environment variables",
1303
+ value: "both",
1304
+ },
1305
+ { name: "Cancel", value: "cancel" },
1306
+ ],
1307
+ },
1308
+ ]);
1309
+ if (!updateChoiceAnswer || updateChoiceAnswer.choice === "cancel") {
1310
+ return;
1311
+ }
1312
+ const updateChoice = updateChoiceAnswer.choice;
1313
+ let newVersionId = undefined;
1314
+ let newEnvVars = undefined;
1315
+ // 4. Handle version update
1316
+ if (updateChoice === "version" || updateChoice === "both") {
1317
+ // Fetch skill details to get available versions
1318
+ writeProgress(`\n🔄 Loading available versions...`);
1319
+ const marketplaceSkillId = installedSkill.marketplaceSkillId;
1320
+ if (!marketplaceSkillId) {
1321
+ console.error("\n❌ Skill is missing marketplace skill ID.\n");
1322
+ return;
1323
+ }
1324
+ const versions = await marketplaceApi.getSkillVersions(marketplaceSkillId);
1325
+ const publishedVersions = (versions || []).filter((v) => v.published);
1326
+ if (publishedVersions.length === 0) {
1327
+ writeInfo("\n❌ No published versions available for this skill.\n");
1328
+ return;
1329
+ }
1330
+ const versionAnswer = await safePrompt([
1331
+ {
1332
+ type: "list",
1333
+ name: "version",
1334
+ message: "Select a version to upgrade to:",
1335
+ choices: publishedVersions
1336
+ .sort((a, b) => compareVersions(b.version, a.version))
1337
+ .map((v) => ({
1338
+ name: `v${v.version}${v.id ===
1339
+ installedSkill.marketplaceInstallMetadata?.installedVersionId
1340
+ ? " (current)"
1341
+ : ""} - ${new Date(v.createdAt).toLocaleDateString()}${v.changelog ? ` - ${v.changelog}` : ""}`,
1342
+ value: v,
1343
+ })),
1344
+ },
1345
+ ]);
1346
+ if (!versionAnswer)
1347
+ return;
1348
+ newVersionId = versionAnswer.version.id;
1349
+ }
1350
+ // 5. Handle environment variables update
1351
+ if (updateChoice === "envVars" || updateChoice === "both") {
1352
+ // Fetch current agent environment variables
1353
+ let currentAgentEnvVars = {};
1354
+ try {
1355
+ const developerApi = new DeveloperApi(BASE_URLS.API, apiKey, agentId);
1356
+ const response = await developerApi.getEnvironmentVariables();
1357
+ if (response.success && response.data?.data) {
1358
+ currentAgentEnvVars = response.data.data;
1359
+ }
1360
+ }
1361
+ catch (e) {
1362
+ // Silent fail, just don't show current values
1363
+ }
1364
+ const marketplaceSkillId = installedSkill.marketplaceSkillId;
1365
+ if (!marketplaceSkillId) {
1366
+ console.error("\n❌ Skill is missing marketplace skill ID.\n");
1367
+ return;
1368
+ }
1369
+ const skillDetails = await marketplaceApi.getSkillById(marketplaceSkillId);
1370
+ // Determine which version's env vars to use
1371
+ let targetVersion;
1372
+ if (newVersionId) {
1373
+ // Use the new version selected
1374
+ targetVersion = skillDetails.versions?.find((v) => v.id === newVersionId);
1375
+ }
1376
+ else if (installedSkill.marketplaceInstallMetadata?.installedVersionId) {
1377
+ // Use the currently installed version
1378
+ targetVersion = skillDetails.versions?.find((v) => v.id ===
1379
+ installedSkill.marketplaceInstallMetadata.installedVersionId);
1380
+ }
1381
+ // Fallback to latest version if not found (shouldn't happen usually)
1382
+ if (!targetVersion &&
1383
+ skillDetails.versions &&
1384
+ skillDetails.versions.length > 0) {
1385
+ targetVersion = skillDetails.versions.sort((a, b) => compareVersions(b.version, a.version))[0];
1386
+ }
1387
+ const envVarsMetadata = targetVersion?.envVarsMetadata || {};
1388
+ if (Object.keys(envVarsMetadata).length === 0) {
1389
+ writeInfo("\n📝 This version has no environment variables to configure.\n");
1390
+ if (updateChoice === "envVars") {
1391
+ return; // Nothing to update
1392
+ }
1393
+ }
1394
+ else {
1395
+ writeInfo("\n📝 Update environment variables:\n");
1396
+ const updatedEnvVars = {};
1397
+ for (const [key, meta] of Object.entries(envVarsMetadata)) {
1398
+ const varMeta = meta;
1399
+ const isRequired = varMeta.required;
1400
+ const description = varMeta.description || "No description";
1401
+ const example = varMeta.example ? ` (e.g., ${varMeta.example})` : "";
1402
+ const currentValue = currentAgentEnvVars[key] || "";
1403
+ console.log(`\n${key}${isRequired ? " (required)" : " (optional)"}`);
1404
+ console.log(` ${description}${example}`);
1405
+ if (currentValue) {
1406
+ const maskedValue = currentValue.length > 4
1407
+ ? currentValue.substring(0, 4) +
1408
+ "*".repeat(Math.min(currentValue.length - 4, 20))
1409
+ : "*".repeat(currentValue.length);
1410
+ console.log(` Current value: ${maskedValue}`);
1411
+ }
1412
+ const valueAnswer = await safePrompt([
1413
+ {
1414
+ type: "input",
1415
+ name: "value",
1416
+ message: `Enter new value for ${key} (leave empty to keep current):`,
1417
+ default: currentValue,
1418
+ validate: (input) => {
1419
+ if (isRequired && !input.trim() && !currentValue) {
1420
+ return `${key} is required`;
1421
+ }
1422
+ return true;
1423
+ },
1424
+ },
1425
+ ]);
1426
+ if (valueAnswer === null) {
1427
+ writeInfo("\n❌ Update cancelled.\n");
1428
+ return;
1429
+ }
1430
+ if (valueAnswer.value &&
1431
+ valueAnswer.value.trim() &&
1432
+ valueAnswer.value !== currentValue) {
1433
+ updatedEnvVars[key] = valueAnswer.value.trim();
1434
+ }
1435
+ }
1436
+ newEnvVars = updatedEnvVars;
1437
+ }
1438
+ }
1439
+ // 6. Confirm update
1440
+ console.log("\n📋 Update Summary:");
1441
+ console.log(` Skill: ${installedSkill.title || installedSkill.name}`);
1442
+ console.log(` Agent: ${agentId}`);
1443
+ if (newVersionId) {
1444
+ console.log(` New version will be applied`);
1445
+ }
1446
+ if (newEnvVars && Object.keys(newEnvVars).length > 0) {
1447
+ console.log(` Environment variables: ${Object.keys(newEnvVars).length} to update`);
1448
+ }
1449
+ const confirmAnswer = await safePrompt([
1450
+ {
1451
+ type: "confirm",
1452
+ name: "confirm",
1453
+ message: "Proceed with update?",
1454
+ default: true,
1455
+ },
1456
+ ]);
1457
+ if (!confirmAnswer || !confirmAnswer.confirm) {
1458
+ writeInfo("\n❌ Update cancelled.\n");
1459
+ return;
1460
+ }
1461
+ // 7. Update the skill
1462
+ writeProgress("\n🔄 Updating installed skill...");
1463
+ const updatePayload = {};
1464
+ if (newVersionId) {
1465
+ updatePayload.versionId = newVersionId;
1466
+ }
1467
+ if (newEnvVars) {
1468
+ updatePayload.envVars = newEnvVars;
1469
+ }
1470
+ if (!installedSkill.marketplaceSkillId) {
1471
+ console.error("\n❌ Skill is missing marketplace skill ID.\n");
1472
+ return;
1473
+ }
1474
+ await marketplaceApi.updateInstallation(installedSkill.marketplaceSkillId, agentId, updatePayload);
1475
+ writeSuccess(`\n✅ "${installedSkill.title || installedSkill.name}" has been updated successfully!`);
1476
+ writeInfo(`💡 The changes are now active on agent ${agentId}.\n`);
1477
+ }
1478
+ catch (error) {
1479
+ console.error(`\n❌ Error updating installed skill: ${error.message}\n`);
1480
+ }
1481
+ }
1482
+ /**
1483
+ * Uninstall a marketplace skill from the agent
1484
+ */
1485
+ async function uninstallMarketplaceSkill(marketplaceApi, config) {
1486
+ try {
1487
+ const agentId = config.agent?.agentId;
1488
+ if (!agentId) {
1489
+ console.error("\n❌ No agent ID found in configuration.\n");
1490
+ return;
1491
+ }
1492
+ // 1. Fetch installed skills
1493
+ writeProgress(`\n🔄 Loading installed marketplace skills...`);
1494
+ const installedSkills = await marketplaceApi.getInstalledSkills(agentId);
1495
+ if (!installedSkills || installedSkills.length === 0) {
1496
+ writeInfo("\n📦 No marketplace skills installed on this agent yet.");
1497
+ writeInfo("💡 Use 'Install a skill' to get started.\n");
1498
+ return;
1499
+ }
1500
+ // 2. Select which installed skill to uninstall
1501
+ const skillAnswer = await safePrompt([
1502
+ {
1503
+ type: "list",
1504
+ name: "skill",
1505
+ message: "Which skill would you like to uninstall?",
1506
+ choices: installedSkills.map((s) => ({
1507
+ name: `${s.title || s.name} (v${s.activeVersionId || "unknown"})`,
1508
+ value: s,
1509
+ })),
1510
+ },
1511
+ ]);
1512
+ if (!skillAnswer)
1513
+ return;
1514
+ const installedSkill = skillAnswer.skill;
1515
+ // 3. Confirm uninstallation
1516
+ const confirmAnswer = await safePrompt([
1517
+ {
1518
+ type: "confirm",
1519
+ name: "confirm",
1520
+ message: `⚠️ Are you sure you want to uninstall "${installedSkill.title || installedSkill.name}"?`,
1521
+ default: false,
1522
+ },
1523
+ ]);
1524
+ if (!confirmAnswer || !confirmAnswer.confirm) {
1525
+ writeInfo("\n❌ Uninstall cancelled.\n");
1526
+ return;
1527
+ }
1528
+ if (!installedSkill.marketplaceSkillId) {
1529
+ console.error("\n❌ Skill is missing marketplace skill ID.\n");
1530
+ return;
1531
+ }
1532
+ // 4. Uninstall the skill
1533
+ writeProgress("\n🔄 Uninstalling skill...");
1534
+ await marketplaceApi.uninstallSkill(installedSkill.marketplaceSkillId, agentId);
1535
+ writeSuccess(`\n✅ "${installedSkill.title || installedSkill.name}" has been uninstalled successfully!`);
1536
+ writeInfo(`💡 The skill has been removed from agent ${agentId}.\n`);
1537
+ }
1538
+ catch (error) {
1539
+ console.error(`\n❌ Error uninstalling skill: ${error.message}\n`);
1540
+ }
1541
+ }
1542
+ /**
1543
+ * List all installed marketplace skills on the agent
1544
+ */
1545
+ async function listInstalledSkills(marketplaceApi, config) {
1546
+ try {
1547
+ const agentId = config.agent?.agentId;
1548
+ if (!agentId) {
1549
+ console.error("\n❌ No agent ID found in configuration.\n");
1550
+ return;
1551
+ }
1552
+ writeProgress("\n🔄 Loading installed marketplace skills...\n");
1553
+ const installedSkills = await marketplaceApi.getInstalledSkills(agentId);
1554
+ if (!installedSkills || installedSkills.length === 0) {
1555
+ writeInfo("\n📦 No marketplace skills installed on this agent yet.");
1556
+ writeInfo("💡 Use 'Install a skill' to browse and install skills.\n");
1557
+ return;
1558
+ }
1559
+ // Display summary
1560
+ console.log(`\n📊 You have ${installedSkills.length} marketplace skill${installedSkills.length === 1 ? "" : "s"} installed:\n`);
1561
+ // Display each installed skill
1562
+ for (const skill of installedSkills) {
1563
+ console.log(`📦 ${skill.title || skill.name}`);
1564
+ if (skill.marketplaceSkillId) {
1565
+ console.log(` Marketplace ID: ${skill.marketplaceSkillId}`);
1566
+ }
1567
+ if (skill.activeVersionId) {
1568
+ console.log(` Version: v${skill.activeVersionId}`);
1569
+ }
1570
+ if (skill.description) {
1571
+ console.log(` Description: ${skill.description}`);
1572
+ }
1573
+ if (skill.marketplaceInstallMetadata?.installedAt) {
1574
+ console.log(` Installed: ${new Date(skill.marketplaceInstallMetadata.installedAt).toLocaleDateString()}`);
1575
+ }
1576
+ if (skill.marketplaceInstallMetadata?.installedBy) {
1577
+ console.log(` Installed By: ${skill.marketplaceInstallMetadata.installedBy}`);
1578
+ }
1579
+ console.log(""); // Empty line between skills
1580
+ }
1581
+ // Prompt to continue
1582
+ await safePrompt([
1583
+ {
1584
+ type: "input",
1585
+ name: "continue",
1586
+ message: "Press Enter to continue...",
1587
+ },
1588
+ ]);
1589
+ }
1590
+ catch (error) {
1591
+ console.error(`\n❌ Error loading installed skills: ${error.message}\n`);
1592
+ }
1593
+ }