lua-cli 3.1.0-alpha.1 → 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.
- package/README.md +8 -1
- package/dist/api/marketplace.api.service.d.ts +25 -0
- package/dist/api/marketplace.api.service.js +105 -0
- package/dist/cli/command-definitions.d.ts +6 -0
- package/dist/cli/command-definitions.js +25 -1
- package/dist/commands/env.d.ts +11 -0
- package/dist/commands/env.js +1 -79
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.js +1 -0
- package/dist/commands/marketplace.d.ts +12 -0
- package/dist/commands/marketplace.js +1593 -0
- package/dist/common/user.instance.js +1 -1
- package/dist/config/constants.d.ts +1 -1
- package/dist/config/constants.js +5 -1
- package/dist/index.js +6 -4
- package/dist/interfaces/marketplace.d.ts +99 -0
- package/dist/interfaces/marketplace.js +2 -0
- package/dist/types/compile.types.d.ts +48 -41
- package/dist/types/compile.types.js +1 -0
- package/dist/utils/env-loader.utils.d.ts +9 -0
- package/dist/utils/env-loader.utils.js +88 -0
- package/dist/utils/job-management.d.ts +3 -3
- package/dist/utils/postprocessor-management.d.ts +3 -3
- package/dist/utils/preprocessor-management.d.ts +3 -3
- package/dist/utils/semver.d.ts +27 -0
- package/dist/utils/semver.js +75 -0
- package/dist/utils/skill-management.d.ts +4 -4
- package/dist/utils/webhook-management.d.ts +3 -3
- package/dist/web/app.css +0 -358
- package/package.json +1 -1
- 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
|
+
}
|