cursor-keys 1.2.0 → 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 +4 -1
- package/bin/cursor-keys.mjs +7 -3
- package/package.json +4 -2
- package/scripts/postinstall.mjs +22 -0
- package/src/ansi.mjs +14 -4
- package/src/commands.mjs +73 -1
- package/src/profile.mjs +39 -10
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`:
|
package/bin/cursor-keys.mjs
CHANGED
|
@@ -9,12 +9,13 @@ const BANNER = `
|
|
|
9
9
|
${yellow}██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ █████╔╝ █████╗ ╚████╔╝ ███████╗
|
|
10
10
|
${yellow}██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ ██╔═██╗ ██╔══╝ ╚██╔╝ ╚════██║
|
|
11
11
|
${yellow}╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ ██║ ██╗███████╗ ██║ ███████║
|
|
12
|
-
${yellow} ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚══════╝${reset}
|
|
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]
|
|
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.
|
|
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
|
-
|
|
32
|
-
|
|
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(
|
|
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
|
|
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 = [
|
|
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
|
|
package/src/profile.mjs
CHANGED
|
@@ -143,21 +143,50 @@ export function needsFirstRunChecklist(profileDir) {
|
|
|
143
143
|
return !fs.existsSync(path.join(profileDir, SETUP_MARKER));
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
/**
|
|
147
|
+
* @param {string} projectName
|
|
148
|
+
* @param {string} baseUrl
|
|
149
|
+
* @param {string} platform Node process.platform
|
|
150
|
+
*/
|
|
151
|
+
export function buildFirstRunChecklist(
|
|
147
152
|
projectName,
|
|
148
153
|
baseUrl = "https://gateway.example.com/v1",
|
|
154
|
+
platform = process.platform,
|
|
149
155
|
) {
|
|
150
|
-
|
|
151
|
-
|
|
156
|
+
if (platform === "darwin") {
|
|
157
|
+
return `
|
|
158
|
+
First-time setup for "${projectName}" · copy-paste in order:
|
|
159
|
+
|
|
160
|
+
cursor-keys clone ${projectName} # inherit sign-in, settings, model config from main Cursor
|
|
161
|
+
cursor-keys set-key ${projectName} # inject the project API key (encrypted, hidden prompt)
|
|
162
|
+
cursor-keys doctor ${projectName} # verify the profile
|
|
163
|
+
|
|
164
|
+
Manual alternative: Cursor Settings → Models → API keys, paste the key under
|
|
165
|
+
OpenAI API Key, enable "Override OpenAI Base URL" with: ${baseUrl}
|
|
166
|
+
|
|
167
|
+
Already inside this profile's window? Close it before running clone or set-key.
|
|
168
|
+
`;
|
|
169
|
+
}
|
|
152
170
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
3. Enable "Use OpenAI API Key" and "Override OpenAI Base URL"
|
|
156
|
-
4. Base URL: ${baseUrl}
|
|
157
|
-
5. Add your gateway models and disable models you do not use
|
|
171
|
+
return `
|
|
172
|
+
First-time setup for "${projectName}" · copy-paste in order:
|
|
158
173
|
|
|
159
|
-
|
|
160
|
-
|
|
174
|
+
cursor-keys clone ${projectName} # inherit sign-in, settings, model config from main Cursor
|
|
175
|
+
cursor-keys open ${projectName} # open once so Cursor creates its state
|
|
176
|
+
cursor-keys doctor ${projectName} # verify the profile
|
|
177
|
+
|
|
178
|
+
Then in that window: Cursor Settings → Models → API keys, paste the key under
|
|
179
|
+
OpenAI API Key, enable "Override OpenAI Base URL" with: ${baseUrl}
|
|
180
|
+
|
|
181
|
+
Already inside this profile's window? Close it before running clone.
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function printFirstRunChecklist(
|
|
186
|
+
projectName,
|
|
187
|
+
baseUrl = "https://gateway.example.com/v1",
|
|
188
|
+
) {
|
|
189
|
+
console.log(buildFirstRunChecklist(projectName, baseUrl, process.platform));
|
|
161
190
|
}
|
|
162
191
|
|
|
163
192
|
export function createProfile({ profileDir, projectName, color, cloneFrom }) {
|