cursor-keys 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -21
- package/bin/cursor-keys.mjs +8 -5
- package/package.json +1 -2
- package/src/commands.mjs +23 -3
- package/src/config.mjs +15 -0
- package/src/profile.mjs +41 -13
package/README.md
CHANGED
|
@@ -1,27 +1,42 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
```
|
|
4
|
+
██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ ██╗ ██╗███████╗██╗ ██╗███████╗
|
|
5
|
+
██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ ██║ ██╔╝██╔════╝╚██╗ ██╔╝██╔════╝
|
|
6
|
+
██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ █████╔╝ █████╗ ╚████╔╝ ███████╗
|
|
7
|
+
██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ ██╔═██╗ ██╔══╝ ╚██╔╝ ╚════██║
|
|
8
|
+
╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ ██║ ██╗███████╗ ██║ ███████║
|
|
9
|
+
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚══════╝
|
|
10
|
+
```
|
|
4
11
|
|
|
5
|
-
|
|
12
|
+
**Per-project Cursor profiles so every window uses the right BYOK API key.**
|
|
6
13
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
14
|
+
[](LICENSE)
|
|
15
|
+
[](https://nodejs.org)
|
|
16
|
+
[](#development)
|
|
17
|
+
|
|
18
|
+
[Website](https://l-ubu.github.io/cursor-keys/) · [Install](#install) · [Commands](#commands) · [Config](#config)
|
|
19
|
+
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
---
|
|
11
23
|
|
|
24
|
+
Cursor stores your OpenAI-compatible API key (LiteLLM, Vercel AI Gateway, corporate proxy, ...) **once per profile**, not per repo. If you bill five client projects, you either babysit Settings all day or bill the wrong project. `cursor-keys` gives each project its own isolated profile and makes opening, switching, and checking keys automatic.
|
|
25
|
+
|
|
26
|
+
```
|
|
12
27
|
$ cursor-keys status
|
|
13
28
|
|
|
14
|
-
Project
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
29
|
+
Project | Color | Windows | Key | Repo
|
|
30
|
+
----------+----------+---------+-----+------------------
|
|
31
|
+
acme | #4f8cff | 1 | yes | ~/Projects/acme
|
|
32
|
+
client-a | #51cf66 | 0 | no | ~/Projects/client-a
|
|
33
|
+
client-b | #845ef7 | 1 | yes | ~/Projects/client-b
|
|
19
34
|
|
|
20
|
-
$ cursor-keys doctor
|
|
35
|
+
$ cursor-keys doctor acme
|
|
21
36
|
|
|
22
|
-
✓ OpenAI
|
|
37
|
+
✓ OpenAI API key row present
|
|
23
38
|
✓ Explore subagent overrides present
|
|
24
|
-
✓ Base URL (
|
|
39
|
+
✓ Base URL (gateway.example.com/v1) in profile state
|
|
25
40
|
```
|
|
26
41
|
|
|
27
42
|
## Install
|
|
@@ -36,8 +51,8 @@ Requires Node ≥18, the Cursor CLI (`Shell Command: Install 'cursor' in PATH`),
|
|
|
36
51
|
## Quick start
|
|
37
52
|
|
|
38
53
|
```bash
|
|
39
|
-
# one-time
|
|
40
|
-
cursor-keys config baseUrl https://
|
|
54
|
+
# one-time: point at your OpenAI-compatible gateway
|
|
55
|
+
cursor-keys config baseUrl https://gateway.example.com/v1
|
|
41
56
|
|
|
42
57
|
# one-time per project
|
|
43
58
|
cursor-keys add acme --path ~/Projects/acme # register + create profile
|
|
@@ -64,7 +79,7 @@ One window, one profile, one key. Each project gets an isolated `--user-data-dir
|
|
|
64
79
|
|
|
65
80
|
Settings flow one way, main to profiles: `sync` re-copies your main `settings.json` into every profile and re-applies that profile's title bar color on top. `open` does this automatically before every launch, and open windows hot-reload the file, so tweaks in your main Cursor show up everywhere. Changes made inside a profile window are overwritten on the next sync.
|
|
66
81
|
|
|
67
|
-
`clone` copies your main Cursor state (SQLite backup) so profiles open signed-in with your theme and model config. `set-key` encrypts your project key with Cursor's own safeStorage (Chromium OSCrypt, key from the "Cursor Safe Storage" Keychain item) and writes it into the profile's secret store, round-trip verified. macOS asks for Keychain permission once; click Always Allow.
|
|
82
|
+
`clone` copies your main Cursor state (SQLite backup) so profiles open signed-in with your theme and model config. `set-key` encrypts your project key with Cursor's own safeStorage (Chromium OSCrypt, key from the "Cursor Safe Storage" macOS Keychain item) and writes it into the profile's secret store, round-trip verified. macOS asks for Keychain permission once; click Always Allow.
|
|
68
83
|
|
|
69
84
|
## "Which key am I on?" in three layers
|
|
70
85
|
|
|
@@ -83,7 +98,7 @@ Settings flow one way, main to profiles: `sync` re-copies your main `settings.js
|
|
|
83
98
|
| `open [name]` | Launch Cursor with the profile (auto-detect from cwd) |
|
|
84
99
|
| `status` | Projects, colors, running windows, key configured |
|
|
85
100
|
| `current` | Mapping for the current directory |
|
|
86
|
-
| `config [baseUrl [URL]]`
|
|
101
|
+
| `config [get <key> · baseUrl [URL]]` | Show summary, print one value, or set the gateway base URL |
|
|
87
102
|
| `sync [name]` | Re-copy main Cursor settings into profiles |
|
|
88
103
|
| `doctor [name]` | Health checks: key row, base URL, subagent overrides |
|
|
89
104
|
| `wrap [--install]` | zsh function so plain `cursor <path>` auto-picks the profile |
|
|
@@ -98,14 +113,21 @@ Settings flow one way, main to profiles: `sync` re-copies your main `settings.js
|
|
|
98
113
|
```json
|
|
99
114
|
{
|
|
100
115
|
"profilesRoot": "~/.cursor-profiles",
|
|
101
|
-
"baseUrl": "https://
|
|
116
|
+
"baseUrl": "https://gateway.example.com/v1",
|
|
102
117
|
"projects": {
|
|
103
118
|
"acme": { "path": "~/Projects/acme", "color": "#4f8cff" }
|
|
104
119
|
}
|
|
105
120
|
}
|
|
106
121
|
```
|
|
107
122
|
|
|
108
|
-
`baseUrl` is your gateway's OpenAI-compatible endpoint
|
|
123
|
+
`baseUrl` is your gateway's OpenAI-compatible endpoint. `doctor` verifies it per profile.
|
|
124
|
+
|
|
125
|
+
Read a single value for scripts (stdout only, exit 1 on unknown keys):
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
cursor-keys config get baseUrl
|
|
129
|
+
cursor-keys config get profilesRoot
|
|
130
|
+
```
|
|
109
131
|
|
|
110
132
|
## Security
|
|
111
133
|
|
package/bin/cursor-keys.mjs
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { runCommand } from "../src/commands.mjs";
|
|
4
|
-
import { bold,
|
|
4
|
+
import { bold, dim, reset, yellow } from "../src/ansi.mjs";
|
|
5
5
|
|
|
6
6
|
const BANNER = `
|
|
7
|
-
${
|
|
8
|
-
${
|
|
9
|
-
${
|
|
7
|
+
${yellow} ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ ██╗ ██╗███████╗██╗ ██╗███████╗
|
|
8
|
+
${yellow}██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ ██║ ██╔╝██╔════╝╚██╗ ██╔╝██╔════╝
|
|
9
|
+
${yellow}██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ █████╔╝ █████╗ ╚████╔╝ ███████╗
|
|
10
|
+
${yellow}██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ ██╔═██╗ ██╔══╝ ╚██╔╝ ╚════██║
|
|
11
|
+
${yellow}╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ ██║ ██╗███████╗ ██║ ███████║
|
|
12
|
+
${yellow} ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚══════╝${reset}${dim} per-project Cursor API keys
|
|
10
13
|
`;
|
|
11
14
|
|
|
12
15
|
function printHelp() {
|
|
@@ -31,7 +34,7 @@ function printHelp() {
|
|
|
31
34
|
);
|
|
32
35
|
console.log(" current Show mapping for cwd");
|
|
33
36
|
console.log(
|
|
34
|
-
" config [baseUrl [URL]]
|
|
37
|
+
" config [get <key>|baseUrl [URL]] Show, read, or set config values",
|
|
35
38
|
);
|
|
36
39
|
console.log(
|
|
37
40
|
" sync [name] Re-copy main Cursor settings into profiles",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursor-keys",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
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": {
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
"cursor",
|
|
20
20
|
"cli",
|
|
21
21
|
"api-key",
|
|
22
|
-
"bonzai",
|
|
23
22
|
"byok",
|
|
24
23
|
"developer-tools",
|
|
25
24
|
"productivity"
|
package/src/commands.mjs
CHANGED
|
@@ -8,8 +8,10 @@ import {
|
|
|
8
8
|
baseUrlFragment,
|
|
9
9
|
collapseHome,
|
|
10
10
|
CONFIG_FILE,
|
|
11
|
+
CONFIG_GETTABLE_KEYS,
|
|
11
12
|
expandHome,
|
|
12
13
|
findProjectByPath,
|
|
14
|
+
getConfigValue,
|
|
13
15
|
getProject,
|
|
14
16
|
loadConfig,
|
|
15
17
|
pickColorForName,
|
|
@@ -177,12 +179,30 @@ function ask(question) {
|
|
|
177
179
|
async function cmdConfig(args) {
|
|
178
180
|
const { flags, positional } = parseFlags(args);
|
|
179
181
|
if (flags.help) {
|
|
180
|
-
console.log(
|
|
182
|
+
console.log(
|
|
183
|
+
"Usage: cursor-keys config Show config summary",
|
|
184
|
+
);
|
|
185
|
+
console.log(
|
|
186
|
+
" cursor-keys config get <key> Print one value (baseUrl, profilesRoot)",
|
|
187
|
+
);
|
|
188
|
+
console.log(
|
|
189
|
+
" cursor-keys config baseUrl <URL> Set gateway base URL",
|
|
190
|
+
);
|
|
181
191
|
return;
|
|
182
192
|
}
|
|
183
193
|
const config = loadConfig();
|
|
184
194
|
const [key, value] = positional;
|
|
185
195
|
|
|
196
|
+
if (key === "get") {
|
|
197
|
+
if (!value) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`Missing config key. Usage: cursor-keys config get <${CONFIG_GETTABLE_KEYS.join("|")}>`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
console.log(getConfigValue(config, value));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
186
206
|
if (!key) {
|
|
187
207
|
const count = Object.keys(config.projects).length;
|
|
188
208
|
console.log(
|
|
@@ -295,7 +315,7 @@ async function cmdInit(args) {
|
|
|
295
315
|
}
|
|
296
316
|
saveConfig(config);
|
|
297
317
|
console.log(
|
|
298
|
-
`\nNext: cursor-keys add <name> --clone main (creates profile + copies
|
|
318
|
+
`\nNext: cursor-keys add <name> --clone main (creates profile + copies main Cursor settings)`,
|
|
299
319
|
);
|
|
300
320
|
}
|
|
301
321
|
|
|
@@ -516,7 +536,7 @@ async function cmdDoctor(args) {
|
|
|
516
536
|
if (fs.existsSync(db)) {
|
|
517
537
|
const hasKey = stateDbHasKey(db, SECRET_OPENAI_KEY);
|
|
518
538
|
console.log(
|
|
519
|
-
` ${hasKey ? ok("✓") : warn("✗")} OpenAI
|
|
539
|
+
` ${hasKey ? ok("✓") : warn("✗")} OpenAI API key row present`,
|
|
520
540
|
);
|
|
521
541
|
if (hasKey) {
|
|
522
542
|
const mainDb = path.join(
|
package/src/config.mjs
CHANGED
|
@@ -98,6 +98,21 @@ export function baseUrlFragment(config) {
|
|
|
98
98
|
return String(config.baseUrl || "").replace(/^https?:\/\//, "");
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
export const CONFIG_GETTABLE_KEYS = ["baseUrl", "profilesRoot"];
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {ReturnType<typeof loadConfig>} config
|
|
105
|
+
* @param {string} key
|
|
106
|
+
*/
|
|
107
|
+
export function getConfigValue(config, key) {
|
|
108
|
+
if (!CONFIG_GETTABLE_KEYS.includes(key)) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`Unknown config key: ${key}. Known keys: ${CONFIG_GETTABLE_KEYS.join(", ")}`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return config[key];
|
|
114
|
+
}
|
|
115
|
+
|
|
101
116
|
const COLOR_PALETTE = [
|
|
102
117
|
"#4f8cff",
|
|
103
118
|
"#ff6b6b",
|
package/src/profile.mjs
CHANGED
|
@@ -143,22 +143,50 @@ export function needsFirstRunChecklist(profileDir) {
|
|
|
143
143
|
return !fs.existsSync(path.join(profileDir, SETUP_MARKER));
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* @param {string} projectName
|
|
148
|
+
* @param {string} baseUrl
|
|
149
|
+
* @param {string} platform Node process.platform
|
|
150
|
+
*/
|
|
151
|
+
export function buildFirstRunChecklist(
|
|
152
|
+
projectName,
|
|
153
|
+
baseUrl = "https://gateway.example.com/v1",
|
|
154
|
+
platform = process.platform,
|
|
155
|
+
) {
|
|
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
|
+
}
|
|
170
|
+
|
|
171
|
+
return `
|
|
172
|
+
First-time setup for "${projectName}" · copy-paste in order:
|
|
173
|
+
|
|
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
|
+
|
|
146
185
|
export function printFirstRunChecklist(
|
|
147
186
|
projectName,
|
|
148
|
-
baseUrl = "https://
|
|
187
|
+
baseUrl = "https://gateway.example.com/v1",
|
|
149
188
|
) {
|
|
150
|
-
console.log(
|
|
151
|
-
First-time setup for "${projectName}" in this Cursor profile:
|
|
152
|
-
|
|
153
|
-
1. Cursor Settings → Models → API keys
|
|
154
|
-
2. Paste your project Bonzai API key under OpenAI API Key
|
|
155
|
-
3. Enable "Use OpenAI API Key" and "Override OpenAI Base URL"
|
|
156
|
-
4. Base URL: ${baseUrl}
|
|
157
|
-
5. Explore Subagent Model: Composer 2.5
|
|
158
|
-
6. Add custom Bonzai models and disable non-Bonzai models
|
|
159
|
-
|
|
160
|
-
Then run: cursor-keys doctor ${projectName}
|
|
161
|
-
`);
|
|
189
|
+
console.log(buildFirstRunChecklist(projectName, baseUrl, process.platform));
|
|
162
190
|
}
|
|
163
191
|
|
|
164
192
|
export function createProfile({ profileDir, projectName, color, cloneFrom }) {
|