@kurokeita/add-skill 1.8.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/bin/cli.js +838 -373
- package/package.json +4 -2
package/dist/bin/cli.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// bin/cli.ts
|
|
4
|
+
import { intro as intro5, isCancel as isCancel5, outro as outro5, select as select5 } from "@clack/prompts";
|
|
4
5
|
import { Command } from "commander";
|
|
6
|
+
import pc5 from "picocolors";
|
|
5
7
|
|
|
6
8
|
// src/commands/add.ts
|
|
7
9
|
import os3 from "node:os";
|
|
8
|
-
import
|
|
10
|
+
import path5 from "node:path";
|
|
9
11
|
import {
|
|
10
12
|
cancel,
|
|
11
13
|
confirm,
|
|
@@ -14,9 +16,10 @@ import {
|
|
|
14
16
|
multiselect,
|
|
15
17
|
note,
|
|
16
18
|
outro,
|
|
19
|
+
select,
|
|
17
20
|
spinner
|
|
18
21
|
} from "@clack/prompts";
|
|
19
|
-
import
|
|
22
|
+
import fs3 from "fs-extra";
|
|
20
23
|
import pc from "picocolors";
|
|
21
24
|
|
|
22
25
|
// src/utils/github.ts
|
|
@@ -103,24 +106,26 @@ var __dirname = path2.dirname(__filename);
|
|
|
103
106
|
var PROJECT_ROOT = path2.resolve(__dirname, "../..");
|
|
104
107
|
var PLATFORM_LABELS = {
|
|
105
108
|
antigravity: "Antigravity",
|
|
109
|
+
codex: "Codex",
|
|
106
110
|
gemini: "Gemini CLI",
|
|
107
111
|
copilot: "GitHub Copilot",
|
|
108
112
|
windsurf: "Windsurf"
|
|
109
113
|
};
|
|
110
114
|
var PLATFORM_PATHS_SKILLS = {
|
|
111
115
|
antigravity: path2.join(os2.homedir(), ".gemini/antigravity/global_skills"),
|
|
116
|
+
codex: path2.join(os2.homedir(), ".codex/skills"),
|
|
112
117
|
copilot: path2.join(os2.homedir(), ".copilot/skills"),
|
|
113
118
|
gemini: path2.join(os2.homedir(), ".gemini/skills"),
|
|
114
119
|
windsurf: path2.join(os2.homedir(), ".codeium/windsurf/skills")
|
|
115
120
|
};
|
|
116
121
|
var PLATFORM_PATHS_AGENTS = {
|
|
117
|
-
|
|
122
|
+
codex: path2.join(os2.homedir(), ".codex/skills"),
|
|
118
123
|
copilot: path2.join(os2.homedir(), ".copilot/agents"),
|
|
119
|
-
gemini: path2.join(os2.homedir(), ".gemini/agents")
|
|
120
|
-
windsurf: path2.join(os2.homedir(), ".codeium/windsurf/agents")
|
|
124
|
+
gemini: path2.join(os2.homedir(), ".gemini/agents")
|
|
121
125
|
};
|
|
122
126
|
var PLATFORM_PATHS_WORKFLOWS = {
|
|
123
127
|
antigravity: path2.join(os2.homedir(), ".gemini/antigravity/global_workflows"),
|
|
128
|
+
codex: path2.join(os2.homedir(), ".codex/skills"),
|
|
124
129
|
copilot: path2.join(os2.homedir(), ".copilot/prompts"),
|
|
125
130
|
gemini: path2.join(os2.homedir(), ".gemini/commands"),
|
|
126
131
|
windsurf: path2.join(os2.homedir(), ".codeium/windsurf/global_workflows")
|
|
@@ -141,6 +146,188 @@ function getTargetPaths(type) {
|
|
|
141
146
|
}
|
|
142
147
|
}
|
|
143
148
|
|
|
149
|
+
// src/utils/platforms/antigravity.ts
|
|
150
|
+
var AntigravityHandler = class {
|
|
151
|
+
platform = "antigravity";
|
|
152
|
+
getTargetFileName(itemName, _) {
|
|
153
|
+
return itemName;
|
|
154
|
+
}
|
|
155
|
+
transform(content, _, __) {
|
|
156
|
+
return content;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// src/utils/platforms/codex.ts
|
|
161
|
+
import path3 from "node:path";
|
|
162
|
+
import fs2 from "fs-extra";
|
|
163
|
+
import yaml from "js-yaml";
|
|
164
|
+
var frontmatterRegex = /^---\n([\s\S]*?)\n---/;
|
|
165
|
+
var CODEX_TYPE_FIELD = "x-ai-agents-type";
|
|
166
|
+
function extractTitle(body) {
|
|
167
|
+
const match = body.match(/^#\s+(.+)$/m);
|
|
168
|
+
return match?.[1]?.trim();
|
|
169
|
+
}
|
|
170
|
+
function buildDescription(type, itemName, title) {
|
|
171
|
+
const subject = title || itemName;
|
|
172
|
+
if (type === "agent") {
|
|
173
|
+
return `This skill should be used when Codex needs to follow the ${subject} agent instructions.`;
|
|
174
|
+
}
|
|
175
|
+
if (type === "workflow") {
|
|
176
|
+
return `This skill should be used when Codex needs to follow the ${subject} workflow instructions.`;
|
|
177
|
+
}
|
|
178
|
+
return `This skill should be used when Codex needs the ${subject} skill.`;
|
|
179
|
+
}
|
|
180
|
+
function toYamlFrontmatter(name, description, extraFields = {}) {
|
|
181
|
+
const normalizedName = name.replace(/\n+/g, " ").trim();
|
|
182
|
+
const normalizedDescription = description.replace(/\n+/g, " ").trim();
|
|
183
|
+
return yaml.dump(
|
|
184
|
+
{
|
|
185
|
+
name: normalizedName,
|
|
186
|
+
description: normalizedDescription,
|
|
187
|
+
...extraFields
|
|
188
|
+
},
|
|
189
|
+
{ lineWidth: -1 }
|
|
190
|
+
).trimEnd();
|
|
191
|
+
}
|
|
192
|
+
function parseCodexType(content) {
|
|
193
|
+
const match = content.match(frontmatterRegex);
|
|
194
|
+
if (!match) return "skill";
|
|
195
|
+
try {
|
|
196
|
+
const parsed = yaml.load(match[1]);
|
|
197
|
+
if (parsed && typeof parsed === "object") {
|
|
198
|
+
const type = parsed[CODEX_TYPE_FIELD];
|
|
199
|
+
if (type === "agent" || type === "workflow" || type === "skill") {
|
|
200
|
+
return type;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} catch {
|
|
204
|
+
return "skill";
|
|
205
|
+
}
|
|
206
|
+
return "skill";
|
|
207
|
+
}
|
|
208
|
+
async function codexEntryMatchesType(basePath, entry, requestedType) {
|
|
209
|
+
let targetPath = null;
|
|
210
|
+
if (entry.isDirectory()) {
|
|
211
|
+
targetPath = path3.join(basePath, entry.name, "SKILL.md");
|
|
212
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
213
|
+
targetPath = path3.join(basePath, entry.name);
|
|
214
|
+
}
|
|
215
|
+
if (!targetPath) return false;
|
|
216
|
+
try {
|
|
217
|
+
const content = await fs2.readFile(targetPath, "utf-8");
|
|
218
|
+
return parseCodexType(content) === requestedType;
|
|
219
|
+
} catch {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
var CodexHandler = class {
|
|
224
|
+
platform = "codex";
|
|
225
|
+
getTargetFileName(itemName, type) {
|
|
226
|
+
if (type === "agent" || type === "workflow") {
|
|
227
|
+
return path3.join(path3.parse(itemName).name, "SKILL.md");
|
|
228
|
+
}
|
|
229
|
+
return itemName;
|
|
230
|
+
}
|
|
231
|
+
transform(content, type, itemName) {
|
|
232
|
+
if (type === "skill") return content;
|
|
233
|
+
if (type !== "agent" && type !== "workflow") return content;
|
|
234
|
+
let name = itemName;
|
|
235
|
+
let description = "";
|
|
236
|
+
let body = content.trim();
|
|
237
|
+
const match = content.match(frontmatterRegex);
|
|
238
|
+
if (match) {
|
|
239
|
+
body = content.replace(frontmatterRegex, "").trim();
|
|
240
|
+
try {
|
|
241
|
+
const parsed = yaml.load(match[1]);
|
|
242
|
+
if (parsed && typeof parsed === "object") {
|
|
243
|
+
const frontmatter = parsed;
|
|
244
|
+
if (typeof frontmatter.name === "string" && frontmatter.name.trim()) {
|
|
245
|
+
name = frontmatter.name.trim();
|
|
246
|
+
}
|
|
247
|
+
if (typeof frontmatter.description === "string" && frontmatter.description.trim()) {
|
|
248
|
+
description = frontmatter.description.trim();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
} catch {
|
|
252
|
+
name = itemName;
|
|
253
|
+
description = "";
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (!description) {
|
|
257
|
+
description = buildDescription(type, itemName, extractTitle(body));
|
|
258
|
+
}
|
|
259
|
+
const yamlStr = toYamlFrontmatter(name, description, {
|
|
260
|
+
[CODEX_TYPE_FIELD]: type
|
|
261
|
+
});
|
|
262
|
+
return `---
|
|
263
|
+
${yamlStr}
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
${body}`;
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// src/utils/platforms/copilot.ts
|
|
271
|
+
import yaml2 from "js-yaml";
|
|
272
|
+
var CopilotHandler = class {
|
|
273
|
+
platform = "copilot";
|
|
274
|
+
getTargetFileName(itemName, type) {
|
|
275
|
+
if (type === "agent") {
|
|
276
|
+
return `${itemName.replace(/\.md$/, "")}.md`;
|
|
277
|
+
}
|
|
278
|
+
return itemName;
|
|
279
|
+
}
|
|
280
|
+
transform(content, type, itemName) {
|
|
281
|
+
if (type !== "agent") return content;
|
|
282
|
+
const frontmatterRegex2 = /^---\n([\s\S]*?)\n---/;
|
|
283
|
+
const match = content.match(frontmatterRegex2);
|
|
284
|
+
let frontmatter = {
|
|
285
|
+
name: itemName,
|
|
286
|
+
description: "",
|
|
287
|
+
target: "github-copilot",
|
|
288
|
+
tools: ["edit", "terminal", "ls", "grep_search", "githubRepo"]
|
|
289
|
+
};
|
|
290
|
+
let body = content;
|
|
291
|
+
if (match) {
|
|
292
|
+
try {
|
|
293
|
+
const parsed = yaml2.load(match[1]);
|
|
294
|
+
frontmatter = { ...frontmatter, ...parsed };
|
|
295
|
+
frontmatter.target = "github-copilot";
|
|
296
|
+
frontmatter.tools = [
|
|
297
|
+
"edit",
|
|
298
|
+
"terminal",
|
|
299
|
+
"ls",
|
|
300
|
+
"grep_search",
|
|
301
|
+
"githubRepo"
|
|
302
|
+
];
|
|
303
|
+
body = content.replace(frontmatterRegex2, "").trim();
|
|
304
|
+
} catch (_) {
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const yamlStr = yaml2.dump(frontmatter);
|
|
308
|
+
return `---
|
|
309
|
+
${yamlStr}---
|
|
310
|
+
|
|
311
|
+
${body}`;
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// src/utils/platforms/default.ts
|
|
316
|
+
var DefaultHandler = class {
|
|
317
|
+
constructor(platform = "default") {
|
|
318
|
+
this.platform = platform;
|
|
319
|
+
}
|
|
320
|
+
getTargetFileName(itemName, _type) {
|
|
321
|
+
return itemName;
|
|
322
|
+
}
|
|
323
|
+
transform(content, _type, _itemName) {
|
|
324
|
+
return content;
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
// src/utils/platforms/gemini.ts
|
|
329
|
+
import yaml3 from "js-yaml";
|
|
330
|
+
|
|
144
331
|
// src/utils/toml.ts
|
|
145
332
|
function convertToGeminiCommandTOML(markdownContent) {
|
|
146
333
|
const lines = markdownContent.split("\n");
|
|
@@ -168,243 +355,403 @@ ${escapedContent.trim()}
|
|
|
168
355
|
`;
|
|
169
356
|
}
|
|
170
357
|
|
|
358
|
+
// src/utils/platforms/gemini.ts
|
|
359
|
+
var GeminiHandler = class {
|
|
360
|
+
platform = "gemini";
|
|
361
|
+
getTargetFileName(itemName, type) {
|
|
362
|
+
if (type === "workflow") {
|
|
363
|
+
return `${itemName.replace(/\.md$/, "")}.toml`;
|
|
364
|
+
}
|
|
365
|
+
return itemName;
|
|
366
|
+
}
|
|
367
|
+
transform(content, type, itemName) {
|
|
368
|
+
if (type === "workflow") {
|
|
369
|
+
return convertToGeminiCommandTOML(content);
|
|
370
|
+
}
|
|
371
|
+
if (type === "agent") {
|
|
372
|
+
const frontmatterRegex2 = /^---\n([\s\S]*?)\n---/;
|
|
373
|
+
const match = content.match(frontmatterRegex2);
|
|
374
|
+
let frontmatter = {
|
|
375
|
+
name: itemName,
|
|
376
|
+
description: "",
|
|
377
|
+
tools: [
|
|
378
|
+
"list_directory",
|
|
379
|
+
"read_file",
|
|
380
|
+
"write_file",
|
|
381
|
+
"glob",
|
|
382
|
+
"grep_search",
|
|
383
|
+
"replace",
|
|
384
|
+
"run_shell_command",
|
|
385
|
+
"google_web_search",
|
|
386
|
+
"web_fetch",
|
|
387
|
+
"ask_user",
|
|
388
|
+
"save_memory",
|
|
389
|
+
"activate_skill"
|
|
390
|
+
]
|
|
391
|
+
};
|
|
392
|
+
let body = content;
|
|
393
|
+
if (match) {
|
|
394
|
+
try {
|
|
395
|
+
const parsed = yaml3.load(match[1]);
|
|
396
|
+
frontmatter = { ...frontmatter, ...parsed };
|
|
397
|
+
frontmatter.tools = [
|
|
398
|
+
"list_directory",
|
|
399
|
+
"read_file",
|
|
400
|
+
"write_file",
|
|
401
|
+
"glob",
|
|
402
|
+
"grep_search",
|
|
403
|
+
"replace",
|
|
404
|
+
"run_shell_command",
|
|
405
|
+
"google_web_search",
|
|
406
|
+
"web_fetch",
|
|
407
|
+
"ask_user",
|
|
408
|
+
"save_memory",
|
|
409
|
+
"activate_skill"
|
|
410
|
+
];
|
|
411
|
+
delete frontmatter.skills;
|
|
412
|
+
body = content.replace(frontmatterRegex2, "").trim();
|
|
413
|
+
} catch (_) {
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const yamlStr = yaml3.dump(frontmatter);
|
|
417
|
+
return `---
|
|
418
|
+
${yamlStr}---
|
|
419
|
+
|
|
420
|
+
${body}`;
|
|
421
|
+
}
|
|
422
|
+
return content;
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
// src/utils/platforms/windsurf.ts
|
|
427
|
+
import path4 from "node:path";
|
|
428
|
+
var WindsurfHandler = class {
|
|
429
|
+
platform = "windsurf";
|
|
430
|
+
getTargetFileName(itemName, type) {
|
|
431
|
+
if (type === "agent") {
|
|
432
|
+
return path4.join(path4.parse(itemName).name, "AGENTS.md");
|
|
433
|
+
}
|
|
434
|
+
return itemName;
|
|
435
|
+
}
|
|
436
|
+
transform(content, _type, _itemName) {
|
|
437
|
+
return content;
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
// src/utils/platforms/index.ts
|
|
442
|
+
var handlers = /* @__PURE__ */ new Map();
|
|
443
|
+
function registerHandler(handler) {
|
|
444
|
+
handlers.set(handler.platform, handler);
|
|
445
|
+
}
|
|
446
|
+
registerHandler(new AntigravityHandler());
|
|
447
|
+
registerHandler(new CodexHandler());
|
|
448
|
+
registerHandler(new CopilotHandler());
|
|
449
|
+
registerHandler(new GeminiHandler());
|
|
450
|
+
registerHandler(new WindsurfHandler());
|
|
451
|
+
function getHandler(platform) {
|
|
452
|
+
return handlers.get(platform) || new DefaultHandler(platform);
|
|
453
|
+
}
|
|
454
|
+
|
|
171
455
|
// src/commands/add.ts
|
|
172
456
|
function getPlatformOptions(type) {
|
|
173
457
|
const paths = getTargetPaths(type);
|
|
174
|
-
return Object.entries(paths).map(([platform, pathStr]) => ({
|
|
458
|
+
return Object.entries(paths).filter(([_, pathStr]) => !!pathStr).map(([platform, pathStr]) => ({
|
|
175
459
|
label: PLATFORM_LABELS[platform],
|
|
176
460
|
value: platform,
|
|
177
461
|
hint: pathStr.replace(os3.homedir(), "~")
|
|
178
462
|
}));
|
|
179
463
|
}
|
|
180
464
|
async function installItem(itemName, overwrite, sourcePath, targetBaseDir) {
|
|
181
|
-
const targetPath =
|
|
182
|
-
if (!await
|
|
465
|
+
const targetPath = path5.join(targetBaseDir, itemName);
|
|
466
|
+
if (!await fs3.pathExists(sourcePath)) {
|
|
183
467
|
throw new Error(`Item '${itemName}' not found at ${sourcePath}`);
|
|
184
468
|
}
|
|
185
|
-
if (!overwrite && await
|
|
469
|
+
if (!overwrite && await fs3.pathExists(targetPath)) {
|
|
186
470
|
return false;
|
|
187
471
|
}
|
|
188
|
-
await
|
|
189
|
-
await
|
|
472
|
+
await fs3.ensureDir(targetBaseDir);
|
|
473
|
+
await fs3.copy(sourcePath, targetPath, { overwrite });
|
|
190
474
|
return true;
|
|
191
475
|
}
|
|
192
|
-
async function add(type, url) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (!TYPE_DIRS[normalizedType]) {
|
|
196
|
-
cancel(`Unknown type: ${type}. Supported: skill, agent, workflow`);
|
|
197
|
-
process.exit(1);
|
|
476
|
+
async function add(type, url, options) {
|
|
477
|
+
if (!options?.skipIntro) {
|
|
478
|
+
intro(pc.bgCyan(pc.black(" AI Manager : Add ")));
|
|
198
479
|
}
|
|
199
|
-
let
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (url) {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
s2.stop(pc.red("Failed to fetch"));
|
|
212
|
-
throw e;
|
|
213
|
-
}
|
|
214
|
-
} else {
|
|
215
|
-
const sourceDir = TYPE_DIRS[normalizedType];
|
|
216
|
-
if (!await fs2.pathExists(sourceDir)) {
|
|
217
|
-
cancel(`${normalizedType} directory not found!`);
|
|
218
|
-
process.exit(1);
|
|
219
|
-
}
|
|
220
|
-
const entries = await fs2.readdir(sourceDir, { withFileTypes: true });
|
|
221
|
-
const availableItems = entries.filter(
|
|
222
|
-
(entry) => entry.isDirectory() || (normalizedType === "workflow" || normalizedType === "agent") && entry.isFile() && entry.name.endsWith(".md")
|
|
223
|
-
).map((entry) => ({ label: entry.name, value: entry.name })).sort((a, b) => a.label.localeCompare(b.label));
|
|
224
|
-
if (availableItems.length === 0) {
|
|
225
|
-
cancel(`No ${normalizedType}s found in directory.`);
|
|
226
|
-
process.exit(0);
|
|
227
|
-
}
|
|
228
|
-
const items = await multiselect({
|
|
229
|
-
message: `Select ${normalizedType}s to install:`,
|
|
230
|
-
options: availableItems,
|
|
231
|
-
required: true
|
|
480
|
+
let currentType = type;
|
|
481
|
+
const isSingleShot = !!type || !!url;
|
|
482
|
+
while (true) {
|
|
483
|
+
if (!currentType && !url) {
|
|
484
|
+
const selectedType = await select({
|
|
485
|
+
message: "What would you like to add?",
|
|
486
|
+
options: [
|
|
487
|
+
{ label: "Skills", value: "skill" },
|
|
488
|
+
{ label: "Agents", value: "agent" },
|
|
489
|
+
{ label: "Workflows", value: "workflow" },
|
|
490
|
+
{ label: "Exit", value: "exit" }
|
|
491
|
+
]
|
|
232
492
|
});
|
|
233
|
-
if (isCancel(
|
|
234
|
-
|
|
235
|
-
process.exit(0);
|
|
493
|
+
if (isCancel(selectedType) || selectedType === "exit") {
|
|
494
|
+
break;
|
|
236
495
|
}
|
|
237
|
-
|
|
496
|
+
currentType = selectedType;
|
|
238
497
|
}
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
cancel(`No supported platforms found for type '${normalizedType}'.`);
|
|
498
|
+
const normalizedType = currentType?.toLowerCase().endsWith("s") && currentType.length > 1 ? currentType.slice(0, -1) : currentType ?? "";
|
|
499
|
+
if (!TYPE_DIRS[normalizedType] && !url) {
|
|
500
|
+
cancel(`Unknown type: ${currentType}. Supported: skill, agent, workflow`);
|
|
243
501
|
process.exit(1);
|
|
502
|
+
return;
|
|
244
503
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
504
|
+
let tempDir = null;
|
|
505
|
+
let selectedItems = [];
|
|
506
|
+
try {
|
|
507
|
+
if (url) {
|
|
508
|
+
const s2 = spinner();
|
|
509
|
+
s2.start(`Fetching ${normalizedType} from GitHub...`);
|
|
510
|
+
try {
|
|
511
|
+
const result = await fetchSkillFromGitHub(url);
|
|
512
|
+
tempDir = result.tempDir;
|
|
513
|
+
selectedItems = [result.skillName];
|
|
514
|
+
s2.stop(pc.green(`Fetched ${normalizedType}: ${result.skillName}`));
|
|
515
|
+
} catch (e) {
|
|
516
|
+
s2.stop(pc.red("Failed to fetch"));
|
|
517
|
+
throw e;
|
|
518
|
+
}
|
|
519
|
+
} else {
|
|
520
|
+
const sourceDir = TYPE_DIRS[normalizedType];
|
|
521
|
+
if (!await fs3.pathExists(sourceDir)) {
|
|
522
|
+
cancel(`${normalizedType} directory not found!`);
|
|
523
|
+
process.exit(1);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const entries = await fs3.readdir(sourceDir, { withFileTypes: true });
|
|
527
|
+
const availableItems = entries.filter(
|
|
528
|
+
(entry) => entry.isDirectory() || (normalizedType === "workflow" || normalizedType === "agent") && entry.isFile() && entry.name.endsWith(".md")
|
|
529
|
+
).map((entry) => ({ label: entry.name, value: entry.name })).sort((a, b) => a.label.localeCompare(b.label));
|
|
530
|
+
if (availableItems.length === 0) {
|
|
531
|
+
note(`No ${normalizedType}s found in directory.`, "Information");
|
|
532
|
+
if (isSingleShot) break;
|
|
533
|
+
currentType = void 0;
|
|
534
|
+
continue;
|
|
265
535
|
}
|
|
536
|
+
const items = await multiselect({
|
|
537
|
+
message: `Select ${normalizedType}s to install:`,
|
|
538
|
+
options: availableItems,
|
|
539
|
+
required: true
|
|
540
|
+
});
|
|
541
|
+
if (isCancel(items)) {
|
|
542
|
+
if (isSingleShot) break;
|
|
543
|
+
currentType = void 0;
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
selectedItems = items;
|
|
266
547
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
548
|
+
const supportedPaths = getTargetPaths(normalizedType);
|
|
549
|
+
const currentPlatformOptions = getPlatformOptions(normalizedType);
|
|
550
|
+
if (currentPlatformOptions.length === 0) {
|
|
551
|
+
cancel(`No supported platforms found for type '${normalizedType}'.`);
|
|
552
|
+
process.exit(1);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const platforms = await multiselect({
|
|
556
|
+
message: "Select target platforms:",
|
|
557
|
+
options: currentPlatformOptions,
|
|
558
|
+
required: true
|
|
278
559
|
});
|
|
279
|
-
if (isCancel(
|
|
280
|
-
if (tempDir) await
|
|
281
|
-
|
|
282
|
-
|
|
560
|
+
if (isCancel(platforms)) {
|
|
561
|
+
if (tempDir) await fs3.remove(tempDir);
|
|
562
|
+
if (isSingleShot) break;
|
|
563
|
+
currentType = void 0;
|
|
564
|
+
continue;
|
|
283
565
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
for (const platform of selectedPlatforms) {
|
|
296
|
-
const targetBase = supportedPaths[platform];
|
|
297
|
-
if (!targetBase) continue;
|
|
298
|
-
for (const item of selectedItems) {
|
|
299
|
-
const message = `Installing ${pc.bold(item)} to ${pc.cyan(platform)}...`;
|
|
300
|
-
s.message(message);
|
|
301
|
-
try {
|
|
302
|
-
if (url && tempDir) {
|
|
566
|
+
const selectedPlatforms = platforms;
|
|
567
|
+
const existingItems = [];
|
|
568
|
+
for (const platform of selectedPlatforms) {
|
|
569
|
+
const targetBase = supportedPaths[platform];
|
|
570
|
+
if (!targetBase) continue;
|
|
571
|
+
const handler = getHandler(platform);
|
|
572
|
+
for (const item of selectedItems) {
|
|
573
|
+
const targetItemName = handler.getTargetFileName(item, normalizedType);
|
|
574
|
+
const targetPath = path5.join(targetBase, targetItemName);
|
|
575
|
+
if (await fs3.pathExists(targetPath)) {
|
|
576
|
+
existingItems.push({ item, platform });
|
|
303
577
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
let overwrite = true;
|
|
581
|
+
if (existingItems.length > 0) {
|
|
582
|
+
const limit = 5;
|
|
583
|
+
const displayList = existingItems.slice(0, limit).map((e) => `${pc.bold(e.item)} (${e.platform})`).join(", ");
|
|
584
|
+
const remainder = existingItems.length - limit;
|
|
585
|
+
const message = remainder > 0 ? `The following items already exist: ${displayList}, and ${remainder} others.` : `The following items already exist: ${displayList}.`;
|
|
586
|
+
note(message, "Attention");
|
|
587
|
+
const shouldOverwrite = await confirm({
|
|
588
|
+
message: "Do you want to overwrite existing items?",
|
|
589
|
+
initialValue: false
|
|
590
|
+
});
|
|
591
|
+
if (isCancel(shouldOverwrite)) {
|
|
592
|
+
if (tempDir) await fs3.remove(tempDir);
|
|
593
|
+
if (isSingleShot) break;
|
|
594
|
+
currentType = void 0;
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
overwrite = shouldOverwrite;
|
|
598
|
+
}
|
|
599
|
+
note(
|
|
600
|
+
`Installing ${selectedItems.length} ${normalizedType}s to ${selectedPlatforms.length} platforms...`,
|
|
601
|
+
"Summary"
|
|
602
|
+
);
|
|
603
|
+
const s = spinner();
|
|
604
|
+
s.start("Installing...");
|
|
605
|
+
const errors = [];
|
|
606
|
+
let installedCount = 0;
|
|
607
|
+
let skippedCount = 0;
|
|
608
|
+
for (const platform of selectedPlatforms) {
|
|
609
|
+
const targetBase = supportedPaths[platform];
|
|
610
|
+
if (!targetBase) continue;
|
|
611
|
+
for (const item of selectedItems) {
|
|
612
|
+
const message = `Installing ${pc.bold(item)} to ${pc.cyan(platform)}...`;
|
|
613
|
+
s.message(message);
|
|
614
|
+
try {
|
|
615
|
+
let currentSourcePath;
|
|
616
|
+
if (url && tempDir) {
|
|
617
|
+
const potentialFile = path5.join(tempDir, item);
|
|
618
|
+
if (await fs3.pathExists(potentialFile) && (await fs3.stat(potentialFile)).isFile()) {
|
|
619
|
+
currentSourcePath = potentialFile;
|
|
620
|
+
} else {
|
|
621
|
+
currentSourcePath = tempDir;
|
|
622
|
+
}
|
|
309
623
|
} else {
|
|
310
|
-
|
|
624
|
+
const src = path5.join(TYPE_DIRS[normalizedType], item);
|
|
625
|
+
if ((await fs3.stat(src)).isFile()) {
|
|
626
|
+
currentSourcePath = src;
|
|
627
|
+
} else {
|
|
628
|
+
currentSourcePath = src;
|
|
629
|
+
}
|
|
311
630
|
}
|
|
312
|
-
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
631
|
+
const handler = getHandler(platform);
|
|
632
|
+
const targetItemName = handler.getTargetFileName(
|
|
633
|
+
item,
|
|
634
|
+
normalizedType
|
|
635
|
+
);
|
|
636
|
+
if ((normalizedType === "agent" || normalizedType === "workflow") && currentSourcePath.endsWith(".md")) {
|
|
637
|
+
const targetPath = path5.join(targetBase, targetItemName);
|
|
638
|
+
if (!overwrite && await fs3.pathExists(targetPath)) {
|
|
639
|
+
skippedCount++;
|
|
640
|
+
} else {
|
|
641
|
+
let content = await fs3.readFile(currentSourcePath, "utf-8");
|
|
642
|
+
content = handler.transform(
|
|
643
|
+
content,
|
|
644
|
+
normalizedType,
|
|
645
|
+
path5.parse(item).name
|
|
646
|
+
);
|
|
647
|
+
await fs3.ensureDir(path5.dirname(targetPath));
|
|
648
|
+
await fs3.writeFile(targetPath, content);
|
|
649
|
+
installedCount++;
|
|
650
|
+
}
|
|
651
|
+
continue;
|
|
318
652
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (platform === "gemini" && normalizedType === "workflow" && currentSourcePath.endsWith(".md")) {
|
|
325
|
-
const targetPath = path3.join(
|
|
326
|
-
targetBase,
|
|
327
|
-
item.replace(/\.md$/, ".toml")
|
|
653
|
+
const installed = await installItem(
|
|
654
|
+
targetItemName,
|
|
655
|
+
overwrite,
|
|
656
|
+
currentSourcePath,
|
|
657
|
+
targetBase
|
|
328
658
|
);
|
|
329
|
-
if (
|
|
330
|
-
skippedCount++;
|
|
331
|
-
} else {
|
|
332
|
-
const content = await fs2.readFile(currentSourcePath, "utf-8");
|
|
333
|
-
const tomlContent = convertToGeminiCommandTOML(content);
|
|
334
|
-
await fs2.ensureDir(targetBase);
|
|
335
|
-
await fs2.writeFile(targetPath, tomlContent);
|
|
659
|
+
if (installed) {
|
|
336
660
|
installedCount++;
|
|
661
|
+
} else {
|
|
662
|
+
skippedCount++;
|
|
337
663
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
targetItemName,
|
|
342
|
-
overwrite,
|
|
343
|
-
currentSourcePath,
|
|
344
|
-
targetBase
|
|
345
|
-
);
|
|
346
|
-
if (installed) {
|
|
347
|
-
installedCount++;
|
|
348
|
-
} else {
|
|
349
|
-
skippedCount++;
|
|
664
|
+
} catch (err) {
|
|
665
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
666
|
+
errors.push(`${item} -> ${platform}: ${errorMessage}`);
|
|
350
667
|
}
|
|
351
|
-
} catch (err) {
|
|
352
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
353
|
-
errors.push(`${item} -> ${platform}: ${errorMessage}`);
|
|
354
668
|
}
|
|
355
669
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
await fs2.remove(tempDir);
|
|
359
|
-
}
|
|
360
|
-
if (errors.length > 0) {
|
|
361
|
-
s.stop(
|
|
362
|
-
pc.yellow(
|
|
363
|
-
`Completed with errors. Installed: ${installedCount}, Skipped: ${skippedCount}, Errors: ${errors.length}`
|
|
364
|
-
)
|
|
365
|
-
);
|
|
366
|
-
console.error(pc.red("\nErrors encountered:"));
|
|
367
|
-
for (const e of errors) {
|
|
368
|
-
console.error(pc.red(`- ${e}`));
|
|
670
|
+
if (tempDir) {
|
|
671
|
+
await fs3.remove(tempDir);
|
|
369
672
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
673
|
+
if (errors.length > 0) {
|
|
674
|
+
s.stop(
|
|
675
|
+
pc.yellow(
|
|
676
|
+
`Completed with errors. Installed: ${installedCount}, Skipped: ${skippedCount}, Errors: ${errors.length}`
|
|
677
|
+
)
|
|
678
|
+
);
|
|
679
|
+
console.error(pc.red("\nErrors encountered:"));
|
|
680
|
+
for (const e of errors) {
|
|
681
|
+
console.error(pc.red(`- ${e}`));
|
|
682
|
+
}
|
|
683
|
+
} else {
|
|
684
|
+
s.stop(
|
|
685
|
+
pc.green(
|
|
686
|
+
`Successfully installed ${installedCount} ${normalizedType}s. (${skippedCount} skipped)`
|
|
687
|
+
)
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
} catch (error) {
|
|
691
|
+
if (tempDir) await fs3.remove(tempDir);
|
|
692
|
+
cancel(`An error occurred: ${error}`);
|
|
693
|
+
process.exit(1);
|
|
694
|
+
return;
|
|
376
695
|
}
|
|
696
|
+
if (isSingleShot) break;
|
|
697
|
+
currentType = void 0;
|
|
698
|
+
}
|
|
699
|
+
if (!options?.skipIntro) {
|
|
377
700
|
outro("You're all set!");
|
|
378
|
-
} catch (error) {
|
|
379
|
-
if (tempDir) await fs2.remove(tempDir);
|
|
380
|
-
cancel(`An error occurred: ${error}`);
|
|
381
|
-
process.exit(1);
|
|
382
701
|
}
|
|
383
702
|
}
|
|
384
703
|
|
|
385
704
|
// src/commands/import.ts
|
|
386
|
-
import
|
|
705
|
+
import path6 from "node:path";
|
|
387
706
|
import {
|
|
388
707
|
cancel as cancel2,
|
|
389
708
|
confirm as confirm2,
|
|
390
709
|
intro as intro2,
|
|
391
710
|
isCancel as isCancel2,
|
|
392
711
|
outro as outro2,
|
|
393
|
-
|
|
712
|
+
select as select2,
|
|
713
|
+
spinner as spinner2,
|
|
714
|
+
text
|
|
394
715
|
} from "@clack/prompts";
|
|
395
|
-
import
|
|
716
|
+
import fs4 from "fs-extra";
|
|
396
717
|
import pc2 from "picocolors";
|
|
397
|
-
async function importItem(type, url) {
|
|
398
|
-
|
|
399
|
-
|
|
718
|
+
async function importItem(type, url, options) {
|
|
719
|
+
if (!options?.skipIntro) {
|
|
720
|
+
intro2(pc2.bgCyan(pc2.black(" AI Manager : Import ")));
|
|
721
|
+
}
|
|
722
|
+
let currentType = type;
|
|
723
|
+
if (!currentType) {
|
|
724
|
+
const selectedType = await select2({
|
|
725
|
+
message: "What would you like to import?",
|
|
726
|
+
options: [
|
|
727
|
+
{ label: "Skill", value: "skill" },
|
|
728
|
+
{ label: "Agent", value: "agent" },
|
|
729
|
+
{ label: "Workflow", value: "workflow" }
|
|
730
|
+
]
|
|
731
|
+
});
|
|
732
|
+
if (isCancel2(selectedType)) return;
|
|
733
|
+
currentType = selectedType;
|
|
734
|
+
}
|
|
735
|
+
const normalizedType = currentType.toLowerCase().endsWith("s") ? currentType.slice(0, -1) : currentType;
|
|
400
736
|
const targetBaseDir = TYPE_DIRS[normalizedType];
|
|
401
737
|
if (!targetBaseDir) {
|
|
402
|
-
cancel2(`Unknown type: ${
|
|
738
|
+
cancel2(`Unknown type: ${currentType}. Supported: skill, agent, workflow`);
|
|
403
739
|
process.exit(1);
|
|
740
|
+
return;
|
|
404
741
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
742
|
+
let currentUrl = url;
|
|
743
|
+
if (!currentUrl) {
|
|
744
|
+
const inputUrl = await text({
|
|
745
|
+
message: "Enter the GitHub URL:",
|
|
746
|
+
placeholder: "https://github.com/owner/repo/tree/main/path/to/item",
|
|
747
|
+
validate: (value) => {
|
|
748
|
+
if (!value) return "URL is required.";
|
|
749
|
+
if (!value.startsWith("https://github.com/"))
|
|
750
|
+
return "Must be a GitHub URL.";
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
if (isCancel2(inputUrl)) return;
|
|
754
|
+
currentUrl = inputUrl;
|
|
408
755
|
}
|
|
409
756
|
let tempDir = null;
|
|
410
757
|
try {
|
|
@@ -413,7 +760,7 @@ async function importItem(type, url) {
|
|
|
413
760
|
let itemName = "";
|
|
414
761
|
let isFile = false;
|
|
415
762
|
try {
|
|
416
|
-
const result = await fetchSkillFromGitHub(
|
|
763
|
+
const result = await fetchSkillFromGitHub(currentUrl);
|
|
417
764
|
tempDir = result.tempDir;
|
|
418
765
|
if (!tempDir) throw new Error("Failed to create temp directory");
|
|
419
766
|
itemName = result.skillName;
|
|
@@ -424,12 +771,12 @@ async function importItem(type, url) {
|
|
|
424
771
|
throw e;
|
|
425
772
|
}
|
|
426
773
|
const shouldFlatten = (normalizedType === "agent" || normalizedType === "workflow") && !isFile;
|
|
427
|
-
const targetPath = shouldFlatten ? targetBaseDir :
|
|
774
|
+
const targetPath = shouldFlatten ? targetBaseDir : path6.join(targetBaseDir, itemName);
|
|
428
775
|
if (shouldFlatten) {
|
|
429
|
-
const srcFiles = await
|
|
776
|
+
const srcFiles = await fs4.readdir(tempDir);
|
|
430
777
|
const conflicts = [];
|
|
431
778
|
for (const file of srcFiles) {
|
|
432
|
-
if (await
|
|
779
|
+
if (await fs4.pathExists(path6.join(targetBaseDir, file))) {
|
|
433
780
|
conflicts.push(file);
|
|
434
781
|
}
|
|
435
782
|
}
|
|
@@ -443,252 +790,370 @@ async function importItem(type, url) {
|
|
|
443
790
|
initialValue: false
|
|
444
791
|
});
|
|
445
792
|
if (isCancel2(shouldOverwrite) || !shouldOverwrite) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
process.exit(0);
|
|
793
|
+
await fs4.remove(tempDir);
|
|
794
|
+
return;
|
|
449
795
|
}
|
|
450
796
|
}
|
|
451
|
-
} else if (await
|
|
797
|
+
} else if (await fs4.pathExists(targetPath)) {
|
|
452
798
|
const shouldOverwrite = await confirm2({
|
|
453
799
|
message: `${normalizedType} '${itemName}' already exists in the repo. Overwrite?`,
|
|
454
800
|
initialValue: false
|
|
455
801
|
});
|
|
456
802
|
if (isCancel2(shouldOverwrite) || !shouldOverwrite) {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
process.exit(0);
|
|
803
|
+
await fs4.remove(tempDir);
|
|
804
|
+
return;
|
|
460
805
|
}
|
|
461
806
|
}
|
|
462
807
|
s.start(`Importing ${itemName} to ${targetBaseDir}...`);
|
|
463
|
-
await
|
|
808
|
+
await fs4.ensureDir(targetBaseDir);
|
|
464
809
|
if (isFile) {
|
|
465
|
-
await
|
|
810
|
+
await fs4.copy(path6.join(tempDir, itemName), targetPath, {
|
|
466
811
|
overwrite: true
|
|
467
812
|
});
|
|
468
813
|
} else {
|
|
469
814
|
if (shouldFlatten) {
|
|
470
|
-
await
|
|
815
|
+
await fs4.copy(tempDir, targetBaseDir, { overwrite: true });
|
|
471
816
|
} else {
|
|
472
|
-
await
|
|
817
|
+
await fs4.copy(tempDir, targetPath, { overwrite: true });
|
|
473
818
|
}
|
|
474
819
|
}
|
|
475
820
|
s.stop(pc2.green(`Successfully imported ${itemName}!`));
|
|
476
|
-
|
|
477
|
-
|
|
821
|
+
await fs4.remove(tempDir);
|
|
822
|
+
if (!options?.skipIntro) {
|
|
823
|
+
outro2(`${normalizedType} available at: ${targetPath}`);
|
|
478
824
|
}
|
|
479
|
-
outro2(`${normalizedType} available at: ${targetPath}`);
|
|
480
825
|
} catch (error) {
|
|
481
|
-
if (tempDir) await
|
|
826
|
+
if (tempDir) await fs4.remove(tempDir);
|
|
482
827
|
cancel2(`An error occurred: ${error}`);
|
|
483
828
|
process.exit(1);
|
|
829
|
+
return;
|
|
484
830
|
}
|
|
485
831
|
}
|
|
486
832
|
|
|
487
833
|
// src/commands/list.ts
|
|
488
|
-
import
|
|
834
|
+
import { intro as intro3, isCancel as isCancel3, outro as outro3, select as select3 } from "@clack/prompts";
|
|
835
|
+
import fs5 from "fs-extra";
|
|
489
836
|
import pc3 from "picocolors";
|
|
490
|
-
async function
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
(entry
|
|
507
|
-
// Usually flattened -> directory (if unpacked) or file (if generic).
|
|
508
|
-
// Existing add logic:
|
|
509
|
-
// Agents/Workflows can be flattened (unpacked to base) or not.
|
|
510
|
-
// If flattened, they are files in the base dir? No, unpacked to base means they are files/dirs IN the base dir.
|
|
511
|
-
// Wait, if I import "agent-a" (dir) and flatten, it copies contents of agent-a to agents/.
|
|
512
|
-
// So "agents/skill.md" might exist.
|
|
513
|
-
// The user requirement says "list all installed agents/skills/workflows".
|
|
514
|
-
// In local global_skills (antigravity), they are directories.
|
|
515
|
-
// In local global_agents, they might be directories or .md files.
|
|
516
|
-
// Let's list directories and .md files.
|
|
517
|
-
entry.isFile() && entry.name.endsWith(".md")
|
|
518
|
-
).map((entry) => entry.name).sort();
|
|
519
|
-
if (items.length > 0) {
|
|
520
|
-
foundAny = true;
|
|
521
|
-
console.log(pc3.cyan(` ${platform}:`));
|
|
522
|
-
for (const item of items) {
|
|
523
|
-
console.log(` - ${item}`);
|
|
524
|
-
}
|
|
837
|
+
async function performListing(type, local) {
|
|
838
|
+
const normalizedType = type.toLowerCase().endsWith("s") ? type.slice(0, -1) : type.toLowerCase();
|
|
839
|
+
if (local) {
|
|
840
|
+
console.log(pc3.bold(pc3.blue(`Installed ${normalizedType}s (Local):`)));
|
|
841
|
+
const targetPaths = getTargetPaths(normalizedType);
|
|
842
|
+
let foundAny = false;
|
|
843
|
+
for (const [platform, pathStr] of Object.entries(targetPaths)) {
|
|
844
|
+
const fullPath = pathStr;
|
|
845
|
+
if (await fs5.pathExists(fullPath)) {
|
|
846
|
+
const entries = await fs5.readdir(fullPath, {
|
|
847
|
+
withFileTypes: true
|
|
848
|
+
});
|
|
849
|
+
const items = [];
|
|
850
|
+
for (const entry of entries) {
|
|
851
|
+
if (platform === "codex") {
|
|
852
|
+
if (await codexEntryMatchesType(fullPath, entry, normalizedType)) {
|
|
853
|
+
items.push(entry.name);
|
|
525
854
|
}
|
|
855
|
+
continue;
|
|
526
856
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
console.log(pc3.dim(` No installed ${normalizedType}s found.`));
|
|
530
|
-
}
|
|
531
|
-
console.log("");
|
|
532
|
-
} else {
|
|
533
|
-
if (!TYPE_DIRS[normalizedType]) {
|
|
534
|
-
if (type) {
|
|
535
|
-
console.error(
|
|
536
|
-
pc3.red(
|
|
537
|
-
`Unknown type: ${type}. Supported types: skill, agent, workflow`
|
|
538
|
-
)
|
|
539
|
-
);
|
|
540
|
-
}
|
|
541
|
-
continue;
|
|
542
|
-
}
|
|
543
|
-
const dirPath = TYPE_DIRS[normalizedType];
|
|
544
|
-
if (!await fs4.pathExists(dirPath)) {
|
|
545
|
-
if (type) {
|
|
546
|
-
console.log(pc3.yellow(`No ${t} directory found at: ${dirPath}`));
|
|
857
|
+
if (entry.isDirectory() || entry.isFile() && entry.name.endsWith(".md")) {
|
|
858
|
+
items.push(entry.name);
|
|
547
859
|
}
|
|
548
|
-
continue;
|
|
549
860
|
}
|
|
550
|
-
|
|
551
|
-
const items = entries.filter(
|
|
552
|
-
(entry) => entry.isDirectory() || normalizedType !== "skill" && entry.isFile() && entry.name.endsWith(".md")
|
|
553
|
-
).map((entry) => entry.name).sort();
|
|
861
|
+
items.sort();
|
|
554
862
|
if (items.length > 0) {
|
|
555
|
-
|
|
863
|
+
foundAny = true;
|
|
864
|
+
console.log(pc3.cyan(` ${platform}:`));
|
|
556
865
|
for (const item of items) {
|
|
557
|
-
console.log(
|
|
866
|
+
console.log(` - ${item}`);
|
|
558
867
|
}
|
|
559
|
-
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
if (!foundAny) {
|
|
872
|
+
console.log(pc3.dim(` No installed ${normalizedType}s found.`));
|
|
873
|
+
}
|
|
874
|
+
console.log("");
|
|
875
|
+
} else {
|
|
876
|
+
if (!TYPE_DIRS[normalizedType]) {
|
|
877
|
+
console.error(
|
|
878
|
+
pc3.red(
|
|
879
|
+
`Unknown type: ${type}. Supported types: skill, agent, workflow`
|
|
880
|
+
)
|
|
881
|
+
);
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
const dirPath = TYPE_DIRS[normalizedType];
|
|
885
|
+
if (!await fs5.pathExists(dirPath)) {
|
|
886
|
+
console.log(
|
|
887
|
+
pc3.yellow(`No ${normalizedType} directory found at: ${dirPath}`)
|
|
888
|
+
);
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
const entries = await fs5.readdir(dirPath, { withFileTypes: true });
|
|
892
|
+
const items = entries.filter(
|
|
893
|
+
(entry) => entry.isDirectory() || normalizedType !== "skill" && entry.isFile() && entry.name.endsWith(".md")
|
|
894
|
+
).map((entry) => entry.name).sort();
|
|
895
|
+
if (items.length > 0) {
|
|
896
|
+
console.log(pc3.bold(pc3.blue(`Available ${normalizedType}s:`)));
|
|
897
|
+
for (const item of items) {
|
|
898
|
+
console.log(`- ${item}`);
|
|
899
|
+
}
|
|
900
|
+
console.log(pc3.dim(`Total: ${items.length} ${normalizedType}s
|
|
560
901
|
`));
|
|
561
|
-
|
|
562
|
-
|
|
902
|
+
} else {
|
|
903
|
+
console.log(pc3.dim(`No ${normalizedType}s found.`));
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
async function list(type, options) {
|
|
908
|
+
if (!options?.skipIntro) {
|
|
909
|
+
intro3(pc3.bgCyan(pc3.black(" AI Manager : List ")));
|
|
910
|
+
}
|
|
911
|
+
let currentType = type;
|
|
912
|
+
let isLocal = !!options?.local;
|
|
913
|
+
const isSingleShot = !!type;
|
|
914
|
+
try {
|
|
915
|
+
while (true) {
|
|
916
|
+
if (!currentType) {
|
|
917
|
+
const selected = await select3({
|
|
918
|
+
message: `What would you like to list? (View: ${isLocal ? "Local" : "Repository"})`,
|
|
919
|
+
options: [
|
|
920
|
+
{ label: "All Skills", value: "skill" },
|
|
921
|
+
{ label: "All Agents", value: "agent" },
|
|
922
|
+
{ label: "All Workflows", value: "workflow" },
|
|
923
|
+
{
|
|
924
|
+
label: `Switch to ${isLocal ? "Repository" : "Local"} View`,
|
|
925
|
+
value: "toggle"
|
|
926
|
+
},
|
|
927
|
+
{ label: "Exit", value: "exit" }
|
|
928
|
+
]
|
|
929
|
+
});
|
|
930
|
+
if (isCancel3(selected) || selected === "exit") {
|
|
931
|
+
break;
|
|
563
932
|
}
|
|
933
|
+
if (selected === "toggle") {
|
|
934
|
+
isLocal = !isLocal;
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
currentType = selected;
|
|
564
938
|
}
|
|
939
|
+
const typesToList = currentType === "all" ? ["skill", "agent", "workflow"] : [currentType];
|
|
940
|
+
for (const t of typesToList) {
|
|
941
|
+
await performListing(t, isLocal);
|
|
942
|
+
}
|
|
943
|
+
if (isSingleShot) break;
|
|
944
|
+
currentType = void 0;
|
|
565
945
|
}
|
|
566
946
|
} catch (error) {
|
|
567
947
|
console.error(pc3.red("Error listing items:"), error);
|
|
568
948
|
}
|
|
949
|
+
if (!options?.skipIntro) {
|
|
950
|
+
outro3("Done!");
|
|
951
|
+
}
|
|
569
952
|
}
|
|
570
953
|
|
|
571
954
|
// src/commands/remove.ts
|
|
572
|
-
import
|
|
955
|
+
import path7 from "node:path";
|
|
573
956
|
import {
|
|
574
|
-
cancel as cancel3,
|
|
575
957
|
confirm as confirm3,
|
|
576
|
-
intro as
|
|
577
|
-
isCancel as
|
|
958
|
+
intro as intro4,
|
|
959
|
+
isCancel as isCancel4,
|
|
578
960
|
multiselect as multiselect2,
|
|
579
|
-
|
|
961
|
+
note as note2,
|
|
962
|
+
outro as outro4,
|
|
963
|
+
select as select4,
|
|
580
964
|
spinner as spinner3
|
|
581
965
|
} from "@clack/prompts";
|
|
582
|
-
import
|
|
966
|
+
import fs6 from "fs-extra";
|
|
583
967
|
import pc4 from "picocolors";
|
|
584
|
-
async function remove(type) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
968
|
+
async function remove(type, options) {
|
|
969
|
+
if (!options?.skipIntro) {
|
|
970
|
+
intro4(pc4.bgCyan(pc4.black(" AI Manager : Remove ")));
|
|
971
|
+
}
|
|
972
|
+
let currentType = type;
|
|
973
|
+
const isSingleShot = !!type;
|
|
974
|
+
while (true) {
|
|
975
|
+
if (!currentType) {
|
|
976
|
+
const selectedType = await select4({
|
|
977
|
+
message: "What would you like to remove?",
|
|
978
|
+
options: [
|
|
979
|
+
{ label: "Skills", value: "skill" },
|
|
980
|
+
{ label: "Agents", value: "agent" },
|
|
981
|
+
{ label: "Workflows", value: "workflow" },
|
|
982
|
+
{ label: "Exit", value: "exit" }
|
|
983
|
+
]
|
|
595
984
|
});
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
985
|
+
if (isCancel4(selectedType) || selectedType === "exit") {
|
|
986
|
+
break;
|
|
987
|
+
}
|
|
988
|
+
currentType = selectedType;
|
|
989
|
+
}
|
|
990
|
+
const normalizedType = currentType.toLowerCase().endsWith("s") ? currentType.slice(0, -1) : currentType;
|
|
991
|
+
const targetPaths = getTargetPaths(normalizedType);
|
|
992
|
+
const uniqueItems = /* @__PURE__ */ new Set();
|
|
993
|
+
const s = spinner3();
|
|
994
|
+
s.start("Scanning for installed items...");
|
|
995
|
+
for (const [platform, pathStr] of Object.entries(targetPaths)) {
|
|
996
|
+
if (await fs6.pathExists(pathStr)) {
|
|
997
|
+
const entries = await fs6.readdir(pathStr, {
|
|
998
|
+
withFileTypes: true
|
|
999
|
+
});
|
|
1000
|
+
for (const entry of entries) {
|
|
1001
|
+
if (platform === "codex") {
|
|
1002
|
+
if (await codexEntryMatchesType(
|
|
1003
|
+
pathStr,
|
|
1004
|
+
entry,
|
|
1005
|
+
normalizedType
|
|
1006
|
+
)) {
|
|
1007
|
+
uniqueItems.add(entry.name);
|
|
1008
|
+
}
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
if (entry.isDirectory() || normalizedType === "workflow" && entry.isFile() && entry.name.endsWith(".md") || normalizedType === "agent" && entry.isFile() && entry.name.endsWith(".md")) {
|
|
1012
|
+
uniqueItems.add(entry.name);
|
|
1013
|
+
}
|
|
599
1014
|
}
|
|
600
1015
|
}
|
|
601
1016
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1017
|
+
s.stop(`Found ${uniqueItems.size} unique installed ${normalizedType}s.`);
|
|
1018
|
+
if (uniqueItems.size === 0) {
|
|
1019
|
+
note2(`No installed ${normalizedType}s found.`, "Information");
|
|
1020
|
+
if (isSingleShot) break;
|
|
1021
|
+
currentType = void 0;
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
const sortedItems = Array.from(uniqueItems).sort();
|
|
1025
|
+
const itemSelections = await multiselect2({
|
|
1026
|
+
message: `Select ${normalizedType}s to remove:`,
|
|
1027
|
+
options: sortedItems.map((item) => ({ value: item, label: item })),
|
|
1028
|
+
required: true
|
|
1029
|
+
});
|
|
1030
|
+
if (isCancel4(itemSelections)) {
|
|
1031
|
+
if (isSingleShot) break;
|
|
1032
|
+
currentType = void 0;
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
const selectedItems = itemSelections;
|
|
1036
|
+
const platformOptions = Object.entries(targetPaths).filter(([_, pathStr]) => !!pathStr).map(([platform]) => ({
|
|
1037
|
+
label: PLATFORM_LABELS[platform],
|
|
1038
|
+
value: platform
|
|
1039
|
+
}));
|
|
1040
|
+
const platformSelections = await multiselect2({
|
|
1041
|
+
message: "Select platforms to remove from:",
|
|
1042
|
+
options: platformOptions,
|
|
1043
|
+
required: true
|
|
1044
|
+
});
|
|
1045
|
+
if (isCancel4(platformSelections)) {
|
|
1046
|
+
if (isSingleShot) break;
|
|
1047
|
+
currentType = void 0;
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
const selectedPlatforms = platformSelections;
|
|
1051
|
+
const confirmDelete = await confirm3({
|
|
1052
|
+
message: `Are you sure you want to remove ${selectedItems.length} item(s) from ${selectedPlatforms.length} platform(s)?`,
|
|
1053
|
+
initialValue: false
|
|
1054
|
+
});
|
|
1055
|
+
if (isCancel4(confirmDelete) || !confirmDelete) {
|
|
1056
|
+
if (isSingleShot) break;
|
|
1057
|
+
currentType = void 0;
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
const sDel = spinner3();
|
|
1061
|
+
sDel.start("Removing items...");
|
|
1062
|
+
let removedCount = 0;
|
|
1063
|
+
let notFoundCount = 0;
|
|
1064
|
+
const errors = [];
|
|
1065
|
+
for (const platform of selectedPlatforms) {
|
|
1066
|
+
const targetBase = targetPaths[platform];
|
|
1067
|
+
if (!targetBase) continue;
|
|
1068
|
+
for (const item of selectedItems) {
|
|
1069
|
+
const targetPath = path7.join(targetBase, item);
|
|
1070
|
+
if (await fs6.pathExists(targetPath)) {
|
|
1071
|
+
sDel.message(`Removing ${pc4.bold(item)} from ${pc4.cyan(platform)}...`);
|
|
1072
|
+
try {
|
|
1073
|
+
await fs6.remove(targetPath);
|
|
1074
|
+
removedCount++;
|
|
1075
|
+
} catch (err) {
|
|
1076
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1077
|
+
errors.push(`${item} (${platform}): ${msg}`);
|
|
1078
|
+
}
|
|
1079
|
+
} else {
|
|
1080
|
+
notFoundCount++;
|
|
659
1081
|
}
|
|
660
|
-
} else {
|
|
661
|
-
notFoundCount++;
|
|
662
1082
|
}
|
|
663
1083
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
)
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
1084
|
+
if (errors.length > 0) {
|
|
1085
|
+
sDel.stop(
|
|
1086
|
+
pc4.yellow(
|
|
1087
|
+
`Completed with errors. Removed: ${removedCount}, Not Found: ${notFoundCount}, Errors: ${errors.length}`
|
|
1088
|
+
)
|
|
1089
|
+
);
|
|
1090
|
+
console.error(pc4.red("\nErrors encountered:"));
|
|
1091
|
+
for (const e of errors) {
|
|
1092
|
+
console.error(pc4.red(`- ${e}`));
|
|
1093
|
+
}
|
|
1094
|
+
} else {
|
|
1095
|
+
sDel.stop(
|
|
1096
|
+
pc4.green(
|
|
1097
|
+
`Successfully removed ${removedCount} items. (${notFoundCount} not found)`
|
|
1098
|
+
)
|
|
1099
|
+
);
|
|
674
1100
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
);
|
|
1101
|
+
if (isSingleShot) break;
|
|
1102
|
+
currentType = void 0;
|
|
1103
|
+
}
|
|
1104
|
+
if (!options?.skipIntro) {
|
|
1105
|
+
outro4("Done!");
|
|
681
1106
|
}
|
|
682
|
-
outro3("Done!");
|
|
683
1107
|
}
|
|
684
1108
|
|
|
685
1109
|
// bin/cli.ts
|
|
686
1110
|
var program = new Command();
|
|
687
1111
|
program.name("ai-agent").description("CLI to manage AI agents, skills, and workflows").version("1.4.0");
|
|
1112
|
+
async function interactiveMain() {
|
|
1113
|
+
intro5(pc5.bgCyan(pc5.black(" AI Manager ")));
|
|
1114
|
+
while (true) {
|
|
1115
|
+
const action = await select5({
|
|
1116
|
+
message: "What would you like to do?",
|
|
1117
|
+
options: [
|
|
1118
|
+
{ label: "Add Item (Install)", value: "add" },
|
|
1119
|
+
{ label: "List Items", value: "list" },
|
|
1120
|
+
{ label: "Import from GitHub", value: "import" },
|
|
1121
|
+
{ label: "Remove Items", value: "remove" },
|
|
1122
|
+
{ label: "Exit", value: "exit" }
|
|
1123
|
+
]
|
|
1124
|
+
});
|
|
1125
|
+
if (isCancel5(action) || action === "exit") {
|
|
1126
|
+
break;
|
|
1127
|
+
}
|
|
1128
|
+
switch (action) {
|
|
1129
|
+
case "add":
|
|
1130
|
+
await add(void 0, void 0, { skipIntro: true });
|
|
1131
|
+
break;
|
|
1132
|
+
case "list":
|
|
1133
|
+
await list(void 0, { skipIntro: true });
|
|
1134
|
+
break;
|
|
1135
|
+
case "import":
|
|
1136
|
+
await importItem(void 0, void 0, { skipIntro: true });
|
|
1137
|
+
break;
|
|
1138
|
+
case "remove":
|
|
1139
|
+
await remove(void 0, { skipIntro: true });
|
|
1140
|
+
break;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
outro5("Goodbye!");
|
|
1144
|
+
}
|
|
688
1145
|
program.command("list [type]").description("List available items (skills, agents, workflows)").option("-l, --local", "List installed items locally").action(list);
|
|
689
|
-
program.command("add
|
|
1146
|
+
program.command("add [type] [url]").description(
|
|
690
1147
|
"Add an item (skill, agent, workflow) to platforms (Interactive or from GitHub URL)"
|
|
691
1148
|
).action(add);
|
|
692
|
-
program.command("import
|
|
693
|
-
|
|
694
|
-
|
|
1149
|
+
program.command("import [type] [url]").description("Import an item from GitHub to the repo").action(async (type, url) => {
|
|
1150
|
+
await importItem(type || "", url || "");
|
|
1151
|
+
});
|
|
1152
|
+
program.command("remove [type]").description("Remove locally installed items (skills, agents, workflows)").action(async (type) => {
|
|
1153
|
+
await remove(type || "");
|
|
1154
|
+
});
|
|
1155
|
+
if (process.argv.length <= 2) {
|
|
1156
|
+
await interactiveMain();
|
|
1157
|
+
} else {
|
|
1158
|
+
await program.parseAsync();
|
|
1159
|
+
}
|