cursor-kit-cli 1.0.4-beta.2 → 1.1.0-beta.3

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/dist/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { defineCommand as defineCommand6, runMain } from "citty";
5
+ import { createRequire } from "module";
5
6
 
6
7
  // src/utils/branding.ts
7
8
  import figlet from "figlet";
@@ -31,7 +32,7 @@ function printDivider() {
31
32
  }
32
33
  function printVersion(version) {
33
34
  console.log(
34
- pc.dim(" v") + cursorGradient(version) + pc.dim(" \u2022 Made with \u2665")
35
+ pc.dim(" ") + cursorGradient(`v${version}`) + pc.dim(" \u2022 Made with \u2665")
35
36
  );
36
37
  console.log();
37
38
  }
@@ -43,7 +44,7 @@ function highlight(text2) {
43
44
  import { defineCommand } from "citty";
44
45
  import * as p from "@clack/prompts";
45
46
  import pc2 from "picocolors";
46
- import { downloadTemplate } from "giget";
47
+ import { join as join3 } from "path";
47
48
 
48
49
  // src/utils/fs.ts
49
50
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, rmSync, statSync } from "fs";
@@ -88,12 +89,190 @@ function getCommandsDir(cwd = process.cwd()) {
88
89
  function getRulesDir(cwd = process.cwd()) {
89
90
  return join(getCursorDir(cwd), "rules");
90
91
  }
92
+ function getConflictingFiles(dir, files) {
93
+ if (!dirExists(dir)) return [];
94
+ return files.filter((file) => fileExists(join(dir, file)));
95
+ }
96
+
97
+ // src/utils/templates.ts
98
+ import { join as join2, dirname as dirname2 } from "path";
99
+ import { fileURLToPath } from "url";
91
100
 
92
101
  // src/utils/constants.ts
93
102
  var REPO_URL = "github:duongductrong/cursor-kit";
94
103
  var REPO_REF = "master";
104
+ var REPO_RAW_URL = "https://raw.githubusercontent.com/duongductrong/cursor-kit/master";
105
+ var TEMPLATE_PATHS = {
106
+ commands: "templates/commands",
107
+ rules: "templates/rules"
108
+ };
109
+
110
+ // src/utils/templates.ts
111
+ function getLocalTemplatesDir() {
112
+ const currentDir = dirname2(fileURLToPath(import.meta.url));
113
+ return join2(currentDir, "..", "templates");
114
+ }
115
+ function getLocalManifest() {
116
+ const templatesDir = getLocalTemplatesDir();
117
+ const manifestPath = join2(templatesDir, "manifest.json");
118
+ if (fileExists(manifestPath)) {
119
+ try {
120
+ return JSON.parse(readFile(manifestPath));
121
+ } catch {
122
+ return null;
123
+ }
124
+ }
125
+ const commandsDir = join2(templatesDir, "commands");
126
+ const rulesDir = join2(templatesDir, "rules");
127
+ if (!dirExists(commandsDir) && !dirExists(rulesDir)) {
128
+ return null;
129
+ }
130
+ return {
131
+ commands: dirExists(commandsDir) ? listFiles(commandsDir, ".md") : [],
132
+ rules: dirExists(rulesDir) ? listFiles(rulesDir, ".mdc") : []
133
+ };
134
+ }
135
+ function getLocalTemplateContent(type, filename) {
136
+ const templatesDir = getLocalTemplatesDir();
137
+ const filePath = join2(templatesDir, type, filename);
138
+ if (fileExists(filePath)) {
139
+ return readFile(filePath);
140
+ }
141
+ return null;
142
+ }
143
+ async function fetchTemplateManifest() {
144
+ const localManifest = getLocalManifest();
145
+ if (localManifest) {
146
+ return localManifest;
147
+ }
148
+ const url = `${REPO_RAW_URL}/templates/manifest.json`;
149
+ const response = await fetch(url);
150
+ if (!response.ok) {
151
+ throw new Error(`Failed to fetch template manifest: ${response.statusText}`);
152
+ }
153
+ return response.json();
154
+ }
155
+ async function fetchTemplateContent(type, filename) {
156
+ const localContent = getLocalTemplateContent(type, filename);
157
+ if (localContent !== null) {
158
+ return localContent;
159
+ }
160
+ const templatePath = TEMPLATE_PATHS[type];
161
+ const url = `${REPO_RAW_URL}/${templatePath}/${filename}`;
162
+ const response = await fetch(url);
163
+ if (!response.ok) {
164
+ throw new Error(`Failed to fetch template ${filename}: ${response.statusText}`);
165
+ }
166
+ return response.text();
167
+ }
168
+ async function fetchMultipleTemplates(type, filenames) {
169
+ const results = /* @__PURE__ */ new Map();
170
+ const fetchPromises = filenames.map(async (filename) => {
171
+ const content = await fetchTemplateContent(type, filename);
172
+ return { filename, content };
173
+ });
174
+ const settled = await Promise.allSettled(fetchPromises);
175
+ for (const result of settled) {
176
+ if (result.status === "fulfilled") {
177
+ results.set(result.value.filename, result.value.content);
178
+ }
179
+ }
180
+ return results;
181
+ }
182
+ function getTemplateLabel(filename) {
183
+ const nameWithoutExt = filename.replace(/\.(md|mdc)$/, "");
184
+ return nameWithoutExt.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
185
+ }
95
186
 
96
187
  // src/commands/init.ts
188
+ async function selectTemplates(type, availableTemplates) {
189
+ const selectionMode = await p.select({
190
+ message: `How would you like to add ${type}?`,
191
+ options: [
192
+ {
193
+ value: "all",
194
+ label: `Add all ${availableTemplates.length} ${type}`,
195
+ hint: "Install everything"
196
+ },
197
+ {
198
+ value: "select",
199
+ label: "Select specific items",
200
+ hint: "Choose which ones to install"
201
+ }
202
+ ]
203
+ });
204
+ if (p.isCancel(selectionMode)) return selectionMode;
205
+ if (selectionMode === "all") {
206
+ return availableTemplates;
207
+ }
208
+ const selectedTemplates = await p.multiselect({
209
+ message: `Select ${type} to add:`,
210
+ options: availableTemplates.map((template) => ({
211
+ value: template,
212
+ label: getTemplateLabel(template),
213
+ hint: template
214
+ })),
215
+ required: true
216
+ });
217
+ return selectedTemplates;
218
+ }
219
+ async function handleConflicts(type, conflictingFiles) {
220
+ console.log();
221
+ console.log(
222
+ pc2.yellow(`\u26A0 ${conflictingFiles.length} existing ${type} found:`)
223
+ );
224
+ for (const file of conflictingFiles) {
225
+ console.log(pc2.dim(` \u2514\u2500 ${file}`));
226
+ }
227
+ console.log();
228
+ const strategy = await p.select({
229
+ message: "How would you like to handle conflicts?",
230
+ options: [
231
+ {
232
+ value: "overwrite",
233
+ label: "Overwrite existing files",
234
+ hint: "Replace all conflicting files"
235
+ },
236
+ {
237
+ value: "merge",
238
+ label: "Merge (keep existing, add new only)",
239
+ hint: "Skip files that already exist"
240
+ },
241
+ {
242
+ value: "cancel",
243
+ label: "Cancel",
244
+ hint: "Abort the operation"
245
+ }
246
+ ]
247
+ });
248
+ return strategy;
249
+ }
250
+ async function installTemplates(type, targetDir, selectedTemplates, conflictStrategy) {
251
+ const result = { added: [], skipped: [] };
252
+ const conflictingFiles = getConflictingFiles(targetDir, selectedTemplates);
253
+ let templatesToInstall;
254
+ if (conflictStrategy === "merge") {
255
+ templatesToInstall = selectedTemplates.filter(
256
+ (t) => !conflictingFiles.includes(t)
257
+ );
258
+ result.skipped = conflictingFiles.filter(
259
+ (f) => selectedTemplates.includes(f)
260
+ );
261
+ } else {
262
+ templatesToInstall = selectedTemplates;
263
+ }
264
+ if (templatesToInstall.length === 0) {
265
+ return result;
266
+ }
267
+ const templates = await fetchMultipleTemplates(type, templatesToInstall);
268
+ ensureDir(targetDir);
269
+ for (const [filename, content] of templates) {
270
+ const filePath = join3(targetDir, filename);
271
+ writeFile(filePath, content);
272
+ result.added.push(filename);
273
+ }
274
+ return result;
275
+ }
97
276
  var initCommand = defineCommand({
98
277
  meta: {
99
278
  name: "init",
@@ -103,7 +282,7 @@ var initCommand = defineCommand({
103
282
  force: {
104
283
  type: "boolean",
105
284
  alias: "f",
106
- description: "Overwrite existing files",
285
+ description: "Overwrite existing files without prompting",
107
286
  default: false
108
287
  },
109
288
  commands: {
@@ -117,6 +296,12 @@ var initCommand = defineCommand({
117
296
  alias: "r",
118
297
  description: "Only initialize rules",
119
298
  default: false
299
+ },
300
+ all: {
301
+ type: "boolean",
302
+ alias: "a",
303
+ description: "Install all templates without selection prompts",
304
+ default: false
120
305
  }
121
306
  },
122
307
  async run({ args }) {
@@ -128,62 +313,126 @@ var initCommand = defineCommand({
128
313
  const shouldInitCommands = initBoth || args.commands;
129
314
  const shouldInitRules = initBoth || args.rules;
130
315
  p.intro(pc2.bgCyan(pc2.black(" cursor-kit init ")));
131
- const commandsExist = dirExists(commandsDir) && listFiles(commandsDir).length > 0;
132
- const rulesExist = dirExists(rulesDir) && listFiles(rulesDir).length > 0;
133
- if ((commandsExist || rulesExist) && !args.force) {
134
- const existingItems = [];
135
- if (commandsExist) existingItems.push("commands");
136
- if (rulesExist) existingItems.push("rules");
137
- const shouldContinue = await p.confirm({
138
- message: `Existing ${existingItems.join(" and ")} found. Overwrite?`,
139
- initialValue: false
140
- });
141
- if (p.isCancel(shouldContinue) || !shouldContinue) {
142
- p.cancel("Operation cancelled");
143
- process.exit(0);
144
- }
145
- }
146
316
  const s = p.spinner();
317
+ let manifest;
318
+ try {
319
+ s.start("Fetching template manifest...");
320
+ manifest = await fetchTemplateManifest();
321
+ s.stop("Template manifest loaded");
322
+ } catch (error) {
323
+ s.stop("Failed to fetch manifest");
324
+ p.cancel(
325
+ `Error: ${error instanceof Error ? error.message : "Unknown error"}`
326
+ );
327
+ process.exit(1);
328
+ }
329
+ const results = {};
147
330
  try {
148
331
  ensureDir(cursorDir);
149
332
  if (shouldInitCommands) {
150
- s.start("Fetching commands templates...");
151
- await downloadTemplate(`${REPO_URL}/templates/commands#${REPO_REF}`, {
152
- dir: commandsDir,
153
- force: true
154
- });
155
- s.stop("Commands initialized");
333
+ let selectedCommands;
334
+ if (args.all) {
335
+ selectedCommands = manifest.commands;
336
+ } else {
337
+ const selection = await selectTemplates("commands", manifest.commands);
338
+ if (p.isCancel(selection)) {
339
+ p.cancel("Operation cancelled");
340
+ process.exit(0);
341
+ }
342
+ selectedCommands = selection;
343
+ }
344
+ const conflictingCommands = getConflictingFiles(
345
+ commandsDir,
346
+ selectedCommands
347
+ );
348
+ let commandStrategy = "overwrite";
349
+ if (conflictingCommands.length > 0 && !args.force) {
350
+ const strategy = await handleConflicts("commands", conflictingCommands);
351
+ if (p.isCancel(strategy) || strategy === "cancel") {
352
+ p.cancel("Operation cancelled");
353
+ process.exit(0);
354
+ }
355
+ commandStrategy = strategy;
356
+ }
357
+ s.start("Installing commands...");
358
+ results.commands = await installTemplates(
359
+ "commands",
360
+ commandsDir,
361
+ selectedCommands,
362
+ commandStrategy
363
+ );
364
+ s.stop("Commands installed");
156
365
  }
157
366
  if (shouldInitRules) {
158
- s.start("Fetching rules templates...");
159
- await downloadTemplate(`${REPO_URL}/templates/rules#${REPO_REF}`, {
160
- dir: rulesDir,
161
- force: true
162
- });
163
- s.stop("Rules initialized");
367
+ let selectedRules;
368
+ if (args.all) {
369
+ selectedRules = manifest.rules;
370
+ } else {
371
+ const selection = await selectTemplates("rules", manifest.rules);
372
+ if (p.isCancel(selection)) {
373
+ p.cancel("Operation cancelled");
374
+ process.exit(0);
375
+ }
376
+ selectedRules = selection;
377
+ }
378
+ const conflictingRules = getConflictingFiles(rulesDir, selectedRules);
379
+ let ruleStrategy = "overwrite";
380
+ if (conflictingRules.length > 0 && !args.force) {
381
+ const strategy = await handleConflicts("rules", conflictingRules);
382
+ if (p.isCancel(strategy) || strategy === "cancel") {
383
+ p.cancel("Operation cancelled");
384
+ process.exit(0);
385
+ }
386
+ ruleStrategy = strategy;
387
+ }
388
+ s.start("Installing rules...");
389
+ results.rules = await installTemplates(
390
+ "rules",
391
+ rulesDir,
392
+ selectedRules,
393
+ ruleStrategy
394
+ );
395
+ s.stop("Rules installed");
164
396
  }
165
397
  printDivider();
166
398
  console.log();
167
- const commandFiles = listFiles(commandsDir, ".md");
168
- const ruleFiles = listFiles(rulesDir, ".mdc");
169
- if (shouldInitCommands && commandFiles.length > 0) {
170
- printSuccess(
171
- `Commands: ${highlight(commandFiles.length.toString())} templates`
172
- );
173
- commandFiles.forEach((f) => {
174
- console.log(pc2.dim(` \u2514\u2500 ${f}`));
175
- });
399
+ if (results.commands) {
400
+ const { added, skipped } = results.commands;
401
+ if (added.length > 0 || skipped.length > 0) {
402
+ printSuccess(
403
+ `Commands: ${highlight(added.length.toString())} added${skipped.length > 0 ? `, ${pc2.yellow(skipped.length.toString())} skipped` : ""}`
404
+ );
405
+ for (const f of added) {
406
+ console.log(pc2.dim(` \u2514\u2500 ${pc2.green("+")} ${f}`));
407
+ }
408
+ for (const f of skipped) {
409
+ console.log(pc2.dim(` \u2514\u2500 ${pc2.yellow("\u25CB")} ${f} (kept existing)`));
410
+ }
411
+ }
176
412
  }
177
- if (shouldInitRules && ruleFiles.length > 0) {
178
- printSuccess(
179
- `Rules: ${highlight(ruleFiles.length.toString())} templates`
180
- );
181
- ruleFiles.forEach((f) => {
182
- console.log(pc2.dim(` \u2514\u2500 ${f}`));
183
- });
413
+ if (results.rules) {
414
+ const { added, skipped } = results.rules;
415
+ if (added.length > 0 || skipped.length > 0) {
416
+ printSuccess(
417
+ `Rules: ${highlight(added.length.toString())} added${skipped.length > 0 ? `, ${pc2.yellow(skipped.length.toString())} skipped` : ""}`
418
+ );
419
+ for (const f of added) {
420
+ console.log(pc2.dim(` \u2514\u2500 ${pc2.green("+")} ${f}`));
421
+ }
422
+ for (const f of skipped) {
423
+ console.log(pc2.dim(` \u2514\u2500 ${pc2.yellow("\u25CB")} ${f} (kept existing)`));
424
+ }
425
+ }
426
+ }
427
+ const totalAdded = (results.commands?.added.length ?? 0) + (results.rules?.added.length ?? 0);
428
+ const totalSkipped = (results.commands?.skipped.length ?? 0) + (results.rules?.skipped.length ?? 0);
429
+ if (totalAdded === 0 && totalSkipped > 0) {
430
+ console.log();
431
+ p.outro(pc2.yellow("No new templates added (all selected files already exist)"));
432
+ } else {
433
+ console.log();
434
+ p.outro(pc2.green("\u2728 Cursor Kit initialized successfully!"));
184
435
  }
185
- console.log();
186
- p.outro(pc2.green("\u2728 Cursor Kit initialized successfully!"));
187
436
  } catch (error) {
188
437
  s.stop("Failed");
189
438
  p.cancel(
@@ -198,7 +447,7 @@ var initCommand = defineCommand({
198
447
  import { defineCommand as defineCommand2 } from "citty";
199
448
  import * as p2 from "@clack/prompts";
200
449
  import pc3 from "picocolors";
201
- import { join as join2 } from "path";
450
+ import { join as join4 } from "path";
202
451
  var COMMAND_TEMPLATE = `You are a helpful assistant. Describe what this command does.
203
452
 
204
453
  ## Instructions
@@ -295,7 +544,7 @@ var addCommand = defineCommand2({
295
544
  const isCommand = itemType === "command";
296
545
  const targetDir = isCommand ? getCommandsDir() : getRulesDir();
297
546
  const extension = isCommand ? ".md" : ".mdc";
298
- const filePath = join2(targetDir, `${slug}${extension}`);
547
+ const filePath = join4(targetDir, `${slug}${extension}`);
299
548
  if (fileExists(filePath)) {
300
549
  const shouldOverwrite = await p2.confirm({
301
550
  message: `${highlight(slug + extension)} already exists. Overwrite?`,
@@ -331,7 +580,7 @@ var addCommand = defineCommand2({
331
580
  import { defineCommand as defineCommand3 } from "citty";
332
581
  import * as p3 from "@clack/prompts";
333
582
  import pc4 from "picocolors";
334
- import { downloadTemplate as downloadTemplate2 } from "giget";
583
+ import { downloadTemplate } from "giget";
335
584
  var pullCommand = defineCommand3({
336
585
  meta: {
337
586
  name: "pull",
@@ -390,7 +639,7 @@ var pullCommand = defineCommand3({
390
639
  ensureDir(getCursorDir());
391
640
  if (shouldPullCommands) {
392
641
  s.start("Pulling commands...");
393
- await downloadTemplate2(`${REPO_URL}/templates/commands#${REPO_REF}`, {
642
+ await downloadTemplate(`${REPO_URL}/templates/commands#${REPO_REF}`, {
394
643
  dir: commandsDir,
395
644
  force: true
396
645
  });
@@ -398,7 +647,7 @@ var pullCommand = defineCommand3({
398
647
  }
399
648
  if (shouldPullRules) {
400
649
  s.start("Pulling rules...");
401
- await downloadTemplate2(`${REPO_URL}/templates/rules#${REPO_REF}`, {
650
+ await downloadTemplate(`${REPO_URL}/templates/rules#${REPO_REF}`, {
402
651
  dir: rulesDir,
403
652
  force: true
404
653
  });
@@ -434,7 +683,7 @@ var pullCommand = defineCommand3({
434
683
  import { defineCommand as defineCommand4 } from "citty";
435
684
  import * as p4 from "@clack/prompts";
436
685
  import pc5 from "picocolors";
437
- import { join as join3 } from "path";
686
+ import { join as join5 } from "path";
438
687
  function extractDescription(content, isCommand) {
439
688
  if (isCommand) {
440
689
  const firstLine = content.trim().split("\n")[0];
@@ -452,7 +701,7 @@ function extractDescription(content, isCommand) {
452
701
  function getItems(dir, extension, isCommand) {
453
702
  const files = listFiles(dir, extension);
454
703
  return files.map((file) => {
455
- const filePath = join3(dir, file);
704
+ const filePath = join5(dir, file);
456
705
  const content = fileExists(filePath) ? readFile(filePath) : "";
457
706
  return {
458
707
  name: file.replace(extension, ""),
@@ -543,7 +792,7 @@ var listCommand = defineCommand4({
543
792
  import { defineCommand as defineCommand5 } from "citty";
544
793
  import * as p5 from "@clack/prompts";
545
794
  import pc6 from "picocolors";
546
- import { join as join4 } from "path";
795
+ import { join as join6 } from "path";
547
796
  var removeCommand = defineCommand5({
548
797
  meta: {
549
798
  name: "remove",
@@ -635,7 +884,7 @@ var removeCommand = defineCommand5({
635
884
  }
636
885
  itemName = nameResult;
637
886
  }
638
- const filePath = join4(dir, `${itemName}${extension}`);
887
+ const filePath = join6(dir, `${itemName}${extension}`);
639
888
  if (!fileExists(filePath)) {
640
889
  p5.cancel(`${itemType} '${itemName}' not found`);
641
890
  process.exit(1);
@@ -664,15 +913,17 @@ var removeCommand = defineCommand5({
664
913
  });
665
914
 
666
915
  // src/cli.ts
916
+ var require2 = createRequire(import.meta.url);
917
+ var pkg = require2("../package.json");
667
918
  var main = defineCommand6({
668
919
  meta: {
669
920
  name: "cursor-kit",
670
- version: "0.1.0",
921
+ version: pkg.version,
671
922
  description: "CLI toolkit to manage Cursor IDE rules and commands"
672
923
  },
673
924
  setup() {
674
925
  printBanner();
675
- printVersion("0.1.0");
926
+ printVersion(pkg.version);
676
927
  },
677
928
  subCommands: {
678
929
  init: initCommand,