@kurokeita/add-skill 1.9.0 → 1.10.1
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 +3 -0
- package/dist/bin/cli.js +220 -70
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -87,11 +87,14 @@ pnpm dev import https://github.com/owner/repo/tree/main/skills/skill-name
|
|
|
87
87
|
| Platform | Agents Path | Skills Path | Workflows Path |
|
|
88
88
|
| :--- | :--- | :--- | :--- |
|
|
89
89
|
| Antigravity | *Not Supported* | `~/.gemini/antigravity/global_skills` | `~/.gemini/antigravity/global_workflows` |
|
|
90
|
+
| Codex | `~/.codex/skills` | `~/.codex/skills` | `~/.codex/skills` |
|
|
90
91
|
| Gemini CLI | `~/.gemini/agents` | `~/.gemini/skills` | `~/.gemini/commands` |
|
|
91
92
|
| GitHub Copilot | `~/.copilot/agents` | `~/.copilot/skills` | `~/.copilot/prompts` |
|
|
92
93
|
| Windsurf | *Not Supported* | `~/.codeium/windsurf/skills` | `~/.codeium/windsurf/global_workflows` |
|
|
93
94
|
<!-- SUPPORTED_AGENTS_END -->
|
|
94
95
|
|
|
96
|
+
For Codex, this package installs skills directly and converts agents and workflows into Codex skill packages under `~/.codex/skills`.
|
|
97
|
+
|
|
95
98
|
## Development
|
|
96
99
|
|
|
97
100
|
1. **Clone the repository:**
|
package/dist/bin/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import pc5 from "picocolors";
|
|
|
7
7
|
|
|
8
8
|
// src/commands/add.ts
|
|
9
9
|
import os3 from "node:os";
|
|
10
|
-
import
|
|
10
|
+
import path5 from "node:path";
|
|
11
11
|
import {
|
|
12
12
|
cancel,
|
|
13
13
|
confirm,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
select,
|
|
20
20
|
spinner
|
|
21
21
|
} from "@clack/prompts";
|
|
22
|
-
import
|
|
22
|
+
import fs4 from "fs-extra";
|
|
23
23
|
import pc from "picocolors";
|
|
24
24
|
|
|
25
25
|
// src/utils/github.ts
|
|
@@ -98,30 +98,47 @@ async function fetchSkillFromGitHub(url) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// src/utils/paths.ts
|
|
101
|
+
import fs2 from "node:fs";
|
|
101
102
|
import os2 from "node:os";
|
|
102
103
|
import path2 from "node:path";
|
|
103
104
|
import { fileURLToPath } from "node:url";
|
|
104
105
|
var __filename = fileURLToPath(import.meta.url);
|
|
105
106
|
var __dirname = path2.dirname(__filename);
|
|
106
|
-
|
|
107
|
+
function hasBundledAssets(rootDir) {
|
|
108
|
+
return ["skills", "agents", "workflows"].some(
|
|
109
|
+
(dir) => fs2.existsSync(path2.join(rootDir, dir))
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
function resolveProjectRoot(moduleDir) {
|
|
113
|
+
const sourceRoot = path2.resolve(moduleDir, "../..");
|
|
114
|
+
if (hasBundledAssets(sourceRoot)) {
|
|
115
|
+
return sourceRoot;
|
|
116
|
+
}
|
|
117
|
+
return path2.resolve(moduleDir, "..");
|
|
118
|
+
}
|
|
119
|
+
var PROJECT_ROOT = resolveProjectRoot(__dirname);
|
|
107
120
|
var PLATFORM_LABELS = {
|
|
108
121
|
antigravity: "Antigravity",
|
|
122
|
+
codex: "Codex",
|
|
109
123
|
gemini: "Gemini CLI",
|
|
110
124
|
copilot: "GitHub Copilot",
|
|
111
125
|
windsurf: "Windsurf"
|
|
112
126
|
};
|
|
113
127
|
var PLATFORM_PATHS_SKILLS = {
|
|
114
128
|
antigravity: path2.join(os2.homedir(), ".gemini/antigravity/global_skills"),
|
|
129
|
+
codex: path2.join(os2.homedir(), ".codex/skills"),
|
|
115
130
|
copilot: path2.join(os2.homedir(), ".copilot/skills"),
|
|
116
131
|
gemini: path2.join(os2.homedir(), ".gemini/skills"),
|
|
117
132
|
windsurf: path2.join(os2.homedir(), ".codeium/windsurf/skills")
|
|
118
133
|
};
|
|
119
134
|
var PLATFORM_PATHS_AGENTS = {
|
|
135
|
+
codex: path2.join(os2.homedir(), ".codex/skills"),
|
|
120
136
|
copilot: path2.join(os2.homedir(), ".copilot/agents"),
|
|
121
137
|
gemini: path2.join(os2.homedir(), ".gemini/agents")
|
|
122
138
|
};
|
|
123
139
|
var PLATFORM_PATHS_WORKFLOWS = {
|
|
124
140
|
antigravity: path2.join(os2.homedir(), ".gemini/antigravity/global_workflows"),
|
|
141
|
+
codex: path2.join(os2.homedir(), ".codex/skills"),
|
|
125
142
|
copilot: path2.join(os2.homedir(), ".copilot/prompts"),
|
|
126
143
|
gemini: path2.join(os2.homedir(), ".gemini/commands"),
|
|
127
144
|
windsurf: path2.join(os2.homedir(), ".codeium/windsurf/global_workflows")
|
|
@@ -153,8 +170,118 @@ var AntigravityHandler = class {
|
|
|
153
170
|
}
|
|
154
171
|
};
|
|
155
172
|
|
|
156
|
-
// src/utils/platforms/
|
|
173
|
+
// src/utils/platforms/codex.ts
|
|
174
|
+
import path3 from "node:path";
|
|
175
|
+
import fs3 from "fs-extra";
|
|
157
176
|
import yaml from "js-yaml";
|
|
177
|
+
var frontmatterRegex = /^---\n([\s\S]*?)\n---/;
|
|
178
|
+
var CODEX_TYPE_FIELD = "x-ai-agents-type";
|
|
179
|
+
function extractTitle(body) {
|
|
180
|
+
const match = body.match(/^#\s+(.+)$/m);
|
|
181
|
+
return match?.[1]?.trim();
|
|
182
|
+
}
|
|
183
|
+
function buildDescription(type, itemName, title) {
|
|
184
|
+
const subject = title || itemName;
|
|
185
|
+
if (type === "agent") {
|
|
186
|
+
return `This skill should be used when Codex needs to follow the ${subject} agent instructions.`;
|
|
187
|
+
}
|
|
188
|
+
if (type === "workflow") {
|
|
189
|
+
return `This skill should be used when Codex needs to follow the ${subject} workflow instructions.`;
|
|
190
|
+
}
|
|
191
|
+
return `This skill should be used when Codex needs the ${subject} skill.`;
|
|
192
|
+
}
|
|
193
|
+
function toYamlFrontmatter(name, description, extraFields = {}) {
|
|
194
|
+
const normalizedName = name.replace(/\n+/g, " ").trim();
|
|
195
|
+
const normalizedDescription = description.replace(/\n+/g, " ").trim();
|
|
196
|
+
return yaml.dump(
|
|
197
|
+
{
|
|
198
|
+
name: normalizedName,
|
|
199
|
+
description: normalizedDescription,
|
|
200
|
+
...extraFields
|
|
201
|
+
},
|
|
202
|
+
{ lineWidth: -1 }
|
|
203
|
+
).trimEnd();
|
|
204
|
+
}
|
|
205
|
+
function parseCodexType(content) {
|
|
206
|
+
const match = content.match(frontmatterRegex);
|
|
207
|
+
if (!match) return "skill";
|
|
208
|
+
try {
|
|
209
|
+
const parsed = yaml.load(match[1]);
|
|
210
|
+
if (parsed && typeof parsed === "object") {
|
|
211
|
+
const type = parsed[CODEX_TYPE_FIELD];
|
|
212
|
+
if (type === "agent" || type === "workflow" || type === "skill") {
|
|
213
|
+
return type;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
} catch {
|
|
217
|
+
return "skill";
|
|
218
|
+
}
|
|
219
|
+
return "skill";
|
|
220
|
+
}
|
|
221
|
+
async function codexEntryMatchesType(basePath, entry, requestedType) {
|
|
222
|
+
let targetPath = null;
|
|
223
|
+
if (entry.isDirectory()) {
|
|
224
|
+
targetPath = path3.join(basePath, entry.name, "SKILL.md");
|
|
225
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
226
|
+
targetPath = path3.join(basePath, entry.name);
|
|
227
|
+
}
|
|
228
|
+
if (!targetPath) return false;
|
|
229
|
+
try {
|
|
230
|
+
const content = await fs3.readFile(targetPath, "utf-8");
|
|
231
|
+
return parseCodexType(content) === requestedType;
|
|
232
|
+
} catch {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
var CodexHandler = class {
|
|
237
|
+
platform = "codex";
|
|
238
|
+
getTargetFileName(itemName, type) {
|
|
239
|
+
if (type === "agent" || type === "workflow") {
|
|
240
|
+
return path3.join(path3.parse(itemName).name, "SKILL.md");
|
|
241
|
+
}
|
|
242
|
+
return itemName;
|
|
243
|
+
}
|
|
244
|
+
transform(content, type, itemName) {
|
|
245
|
+
if (type === "skill") return content;
|
|
246
|
+
if (type !== "agent" && type !== "workflow") return content;
|
|
247
|
+
let name = itemName;
|
|
248
|
+
let description = "";
|
|
249
|
+
let body = content.trim();
|
|
250
|
+
const match = content.match(frontmatterRegex);
|
|
251
|
+
if (match) {
|
|
252
|
+
body = content.replace(frontmatterRegex, "").trim();
|
|
253
|
+
try {
|
|
254
|
+
const parsed = yaml.load(match[1]);
|
|
255
|
+
if (parsed && typeof parsed === "object") {
|
|
256
|
+
const frontmatter = parsed;
|
|
257
|
+
if (typeof frontmatter.name === "string" && frontmatter.name.trim()) {
|
|
258
|
+
name = frontmatter.name.trim();
|
|
259
|
+
}
|
|
260
|
+
if (typeof frontmatter.description === "string" && frontmatter.description.trim()) {
|
|
261
|
+
description = frontmatter.description.trim();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
} catch {
|
|
265
|
+
name = itemName;
|
|
266
|
+
description = "";
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (!description) {
|
|
270
|
+
description = buildDescription(type, itemName, extractTitle(body));
|
|
271
|
+
}
|
|
272
|
+
const yamlStr = toYamlFrontmatter(name, description, {
|
|
273
|
+
[CODEX_TYPE_FIELD]: type
|
|
274
|
+
});
|
|
275
|
+
return `---
|
|
276
|
+
${yamlStr}
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
${body}`;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// src/utils/platforms/copilot.ts
|
|
284
|
+
import yaml2 from "js-yaml";
|
|
158
285
|
var CopilotHandler = class {
|
|
159
286
|
platform = "copilot";
|
|
160
287
|
getTargetFileName(itemName, type) {
|
|
@@ -165,8 +292,8 @@ var CopilotHandler = class {
|
|
|
165
292
|
}
|
|
166
293
|
transform(content, type, itemName) {
|
|
167
294
|
if (type !== "agent") return content;
|
|
168
|
-
const
|
|
169
|
-
const match = content.match(
|
|
295
|
+
const frontmatterRegex2 = /^---\n([\s\S]*?)\n---/;
|
|
296
|
+
const match = content.match(frontmatterRegex2);
|
|
170
297
|
let frontmatter = {
|
|
171
298
|
name: itemName,
|
|
172
299
|
description: "",
|
|
@@ -176,7 +303,7 @@ var CopilotHandler = class {
|
|
|
176
303
|
let body = content;
|
|
177
304
|
if (match) {
|
|
178
305
|
try {
|
|
179
|
-
const parsed =
|
|
306
|
+
const parsed = yaml2.load(match[1]);
|
|
180
307
|
frontmatter = { ...frontmatter, ...parsed };
|
|
181
308
|
frontmatter.target = "github-copilot";
|
|
182
309
|
frontmatter.tools = [
|
|
@@ -186,11 +313,11 @@ var CopilotHandler = class {
|
|
|
186
313
|
"grep_search",
|
|
187
314
|
"githubRepo"
|
|
188
315
|
];
|
|
189
|
-
body = content.replace(
|
|
316
|
+
body = content.replace(frontmatterRegex2, "").trim();
|
|
190
317
|
} catch (_) {
|
|
191
318
|
}
|
|
192
319
|
}
|
|
193
|
-
const yamlStr =
|
|
320
|
+
const yamlStr = yaml2.dump(frontmatter);
|
|
194
321
|
return `---
|
|
195
322
|
${yamlStr}---
|
|
196
323
|
|
|
@@ -212,7 +339,7 @@ var DefaultHandler = class {
|
|
|
212
339
|
};
|
|
213
340
|
|
|
214
341
|
// src/utils/platforms/gemini.ts
|
|
215
|
-
import
|
|
342
|
+
import yaml3 from "js-yaml";
|
|
216
343
|
|
|
217
344
|
// src/utils/toml.ts
|
|
218
345
|
function convertToGeminiCommandTOML(markdownContent) {
|
|
@@ -255,8 +382,8 @@ var GeminiHandler = class {
|
|
|
255
382
|
return convertToGeminiCommandTOML(content);
|
|
256
383
|
}
|
|
257
384
|
if (type === "agent") {
|
|
258
|
-
const
|
|
259
|
-
const match = content.match(
|
|
385
|
+
const frontmatterRegex2 = /^---\n([\s\S]*?)\n---/;
|
|
386
|
+
const match = content.match(frontmatterRegex2);
|
|
260
387
|
let frontmatter = {
|
|
261
388
|
name: itemName,
|
|
262
389
|
description: "",
|
|
@@ -278,7 +405,7 @@ var GeminiHandler = class {
|
|
|
278
405
|
let body = content;
|
|
279
406
|
if (match) {
|
|
280
407
|
try {
|
|
281
|
-
const parsed =
|
|
408
|
+
const parsed = yaml3.load(match[1]);
|
|
282
409
|
frontmatter = { ...frontmatter, ...parsed };
|
|
283
410
|
frontmatter.tools = [
|
|
284
411
|
"list_directory",
|
|
@@ -295,11 +422,11 @@ var GeminiHandler = class {
|
|
|
295
422
|
"activate_skill"
|
|
296
423
|
];
|
|
297
424
|
delete frontmatter.skills;
|
|
298
|
-
body = content.replace(
|
|
425
|
+
body = content.replace(frontmatterRegex2, "").trim();
|
|
299
426
|
} catch (_) {
|
|
300
427
|
}
|
|
301
428
|
}
|
|
302
|
-
const yamlStr =
|
|
429
|
+
const yamlStr = yaml3.dump(frontmatter);
|
|
303
430
|
return `---
|
|
304
431
|
${yamlStr}---
|
|
305
432
|
|
|
@@ -310,12 +437,12 @@ ${body}`;
|
|
|
310
437
|
};
|
|
311
438
|
|
|
312
439
|
// src/utils/platforms/windsurf.ts
|
|
313
|
-
import
|
|
440
|
+
import path4 from "node:path";
|
|
314
441
|
var WindsurfHandler = class {
|
|
315
442
|
platform = "windsurf";
|
|
316
443
|
getTargetFileName(itemName, type) {
|
|
317
444
|
if (type === "agent") {
|
|
318
|
-
return
|
|
445
|
+
return path4.join(path4.parse(itemName).name, "AGENTS.md");
|
|
319
446
|
}
|
|
320
447
|
return itemName;
|
|
321
448
|
}
|
|
@@ -330,6 +457,7 @@ function registerHandler(handler) {
|
|
|
330
457
|
handlers.set(handler.platform, handler);
|
|
331
458
|
}
|
|
332
459
|
registerHandler(new AntigravityHandler());
|
|
460
|
+
registerHandler(new CodexHandler());
|
|
333
461
|
registerHandler(new CopilotHandler());
|
|
334
462
|
registerHandler(new GeminiHandler());
|
|
335
463
|
registerHandler(new WindsurfHandler());
|
|
@@ -347,15 +475,15 @@ function getPlatformOptions(type) {
|
|
|
347
475
|
}));
|
|
348
476
|
}
|
|
349
477
|
async function installItem(itemName, overwrite, sourcePath, targetBaseDir) {
|
|
350
|
-
const targetPath =
|
|
351
|
-
if (!await
|
|
478
|
+
const targetPath = path5.join(targetBaseDir, itemName);
|
|
479
|
+
if (!await fs4.pathExists(sourcePath)) {
|
|
352
480
|
throw new Error(`Item '${itemName}' not found at ${sourcePath}`);
|
|
353
481
|
}
|
|
354
|
-
if (!overwrite && await
|
|
482
|
+
if (!overwrite && await fs4.pathExists(targetPath)) {
|
|
355
483
|
return false;
|
|
356
484
|
}
|
|
357
|
-
await
|
|
358
|
-
await
|
|
485
|
+
await fs4.ensureDir(targetBaseDir);
|
|
486
|
+
await fs4.copy(sourcePath, targetPath, { overwrite });
|
|
359
487
|
return true;
|
|
360
488
|
}
|
|
361
489
|
async function add(type, url, options) {
|
|
@@ -403,12 +531,12 @@ async function add(type, url, options) {
|
|
|
403
531
|
}
|
|
404
532
|
} else {
|
|
405
533
|
const sourceDir = TYPE_DIRS[normalizedType];
|
|
406
|
-
if (!await
|
|
534
|
+
if (!await fs4.pathExists(sourceDir)) {
|
|
407
535
|
cancel(`${normalizedType} directory not found!`);
|
|
408
536
|
process.exit(1);
|
|
409
537
|
return;
|
|
410
538
|
}
|
|
411
|
-
const entries = await
|
|
539
|
+
const entries = await fs4.readdir(sourceDir, { withFileTypes: true });
|
|
412
540
|
const availableItems = entries.filter(
|
|
413
541
|
(entry) => entry.isDirectory() || (normalizedType === "workflow" || normalizedType === "agent") && entry.isFile() && entry.name.endsWith(".md")
|
|
414
542
|
).map((entry) => ({ label: entry.name, value: entry.name })).sort((a, b) => a.label.localeCompare(b.label));
|
|
@@ -443,7 +571,7 @@ async function add(type, url, options) {
|
|
|
443
571
|
required: true
|
|
444
572
|
});
|
|
445
573
|
if (isCancel(platforms)) {
|
|
446
|
-
if (tempDir) await
|
|
574
|
+
if (tempDir) await fs4.remove(tempDir);
|
|
447
575
|
if (isSingleShot) break;
|
|
448
576
|
currentType = void 0;
|
|
449
577
|
continue;
|
|
@@ -453,9 +581,11 @@ async function add(type, url, options) {
|
|
|
453
581
|
for (const platform of selectedPlatforms) {
|
|
454
582
|
const targetBase = supportedPaths[platform];
|
|
455
583
|
if (!targetBase) continue;
|
|
584
|
+
const handler = getHandler(platform);
|
|
456
585
|
for (const item of selectedItems) {
|
|
457
|
-
const
|
|
458
|
-
|
|
586
|
+
const targetItemName = handler.getTargetFileName(item, normalizedType);
|
|
587
|
+
const targetPath = path5.join(targetBase, targetItemName);
|
|
588
|
+
if (await fs4.pathExists(targetPath)) {
|
|
459
589
|
existingItems.push({ item, platform });
|
|
460
590
|
}
|
|
461
591
|
}
|
|
@@ -472,7 +602,7 @@ async function add(type, url, options) {
|
|
|
472
602
|
initialValue: false
|
|
473
603
|
});
|
|
474
604
|
if (isCancel(shouldOverwrite)) {
|
|
475
|
-
if (tempDir) await
|
|
605
|
+
if (tempDir) await fs4.remove(tempDir);
|
|
476
606
|
if (isSingleShot) break;
|
|
477
607
|
currentType = void 0;
|
|
478
608
|
continue;
|
|
@@ -497,15 +627,15 @@ async function add(type, url, options) {
|
|
|
497
627
|
try {
|
|
498
628
|
let currentSourcePath;
|
|
499
629
|
if (url && tempDir) {
|
|
500
|
-
const potentialFile =
|
|
501
|
-
if (await
|
|
630
|
+
const potentialFile = path5.join(tempDir, item);
|
|
631
|
+
if (await fs4.pathExists(potentialFile) && (await fs4.stat(potentialFile)).isFile()) {
|
|
502
632
|
currentSourcePath = potentialFile;
|
|
503
633
|
} else {
|
|
504
634
|
currentSourcePath = tempDir;
|
|
505
635
|
}
|
|
506
636
|
} else {
|
|
507
|
-
const src =
|
|
508
|
-
if ((await
|
|
637
|
+
const src = path5.join(TYPE_DIRS[normalizedType], item);
|
|
638
|
+
if ((await fs4.stat(src)).isFile()) {
|
|
509
639
|
currentSourcePath = src;
|
|
510
640
|
} else {
|
|
511
641
|
currentSourcePath = src;
|
|
@@ -517,18 +647,18 @@ async function add(type, url, options) {
|
|
|
517
647
|
normalizedType
|
|
518
648
|
);
|
|
519
649
|
if ((normalizedType === "agent" || normalizedType === "workflow") && currentSourcePath.endsWith(".md")) {
|
|
520
|
-
const targetPath =
|
|
521
|
-
if (!overwrite && await
|
|
650
|
+
const targetPath = path5.join(targetBase, targetItemName);
|
|
651
|
+
if (!overwrite && await fs4.pathExists(targetPath)) {
|
|
522
652
|
skippedCount++;
|
|
523
653
|
} else {
|
|
524
|
-
let content = await
|
|
654
|
+
let content = await fs4.readFile(currentSourcePath, "utf-8");
|
|
525
655
|
content = handler.transform(
|
|
526
656
|
content,
|
|
527
657
|
normalizedType,
|
|
528
|
-
|
|
658
|
+
path5.parse(item).name
|
|
529
659
|
);
|
|
530
|
-
await
|
|
531
|
-
await
|
|
660
|
+
await fs4.ensureDir(path5.dirname(targetPath));
|
|
661
|
+
await fs4.writeFile(targetPath, content);
|
|
532
662
|
installedCount++;
|
|
533
663
|
}
|
|
534
664
|
continue;
|
|
@@ -551,7 +681,7 @@ async function add(type, url, options) {
|
|
|
551
681
|
}
|
|
552
682
|
}
|
|
553
683
|
if (tempDir) {
|
|
554
|
-
await
|
|
684
|
+
await fs4.remove(tempDir);
|
|
555
685
|
}
|
|
556
686
|
if (errors.length > 0) {
|
|
557
687
|
s.stop(
|
|
@@ -571,7 +701,7 @@ async function add(type, url, options) {
|
|
|
571
701
|
);
|
|
572
702
|
}
|
|
573
703
|
} catch (error) {
|
|
574
|
-
if (tempDir) await
|
|
704
|
+
if (tempDir) await fs4.remove(tempDir);
|
|
575
705
|
cancel(`An error occurred: ${error}`);
|
|
576
706
|
process.exit(1);
|
|
577
707
|
return;
|
|
@@ -585,7 +715,7 @@ async function add(type, url, options) {
|
|
|
585
715
|
}
|
|
586
716
|
|
|
587
717
|
// src/commands/import.ts
|
|
588
|
-
import
|
|
718
|
+
import path6 from "node:path";
|
|
589
719
|
import {
|
|
590
720
|
cancel as cancel2,
|
|
591
721
|
confirm as confirm2,
|
|
@@ -596,7 +726,7 @@ import {
|
|
|
596
726
|
spinner as spinner2,
|
|
597
727
|
text
|
|
598
728
|
} from "@clack/prompts";
|
|
599
|
-
import
|
|
729
|
+
import fs5 from "fs-extra";
|
|
600
730
|
import pc2 from "picocolors";
|
|
601
731
|
async function importItem(type, url, options) {
|
|
602
732
|
if (!options?.skipIntro) {
|
|
@@ -654,12 +784,12 @@ async function importItem(type, url, options) {
|
|
|
654
784
|
throw e;
|
|
655
785
|
}
|
|
656
786
|
const shouldFlatten = (normalizedType === "agent" || normalizedType === "workflow") && !isFile;
|
|
657
|
-
const targetPath = shouldFlatten ? targetBaseDir :
|
|
787
|
+
const targetPath = shouldFlatten ? targetBaseDir : path6.join(targetBaseDir, itemName);
|
|
658
788
|
if (shouldFlatten) {
|
|
659
|
-
const srcFiles = await
|
|
789
|
+
const srcFiles = await fs5.readdir(tempDir);
|
|
660
790
|
const conflicts = [];
|
|
661
791
|
for (const file of srcFiles) {
|
|
662
|
-
if (await
|
|
792
|
+
if (await fs5.pathExists(path6.join(targetBaseDir, file))) {
|
|
663
793
|
conflicts.push(file);
|
|
664
794
|
}
|
|
665
795
|
}
|
|
@@ -673,40 +803,40 @@ async function importItem(type, url, options) {
|
|
|
673
803
|
initialValue: false
|
|
674
804
|
});
|
|
675
805
|
if (isCancel2(shouldOverwrite) || !shouldOverwrite) {
|
|
676
|
-
await
|
|
806
|
+
await fs5.remove(tempDir);
|
|
677
807
|
return;
|
|
678
808
|
}
|
|
679
809
|
}
|
|
680
|
-
} else if (await
|
|
810
|
+
} else if (await fs5.pathExists(targetPath)) {
|
|
681
811
|
const shouldOverwrite = await confirm2({
|
|
682
812
|
message: `${normalizedType} '${itemName}' already exists in the repo. Overwrite?`,
|
|
683
813
|
initialValue: false
|
|
684
814
|
});
|
|
685
815
|
if (isCancel2(shouldOverwrite) || !shouldOverwrite) {
|
|
686
|
-
await
|
|
816
|
+
await fs5.remove(tempDir);
|
|
687
817
|
return;
|
|
688
818
|
}
|
|
689
819
|
}
|
|
690
820
|
s.start(`Importing ${itemName} to ${targetBaseDir}...`);
|
|
691
|
-
await
|
|
821
|
+
await fs5.ensureDir(targetBaseDir);
|
|
692
822
|
if (isFile) {
|
|
693
|
-
await
|
|
823
|
+
await fs5.copy(path6.join(tempDir, itemName), targetPath, {
|
|
694
824
|
overwrite: true
|
|
695
825
|
});
|
|
696
826
|
} else {
|
|
697
827
|
if (shouldFlatten) {
|
|
698
|
-
await
|
|
828
|
+
await fs5.copy(tempDir, targetBaseDir, { overwrite: true });
|
|
699
829
|
} else {
|
|
700
|
-
await
|
|
830
|
+
await fs5.copy(tempDir, targetPath, { overwrite: true });
|
|
701
831
|
}
|
|
702
832
|
}
|
|
703
833
|
s.stop(pc2.green(`Successfully imported ${itemName}!`));
|
|
704
|
-
await
|
|
834
|
+
await fs5.remove(tempDir);
|
|
705
835
|
if (!options?.skipIntro) {
|
|
706
836
|
outro2(`${normalizedType} available at: ${targetPath}`);
|
|
707
837
|
}
|
|
708
838
|
} catch (error) {
|
|
709
|
-
if (tempDir) await
|
|
839
|
+
if (tempDir) await fs5.remove(tempDir);
|
|
710
840
|
cancel2(`An error occurred: ${error}`);
|
|
711
841
|
process.exit(1);
|
|
712
842
|
return;
|
|
@@ -715,7 +845,7 @@ async function importItem(type, url, options) {
|
|
|
715
845
|
|
|
716
846
|
// src/commands/list.ts
|
|
717
847
|
import { intro as intro3, isCancel as isCancel3, outro as outro3, select as select3 } from "@clack/prompts";
|
|
718
|
-
import
|
|
848
|
+
import fs6 from "fs-extra";
|
|
719
849
|
import pc3 from "picocolors";
|
|
720
850
|
async function performListing(type, local) {
|
|
721
851
|
const normalizedType = type.toLowerCase().endsWith("s") ? type.slice(0, -1) : type.toLowerCase();
|
|
@@ -725,13 +855,23 @@ async function performListing(type, local) {
|
|
|
725
855
|
let foundAny = false;
|
|
726
856
|
for (const [platform, pathStr] of Object.entries(targetPaths)) {
|
|
727
857
|
const fullPath = pathStr;
|
|
728
|
-
if (await
|
|
729
|
-
const entries = await
|
|
858
|
+
if (await fs6.pathExists(fullPath)) {
|
|
859
|
+
const entries = await fs6.readdir(fullPath, {
|
|
730
860
|
withFileTypes: true
|
|
731
861
|
});
|
|
732
|
-
const items =
|
|
733
|
-
|
|
734
|
-
|
|
862
|
+
const items = [];
|
|
863
|
+
for (const entry of entries) {
|
|
864
|
+
if (platform === "codex") {
|
|
865
|
+
if (await codexEntryMatchesType(fullPath, entry, normalizedType)) {
|
|
866
|
+
items.push(entry.name);
|
|
867
|
+
}
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
if (entry.isDirectory() || entry.isFile() && entry.name.endsWith(".md")) {
|
|
871
|
+
items.push(entry.name);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
items.sort();
|
|
735
875
|
if (items.length > 0) {
|
|
736
876
|
foundAny = true;
|
|
737
877
|
console.log(pc3.cyan(` ${platform}:`));
|
|
@@ -755,13 +895,13 @@ async function performListing(type, local) {
|
|
|
755
895
|
return;
|
|
756
896
|
}
|
|
757
897
|
const dirPath = TYPE_DIRS[normalizedType];
|
|
758
|
-
if (!await
|
|
898
|
+
if (!await fs6.pathExists(dirPath)) {
|
|
759
899
|
console.log(
|
|
760
900
|
pc3.yellow(`No ${normalizedType} directory found at: ${dirPath}`)
|
|
761
901
|
);
|
|
762
902
|
return;
|
|
763
903
|
}
|
|
764
|
-
const entries = await
|
|
904
|
+
const entries = await fs6.readdir(dirPath, { withFileTypes: true });
|
|
765
905
|
const items = entries.filter(
|
|
766
906
|
(entry) => entry.isDirectory() || normalizedType !== "skill" && entry.isFile() && entry.name.endsWith(".md")
|
|
767
907
|
).map((entry) => entry.name).sort();
|
|
@@ -825,7 +965,7 @@ async function list(type, options) {
|
|
|
825
965
|
}
|
|
826
966
|
|
|
827
967
|
// src/commands/remove.ts
|
|
828
|
-
import
|
|
968
|
+
import path7 from "node:path";
|
|
829
969
|
import {
|
|
830
970
|
confirm as confirm3,
|
|
831
971
|
intro as intro4,
|
|
@@ -836,7 +976,7 @@ import {
|
|
|
836
976
|
select as select4,
|
|
837
977
|
spinner as spinner3
|
|
838
978
|
} from "@clack/prompts";
|
|
839
|
-
import
|
|
979
|
+
import fs7 from "fs-extra";
|
|
840
980
|
import pc4 from "picocolors";
|
|
841
981
|
async function remove(type, options) {
|
|
842
982
|
if (!options?.skipIntro) {
|
|
@@ -865,12 +1005,22 @@ async function remove(type, options) {
|
|
|
865
1005
|
const uniqueItems = /* @__PURE__ */ new Set();
|
|
866
1006
|
const s = spinner3();
|
|
867
1007
|
s.start("Scanning for installed items...");
|
|
868
|
-
for (const pathStr of Object.
|
|
869
|
-
if (await
|
|
870
|
-
const entries = await
|
|
1008
|
+
for (const [platform, pathStr] of Object.entries(targetPaths)) {
|
|
1009
|
+
if (await fs7.pathExists(pathStr)) {
|
|
1010
|
+
const entries = await fs7.readdir(pathStr, {
|
|
871
1011
|
withFileTypes: true
|
|
872
1012
|
});
|
|
873
1013
|
for (const entry of entries) {
|
|
1014
|
+
if (platform === "codex") {
|
|
1015
|
+
if (await codexEntryMatchesType(
|
|
1016
|
+
pathStr,
|
|
1017
|
+
entry,
|
|
1018
|
+
normalizedType
|
|
1019
|
+
)) {
|
|
1020
|
+
uniqueItems.add(entry.name);
|
|
1021
|
+
}
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
874
1024
|
if (entry.isDirectory() || normalizedType === "workflow" && entry.isFile() && entry.name.endsWith(".md") || normalizedType === "agent" && entry.isFile() && entry.name.endsWith(".md")) {
|
|
875
1025
|
uniqueItems.add(entry.name);
|
|
876
1026
|
}
|
|
@@ -929,11 +1079,11 @@ async function remove(type, options) {
|
|
|
929
1079
|
const targetBase = targetPaths[platform];
|
|
930
1080
|
if (!targetBase) continue;
|
|
931
1081
|
for (const item of selectedItems) {
|
|
932
|
-
const targetPath =
|
|
933
|
-
if (await
|
|
1082
|
+
const targetPath = path7.join(targetBase, item);
|
|
1083
|
+
if (await fs7.pathExists(targetPath)) {
|
|
934
1084
|
sDel.message(`Removing ${pc4.bold(item)} from ${pc4.cyan(platform)}...`);
|
|
935
1085
|
try {
|
|
936
|
-
await
|
|
1086
|
+
await fs7.remove(targetPath);
|
|
937
1087
|
removedCount++;
|
|
938
1088
|
} catch (err) {
|
|
939
1089
|
const msg = err instanceof Error ? err.message : String(err);
|