@neus/sdk 1.1.1 → 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.
- package/README.md +184 -206
- package/cjs/client.cjs +23 -15
- package/cjs/index.cjs +23 -15
- package/cjs/mcp-hosts.cjs +125 -0
- package/cli/neus.mjs +613 -342
- package/client.js +32 -17
- package/mcp-hosts.js +121 -0
- package/package.json +147 -142
- package/types.d.ts +38 -7
- package/widgets/README.md +6 -10
- package/widgets/verify-gate/dist/ProofBadge.js +14 -4
- package/widgets/verify-gate/dist/VerifyGate.js +66 -203
package/cli/neus.mjs
CHANGED
|
@@ -1,30 +1,33 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from 'node:child_process';
|
|
3
3
|
import { createHash, randomBytes } from 'node:crypto';
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import {
|
|
9
|
+
NEUS_MCP_SERVER_NAME,
|
|
10
|
+
NEUS_MCP_URL,
|
|
11
|
+
buildNeusMcpHttpConfig
|
|
12
|
+
} from '../mcp-hosts.js';
|
|
13
|
+
|
|
14
|
+
const __cliDir = path.dirname(fileURLToPath(import.meta.url));
|
|
7
15
|
|
|
8
|
-
const NEUS_SERVER_NAME = 'neus';
|
|
9
|
-
const NEUS_MCP_URL = 'https://mcp.neus.network/mcp';
|
|
10
16
|
const NEUS_APP_URL = 'https://neus.network';
|
|
11
17
|
const NEUS_TOKEN_ENDPOINT = 'https://neus.network/api/v1/auth/mcp/token';
|
|
12
18
|
const NEUS_DISCONNECT_ENDPOINT = 'https://neus.network/api/v1/auth/mcp/revoke';
|
|
13
19
|
const NEUS_PROFILE_KEY_ENDPOINT = 'https://api.neus.network/api/v1/auth/profile-key';
|
|
14
|
-
const SUPPORTED_CLIENTS = ['claude', 'cursor', 'vscode'];
|
|
20
|
+
const SUPPORTED_CLIENTS = ['claude', 'codex', 'cursor', 'vscode'];
|
|
21
|
+
const PROJECT_CLIENTS = ['claude', 'cursor', 'vscode'];
|
|
22
|
+
const CODEX_OAUTH_SCOPES = 'neus:core,neus:profile,neus:secrets,offline_access';
|
|
15
23
|
const IMPORT_SCHEMA = 'neus.portable-agent.v1';
|
|
16
24
|
const SUPPORTED_IMPORT_SOURCES = [
|
|
17
25
|
'auto',
|
|
18
|
-
'openclaw',
|
|
19
|
-
'hermes',
|
|
20
26
|
'cursor',
|
|
21
27
|
'claude-code',
|
|
22
28
|
'claude-desktop'
|
|
23
29
|
];
|
|
24
30
|
const SUPPORTED_EXPORT_FORMATS = ['manifest', 'json'];
|
|
25
|
-
const SECRET_NAME_PATTERN =
|
|
26
|
-
/(?:^|_)(?:api[_-]?key|secret|token|password|private[_-]?key|access[_-]?key|bearer)(?:$|_)/i;
|
|
27
|
-
const ANSI_ENABLED = process.env.NO_COLOR !== '1' && process.env.TERM !== 'dumb';
|
|
28
31
|
|
|
29
32
|
const ansi = {
|
|
30
33
|
reset: '\x1b[0m',
|
|
@@ -36,18 +39,299 @@ const ansi = {
|
|
|
36
39
|
bold: '\x1b[1m'
|
|
37
40
|
};
|
|
38
41
|
|
|
42
|
+
function isTruthyEnv(value) {
|
|
43
|
+
const normalized = String(value || '')
|
|
44
|
+
.trim()
|
|
45
|
+
.toLowerCase();
|
|
46
|
+
return normalized === '1' || normalized === 'true' || normalized === 'yes';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function resolveColorEnabled() {
|
|
50
|
+
if (isTruthyEnv(process.env.NO_COLOR)) return false;
|
|
51
|
+
if (process.env.TERM === 'dumb') return false;
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
39
55
|
function paint(value, color) {
|
|
40
|
-
if (!
|
|
56
|
+
if (!resolveColorEnabled()) return String(value);
|
|
41
57
|
return `${ansi[color] || ''}${value}${ansi.reset}`;
|
|
42
58
|
}
|
|
43
59
|
|
|
44
|
-
function
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
60
|
+
function terminalColumns() {
|
|
61
|
+
const cols = Number(process.stderr.columns || process.stdout.columns || 0);
|
|
62
|
+
if (Number.isFinite(cols) && cols >= 40) return cols;
|
|
63
|
+
return 80;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function truncateDetail(text) {
|
|
67
|
+
const raw = String(text || '');
|
|
68
|
+
const max = Math.max(24, terminalColumns() - 18);
|
|
69
|
+
if (raw.length <= max) return raw;
|
|
70
|
+
return `${raw.slice(0, Math.max(0, max - 3))}...`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function useUnicodeSymbols() {
|
|
74
|
+
if (!resolveColorEnabled()) return false;
|
|
75
|
+
if (process.platform !== 'win32') return true;
|
|
76
|
+
return Boolean(
|
|
77
|
+
process.env.WT_SESSION ||
|
|
78
|
+
process.env.TERMINAL_EMULATOR === 'JetBrains-JediTerm' ||
|
|
79
|
+
process.env.TERM_PROGRAM === 'vscode' ||
|
|
80
|
+
process.env.TERM_PROGRAM === 'cursor'
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function cliSymbols() {
|
|
85
|
+
if (useUnicodeSymbols()) {
|
|
86
|
+
return { ok: '✓', warn: '!', next: '→', skip: '-' };
|
|
87
|
+
}
|
|
88
|
+
return { ok: 'ok', warn: '!', next: '>', skip: '-' };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function writeCliLine(line) {
|
|
92
|
+
process.stderr.write(`${line}\n`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let cliBannerEmitted = false;
|
|
96
|
+
|
|
97
|
+
function readCliVersion() {
|
|
98
|
+
try {
|
|
99
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__cliDir, '..', 'package.json'), 'utf8'));
|
|
100
|
+
return String(pkg.version || '0.0.0').trim();
|
|
101
|
+
} catch {
|
|
102
|
+
return '0.0.0';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function shouldEmitCliBanner(cliOptions = {}) {
|
|
107
|
+
if (cliBannerEmitted) return false;
|
|
108
|
+
if (cliOptions.json) return false;
|
|
109
|
+
if (!process.stderr.isTTY) return false;
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function emitCliBanner(cliOptions = {}) {
|
|
114
|
+
if (!shouldEmitCliBanner(cliOptions)) return;
|
|
115
|
+
const version = readCliVersion();
|
|
116
|
+
const title = paint('NEUS', 'green');
|
|
117
|
+
const meta = `${paint(`v${version}`, 'dim')}${paint(' | trust receipts', 'dim')}`;
|
|
118
|
+
writeCliLine('');
|
|
119
|
+
writeCliLine(` ${title} ${meta}`);
|
|
120
|
+
writeCliLine('');
|
|
121
|
+
cliBannerEmitted = true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function logStep(kind, label, detail = '') {
|
|
125
|
+
const symbols = cliSymbols();
|
|
126
|
+
const iconKey = kind === 'ok' ? 'ok' : kind === 'warn' ? 'warn' : kind === 'next' ? 'next' : 'skip';
|
|
127
|
+
const iconColor = kind === 'ok' ? 'green' : kind === 'warn' ? 'yellow' : kind === 'next' ? 'cyan' : 'dim';
|
|
128
|
+
const iconCell = useUnicodeSymbols() ? symbols[iconKey] : symbols[iconKey].padEnd(2);
|
|
129
|
+
const icon = paint(iconCell, iconColor);
|
|
130
|
+
const name = paint(String(label).padEnd(10), 'cyan');
|
|
131
|
+
const suffix = detail ? ` ${paint(truncateDetail(detail), 'dim')}` : '';
|
|
132
|
+
writeCliLine(` ${icon} ${name}${suffix}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function writeGuidanceLine(text) {
|
|
136
|
+
writeCliLine(` ${paint('-', 'dim')} ${text}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function describeClientResult(command, result) {
|
|
140
|
+
if (result.dryRun && result.changed) {
|
|
141
|
+
if (result.client === 'codex') {
|
|
142
|
+
return `would update ${result.targetPath || '~/.codex/config.toml'}`;
|
|
143
|
+
}
|
|
144
|
+
return 'would update';
|
|
145
|
+
}
|
|
146
|
+
if (result.client === 'codex' && result.configured) {
|
|
147
|
+
if (command === 'auth') {
|
|
148
|
+
return result.authConfigured ? 'Codex OAuth complete' : 'Codex MCP config ready';
|
|
149
|
+
}
|
|
150
|
+
return `Codex MCP config: ${result.targetPath || '~/.codex/config.toml'}`;
|
|
151
|
+
}
|
|
152
|
+
if (result.changed) return 'updated';
|
|
153
|
+
if (result.authConfigured) return 'signed in';
|
|
154
|
+
return 'ready';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function printBuilderGuidance(command, results) {
|
|
158
|
+
if (!['setup', 'auth'].includes(command)) return;
|
|
159
|
+
const hasCodex = results.some(result => result.client === 'codex');
|
|
160
|
+
writeCliLine('');
|
|
161
|
+
writeCliLine(paint('Builder notes', 'cyan'));
|
|
162
|
+
writeGuidanceLine('Use from any shell without a global install: `npx -y -p @neus/sdk neus ...`.');
|
|
163
|
+
if (hasCodex) {
|
|
164
|
+
writeGuidanceLine('Codex owns OAuth: run `neus auth --client codex` or `codex mcp login neus`.');
|
|
165
|
+
}
|
|
166
|
+
writeGuidanceLine(
|
|
167
|
+
'Claude plugin commands run inside Claude Code chat, not as `claude install`: `/plugin marketplace add https://github.com/neus/network`.'
|
|
48
168
|
);
|
|
49
169
|
}
|
|
50
170
|
|
|
171
|
+
function selectedClientNames(results) {
|
|
172
|
+
return results.map(result => result.client).filter(Boolean);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function preferredSetupCommand(results) {
|
|
176
|
+
const clients = selectedClientNames(results);
|
|
177
|
+
const suffix = clients.length === 1 ? ` --client ${clients[0]}` : '';
|
|
178
|
+
return `npx -y -p @neus/sdk neus setup${suffix}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function preferredAuthCommand(results) {
|
|
182
|
+
const clients = selectedClientNames(results);
|
|
183
|
+
if (clients.length === 1 && clients[0] === 'codex') {
|
|
184
|
+
return 'npx -y -p @neus/sdk neus auth --client codex';
|
|
185
|
+
}
|
|
186
|
+
return 'npx -y -p @neus/sdk neus auth';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function printStatusGuidance(results) {
|
|
190
|
+
writeCliLine('');
|
|
191
|
+
writeCliLine(paint('MCP endpoint', 'cyan'));
|
|
192
|
+
writeGuidanceLine(NEUS_MCP_URL);
|
|
193
|
+
writeCliLine(paint('Profile connection', 'cyan'));
|
|
194
|
+
if (results.some(result => result.configured)) {
|
|
195
|
+
writeGuidanceLine('Saved config found. Run `npx -y -p @neus/sdk neus doctor --live` to confirm live Profile context.');
|
|
196
|
+
} else {
|
|
197
|
+
writeGuidanceLine(`No selected MCP host is configured yet. Run \`${preferredSetupCommand(results)}\`.`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function printHostAuthIntro(host, cliOptions = {}) {
|
|
202
|
+
if (cliOptions.json) return;
|
|
203
|
+
emitCliBanner(cliOptions);
|
|
204
|
+
writeCliLine(paint('auth', 'green'));
|
|
205
|
+
if (host === 'codex') {
|
|
206
|
+
logStep('next', 'codex', 'starting Codex-owned MCP OAuth');
|
|
207
|
+
logStep('next', 'command', 'codex mcp login neus');
|
|
208
|
+
writeCliLine('');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function printFlowSummary(command, scope, results, { nextStep = '', cliOptions = {} } = {}) {
|
|
213
|
+
emitCliBanner(cliOptions);
|
|
214
|
+
writeCliLine(paint(String(command), 'green'));
|
|
215
|
+
|
|
216
|
+
for (const result of results) {
|
|
217
|
+
const client = result.client;
|
|
218
|
+
if (result.error) {
|
|
219
|
+
logStep('warn', client, result.error);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (result.configured) {
|
|
223
|
+
const detail = describeClientResult(command, result);
|
|
224
|
+
logStep('ok', client, detail);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (result.authConfigured === null) {
|
|
228
|
+
logStep('skip', client, 'not installed');
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
logStep('skip', client, 'not configured');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (nextStep) {
|
|
235
|
+
writeCliLine('');
|
|
236
|
+
logStep('next', 'next', nextStep);
|
|
237
|
+
}
|
|
238
|
+
if (command === 'status') {
|
|
239
|
+
printStatusGuidance(results);
|
|
240
|
+
}
|
|
241
|
+
printBuilderGuidance(command, results);
|
|
242
|
+
writeCliLine('');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function printAuthBrowserIntro(authUrl, cliOptions = {}) {
|
|
246
|
+
emitCliBanner(cliOptions);
|
|
247
|
+
writeCliLine(paint('auth', 'green'));
|
|
248
|
+
logStep('next', 'sign-in', 'opens in your browser');
|
|
249
|
+
writeCliLine('');
|
|
250
|
+
writeCliLine(` ${paint(truncateDetail(authUrl), 'dim')}`);
|
|
251
|
+
writeCliLine('');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function parseBearerHeader(value) {
|
|
255
|
+
const raw = String(value || '').trim();
|
|
256
|
+
if (!raw.toLowerCase().startsWith('bearer ')) return '';
|
|
257
|
+
return raw.slice(7).trim();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function readCursorBearer(scope, cwd) {
|
|
261
|
+
const targetPath = cursorConfigPath(scope, cwd);
|
|
262
|
+
if (!fileExists(targetPath)) return '';
|
|
263
|
+
const doc = readJsonFile(targetPath, {});
|
|
264
|
+
return parseBearerHeader(doc.mcpServers?.[NEUS_MCP_SERVER_NAME]?.headers?.Authorization);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function readVsCodeBearer(scope, cwd) {
|
|
268
|
+
const targetPath = vscodeConfigPath(scope, cwd);
|
|
269
|
+
if (!fileExists(targetPath)) return '';
|
|
270
|
+
const doc = readJsonFile(targetPath, {});
|
|
271
|
+
return parseBearerHeader(doc.servers?.[NEUS_MCP_SERVER_NAME]?.headers?.Authorization);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function readClaudeBearer(scope, cwd) {
|
|
275
|
+
if (scope === 'project') {
|
|
276
|
+
const targetPath = claudeProjectConfigPath(cwd);
|
|
277
|
+
if (!fileExists(targetPath)) return '';
|
|
278
|
+
const doc = readJsonFile(targetPath, {});
|
|
279
|
+
return parseBearerHeader(doc.mcpServers?.[NEUS_MCP_SERVER_NAME]?.headers?.Authorization);
|
|
280
|
+
}
|
|
281
|
+
if (!commandExists('claude')) return '';
|
|
282
|
+
const result = spawnSync('claude', ['mcp', 'list'], {
|
|
283
|
+
encoding: 'utf8',
|
|
284
|
+
env: process.env
|
|
285
|
+
});
|
|
286
|
+
if (result.status !== 0) return '';
|
|
287
|
+
const lines = String(result.stdout || '').split(/\r?\n/);
|
|
288
|
+
if (!lines.includes(NEUS_MCP_SERVER_NAME)) return '';
|
|
289
|
+
const statePath = process.env.NEUS_TEST_CLAUDE_STATE;
|
|
290
|
+
if (statePath && fileExists(statePath)) {
|
|
291
|
+
const state = readJsonFile(statePath, { servers: {} });
|
|
292
|
+
const headers = state.servers?.[NEUS_MCP_SERVER_NAME]?.headers || [];
|
|
293
|
+
const authLine = headers.find(line => String(line).toLowerCase().startsWith('authorization:'));
|
|
294
|
+
if (authLine) {
|
|
295
|
+
return parseBearerHeader(authLine.replace(/^authorization:\s*/i, ''));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return '';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function readInstalledAccessKey(scope, cwd) {
|
|
302
|
+
for (const reader of [readCursorBearer, readVsCodeBearer, readClaudeBearer]) {
|
|
303
|
+
const token = reader(scope, cwd);
|
|
304
|
+
if (token) return token;
|
|
305
|
+
}
|
|
306
|
+
return '';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function envAccessKey() {
|
|
310
|
+
return String(process.env.NEUS_ACCESS_KEY || '').trim();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** --access-key flag, else NEUS_ACCESS_KEY from the environment, else browser sign-in. */
|
|
314
|
+
function resolveAccessKey(options) {
|
|
315
|
+
const explicit = String(options.accessKey || '').trim();
|
|
316
|
+
if (explicit) return explicit;
|
|
317
|
+
return envAccessKey();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** --access-key, IDE MCP config, then NEUS_ACCESS_KEY from the environment. */
|
|
321
|
+
function resolveLiveAccessKey(options, scope, cwd) {
|
|
322
|
+
const explicit = String(options.accessKey || '').trim();
|
|
323
|
+
if (explicit) return explicit;
|
|
324
|
+
const installed = readInstalledAccessKey(scope, cwd);
|
|
325
|
+
if (installed) return installed;
|
|
326
|
+
return envAccessKey();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function resolveAuthMethod(options, accessKey) {
|
|
330
|
+
if (!accessKey) return 'browser';
|
|
331
|
+
if (String(options.accessKey || '').trim()) return 'access-key';
|
|
332
|
+
return 'env-key';
|
|
333
|
+
}
|
|
334
|
+
|
|
51
335
|
function fileExists(targetPath) {
|
|
52
336
|
try {
|
|
53
337
|
fs.accessSync(targetPath);
|
|
@@ -179,36 +463,20 @@ function instructionEntry(targetPath, name) {
|
|
|
179
463
|
};
|
|
180
464
|
}
|
|
181
465
|
|
|
182
|
-
function parseEnvSecretRefs(targetPath, source, warnings) {
|
|
183
|
-
if (!fileExists(targetPath)) return [];
|
|
184
|
-
const refs = [];
|
|
185
|
-
const seen = new Set();
|
|
186
|
-
const raw = readTextFile(targetPath);
|
|
187
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
188
|
-
const trimmed = line.trim();
|
|
189
|
-
if (!trimmed || trimmed.startsWith('#') || !trimmed.includes('=')) continue;
|
|
190
|
-
const name = trimmed.split('=')[0].trim();
|
|
191
|
-
if (!name || !SECRET_NAME_PATTERN.test(name) || seen.has(name)) continue;
|
|
192
|
-
seen.add(name);
|
|
193
|
-
refs.push({ name, source, handling: 'detected-only' });
|
|
194
|
-
}
|
|
195
|
-
if (refs.length > 0) {
|
|
196
|
-
warnings.push(
|
|
197
|
-
`Detected ${refs.length} secret-like env name${refs.length === 1 ? '' : 's'} in ${portablePath(targetPath)}; values were not read into the manifest.`
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
return refs;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
466
|
function readMcpServers(targetPath, source, warnings) {
|
|
204
467
|
const doc = safeReadJson(targetPath, warnings);
|
|
205
468
|
if (!doc) return [];
|
|
469
|
+
const mcpSection = doc.mcp && typeof doc.mcp === 'object' && !Array.isArray(doc.mcp) ? doc.mcp : null;
|
|
206
470
|
const servers =
|
|
207
471
|
doc.mcpServers && typeof doc.mcpServers === 'object' && !Array.isArray(doc.mcpServers)
|
|
208
472
|
? doc.mcpServers
|
|
209
|
-
:
|
|
210
|
-
|
|
211
|
-
|
|
473
|
+
: mcpSection?.servers &&
|
|
474
|
+
typeof mcpSection.servers === 'object' &&
|
|
475
|
+
!Array.isArray(mcpSection.servers)
|
|
476
|
+
? mcpSection.servers
|
|
477
|
+
: doc.servers && typeof doc.servers === 'object' && !Array.isArray(doc.servers)
|
|
478
|
+
? doc.servers
|
|
479
|
+
: {};
|
|
212
480
|
return Object.keys(servers)
|
|
213
481
|
.sort((a, b) => a.localeCompare(b))
|
|
214
482
|
.map(name => ({
|
|
@@ -285,6 +553,7 @@ function cursorInstalled() {
|
|
|
285
553
|
function defaultUserClients() {
|
|
286
554
|
const detected = [];
|
|
287
555
|
if (commandExists('claude')) detected.push('claude');
|
|
556
|
+
if (commandExists('codex')) detected.push('codex');
|
|
288
557
|
if (cursorInstalled()) detected.push('cursor');
|
|
289
558
|
if (commandExists('code') || fileExists(path.join(process.env.APPDATA || '', 'Code')))
|
|
290
559
|
detected.push('vscode');
|
|
@@ -303,7 +572,7 @@ function parseArgs(argv) {
|
|
|
303
572
|
return {
|
|
304
573
|
command: 'help',
|
|
305
574
|
options: {
|
|
306
|
-
accessKey:
|
|
575
|
+
accessKey: '',
|
|
307
576
|
clients: [],
|
|
308
577
|
source: 'auto',
|
|
309
578
|
format: 'manifest',
|
|
@@ -318,7 +587,7 @@ function parseArgs(argv) {
|
|
|
318
587
|
|
|
319
588
|
const command = argv[0];
|
|
320
589
|
const options = {
|
|
321
|
-
accessKey:
|
|
590
|
+
accessKey: '',
|
|
322
591
|
clients: [],
|
|
323
592
|
source: 'auto',
|
|
324
593
|
format: 'manifest',
|
|
@@ -399,24 +668,24 @@ function printUsage(exitCode = 0) {
|
|
|
399
668
|
'Usage: neus <command> [options]',
|
|
400
669
|
'',
|
|
401
670
|
'Commands:',
|
|
402
|
-
' setup
|
|
671
|
+
' setup Configure hosted NEUS MCP for supported clients',
|
|
403
672
|
' init Configure supported MCP clients automatically',
|
|
404
|
-
' auth Sign in
|
|
673
|
+
' auth Sign in (browser, or NEUS_ACCESS_KEY / --access-key when set)',
|
|
405
674
|
' disconnect Disconnect NEUS MCP (revoke the stored OAuth token or access key)',
|
|
406
675
|
' status Show current NEUS MCP setup',
|
|
407
|
-
' doctor Deep check: config status, profile connection,
|
|
408
|
-
' import Detect and package
|
|
676
|
+
' doctor Deep check: config status, profile connection, and live MCP context',
|
|
677
|
+
' import Detect and package supported assistant context for NEUS portability',
|
|
409
678
|
' export Export the latest local NEUS portable agent manifest',
|
|
410
679
|
' help Show this message',
|
|
411
680
|
'',
|
|
412
681
|
'Options:',
|
|
413
|
-
' --client <name[,name]> Limit setup to claude, cursor, or vscode',
|
|
682
|
+
' --client <name[,name]> Limit setup to claude, codex, cursor, or vscode',
|
|
414
683
|
' --project Write shared project config instead of user config',
|
|
415
|
-
' --access-key <npk_...>
|
|
416
|
-
' --from <source> Import source: auto,
|
|
684
|
+
' --access-key <npk_...> Override profile access key (else uses NEUS_ACCESS_KEY if set)',
|
|
685
|
+
' --from <source> Import source: auto, cursor, claude-code, or claude-desktop',
|
|
417
686
|
' --to <format> Export format: manifest or json',
|
|
418
687
|
' --output <path> Write exported manifest to a specific path',
|
|
419
|
-
' --live Run live MCP checks
|
|
688
|
+
' --live Run live MCP checks (uses IDE credential or --access-key)',
|
|
420
689
|
' --json Print JSON output',
|
|
421
690
|
' --dry-run Preview changes without writing files'
|
|
422
691
|
];
|
|
@@ -440,7 +709,7 @@ function resolveScope(options) {
|
|
|
440
709
|
function resolveClients(scope, requestedClients) {
|
|
441
710
|
assertValidClients(requestedClients);
|
|
442
711
|
if (requestedClients.length > 0) return requestedClients;
|
|
443
|
-
if (scope === 'project') return [...
|
|
712
|
+
if (scope === 'project') return [...PROJECT_CLIENTS];
|
|
444
713
|
return defaultUserClients();
|
|
445
714
|
}
|
|
446
715
|
|
|
@@ -466,27 +735,15 @@ function ensureSafeAuth(command, scope, accessKey) {
|
|
|
466
735
|
}
|
|
467
736
|
|
|
468
737
|
function buildCursorServer(accessKey) {
|
|
469
|
-
return
|
|
470
|
-
type: 'http',
|
|
471
|
-
url: NEUS_MCP_URL,
|
|
472
|
-
...(accessKey ? { headers: { Authorization: `Bearer ${accessKey}` } } : {})
|
|
473
|
-
};
|
|
738
|
+
return buildNeusMcpHttpConfig(accessKey);
|
|
474
739
|
}
|
|
475
740
|
|
|
476
741
|
function buildVsCodeServer(accessKey) {
|
|
477
|
-
return
|
|
478
|
-
type: 'http',
|
|
479
|
-
url: NEUS_MCP_URL,
|
|
480
|
-
...(accessKey ? { headers: { Authorization: `Bearer ${accessKey}` } } : {})
|
|
481
|
-
};
|
|
742
|
+
return buildNeusMcpHttpConfig(accessKey);
|
|
482
743
|
}
|
|
483
744
|
|
|
484
745
|
function buildClaudeServer(accessKey) {
|
|
485
|
-
return
|
|
486
|
-
type: 'http',
|
|
487
|
-
url: NEUS_MCP_URL,
|
|
488
|
-
...(accessKey ? { headers: { Authorization: `Bearer ${accessKey}` } } : {})
|
|
489
|
-
};
|
|
746
|
+
return buildNeusMcpHttpConfig(accessKey);
|
|
490
747
|
}
|
|
491
748
|
|
|
492
749
|
function cursorConfigPath(scope, cwd) {
|
|
@@ -517,6 +774,10 @@ function claudeProjectConfigPath(cwd) {
|
|
|
517
774
|
return path.join(cwd, '.mcp.json');
|
|
518
775
|
}
|
|
519
776
|
|
|
777
|
+
function codexConfigPath() {
|
|
778
|
+
return path.join(os.homedir(), '.codex', 'config.toml');
|
|
779
|
+
}
|
|
780
|
+
|
|
520
781
|
function installCursor(scope, accessKey, dryRun, cwd) {
|
|
521
782
|
const targetPath = cursorConfigPath(scope, cwd);
|
|
522
783
|
const doc = readJsonFile(targetPath, { mcpServers: {} });
|
|
@@ -526,7 +787,7 @@ function installCursor(scope, accessKey, dryRun, cwd) {
|
|
|
526
787
|
...(doc.mcpServers && typeof doc.mcpServers === 'object' && !Array.isArray(doc.mcpServers)
|
|
527
788
|
? doc.mcpServers
|
|
528
789
|
: {}),
|
|
529
|
-
[
|
|
790
|
+
[NEUS_MCP_SERVER_NAME]: buildCursorServer(accessKey)
|
|
530
791
|
}
|
|
531
792
|
};
|
|
532
793
|
const writeResult = writeJsonFile(targetPath, next, dryRun);
|
|
@@ -552,7 +813,7 @@ function installVsCode(scope, accessKey, dryRun, cwd) {
|
|
|
552
813
|
...(doc.servers && typeof doc.servers === 'object' && !Array.isArray(doc.servers)
|
|
553
814
|
? doc.servers
|
|
554
815
|
: {}),
|
|
555
|
-
[
|
|
816
|
+
[NEUS_MCP_SERVER_NAME]: buildVsCodeServer(accessKey)
|
|
556
817
|
}
|
|
557
818
|
};
|
|
558
819
|
const writeResult = writeJsonFile(targetPath, next, dryRun);
|
|
@@ -578,7 +839,7 @@ function installClaudeProject(scope, accessKey, dryRun, cwd) {
|
|
|
578
839
|
...(doc.mcpServers && typeof doc.mcpServers === 'object' && !Array.isArray(doc.mcpServers)
|
|
579
840
|
? doc.mcpServers
|
|
580
841
|
: {}),
|
|
581
|
-
[
|
|
842
|
+
[NEUS_MCP_SERVER_NAME]: buildClaudeServer(accessKey)
|
|
582
843
|
}
|
|
583
844
|
};
|
|
584
845
|
const writeResult = writeJsonFile(targetPath, next, dryRun);
|
|
@@ -601,7 +862,7 @@ function installClaudeUser(scope, accessKey, dryRun, cwd) {
|
|
|
601
862
|
}
|
|
602
863
|
|
|
603
864
|
if (!dryRun) {
|
|
604
|
-
runCommand('claude', ['mcp', 'remove', '--scope', 'user',
|
|
865
|
+
runCommand('claude', ['mcp', 'remove', '--scope', 'user', NEUS_MCP_SERVER_NAME], cwd, true);
|
|
605
866
|
const addArgs = [
|
|
606
867
|
'mcp',
|
|
607
868
|
'add',
|
|
@@ -609,7 +870,7 @@ function installClaudeUser(scope, accessKey, dryRun, cwd) {
|
|
|
609
870
|
'http',
|
|
610
871
|
'--scope',
|
|
611
872
|
'user',
|
|
612
|
-
|
|
873
|
+
NEUS_MCP_SERVER_NAME,
|
|
613
874
|
NEUS_MCP_URL
|
|
614
875
|
];
|
|
615
876
|
if (accessKey) {
|
|
@@ -638,10 +899,66 @@ function installClaude(scope, accessKey, dryRun, cwd) {
|
|
|
638
899
|
return installClaudeUser(scope, accessKey, dryRun, cwd);
|
|
639
900
|
}
|
|
640
901
|
|
|
902
|
+
function installCodex(scope, accessKey, dryRun, cwd) {
|
|
903
|
+
if (scope !== 'user') {
|
|
904
|
+
throw new Error('Codex MCP setup is user-scoped through ~/.codex/config.toml.');
|
|
905
|
+
}
|
|
906
|
+
if (!commandExists('codex')) {
|
|
907
|
+
throw new Error('Codex CLI is not installed or not on PATH.');
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
const bearerTokenEnvVar = envAccessKey() ? 'NEUS_ACCESS_KEY' : '';
|
|
911
|
+
|
|
912
|
+
if (!dryRun) {
|
|
913
|
+
runCommand('codex', ['mcp', 'remove', NEUS_MCP_SERVER_NAME], cwd, true);
|
|
914
|
+
const addArgs = [
|
|
915
|
+
'mcp',
|
|
916
|
+
'add',
|
|
917
|
+
NEUS_MCP_SERVER_NAME,
|
|
918
|
+
'--url',
|
|
919
|
+
NEUS_MCP_URL,
|
|
920
|
+
'--oauth-client-id',
|
|
921
|
+
NEUS_OAUTH_CLIENT_ID,
|
|
922
|
+
'--oauth-resource',
|
|
923
|
+
NEUS_MCP_RESOURCE
|
|
924
|
+
];
|
|
925
|
+
if (bearerTokenEnvVar) {
|
|
926
|
+
addArgs.push('--bearer-token-env-var', bearerTokenEnvVar);
|
|
927
|
+
}
|
|
928
|
+
runCommand('codex', addArgs, cwd);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
return {
|
|
932
|
+
client: 'codex',
|
|
933
|
+
scope,
|
|
934
|
+
configured: true,
|
|
935
|
+
authConfigured: bearerTokenEnvVar ? true : null,
|
|
936
|
+
changed: true,
|
|
937
|
+
targetPath: portablePath(codexConfigPath()),
|
|
938
|
+
backupPath: null,
|
|
939
|
+
dryRun,
|
|
940
|
+
error: null
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
function authCodex(scope, dryRun, cwd, cliOptions = {}) {
|
|
945
|
+
const setupResult = installCodex(scope, '', dryRun, cwd);
|
|
946
|
+
if (!dryRun) {
|
|
947
|
+
printHostAuthIntro('codex', cliOptions);
|
|
948
|
+
runCommand('codex', ['mcp', 'login', NEUS_MCP_SERVER_NAME, '--scopes', CODEX_OAUTH_SCOPES], cwd);
|
|
949
|
+
}
|
|
950
|
+
return {
|
|
951
|
+
...setupResult,
|
|
952
|
+
authConfigured: !dryRun,
|
|
953
|
+
changed: true
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
|
|
641
957
|
function installClient(client, scope, accessKey, dryRun, cwd) {
|
|
642
958
|
if (client === 'cursor') return installCursor(scope, accessKey, dryRun, cwd);
|
|
643
959
|
if (client === 'vscode') return installVsCode(scope, accessKey, dryRun, cwd);
|
|
644
960
|
if (client === 'claude') return installClaude(scope, accessKey, dryRun, cwd);
|
|
961
|
+
if (client === 'codex') return installCodex(scope, accessKey, dryRun, cwd);
|
|
645
962
|
throw new Error(`Unsupported client: ${client}`);
|
|
646
963
|
}
|
|
647
964
|
|
|
@@ -658,7 +975,7 @@ function inspectCursor(scope, cwd) {
|
|
|
658
975
|
};
|
|
659
976
|
}
|
|
660
977
|
const doc = readJsonFile(targetPath, {});
|
|
661
|
-
const server = doc.mcpServers?.[
|
|
978
|
+
const server = doc.mcpServers?.[NEUS_MCP_SERVER_NAME];
|
|
662
979
|
return {
|
|
663
980
|
client: 'cursor',
|
|
664
981
|
scope,
|
|
@@ -682,7 +999,7 @@ function inspectVsCode(scope, cwd) {
|
|
|
682
999
|
};
|
|
683
1000
|
}
|
|
684
1001
|
const doc = readJsonFile(targetPath, {});
|
|
685
|
-
const server = doc.servers?.[
|
|
1002
|
+
const server = doc.servers?.[NEUS_MCP_SERVER_NAME];
|
|
686
1003
|
return {
|
|
687
1004
|
client: 'vscode',
|
|
688
1005
|
scope,
|
|
@@ -707,7 +1024,7 @@ function inspectClaude(scope, cwd) {
|
|
|
707
1024
|
};
|
|
708
1025
|
}
|
|
709
1026
|
const doc = readJsonFile(targetPath, {});
|
|
710
|
-
const server = doc.mcpServers?.[
|
|
1027
|
+
const server = doc.mcpServers?.[NEUS_MCP_SERVER_NAME];
|
|
711
1028
|
return {
|
|
712
1029
|
client: 'claude',
|
|
713
1030
|
scope,
|
|
@@ -732,21 +1049,59 @@ function inspectClaude(scope, cwd) {
|
|
|
732
1049
|
const result = runCommand('claude', ['mcp', 'list'], cwd, true);
|
|
733
1050
|
const configured =
|
|
734
1051
|
result.status === 0 &&
|
|
735
|
-
result.stdout.split(/\r?\n/).some(line => line.trim() ===
|
|
1052
|
+
result.stdout.split(/\r?\n/).some(line => line.trim() === NEUS_MCP_SERVER_NAME);
|
|
736
1053
|
return {
|
|
737
1054
|
client: 'claude',
|
|
738
1055
|
scope,
|
|
739
1056
|
configured,
|
|
740
|
-
authConfigured: null,
|
|
1057
|
+
authConfigured: configured ? null : false,
|
|
741
1058
|
targetPath: '~/.claude.json',
|
|
742
1059
|
error: null
|
|
743
1060
|
};
|
|
744
1061
|
}
|
|
745
1062
|
|
|
1063
|
+
function inspectCodex(scope, cwd) {
|
|
1064
|
+
const targetPath = portablePath(codexConfigPath());
|
|
1065
|
+
if (scope !== 'user') {
|
|
1066
|
+
return {
|
|
1067
|
+
client: 'codex',
|
|
1068
|
+
scope,
|
|
1069
|
+
configured: false,
|
|
1070
|
+
authConfigured: null,
|
|
1071
|
+
targetPath,
|
|
1072
|
+
error: 'Codex MCP setup is user-scoped through ~/.codex/config.toml.'
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
if (!commandExists('codex')) {
|
|
1076
|
+
return {
|
|
1077
|
+
client: 'codex',
|
|
1078
|
+
scope,
|
|
1079
|
+
configured: false,
|
|
1080
|
+
authConfigured: null,
|
|
1081
|
+
targetPath,
|
|
1082
|
+
error: null
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
const result = runCommand('codex', ['mcp', 'get', NEUS_MCP_SERVER_NAME], cwd, true);
|
|
1087
|
+
const configured =
|
|
1088
|
+
result.status === 0 &&
|
|
1089
|
+
result.stdout.split(/\r?\n/).some(line => line.trim() === `url: ${NEUS_MCP_URL}`);
|
|
1090
|
+
return {
|
|
1091
|
+
client: 'codex',
|
|
1092
|
+
scope,
|
|
1093
|
+
configured,
|
|
1094
|
+
authConfigured: configured ? null : false,
|
|
1095
|
+
targetPath,
|
|
1096
|
+
error: null
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
|
|
746
1100
|
function inspectClient(client, scope, cwd) {
|
|
747
1101
|
if (client === 'cursor') return inspectCursor(scope, cwd);
|
|
748
1102
|
if (client === 'vscode') return inspectVsCode(scope, cwd);
|
|
749
1103
|
if (client === 'claude') return inspectClaude(scope, cwd);
|
|
1104
|
+
if (client === 'codex') return inspectCodex(scope, cwd);
|
|
750
1105
|
throw new Error(`Unsupported client: ${client}`);
|
|
751
1106
|
}
|
|
752
1107
|
|
|
@@ -769,29 +1124,7 @@ function createEmptyManifest(source) {
|
|
|
769
1124
|
};
|
|
770
1125
|
}
|
|
771
1126
|
|
|
772
|
-
function openclawRoots() {
|
|
773
|
-
return [
|
|
774
|
-
path.join(os.homedir(), '.openclaw', 'workspace'),
|
|
775
|
-
path.join(process.cwd(), '.openclaw', 'workspace'),
|
|
776
|
-
process.cwd()
|
|
777
|
-
];
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
function hermesRoots() {
|
|
781
|
-
return [path.join(os.homedir(), '.hermes'), path.join(process.cwd(), '.hermes')];
|
|
782
|
-
}
|
|
783
|
-
|
|
784
1127
|
function sourceDetected(source) {
|
|
785
|
-
if (source === 'openclaw') {
|
|
786
|
-
return openclawRoots().some(
|
|
787
|
-
root => fileExists(path.join(root, 'SOUL.md')) || fileExists(path.join(root, 'skills'))
|
|
788
|
-
);
|
|
789
|
-
}
|
|
790
|
-
if (source === 'hermes') {
|
|
791
|
-
return hermesRoots().some(
|
|
792
|
-
root => fileExists(path.join(root, 'SOUL.md')) || fileExists(path.join(root, 'skills'))
|
|
793
|
-
);
|
|
794
|
-
}
|
|
795
1128
|
if (source === 'cursor') {
|
|
796
1129
|
return (
|
|
797
1130
|
fileExists(path.join(process.cwd(), '.cursor', 'rules')) ||
|
|
@@ -821,7 +1154,7 @@ function detectImportSources() {
|
|
|
821
1154
|
|
|
822
1155
|
function chooseImportSource(requestedSource, detectedSources) {
|
|
823
1156
|
if (requestedSource && requestedSource !== 'auto') return requestedSource;
|
|
824
|
-
const preference = ['
|
|
1157
|
+
const preference = ['claude-code', 'cursor', 'claude-desktop'];
|
|
825
1158
|
return (
|
|
826
1159
|
preference.find(source => detectedSources.some(candidate => candidate.source === source)) ||
|
|
827
1160
|
'cursor'
|
|
@@ -840,79 +1173,6 @@ function mergeManifest(base, next) {
|
|
|
840
1173
|
};
|
|
841
1174
|
}
|
|
842
1175
|
|
|
843
|
-
function buildOpenclawManifest(warnings) {
|
|
844
|
-
const source = 'openclaw';
|
|
845
|
-
const root = openclawRoots().find(
|
|
846
|
-
candidate =>
|
|
847
|
-
fileExists(path.join(candidate, 'SOUL.md')) || fileExists(path.join(candidate, 'skills'))
|
|
848
|
-
);
|
|
849
|
-
const manifest = createEmptyManifest(source);
|
|
850
|
-
if (!root) {
|
|
851
|
-
warnings.push('OpenClaw workspace was not found.');
|
|
852
|
-
return manifest;
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
const soul = instructionEntry(path.join(root, 'SOUL.md'), 'SOUL.md');
|
|
856
|
-
const memory = instructionEntry(path.join(root, 'MEMORY.md'), 'MEMORY.md');
|
|
857
|
-
if (soul) manifest.instructions.push(soul);
|
|
858
|
-
if (memory) manifest.memories.push(memory);
|
|
859
|
-
|
|
860
|
-
for (const skillName of listDirectoryNames(path.join(root, 'skills'))) {
|
|
861
|
-
manifest.skills.push({
|
|
862
|
-
name: skillName,
|
|
863
|
-
kind: 'skill',
|
|
864
|
-
source,
|
|
865
|
-
path: portablePath(path.join(root, 'skills', skillName)),
|
|
866
|
-
hasSkillMd: fileExists(path.join(root, 'skills', skillName, 'SKILL.md'))
|
|
867
|
-
});
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
manifest.secretRefs.push(...parseEnvSecretRefs(path.join(root, '.env'), source, warnings));
|
|
871
|
-
manifest.mcpServers.push(
|
|
872
|
-
...readMcpServers(
|
|
873
|
-
path.join(os.homedir(), '.openclaw', 'agents', 'main', 'agent', 'claude-mcp.json'),
|
|
874
|
-
source,
|
|
875
|
-
warnings
|
|
876
|
-
),
|
|
877
|
-
...readMcpServers(
|
|
878
|
-
path.join(os.homedir(), '.openclaw', 'agents', 'main', 'agent', 'runtime-mcp.json'),
|
|
879
|
-
source,
|
|
880
|
-
warnings
|
|
881
|
-
)
|
|
882
|
-
);
|
|
883
|
-
return manifest;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
function buildHermesManifest(warnings) {
|
|
887
|
-
const source = 'hermes';
|
|
888
|
-
const root = hermesRoots().find(
|
|
889
|
-
candidate =>
|
|
890
|
-
fileExists(path.join(candidate, 'SOUL.md')) || fileExists(path.join(candidate, 'skills'))
|
|
891
|
-
);
|
|
892
|
-
const manifest = createEmptyManifest(source);
|
|
893
|
-
if (!root) {
|
|
894
|
-
warnings.push('HERMES workspace was not found.');
|
|
895
|
-
return manifest;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
const soul = instructionEntry(path.join(root, 'SOUL.md'), 'SOUL.md');
|
|
899
|
-
if (soul) manifest.instructions.push(soul);
|
|
900
|
-
|
|
901
|
-
for (const skillName of listDirectoryNames(path.join(root, 'skills'))) {
|
|
902
|
-
manifest.skills.push({
|
|
903
|
-
name: skillName,
|
|
904
|
-
kind: 'skill',
|
|
905
|
-
source,
|
|
906
|
-
path: portablePath(path.join(root, 'skills', skillName)),
|
|
907
|
-
hasSkillMd: fileExists(path.join(root, 'skills', skillName, 'SKILL.md'))
|
|
908
|
-
});
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
manifest.secretRefs.push(...parseEnvSecretRefs(path.join(root, '.env'), source, warnings));
|
|
912
|
-
manifest.mcpServers.push(...readMcpServers(path.join(root, 'config.json'), source, warnings));
|
|
913
|
-
return manifest;
|
|
914
|
-
}
|
|
915
|
-
|
|
916
1176
|
function buildCursorManifest(warnings) {
|
|
917
1177
|
const source = 'cursor';
|
|
918
1178
|
const manifest = createEmptyManifest(source);
|
|
@@ -966,8 +1226,6 @@ function buildClaudeDesktopManifest(warnings) {
|
|
|
966
1226
|
}
|
|
967
1227
|
|
|
968
1228
|
function buildSourceManifest(source, warnings) {
|
|
969
|
-
if (source === 'openclaw') return buildOpenclawManifest(warnings);
|
|
970
|
-
if (source === 'hermes') return buildHermesManifest(warnings);
|
|
971
1229
|
if (source === 'cursor') return buildCursorManifest(warnings);
|
|
972
1230
|
if (source === 'claude-code') return buildClaudeCodeManifest(warnings);
|
|
973
1231
|
if (source === 'claude-desktop') return buildClaudeDesktopManifest(warnings);
|
|
@@ -1240,111 +1498,48 @@ function runClientOperations(clients, scope, cwd, dryRun, runner) {
|
|
|
1240
1498
|
});
|
|
1241
1499
|
}
|
|
1242
1500
|
|
|
1243
|
-
function printResultSummary(command, scope, results, accessKey) {
|
|
1244
|
-
const changedCount = results.filter(result => result.changed).length;
|
|
1245
|
-
const configuredClients = results
|
|
1246
|
-
.filter(result => result.configured)
|
|
1247
|
-
.map(result => result.client)
|
|
1248
|
-
.join(', ');
|
|
1249
|
-
const failures = results.filter(result => result.error);
|
|
1250
|
-
const lines = [
|
|
1251
|
-
`NEUS ${command} completed for ${results.length} client${results.length === 1 ? '' : 's'} in ${scope} scope.`,
|
|
1252
|
-
`Configured: ${configuredClients || 'none'}.`
|
|
1253
|
-
];
|
|
1254
|
-
|
|
1255
|
-
if (changedCount > 0) {
|
|
1256
|
-
lines.push(`Updated: ${changedCount} target${changedCount === 1 ? '' : 's'}.`);
|
|
1257
|
-
}
|
|
1258
1501
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
`Sign in with: neus auth (opens browser) or neus auth --access-key <npk_...> (servers and CI only)`
|
|
1262
|
-
);
|
|
1263
|
-
}
|
|
1264
|
-
if (command === 'init' || command === 'setup') {
|
|
1265
|
-
lines.push('All hosts (Cursor, Codex, OpenClaw, Hermes, Windsurf, Gemini, …): https://docs.neus.network/mcp/ide-plugin');
|
|
1266
|
-
lines.push('Claude Code plugin: neus-trust@neus — same page');
|
|
1267
|
-
lines.push(
|
|
1268
|
-
'Auto-setup clients: claude, cursor, vscode — re-run with --client to limit scope'
|
|
1269
|
-
);
|
|
1270
|
-
}
|
|
1271
|
-
if ((command === 'init' || command === 'auth') && accessKey) {
|
|
1272
|
-
lines.push(
|
|
1273
|
-
'Personal account tools are enabled.'
|
|
1274
|
-
);
|
|
1275
|
-
}
|
|
1276
|
-
if (command === 'status') {
|
|
1277
|
-
const enabled = results.filter(result => result.configured).map(result => result.client);
|
|
1278
|
-
lines.push(`Active: ${enabled.length > 0 ? enabled.join(', ') : 'none'}.`);
|
|
1279
|
-
}
|
|
1280
|
-
if (failures.length > 0) {
|
|
1281
|
-
lines.push(
|
|
1282
|
-
`Issues: ${failures.map(result => `${result.client}: ${result.error}`).join(' | ')}`
|
|
1283
|
-
);
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
process.stdout.write(`${lines.join('\n')}\n`);
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
function printImportSummary(payload) {
|
|
1290
|
-
printBrandHeader('agent import');
|
|
1502
|
+
function printImportSummary(payload, cliOptions = {}) {
|
|
1503
|
+
emitCliBanner(cliOptions);
|
|
1291
1504
|
const manifest = payload.manifest;
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
lines.push('');
|
|
1307
|
-
lines.push(paint('Notes', 'yellow'));
|
|
1308
|
-
lines.push(...payload.warnings.map(warning => `- ${warning}`));
|
|
1309
|
-
}
|
|
1310
|
-
lines.push('');
|
|
1311
|
-
lines.push(
|
|
1312
|
-
'Next: run `neus setup`, then `neus doctor --live`, then call `neus_agent_create` from your MCP client.'
|
|
1313
|
-
);
|
|
1314
|
-
process.stdout.write(`${lines.join('\n')}\n`);
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
function printExportSummary(payload) {
|
|
1318
|
-
printBrandHeader('agent export');
|
|
1319
|
-
const lines = [
|
|
1320
|
-
`${paint('Format', 'cyan')}: ${payload.format}`,
|
|
1321
|
-
`${paint('Source', 'cyan')}: ${payload.manifest.source}`,
|
|
1322
|
-
`${paint('Skills', 'cyan')}: ${payload.manifest.skills?.length || 0}`,
|
|
1323
|
-
`${paint('Proof refs', 'cyan')}: ${payload.manifest.proofHints?.qHashes?.length || 0} qHash value${payload.manifest.proofHints?.qHashes?.length === 1 ? '' : 's'}`
|
|
1324
|
-
];
|
|
1505
|
+
writeCliLine(paint('import', 'green'));
|
|
1506
|
+
logStep('ok', 'source', `${manifest.source}${payload.dryRun ? ' (dry run)' : ''}`);
|
|
1507
|
+
logStep('ok', 'skills', String(manifest.skills.length));
|
|
1508
|
+
logStep('ok', 'servers', String(manifest.mcpServers.length));
|
|
1509
|
+
writeCliLine('');
|
|
1510
|
+
logStep('next', 'next', 'neus setup | neus auth');
|
|
1511
|
+
writeCliLine('');
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
function printExportSummary(payload, cliOptions = {}) {
|
|
1515
|
+
emitCliBanner(cliOptions);
|
|
1516
|
+
writeCliLine(paint('export', 'green'));
|
|
1517
|
+
logStep('ok', 'format', payload.format);
|
|
1518
|
+
logStep('ok', 'source', payload.manifest.source);
|
|
1325
1519
|
if (payload.outputPath) {
|
|
1326
|
-
|
|
1520
|
+
logStep('ok', 'output', payload.outputPath);
|
|
1327
1521
|
}
|
|
1328
|
-
|
|
1522
|
+
writeCliLine('');
|
|
1329
1523
|
}
|
|
1330
1524
|
|
|
1331
1525
|
function runInit(options) {
|
|
1332
1526
|
const scope = resolveScope(options);
|
|
1333
|
-
|
|
1527
|
+
const accessKey = resolveAccessKey(options);
|
|
1528
|
+
ensureSafeAuth('init', scope, accessKey);
|
|
1334
1529
|
const cwd = process.cwd();
|
|
1335
1530
|
|
|
1336
1531
|
const clients = resolveClients(scope, options.clients);
|
|
1337
1532
|
ensureClientSelection(scope, clients);
|
|
1338
1533
|
|
|
1339
1534
|
const results = runClientOperations(clients, scope, cwd, options.dryRun, client =>
|
|
1340
|
-
installClient(client, scope,
|
|
1535
|
+
installClient(client, scope, accessKey, options.dryRun, cwd)
|
|
1341
1536
|
);
|
|
1342
1537
|
const payload = {
|
|
1343
1538
|
command: 'init',
|
|
1344
1539
|
scope,
|
|
1345
1540
|
detectedClients: defaultUserClients(),
|
|
1346
1541
|
clients,
|
|
1347
|
-
accessKeyConfigured: Boolean(
|
|
1542
|
+
accessKeyConfigured: Boolean(accessKey),
|
|
1348
1543
|
results,
|
|
1349
1544
|
hasErrors: results.some(result => result.error)
|
|
1350
1545
|
};
|
|
@@ -1352,7 +1547,10 @@ function runInit(options) {
|
|
|
1352
1547
|
if (options.json) {
|
|
1353
1548
|
printJson(payload);
|
|
1354
1549
|
} else {
|
|
1355
|
-
|
|
1550
|
+
printFlowSummary('init', scope, results, {
|
|
1551
|
+
nextStep: accessKey ? '' : 'neus auth',
|
|
1552
|
+
cliOptions: options
|
|
1553
|
+
});
|
|
1356
1554
|
}
|
|
1357
1555
|
|
|
1358
1556
|
if (payload.hasErrors) {
|
|
@@ -1386,6 +1584,8 @@ async function runAuthBrowser(options) {
|
|
|
1386
1584
|
}
|
|
1387
1585
|
const clients = resolveClients(scope, options.clients);
|
|
1388
1586
|
ensureClientSelection(scope, clients);
|
|
1587
|
+
const browserManagedClients = clients.filter(client => client !== 'codex');
|
|
1588
|
+
const hostManagedClients = clients.filter(client => client === 'codex');
|
|
1389
1589
|
const cwd = process.cwd();
|
|
1390
1590
|
|
|
1391
1591
|
const { createServer } = await import('node:http');
|
|
@@ -1456,9 +1656,14 @@ async function runAuthBrowser(options) {
|
|
|
1456
1656
|
res.end('<html><body><h2>Authenticated</h2><p>You can close this tab and return to your terminal.</p></body></html>');
|
|
1457
1657
|
server.close();
|
|
1458
1658
|
|
|
1459
|
-
const results = runClientOperations(
|
|
1659
|
+
const results = runClientOperations(browserManagedClients, scope, cwd, options.dryRun, client =>
|
|
1460
1660
|
installClient(client, scope, accessToken, options.dryRun, cwd)
|
|
1461
1661
|
);
|
|
1662
|
+
results.push(
|
|
1663
|
+
...runClientOperations(hostManagedClients, scope, cwd, options.dryRun, () =>
|
|
1664
|
+
authCodex(scope, options.dryRun, cwd, options)
|
|
1665
|
+
)
|
|
1666
|
+
);
|
|
1462
1667
|
const payload = {
|
|
1463
1668
|
command: 'auth',
|
|
1464
1669
|
scope,
|
|
@@ -1497,17 +1702,16 @@ async function runAuthBrowser(options) {
|
|
|
1497
1702
|
});
|
|
1498
1703
|
const authUrl = `${NEUS_APP_URL}/oauth/authorize?${authParams.toString()}`;
|
|
1499
1704
|
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
console.log('');
|
|
1705
|
+
if (!options.json) {
|
|
1706
|
+
printAuthBrowserIntro(authUrl, options);
|
|
1707
|
+
logStep('next', 'wait', 'finish sign-in in the browser');
|
|
1708
|
+
}
|
|
1505
1709
|
|
|
1506
1710
|
const { exec } = require('node:child_process');
|
|
1507
1711
|
const openCmd = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
1508
|
-
exec(`${openCmd} "${authUrl}"`,
|
|
1509
|
-
if (err) {
|
|
1510
|
-
|
|
1712
|
+
exec(`${openCmd} "${authUrl}"`, err => {
|
|
1713
|
+
if (err && !options.json) {
|
|
1714
|
+
logStep('warn', 'browser', 'open the URL above manually');
|
|
1511
1715
|
}
|
|
1512
1716
|
});
|
|
1513
1717
|
});
|
|
@@ -1522,27 +1726,39 @@ async function runAuthBrowser(options) {
|
|
|
1522
1726
|
|
|
1523
1727
|
function runAuth(options) {
|
|
1524
1728
|
const scope = resolveScope(options);
|
|
1525
|
-
|
|
1729
|
+
const accessKey = resolveAccessKey(options);
|
|
1730
|
+
ensureSafeAuth('auth', scope, accessKey);
|
|
1526
1731
|
const cwd = process.cwd();
|
|
1732
|
+
const clients = resolveClients(scope, options.clients);
|
|
1733
|
+
ensureClientSelection(scope, clients);
|
|
1527
1734
|
|
|
1528
|
-
|
|
1529
|
-
|
|
1735
|
+
if (!accessKey) {
|
|
1736
|
+
if (clients.length === 1 && clients[0] === 'codex') {
|
|
1737
|
+
const results = runClientOperations(clients, scope, cwd, options.dryRun, () =>
|
|
1738
|
+
authCodex(scope, options.dryRun, cwd, options)
|
|
1739
|
+
);
|
|
1740
|
+
return {
|
|
1741
|
+
command: 'auth',
|
|
1742
|
+
scope,
|
|
1743
|
+
clients,
|
|
1744
|
+
accessKeyConfigured: results.some(result => result.authConfigured === true),
|
|
1745
|
+
authMethod: 'host-oauth',
|
|
1746
|
+
results,
|
|
1747
|
+
hasErrors: results.some(result => result.error)
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1530
1750
|
return runAuthBrowser(options);
|
|
1531
1751
|
}
|
|
1532
1752
|
|
|
1533
|
-
// Manual key flow: --access-key provided
|
|
1534
|
-
const clients = resolveClients(scope, options.clients);
|
|
1535
|
-
ensureClientSelection(scope, clients);
|
|
1536
|
-
|
|
1537
1753
|
const results = runClientOperations(clients, scope, cwd, options.dryRun, client =>
|
|
1538
|
-
installClient(client, scope,
|
|
1754
|
+
installClient(client, scope, accessKey, options.dryRun, cwd)
|
|
1539
1755
|
);
|
|
1540
1756
|
const payload = {
|
|
1541
1757
|
command: 'auth',
|
|
1542
1758
|
scope,
|
|
1543
1759
|
clients,
|
|
1544
1760
|
accessKeyConfigured: true,
|
|
1545
|
-
authMethod:
|
|
1761
|
+
authMethod: resolveAuthMethod(options, accessKey),
|
|
1546
1762
|
results,
|
|
1547
1763
|
hasErrors: results.some(result => result.error)
|
|
1548
1764
|
};
|
|
@@ -1570,14 +1786,15 @@ function runStatus(options) {
|
|
|
1570
1786
|
printJson(payload);
|
|
1571
1787
|
return;
|
|
1572
1788
|
}
|
|
1573
|
-
|
|
1789
|
+
printFlowSummary('status', scope, inspected, { cliOptions: options });
|
|
1574
1790
|
}
|
|
1575
1791
|
|
|
1576
|
-
function runSetup(options) {
|
|
1792
|
+
async function runSetup(options) {
|
|
1577
1793
|
const scope = resolveScope(options);
|
|
1578
|
-
|
|
1794
|
+
const accessKey = resolveAccessKey(options);
|
|
1795
|
+
ensureSafeAuth('setup', scope, accessKey);
|
|
1579
1796
|
const cwd = process.cwd();
|
|
1580
|
-
if (options.project &&
|
|
1797
|
+
if (options.project && accessKey) {
|
|
1581
1798
|
throw new Error(
|
|
1582
1799
|
'Access keys are only supported in user scope. Remove --project or omit --access-key.'
|
|
1583
1800
|
);
|
|
@@ -1585,9 +1802,8 @@ function runSetup(options) {
|
|
|
1585
1802
|
|
|
1586
1803
|
const clients = resolveClients(scope, options.clients);
|
|
1587
1804
|
ensureClientSelection(scope, clients);
|
|
1588
|
-
|
|
1589
1805
|
const initResults = runClientOperations(clients, scope, cwd, options.dryRun, client =>
|
|
1590
|
-
installClient(client, scope,
|
|
1806
|
+
installClient(client, scope, accessKey, options.dryRun, cwd)
|
|
1591
1807
|
);
|
|
1592
1808
|
|
|
1593
1809
|
const payload = {
|
|
@@ -1595,23 +1811,46 @@ function runSetup(options) {
|
|
|
1595
1811
|
scope,
|
|
1596
1812
|
detectedClients: defaultUserClients(),
|
|
1597
1813
|
clients,
|
|
1598
|
-
accessKeyConfigured: Boolean(
|
|
1814
|
+
accessKeyConfigured: Boolean(accessKey),
|
|
1599
1815
|
results: initResults,
|
|
1600
1816
|
hasErrors: initResults.some(result => result.error)
|
|
1601
1817
|
};
|
|
1602
1818
|
|
|
1819
|
+
if (payload.hasErrors) {
|
|
1820
|
+
if (options.json) printJson(payload);
|
|
1821
|
+
else printFlowSummary('setup', scope, initResults, { cliOptions: options });
|
|
1822
|
+
process.exitCode = 1;
|
|
1823
|
+
return payload;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1603
1826
|
if (options.json) {
|
|
1604
1827
|
printJson(payload);
|
|
1605
|
-
|
|
1606
|
-
printResultSummary('setup', scope, initResults, options.accessKey);
|
|
1828
|
+
return payload;
|
|
1607
1829
|
}
|
|
1608
1830
|
|
|
1609
|
-
|
|
1610
|
-
|
|
1831
|
+
printFlowSummary('setup', scope, initResults, {
|
|
1832
|
+
nextStep: accessKey ? 'neus_context in your MCP client' : '',
|
|
1833
|
+
cliOptions: options
|
|
1834
|
+
});
|
|
1835
|
+
|
|
1836
|
+
if (!accessKey && !options.dryRun) {
|
|
1837
|
+
const authResult = await runAuth(options);
|
|
1838
|
+
if (authResult && !authResult.hasErrors) {
|
|
1839
|
+
printFlowSummary('auth', authResult.scope, authResult.results, {
|
|
1840
|
+
nextStep: 'neus_context in your MCP client',
|
|
1841
|
+
cliOptions: options
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
if (authResult?.hasErrors) {
|
|
1845
|
+
process.exitCode = 1;
|
|
1846
|
+
}
|
|
1847
|
+
return authResult || payload;
|
|
1611
1848
|
}
|
|
1849
|
+
|
|
1850
|
+
return payload;
|
|
1612
1851
|
}
|
|
1613
1852
|
|
|
1614
|
-
function runImport(options) {
|
|
1853
|
+
function runImport(options, { emitOutput = true } = {}) {
|
|
1615
1854
|
if (!SUPPORTED_IMPORT_SOURCES.includes(options.source)) {
|
|
1616
1855
|
throw new Error(`Unsupported import source: ${options.source}`);
|
|
1617
1856
|
}
|
|
@@ -1636,15 +1875,18 @@ function runImport(options) {
|
|
|
1636
1875
|
manifest.mcpServers.length === 0
|
|
1637
1876
|
};
|
|
1638
1877
|
|
|
1639
|
-
if (
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1878
|
+
if (emitOutput) {
|
|
1879
|
+
if (options.json) {
|
|
1880
|
+
printJson(payload);
|
|
1881
|
+
} else {
|
|
1882
|
+
printImportSummary(payload, options);
|
|
1883
|
+
}
|
|
1643
1884
|
}
|
|
1644
1885
|
|
|
1645
|
-
if (payload.hasErrors) {
|
|
1886
|
+
if (emitOutput && payload.hasErrors) {
|
|
1646
1887
|
process.exitCode = 1;
|
|
1647
1888
|
}
|
|
1889
|
+
return payload;
|
|
1648
1890
|
}
|
|
1649
1891
|
|
|
1650
1892
|
function runExport(options) {
|
|
@@ -1679,7 +1921,7 @@ function runExport(options) {
|
|
|
1679
1921
|
printJson(payload);
|
|
1680
1922
|
return;
|
|
1681
1923
|
}
|
|
1682
|
-
printExportSummary(payload);
|
|
1924
|
+
printExportSummary(payload, options);
|
|
1683
1925
|
}
|
|
1684
1926
|
|
|
1685
1927
|
async function runDoctor(options) {
|
|
@@ -1692,12 +1934,13 @@ async function runDoctor(options) {
|
|
|
1692
1934
|
inspectClient(client, scope, cwd)
|
|
1693
1935
|
);
|
|
1694
1936
|
const configuredClients = inspected.filter(r => r.configured);
|
|
1937
|
+
const liveAccessKey = resolveLiveAccessKey(options, scope, cwd);
|
|
1695
1938
|
const payload = {
|
|
1696
1939
|
command: 'doctor',
|
|
1697
1940
|
scope,
|
|
1698
1941
|
clients: inspected,
|
|
1699
1942
|
configuredCount: configuredClients.length,
|
|
1700
|
-
accessKeyPresent: Boolean(
|
|
1943
|
+
accessKeyPresent: Boolean(liveAccessKey),
|
|
1701
1944
|
profileConnectable: false,
|
|
1702
1945
|
agentVerified: false,
|
|
1703
1946
|
live: options.live,
|
|
@@ -1707,9 +1950,12 @@ async function runDoctor(options) {
|
|
|
1707
1950
|
};
|
|
1708
1951
|
|
|
1709
1952
|
if (options.live) {
|
|
1710
|
-
payload.mcp = await runLiveMcpDiagnostics(
|
|
1711
|
-
|
|
1712
|
-
|
|
1953
|
+
payload.mcp = await runLiveMcpDiagnostics(liveAccessKey);
|
|
1954
|
+
if (liveAccessKey) {
|
|
1955
|
+
payload.profileConnectable = Boolean(payload.mcp.authenticated);
|
|
1956
|
+
payload.hasErrors =
|
|
1957
|
+
payload.hasErrors || !payload.mcp.reachable || !payload.mcp.authenticated;
|
|
1958
|
+
}
|
|
1713
1959
|
}
|
|
1714
1960
|
|
|
1715
1961
|
if (options.json) {
|
|
@@ -1717,45 +1963,60 @@ async function runDoctor(options) {
|
|
|
1717
1963
|
return;
|
|
1718
1964
|
}
|
|
1719
1965
|
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1966
|
+
if (configuredClients.length === 0) {
|
|
1967
|
+
emitCliBanner(options);
|
|
1968
|
+
writeCliLine(paint('doctor', 'green'));
|
|
1969
|
+
for (const result of inspected) {
|
|
1970
|
+
if (result.error) {
|
|
1971
|
+
logStep('warn', result.client, result.error);
|
|
1972
|
+
} else if (result.authConfigured === null) {
|
|
1973
|
+
logStep('skip', result.client, 'not installed');
|
|
1974
|
+
} else {
|
|
1975
|
+
logStep('skip', result.client, 'not configured');
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
writeCliLine('');
|
|
1979
|
+
writeCliLine(paint('MCP endpoint', 'cyan'));
|
|
1980
|
+
writeGuidanceLine(NEUS_MCP_URL);
|
|
1981
|
+
writeCliLine(paint('Profile connection', 'cyan'));
|
|
1982
|
+
writeGuidanceLine(`No selected MCP host is configured yet. Run \`${preferredSetupCommand(inspected)}\`.`);
|
|
1983
|
+
writeGuidanceLine(`Then run \`${preferredAuthCommand(inspected)}\` and re-check with \`npx -y -p @neus/sdk neus doctor --live\`.`);
|
|
1984
|
+
writeCliLine('');
|
|
1985
|
+
process.exitCode = 1;
|
|
1986
|
+
return;
|
|
1731
1987
|
}
|
|
1732
1988
|
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1989
|
+
printFlowSummary('doctor', scope, inspected, { cliOptions: options });
|
|
1990
|
+
const hasCodex = inspected.some(result => result.client === 'codex');
|
|
1991
|
+
writeCliLine(paint('Profile connection', 'cyan'));
|
|
1992
|
+
if (options.live && payload.mcp) {
|
|
1993
|
+
if (!liveAccessKey) {
|
|
1994
|
+
writeGuidanceLine(
|
|
1995
|
+
hasCodex
|
|
1996
|
+
? 'Codex owns OAuth: run `neus auth --client codex` or `codex mcp login neus`.'
|
|
1997
|
+
: 'No account credential found for the configured MCP clients. Run `neus auth`.'
|
|
1737
1998
|
);
|
|
1738
|
-
lines.push(`Tools: ${payload.mcp.toolsCount || 0} discovered.`);
|
|
1739
1999
|
} else {
|
|
1740
|
-
|
|
1741
|
-
|
|
2000
|
+
logStep(
|
|
2001
|
+
payload.mcp.authenticated ? 'ok' : 'warn',
|
|
2002
|
+
'profile',
|
|
2003
|
+
payload.mcp.authenticated
|
|
2004
|
+
? `live MCP context confirmed; ${payload.mcp.toolsCount || 0} tools discovered`
|
|
2005
|
+
: 'live MCP context was not confirmed'
|
|
1742
2006
|
);
|
|
1743
2007
|
}
|
|
2008
|
+
} else if (liveAccessKey) {
|
|
2009
|
+
writeGuidanceLine('Saved credential found. Run `neus doctor --live` to confirm Profile context.');
|
|
1744
2010
|
} else {
|
|
1745
|
-
|
|
1746
|
-
|
|
2011
|
+
writeGuidanceLine(
|
|
2012
|
+
hasCodex
|
|
2013
|
+
? 'Codex owns OAuth: run `neus auth --client codex` or `codex mcp login neus`.'
|
|
2014
|
+
: 'No account credential found. Run `neus auth` for browser sign-in.'
|
|
1747
2015
|
);
|
|
1748
2016
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
);
|
|
1753
|
-
lines.push('');
|
|
1754
|
-
lines.push(
|
|
1755
|
-
'Next: Open your editor/IDE, connect to the NEUS MCP endpoint, and run `neus_context`.'
|
|
1756
|
-
);
|
|
1757
|
-
|
|
1758
|
-
process.stdout.write(`\n${lines.join('\n')}\n`);
|
|
2017
|
+
writeGuidanceLine('In the connected MCP client, call `neus_context` first.');
|
|
2018
|
+
writeGuidanceLine('For agents, run `neus_agent_link` before assuming identity or delegation is ready.');
|
|
2019
|
+
writeCliLine('');
|
|
1759
2020
|
}
|
|
1760
2021
|
|
|
1761
2022
|
async function runDisconnect(options) {
|
|
@@ -1764,12 +2025,15 @@ async function runDisconnect(options) {
|
|
|
1764
2025
|
throw new Error('Disconnect only supports user scope. Remove --project flag.');
|
|
1765
2026
|
}
|
|
1766
2027
|
|
|
1767
|
-
|
|
1768
|
-
|
|
2028
|
+
const cwd = process.cwd();
|
|
2029
|
+
const token = resolveLiveAccessKey(options, scope, cwd);
|
|
2030
|
+
if (!token) {
|
|
2031
|
+
throw new Error(
|
|
2032
|
+
'Credential required. Run `neus disconnect --access-key <token>` or sign in first (`neus auth`).'
|
|
2033
|
+
);
|
|
1769
2034
|
}
|
|
1770
2035
|
|
|
1771
2036
|
try {
|
|
1772
|
-
const token = String(options.accessKey || '').trim();
|
|
1773
2037
|
const isProfileKey = token.startsWith('npk_');
|
|
1774
2038
|
const resp = isProfileKey
|
|
1775
2039
|
? await fetch(NEUS_PROFILE_KEY_ENDPOINT, {
|
|
@@ -1802,7 +2066,6 @@ async function runDisconnect(options) {
|
|
|
1802
2066
|
throw error;
|
|
1803
2067
|
}
|
|
1804
2068
|
|
|
1805
|
-
const cwd = process.cwd();
|
|
1806
2069
|
const clients = resolveClients(scope, options.clients);
|
|
1807
2070
|
ensureClientSelection(scope, clients);
|
|
1808
2071
|
|
|
@@ -1822,9 +2085,11 @@ async function runDisconnect(options) {
|
|
|
1822
2085
|
if (options.json) {
|
|
1823
2086
|
printJson(payload);
|
|
1824
2087
|
} else {
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
2088
|
+
emitCliBanner(options);
|
|
2089
|
+
writeCliLine(paint('disconnect', 'green'));
|
|
2090
|
+
logStep('ok', 'signed-out', 'MCP configs updated');
|
|
2091
|
+
logStep('next', 'next', 'neus auth');
|
|
2092
|
+
writeCliLine('');
|
|
1828
2093
|
}
|
|
1829
2094
|
}
|
|
1830
2095
|
|
|
@@ -1845,13 +2110,16 @@ async function main() {
|
|
|
1845
2110
|
if (result) {
|
|
1846
2111
|
if (options.json) {
|
|
1847
2112
|
printJson(result);
|
|
2113
|
+
} else if (result.authMethod !== 'browser') {
|
|
2114
|
+
printFlowSummary('auth', result.scope, result.results, {
|
|
2115
|
+
nextStep: 'neus_context in your MCP client',
|
|
2116
|
+
cliOptions: options
|
|
2117
|
+
});
|
|
1848
2118
|
} else {
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
console.log(' Authenticated via browser. Your MCP clients are now configured.');
|
|
1854
|
-
}
|
|
2119
|
+
printFlowSummary('auth', result.scope, result.results, {
|
|
2120
|
+
nextStep: 'neus_context in your MCP client',
|
|
2121
|
+
cliOptions: options
|
|
2122
|
+
});
|
|
1855
2123
|
}
|
|
1856
2124
|
if (result.hasErrors) {
|
|
1857
2125
|
process.exitCode = 1;
|
|
@@ -1864,7 +2132,10 @@ async function main() {
|
|
|
1864
2132
|
return;
|
|
1865
2133
|
}
|
|
1866
2134
|
if (command === 'setup') {
|
|
1867
|
-
runSetup(options);
|
|
2135
|
+
const setupResult = await runSetup(options);
|
|
2136
|
+
if (setupResult?.hasErrors) {
|
|
2137
|
+
process.exitCode = 1;
|
|
2138
|
+
}
|
|
1868
2139
|
return;
|
|
1869
2140
|
}
|
|
1870
2141
|
if (command === 'doctor') {
|