@connxio/cli 0.1.4 → 0.1.5
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 +21 -15
- package/dist/index.mjs +140 -30
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -40,8 +40,6 @@ You will be prompted for:
|
|
|
40
40
|
- OAuth scope, defaulting to `api://connxio/.default`
|
|
41
41
|
- OAuth client secret
|
|
42
42
|
|
|
43
|
-
The OAuth token URL is always `https://api.connxio.com/oauth/token`.
|
|
44
|
-
|
|
45
43
|
Check status:
|
|
46
44
|
|
|
47
45
|
```bash
|
|
@@ -65,8 +63,6 @@ CONNXIO_OAUTH_SCOPE="api://connxio/.default"
|
|
|
65
63
|
|
|
66
64
|
`CONNXIO_OAUTH_TOKEN_URL` and `CONNXIO_OAUTH_SCOPE` are optional overrides.
|
|
67
65
|
|
|
68
|
-
Do not commit OAuth client secrets or put them in MCP client config unless you explicitly accept that local setup tradeoff.
|
|
69
|
-
|
|
70
66
|
## Configure Contexts
|
|
71
67
|
|
|
72
68
|
A context represents one Connxio subscription. Connxio API keys are subscription-scoped, so add one context for each subscription you want to use.
|
|
@@ -101,17 +97,19 @@ VS Code 1.99+ supports MCP servers natively via GitHub Copilot agent mode.
|
|
|
101
97
|
|
|
102
98
|
**Option 1 — user settings (all projects)**
|
|
103
99
|
|
|
104
|
-
Open
|
|
100
|
+
Open the command palette (`Ctrl+Shift+P`/`Cmd+Shift+P`) and select `MCP: Add server`.
|
|
101
|
+
|
|
102
|
+
Select `stdio` transport, `connxio mcp serve` as the command, and set the name to `connxio`. This will create a user-level MCP server registration that is available in all projects.
|
|
103
|
+
|
|
104
|
+
Alternatively, edit the user level mcp.json config file directly. Open the command palette and select `MCP: Open User Configuration`, then add:
|
|
105
105
|
|
|
106
106
|
```json
|
|
107
107
|
{
|
|
108
|
-
"
|
|
109
|
-
"
|
|
110
|
-
"
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
"args": ["mcp", "serve"]
|
|
114
|
-
}
|
|
108
|
+
"servers": {
|
|
109
|
+
"connxio": {
|
|
110
|
+
"type": "stdio",
|
|
111
|
+
"command": "connxio",
|
|
112
|
+
"args": ["mcp", "serve"]
|
|
115
113
|
}
|
|
116
114
|
}
|
|
117
115
|
}
|
|
@@ -133,7 +131,7 @@ Create `.vscode/mcp.json` in your project root:
|
|
|
133
131
|
}
|
|
134
132
|
```
|
|
135
133
|
|
|
136
|
-
|
|
134
|
+
The Connxio MCP server will now be available when you open this project in VS Code.
|
|
137
135
|
|
|
138
136
|
If you need to pass environment variables (e.g. for OAuth or a custom API base URL), add an `env` block:
|
|
139
137
|
|
|
@@ -159,6 +157,12 @@ Do not commit files containing OAuth client secrets to source control.
|
|
|
159
157
|
|
|
160
158
|
Register the installed MCP server:
|
|
161
159
|
|
|
160
|
+
```bash
|
|
161
|
+
claude mcp add --transport stdio connxio -- connxio mcp serve
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
To register the MCP server at user level (available in all projects), add `--scope user`:
|
|
165
|
+
|
|
162
166
|
```bash
|
|
163
167
|
claude mcp add --scope user --transport stdio connxio -- connxio mcp serve
|
|
164
168
|
```
|
|
@@ -173,7 +177,7 @@ If you need to replace an existing registration:
|
|
|
173
177
|
|
|
174
178
|
```bash
|
|
175
179
|
claude mcp remove connxio
|
|
176
|
-
claude mcp add --
|
|
180
|
+
claude mcp add --transport stdio connxio -- connxio mcp serve
|
|
177
181
|
```
|
|
178
182
|
|
|
179
183
|
## API Base URL
|
|
@@ -271,4 +275,6 @@ Non-secret config is stored in:
|
|
|
271
275
|
- macOS/Linux: `${XDG_CONFIG_HOME:-~/.config}/connxio/config.json`
|
|
272
276
|
- Windows: `%APPDATA%\connxio\config.json`
|
|
273
277
|
|
|
274
|
-
Secrets are stored through the CLI credential abstraction. The
|
|
278
|
+
Secrets are stored through the CLI credential abstraction. The CLI now uses the OS keyring when available and falls back to `${XDG_CONFIG_HOME:-~/.config}/connxio/credentials.json` on macOS/Linux or `%APPDATA%\connxio\credentials.json` on Windows when secure storage is unavailable.
|
|
279
|
+
|
|
280
|
+
If you already have secrets in `credentials.json`, the CLI migrates them into the OS keyring lazily as they are used and removes the legacy file entries after a successful migration.
|
package/dist/index.mjs
CHANGED
|
@@ -11222,7 +11222,7 @@ function updateNotifier(options) {
|
|
|
11222
11222
|
//#endregion
|
|
11223
11223
|
//#region packages/cli/package.json
|
|
11224
11224
|
var name = "@connxio/cli";
|
|
11225
|
-
var version$1 = "0.1.
|
|
11225
|
+
var version$1 = "0.1.5";
|
|
11226
11226
|
var package_default = {
|
|
11227
11227
|
name,
|
|
11228
11228
|
version: version$1,
|
|
@@ -11252,6 +11252,7 @@ var package_default = {
|
|
|
11252
11252
|
},
|
|
11253
11253
|
dependencies: {
|
|
11254
11254
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
11255
|
+
"@napi-rs/keyring": "^1.3.0",
|
|
11255
11256
|
"commander": "^14.0.3",
|
|
11256
11257
|
"update-notifier": "^7.3.1",
|
|
11257
11258
|
"yazl": "^3.3.1",
|
|
@@ -11337,51 +11338,157 @@ function isNodeError$1(error) {
|
|
|
11337
11338
|
}
|
|
11338
11339
|
//#endregion
|
|
11339
11340
|
//#region packages/cli/src/connxio/credentials.ts
|
|
11340
|
-
|
|
11341
|
-
|
|
11341
|
+
const KEYRING_SERVICE_NAME = "com.connxio.cli";
|
|
11342
|
+
let credentialBackendPromise;
|
|
11343
|
+
let fileFallbackWarningEmitted = false;
|
|
11344
|
+
async function getCredentialStoreDescription() {
|
|
11345
|
+
return (await getCredentialBackend()).description;
|
|
11342
11346
|
}
|
|
11343
11347
|
async function deleteApiKey(ref) {
|
|
11344
|
-
|
|
11345
|
-
delete store.apiKeys[ref];
|
|
11346
|
-
await writeCredentialFile(store);
|
|
11348
|
+
await deleteCredential("apiKey", ref);
|
|
11347
11349
|
}
|
|
11348
11350
|
async function deleteOAuthClientSecret(ref) {
|
|
11349
|
-
|
|
11350
|
-
delete store.oauthClientSecrets[ref];
|
|
11351
|
-
await writeCredentialFile(store);
|
|
11351
|
+
await deleteCredential("oauthClientSecret", ref);
|
|
11352
11352
|
}
|
|
11353
11353
|
async function getApiKey(ref) {
|
|
11354
|
-
|
|
11355
|
-
if (!apiKey) throw new Error(`Missing credential for context ${ref}. Run \`connxio context add ${ref}\`.`);
|
|
11356
|
-
return apiKey;
|
|
11354
|
+
return getCredential("apiKey", ref);
|
|
11357
11355
|
}
|
|
11358
11356
|
async function getOAuthClientSecret(ref) {
|
|
11359
|
-
|
|
11360
|
-
if (!clientSecret) throw new Error("Missing OAuth client secret. Run `connxio auth configure`.");
|
|
11361
|
-
return clientSecret;
|
|
11357
|
+
return getCredential("oauthClientSecret", ref);
|
|
11362
11358
|
}
|
|
11363
11359
|
async function hasApiKey(ref) {
|
|
11364
|
-
|
|
11365
|
-
return typeof store.apiKeys[ref] === "string" && store.apiKeys[ref].length > 0;
|
|
11360
|
+
return hasCredential("apiKey", ref);
|
|
11366
11361
|
}
|
|
11367
11362
|
async function hasOAuthClientSecret(ref) {
|
|
11368
|
-
|
|
11369
|
-
return typeof store.oauthClientSecrets[ref] === "string" && store.oauthClientSecrets[ref].length > 0;
|
|
11363
|
+
return hasCredential("oauthClientSecret", ref);
|
|
11370
11364
|
}
|
|
11371
11365
|
async function setApiKey(ref, apiKey) {
|
|
11372
|
-
|
|
11373
|
-
if (!trimmed) throw new Error("API key cannot be empty.");
|
|
11374
|
-
const store = await readCredentialFile();
|
|
11375
|
-
store.apiKeys[ref] = trimmed;
|
|
11376
|
-
await writeCredentialFile(store);
|
|
11366
|
+
await setCredential("apiKey", ref, apiKey);
|
|
11377
11367
|
}
|
|
11378
11368
|
async function setOAuthClientSecret(ref, clientSecret) {
|
|
11379
|
-
|
|
11380
|
-
|
|
11381
|
-
|
|
11382
|
-
|
|
11383
|
-
await
|
|
11369
|
+
await setCredential("oauthClientSecret", ref, clientSecret);
|
|
11370
|
+
}
|
|
11371
|
+
async function deleteCredential(kind, ref) {
|
|
11372
|
+
const backend = await getCredentialBackend();
|
|
11373
|
+
await backend.delete(kind, ref);
|
|
11374
|
+
if (backend.type === "keyring") await deleteLegacyCredentialQuietly(kind, ref);
|
|
11375
|
+
}
|
|
11376
|
+
async function getCredential(kind, ref) {
|
|
11377
|
+
const backend = await getCredentialBackend();
|
|
11378
|
+
const credential = await backend.get(kind, ref);
|
|
11379
|
+
if (credential) return credential;
|
|
11380
|
+
if (backend.type === "keyring") {
|
|
11381
|
+
const legacyCredential = await fileCredentialBackend.get(kind, ref);
|
|
11382
|
+
if (legacyCredential) {
|
|
11383
|
+
await backend.set(kind, ref, legacyCredential);
|
|
11384
|
+
await deleteLegacyCredentialQuietly(kind, ref);
|
|
11385
|
+
return legacyCredential;
|
|
11386
|
+
}
|
|
11387
|
+
}
|
|
11388
|
+
throw new Error(getMissingCredentialMessage(kind, ref));
|
|
11389
|
+
}
|
|
11390
|
+
async function getCredentialBackend() {
|
|
11391
|
+
credentialBackendPromise ??= resolveCredentialBackend();
|
|
11392
|
+
return credentialBackendPromise;
|
|
11393
|
+
}
|
|
11394
|
+
function getCredentialFileKey(kind) {
|
|
11395
|
+
return kind === "apiKey" ? "apiKeys" : "oauthClientSecrets";
|
|
11396
|
+
}
|
|
11397
|
+
function getKeyringAccountName(kind, ref) {
|
|
11398
|
+
return kind === "apiKey" ? `api-key:${ref}` : `oauth-client-secret:${ref}`;
|
|
11399
|
+
}
|
|
11400
|
+
function getMissingCredentialMessage(kind, ref) {
|
|
11401
|
+
return kind === "apiKey" ? `Missing credential for context ${ref}. Run \`connxio context add ${ref}\`.` : "Missing OAuth client secret. Run `connxio auth configure`.";
|
|
11402
|
+
}
|
|
11403
|
+
async function hasCredential(kind, ref) {
|
|
11404
|
+
const backend = await getCredentialBackend();
|
|
11405
|
+
if (await backend.has(kind, ref)) return true;
|
|
11406
|
+
return backend.type === "keyring" ? fileCredentialBackend.has(kind, ref) : false;
|
|
11407
|
+
}
|
|
11408
|
+
function normalizeKeyringModule(value) {
|
|
11409
|
+
const candidate = isRecord$3(value) ? value : void 0;
|
|
11410
|
+
const defaultCandidate = candidate && Object.prototype.hasOwnProperty.call(candidate, "default") && isRecord$3(candidate["default"]) ? candidate["default"] : void 0;
|
|
11411
|
+
const normalizedCandidate = defaultCandidate ? {
|
|
11412
|
+
...defaultCandidate,
|
|
11413
|
+
...candidate
|
|
11414
|
+
} : candidate;
|
|
11415
|
+
if (!normalizedCandidate || typeof normalizedCandidate.AsyncEntry !== "function" || typeof normalizedCandidate.findCredentialsAsync !== "function") throw new Error("@napi-rs/keyring did not expose the expected API.");
|
|
11416
|
+
return {
|
|
11417
|
+
AsyncEntry: normalizedCandidate.AsyncEntry,
|
|
11418
|
+
findCredentialsAsync: normalizedCandidate.findCredentialsAsync
|
|
11419
|
+
};
|
|
11420
|
+
}
|
|
11421
|
+
async function resolveCredentialBackend() {
|
|
11422
|
+
try {
|
|
11423
|
+
const keyring = normalizeKeyringModule(await import("@napi-rs/keyring"));
|
|
11424
|
+
await keyring.findCredentialsAsync(KEYRING_SERVICE_NAME);
|
|
11425
|
+
return createKeyringCredentialBackend(keyring);
|
|
11426
|
+
} catch (error) {
|
|
11427
|
+
warnFileFallback(error);
|
|
11428
|
+
return fileCredentialBackend;
|
|
11429
|
+
}
|
|
11430
|
+
}
|
|
11431
|
+
async function setCredential(kind, ref, value) {
|
|
11432
|
+
const trimmed = value.trim();
|
|
11433
|
+
if (!trimmed) throw new Error(kind === "apiKey" ? "API key cannot be empty." : "OAuth client secret cannot be empty.");
|
|
11434
|
+
const backend = await getCredentialBackend();
|
|
11435
|
+
await backend.set(kind, ref, trimmed);
|
|
11436
|
+
if (backend.type === "keyring") await deleteLegacyCredentialQuietly(kind, ref);
|
|
11437
|
+
}
|
|
11438
|
+
function warnFileFallback(error) {
|
|
11439
|
+
if (fileFallbackWarningEmitted) return;
|
|
11440
|
+
fileFallbackWarningEmitted = true;
|
|
11441
|
+
console.warn(`Connxio secure credential storage unavailable; falling back to local file storage at ${getCredentialPath()}: ${formatError$4(error)}`);
|
|
11442
|
+
}
|
|
11443
|
+
async function deleteLegacyCredentialQuietly(kind, ref) {
|
|
11444
|
+
try {
|
|
11445
|
+
await fileCredentialBackend.delete(kind, ref);
|
|
11446
|
+
} catch {}
|
|
11447
|
+
}
|
|
11448
|
+
function createKeyringCredentialBackend(keyring) {
|
|
11449
|
+
const get = async (kind, ref) => {
|
|
11450
|
+
const credential = await new keyring.AsyncEntry(KEYRING_SERVICE_NAME, getKeyringAccountName(kind, ref)).getPassword();
|
|
11451
|
+
return typeof credential === "string" && credential.length > 0 ? credential : void 0;
|
|
11452
|
+
};
|
|
11453
|
+
return {
|
|
11454
|
+
description: "OS keyring",
|
|
11455
|
+
type: "keyring",
|
|
11456
|
+
async delete(kind, ref) {
|
|
11457
|
+
const entry = new keyring.AsyncEntry(KEYRING_SERVICE_NAME, getKeyringAccountName(kind, ref));
|
|
11458
|
+
if (await get(kind, ref) === void 0) return;
|
|
11459
|
+
await entry.deleteCredential();
|
|
11460
|
+
},
|
|
11461
|
+
get,
|
|
11462
|
+
async has(kind, ref) {
|
|
11463
|
+
return await get(kind, ref) !== void 0;
|
|
11464
|
+
},
|
|
11465
|
+
async set(kind, ref, value) {
|
|
11466
|
+
await new keyring.AsyncEntry(KEYRING_SERVICE_NAME, getKeyringAccountName(kind, ref)).setPassword(value);
|
|
11467
|
+
}
|
|
11468
|
+
};
|
|
11384
11469
|
}
|
|
11470
|
+
const fileCredentialBackend = {
|
|
11471
|
+
description: `local file (${getCredentialPath()})`,
|
|
11472
|
+
type: "file",
|
|
11473
|
+
async delete(kind, ref) {
|
|
11474
|
+
const store = await readCredentialFile();
|
|
11475
|
+
delete store[getCredentialFileKey(kind)][ref];
|
|
11476
|
+
await writeCredentialFile(store);
|
|
11477
|
+
},
|
|
11478
|
+
async get(kind, ref) {
|
|
11479
|
+
const credential = (await readCredentialFile())[getCredentialFileKey(kind)][ref];
|
|
11480
|
+
return typeof credential === "string" && credential.length > 0 ? credential : void 0;
|
|
11481
|
+
},
|
|
11482
|
+
async has(kind, ref) {
|
|
11483
|
+
const credential = (await readCredentialFile())[getCredentialFileKey(kind)][ref];
|
|
11484
|
+
return typeof credential === "string" && credential.length > 0;
|
|
11485
|
+
},
|
|
11486
|
+
async set(kind, ref, value) {
|
|
11487
|
+
const store = await readCredentialFile();
|
|
11488
|
+
store[getCredentialFileKey(kind)][ref] = value;
|
|
11489
|
+
await writeCredentialFile(store);
|
|
11490
|
+
}
|
|
11491
|
+
};
|
|
11385
11492
|
function getCredentialPath() {
|
|
11386
11493
|
return path.join(getConfigDir(), "credentials.json");
|
|
11387
11494
|
}
|
|
@@ -11433,6 +11540,9 @@ function isStringRecord(value) {
|
|
|
11433
11540
|
function isNodeError(error) {
|
|
11434
11541
|
return error instanceof Error && "code" in error;
|
|
11435
11542
|
}
|
|
11543
|
+
function formatError$4(error) {
|
|
11544
|
+
return error instanceof Error ? error.message : String(error);
|
|
11545
|
+
}
|
|
11436
11546
|
//#endregion
|
|
11437
11547
|
//#region packages/cli/src/connxio/http.ts
|
|
11438
11548
|
function allowInsecureTlsIfLocal(url) {
|
|
@@ -33187,7 +33297,7 @@ function registerMcpCommands(program) {
|
|
|
33187
33297
|
const oauth = await getOAuthStatus();
|
|
33188
33298
|
const problems = [];
|
|
33189
33299
|
console.log(`Config: ${getConfigPath()}`);
|
|
33190
|
-
console.log(`Credential store: ${getCredentialStoreDescription()}`);
|
|
33300
|
+
console.log(`Credential store: ${await getCredentialStoreDescription()}`);
|
|
33191
33301
|
if (!oauth.configured) problems.push("OAuth is not configured. Run `connxio auth configure`.");
|
|
33192
33302
|
else if (!oauth.hasClientSecret) problems.push("OAuth client secret is missing. Run `connxio auth configure`.");
|
|
33193
33303
|
if (contexts.length === 0) problems.push("No contexts configured. Run `connxio context add`.");
|