cursor-kit-cli 1.1.1 → 1.2.0-beta.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 +36 -0
- package/bin/cursor-new-instance +74 -0
- package/bin/cursor-remove-instance +69 -0
- package/dist/cli.cjs +601 -62
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +601 -62
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +39 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +33 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/templates/commands/docs.md +5 -3
- package/templates/commands/explain.md +5 -3
- package/templates/commands/fix.md +5 -3
- package/templates/commands/implement.md +5 -3
- package/templates/commands/refactor.md +5 -3
- package/templates/commands/review.md +5 -3
- package/templates/commands/test.md +5 -3
- package/templates/manifest.json +11 -8
- package/templates/rules/git.mdc +0 -2
- package/templates/rules/toc.mdc +17 -9
- package/templates/skills/aesthetic/SKILL.md +121 -0
- package/templates/skills/aesthetic/assets/design-guideline-template.md +163 -0
- package/templates/skills/aesthetic/assets/design-story-template.md +135 -0
- package/templates/skills/aesthetic/references/design-principles.md +62 -0
- package/templates/skills/aesthetic/references/design-resources.md +75 -0
- package/templates/skills/aesthetic/references/micro-interactions.md +53 -0
- package/templates/skills/aesthetic/references/storytelling-design.md +50 -0
- package/templates/skills/backend-development/SKILL.mdc +95 -0
- package/templates/skills/backend-development/references/backend-api-design.md +495 -0
- package/templates/skills/backend-development/references/backend-architecture.md +454 -0
- package/templates/skills/backend-development/references/backend-authentication.md +338 -0
- package/templates/skills/backend-development/references/backend-code-quality.md +659 -0
- package/templates/skills/backend-development/references/backend-debugging.md +904 -0
- package/templates/skills/backend-development/references/backend-devops.md +494 -0
- package/templates/skills/backend-development/references/backend-mindset.md +387 -0
- package/templates/skills/backend-development/references/backend-performance.md +397 -0
- package/templates/skills/backend-development/references/backend-security.md +290 -0
- package/templates/skills/backend-development/references/backend-technologies.md +256 -0
- package/templates/skills/backend-development/references/backend-testing.md +429 -0
- package/templates/skills/frontend-design/SKILL.mdc +41 -0
- package/templates/skills/frontend-design/references/animejs.md +396 -0
- package/templates/skills/frontend-development/SKILL.mdc +399 -0
- package/templates/skills/frontend-development/resources/common-patterns.md +331 -0
- package/templates/skills/frontend-development/resources/complete-examples.md +872 -0
- package/templates/skills/frontend-development/resources/component-patterns.md +502 -0
- package/templates/skills/frontend-development/resources/data-fetching.md +767 -0
- package/templates/skills/frontend-development/resources/file-organization.md +502 -0
- package/templates/skills/frontend-development/resources/loading-and-error-states.md +501 -0
- package/templates/skills/frontend-development/resources/performance.md +406 -0
- package/templates/skills/frontend-development/resources/routing-guide.md +364 -0
- package/templates/skills/frontend-development/resources/styling-guide.md +428 -0
- package/templates/skills/frontend-development/resources/typescript-standards.md +418 -0
- package/templates/skills/problem-solving/SKILL.mdc +96 -0
- package/templates/skills/problem-solving/references/attribution.md +69 -0
- package/templates/skills/problem-solving/references/collision-zone-thinking.md +79 -0
- package/templates/skills/problem-solving/references/inversion-exercise.md +91 -0
- package/templates/skills/problem-solving/references/meta-pattern-recognition.md +87 -0
- package/templates/skills/problem-solving/references/scale-game.md +95 -0
- package/templates/skills/problem-solving/references/simplification-cascades.md +80 -0
- package/templates/skills/problem-solving/references/when-stuck.md +72 -0
- package/templates/skills/research/SKILL.mdc +168 -0
- package/templates/skills/sequential-thinking/.env.example +8 -0
- package/templates/skills/sequential-thinking/README.md +183 -0
- package/templates/skills/sequential-thinking/SKILL.mdc +94 -0
- package/templates/skills/sequential-thinking/package.json +31 -0
- package/templates/skills/sequential-thinking/references/advanced-strategies.md +79 -0
- package/templates/skills/sequential-thinking/references/advanced-techniques.md +76 -0
- package/templates/skills/sequential-thinking/references/core-patterns.md +95 -0
- package/templates/skills/sequential-thinking/references/examples-api.md +88 -0
- package/templates/skills/sequential-thinking/references/examples-architecture.md +94 -0
- package/templates/skills/sequential-thinking/references/examples-debug.md +90 -0
- package/templates/skills/sequential-thinking/scripts/format-thought.js +159 -0
- package/templates/skills/sequential-thinking/scripts/process-thought.js +236 -0
- package/templates/skills/sequential-thinking/tests/format-thought.test.js +133 -0
- package/templates/skills/sequential-thinking/tests/process-thought.test.js +215 -0
- package/templates/skills/ui-styling/LICENSE.txt +202 -0
- package/templates/skills/ui-styling/SKILL.mdc +321 -0
- package/templates/skills/ui-styling/references/canvas-design-system.md +320 -0
- package/templates/skills/ui-styling/references/shadcn-accessibility.md +471 -0
- package/templates/skills/ui-styling/references/shadcn-components.md +424 -0
- package/templates/skills/ui-styling/references/shadcn-theming.md +373 -0
- package/templates/skills/ui-styling/references/tailwind-customization.md +483 -0
- package/templates/skills/ui-styling/references/tailwind-responsive.md +382 -0
- package/templates/skills/ui-styling/references/tailwind-utilities.md +455 -0
- package/templates/rules/frontend-design.mdc +0 -48
- package/templates/rules/performance.mdc +0 -54
- package/templates/rules/react.mdc +0 -58
- package/templates/rules/security.mdc +0 -50
- package/templates/rules/testing.mdc +0 -54
- package/templates/rules/typescript.mdc +0 -36
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { defineCommand as
|
|
4
|
+
import { defineCommand as defineCommand7, runMain } from "citty";
|
|
5
5
|
import { createRequire } from "module";
|
|
6
6
|
|
|
7
7
|
// src/utils/branding.ts
|
|
@@ -27,6 +27,9 @@ function printSuccess(message) {
|
|
|
27
27
|
function printInfo(message) {
|
|
28
28
|
console.log(pc.cyan("\u2139") + pc.dim(" ") + message);
|
|
29
29
|
}
|
|
30
|
+
function printWarning(message) {
|
|
31
|
+
console.log(pc.yellow("\u26A0") + pc.dim(" ") + message);
|
|
32
|
+
}
|
|
30
33
|
function printDivider() {
|
|
31
34
|
console.log(pc.dim("\u2500".repeat(50)));
|
|
32
35
|
}
|
|
@@ -36,8 +39,8 @@ function printVersion(version) {
|
|
|
36
39
|
);
|
|
37
40
|
console.log();
|
|
38
41
|
}
|
|
39
|
-
function highlight(
|
|
40
|
-
return pc.cyan(
|
|
42
|
+
function highlight(text3) {
|
|
43
|
+
return pc.cyan(text3);
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
// src/commands/init.ts
|
|
@@ -47,7 +50,7 @@ import pc2 from "picocolors";
|
|
|
47
50
|
import { join as join3 } from "path";
|
|
48
51
|
|
|
49
52
|
// src/utils/fs.ts
|
|
50
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, rmSync, statSync } from "fs";
|
|
53
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, rmSync, statSync, cpSync } from "fs";
|
|
51
54
|
import { dirname, join, resolve } from "path";
|
|
52
55
|
function ensureDir(path) {
|
|
53
56
|
if (!existsSync(path)) {
|
|
@@ -72,6 +75,9 @@ function removeFile(path) {
|
|
|
72
75
|
rmSync(path, { recursive: true });
|
|
73
76
|
}
|
|
74
77
|
}
|
|
78
|
+
function copyDir(src, dest) {
|
|
79
|
+
cpSync(src, dest, { recursive: true });
|
|
80
|
+
}
|
|
75
81
|
function listFiles(dir, extension) {
|
|
76
82
|
if (!dirExists(dir)) return [];
|
|
77
83
|
const files = readdirSync(dir);
|
|
@@ -80,6 +86,13 @@ function listFiles(dir, extension) {
|
|
|
80
86
|
}
|
|
81
87
|
return files;
|
|
82
88
|
}
|
|
89
|
+
function listDirs(dir) {
|
|
90
|
+
if (!dirExists(dir)) return [];
|
|
91
|
+
return readdirSync(dir).filter((item) => {
|
|
92
|
+
const itemPath = join(dir, item);
|
|
93
|
+
return statSync(itemPath).isDirectory();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
83
96
|
function getCursorDir(cwd = process.cwd()) {
|
|
84
97
|
return join(cwd, ".cursor");
|
|
85
98
|
}
|
|
@@ -89,10 +102,17 @@ function getCommandsDir(cwd = process.cwd()) {
|
|
|
89
102
|
function getRulesDir(cwd = process.cwd()) {
|
|
90
103
|
return join(getCursorDir(cwd), "rules");
|
|
91
104
|
}
|
|
105
|
+
function getSkillsDir(cwd = process.cwd()) {
|
|
106
|
+
return join(getCursorDir(cwd), "skills");
|
|
107
|
+
}
|
|
92
108
|
function getConflictingFiles(dir, files) {
|
|
93
109
|
if (!dirExists(dir)) return [];
|
|
94
110
|
return files.filter((file) => fileExists(join(dir, file)));
|
|
95
111
|
}
|
|
112
|
+
function getConflictingDirs(dir, dirs) {
|
|
113
|
+
if (!dirExists(dir)) return [];
|
|
114
|
+
return dirs.filter((d) => dirExists(join(dir, d)));
|
|
115
|
+
}
|
|
96
116
|
|
|
97
117
|
// src/utils/templates.ts
|
|
98
118
|
import { join as join2, dirname as dirname2 } from "path";
|
|
@@ -104,7 +124,8 @@ var REPO_REF = "master";
|
|
|
104
124
|
var REPO_RAW_URL = "https://raw.githubusercontent.com/duongductrong/cursor-kit/master";
|
|
105
125
|
var TEMPLATE_PATHS = {
|
|
106
126
|
commands: "templates/commands",
|
|
107
|
-
rules: "templates/rules"
|
|
127
|
+
rules: "templates/rules",
|
|
128
|
+
skills: "templates/skills"
|
|
108
129
|
};
|
|
109
130
|
|
|
110
131
|
// src/utils/templates.ts
|
|
@@ -124,12 +145,14 @@ function getLocalManifest() {
|
|
|
124
145
|
}
|
|
125
146
|
const commandsDir = join2(templatesDir, "commands");
|
|
126
147
|
const rulesDir = join2(templatesDir, "rules");
|
|
127
|
-
|
|
148
|
+
const skillsDir = join2(templatesDir, "skills");
|
|
149
|
+
if (!dirExists(commandsDir) && !dirExists(rulesDir) && !dirExists(skillsDir)) {
|
|
128
150
|
return null;
|
|
129
151
|
}
|
|
130
152
|
return {
|
|
131
153
|
commands: dirExists(commandsDir) ? listFiles(commandsDir, ".md") : [],
|
|
132
|
-
rules: dirExists(rulesDir) ? listFiles(rulesDir, ".mdc") : []
|
|
154
|
+
rules: dirExists(rulesDir) ? listFiles(rulesDir, ".mdc") : [],
|
|
155
|
+
skills: dirExists(skillsDir) ? listDirs(skillsDir) : []
|
|
133
156
|
};
|
|
134
157
|
}
|
|
135
158
|
function getLocalTemplateContent(type, filename) {
|
|
@@ -140,6 +163,22 @@ function getLocalTemplateContent(type, filename) {
|
|
|
140
163
|
}
|
|
141
164
|
return null;
|
|
142
165
|
}
|
|
166
|
+
function getLocalSkillDir(skillName) {
|
|
167
|
+
const templatesDir = getLocalTemplatesDir();
|
|
168
|
+
const skillPath = join2(templatesDir, "skills", skillName);
|
|
169
|
+
if (dirExists(skillPath)) {
|
|
170
|
+
return skillPath;
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
function copyLocalSkill(skillName, targetDir) {
|
|
175
|
+
const sourcePath = getLocalSkillDir(skillName);
|
|
176
|
+
if (!sourcePath) return false;
|
|
177
|
+
const destPath = join2(targetDir, skillName);
|
|
178
|
+
ensureDir(destPath);
|
|
179
|
+
copyDir(sourcePath, destPath);
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
143
182
|
async function fetchTemplateManifest() {
|
|
144
183
|
const localManifest = getLocalManifest();
|
|
145
184
|
if (localManifest) {
|
|
@@ -183,9 +222,13 @@ function getTemplateLabel(filename) {
|
|
|
183
222
|
const nameWithoutExt = filename.replace(/\.(md|mdc)$/, "");
|
|
184
223
|
return nameWithoutExt.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
185
224
|
}
|
|
225
|
+
function getSkillLabel(skillName) {
|
|
226
|
+
return skillName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
227
|
+
}
|
|
186
228
|
|
|
187
229
|
// src/commands/init.ts
|
|
188
230
|
async function selectTemplates(type, availableTemplates) {
|
|
231
|
+
const labelFn = type === "skills" ? getSkillLabel : getTemplateLabel;
|
|
189
232
|
const selectionMode = await p.select({
|
|
190
233
|
message: `How would you like to add ${type}?`,
|
|
191
234
|
options: [
|
|
@@ -209,7 +252,7 @@ async function selectTemplates(type, availableTemplates) {
|
|
|
209
252
|
message: `Select ${type} to add:`,
|
|
210
253
|
options: availableTemplates.map((template) => ({
|
|
211
254
|
value: template,
|
|
212
|
-
label:
|
|
255
|
+
label: labelFn(template),
|
|
213
256
|
hint: template
|
|
214
257
|
})),
|
|
215
258
|
required: true
|
|
@@ -273,10 +316,36 @@ async function installTemplates(type, targetDir, selectedTemplates, conflictStra
|
|
|
273
316
|
}
|
|
274
317
|
return result;
|
|
275
318
|
}
|
|
319
|
+
async function installSkills(targetDir, selectedSkills, conflictStrategy) {
|
|
320
|
+
const result = { added: [], skipped: [] };
|
|
321
|
+
const conflictingDirs = getConflictingDirs(targetDir, selectedSkills);
|
|
322
|
+
let skillsToInstall;
|
|
323
|
+
if (conflictStrategy === "merge") {
|
|
324
|
+
skillsToInstall = selectedSkills.filter(
|
|
325
|
+
(s) => !conflictingDirs.includes(s)
|
|
326
|
+
);
|
|
327
|
+
result.skipped = conflictingDirs.filter(
|
|
328
|
+
(d) => selectedSkills.includes(d)
|
|
329
|
+
);
|
|
330
|
+
} else {
|
|
331
|
+
skillsToInstall = selectedSkills;
|
|
332
|
+
}
|
|
333
|
+
if (skillsToInstall.length === 0) {
|
|
334
|
+
return result;
|
|
335
|
+
}
|
|
336
|
+
ensureDir(targetDir);
|
|
337
|
+
for (const skillName of skillsToInstall) {
|
|
338
|
+
const success = copyLocalSkill(skillName, targetDir);
|
|
339
|
+
if (success) {
|
|
340
|
+
result.added.push(skillName);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return result;
|
|
344
|
+
}
|
|
276
345
|
var initCommand = defineCommand({
|
|
277
346
|
meta: {
|
|
278
347
|
name: "init",
|
|
279
|
-
description: "Initialize .cursor/commands and .cursor/
|
|
348
|
+
description: "Initialize .cursor/commands, .cursor/rules, and .cursor/skills in your project"
|
|
280
349
|
},
|
|
281
350
|
args: {
|
|
282
351
|
force: {
|
|
@@ -297,6 +366,12 @@ var initCommand = defineCommand({
|
|
|
297
366
|
description: "Only initialize rules",
|
|
298
367
|
default: false
|
|
299
368
|
},
|
|
369
|
+
skills: {
|
|
370
|
+
type: "boolean",
|
|
371
|
+
alias: "s",
|
|
372
|
+
description: "Only initialize skills",
|
|
373
|
+
default: false
|
|
374
|
+
},
|
|
300
375
|
all: {
|
|
301
376
|
type: "boolean",
|
|
302
377
|
alias: "a",
|
|
@@ -309,9 +384,11 @@ var initCommand = defineCommand({
|
|
|
309
384
|
const cursorDir = getCursorDir(cwd);
|
|
310
385
|
const commandsDir = getCommandsDir(cwd);
|
|
311
386
|
const rulesDir = getRulesDir(cwd);
|
|
312
|
-
const
|
|
313
|
-
const
|
|
314
|
-
const
|
|
387
|
+
const skillsDir = getSkillsDir(cwd);
|
|
388
|
+
const initAll = !args.commands && !args.rules && !args.skills;
|
|
389
|
+
const shouldInitCommands = initAll || args.commands;
|
|
390
|
+
const shouldInitRules = initAll || args.rules;
|
|
391
|
+
const shouldInitSkills = initAll || args.skills;
|
|
315
392
|
p.intro(pc2.bgCyan(pc2.black(" cursor-kit init ")));
|
|
316
393
|
const s = p.spinner();
|
|
317
394
|
let manifest;
|
|
@@ -394,6 +471,36 @@ var initCommand = defineCommand({
|
|
|
394
471
|
);
|
|
395
472
|
s.stop("Rules installed");
|
|
396
473
|
}
|
|
474
|
+
if (shouldInitSkills) {
|
|
475
|
+
let selectedSkills;
|
|
476
|
+
if (args.all) {
|
|
477
|
+
selectedSkills = manifest.skills;
|
|
478
|
+
} else {
|
|
479
|
+
const selection = await selectTemplates("skills", manifest.skills);
|
|
480
|
+
if (p.isCancel(selection)) {
|
|
481
|
+
p.cancel("Operation cancelled");
|
|
482
|
+
process.exit(0);
|
|
483
|
+
}
|
|
484
|
+
selectedSkills = selection;
|
|
485
|
+
}
|
|
486
|
+
const conflictingSkills = getConflictingDirs(skillsDir, selectedSkills);
|
|
487
|
+
let skillStrategy = "overwrite";
|
|
488
|
+
if (conflictingSkills.length > 0 && !args.force) {
|
|
489
|
+
const strategy = await handleConflicts("skills", conflictingSkills);
|
|
490
|
+
if (p.isCancel(strategy) || strategy === "cancel") {
|
|
491
|
+
p.cancel("Operation cancelled");
|
|
492
|
+
process.exit(0);
|
|
493
|
+
}
|
|
494
|
+
skillStrategy = strategy;
|
|
495
|
+
}
|
|
496
|
+
s.start("Installing skills...");
|
|
497
|
+
results.skills = await installSkills(
|
|
498
|
+
skillsDir,
|
|
499
|
+
selectedSkills,
|
|
500
|
+
skillStrategy
|
|
501
|
+
);
|
|
502
|
+
s.stop("Skills installed");
|
|
503
|
+
}
|
|
397
504
|
printDivider();
|
|
398
505
|
console.log();
|
|
399
506
|
if (results.commands) {
|
|
@@ -424,8 +531,22 @@ var initCommand = defineCommand({
|
|
|
424
531
|
}
|
|
425
532
|
}
|
|
426
533
|
}
|
|
427
|
-
|
|
428
|
-
|
|
534
|
+
if (results.skills) {
|
|
535
|
+
const { added, skipped } = results.skills;
|
|
536
|
+
if (added.length > 0 || skipped.length > 0) {
|
|
537
|
+
printSuccess(
|
|
538
|
+
`Skills: ${highlight(added.length.toString())} added${skipped.length > 0 ? `, ${pc2.yellow(skipped.length.toString())} skipped` : ""}`
|
|
539
|
+
);
|
|
540
|
+
for (const f of added) {
|
|
541
|
+
console.log(pc2.dim(` \u2514\u2500 ${pc2.green("+")} ${f}`));
|
|
542
|
+
}
|
|
543
|
+
for (const f of skipped) {
|
|
544
|
+
console.log(pc2.dim(` \u2514\u2500 ${pc2.yellow("\u25CB")} ${f} (kept existing)`));
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const totalAdded = (results.commands?.added.length ?? 0) + (results.rules?.added.length ?? 0) + (results.skills?.added.length ?? 0);
|
|
549
|
+
const totalSkipped = (results.commands?.skipped.length ?? 0) + (results.rules?.skipped.length ?? 0) + (results.skills?.skipped.length ?? 0);
|
|
429
550
|
if (totalAdded === 0 && totalSkipped > 0) {
|
|
430
551
|
console.log();
|
|
431
552
|
p.outro(pc2.yellow("No new templates added (all selected files already exist)"));
|
|
@@ -444,10 +565,10 @@ var initCommand = defineCommand({
|
|
|
444
565
|
});
|
|
445
566
|
|
|
446
567
|
// src/commands/add.ts
|
|
447
|
-
import { defineCommand as defineCommand2 } from "citty";
|
|
448
568
|
import * as p2 from "@clack/prompts";
|
|
449
|
-
import
|
|
569
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
450
570
|
import { join as join4 } from "path";
|
|
571
|
+
import pc3 from "picocolors";
|
|
451
572
|
var COMMAND_TEMPLATE = `You are a helpful assistant. Describe what this command does.
|
|
452
573
|
|
|
453
574
|
## Instructions
|
|
@@ -474,31 +595,69 @@ Describe the rule behavior here.
|
|
|
474
595
|
- Guideline 1
|
|
475
596
|
- Guideline 2
|
|
476
597
|
`;
|
|
598
|
+
var SKILL_TEMPLATE = `---
|
|
599
|
+
description: Describe when this skill should be activated
|
|
600
|
+
globs:
|
|
601
|
+
alwaysApply: false
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
# Skill Name
|
|
605
|
+
|
|
606
|
+
Brief description of what this skill enables.
|
|
607
|
+
|
|
608
|
+
## Core Capabilities
|
|
609
|
+
|
|
610
|
+
- Capability 1
|
|
611
|
+
- Capability 2
|
|
612
|
+
|
|
613
|
+
## When to Use
|
|
614
|
+
|
|
615
|
+
Use this skill when:
|
|
616
|
+
- Condition 1
|
|
617
|
+
- Condition 2
|
|
618
|
+
|
|
619
|
+
## References
|
|
620
|
+
|
|
621
|
+
For detailed guidance, see the references folder:
|
|
622
|
+
- [Reference 1](./references/example.md) - Description
|
|
623
|
+
`;
|
|
624
|
+
var SKILL_REFERENCE_TEMPLATE = `# Reference Title
|
|
625
|
+
|
|
626
|
+
Detailed reference content goes here.
|
|
627
|
+
|
|
628
|
+
## Section 1
|
|
629
|
+
|
|
630
|
+
Content...
|
|
631
|
+
|
|
632
|
+
## Section 2
|
|
633
|
+
|
|
634
|
+
Content...
|
|
635
|
+
`;
|
|
477
636
|
function generateSlug(name) {
|
|
478
637
|
return name.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").trim();
|
|
479
638
|
}
|
|
480
639
|
var addCommand = defineCommand2({
|
|
481
640
|
meta: {
|
|
482
641
|
name: "add",
|
|
483
|
-
description: "Add a new command or
|
|
642
|
+
description: "Add a new command, rule, or skill"
|
|
484
643
|
},
|
|
485
644
|
args: {
|
|
486
645
|
type: {
|
|
487
646
|
type: "string",
|
|
488
647
|
alias: "t",
|
|
489
|
-
description: "Type: 'command' or '
|
|
648
|
+
description: "Type: 'command', 'rule', or 'skill'"
|
|
490
649
|
},
|
|
491
650
|
name: {
|
|
492
651
|
type: "string",
|
|
493
652
|
alias: "n",
|
|
494
|
-
description: "Name of the command or
|
|
653
|
+
description: "Name of the command, rule, or skill"
|
|
495
654
|
}
|
|
496
655
|
},
|
|
497
656
|
async run({ args }) {
|
|
498
657
|
p2.intro(pc3.bgCyan(pc3.black(" cursor-kit add ")));
|
|
499
658
|
let itemType;
|
|
500
659
|
let itemName;
|
|
501
|
-
if (args.type && ["command", "rule"].includes(args.type)) {
|
|
660
|
+
if (args.type && ["command", "rule", "skill"].includes(args.type)) {
|
|
502
661
|
itemType = args.type;
|
|
503
662
|
} else {
|
|
504
663
|
const typeResult = await p2.select({
|
|
@@ -513,6 +672,11 @@ var addCommand = defineCommand2({
|
|
|
513
672
|
value: "rule",
|
|
514
673
|
label: "Rule",
|
|
515
674
|
hint: "Project-specific AI behavior rules"
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
value: "skill",
|
|
678
|
+
label: "Skill",
|
|
679
|
+
hint: "Comprehensive guide with references"
|
|
516
680
|
}
|
|
517
681
|
]
|
|
518
682
|
});
|
|
@@ -527,7 +691,7 @@ var addCommand = defineCommand2({
|
|
|
527
691
|
} else {
|
|
528
692
|
const nameResult = await p2.text({
|
|
529
693
|
message: `Enter ${itemType} name:`,
|
|
530
|
-
placeholder: itemType === "command" ? "my-command" : "my-rule",
|
|
694
|
+
placeholder: itemType === "command" ? "my-command" : itemType === "rule" ? "my-rule" : "my-skill",
|
|
531
695
|
validate: (value) => {
|
|
532
696
|
if (!value.trim()) return "Name is required";
|
|
533
697
|
if (value.length < 2) return "Name must be at least 2 characters";
|
|
@@ -542,12 +706,22 @@ var addCommand = defineCommand2({
|
|
|
542
706
|
}
|
|
543
707
|
const slug = generateSlug(itemName);
|
|
544
708
|
const isCommand = itemType === "command";
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
if (
|
|
709
|
+
const isSkill = itemType === "skill";
|
|
710
|
+
let targetPath;
|
|
711
|
+
let displayPath;
|
|
712
|
+
if (isSkill) {
|
|
713
|
+
const skillsDir = getSkillsDir();
|
|
714
|
+
targetPath = join4(skillsDir, slug);
|
|
715
|
+
displayPath = targetPath;
|
|
716
|
+
} else {
|
|
717
|
+
const targetDir = isCommand ? getCommandsDir() : getRulesDir();
|
|
718
|
+
const extension = isCommand ? ".md" : ".mdc";
|
|
719
|
+
targetPath = join4(targetDir, `${slug}${extension}`);
|
|
720
|
+
displayPath = targetPath;
|
|
721
|
+
}
|
|
722
|
+
if (isSkill ? dirExists(targetPath) : fileExists(targetPath)) {
|
|
549
723
|
const shouldOverwrite = await p2.confirm({
|
|
550
|
-
message: `${highlight(slug +
|
|
724
|
+
message: `${highlight(isSkill ? slug : slug + (isCommand ? ".md" : ".mdc"))} already exists. Overwrite?`,
|
|
551
725
|
initialValue: false
|
|
552
726
|
});
|
|
553
727
|
if (p2.isCancel(shouldOverwrite) || !shouldOverwrite) {
|
|
@@ -558,12 +732,25 @@ var addCommand = defineCommand2({
|
|
|
558
732
|
const s = p2.spinner();
|
|
559
733
|
s.start(`Creating ${itemType}...`);
|
|
560
734
|
try {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
735
|
+
if (isSkill) {
|
|
736
|
+
ensureDir(targetPath);
|
|
737
|
+
ensureDir(join4(targetPath, "references"));
|
|
738
|
+
writeFile(join4(targetPath, "SKILL.mdc"), SKILL_TEMPLATE);
|
|
739
|
+
writeFile(join4(targetPath, "references", "example.md"), SKILL_REFERENCE_TEMPLATE);
|
|
740
|
+
} else {
|
|
741
|
+
const targetDir = isCommand ? getCommandsDir() : getRulesDir();
|
|
742
|
+
ensureDir(targetDir);
|
|
743
|
+
const template = isCommand ? COMMAND_TEMPLATE : RULE_TEMPLATE;
|
|
744
|
+
writeFile(targetPath, template);
|
|
745
|
+
}
|
|
564
746
|
s.stop(`${itemType.charAt(0).toUpperCase() + itemType.slice(1)} created`);
|
|
565
747
|
console.log();
|
|
566
|
-
|
|
748
|
+
if (isSkill) {
|
|
749
|
+
console.log(pc3.dim(" Directory: ") + highlight(displayPath));
|
|
750
|
+
console.log(pc3.dim(" Main file: ") + highlight(join4(displayPath, "SKILL.mdc")));
|
|
751
|
+
} else {
|
|
752
|
+
console.log(pc3.dim(" File: ") + highlight(displayPath));
|
|
753
|
+
}
|
|
567
754
|
console.log();
|
|
568
755
|
p2.outro(
|
|
569
756
|
pc3.green(`\u2728 ${itemType.charAt(0).toUpperCase() + itemType.slice(1)} created! Edit the file to customize it.`)
|
|
@@ -599,6 +786,12 @@ var pullCommand = defineCommand3({
|
|
|
599
786
|
description: "Only pull rules",
|
|
600
787
|
default: false
|
|
601
788
|
},
|
|
789
|
+
skills: {
|
|
790
|
+
type: "boolean",
|
|
791
|
+
alias: "s",
|
|
792
|
+
description: "Only pull skills",
|
|
793
|
+
default: false
|
|
794
|
+
},
|
|
602
795
|
force: {
|
|
603
796
|
type: "boolean",
|
|
604
797
|
alias: "f",
|
|
@@ -607,15 +800,18 @@ var pullCommand = defineCommand3({
|
|
|
607
800
|
}
|
|
608
801
|
},
|
|
609
802
|
async run({ args }) {
|
|
610
|
-
const
|
|
611
|
-
const shouldPullCommands =
|
|
612
|
-
const shouldPullRules =
|
|
803
|
+
const pullAll = !args.commands && !args.rules && !args.skills;
|
|
804
|
+
const shouldPullCommands = pullAll || args.commands;
|
|
805
|
+
const shouldPullRules = pullAll || args.rules;
|
|
806
|
+
const shouldPullSkills = pullAll || args.skills;
|
|
613
807
|
p3.intro(pc4.bgCyan(pc4.black(" cursor-kit pull ")));
|
|
614
808
|
const commandsDir = getCommandsDir();
|
|
615
809
|
const rulesDir = getRulesDir();
|
|
810
|
+
const skillsDir = getSkillsDir();
|
|
616
811
|
const existingCommands = listFiles(commandsDir, ".md");
|
|
617
812
|
const existingRules = listFiles(rulesDir, ".mdc");
|
|
618
|
-
const
|
|
813
|
+
const existingSkills = listDirs(skillsDir);
|
|
814
|
+
const hasExisting = existingCommands.length > 0 || existingRules.length > 0 || existingSkills.length > 0;
|
|
619
815
|
if (hasExisting && !args.force) {
|
|
620
816
|
printInfo("Current status:");
|
|
621
817
|
if (existingCommands.length > 0) {
|
|
@@ -624,6 +820,9 @@ var pullCommand = defineCommand3({
|
|
|
624
820
|
if (existingRules.length > 0) {
|
|
625
821
|
console.log(pc4.dim(` Rules: ${existingRules.length} files`));
|
|
626
822
|
}
|
|
823
|
+
if (existingSkills.length > 0) {
|
|
824
|
+
console.log(pc4.dim(` Skills: ${existingSkills.length} directories`));
|
|
825
|
+
}
|
|
627
826
|
console.log();
|
|
628
827
|
const shouldContinue = await p3.confirm({
|
|
629
828
|
message: "This will merge with existing files. Continue?",
|
|
@@ -653,10 +852,19 @@ var pullCommand = defineCommand3({
|
|
|
653
852
|
});
|
|
654
853
|
s.stop("Rules updated");
|
|
655
854
|
}
|
|
855
|
+
if (shouldPullSkills) {
|
|
856
|
+
s.start("Pulling skills...");
|
|
857
|
+
await downloadTemplate(`${REPO_URL}/templates/skills#${REPO_REF}`, {
|
|
858
|
+
dir: skillsDir,
|
|
859
|
+
force: true
|
|
860
|
+
});
|
|
861
|
+
s.stop("Skills updated");
|
|
862
|
+
}
|
|
656
863
|
printDivider();
|
|
657
864
|
console.log();
|
|
658
865
|
const newCommands = listFiles(commandsDir, ".md");
|
|
659
866
|
const newRules = listFiles(rulesDir, ".mdc");
|
|
867
|
+
const newSkills = listDirs(skillsDir);
|
|
660
868
|
if (shouldPullCommands) {
|
|
661
869
|
const added = newCommands.length - existingCommands.length;
|
|
662
870
|
printSuccess(
|
|
@@ -669,6 +877,12 @@ var pullCommand = defineCommand3({
|
|
|
669
877
|
`Rules: ${highlight(newRules.length.toString())} total` + (added > 0 ? pc4.green(` (+${added} new)`) : "")
|
|
670
878
|
);
|
|
671
879
|
}
|
|
880
|
+
if (shouldPullSkills) {
|
|
881
|
+
const added = newSkills.length - existingSkills.length;
|
|
882
|
+
printSuccess(
|
|
883
|
+
`Skills: ${highlight(newSkills.length.toString())} total` + (added > 0 ? pc4.green(` (+${added} new)`) : "")
|
|
884
|
+
);
|
|
885
|
+
}
|
|
672
886
|
console.log();
|
|
673
887
|
p3.outro(pc4.green("\u2728 Successfully pulled latest updates!"));
|
|
674
888
|
} catch (error) {
|
|
@@ -680,10 +894,10 @@ var pullCommand = defineCommand3({
|
|
|
680
894
|
});
|
|
681
895
|
|
|
682
896
|
// src/commands/list.ts
|
|
683
|
-
import { defineCommand as defineCommand4 } from "citty";
|
|
684
897
|
import * as p4 from "@clack/prompts";
|
|
685
|
-
import
|
|
898
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
686
899
|
import { join as join5 } from "path";
|
|
900
|
+
import pc5 from "picocolors";
|
|
687
901
|
function extractDescription(content, isCommand) {
|
|
688
902
|
if (isCommand) {
|
|
689
903
|
const firstLine = content.trim().split("\n")[0];
|
|
@@ -710,10 +924,31 @@ function getItems(dir, extension, isCommand) {
|
|
|
710
924
|
};
|
|
711
925
|
});
|
|
712
926
|
}
|
|
927
|
+
function getSkills(dir) {
|
|
928
|
+
const skillDirs = listDirs(dir);
|
|
929
|
+
return skillDirs.map((skillName) => {
|
|
930
|
+
const skillPath = join5(dir, skillName);
|
|
931
|
+
const skillFile = join5(skillPath, "SKILL.mdc");
|
|
932
|
+
const altSkillFile = join5(skillPath, "SKILL.md");
|
|
933
|
+
let description;
|
|
934
|
+
if (fileExists(skillFile)) {
|
|
935
|
+
const content = readFile(skillFile);
|
|
936
|
+
description = extractDescription(content, false);
|
|
937
|
+
} else if (fileExists(altSkillFile)) {
|
|
938
|
+
const content = readFile(altSkillFile);
|
|
939
|
+
description = extractDescription(content, false);
|
|
940
|
+
}
|
|
941
|
+
return {
|
|
942
|
+
name: skillName,
|
|
943
|
+
path: skillPath,
|
|
944
|
+
description
|
|
945
|
+
};
|
|
946
|
+
});
|
|
947
|
+
}
|
|
713
948
|
var listCommand = defineCommand4({
|
|
714
949
|
meta: {
|
|
715
950
|
name: "list",
|
|
716
|
-
description: "List all commands and
|
|
951
|
+
description: "List all commands, rules, and skills"
|
|
717
952
|
},
|
|
718
953
|
args: {
|
|
719
954
|
commands: {
|
|
@@ -728,6 +963,12 @@ var listCommand = defineCommand4({
|
|
|
728
963
|
description: "Only list rules",
|
|
729
964
|
default: false
|
|
730
965
|
},
|
|
966
|
+
skills: {
|
|
967
|
+
type: "boolean",
|
|
968
|
+
alias: "s",
|
|
969
|
+
description: "Only list skills",
|
|
970
|
+
default: false
|
|
971
|
+
},
|
|
731
972
|
verbose: {
|
|
732
973
|
type: "boolean",
|
|
733
974
|
alias: "v",
|
|
@@ -736,18 +977,23 @@ var listCommand = defineCommand4({
|
|
|
736
977
|
}
|
|
737
978
|
},
|
|
738
979
|
async run({ args }) {
|
|
739
|
-
const
|
|
740
|
-
const shouldListCommands =
|
|
741
|
-
const shouldListRules =
|
|
980
|
+
const listAll = !args.commands && !args.rules && !args.skills;
|
|
981
|
+
const shouldListCommands = listAll || args.commands;
|
|
982
|
+
const shouldListRules = listAll || args.rules;
|
|
983
|
+
const shouldListSkills = listAll || args.skills;
|
|
742
984
|
p4.intro(pc5.bgCyan(pc5.black(" cursor-kit list ")));
|
|
743
985
|
const commandsDir = getCommandsDir();
|
|
744
986
|
const rulesDir = getRulesDir();
|
|
987
|
+
const skillsDir = getSkillsDir();
|
|
745
988
|
const commands = shouldListCommands ? getItems(commandsDir, ".md", true) : [];
|
|
746
989
|
const rules = shouldListRules ? getItems(rulesDir, ".mdc", false) : [];
|
|
747
|
-
|
|
990
|
+
const skills = shouldListSkills ? getSkills(skillsDir) : [];
|
|
991
|
+
if (commands.length === 0 && rules.length === 0 && skills.length === 0) {
|
|
748
992
|
console.log();
|
|
749
|
-
console.log(pc5.yellow(" No commands or
|
|
750
|
-
console.log(
|
|
993
|
+
console.log(pc5.yellow(" No commands, rules, or skills found."));
|
|
994
|
+
console.log(
|
|
995
|
+
pc5.dim(" Run ") + highlight("cursor-kit init") + pc5.dim(" to get started.")
|
|
996
|
+
);
|
|
751
997
|
console.log();
|
|
752
998
|
p4.outro(pc5.dim("Nothing to show"));
|
|
753
999
|
return;
|
|
@@ -755,7 +1001,9 @@ var listCommand = defineCommand4({
|
|
|
755
1001
|
printDivider();
|
|
756
1002
|
if (shouldListCommands && commands.length > 0) {
|
|
757
1003
|
console.log();
|
|
758
|
-
console.log(
|
|
1004
|
+
console.log(
|
|
1005
|
+
pc5.bold(pc5.cyan(" \u{1F4DC} Commands")) + pc5.dim(` (${commands.length})`)
|
|
1006
|
+
);
|
|
759
1007
|
console.log();
|
|
760
1008
|
commands.forEach((cmd) => {
|
|
761
1009
|
console.log(` ${pc5.green("\u25CF")} ${highlight(cmd.name)}`);
|
|
@@ -769,7 +1017,9 @@ var listCommand = defineCommand4({
|
|
|
769
1017
|
}
|
|
770
1018
|
if (shouldListRules && rules.length > 0) {
|
|
771
1019
|
console.log();
|
|
772
|
-
console.log(
|
|
1020
|
+
console.log(
|
|
1021
|
+
pc5.bold(pc5.cyan(" \u{1F4CB} Rules")) + pc5.dim(` (${rules.length})`)
|
|
1022
|
+
);
|
|
773
1023
|
console.log();
|
|
774
1024
|
rules.forEach((rule) => {
|
|
775
1025
|
console.log(` ${pc5.green("\u25CF")} ${highlight(rule.name)}`);
|
|
@@ -781,9 +1031,25 @@ var listCommand = defineCommand4({
|
|
|
781
1031
|
}
|
|
782
1032
|
});
|
|
783
1033
|
}
|
|
1034
|
+
if (shouldListSkills && skills.length > 0) {
|
|
1035
|
+
console.log();
|
|
1036
|
+
console.log(
|
|
1037
|
+
pc5.bold(pc5.cyan(" \u{1F3AF} Skills")) + pc5.dim(` (${skills.length})`)
|
|
1038
|
+
);
|
|
1039
|
+
console.log();
|
|
1040
|
+
skills.forEach((skill) => {
|
|
1041
|
+
console.log(` ${pc5.green("\u25CF")} ${highlight(skill.name)}`);
|
|
1042
|
+
if (skill.description) {
|
|
1043
|
+
console.log(pc5.dim(` ${skill.description}`));
|
|
1044
|
+
}
|
|
1045
|
+
if (args.verbose) {
|
|
1046
|
+
console.log(pc5.dim(` ${skill.path}`));
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
784
1050
|
console.log();
|
|
785
1051
|
printDivider();
|
|
786
|
-
const total = commands.length + rules.length;
|
|
1052
|
+
const total = commands.length + rules.length + skills.length;
|
|
787
1053
|
p4.outro(pc5.dim(`Total: ${total} item${total !== 1 ? "s" : ""}`));
|
|
788
1054
|
}
|
|
789
1055
|
});
|
|
@@ -796,18 +1062,18 @@ import { join as join6 } from "path";
|
|
|
796
1062
|
var removeCommand = defineCommand5({
|
|
797
1063
|
meta: {
|
|
798
1064
|
name: "remove",
|
|
799
|
-
description: "Remove a command or
|
|
1065
|
+
description: "Remove a command, rule, or skill"
|
|
800
1066
|
},
|
|
801
1067
|
args: {
|
|
802
1068
|
type: {
|
|
803
1069
|
type: "string",
|
|
804
1070
|
alias: "t",
|
|
805
|
-
description: "Type: 'command' or '
|
|
1071
|
+
description: "Type: 'command', 'rule', or 'skill'"
|
|
806
1072
|
},
|
|
807
1073
|
name: {
|
|
808
1074
|
type: "string",
|
|
809
1075
|
alias: "n",
|
|
810
|
-
description: "Name of the command or
|
|
1076
|
+
description: "Name of the command, rule, or skill to remove"
|
|
811
1077
|
},
|
|
812
1078
|
force: {
|
|
813
1079
|
type: "boolean",
|
|
@@ -820,18 +1086,20 @@ var removeCommand = defineCommand5({
|
|
|
820
1086
|
p5.intro(pc6.bgCyan(pc6.black(" cursor-kit remove ")));
|
|
821
1087
|
const commandsDir = getCommandsDir();
|
|
822
1088
|
const rulesDir = getRulesDir();
|
|
1089
|
+
const skillsDir = getSkillsDir();
|
|
823
1090
|
const commands = listFiles(commandsDir, ".md").map((f) => f.replace(".md", ""));
|
|
824
1091
|
const rules = listFiles(rulesDir, ".mdc").map((f) => f.replace(".mdc", ""));
|
|
825
|
-
|
|
1092
|
+
const skills = listDirs(skillsDir);
|
|
1093
|
+
if (commands.length === 0 && rules.length === 0 && skills.length === 0) {
|
|
826
1094
|
console.log();
|
|
827
|
-
console.log(pc6.yellow(" No commands or
|
|
1095
|
+
console.log(pc6.yellow(" No commands, rules, or skills to remove."));
|
|
828
1096
|
console.log();
|
|
829
1097
|
p5.outro(pc6.dim("Nothing to do"));
|
|
830
1098
|
return;
|
|
831
1099
|
}
|
|
832
1100
|
let itemType;
|
|
833
1101
|
let itemName;
|
|
834
|
-
if (args.type && ["command", "rule"].includes(args.type)) {
|
|
1102
|
+
if (args.type && ["command", "rule", "skill"].includes(args.type)) {
|
|
835
1103
|
itemType = args.type;
|
|
836
1104
|
} else {
|
|
837
1105
|
const typeOptions = [];
|
|
@@ -849,6 +1117,13 @@ var removeCommand = defineCommand5({
|
|
|
849
1117
|
hint: `${rules.length} available`
|
|
850
1118
|
});
|
|
851
1119
|
}
|
|
1120
|
+
if (skills.length > 0) {
|
|
1121
|
+
typeOptions.push({
|
|
1122
|
+
value: "skill",
|
|
1123
|
+
label: "Skill",
|
|
1124
|
+
hint: `${skills.length} available`
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
852
1127
|
const typeResult = await p5.select({
|
|
853
1128
|
message: "What do you want to remove?",
|
|
854
1129
|
options: typeOptions
|
|
@@ -860,9 +1135,11 @@ var removeCommand = defineCommand5({
|
|
|
860
1135
|
itemType = typeResult;
|
|
861
1136
|
}
|
|
862
1137
|
const isCommand = itemType === "command";
|
|
863
|
-
const
|
|
864
|
-
const
|
|
865
|
-
const
|
|
1138
|
+
const isRule = itemType === "rule";
|
|
1139
|
+
const isSkill = itemType === "skill";
|
|
1140
|
+
const items = isCommand ? commands : isRule ? rules : skills;
|
|
1141
|
+
const dir = isCommand ? commandsDir : isRule ? rulesDir : skillsDir;
|
|
1142
|
+
const extension = isCommand ? ".md" : isRule ? ".mdc" : "";
|
|
866
1143
|
if (items.length === 0) {
|
|
867
1144
|
p5.cancel(`No ${itemType}s found`);
|
|
868
1145
|
process.exit(0);
|
|
@@ -884,14 +1161,16 @@ var removeCommand = defineCommand5({
|
|
|
884
1161
|
}
|
|
885
1162
|
itemName = nameResult;
|
|
886
1163
|
}
|
|
887
|
-
const
|
|
888
|
-
|
|
1164
|
+
const targetPath = isSkill ? join6(dir, itemName) : join6(dir, `${itemName}${extension}`);
|
|
1165
|
+
const exists = isSkill ? dirExists(targetPath) : fileExists(targetPath);
|
|
1166
|
+
if (!exists) {
|
|
889
1167
|
p5.cancel(`${itemType} '${itemName}' not found`);
|
|
890
1168
|
process.exit(1);
|
|
891
1169
|
}
|
|
892
1170
|
if (!args.force) {
|
|
1171
|
+
const displayName = isSkill ? itemName : itemName + extension;
|
|
893
1172
|
const shouldDelete = await p5.confirm({
|
|
894
|
-
message: `Are you sure you want to delete ${highlight(
|
|
1173
|
+
message: `Are you sure you want to delete ${highlight(displayName)}?${isSkill ? " (This will remove the entire skill directory)" : ""}`,
|
|
895
1174
|
initialValue: false
|
|
896
1175
|
});
|
|
897
1176
|
if (p5.isCancel(shouldDelete) || !shouldDelete) {
|
|
@@ -900,9 +1179,10 @@ var removeCommand = defineCommand5({
|
|
|
900
1179
|
}
|
|
901
1180
|
}
|
|
902
1181
|
try {
|
|
903
|
-
removeFile(
|
|
1182
|
+
removeFile(targetPath);
|
|
1183
|
+
const displayName = isSkill ? itemName : itemName + extension;
|
|
904
1184
|
console.log();
|
|
905
|
-
printSuccess(`Removed ${highlight(
|
|
1185
|
+
printSuccess(`Removed ${highlight(displayName)}`);
|
|
906
1186
|
console.log();
|
|
907
1187
|
p5.outro(pc6.green("\u2728 Done!"));
|
|
908
1188
|
} catch (error) {
|
|
@@ -912,10 +1192,268 @@ var removeCommand = defineCommand5({
|
|
|
912
1192
|
}
|
|
913
1193
|
});
|
|
914
1194
|
|
|
1195
|
+
// src/commands/instance.ts
|
|
1196
|
+
import { defineCommand as defineCommand6 } from "citty";
|
|
1197
|
+
import * as p6 from "@clack/prompts";
|
|
1198
|
+
import pc7 from "picocolors";
|
|
1199
|
+
import { spawn } from "child_process";
|
|
1200
|
+
import { join as join7, dirname as dirname3 } from "path";
|
|
1201
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1202
|
+
import { existsSync as existsSync2, chmodSync, readdirSync as readdirSync2 } from "fs";
|
|
1203
|
+
function getBinPath() {
|
|
1204
|
+
const currentDir = dirname3(fileURLToPath2(import.meta.url));
|
|
1205
|
+
const possiblePaths = [
|
|
1206
|
+
join7(currentDir, "..", "..", "bin"),
|
|
1207
|
+
join7(currentDir, "..", "bin")
|
|
1208
|
+
];
|
|
1209
|
+
for (const binPath of possiblePaths) {
|
|
1210
|
+
if (existsSync2(binPath)) {
|
|
1211
|
+
return binPath;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
return possiblePaths[0];
|
|
1215
|
+
}
|
|
1216
|
+
function ensureExecutable(scriptPath) {
|
|
1217
|
+
try {
|
|
1218
|
+
chmodSync(scriptPath, 493);
|
|
1219
|
+
} catch {
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
function getExistingInstances() {
|
|
1223
|
+
const userAppsDir = join7(process.env.HOME ?? "", "Applications");
|
|
1224
|
+
if (!existsSync2(userAppsDir)) return [];
|
|
1225
|
+
try {
|
|
1226
|
+
const apps = readdirSync2(userAppsDir);
|
|
1227
|
+
return apps.filter((app) => app.startsWith("Cursor") && app.endsWith(".app") && app !== "Cursor.app").map((app) => ({
|
|
1228
|
+
name: app.replace(".app", ""),
|
|
1229
|
+
path: join7(userAppsDir, app)
|
|
1230
|
+
}));
|
|
1231
|
+
} catch {
|
|
1232
|
+
return [];
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
function runScript(scriptPath, args) {
|
|
1236
|
+
return new Promise((resolve2) => {
|
|
1237
|
+
ensureExecutable(scriptPath);
|
|
1238
|
+
const child = spawn(scriptPath, args, {
|
|
1239
|
+
stdio: "inherit"
|
|
1240
|
+
});
|
|
1241
|
+
child.on("close", (code) => {
|
|
1242
|
+
resolve2(code ?? 1);
|
|
1243
|
+
});
|
|
1244
|
+
child.on("error", () => {
|
|
1245
|
+
resolve2(1);
|
|
1246
|
+
});
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
var instanceCommand = defineCommand6({
|
|
1250
|
+
meta: {
|
|
1251
|
+
name: "instance",
|
|
1252
|
+
description: "Manage Cursor IDE instances for multi-account login (macOS only)"
|
|
1253
|
+
},
|
|
1254
|
+
args: {
|
|
1255
|
+
action: {
|
|
1256
|
+
type: "string",
|
|
1257
|
+
alias: "a",
|
|
1258
|
+
description: "Action: 'create' or 'remove'"
|
|
1259
|
+
},
|
|
1260
|
+
name: {
|
|
1261
|
+
type: "string",
|
|
1262
|
+
alias: "n",
|
|
1263
|
+
description: "Name of the instance (e.g. 'Cursor Enterprise')"
|
|
1264
|
+
},
|
|
1265
|
+
list: {
|
|
1266
|
+
type: "boolean",
|
|
1267
|
+
alias: "l",
|
|
1268
|
+
description: "List existing Cursor instances",
|
|
1269
|
+
default: false
|
|
1270
|
+
}
|
|
1271
|
+
},
|
|
1272
|
+
async run({ args }) {
|
|
1273
|
+
p6.intro(pc7.bgCyan(pc7.black(" cursor-kit instance ")));
|
|
1274
|
+
if (process.platform !== "darwin") {
|
|
1275
|
+
console.log();
|
|
1276
|
+
printWarning("This command only works on macOS.");
|
|
1277
|
+
console.log(pc7.dim(" Cursor instance management requires macOS-specific features."));
|
|
1278
|
+
console.log();
|
|
1279
|
+
p6.outro(pc7.dim("Exiting"));
|
|
1280
|
+
process.exit(1);
|
|
1281
|
+
}
|
|
1282
|
+
if (args.list) {
|
|
1283
|
+
const instances = getExistingInstances();
|
|
1284
|
+
printDivider();
|
|
1285
|
+
console.log();
|
|
1286
|
+
if (instances.length === 0) {
|
|
1287
|
+
printInfo("No custom Cursor instances found.");
|
|
1288
|
+
console.log(pc7.dim(" Run ") + highlight("cursor-kit instance") + pc7.dim(" to create one."));
|
|
1289
|
+
} else {
|
|
1290
|
+
console.log(pc7.bold(pc7.cyan(" \u{1F5A5} Cursor Instances")) + pc7.dim(` (${instances.length})`));
|
|
1291
|
+
console.log();
|
|
1292
|
+
for (const instance of instances) {
|
|
1293
|
+
console.log(` ${pc7.green("\u25CF")} ${highlight(instance.name)}`);
|
|
1294
|
+
console.log(pc7.dim(` \u2514\u2500 ${instance.path}`));
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
console.log();
|
|
1298
|
+
printDivider();
|
|
1299
|
+
p6.outro(pc7.dim(`Total: ${instances.length} instance${instances.length !== 1 ? "s" : ""}`));
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
const s = p6.spinner();
|
|
1303
|
+
s.start("Checking prerequisites...");
|
|
1304
|
+
const binPath = getBinPath();
|
|
1305
|
+
const createScript = join7(binPath, "cursor-new-instance");
|
|
1306
|
+
const removeScript = join7(binPath, "cursor-remove-instance");
|
|
1307
|
+
const scriptsExist = existsSync2(createScript) && existsSync2(removeScript);
|
|
1308
|
+
if (!scriptsExist) {
|
|
1309
|
+
s.stop("Prerequisites check failed");
|
|
1310
|
+
console.log();
|
|
1311
|
+
printWarning("Required scripts not found.");
|
|
1312
|
+
console.log(pc7.dim(` Expected at: ${binPath}`));
|
|
1313
|
+
console.log();
|
|
1314
|
+
p6.outro(pc7.red("Installation may be corrupted"));
|
|
1315
|
+
process.exit(1);
|
|
1316
|
+
}
|
|
1317
|
+
const originalCursor = "/Applications/Cursor.app";
|
|
1318
|
+
if (!existsSync2(originalCursor)) {
|
|
1319
|
+
s.stop("Prerequisites check failed");
|
|
1320
|
+
console.log();
|
|
1321
|
+
printWarning("Cursor.app not found in /Applications");
|
|
1322
|
+
console.log(pc7.dim(" Please install Cursor IDE first."));
|
|
1323
|
+
console.log();
|
|
1324
|
+
p6.outro(pc7.red("Cursor IDE required"));
|
|
1325
|
+
process.exit(1);
|
|
1326
|
+
}
|
|
1327
|
+
s.stop("Prerequisites verified");
|
|
1328
|
+
const existingInstances = getExistingInstances();
|
|
1329
|
+
let action;
|
|
1330
|
+
let instanceName;
|
|
1331
|
+
if (args.action && ["create", "remove"].includes(args.action)) {
|
|
1332
|
+
action = args.action;
|
|
1333
|
+
} else {
|
|
1334
|
+
const actionResult = await p6.select({
|
|
1335
|
+
message: "What would you like to do?",
|
|
1336
|
+
options: [
|
|
1337
|
+
{
|
|
1338
|
+
value: "create",
|
|
1339
|
+
label: "Create new instance",
|
|
1340
|
+
hint: "Clone Cursor with separate identity"
|
|
1341
|
+
},
|
|
1342
|
+
{
|
|
1343
|
+
value: "remove",
|
|
1344
|
+
label: "Remove instance",
|
|
1345
|
+
hint: existingInstances.length > 0 ? `${existingInstances.length} instance${existingInstances.length !== 1 ? "s" : ""} available` : "No instances to remove"
|
|
1346
|
+
}
|
|
1347
|
+
]
|
|
1348
|
+
});
|
|
1349
|
+
if (p6.isCancel(actionResult)) {
|
|
1350
|
+
p6.cancel("Operation cancelled");
|
|
1351
|
+
process.exit(0);
|
|
1352
|
+
}
|
|
1353
|
+
action = actionResult;
|
|
1354
|
+
}
|
|
1355
|
+
if (args.name) {
|
|
1356
|
+
instanceName = args.name;
|
|
1357
|
+
} else if (action === "remove" && existingInstances.length > 0) {
|
|
1358
|
+
const instanceResult = await p6.select({
|
|
1359
|
+
message: "Select instance to remove:",
|
|
1360
|
+
options: existingInstances.map((inst) => ({
|
|
1361
|
+
value: inst.name,
|
|
1362
|
+
label: inst.name,
|
|
1363
|
+
hint: inst.path
|
|
1364
|
+
}))
|
|
1365
|
+
});
|
|
1366
|
+
if (p6.isCancel(instanceResult)) {
|
|
1367
|
+
p6.cancel("Operation cancelled");
|
|
1368
|
+
process.exit(0);
|
|
1369
|
+
}
|
|
1370
|
+
instanceName = instanceResult;
|
|
1371
|
+
} else if (action === "remove" && existingInstances.length === 0) {
|
|
1372
|
+
console.log();
|
|
1373
|
+
printInfo("No custom Cursor instances found to remove.");
|
|
1374
|
+
console.log();
|
|
1375
|
+
p6.outro(pc7.dim("Nothing to do"));
|
|
1376
|
+
return;
|
|
1377
|
+
} else {
|
|
1378
|
+
const nameResult = await p6.text({
|
|
1379
|
+
message: "Enter a name for the new instance:",
|
|
1380
|
+
placeholder: "Cursor Enterprise",
|
|
1381
|
+
validate: (value) => {
|
|
1382
|
+
if (!value.trim()) return "Instance name is required";
|
|
1383
|
+
if (value.length < 2) return "Name must be at least 2 characters";
|
|
1384
|
+
const existing = existingInstances.find(
|
|
1385
|
+
(i) => i.name.toLowerCase() === value.toLowerCase()
|
|
1386
|
+
);
|
|
1387
|
+
if (existing) return `Instance "${value}" already exists`;
|
|
1388
|
+
return void 0;
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
if (p6.isCancel(nameResult)) {
|
|
1392
|
+
p6.cancel("Operation cancelled");
|
|
1393
|
+
process.exit(0);
|
|
1394
|
+
}
|
|
1395
|
+
instanceName = nameResult;
|
|
1396
|
+
}
|
|
1397
|
+
printDivider();
|
|
1398
|
+
console.log();
|
|
1399
|
+
console.log(pc7.bold(pc7.cyan(" \u{1F4CB} Summary")));
|
|
1400
|
+
console.log();
|
|
1401
|
+
console.log(` ${pc7.dim("Action:")} ${action === "create" ? pc7.green("Create") : pc7.yellow("Remove")}`);
|
|
1402
|
+
console.log(` ${pc7.dim("Instance:")} ${highlight(instanceName)}`);
|
|
1403
|
+
if (action === "create") {
|
|
1404
|
+
const slug = instanceName.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1405
|
+
console.log(` ${pc7.dim("Bundle ID:")} ${pc7.dim("com.cursor.")}${highlight(slug)}`);
|
|
1406
|
+
console.log(` ${pc7.dim("Location:")} ${pc7.dim("~/Applications/")}${highlight(instanceName + ".app")}`);
|
|
1407
|
+
} else {
|
|
1408
|
+
const targetPath = join7(process.env.HOME ?? "", "Applications", `${instanceName}.app`);
|
|
1409
|
+
console.log(` ${pc7.dim("Path:")} ${pc7.dim(targetPath)}`);
|
|
1410
|
+
}
|
|
1411
|
+
console.log();
|
|
1412
|
+
printDivider();
|
|
1413
|
+
console.log();
|
|
1414
|
+
const shouldContinue = await p6.confirm({
|
|
1415
|
+
message: action === "create" ? "Create this Cursor instance?" : "Remove this Cursor instance? This cannot be undone.",
|
|
1416
|
+
initialValue: action === "create"
|
|
1417
|
+
});
|
|
1418
|
+
if (p6.isCancel(shouldContinue) || !shouldContinue) {
|
|
1419
|
+
p6.cancel("Operation cancelled");
|
|
1420
|
+
process.exit(0);
|
|
1421
|
+
}
|
|
1422
|
+
console.log();
|
|
1423
|
+
printDivider();
|
|
1424
|
+
console.log();
|
|
1425
|
+
const scriptPath = action === "create" ? createScript : removeScript;
|
|
1426
|
+
const scriptArgs = action === "remove" ? ["--yes", instanceName] : [instanceName];
|
|
1427
|
+
const exitCode = await runScript(scriptPath, scriptArgs);
|
|
1428
|
+
console.log();
|
|
1429
|
+
printDivider();
|
|
1430
|
+
console.log();
|
|
1431
|
+
if (exitCode === 0) {
|
|
1432
|
+
if (action === "create") {
|
|
1433
|
+
printSuccess(`Instance ${highlight(instanceName)} created successfully!`);
|
|
1434
|
+
console.log();
|
|
1435
|
+
console.log(pc7.dim(" Next steps:"));
|
|
1436
|
+
console.log(pc7.dim(" \u2022 The new instance should launch automatically"));
|
|
1437
|
+
console.log(pc7.dim(" \u2022 Sign in with a different Cursor account"));
|
|
1438
|
+
console.log(pc7.dim(" \u2022 Find it in ~/Applications/"));
|
|
1439
|
+
} else {
|
|
1440
|
+
printSuccess(`Instance ${highlight(instanceName)} removed successfully!`);
|
|
1441
|
+
}
|
|
1442
|
+
console.log();
|
|
1443
|
+
p6.outro(pc7.green("\u2728 Done!"));
|
|
1444
|
+
} else {
|
|
1445
|
+
printWarning(`Operation completed with exit code ${exitCode}`);
|
|
1446
|
+
console.log();
|
|
1447
|
+
p6.outro(pc7.yellow("Check the output above for details"));
|
|
1448
|
+
process.exit(exitCode);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1452
|
+
|
|
915
1453
|
// src/cli.ts
|
|
916
1454
|
var require2 = createRequire(import.meta.url);
|
|
917
1455
|
var pkg = require2("../package.json");
|
|
918
|
-
var main =
|
|
1456
|
+
var main = defineCommand7({
|
|
919
1457
|
meta: {
|
|
920
1458
|
name: "cursor-kit",
|
|
921
1459
|
version: pkg.version,
|
|
@@ -930,7 +1468,8 @@ var main = defineCommand6({
|
|
|
930
1468
|
add: addCommand,
|
|
931
1469
|
pull: pullCommand,
|
|
932
1470
|
list: listCommand,
|
|
933
|
-
remove: removeCommand
|
|
1471
|
+
remove: removeCommand,
|
|
1472
|
+
instance: instanceCommand
|
|
934
1473
|
}
|
|
935
1474
|
});
|
|
936
1475
|
runMain(main);
|