@neus/sdk 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.
- package/README.md +31 -53
- package/cjs/client.cjs +162 -15
- package/cjs/errors.cjs +2 -35
- package/cjs/gates.cjs +1 -21
- package/cjs/index.cjs +174 -15
- package/cjs/mcp-hosts.cjs +125 -0
- package/cjs/utils.cjs +2 -0
- package/cli/neus.mjs +628 -349
- package/client.js +119 -15
- package/errors.js +154 -189
- package/gates.js +0 -20
- package/index.js +2 -0
- package/mcp-hosts.js +121 -0
- package/package.json +9 -5
- package/sponsor.js +95 -0
- package/types.d.ts +66 -6
- package/utils.js +2 -0
- package/widgets/README.md +7 -11
- package/widgets/verify-gate/dist/ProofBadge.js +14 -4
- package/widgets/verify-gate/dist/VerifyGate.js +73 -195
- package/CHANGELOG.md +0 -3
- package/neus-logo.svg +0 -3
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) {
|
|
@@ -496,20 +753,31 @@ function cursorConfigPath(scope, cwd) {
|
|
|
496
753
|
}
|
|
497
754
|
|
|
498
755
|
function vscodeConfigPath(scope, cwd) {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
756
|
+
if (scope !== 'user') {
|
|
757
|
+
return path.join(cwd, '.vscode', 'mcp.json');
|
|
758
|
+
}
|
|
759
|
+
if (process.platform === 'darwin') {
|
|
760
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'Code', 'User', 'mcp.json');
|
|
761
|
+
}
|
|
762
|
+
if (process.platform === 'win32') {
|
|
763
|
+
return path.join(
|
|
764
|
+
process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
|
|
765
|
+
'Code',
|
|
766
|
+
'User',
|
|
767
|
+
'mcp.json'
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
return path.join(os.homedir(), '.config', 'Code', 'User', 'mcp.json');
|
|
507
771
|
}
|
|
508
772
|
|
|
509
773
|
function claudeProjectConfigPath(cwd) {
|
|
510
774
|
return path.join(cwd, '.mcp.json');
|
|
511
775
|
}
|
|
512
776
|
|
|
777
|
+
function codexConfigPath() {
|
|
778
|
+
return path.join(os.homedir(), '.codex', 'config.toml');
|
|
779
|
+
}
|
|
780
|
+
|
|
513
781
|
function installCursor(scope, accessKey, dryRun, cwd) {
|
|
514
782
|
const targetPath = cursorConfigPath(scope, cwd);
|
|
515
783
|
const doc = readJsonFile(targetPath, { mcpServers: {} });
|
|
@@ -519,7 +787,7 @@ function installCursor(scope, accessKey, dryRun, cwd) {
|
|
|
519
787
|
...(doc.mcpServers && typeof doc.mcpServers === 'object' && !Array.isArray(doc.mcpServers)
|
|
520
788
|
? doc.mcpServers
|
|
521
789
|
: {}),
|
|
522
|
-
[
|
|
790
|
+
[NEUS_MCP_SERVER_NAME]: buildCursorServer(accessKey)
|
|
523
791
|
}
|
|
524
792
|
};
|
|
525
793
|
const writeResult = writeJsonFile(targetPath, next, dryRun);
|
|
@@ -545,7 +813,7 @@ function installVsCode(scope, accessKey, dryRun, cwd) {
|
|
|
545
813
|
...(doc.servers && typeof doc.servers === 'object' && !Array.isArray(doc.servers)
|
|
546
814
|
? doc.servers
|
|
547
815
|
: {}),
|
|
548
|
-
[
|
|
816
|
+
[NEUS_MCP_SERVER_NAME]: buildVsCodeServer(accessKey)
|
|
549
817
|
}
|
|
550
818
|
};
|
|
551
819
|
const writeResult = writeJsonFile(targetPath, next, dryRun);
|
|
@@ -571,7 +839,7 @@ function installClaudeProject(scope, accessKey, dryRun, cwd) {
|
|
|
571
839
|
...(doc.mcpServers && typeof doc.mcpServers === 'object' && !Array.isArray(doc.mcpServers)
|
|
572
840
|
? doc.mcpServers
|
|
573
841
|
: {}),
|
|
574
|
-
[
|
|
842
|
+
[NEUS_MCP_SERVER_NAME]: buildClaudeServer(accessKey)
|
|
575
843
|
}
|
|
576
844
|
};
|
|
577
845
|
const writeResult = writeJsonFile(targetPath, next, dryRun);
|
|
@@ -594,7 +862,7 @@ function installClaudeUser(scope, accessKey, dryRun, cwd) {
|
|
|
594
862
|
}
|
|
595
863
|
|
|
596
864
|
if (!dryRun) {
|
|
597
|
-
runCommand('claude', ['mcp', 'remove', '--scope', 'user',
|
|
865
|
+
runCommand('claude', ['mcp', 'remove', '--scope', 'user', NEUS_MCP_SERVER_NAME], cwd, true);
|
|
598
866
|
const addArgs = [
|
|
599
867
|
'mcp',
|
|
600
868
|
'add',
|
|
@@ -602,7 +870,7 @@ function installClaudeUser(scope, accessKey, dryRun, cwd) {
|
|
|
602
870
|
'http',
|
|
603
871
|
'--scope',
|
|
604
872
|
'user',
|
|
605
|
-
|
|
873
|
+
NEUS_MCP_SERVER_NAME,
|
|
606
874
|
NEUS_MCP_URL
|
|
607
875
|
];
|
|
608
876
|
if (accessKey) {
|
|
@@ -631,10 +899,66 @@ function installClaude(scope, accessKey, dryRun, cwd) {
|
|
|
631
899
|
return installClaudeUser(scope, accessKey, dryRun, cwd);
|
|
632
900
|
}
|
|
633
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
|
+
|
|
634
957
|
function installClient(client, scope, accessKey, dryRun, cwd) {
|
|
635
958
|
if (client === 'cursor') return installCursor(scope, accessKey, dryRun, cwd);
|
|
636
959
|
if (client === 'vscode') return installVsCode(scope, accessKey, dryRun, cwd);
|
|
637
960
|
if (client === 'claude') return installClaude(scope, accessKey, dryRun, cwd);
|
|
961
|
+
if (client === 'codex') return installCodex(scope, accessKey, dryRun, cwd);
|
|
638
962
|
throw new Error(`Unsupported client: ${client}`);
|
|
639
963
|
}
|
|
640
964
|
|
|
@@ -651,7 +975,7 @@ function inspectCursor(scope, cwd) {
|
|
|
651
975
|
};
|
|
652
976
|
}
|
|
653
977
|
const doc = readJsonFile(targetPath, {});
|
|
654
|
-
const server = doc.mcpServers?.[
|
|
978
|
+
const server = doc.mcpServers?.[NEUS_MCP_SERVER_NAME];
|
|
655
979
|
return {
|
|
656
980
|
client: 'cursor',
|
|
657
981
|
scope,
|
|
@@ -675,7 +999,7 @@ function inspectVsCode(scope, cwd) {
|
|
|
675
999
|
};
|
|
676
1000
|
}
|
|
677
1001
|
const doc = readJsonFile(targetPath, {});
|
|
678
|
-
const server = doc.servers?.[
|
|
1002
|
+
const server = doc.servers?.[NEUS_MCP_SERVER_NAME];
|
|
679
1003
|
return {
|
|
680
1004
|
client: 'vscode',
|
|
681
1005
|
scope,
|
|
@@ -700,7 +1024,7 @@ function inspectClaude(scope, cwd) {
|
|
|
700
1024
|
};
|
|
701
1025
|
}
|
|
702
1026
|
const doc = readJsonFile(targetPath, {});
|
|
703
|
-
const server = doc.mcpServers?.[
|
|
1027
|
+
const server = doc.mcpServers?.[NEUS_MCP_SERVER_NAME];
|
|
704
1028
|
return {
|
|
705
1029
|
client: 'claude',
|
|
706
1030
|
scope,
|
|
@@ -725,21 +1049,59 @@ function inspectClaude(scope, cwd) {
|
|
|
725
1049
|
const result = runCommand('claude', ['mcp', 'list'], cwd, true);
|
|
726
1050
|
const configured =
|
|
727
1051
|
result.status === 0 &&
|
|
728
|
-
result.stdout.split(/\r?\n/).some(line => line.trim() ===
|
|
1052
|
+
result.stdout.split(/\r?\n/).some(line => line.trim() === NEUS_MCP_SERVER_NAME);
|
|
729
1053
|
return {
|
|
730
1054
|
client: 'claude',
|
|
731
1055
|
scope,
|
|
732
1056
|
configured,
|
|
733
|
-
authConfigured: null,
|
|
1057
|
+
authConfigured: configured ? null : false,
|
|
734
1058
|
targetPath: '~/.claude.json',
|
|
735
1059
|
error: null
|
|
736
1060
|
};
|
|
737
1061
|
}
|
|
738
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
|
+
|
|
739
1100
|
function inspectClient(client, scope, cwd) {
|
|
740
1101
|
if (client === 'cursor') return inspectCursor(scope, cwd);
|
|
741
1102
|
if (client === 'vscode') return inspectVsCode(scope, cwd);
|
|
742
1103
|
if (client === 'claude') return inspectClaude(scope, cwd);
|
|
1104
|
+
if (client === 'codex') return inspectCodex(scope, cwd);
|
|
743
1105
|
throw new Error(`Unsupported client: ${client}`);
|
|
744
1106
|
}
|
|
745
1107
|
|
|
@@ -762,29 +1124,7 @@ function createEmptyManifest(source) {
|
|
|
762
1124
|
};
|
|
763
1125
|
}
|
|
764
1126
|
|
|
765
|
-
function openclawRoots() {
|
|
766
|
-
return [
|
|
767
|
-
path.join(os.homedir(), '.openclaw', 'workspace'),
|
|
768
|
-
path.join(process.cwd(), '.openclaw', 'workspace'),
|
|
769
|
-
process.cwd()
|
|
770
|
-
];
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
function hermesRoots() {
|
|
774
|
-
return [path.join(os.homedir(), '.hermes'), path.join(process.cwd(), '.hermes')];
|
|
775
|
-
}
|
|
776
|
-
|
|
777
1127
|
function sourceDetected(source) {
|
|
778
|
-
if (source === 'openclaw') {
|
|
779
|
-
return openclawRoots().some(
|
|
780
|
-
root => fileExists(path.join(root, 'SOUL.md')) || fileExists(path.join(root, 'skills'))
|
|
781
|
-
);
|
|
782
|
-
}
|
|
783
|
-
if (source === 'hermes') {
|
|
784
|
-
return hermesRoots().some(
|
|
785
|
-
root => fileExists(path.join(root, 'SOUL.md')) || fileExists(path.join(root, 'skills'))
|
|
786
|
-
);
|
|
787
|
-
}
|
|
788
1128
|
if (source === 'cursor') {
|
|
789
1129
|
return (
|
|
790
1130
|
fileExists(path.join(process.cwd(), '.cursor', 'rules')) ||
|
|
@@ -814,7 +1154,7 @@ function detectImportSources() {
|
|
|
814
1154
|
|
|
815
1155
|
function chooseImportSource(requestedSource, detectedSources) {
|
|
816
1156
|
if (requestedSource && requestedSource !== 'auto') return requestedSource;
|
|
817
|
-
const preference = ['
|
|
1157
|
+
const preference = ['claude-code', 'cursor', 'claude-desktop'];
|
|
818
1158
|
return (
|
|
819
1159
|
preference.find(source => detectedSources.some(candidate => candidate.source === source)) ||
|
|
820
1160
|
'cursor'
|
|
@@ -833,79 +1173,6 @@ function mergeManifest(base, next) {
|
|
|
833
1173
|
};
|
|
834
1174
|
}
|
|
835
1175
|
|
|
836
|
-
function buildOpenclawManifest(warnings) {
|
|
837
|
-
const source = 'openclaw';
|
|
838
|
-
const root = openclawRoots().find(
|
|
839
|
-
candidate =>
|
|
840
|
-
fileExists(path.join(candidate, 'SOUL.md')) || fileExists(path.join(candidate, 'skills'))
|
|
841
|
-
);
|
|
842
|
-
const manifest = createEmptyManifest(source);
|
|
843
|
-
if (!root) {
|
|
844
|
-
warnings.push('OpenClaw workspace was not found.');
|
|
845
|
-
return manifest;
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
const soul = instructionEntry(path.join(root, 'SOUL.md'), 'SOUL.md');
|
|
849
|
-
const memory = instructionEntry(path.join(root, 'MEMORY.md'), 'MEMORY.md');
|
|
850
|
-
if (soul) manifest.instructions.push(soul);
|
|
851
|
-
if (memory) manifest.memories.push(memory);
|
|
852
|
-
|
|
853
|
-
for (const skillName of listDirectoryNames(path.join(root, 'skills'))) {
|
|
854
|
-
manifest.skills.push({
|
|
855
|
-
name: skillName,
|
|
856
|
-
kind: 'skill',
|
|
857
|
-
source,
|
|
858
|
-
path: portablePath(path.join(root, 'skills', skillName)),
|
|
859
|
-
hasSkillMd: fileExists(path.join(root, 'skills', skillName, 'SKILL.md'))
|
|
860
|
-
});
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
manifest.secretRefs.push(...parseEnvSecretRefs(path.join(root, '.env'), source, warnings));
|
|
864
|
-
manifest.mcpServers.push(
|
|
865
|
-
...readMcpServers(
|
|
866
|
-
path.join(os.homedir(), '.openclaw', 'agents', 'main', 'agent', 'claude-mcp.json'),
|
|
867
|
-
source,
|
|
868
|
-
warnings
|
|
869
|
-
),
|
|
870
|
-
...readMcpServers(
|
|
871
|
-
path.join(os.homedir(), '.openclaw', 'agents', 'main', 'agent', 'runtime-mcp.json'),
|
|
872
|
-
source,
|
|
873
|
-
warnings
|
|
874
|
-
)
|
|
875
|
-
);
|
|
876
|
-
return manifest;
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
function buildHermesManifest(warnings) {
|
|
880
|
-
const source = 'hermes';
|
|
881
|
-
const root = hermesRoots().find(
|
|
882
|
-
candidate =>
|
|
883
|
-
fileExists(path.join(candidate, 'SOUL.md')) || fileExists(path.join(candidate, 'skills'))
|
|
884
|
-
);
|
|
885
|
-
const manifest = createEmptyManifest(source);
|
|
886
|
-
if (!root) {
|
|
887
|
-
warnings.push('HERMES workspace was not found.');
|
|
888
|
-
return manifest;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
const soul = instructionEntry(path.join(root, 'SOUL.md'), 'SOUL.md');
|
|
892
|
-
if (soul) manifest.instructions.push(soul);
|
|
893
|
-
|
|
894
|
-
for (const skillName of listDirectoryNames(path.join(root, 'skills'))) {
|
|
895
|
-
manifest.skills.push({
|
|
896
|
-
name: skillName,
|
|
897
|
-
kind: 'skill',
|
|
898
|
-
source,
|
|
899
|
-
path: portablePath(path.join(root, 'skills', skillName)),
|
|
900
|
-
hasSkillMd: fileExists(path.join(root, 'skills', skillName, 'SKILL.md'))
|
|
901
|
-
});
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
manifest.secretRefs.push(...parseEnvSecretRefs(path.join(root, '.env'), source, warnings));
|
|
905
|
-
manifest.mcpServers.push(...readMcpServers(path.join(root, 'config.json'), source, warnings));
|
|
906
|
-
return manifest;
|
|
907
|
-
}
|
|
908
|
-
|
|
909
1176
|
function buildCursorManifest(warnings) {
|
|
910
1177
|
const source = 'cursor';
|
|
911
1178
|
const manifest = createEmptyManifest(source);
|
|
@@ -959,8 +1226,6 @@ function buildClaudeDesktopManifest(warnings) {
|
|
|
959
1226
|
}
|
|
960
1227
|
|
|
961
1228
|
function buildSourceManifest(source, warnings) {
|
|
962
|
-
if (source === 'openclaw') return buildOpenclawManifest(warnings);
|
|
963
|
-
if (source === 'hermes') return buildHermesManifest(warnings);
|
|
964
1229
|
if (source === 'cursor') return buildCursorManifest(warnings);
|
|
965
1230
|
if (source === 'claude-code') return buildClaudeCodeManifest(warnings);
|
|
966
1231
|
if (source === 'claude-desktop') return buildClaudeDesktopManifest(warnings);
|
|
@@ -1233,110 +1498,48 @@ function runClientOperations(clients, scope, cwd, dryRun, runner) {
|
|
|
1233
1498
|
});
|
|
1234
1499
|
}
|
|
1235
1500
|
|
|
1236
|
-
function printResultSummary(command, scope, results, accessKey) {
|
|
1237
|
-
const changedCount = results.filter(result => result.changed).length;
|
|
1238
|
-
const configuredClients = results
|
|
1239
|
-
.filter(result => result.configured)
|
|
1240
|
-
.map(result => result.client)
|
|
1241
|
-
.join(', ');
|
|
1242
|
-
const failures = results.filter(result => result.error);
|
|
1243
|
-
const lines = [
|
|
1244
|
-
`NEUS ${command} completed for ${results.length} client${results.length === 1 ? '' : 's'} in ${scope} scope.`,
|
|
1245
|
-
`Configured: ${configuredClients || 'none'}.`
|
|
1246
|
-
];
|
|
1247
|
-
|
|
1248
|
-
if (changedCount > 0) {
|
|
1249
|
-
lines.push(`Updated: ${changedCount} target${changedCount === 1 ? '' : 's'}.`);
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
if ((command === 'init' || command === 'setup') && !accessKey) {
|
|
1253
|
-
lines.push(
|
|
1254
|
-
`Sign in with: neus auth (opens browser) or neus auth --access-key <npk_...> (servers and CI only)`
|
|
1255
|
-
);
|
|
1256
|
-
}
|
|
1257
|
-
if (command === 'init' || command === 'setup') {
|
|
1258
|
-
lines.push('Claude Code skill bundle: https://docs.neus.network/mcp/claude-code-marketplace');
|
|
1259
|
-
lines.push(
|
|
1260
|
-
'Cursor / VS Code: same command when those apps are detected (local MCP config) — https://docs.neus.network/mcp/setup'
|
|
1261
|
-
);
|
|
1262
|
-
}
|
|
1263
|
-
if ((command === 'init' || command === 'auth') && accessKey) {
|
|
1264
|
-
lines.push(
|
|
1265
|
-
'Personal account tools are enabled.'
|
|
1266
|
-
);
|
|
1267
|
-
}
|
|
1268
|
-
if (command === 'status') {
|
|
1269
|
-
const enabled = results.filter(result => result.configured).map(result => result.client);
|
|
1270
|
-
lines.push(`Active: ${enabled.length > 0 ? enabled.join(', ') : 'none'}.`);
|
|
1271
|
-
}
|
|
1272
|
-
if (failures.length > 0) {
|
|
1273
|
-
lines.push(
|
|
1274
|
-
`Issues: ${failures.map(result => `${result.client}: ${result.error}`).join(' | ')}`
|
|
1275
|
-
);
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
process.stdout.write(`${lines.join('\n')}\n`);
|
|
1279
|
-
}
|
|
1280
1501
|
|
|
1281
|
-
function printImportSummary(payload) {
|
|
1282
|
-
|
|
1502
|
+
function printImportSummary(payload, cliOptions = {}) {
|
|
1503
|
+
emitCliBanner(cliOptions);
|
|
1283
1504
|
const manifest = payload.manifest;
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
lines.push('');
|
|
1299
|
-
lines.push(paint('Notes', 'yellow'));
|
|
1300
|
-
lines.push(...payload.warnings.map(warning => `- ${warning}`));
|
|
1301
|
-
}
|
|
1302
|
-
lines.push('');
|
|
1303
|
-
lines.push(
|
|
1304
|
-
'Next: run `neus setup`, then `neus doctor --live`, then call `neus_agent_create` from your MCP client.'
|
|
1305
|
-
);
|
|
1306
|
-
process.stdout.write(`${lines.join('\n')}\n`);
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
function printExportSummary(payload) {
|
|
1310
|
-
printBrandHeader('agent export');
|
|
1311
|
-
const lines = [
|
|
1312
|
-
`${paint('Format', 'cyan')}: ${payload.format}`,
|
|
1313
|
-
`${paint('Source', 'cyan')}: ${payload.manifest.source}`,
|
|
1314
|
-
`${paint('Skills', 'cyan')}: ${payload.manifest.skills?.length || 0}`,
|
|
1315
|
-
`${paint('Proof refs', 'cyan')}: ${payload.manifest.proofHints?.qHashes?.length || 0} qHash value${payload.manifest.proofHints?.qHashes?.length === 1 ? '' : 's'}`
|
|
1316
|
-
];
|
|
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);
|
|
1317
1519
|
if (payload.outputPath) {
|
|
1318
|
-
|
|
1520
|
+
logStep('ok', 'output', payload.outputPath);
|
|
1319
1521
|
}
|
|
1320
|
-
|
|
1522
|
+
writeCliLine('');
|
|
1321
1523
|
}
|
|
1322
1524
|
|
|
1323
1525
|
function runInit(options) {
|
|
1324
1526
|
const scope = resolveScope(options);
|
|
1325
|
-
|
|
1527
|
+
const accessKey = resolveAccessKey(options);
|
|
1528
|
+
ensureSafeAuth('init', scope, accessKey);
|
|
1326
1529
|
const cwd = process.cwd();
|
|
1327
1530
|
|
|
1328
1531
|
const clients = resolveClients(scope, options.clients);
|
|
1329
1532
|
ensureClientSelection(scope, clients);
|
|
1330
1533
|
|
|
1331
1534
|
const results = runClientOperations(clients, scope, cwd, options.dryRun, client =>
|
|
1332
|
-
installClient(client, scope,
|
|
1535
|
+
installClient(client, scope, accessKey, options.dryRun, cwd)
|
|
1333
1536
|
);
|
|
1334
1537
|
const payload = {
|
|
1335
1538
|
command: 'init',
|
|
1336
1539
|
scope,
|
|
1337
1540
|
detectedClients: defaultUserClients(),
|
|
1338
1541
|
clients,
|
|
1339
|
-
accessKeyConfigured: Boolean(
|
|
1542
|
+
accessKeyConfigured: Boolean(accessKey),
|
|
1340
1543
|
results,
|
|
1341
1544
|
hasErrors: results.some(result => result.error)
|
|
1342
1545
|
};
|
|
@@ -1344,7 +1547,10 @@ function runInit(options) {
|
|
|
1344
1547
|
if (options.json) {
|
|
1345
1548
|
printJson(payload);
|
|
1346
1549
|
} else {
|
|
1347
|
-
|
|
1550
|
+
printFlowSummary('init', scope, results, {
|
|
1551
|
+
nextStep: accessKey ? '' : 'neus auth',
|
|
1552
|
+
cliOptions: options
|
|
1553
|
+
});
|
|
1348
1554
|
}
|
|
1349
1555
|
|
|
1350
1556
|
if (payload.hasErrors) {
|
|
@@ -1378,6 +1584,8 @@ async function runAuthBrowser(options) {
|
|
|
1378
1584
|
}
|
|
1379
1585
|
const clients = resolveClients(scope, options.clients);
|
|
1380
1586
|
ensureClientSelection(scope, clients);
|
|
1587
|
+
const browserManagedClients = clients.filter(client => client !== 'codex');
|
|
1588
|
+
const hostManagedClients = clients.filter(client => client === 'codex');
|
|
1381
1589
|
const cwd = process.cwd();
|
|
1382
1590
|
|
|
1383
1591
|
const { createServer } = await import('node:http');
|
|
@@ -1448,9 +1656,14 @@ async function runAuthBrowser(options) {
|
|
|
1448
1656
|
res.end('<html><body><h2>Authenticated</h2><p>You can close this tab and return to your terminal.</p></body></html>');
|
|
1449
1657
|
server.close();
|
|
1450
1658
|
|
|
1451
|
-
const results = runClientOperations(
|
|
1659
|
+
const results = runClientOperations(browserManagedClients, scope, cwd, options.dryRun, client =>
|
|
1452
1660
|
installClient(client, scope, accessToken, options.dryRun, cwd)
|
|
1453
1661
|
);
|
|
1662
|
+
results.push(
|
|
1663
|
+
...runClientOperations(hostManagedClients, scope, cwd, options.dryRun, () =>
|
|
1664
|
+
authCodex(scope, options.dryRun, cwd, options)
|
|
1665
|
+
)
|
|
1666
|
+
);
|
|
1454
1667
|
const payload = {
|
|
1455
1668
|
command: 'auth',
|
|
1456
1669
|
scope,
|
|
@@ -1489,17 +1702,16 @@ async function runAuthBrowser(options) {
|
|
|
1489
1702
|
});
|
|
1490
1703
|
const authUrl = `${NEUS_APP_URL}/oauth/authorize?${authParams.toString()}`;
|
|
1491
1704
|
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
console.log('');
|
|
1705
|
+
if (!options.json) {
|
|
1706
|
+
printAuthBrowserIntro(authUrl, options);
|
|
1707
|
+
logStep('next', 'wait', 'finish sign-in in the browser');
|
|
1708
|
+
}
|
|
1497
1709
|
|
|
1498
1710
|
const { exec } = require('node:child_process');
|
|
1499
1711
|
const openCmd = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
1500
|
-
exec(`${openCmd} "${authUrl}"`,
|
|
1501
|
-
if (err) {
|
|
1502
|
-
|
|
1712
|
+
exec(`${openCmd} "${authUrl}"`, err => {
|
|
1713
|
+
if (err && !options.json) {
|
|
1714
|
+
logStep('warn', 'browser', 'open the URL above manually');
|
|
1503
1715
|
}
|
|
1504
1716
|
});
|
|
1505
1717
|
});
|
|
@@ -1514,27 +1726,39 @@ async function runAuthBrowser(options) {
|
|
|
1514
1726
|
|
|
1515
1727
|
function runAuth(options) {
|
|
1516
1728
|
const scope = resolveScope(options);
|
|
1517
|
-
|
|
1729
|
+
const accessKey = resolveAccessKey(options);
|
|
1730
|
+
ensureSafeAuth('auth', scope, accessKey);
|
|
1518
1731
|
const cwd = process.cwd();
|
|
1732
|
+
const clients = resolveClients(scope, options.clients);
|
|
1733
|
+
ensureClientSelection(scope, clients);
|
|
1519
1734
|
|
|
1520
|
-
|
|
1521
|
-
|
|
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
|
+
}
|
|
1522
1750
|
return runAuthBrowser(options);
|
|
1523
1751
|
}
|
|
1524
1752
|
|
|
1525
|
-
// Manual key flow: --access-key provided
|
|
1526
|
-
const clients = resolveClients(scope, options.clients);
|
|
1527
|
-
ensureClientSelection(scope, clients);
|
|
1528
|
-
|
|
1529
1753
|
const results = runClientOperations(clients, scope, cwd, options.dryRun, client =>
|
|
1530
|
-
installClient(client, scope,
|
|
1754
|
+
installClient(client, scope, accessKey, options.dryRun, cwd)
|
|
1531
1755
|
);
|
|
1532
1756
|
const payload = {
|
|
1533
1757
|
command: 'auth',
|
|
1534
1758
|
scope,
|
|
1535
1759
|
clients,
|
|
1536
1760
|
accessKeyConfigured: true,
|
|
1537
|
-
authMethod:
|
|
1761
|
+
authMethod: resolveAuthMethod(options, accessKey),
|
|
1538
1762
|
results,
|
|
1539
1763
|
hasErrors: results.some(result => result.error)
|
|
1540
1764
|
};
|
|
@@ -1562,14 +1786,15 @@ function runStatus(options) {
|
|
|
1562
1786
|
printJson(payload);
|
|
1563
1787
|
return;
|
|
1564
1788
|
}
|
|
1565
|
-
|
|
1789
|
+
printFlowSummary('status', scope, inspected, { cliOptions: options });
|
|
1566
1790
|
}
|
|
1567
1791
|
|
|
1568
|
-
function runSetup(options) {
|
|
1792
|
+
async function runSetup(options) {
|
|
1569
1793
|
const scope = resolveScope(options);
|
|
1570
|
-
|
|
1794
|
+
const accessKey = resolveAccessKey(options);
|
|
1795
|
+
ensureSafeAuth('setup', scope, accessKey);
|
|
1571
1796
|
const cwd = process.cwd();
|
|
1572
|
-
if (options.project &&
|
|
1797
|
+
if (options.project && accessKey) {
|
|
1573
1798
|
throw new Error(
|
|
1574
1799
|
'Access keys are only supported in user scope. Remove --project or omit --access-key.'
|
|
1575
1800
|
);
|
|
@@ -1577,9 +1802,8 @@ function runSetup(options) {
|
|
|
1577
1802
|
|
|
1578
1803
|
const clients = resolveClients(scope, options.clients);
|
|
1579
1804
|
ensureClientSelection(scope, clients);
|
|
1580
|
-
|
|
1581
1805
|
const initResults = runClientOperations(clients, scope, cwd, options.dryRun, client =>
|
|
1582
|
-
installClient(client, scope,
|
|
1806
|
+
installClient(client, scope, accessKey, options.dryRun, cwd)
|
|
1583
1807
|
);
|
|
1584
1808
|
|
|
1585
1809
|
const payload = {
|
|
@@ -1587,23 +1811,46 @@ function runSetup(options) {
|
|
|
1587
1811
|
scope,
|
|
1588
1812
|
detectedClients: defaultUserClients(),
|
|
1589
1813
|
clients,
|
|
1590
|
-
accessKeyConfigured: Boolean(
|
|
1814
|
+
accessKeyConfigured: Boolean(accessKey),
|
|
1591
1815
|
results: initResults,
|
|
1592
1816
|
hasErrors: initResults.some(result => result.error)
|
|
1593
1817
|
};
|
|
1594
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
|
+
|
|
1595
1826
|
if (options.json) {
|
|
1596
1827
|
printJson(payload);
|
|
1597
|
-
|
|
1598
|
-
printResultSummary('setup', scope, initResults, options.accessKey);
|
|
1828
|
+
return payload;
|
|
1599
1829
|
}
|
|
1600
1830
|
|
|
1601
|
-
|
|
1602
|
-
|
|
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;
|
|
1603
1848
|
}
|
|
1849
|
+
|
|
1850
|
+
return payload;
|
|
1604
1851
|
}
|
|
1605
1852
|
|
|
1606
|
-
function runImport(options) {
|
|
1853
|
+
function runImport(options, { emitOutput = true } = {}) {
|
|
1607
1854
|
if (!SUPPORTED_IMPORT_SOURCES.includes(options.source)) {
|
|
1608
1855
|
throw new Error(`Unsupported import source: ${options.source}`);
|
|
1609
1856
|
}
|
|
@@ -1628,15 +1875,18 @@ function runImport(options) {
|
|
|
1628
1875
|
manifest.mcpServers.length === 0
|
|
1629
1876
|
};
|
|
1630
1877
|
|
|
1631
|
-
if (
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1878
|
+
if (emitOutput) {
|
|
1879
|
+
if (options.json) {
|
|
1880
|
+
printJson(payload);
|
|
1881
|
+
} else {
|
|
1882
|
+
printImportSummary(payload, options);
|
|
1883
|
+
}
|
|
1635
1884
|
}
|
|
1636
1885
|
|
|
1637
|
-
if (payload.hasErrors) {
|
|
1886
|
+
if (emitOutput && payload.hasErrors) {
|
|
1638
1887
|
process.exitCode = 1;
|
|
1639
1888
|
}
|
|
1889
|
+
return payload;
|
|
1640
1890
|
}
|
|
1641
1891
|
|
|
1642
1892
|
function runExport(options) {
|
|
@@ -1671,7 +1921,7 @@ function runExport(options) {
|
|
|
1671
1921
|
printJson(payload);
|
|
1672
1922
|
return;
|
|
1673
1923
|
}
|
|
1674
|
-
printExportSummary(payload);
|
|
1924
|
+
printExportSummary(payload, options);
|
|
1675
1925
|
}
|
|
1676
1926
|
|
|
1677
1927
|
async function runDoctor(options) {
|
|
@@ -1684,12 +1934,13 @@ async function runDoctor(options) {
|
|
|
1684
1934
|
inspectClient(client, scope, cwd)
|
|
1685
1935
|
);
|
|
1686
1936
|
const configuredClients = inspected.filter(r => r.configured);
|
|
1937
|
+
const liveAccessKey = resolveLiveAccessKey(options, scope, cwd);
|
|
1687
1938
|
const payload = {
|
|
1688
1939
|
command: 'doctor',
|
|
1689
1940
|
scope,
|
|
1690
1941
|
clients: inspected,
|
|
1691
1942
|
configuredCount: configuredClients.length,
|
|
1692
|
-
accessKeyPresent: Boolean(
|
|
1943
|
+
accessKeyPresent: Boolean(liveAccessKey),
|
|
1693
1944
|
profileConnectable: false,
|
|
1694
1945
|
agentVerified: false,
|
|
1695
1946
|
live: options.live,
|
|
@@ -1699,9 +1950,12 @@ async function runDoctor(options) {
|
|
|
1699
1950
|
};
|
|
1700
1951
|
|
|
1701
1952
|
if (options.live) {
|
|
1702
|
-
payload.mcp = await runLiveMcpDiagnostics(
|
|
1703
|
-
|
|
1704
|
-
|
|
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
|
+
}
|
|
1705
1959
|
}
|
|
1706
1960
|
|
|
1707
1961
|
if (options.json) {
|
|
@@ -1709,45 +1963,60 @@ async function runDoctor(options) {
|
|
|
1709
1963
|
return;
|
|
1710
1964
|
}
|
|
1711
1965
|
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
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;
|
|
1723
1987
|
}
|
|
1724
1988
|
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
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`.'
|
|
1729
1998
|
);
|
|
1730
|
-
lines.push(`Tools: ${payload.mcp.toolsCount || 0} discovered.`);
|
|
1731
1999
|
} else {
|
|
1732
|
-
|
|
1733
|
-
|
|
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'
|
|
1734
2006
|
);
|
|
1735
2007
|
}
|
|
2008
|
+
} else if (liveAccessKey) {
|
|
2009
|
+
writeGuidanceLine('Saved credential found. Run `neus doctor --live` to confirm Profile context.');
|
|
1736
2010
|
} else {
|
|
1737
|
-
|
|
1738
|
-
|
|
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.'
|
|
1739
2015
|
);
|
|
1740
2016
|
}
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
);
|
|
1745
|
-
lines.push('');
|
|
1746
|
-
lines.push(
|
|
1747
|
-
'Next: Open your editor/IDE, connect to the NEUS MCP endpoint, and run `neus_context`.'
|
|
1748
|
-
);
|
|
1749
|
-
|
|
1750
|
-
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('');
|
|
1751
2020
|
}
|
|
1752
2021
|
|
|
1753
2022
|
async function runDisconnect(options) {
|
|
@@ -1756,12 +2025,15 @@ async function runDisconnect(options) {
|
|
|
1756
2025
|
throw new Error('Disconnect only supports user scope. Remove --project flag.');
|
|
1757
2026
|
}
|
|
1758
2027
|
|
|
1759
|
-
|
|
1760
|
-
|
|
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
|
+
);
|
|
1761
2034
|
}
|
|
1762
2035
|
|
|
1763
2036
|
try {
|
|
1764
|
-
const token = String(options.accessKey || '').trim();
|
|
1765
2037
|
const isProfileKey = token.startsWith('npk_');
|
|
1766
2038
|
const resp = isProfileKey
|
|
1767
2039
|
? await fetch(NEUS_PROFILE_KEY_ENDPOINT, {
|
|
@@ -1794,7 +2066,6 @@ async function runDisconnect(options) {
|
|
|
1794
2066
|
throw error;
|
|
1795
2067
|
}
|
|
1796
2068
|
|
|
1797
|
-
const cwd = process.cwd();
|
|
1798
2069
|
const clients = resolveClients(scope, options.clients);
|
|
1799
2070
|
ensureClientSelection(scope, clients);
|
|
1800
2071
|
|
|
@@ -1814,9 +2085,11 @@ async function runDisconnect(options) {
|
|
|
1814
2085
|
if (options.json) {
|
|
1815
2086
|
printJson(payload);
|
|
1816
2087
|
} else {
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
2088
|
+
emitCliBanner(options);
|
|
2089
|
+
writeCliLine(paint('disconnect', 'green'));
|
|
2090
|
+
logStep('ok', 'signed-out', 'MCP configs updated');
|
|
2091
|
+
logStep('next', 'next', 'neus auth');
|
|
2092
|
+
writeCliLine('');
|
|
1820
2093
|
}
|
|
1821
2094
|
}
|
|
1822
2095
|
|
|
@@ -1837,13 +2110,16 @@ async function main() {
|
|
|
1837
2110
|
if (result) {
|
|
1838
2111
|
if (options.json) {
|
|
1839
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
|
+
});
|
|
1840
2118
|
} else {
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
console.log(' Authenticated via browser. Your MCP clients are now configured.');
|
|
1846
|
-
}
|
|
2119
|
+
printFlowSummary('auth', result.scope, result.results, {
|
|
2120
|
+
nextStep: 'neus_context in your MCP client',
|
|
2121
|
+
cliOptions: options
|
|
2122
|
+
});
|
|
1847
2123
|
}
|
|
1848
2124
|
if (result.hasErrors) {
|
|
1849
2125
|
process.exitCode = 1;
|
|
@@ -1856,7 +2132,10 @@ async function main() {
|
|
|
1856
2132
|
return;
|
|
1857
2133
|
}
|
|
1858
2134
|
if (command === 'setup') {
|
|
1859
|
-
runSetup(options);
|
|
2135
|
+
const setupResult = await runSetup(options);
|
|
2136
|
+
if (setupResult?.hasErrors) {
|
|
2137
|
+
process.exitCode = 1;
|
|
2138
|
+
}
|
|
1860
2139
|
return;
|
|
1861
2140
|
}
|
|
1862
2141
|
if (command === 'doctor') {
|