@nexusmcp/cli 1.1.0 → 1.1.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.
Files changed (3) hide show
  1. package/README.md +8 -2
  2. package/bin/cli.js +188 -66
  3. package/package.json +7 -7
package/README.md CHANGED
@@ -15,8 +15,14 @@ token copy/paste is needed. The CLI creates a protected `.env.nexus`, project-lo
15
15
  `.mcp.json`, `.nexus/project.json`, and agent guidance without printing or embedding the
16
16
  credential in MCP configuration. For Codex, a local header helper reads `.env.nexus` at
17
17
  connection time, so a restarted client does not depend on a globally exported token.
18
- Existing configuration is preserved; conflicting project or credential context fails
19
- closed.
18
+ Existing configuration is preserved; conflicting project or credential context fails
19
+ closed.
20
+
21
+ The CLI restricts `.env.nexus` to the current account (`0600` on POSIX and an
22
+ inheritance-disabled user/SYSTEM ACL on Windows). `doctor` fails when that protection
23
+ cannot be verified. The generated Codex setting uses the supported
24
+ `http_headers_helper` contract; it emits the Authorization header only to Codex and
25
+ never prints the credential in normal CLI output.
20
26
 
21
27
  During the private beta the CLI connects to the hosted Cloud Run service by default.
22
28
  Use `--server=<https-url>` only for an explicitly approved alternate deployment.
package/bin/cli.js CHANGED
@@ -3,7 +3,7 @@
3
3
  import fs from 'fs';
4
4
  import path from 'path';
5
5
  import crypto from 'crypto';
6
- import { execSync, spawn } from 'child_process';
6
+ import { execSync, spawn, spawnSync } from 'child_process';
7
7
  import { fileURLToPath } from 'url';
8
8
 
9
9
  const CLI_DIR = path.dirname(fileURLToPath(import.meta.url));
@@ -25,7 +25,9 @@ const EXTENSION_TOOLS = [
25
25
  'save_skill',
26
26
  'search_skills',
27
27
  ].sort();
28
- const CODEX_AUTH_HELPER_PATH = path.join('.nexus', 'auth-headers.cjs');
28
+ const CODEX_AUTH_HELPER_PATH = path.join('.nexus', 'auth-headers.cjs');
29
+ const CODEX_AUTH_HELPER_SETTING = 'http_headers_helper = "node .nexus/auth-headers.cjs"';
30
+ const CODEX_LEGACY_ENV_SETTING = 'bearer_token_env_var = "NEXUS_API_KEY"';
29
31
  const CODEX_AUTH_HELPER_CONTENT = `#!/usr/bin/env node
30
32
  // Nexus-owned Codex HTTP header helper. The bearer secret stays in .env.nexus.
31
33
  const fs = require('node:fs');
@@ -105,6 +107,61 @@ function writeJsonFile(filePath, value) {
105
107
  function sha256(content) {
106
108
  return crypto.createHash('sha256').update(content).digest('hex');
107
109
  }
110
+
111
+ function windowsUserSid() {
112
+ const result = spawnSync('whoami.exe', ['/user', '/fo', 'csv', '/nh'], {
113
+ encoding: 'utf8',
114
+ windowsHide: true,
115
+ stdio: ['ignore', 'pipe', 'pipe'],
116
+ });
117
+ if (result.status !== 0) return '';
118
+ return result.stdout.match(/S-1-(?:\d+-)+\d+/)?.[0] || '';
119
+ }
120
+
121
+ function protectCredentialFile(filePath) {
122
+ const absolutePath = path.resolve(filePath);
123
+ if (process.platform !== 'win32') {
124
+ fs.chmodSync(absolutePath, 0o600);
125
+ return;
126
+ }
127
+
128
+ const userSid = windowsUserSid();
129
+ if (!userSid) throw new Error('Unable to resolve the current Windows account SID.');
130
+ const result = spawnSync('icacls.exe', [
131
+ absolutePath,
132
+ '/inheritance:r',
133
+ '/grant:r', `*${userSid}:(F)`,
134
+ '/grant:r', '*S-1-5-18:(F)',
135
+ '/remove:g', '*S-1-1-0', '*S-1-5-11', '*S-1-5-32-545',
136
+ ], {
137
+ encoding: 'utf8',
138
+ windowsHide: true,
139
+ stdio: ['ignore', 'pipe', 'pipe'],
140
+ });
141
+ if (result.status !== 0) {
142
+ throw new Error('Windows refused the restricted credential-file ACL.');
143
+ }
144
+ }
145
+
146
+ function credentialProtectionStatus(filePath) {
147
+ if (!fs.existsSync(filePath)) return { ok: false, detail: 'credential file is missing' };
148
+ if (process.platform !== 'win32') {
149
+ const permissions = fs.statSync(filePath).mode & 0o777;
150
+ return {
151
+ ok: (permissions & 0o077) === 0,
152
+ detail: `mode ${permissions.toString(8).padStart(3, '0')}`,
153
+ };
154
+ }
155
+ const result = spawnSync('icacls.exe', [path.resolve(filePath), '/verify'], {
156
+ encoding: 'utf8',
157
+ windowsHide: true,
158
+ stdio: ['ignore', 'pipe', 'pipe'],
159
+ });
160
+ return {
161
+ ok: result.status === 0,
162
+ detail: result.status === 0 ? 'restricted Windows ACL is readable and valid' : 'Windows ACL verification failed',
163
+ };
164
+ }
108
165
 
109
166
  function writeTextIfAbsent(filePath, content, label) {
110
167
  if (fs.existsSync(filePath) && !force) {
@@ -142,7 +199,7 @@ function ensureRemoteMcpJson(filePath, entry, label, explicitHttpType = false) {
142
199
  }
143
200
  }
144
201
 
145
- function ensureCodexAuthHelper() {
202
+ function ensureCodexAuthHelper() {
146
203
  const helperDir = path.dirname(CODEX_AUTH_HELPER_PATH);
147
204
  if (!fs.existsSync(helperDir)) fs.mkdirSync(helperDir, { recursive: true });
148
205
  if (fs.existsSync(CODEX_AUTH_HELPER_PATH)) {
@@ -154,17 +211,27 @@ function ensureCodexAuthHelper() {
154
211
  }
155
212
  fs.writeFileSync(CODEX_AUTH_HELPER_PATH, CODEX_AUTH_HELPER_CONTENT, { encoding: 'utf-8', mode: 0o700 });
156
213
  try { fs.chmodSync(CODEX_AUTH_HELPER_PATH, 0o700); } catch (_) { /* Windows ACLs may ignore POSIX mode. */ }
157
- console.log(' [OK] Created the local Codex credential helper.');
158
- }
214
+ console.log(' [OK] Created the local Codex credential helper.');
215
+ }
216
+
217
+ function inspectCodexAuthHelper() {
218
+ if (!fs.existsSync(CODEX_AUTH_HELPER_PATH)) {
219
+ return { action: 'create credential helper', label: '.nexus/auth-headers.cjs' };
220
+ }
221
+ const content = fs.readFileSync(CODEX_AUTH_HELPER_PATH, 'utf-8');
222
+ return content === CODEX_AUTH_HELPER_CONTENT
223
+ ? { action: 'healthy', label: '.nexus/auth-headers.cjs' }
224
+ : { action: 'refused', label: '.nexus/auth-headers.cjs', detail: 'existing helper is not Nexus-owned' };
225
+ }
159
226
 
160
227
  function ensureCodexProjectConfig(apiUrl) {
161
- const codexDir = '.codex';
162
- const configPath = path.join(codexDir, 'config.toml');
163
- const block = `[mcp_servers.nexus-mcp]\nurl = "${apiUrl}/mcp"\nbearer_token_env_var = "NEXUS_API_KEY"\nstartup_timeout_sec = 10\ntool_timeout_sec = 60\n`;
164
- if (!fs.existsSync(codexDir)) fs.mkdirSync(codexDir, { recursive: true });
165
- if (!fs.existsSync(configPath) || force) {
166
- fs.writeFileSync(configPath, block, 'utf-8');
167
- console.log(' [OK] Created project-scoped Codex MCP configuration.');
228
+ const codexDir = '.codex';
229
+ const configPath = path.join(codexDir, 'config.toml');
230
+ const block = `[mcp_servers.nexus-mcp]\nurl = "${apiUrl}/mcp"\n${CODEX_AUTH_HELPER_SETTING}\nstartup_timeout_sec = 10\ntool_timeout_sec = 60\n`;
231
+ if (!fs.existsSync(codexDir)) fs.mkdirSync(codexDir, { recursive: true });
232
+ if (!fs.existsSync(configPath)) {
233
+ fs.writeFileSync(configPath, block, 'utf-8');
234
+ console.log(' [OK] Created project-scoped Codex MCP configuration.');
168
235
  return;
169
236
  }
170
237
  const existing = fs.readFileSync(configPath, 'utf-8');
@@ -175,23 +242,28 @@ function ensureCodexProjectConfig(apiUrl) {
175
242
  console.error('[NEXUS-CLI] Existing Nexus Codex entry points to another server; refusing to replace it.');
176
243
  process.exit(1);
177
244
  }
178
- if (existingSection[1].includes('bearer_token_env_var = "NEXUS_API_KEY"')) {
179
- console.log(' [NOTE] Preserved existing Nexus entry in .codex/config.toml.');
180
- return;
181
- }
182
- const prefix = existingSection[0].startsWith('\n') ? '\n' : '';
183
- fs.writeFileSync(configPath, existing.replace(sectionPattern, `${prefix}${block.trimEnd()}`), 'utf-8');
184
- console.log(' [OK] Updated the Nexus Codex entry to use its project-local credential helper.');
245
+ if (existingSection[1].includes(CODEX_AUTH_HELPER_SETTING)) {
246
+ console.log(' [NOTE] Preserved existing Nexus entry in .codex/config.toml.');
247
+ return;
248
+ }
249
+ if (!existingSection[1].includes(CODEX_LEGACY_ENV_SETTING)) {
250
+ console.error('[NEXUS-CLI] Existing Nexus Codex entry has an unrecognized credential source; refusing to replace it.');
251
+ process.exit(1);
252
+ }
253
+ const prefix = existingSection[0].startsWith('\n') ? '\n' : '';
254
+ fs.writeFileSync(configPath, existing.replace(sectionPattern, `${prefix}${block.trimEnd()}`), 'utf-8');
255
+ console.log(' [OK] Migrated the Nexus Codex entry to its project-local credential helper.');
185
256
  return;
186
257
  }
187
258
  fs.appendFileSync(configPath, `${existing.endsWith('\n') ? '' : '\n'}\n${block}`, 'utf-8');
188
259
  console.log(' [OK] Added Nexus to existing .codex/config.toml without changing other servers.');
189
260
  }
190
261
 
191
- function readEnvCredential() {
192
- if (process.env.NEXUS_API_KEY) return process.env.NEXUS_API_KEY.trim();
193
- if (!fs.existsSync('.env.nexus')) return '';
194
- return fs.readFileSync('.env.nexus', 'utf-8').match(/^NEXUS_API_KEY=(.*)$/m)?.[1]?.trim() || '';
262
+ function readEnvCredential() {
263
+ if (fs.existsSync('.env.nexus')) {
264
+ return fs.readFileSync('.env.nexus', 'utf-8').match(/^NEXUS_API_KEY=(.*)$/m)?.[1]?.trim() || '';
265
+ }
266
+ return process.env.NEXUS_API_KEY?.trim() || '';
195
267
  }
196
268
 
197
269
  function doctorCheck(ok, label, detail) {
@@ -420,8 +492,8 @@ function removeCodexEntry(apiUrl, apply) {
420
492
  const sectionPattern = /(?:^|\n)\[mcp_servers\.nexus-mcp\]\s*\n([\s\S]*?)(?=\n\s*\[|(?![\s\S]))/m;
421
493
  const match = content.match(sectionPattern);
422
494
  if (!match) return { action: 'absent', label };
423
- const ownedCredentialSource = match[1].includes('http_headers_helper = "node .nexus/auth-headers.cjs"')
424
- || match[1].includes('bearer_token_env_var = "NEXUS_API_KEY"');
495
+ const ownedCredentialSource = match[1].includes(CODEX_AUTH_HELPER_SETTING)
496
+ || match[1].includes(CODEX_LEGACY_ENV_SETTING);
425
497
  if (!match[1].includes(`url = "${apiUrl}/mcp"`) || !ownedCredentialSource) {
426
498
  return { action: 'refused', label, detail: 'Nexus section does not match the recorded server and credential contract' };
427
499
  }
@@ -578,18 +650,24 @@ function runRepair(title = 'Repair') {
578
650
  repairJsonEntry(path.join('.cursor', 'mcp.json'), { url: `${project.api_url}/mcp`, headers: { Authorization: 'Bearer ${env:NEXUS_API_KEY}' } }, project.api_url, '.cursor/mcp.json', shouldApply),
579
651
  repairJsonEntry(path.join('.agents', 'mcp_config.json'), { serverUrl: `${project.api_url}/mcp`, headers: { Authorization: 'Bearer ${NEXUS_API_KEY}' } }, project.api_url, '.agents/mcp_config.json', shouldApply),
580
652
  ];
581
- let operations = collectJsonOperations(false);
582
- const codexPath = path.join('.codex', 'config.toml');
653
+ let operations = collectJsonOperations(false);
654
+ const helperOperation = inspectCodexAuthHelper();
655
+ operations.push(helperOperation);
656
+ const codexPath = path.join('.codex', 'config.toml');
583
657
  const codexContent = fs.existsSync(codexPath) ? fs.readFileSync(codexPath, 'utf-8') : '';
584
658
  const codexHasSection = /^\s*\[mcp_servers\.nexus-mcp\]\s*$/m.test(codexContent);
585
659
  const codexHealthy = codexHasSection
586
660
  && codexContent.includes(`url = "${project.api_url}/mcp"`)
587
- && codexContent.includes('bearer_token_env_var = "NEXUS_API_KEY"');
588
- operations.push(codexHealthy
589
- ? { action: 'healthy', label: '.codex/config.toml' }
590
- : codexHasSection
591
- ? { action: 'refused', label: '.codex/config.toml', detail: 'existing Nexus section is not owned by the recorded project context' }
592
- : { action: 'add missing Nexus section', label: '.codex/config.toml' });
661
+ && codexContent.includes(CODEX_AUTH_HELPER_SETTING);
662
+ const codexOwned = !codexHasSection || (
663
+ codexContent.includes(`url = "${project.api_url}/mcp"`)
664
+ && (codexContent.includes(CODEX_AUTH_HELPER_SETTING) || codexContent.includes(CODEX_LEGACY_ENV_SETTING))
665
+ );
666
+ operations.push(codexHealthy
667
+ ? { action: 'healthy', label: '.codex/config.toml' }
668
+ : codexOwned
669
+ ? { action: codexHasSection ? 'migrate credential helper' : 'add missing Nexus section', label: '.codex/config.toml' }
670
+ : { action: 'refused', label: '.codex/config.toml', detail: 'existing Nexus section is not owned by the recorded project context' });
593
671
 
594
672
  const skillPath = path.join('.agents', 'skills', 'nexus', 'SKILL.md');
595
673
  const skillContent = provisionedNexusSkillContent();
@@ -600,8 +678,10 @@ function runRepair(title = 'Repair') {
600
678
  console.error(`[NEXUS-CLI] ${title} refused before making changes because ownership checks failed.`);
601
679
  process.exit(1);
602
680
  }
603
- if (apply) {
681
+ if (apply) {
604
682
  operations = collectJsonOperations(true);
683
+ if (helperOperation.action !== 'healthy') ensureCodexAuthHelper();
684
+ operations.push({ action: helperOperation.action === 'healthy' ? 'healthy' : 'create credential helper', label: '.nexus/auth-headers.cjs' });
605
685
  if (!codexHealthy) {
606
686
  ensureCodexProjectConfig(project.api_url);
607
687
  }
@@ -636,8 +716,21 @@ async function runDoctor() {
636
716
  healthy = doctorCheck(false, 'Project binding', 'run nexus init first') && healthy;
637
717
  }
638
718
 
639
- const token = readEnvCredential();
640
- healthy = doctorCheck(Boolean(token), 'Scoped credential', token ? 'available without printing it' : 'set NEXUS_API_KEY or initialize .env.nexus') && healthy;
719
+ const token = readEnvCredential();
720
+ healthy = doctorCheck(Boolean(token), 'Scoped credential', token ? 'available without printing it' : 'set NEXUS_API_KEY or initialize .env.nexus') && healthy;
721
+ const credentialProtection = fs.existsSync('.env.nexus')
722
+ ? credentialProtectionStatus('.env.nexus')
723
+ : {
724
+ ok: Boolean(process.env.NEXUS_API_KEY),
725
+ detail: process.env.NEXUS_API_KEY
726
+ ? 'credential supplied by the process environment'
727
+ : 'credential file is missing',
728
+ };
729
+ healthy = doctorCheck(
730
+ credentialProtection.ok,
731
+ 'Credential file permissions',
732
+ credentialProtection.detail,
733
+ ) && healthy;
641
734
 
642
735
  let mcpConfigured = false;
643
736
  if (fs.existsSync('.mcp.json')) {
@@ -658,8 +751,9 @@ async function runDoctor() {
658
751
  && nexusRemoteEntryIsOwned(cursorEntry, apiUrl)
659
752
  && nexusRemoteEntryIsOwned(agentsEntry, apiUrl)
660
753
  && codexContent.includes(`url = "${apiUrl}/mcp"`)
661
- && codexContent.includes('bearer_token_env_var = "NEXUS_API_KEY"');
662
- healthy = doctorCheck(clientAuthHealthy, 'Supported client auth', clientAuthHealthy ? 'Cursor, agents, and Codex use environment-based credentials' : 'run repair to restore supported environment-based auth contracts') && healthy;
754
+ && codexContent.includes(CODEX_AUTH_HELPER_SETTING)
755
+ && inspectCodexAuthHelper().action === 'healthy';
756
+ healthy = doctorCheck(clientAuthHealthy, 'Supported client auth', clientAuthHealthy ? 'Cursor and agents use environment expansion; Codex uses the project-local header helper' : 'run repair to restore supported credential contracts') && healthy;
663
757
 
664
758
  const gitignore = fs.existsSync('.gitignore') ? fs.readFileSync('.gitignore', 'utf-8') : '';
665
759
  const secretsIgnored = gitignore.split(/\r?\n/).some((line) => line.trim() === '.env.nexus');
@@ -714,19 +808,28 @@ if (command === 'uninstall') runUninstall();
714
808
  if (command === 'init') {
715
809
  console.log('[NEXUS-CLI] 🚀 Initializing Nexus MCP in current repository...');
716
810
 
717
- const apiUrl = (flags.server || flags.url || process.env.NEXUS_API_URL || DEFAULT_API_URL).replace(/\/$/, '');
718
- let token = flags.token || process.env.NEXUS_API_KEY;
719
- let projectId = flags['project-id'] || process.env.NEXUS_PROJECT_ID;
720
-
721
- if (flags.token) {
722
- console.warn('[NEXUS-CLI] Warning: --token may be retained in shell history. Prefer setting NEXUS_API_KEY in the process environment.');
723
- }
724
-
725
- if (!token || token === 'nx_live_default_token') {
726
- const authorized = await authorizeDevice(apiUrl);
727
- token = authorized.token;
728
- projectId = authorized.projectId;
729
- }
811
+ const apiUrl = (flags.server || flags.url || process.env.NEXUS_API_URL || DEFAULT_API_URL).replace(/\/$/, '');
812
+ let token = flags.token || process.env.NEXUS_API_KEY;
813
+ let projectId = flags['project-id'] || process.env.NEXUS_PROJECT_ID;
814
+ const manualCredentialFlagProvided = Boolean(flags.token || flags['project-id']);
815
+
816
+ if (flags.token) {
817
+ console.warn('[NEXUS-CLI] Warning: --token may be retained in shell history. Prefer setting NEXUS_API_KEY in the process environment.');
818
+ }
819
+
820
+ if (manualCredentialFlagProvided && (!token || !projectId)) {
821
+ console.error('[NEXUS-CLI] Manual credential mode requires both --token and --project-id (or the missing matching environment value).');
822
+ process.exit(1);
823
+ }
824
+
825
+ if (!token || !projectId || token === 'nx_live_default_token') {
826
+ if (token || projectId) {
827
+ console.log('[NEXUS-CLI] Ignoring incomplete Nexus environment credentials and starting browser authorization.');
828
+ }
829
+ const authorized = await authorizeDevice(apiUrl);
830
+ token = authorized.token;
831
+ projectId = authorized.projectId;
832
+ }
730
833
  if (!projectId) {
731
834
  console.error('[NEXUS-CLI] Missing project id. Pass --project-id or set NEXUS_PROJECT_ID so hooks cannot ingest into the wrong tenant.');
732
835
  process.exit(1);
@@ -758,22 +861,36 @@ if (command === 'init') {
758
861
  },
759
862
  '.agents/mcp_config.json'
760
863
  );
761
- ensureCodexProjectConfig(apiUrl);
762
-
763
- // 2. Write .env.nexus file with actual token
864
+ // 2. Write .env.nexus file with actual token
764
865
  const envContent = `NEXUS_API_KEY=${token}\nNEXUS_API_URL=${apiUrl}\n`;
765
- if (fs.existsSync('.env.nexus') && !force) {
766
- const existingEnv = fs.readFileSync('.env.nexus', 'utf-8').match(/^NEXUS_API_KEY=(.*)$/m)?.[1]?.trim();
767
- if (existingEnv && existingEnv !== token) {
866
+ if (fs.existsSync('.env.nexus') && !force) {
867
+ const existingEnv = fs.readFileSync('.env.nexus', 'utf-8').match(/^NEXUS_API_KEY=(.*)$/m)?.[1]?.trim();
868
+ if (!existingEnv) {
869
+ console.error('[NEXUS-CLI] Existing .env.nexus has no valid Nexus credential; refusing to preserve it.');
870
+ process.exit(1);
871
+ }
872
+ if (existingEnv && existingEnv !== token) {
768
873
  console.error('[NEXUS-CLI] Existing .env.nexus contains a different credential; refusing to replace it.');
769
874
  process.exit(1);
770
875
  }
771
- console.log(' [NOTE] Preserved existing .env.nexus credential file.');
772
- } else {
773
- fs.writeFileSync('.env.nexus', envContent, { encoding: 'utf-8', mode: 0o600 });
774
- try { fs.chmodSync('.env.nexus', 0o600); } catch (_) { /* Windows ACLs may ignore POSIX mode. */ }
775
- console.log(' [OK] Created .env.nexus credential file.');
776
- }
876
+ try {
877
+ protectCredentialFile('.env.nexus');
878
+ } catch (error) {
879
+ console.error(`[NEXUS-CLI] ${error.message} Refusing to continue with an unprotected credential.`);
880
+ process.exit(1);
881
+ }
882
+ console.log(' [NOTE] Preserved existing .env.nexus credential file and refreshed its protection.');
883
+ } else {
884
+ fs.writeFileSync('.env.nexus', envContent, { encoding: 'utf-8', mode: 0o600 });
885
+ try {
886
+ protectCredentialFile('.env.nexus');
887
+ } catch (error) {
888
+ try { fs.unlinkSync('.env.nexus'); } catch (_) { /* best-effort secret cleanup */ }
889
+ console.error(`[NEXUS-CLI] ${error.message} The credential file was removed.`);
890
+ process.exit(1);
891
+ }
892
+ console.log(' [OK] Created protected .env.nexus credential file.');
893
+ }
777
894
 
778
895
  // 3. Shield credentials in .gitignore
779
896
  const gitignorePath = '.gitignore';
@@ -782,11 +899,16 @@ if (command === 'init') {
782
899
  gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
783
900
  }
784
901
 
785
- if (!gitignoreContent.includes('.env.nexus')) {
902
+ if (!gitignoreContent.includes('.env.nexus')) {
786
903
  gitignoreContent += '\n# Nexus Secrets & Cache Shielding\n.env.nexus\n.nexus/\n';
787
904
  fs.writeFileSync(gitignorePath, gitignoreContent, 'utf-8');
788
- console.log(' [OK] Updated .gitignore to shield .env.nexus credentials.');
789
- }
905
+ console.log(' [OK] Updated .gitignore to shield .env.nexus credentials.');
906
+ }
907
+
908
+ // Codex supports a local HTTP-header helper. It reads the protected project
909
+ // credential at connection time, so Codex does not require a global env var.
910
+ ensureCodexAuthHelper();
911
+ ensureCodexProjectConfig(apiUrl);
790
912
 
791
913
  // 4. Create .nexus/project.json
792
914
  const nexusDir = '.nexus';
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@nexusmcp/cli",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Nexus MCP Lightweight Onboarding CLI & Session Hook Assistant",
5
5
  "main": "bin/cli.js",
6
- "files": [
7
- "bin",
8
- "templates",
9
- "README.md",
6
+ "files": [
7
+ "bin",
8
+ "templates",
9
+ "README.md",
10
10
  "LICENSE"
11
11
  ],
12
12
  "bin": {
@@ -26,9 +26,9 @@
26
26
  "directory": "packages/cli"
27
27
  },
28
28
  "scripts": {
29
- "test": "node test/guidance-sync.cjs && node test/smoke.cjs && node test/session-end.cjs && node test/upgrade-doctor.cjs",
29
+ "test": "node test/guidance-sync.cjs && node test/smoke.cjs && node test/session-end.cjs && node test/upgrade-doctor.cjs",
30
30
  "test:package": "node test/package-install.cjs",
31
- "prepublishOnly": "npm test && npm run test:package && node --check bin/cli.js"
31
+ "prepublishOnly": "npm test && npm run test:package && node --check bin/cli.js"
32
32
  },
33
33
  "keywords": [
34
34
  "mcp",