@connxio/cli 0.1.3 → 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 ADDED
@@ -0,0 +1,280 @@
1
+ # Connxio CLI
2
+
3
+ Connxio CLI provides the `connxio` command and an MCP server for Connxio management APIs.
4
+
5
+ The current implementation focuses on MCP usage through:
6
+
7
+ ```bash
8
+ connxio mcp serve
9
+ ```
10
+
11
+ ## Prerequisites
12
+
13
+ - Node.js 24 or newer
14
+ - Connxio OAuth client credentials
15
+ - A Connxio subscription-scoped API key for each subscription context you want to use
16
+
17
+ ## Install
18
+
19
+ Install the CLI globally from npm:
20
+
21
+ ```bash
22
+ npm i -g @connxio/cli
23
+ ```
24
+
25
+ Verify installation:
26
+
27
+ ```bash
28
+ connxio --help
29
+ ```
30
+
31
+ ## Configure OAuth
32
+
33
+ ```bash
34
+ connxio auth configure
35
+ ```
36
+
37
+ You will be prompted for:
38
+
39
+ - OAuth client id
40
+ - OAuth scope, defaulting to `api://connxio/.default`
41
+ - OAuth client secret
42
+
43
+ Check status:
44
+
45
+ ```bash
46
+ connxio auth status
47
+ ```
48
+
49
+ Clear OAuth configuration:
50
+
51
+ ```bash
52
+ connxio auth clear
53
+ ```
54
+
55
+ You can also configure OAuth with environment variables:
56
+
57
+ ```bash
58
+ CONNXIO_OAUTH_CLIENT_ID="<client-id>"
59
+ CONNXIO_OAUTH_CLIENT_SECRET="<client-secret>"
60
+ CONNXIO_OAUTH_TOKEN_URL="https://api.connxio.com/oauth/token"
61
+ CONNXIO_OAUTH_SCOPE="api://connxio/.default"
62
+ ```
63
+
64
+ `CONNXIO_OAUTH_TOKEN_URL` and `CONNXIO_OAUTH_SCOPE` are optional overrides.
65
+
66
+ ## Configure Contexts
67
+
68
+ A context represents one Connxio subscription. Connxio API keys are subscription-scoped, so add one context for each subscription you want to use.
69
+
70
+ ```bash
71
+ connxio context add
72
+ ```
73
+
74
+ The CLI calls `/v2/subscriptions/current` with the provided API key and stores the subscription/company metadata locally. API keys are stored through the credential abstraction, not in `config.json`.
75
+
76
+ List contexts:
77
+
78
+ ```bash
79
+ connxio context list
80
+ ```
81
+
82
+ Set a default context:
83
+
84
+ ```bash
85
+ connxio context default <context-id>
86
+ ```
87
+
88
+ Remove a context:
89
+
90
+ ```bash
91
+ connxio context remove <context-id>
92
+ ```
93
+
94
+ ## Register With VS Code
95
+
96
+ VS Code 1.99+ supports MCP servers natively via GitHub Copilot agent mode.
97
+
98
+ **Option 1 — user settings (all projects)**
99
+
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
+
106
+ ```json
107
+ {
108
+ "servers": {
109
+ "connxio": {
110
+ "type": "stdio",
111
+ "command": "connxio",
112
+ "args": ["mcp", "serve"]
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ **Option 2 — workspace settings (this project only)**
119
+
120
+ Create `.vscode/mcp.json` in your project root:
121
+
122
+ ```json
123
+ {
124
+ "servers": {
125
+ "connxio": {
126
+ "type": "stdio",
127
+ "command": "connxio",
128
+ "args": ["mcp", "serve"]
129
+ }
130
+ }
131
+ }
132
+ ```
133
+
134
+ The Connxio MCP server will now be available when you open this project in VS Code.
135
+
136
+ If you need to pass environment variables (e.g. for OAuth or a custom API base URL), add an `env` block:
137
+
138
+ ```json
139
+ {
140
+ "servers": {
141
+ "connxio": {
142
+ "type": "stdio",
143
+ "command": "connxio",
144
+ "args": ["mcp", "serve"],
145
+ "env": {
146
+ "CONNXIO_OAUTH_CLIENT_ID": "<client-id>",
147
+ "CONNXIO_OAUTH_CLIENT_SECRET": "<client-secret>"
148
+ }
149
+ }
150
+ }
151
+ }
152
+ ```
153
+
154
+ Do not commit files containing OAuth client secrets to source control.
155
+
156
+ ## Register With Claude Code
157
+
158
+ Register the installed MCP server:
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
+
166
+ ```bash
167
+ claude mcp add --scope user --transport stdio connxio -- connxio mcp serve
168
+ ```
169
+
170
+ Verify registration:
171
+
172
+ ```bash
173
+ claude mcp list
174
+ ```
175
+
176
+ If you need to replace an existing registration:
177
+
178
+ ```bash
179
+ claude mcp remove connxio
180
+ claude mcp add --transport stdio connxio -- connxio mcp serve
181
+ ```
182
+
183
+ ## API Base URL
184
+
185
+ The default API base URL is:
186
+
187
+ ```text
188
+ https://api.connxio.com
189
+ ```
190
+
191
+ Override it per context:
192
+
193
+ ```bash
194
+ connxio context add --base-url http://localhost:5119/api
195
+ ```
196
+
197
+ Or with an environment variable:
198
+
199
+ ```bash
200
+ CONNXIO_API_BASE_URL="http://localhost:5119/api"
201
+ ```
202
+
203
+ For localhost development with self-signed HTTPS certificates, the CLI automatically allows insecure TLS for localhost URLs. For other local development endpoints, use:
204
+
205
+ ```bash
206
+ CONNXIO_INSECURE_TLS=true
207
+ ```
208
+
209
+ Do not use insecure TLS settings in production.
210
+
211
+ ## Run Diagnostics
212
+
213
+ ```bash
214
+ connxio mcp doctor
215
+ ```
216
+
217
+ For HTTP troubleshooting, enable redacted request diagnostics:
218
+
219
+ ```bash
220
+ CONNXIO_DEBUG_HTTP=true connxio mcp doctor
221
+ ```
222
+
223
+ ## MCP Tools
224
+
225
+ The MCP server exposes non-message Connxio v2 management operations.
226
+
227
+ Context and subscription tools:
228
+
229
+ - `list_contexts`
230
+ - `get_current_context`
231
+ - `list_subscriptions`
232
+ - `get_current_subscription`
233
+
234
+ Integration tools:
235
+
236
+ - `list_integrations`
237
+ - `get_integration`
238
+ - `create_integration`
239
+ - `create_integration_no_validation`
240
+ - `update_integration`
241
+ - `delete_integration`
242
+
243
+ Code component tools:
244
+
245
+ - `list_code_components`
246
+ - `get_code_component`
247
+ - `get_code_component_versions`
248
+ - `create_code_component`
249
+ - `deprecate_code_component`
250
+ - `rename_code_component`
251
+ - `delete_code_component`
252
+
253
+ Environment variable tools:
254
+
255
+ - `list_environment_variables`
256
+ - `get_environment_variable`
257
+ - `create_environment_variable`
258
+ - `delete_environment_variable`
259
+
260
+ Security configuration tools:
261
+
262
+ - `list_security_configs`
263
+ - `get_security_config`
264
+ - `create_security_config`
265
+ - `delete_security_config`
266
+
267
+ Write tools require `contextId`. Destructive tools require `contextId` and `confirm: true`.
268
+
269
+ `/messages` operations are intentionally not exposed yet.
270
+
271
+ ## Local Config Files
272
+
273
+ Non-secret config is stored in:
274
+
275
+ - macOS/Linux: `${XDG_CONFIG_HOME:-~/.config}/connxio/config.json`
276
+ - Windows: `%APPDATA%\connxio\config.json`
277
+
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.3";
11225
+ var version$1 = "0.1.5";
11226
11226
  var package_default = {
11227
11227
  name,
11228
11228
  version: version$1,
@@ -11246,8 +11246,13 @@ var package_default = {
11246
11246
  types: "./dist/index.d.mts",
11247
11247
  exports: { ".": "./dist/index.mjs" },
11248
11248
  publishConfig: { "access": "public" },
11249
+ scripts: {
11250
+ "prepack": "cp ../../README.md ./README.md",
11251
+ "postpack": "rm -f ./README.md"
11252
+ },
11249
11253
  dependencies: {
11250
11254
  "@modelcontextprotocol/sdk": "^1.29.0",
11255
+ "@napi-rs/keyring": "^1.3.0",
11251
11256
  "commander": "^14.0.3",
11252
11257
  "update-notifier": "^7.3.1",
11253
11258
  "yazl": "^3.3.1",
@@ -11333,51 +11338,157 @@ function isNodeError$1(error) {
11333
11338
  }
11334
11339
  //#endregion
11335
11340
  //#region packages/cli/src/connxio/credentials.ts
11336
- function getCredentialStoreDescription() {
11337
- return `local file (${getCredentialPath()})`;
11341
+ const KEYRING_SERVICE_NAME = "com.connxio.cli";
11342
+ let credentialBackendPromise;
11343
+ let fileFallbackWarningEmitted = false;
11344
+ async function getCredentialStoreDescription() {
11345
+ return (await getCredentialBackend()).description;
11338
11346
  }
11339
11347
  async function deleteApiKey(ref) {
11340
- const store = await readCredentialFile();
11341
- delete store.apiKeys[ref];
11342
- await writeCredentialFile(store);
11348
+ await deleteCredential("apiKey", ref);
11343
11349
  }
11344
11350
  async function deleteOAuthClientSecret(ref) {
11345
- const store = await readCredentialFile();
11346
- delete store.oauthClientSecrets[ref];
11347
- await writeCredentialFile(store);
11351
+ await deleteCredential("oauthClientSecret", ref);
11348
11352
  }
11349
11353
  async function getApiKey(ref) {
11350
- const apiKey = (await readCredentialFile()).apiKeys[ref];
11351
- if (!apiKey) throw new Error(`Missing credential for context ${ref}. Run \`connxio context add ${ref}\`.`);
11352
- return apiKey;
11354
+ return getCredential("apiKey", ref);
11353
11355
  }
11354
11356
  async function getOAuthClientSecret(ref) {
11355
- const clientSecret = (await readCredentialFile()).oauthClientSecrets[ref];
11356
- if (!clientSecret) throw new Error("Missing OAuth client secret. Run `connxio auth configure`.");
11357
- return clientSecret;
11357
+ return getCredential("oauthClientSecret", ref);
11358
11358
  }
11359
11359
  async function hasApiKey(ref) {
11360
- const store = await readCredentialFile();
11361
- return typeof store.apiKeys[ref] === "string" && store.apiKeys[ref].length > 0;
11360
+ return hasCredential("apiKey", ref);
11362
11361
  }
11363
11362
  async function hasOAuthClientSecret(ref) {
11364
- const store = await readCredentialFile();
11365
- return typeof store.oauthClientSecrets[ref] === "string" && store.oauthClientSecrets[ref].length > 0;
11363
+ return hasCredential("oauthClientSecret", ref);
11366
11364
  }
11367
11365
  async function setApiKey(ref, apiKey) {
11368
- const trimmed = apiKey.trim();
11369
- if (!trimmed) throw new Error("API key cannot be empty.");
11370
- const store = await readCredentialFile();
11371
- store.apiKeys[ref] = trimmed;
11372
- await writeCredentialFile(store);
11366
+ await setCredential("apiKey", ref, apiKey);
11373
11367
  }
11374
11368
  async function setOAuthClientSecret(ref, clientSecret) {
11375
- const trimmed = clientSecret.trim();
11376
- if (!trimmed) throw new Error("OAuth client secret cannot be empty.");
11377
- const store = await readCredentialFile();
11378
- store.oauthClientSecrets[ref] = trimmed;
11379
- await writeCredentialFile(store);
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
+ }
11380
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
+ };
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
+ };
11381
11492
  function getCredentialPath() {
11382
11493
  return path.join(getConfigDir(), "credentials.json");
11383
11494
  }
@@ -11429,6 +11540,9 @@ function isStringRecord(value) {
11429
11540
  function isNodeError(error) {
11430
11541
  return error instanceof Error && "code" in error;
11431
11542
  }
11543
+ function formatError$4(error) {
11544
+ return error instanceof Error ? error.message : String(error);
11545
+ }
11432
11546
  //#endregion
11433
11547
  //#region packages/cli/src/connxio/http.ts
11434
11548
  function allowInsecureTlsIfLocal(url) {
@@ -33183,7 +33297,7 @@ function registerMcpCommands(program) {
33183
33297
  const oauth = await getOAuthStatus();
33184
33298
  const problems = [];
33185
33299
  console.log(`Config: ${getConfigPath()}`);
33186
- console.log(`Credential store: ${getCredentialStoreDescription()}`);
33300
+ console.log(`Credential store: ${await getCredentialStoreDescription()}`);
33187
33301
  if (!oauth.configured) problems.push("OAuth is not configured. Run `connxio auth configure`.");
33188
33302
  else if (!oauth.hasClientSecret) problems.push("OAuth client secret is missing. Run `connxio auth configure`.");
33189
33303
  if (contexts.length === 0) problems.push("No contexts configured. Run `connxio context add`.");