cursor-keys 1.2.1 → 1.2.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 CHANGED
@@ -102,10 +102,13 @@ Settings flow one way, main to profiles: `sync` re-copies your main `settings.js
102
102
  | `sync [name]` | Re-copy main Cursor settings into profiles |
103
103
  | `doctor [name]` | Health checks: key row, base URL, subagent overrides |
104
104
  | `wrap [--install]` | zsh function so plain `cursor <path>` auto-picks the profile |
105
- | `aliases [--install]` | Per-project shell aliases |
105
+ | `aliases [--install]` | Per-project shell aliases (also `ck` shortcut + tab completion) |
106
+ | `completion` | Print zsh tab completion (auto-sourced by `aliases --install`) |
106
107
  | `install-extension` | Link the Cursor extension into `~/.cursor/extensions` |
107
108
  | `rm <name> [--delete]` | Unregister (optionally delete the profile) |
108
109
 
110
+ `cursor-keys ls` prints registered project names one per line for scripts and tab completion; it is hidden from `--help`.
111
+
109
112
  ## Config
110
113
 
111
114
  `~/.config/cursor-keys/config.json`:
@@ -9,12 +9,13 @@ const BANNER = `
9
9
  ${yellow}██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ █████╔╝ █████╗ ╚████╔╝ ███████╗
10
10
  ${yellow}██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ ██╔═██╗ ██╔══╝ ╚██╔╝ ╚════██║
11
11
  ${yellow}╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ ██║ ██╗███████╗ ██║ ███████║
12
- ${yellow} ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚══════╝${reset}${dim} per-project Cursor API keys
12
+ ${yellow} ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚══════╝${reset}
13
+ ${dim}per-project Cursor API keys${reset}
13
14
  `;
14
15
 
15
16
  function printHelp() {
16
17
  console.log(BANNER);
17
- console.log(`${bold}Usage:${reset} cursor-keys <command> [options]\n`);
18
+ console.log(`${bold}Usage:${reset} cursor-keys (or ck, ckeys) <command> [options]\n`);
18
19
  console.log(`${bold}Commands:${reset}`);
19
20
  console.log(
20
21
  " init [--root DIR] [--all] Scan for git repos and register projects",
@@ -42,7 +43,10 @@ function printHelp() {
42
43
  console.log(
43
44
  " doctor [name] Verify setup for one or all projects",
44
45
  );
45
- console.log(" aliases [--install] Print or install shell aliases");
46
+ console.log(" aliases [--install] Shell aliases (incl. 'ck' + tab completion)");
47
+ console.log(
48
+ " completion Print zsh tab completion (auto-sourced by aliases)",
49
+ );
46
50
  console.log(
47
51
  " wrap [--install] Make plain `cursor <path>` auto-pick profile",
48
52
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursor-keys",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "Per-project Cursor profiles so each window uses the correct BYOK API key. Zero dependencies.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,7 +13,8 @@
13
13
  "files": [
14
14
  "bin",
15
15
  "src",
16
- "extension"
16
+ "extension",
17
+ "scripts"
17
18
  ],
18
19
  "keywords": [
19
20
  "cursor",
@@ -31,6 +32,7 @@
31
32
  },
32
33
  "scripts": {
33
34
  "test": "node --test",
35
+ "postinstall": "node scripts/postinstall.mjs",
34
36
  "prepublishOnly": "npm test"
35
37
  }
36
38
  }
@@ -0,0 +1,22 @@
1
+ // Postinstall hint: prints once on global install, never breaks the install.
2
+ // Quiet installs: CURSOR_KEYS_NO_POSTINSTALL=1
3
+ try {
4
+ if (process.env.CURSOR_KEYS_NO_POSTINSTALL) process.exit(0);
5
+ const yellow = "\x1b[33m";
6
+ const dim = "\x1b[2m";
7
+ const reset = "\x1b[0m";
8
+ console.log(`
9
+ ${yellow}cursor-keys installed${reset}
10
+
11
+ cursor-keys --help commands
12
+ cursor-keys aliases --install aliases, 'ck' shortcut, tab completion
13
+ cursor-keys wrap --install plain 'cursor <path>' picks the profile
14
+
15
+ Inside Cursor: Cmd+Shift+P then "cursor-keys: Open Project..." after
16
+ 'cursor-keys install-extension' (or install 'cursor-keys' from Open VSX).
17
+
18
+ ${dim}Quiet installs: CURSOR_KEYS_NO_POSTINSTALL=1${reset}
19
+ `);
20
+ } catch {
21
+ /* postinstall must never break install */
22
+ }
package/src/ansi.mjs CHANGED
@@ -26,15 +26,25 @@ export function err(text) {
26
26
  return colorize(text, red);
27
27
  }
28
28
 
29
+ const ANSI_RE = /\x1b\[[0-9;]*m/g;
30
+
31
+ export function stripAnsi(s) {
32
+ return String(s).replace(ANSI_RE, "");
33
+ }
34
+
29
35
  export function padEnd(str, len) {
30
36
  const s = String(str);
31
- if (s.length >= len) return s.slice(0, len);
32
- return s + " ".repeat(len - s.length);
37
+ const vis = stripAnsi(s).length;
38
+ if (vis >= len) return s;
39
+ return s + " ".repeat(len - vis);
33
40
  }
34
41
 
35
42
  export function printTable(headers, rows) {
36
43
  const widths = headers.map((h, i) =>
37
- Math.max(h.length, ...rows.map((r) => String(r[i] ?? "").length)),
44
+ Math.max(
45
+ h.length,
46
+ ...rows.map((r) => stripAnsi(String(r[i] ?? "")).length),
47
+ ),
38
48
  );
39
49
  const line = widths.map((w) => "-".repeat(w)).join("-+-");
40
50
  console.log(widths.map((w, i) => padEnd(headers[i], w)).join(" | "));
@@ -51,5 +61,5 @@ export function blockSwatch(hex) {
51
61
  const r = parseInt(hex.slice(1, 3), 16);
52
62
  const g = parseInt(hex.slice(3, 5), 16);
53
63
  const b = parseInt(hex.slice(5, 7), 16);
54
- return `${esc(48)};2;${r};${g};${b}m ${reset}`;
64
+ return `\x1b[48;2;${r};${g};${b}m ${reset}`;
55
65
  }
package/src/commands.mjs CHANGED
@@ -93,6 +93,10 @@ export async function runCommand(argv) {
93
93
  return cmdClone(rest);
94
94
  case "rm":
95
95
  return cmdRm(rest);
96
+ case "ls":
97
+ return cmdLs(rest);
98
+ case "completion":
99
+ return cmdCompletion(rest);
96
100
  default:
97
101
  throw new Error(`Unknown command: ${cmd}. Run cursor-keys --help`);
98
102
  }
@@ -569,7 +573,13 @@ async function cmdAliases(args) {
569
573
  const lines = names.map(
570
574
  (n) => `alias ${sanitizeAlias(n)}='cursor-keys open ${shellEscapeName(n)}'`,
571
575
  );
572
- const block = [ZSHRC_MARKER_START, ...lines, ZSHRC_MARKER_END].join("\n");
576
+ const block = [
577
+ ZSHRC_MARKER_START,
578
+ "alias ck='cursor-keys'",
579
+ 'eval "$(cursor-keys completion)" # tab completion',
580
+ ...lines,
581
+ ZSHRC_MARKER_END,
582
+ ].join("\n");
573
583
 
574
584
  if (!flags.install) {
575
585
  console.log(block);
@@ -591,6 +601,13 @@ async function cmdAliases(args) {
591
601
  }
592
602
  fs.writeFileSync(zshrc, content, "utf8");
593
603
  console.log(ok(`Updated ${zshrc}`));
604
+ if (!content.includes("compinit")) {
605
+ console.log(
606
+ warn(
607
+ "Note: `compinit` not found in ~/.zshrc. Tab completion needs it; add this line: autoload -Uz compinit && compinit",
608
+ ),
609
+ );
610
+ }
594
611
  }
595
612
 
596
613
  function sanitizeAlias(name) {
@@ -601,6 +618,54 @@ function shellEscapeName(name) {
601
618
  return name.replace(/'/g, `'\\''`);
602
619
  }
603
620
 
621
+ const COMPLETION_COMMANDS =
622
+ "init add clone set-key open status current config sync doctor aliases wrap install-extension completion ls rm";
623
+
624
+ /**
625
+ * Pure builder for the zsh completion script. Sourced via
626
+ * `eval "$(cursor-keys completion)"` from the aliases/wrap zshrc blocks,
627
+ * so updates ship with `npm update` and never go stale on disk.
628
+ */
629
+ export function completionScript() {
630
+ return `#compdef cursor-keys ckeys ck
631
+ _cursor_keys() {
632
+ local -a projects
633
+ if (( CURRENT == 3 )) && [[ \${words[2]} == (open|clone|set-key|doctor|sync|rm) ]]; then
634
+ projects=(\${(f)"$(cursor-keys ls 2>/dev/null)"})
635
+ _describe 'project' projects
636
+ else
637
+ _arguments '1:command:(${COMPLETION_COMMANDS})'
638
+ fi
639
+ }
640
+ compdef _cursor_keys cursor-keys ckeys ck
641
+ `;
642
+ }
643
+
644
+ function cmdCompletion(args) {
645
+ const { flags } = parseFlags(args);
646
+ if (flags.help) {
647
+ console.log("Usage: cursor-keys completion");
648
+ console.log("Prints the zsh tab completion script to stdout.");
649
+ console.log("Auto-sourced by `cursor-keys aliases --install`; no separate install needed.");
650
+ return;
651
+ }
652
+ process.stdout.write(completionScript());
653
+ }
654
+
655
+ /**
656
+ * Hidden command: bare project names, one per line, for completion/scripts.
657
+ */
658
+ function cmdLs(args) {
659
+ const { flags } = parseFlags(args);
660
+ if (flags.help) {
661
+ console.log("Usage: cursor-keys ls");
662
+ console.log("Prints registered project names, one per line (for scripts and tab completion).");
663
+ return;
664
+ }
665
+ const config = loadConfig();
666
+ console.log(Object.keys(config.projects).sort().join("\n"));
667
+ }
668
+
604
669
  function installZshrcBlock(startMarker, endMarker, block) {
605
670
  const zshrc = path.join(os.homedir(), ".zshrc");
606
671
  let content = fs.existsSync(zshrc) ? fs.readFileSync(zshrc, "utf8") : "";
@@ -615,6 +680,13 @@ function installZshrcBlock(startMarker, endMarker, block) {
615
680
  content = content.trimEnd() + "\n\n" + block + "\n";
616
681
  }
617
682
  fs.writeFileSync(zshrc, content, "utf8");
683
+ if (!content.includes("compinit")) {
684
+ console.log(
685
+ warn(
686
+ "Note: `compinit` not found in ~/.zshrc. Tab completion needs it; add this line: autoload -Uz compinit && compinit",
687
+ ),
688
+ );
689
+ }
618
690
  return zshrc;
619
691
  }
620
692