nexarch 0.3.1 → 0.4.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 +6 -3
- package/dist/commands/init-agent.js +71 -31
- package/dist/commands/login.js +15 -0
- package/dist/commands/mcp-config.js +36 -55
- package/dist/commands/mcp-proxy.js +1 -1
- package/dist/commands/setup.js +39 -27
- package/dist/commands/status.js +11 -2
- package/dist/index.js +4 -21
- package/dist/lib/agent-registry.js +49 -0
- package/dist/lib/clients.js +57 -52
- package/dist/lib/mcp.js +8 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# nexarch
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Your architecture workspace for AI delivery.
|
|
4
4
|
|
|
5
5
|
## Quick start
|
|
6
6
|
|
|
@@ -9,15 +9,18 @@ npx nexarch login
|
|
|
9
9
|
npx nexarch setup
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
+
> `setup`, `mcp-config`, and `init-agent` are registry-driven and fail closed.
|
|
13
|
+
> If the published integration registry is unavailable/invalid, these commands stop until service is restored.
|
|
14
|
+
|
|
12
15
|
## Commands
|
|
13
16
|
|
|
14
17
|
| Command | Description |
|
|
15
18
|
|---|---|
|
|
16
19
|
| `nexarch login [--company <id>]` | Authenticate via browser and store company-scoped credentials; prompts for company when multiple memberships exist |
|
|
17
20
|
| `nexarch logout` | Remove local credentials |
|
|
18
|
-
| `nexarch status` | Check connection and
|
|
21
|
+
| `nexarch status` | Check connection, registry health/version, and architecture summary for the company selected at login |
|
|
19
22
|
| `nexarch setup` | Auto-configure detected MCP clients |
|
|
20
|
-
| `nexarch mcp-config [client]` | Print config block for manual setup (
|
|
23
|
+
| `nexarch mcp-config [client]` | Print config block for manual setup (supported client codes are registry-managed) |
|
|
21
24
|
| `nexarch mcp-proxy` | stdio→HTTP bridge used internally by MCP clients |
|
|
22
25
|
| `nexarch init-agent [--json] [--strict] [--agent-id <id>] [--redact-hostname]` | Run onboarding handshake and mandatory `agent` entity registration in graph |
|
|
23
26
|
| `nexarch init-agent --bind-to-external-key <key> [--bind-relationship-type <code>]` | Optionally bind the agent node to an existing graph external key |
|
|
@@ -3,8 +3,9 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import process from "process";
|
|
5
5
|
import { requireCredentials } from "../lib/credentials.js";
|
|
6
|
+
import { fetchAgentRegistryOrThrow } from "../lib/agent-registry.js";
|
|
6
7
|
import { callMcpTool, mcpInitialize, mcpListTools } from "../lib/mcp.js";
|
|
7
|
-
const CLI_VERSION = "0.2
|
|
8
|
+
const CLI_VERSION = "0.4.2";
|
|
8
9
|
const AGENT_ENTITY_TYPE = "agent";
|
|
9
10
|
const TECH_COMPONENT_ENTITY_TYPE = "technology_component";
|
|
10
11
|
function parseFlag(args, flag) {
|
|
@@ -23,6 +24,19 @@ function parseToolText(result) {
|
|
|
23
24
|
const text = result.content?.[0]?.text ?? "{}";
|
|
24
25
|
return JSON.parse(text);
|
|
25
26
|
}
|
|
27
|
+
function renderChecksTable(checks) {
|
|
28
|
+
const headers = ["Status", "Check", "Details"];
|
|
29
|
+
const rows = checks.map((check) => [
|
|
30
|
+
check.ok ? "✓" : "x",
|
|
31
|
+
check.name,
|
|
32
|
+
check.detail?.trim() || "-",
|
|
33
|
+
]);
|
|
34
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
35
|
+
const hr = `+${widths.map((w) => "-".repeat(w + 2)).join("+")}+`;
|
|
36
|
+
const formatRow = (cells) => `| ${cells.map((cell, i) => cell.padEnd(widths[i])).join(" | ")} |`;
|
|
37
|
+
const lines = [hr, formatRow(headers), hr, ...rows.map(formatRow), hr];
|
|
38
|
+
return lines.join("\n");
|
|
39
|
+
}
|
|
26
40
|
function getDefaultAgentId() {
|
|
27
41
|
const osUser = process.env.USERNAME || process.env.USER || userInfo().username || "unknown";
|
|
28
42
|
const host = hostname() || "unknown-host";
|
|
@@ -145,36 +159,58 @@ After running, confirm \`"ok": true\` in the JSON output. No further action is n
|
|
|
145
159
|
writeFileSync(filePath, content, "utf8");
|
|
146
160
|
return filePath;
|
|
147
161
|
}
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
function injectAgentConfigs() {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
"AGENTS.md", "agents.md", // OpenAI Codex CLI / generic
|
|
160
|
-
".github/copilot-instructions.md", // GitHub Copilot
|
|
161
|
-
".cursorrules", // Cursor (legacy)
|
|
162
|
-
".continue/rules.md", // Continue.dev
|
|
163
|
-
".windsurfrules", // Windsurf
|
|
164
|
-
];
|
|
162
|
+
function replaceInjectedSection(existing, sectionHeading, sectionBody) {
|
|
163
|
+
const headingRegex = new RegExp(`^${sectionHeading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?(?=^##\\s|\\Z)`, "m");
|
|
164
|
+
if (headingRegex.test(existing)) {
|
|
165
|
+
const replaced = existing.replace(headingRegex, sectionBody.trim());
|
|
166
|
+
return replaced.endsWith("\n") ? replaced : `${replaced}\n`;
|
|
167
|
+
}
|
|
168
|
+
return existing;
|
|
169
|
+
}
|
|
170
|
+
function injectAgentConfigs(registry) {
|
|
171
|
+
const templateByCode = new Map(registry.instructionTemplates.map((t) => [t.code, t]));
|
|
172
|
+
const targets = [...registry.instructionTargets].sort((a, b) => a.sortOrder - b.sortOrder || a.filePathPattern.localeCompare(b.filePathPattern));
|
|
165
173
|
const results = [];
|
|
166
|
-
for (const
|
|
167
|
-
|
|
174
|
+
for (const target of targets) {
|
|
175
|
+
if (target.matchMode !== "exact")
|
|
176
|
+
continue;
|
|
177
|
+
const template = templateByCode.get(target.templateCode);
|
|
178
|
+
if (!template)
|
|
179
|
+
continue;
|
|
180
|
+
const filePath = join(process.cwd(), target.filePathPattern);
|
|
168
181
|
if (!existsSync(filePath))
|
|
169
182
|
continue;
|
|
170
183
|
const existing = readFileSync(filePath, "utf8");
|
|
171
|
-
|
|
172
|
-
|
|
184
|
+
const sectionBody = template.body.trim();
|
|
185
|
+
const sectionHeading = target.sectionHeading ?? "## Nexarch Agent Registration";
|
|
186
|
+
const sectionMarker = target.sectionMarker ?? sectionHeading;
|
|
187
|
+
if (target.insertionMode === "replace_section") {
|
|
188
|
+
let replaced = replaceInjectedSection(existing, sectionHeading, sectionBody);
|
|
189
|
+
if (replaced === existing && existing.includes(sectionMarker)) {
|
|
190
|
+
const markerIndex = existing.indexOf(sectionMarker);
|
|
191
|
+
const before = existing.slice(0, markerIndex).replace(/\s*$/, "\n\n");
|
|
192
|
+
replaced = `${before}${sectionBody}\n`;
|
|
193
|
+
}
|
|
194
|
+
if (replaced !== existing) {
|
|
195
|
+
writeFileSync(filePath, replaced, "utf8");
|
|
196
|
+
results.push({ path: filePath, status: "updated" });
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
200
|
+
writeFileSync(filePath, existing + separator + sectionBody + "\n", "utf8");
|
|
201
|
+
results.push({ path: filePath, status: "injected" });
|
|
202
|
+
}
|
|
173
203
|
continue;
|
|
174
204
|
}
|
|
175
205
|
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
176
|
-
|
|
177
|
-
|
|
206
|
+
const next = existing + separator + sectionBody + "\n";
|
|
207
|
+
if (next === existing) {
|
|
208
|
+
results.push({ path: filePath, status: "already_present" });
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
writeFileSync(filePath, next, "utf8");
|
|
212
|
+
results.push({ path: filePath, status: "injected" });
|
|
213
|
+
}
|
|
178
214
|
}
|
|
179
215
|
return results;
|
|
180
216
|
}
|
|
@@ -198,6 +234,7 @@ export async function initAgent(args) {
|
|
|
198
234
|
const checks = [];
|
|
199
235
|
const creds = requireCredentials();
|
|
200
236
|
const selectedCompanyId = creds.companyId;
|
|
237
|
+
const registry = await fetchAgentRegistryOrThrow();
|
|
201
238
|
const init = await mcpInitialize({ companyId: selectedCompanyId });
|
|
202
239
|
checks.push({
|
|
203
240
|
name: "mcp.initialize",
|
|
@@ -207,6 +244,7 @@ export async function initAgent(args) {
|
|
|
207
244
|
const tools = await mcpListTools({ companyId: selectedCompanyId });
|
|
208
245
|
const toolNames = new Set(tools.map((t) => t.name));
|
|
209
246
|
const required = [
|
|
247
|
+
"nexarch_get_agent_registry",
|
|
210
248
|
"nexarch_get_applied_policies",
|
|
211
249
|
"nexarch_get_ingest_contract",
|
|
212
250
|
"nexarch_upsert_entities",
|
|
@@ -639,7 +677,7 @@ export async function initAgent(args) {
|
|
|
639
677
|
// non-fatal
|
|
640
678
|
}
|
|
641
679
|
try {
|
|
642
|
-
agentConfigResults = injectAgentConfigs();
|
|
680
|
+
agentConfigResults = injectAgentConfigs(registry);
|
|
643
681
|
}
|
|
644
682
|
catch {
|
|
645
683
|
// non-fatal
|
|
@@ -703,6 +741,7 @@ export async function initAgent(args) {
|
|
|
703
741
|
submitCommandTemplate: `nexarch agent identify --agent-id \"${agentId}\" --provider \"<provider>\" --model \"<model>\" --client \"<client>\" --framework \"<openclaw|n8n|m365-agent-framework|other>\" --session-id \"<session-id>\" --tool-version \"<tool-version>\" --capabilities \"<capability1,capability2>\" --notes \"<optional notes>\" --json`,
|
|
704
742
|
},
|
|
705
743
|
companyId: creds.companyId,
|
|
744
|
+
registry: { version: registry.release.version, registryVersion: registry.registryVersion, publishedAt: registry.release.publishedAt },
|
|
706
745
|
bootstrapFile: bootstrapFilePath,
|
|
707
746
|
agentConfigs: agentConfigResults,
|
|
708
747
|
};
|
|
@@ -711,12 +750,9 @@ export async function initAgent(args) {
|
|
|
711
750
|
process.exitCode = 1;
|
|
712
751
|
return;
|
|
713
752
|
}
|
|
753
|
+
console.log(`Using integration registry release v${registry.release.version}.`);
|
|
714
754
|
console.log("Running agent onboarding handshake…\n");
|
|
715
|
-
|
|
716
|
-
console.log(`${check.ok ? "✓" : "✗"} ${check.name}`);
|
|
717
|
-
if (check.detail)
|
|
718
|
-
console.log(` ${check.detail}`);
|
|
719
|
-
}
|
|
755
|
+
console.log(renderChecksTable(checks));
|
|
720
756
|
if (registration.graphEntityId) {
|
|
721
757
|
console.log(`\nRegistered graph entity: ${registration.graphEntityId}`);
|
|
722
758
|
}
|
|
@@ -733,8 +769,12 @@ export async function initAgent(args) {
|
|
|
733
769
|
if (r.status === "injected") {
|
|
734
770
|
console.log(`📝 Nexarch registration instructions added to: ${r.path}`);
|
|
735
771
|
}
|
|
772
|
+
if (r.status === "updated") {
|
|
773
|
+
console.log(`📝 Nexarch registration instructions updated in: ${r.path}`);
|
|
774
|
+
}
|
|
736
775
|
}
|
|
737
|
-
console.log("\n
|
|
776
|
+
console.log("\n➡ Next step: paste this into your coding agent:");
|
|
777
|
+
console.log("\nrun npx nexarch@latest init-agent\n");
|
|
738
778
|
}
|
|
739
779
|
else {
|
|
740
780
|
console.log("⚠ Handshake/registration completed with issues.");
|
package/dist/commands/login.js
CHANGED
|
@@ -5,6 +5,20 @@ import { execFile } from "child_process";
|
|
|
5
5
|
import { saveCredentials } from "../lib/credentials.js";
|
|
6
6
|
const NEXARCH_URL = "https://nexarch.ai";
|
|
7
7
|
const LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
8
|
+
function printLoginBanner() {
|
|
9
|
+
const logo = String.raw `
|
|
10
|
+
###### ######
|
|
11
|
+
####### ######
|
|
12
|
+
######### ###### ##
|
|
13
|
+
################## #####
|
|
14
|
+
##### ########## ######
|
|
15
|
+
###### ####### ######
|
|
16
|
+
##### #### #############
|
|
17
|
+
###### ## ###############
|
|
18
|
+
`;
|
|
19
|
+
console.log(logo);
|
|
20
|
+
console.log("NEXARCH CLI login\n");
|
|
21
|
+
}
|
|
8
22
|
function generateState() {
|
|
9
23
|
return randomBytes(16).toString("hex");
|
|
10
24
|
}
|
|
@@ -115,6 +129,7 @@ function getArgValue(args, flag) {
|
|
|
115
129
|
return v;
|
|
116
130
|
}
|
|
117
131
|
export async function login(args) {
|
|
132
|
+
printLoginBanner();
|
|
118
133
|
const state = generateState();
|
|
119
134
|
const port = await findFreePort();
|
|
120
135
|
const requestedCompany = getArgValue(args, "--company");
|
|
@@ -1,53 +1,23 @@
|
|
|
1
1
|
import { loadCredentials } from "../lib/credentials.js";
|
|
2
|
-
import {
|
|
2
|
+
import { fetchAgentRegistryOrThrow } from "../lib/agent-registry.js";
|
|
3
|
+
import { findClientProfile } from "../lib/clients.js";
|
|
4
|
+
function renderConfigForProfile(profile) {
|
|
5
|
+
const serverBlock = { command: profile.serverCommand, args: profile.serverArgs };
|
|
6
|
+
if (profile.mergeStrategy === "continue_style") {
|
|
7
|
+
return { mcpServers: [{ name: "nexarch", ...serverBlock }] };
|
|
8
|
+
}
|
|
9
|
+
return { mcpServers: { nexarch: serverBlock } };
|
|
10
|
+
}
|
|
3
11
|
export async function mcpConfig(args) {
|
|
4
|
-
const clientFlag = args.find((a) => !a.startsWith("-")) ?? "claude-desktop";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Locations:
|
|
14
|
-
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
|
|
15
|
-
Windows: %APPDATA%\\Claude\\claude_desktop_config.json
|
|
16
|
-
`);
|
|
17
|
-
break;
|
|
18
|
-
}
|
|
19
|
-
case "cursor":
|
|
20
|
-
case "windsurf": {
|
|
21
|
-
const config = { mcpServers: { nexarch: serverBlock } };
|
|
22
|
-
console.log(`Add the following to your ${clientFlag === "cursor" ? ".cursor/mcp.json" : ".windsurf/mcp.json"}:\n`);
|
|
23
|
-
console.log(JSON.stringify(config, null, 2));
|
|
24
|
-
break;
|
|
25
|
-
}
|
|
26
|
-
case "continue":
|
|
27
|
-
case "continue-dev": {
|
|
28
|
-
// Continue.dev uses an array-style mcpServers with a "name" field per entry
|
|
29
|
-
const config = { mcpServers: [{ name: "nexarch", ...serverBlock }] };
|
|
30
|
-
console.log("Add (or merge) the following into your ~/.continue/config.json:\n");
|
|
31
|
-
console.log(JSON.stringify(config, null, 2));
|
|
32
|
-
console.log(`
|
|
33
|
-
Locations:
|
|
34
|
-
macOS/Linux: ~/.continue/config.json
|
|
35
|
-
Windows: %USERPROFILE%\\.continue\\config.json
|
|
36
|
-
`);
|
|
37
|
-
break;
|
|
38
|
-
}
|
|
39
|
-
case "http":
|
|
40
|
-
case "direct": {
|
|
41
|
-
// Direct HTTP connection — for MCP clients that support Streamable HTTP transport.
|
|
42
|
-
// The gateway accepts JSON-RPC 2.0 POST requests at the /mcp endpoint.
|
|
43
|
-
// Auth is via Bearer token (your nexarch token from ~/.nexarch/credentials.json).
|
|
44
|
-
const creds = loadCredentials();
|
|
45
|
-
const tokenHint = creds ? creds.token : "<your-nexarch-token>";
|
|
46
|
-
console.log("Direct HTTP MCP connection (Streamable HTTP / SSE transport):\n");
|
|
47
|
-
console.log(` Endpoint : https://mcp.nexarch.ai/mcp`);
|
|
48
|
-
console.log(` Auth : Authorization: Bearer ${tokenHint}`);
|
|
49
|
-
console.log(` Protocol : MCP JSON-RPC 2.0 over HTTP POST`);
|
|
50
|
-
console.log(`
|
|
12
|
+
const clientFlag = (args.find((a) => !a.startsWith("-")) ?? "claude-desktop").toLowerCase();
|
|
13
|
+
if (clientFlag === "http" || clientFlag === "direct") {
|
|
14
|
+
const creds = loadCredentials();
|
|
15
|
+
const tokenHint = creds ? creds.token : "<your-nexarch-token>";
|
|
16
|
+
console.log("Direct HTTP MCP connection (Streamable HTTP / SSE transport):\n");
|
|
17
|
+
console.log(` Endpoint : https://mcp.nexarch.ai/mcp`);
|
|
18
|
+
console.log(` Auth : Authorization: Bearer ${tokenHint}`);
|
|
19
|
+
console.log(` Protocol : MCP JSON-RPC 2.0 over HTTP POST`);
|
|
20
|
+
console.log(`
|
|
51
21
|
Use this when your MCP client supports connecting to a remote HTTP MCP server directly
|
|
52
22
|
(no stdio proxy required). Examples: custom agent frameworks, LangChain MCP integration,
|
|
53
23
|
or any client that speaks the MCP Streamable HTTP or SSE transport.
|
|
@@ -62,13 +32,24 @@ Example curl to verify connectivity:
|
|
|
62
32
|
If your client requires an MCP server URL instead of command/args, use:
|
|
63
33
|
https://mcp.nexarch.ai/mcp
|
|
64
34
|
`);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const registry = await fetchAgentRegistryOrThrow();
|
|
38
|
+
const profile = findClientProfile(registry, clientFlag);
|
|
39
|
+
if (!profile) {
|
|
40
|
+
const known = registry.mcpClientProfiles.map((c) => c.code).join(", ");
|
|
41
|
+
throw new Error(`Unknown MCP client '${clientFlag}'. Supported clients from registry: ${known}`);
|
|
42
|
+
}
|
|
43
|
+
const config = renderConfigForProfile(profile);
|
|
44
|
+
console.log(`\nUsing integration registry release v${registry.release.version}.`);
|
|
45
|
+
console.log(`\nMCP server config for ${profile.code} (${profile.name}):\n`);
|
|
46
|
+
console.log(JSON.stringify(config, null, 2));
|
|
47
|
+
const platformKey = process.platform === "darwin" ? "darwin" : process.platform === "win32" ? "win32" : "linux";
|
|
48
|
+
const locations = profile.osPaths?.[platformKey] ?? [];
|
|
49
|
+
if (locations.length > 0) {
|
|
50
|
+
console.log("\nKnown config locations for this platform:");
|
|
51
|
+
for (const p of locations)
|
|
52
|
+
console.log(` ${p}`);
|
|
72
53
|
}
|
|
73
54
|
console.log("\nOr run `nexarch setup` to have the CLI write the config automatically.");
|
|
74
55
|
}
|
|
@@ -2,7 +2,7 @@ import * as readline from "readline";
|
|
|
2
2
|
import { requireCredentials } from "../lib/credentials.js";
|
|
3
3
|
import { forwardToGateway } from "../lib/mcp.js";
|
|
4
4
|
/**
|
|
5
|
-
* stdio MCP proxy — bridges MCP clients
|
|
5
|
+
* stdio MCP proxy — bridges registry-managed MCP clients that
|
|
6
6
|
* use stdio transport to the Nexarch HTTP MCP gateway.
|
|
7
7
|
*
|
|
8
8
|
* The MCP protocol over stdio is newline-delimited JSON-RPC 2.0.
|
package/dist/commands/setup.js
CHANGED
|
@@ -1,37 +1,49 @@
|
|
|
1
1
|
import { requireCredentials } from "../lib/credentials.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { detectClientsFromRegistry, writeClientConfig, nexarchServerBlockFromRegistry } from "../lib/clients.js";
|
|
3
|
+
import { fetchAgentRegistryOrThrow } from "../lib/agent-registry.js";
|
|
4
|
+
import { initAgent } from "./init-agent.js";
|
|
5
|
+
import { login } from "./login.js";
|
|
6
|
+
export async function setup(args) {
|
|
7
|
+
try {
|
|
8
|
+
requireCredentials();
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
console.log("No active Nexarch session found. Starting login flow…\n");
|
|
12
|
+
await login(args);
|
|
13
|
+
}
|
|
14
|
+
requireCredentials(); // ensure login succeeded before continuing
|
|
15
|
+
const registry = await fetchAgentRegistryOrThrow();
|
|
16
|
+
console.log(`Using integration registry release v${registry.release.version}.`);
|
|
5
17
|
console.log("Detecting installed MCP clients…\n");
|
|
6
|
-
const clients =
|
|
18
|
+
const clients = detectClientsFromRegistry(registry);
|
|
7
19
|
if (clients.length === 0) {
|
|
8
|
-
console.log("No supported MCP clients detected.");
|
|
9
|
-
|
|
20
|
+
console.log("No supported MCP clients detected for this platform.");
|
|
21
|
+
const supported = registry.mcpClientProfiles.map((c) => c.name).join(", ");
|
|
22
|
+
console.log(`\nSupported clients (registry): ${supported}`);
|
|
10
23
|
console.log("\nTo configure manually, run:");
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
24
|
+
for (const cp of registry.mcpClientProfiles) {
|
|
25
|
+
console.log(` nexarch mcp-config --client ${cp.code}`);
|
|
26
|
+
}
|
|
14
27
|
console.log("\nFor clients that support direct HTTP MCP connections:");
|
|
15
28
|
console.log(" nexarch mcp-config --client http");
|
|
16
|
-
return;
|
|
17
29
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
else {
|
|
31
|
+
const serverBlock = nexarchServerBlockFromRegistry(registry);
|
|
32
|
+
for (const client of clients) {
|
|
33
|
+
process.stdout.write(` ${client.name.padEnd(20)} ${client.configPath}\n`);
|
|
34
|
+
process.stdout.write(` ${"".padEnd(20)} `);
|
|
35
|
+
try {
|
|
36
|
+
writeClientConfig(client, serverBlock);
|
|
37
|
+
console.log("✓ configured");
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
41
|
+
console.log(`✗ failed — ${message}`);
|
|
42
|
+
}
|
|
29
43
|
}
|
|
44
|
+
console.log("\nDone. Restart your MCP client for the changes to take effect.");
|
|
30
45
|
}
|
|
31
|
-
console.log("\
|
|
32
|
-
|
|
33
|
-
console.log("
|
|
34
|
-
console.log(" nexarch_get_snapshot_facts");
|
|
35
|
-
console.log(" nexarch_get_governance_summary");
|
|
36
|
-
console.log(" nexarch_get_applied_policies");
|
|
46
|
+
console.log("\nRegistering this runtime as a Nexarch agent…\n");
|
|
47
|
+
await initAgent(args.includes("--strict") ? args : [...args, "--strict"]);
|
|
48
|
+
console.log("\nSetup complete: MCP client config + agent registration are now in place.");
|
|
37
49
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { requireCredentials } from "../lib/credentials.js";
|
|
2
|
+
import { fetchAgentRegistryOrThrow } from "../lib/agent-registry.js";
|
|
2
3
|
import { callMcpTool } from "../lib/mcp.js";
|
|
3
4
|
export async function status(_args) {
|
|
4
5
|
const creds = requireCredentials();
|
|
@@ -7,9 +8,14 @@ export async function status(_args) {
|
|
|
7
8
|
const daysLeft = Math.ceil((expiresAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
|
|
8
9
|
process.stdout.write("Connecting to mcp.nexarch.ai… ");
|
|
9
10
|
let governance;
|
|
11
|
+
let registryInfo = null;
|
|
10
12
|
try {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
+
const [governanceResult, registry] = await Promise.all([
|
|
14
|
+
callMcpTool("nexarch_get_governance_summary", { companyId: selectedCompanyId || undefined }, { companyId: selectedCompanyId || undefined }),
|
|
15
|
+
fetchAgentRegistryOrThrow(),
|
|
16
|
+
]);
|
|
17
|
+
governance = JSON.parse(governanceResult.content[0]?.text ?? "{}");
|
|
18
|
+
registryInfo = { version: registry.release.version, publishedAt: registry.release.publishedAt };
|
|
13
19
|
}
|
|
14
20
|
catch (err) {
|
|
15
21
|
console.error("failed\n");
|
|
@@ -22,6 +28,9 @@ export async function status(_args) {
|
|
|
22
28
|
if (creds.company) {
|
|
23
29
|
console.log(` Workspace: ${creds.company}`);
|
|
24
30
|
}
|
|
31
|
+
if (registryInfo) {
|
|
32
|
+
console.log(`\n Integration registry: v${registryInfo.version} (published ${new Date(registryInfo.publishedAt).toLocaleString()})`);
|
|
33
|
+
}
|
|
25
34
|
console.log(`\n Architecture facts: ${governance.canonicalFactCount}`);
|
|
26
35
|
console.log(` Pending review: ${governance.reviewQueue.pending_entities} entities, ${governance.reviewQueue.pending_relationships} relationships`);
|
|
27
36
|
if (governance.latestSnapshot) {
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { homedir } from "os";
|
|
3
|
-
import { readFileSync } from "fs";
|
|
4
|
-
import { join } from "path";
|
|
5
2
|
import { login } from "./commands/login.js";
|
|
6
3
|
import { logout } from "./commands/logout.js";
|
|
7
4
|
import { status } from "./commands/status.js";
|
|
@@ -44,39 +41,25 @@ async function main() {
|
|
|
44
41
|
await agentIdentify(subArgs);
|
|
45
42
|
return;
|
|
46
43
|
}
|
|
47
|
-
if (subcommand === "prompt") {
|
|
48
|
-
const filePath = join(homedir(), ".nexarch", "agent-bootstrap.md");
|
|
49
|
-
try {
|
|
50
|
-
process.stdout.write(readFileSync(filePath, "utf8") + "\n");
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
console.error("No bootstrap file found. Run 'nexarch init-agent' first.");
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
44
|
}
|
|
59
45
|
const handler = commands[command ?? ""];
|
|
60
46
|
if (!handler) {
|
|
61
47
|
console.log(`
|
|
62
|
-
nexarch —
|
|
48
|
+
nexarch — Your architecture workspace for AI delivery.
|
|
63
49
|
|
|
64
50
|
Usage:
|
|
65
51
|
nexarch login Authenticate in browser and store company-scoped credentials
|
|
66
52
|
Option: --company <id>
|
|
67
53
|
nexarch logout Remove stored credentials
|
|
68
54
|
nexarch status Check connection and show architecture summary
|
|
69
|
-
nexarch setup
|
|
55
|
+
nexarch setup One-step onboarding: login (if needed) + MCP config + register agent
|
|
70
56
|
nexarch mcp-config Print MCP server config block for manual setup
|
|
57
|
+
Client list is registry-managed (see 'nexarch mcp-config --client <code>')
|
|
71
58
|
nexarch mcp-proxy Run as stdio MCP proxy (used by MCP clients)
|
|
72
|
-
nexarch init-agent Run handshake + mandatory agent registration in graph
|
|
59
|
+
nexarch init-agent Run handshake + mandatory agent registration in graph (advanced/manual)
|
|
73
60
|
Options: --agent-id <id> --bind-to-external-key <key>
|
|
74
61
|
--bind-relationship-type <code> --redact-hostname
|
|
75
62
|
--json --strict
|
|
76
|
-
nexarch agent prompt
|
|
77
|
-
Print bootstrap instructions for a Claude Code agent to complete
|
|
78
|
-
its registration (written by init-agent). Tell Claude:
|
|
79
|
-
"run npx nexarch agent prompt and follow the instructions"
|
|
80
63
|
nexarch agent identify
|
|
81
64
|
Capture richer coding-agent identity metadata
|
|
82
65
|
Options: --agent-id <id> --provider <provider> --model <model>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { callMcpTool } from "./mcp.js";
|
|
2
|
+
import { requireCredentials } from "./credentials.js";
|
|
3
|
+
function parseToolText(result) {
|
|
4
|
+
const text = result.content?.[0]?.text ?? "{}";
|
|
5
|
+
return JSON.parse(text);
|
|
6
|
+
}
|
|
7
|
+
function isString(v) {
|
|
8
|
+
return typeof v === "string";
|
|
9
|
+
}
|
|
10
|
+
function ensureRegistryShape(raw) {
|
|
11
|
+
const r = raw;
|
|
12
|
+
if (!r || typeof r !== "object")
|
|
13
|
+
throw new Error("invalid payload");
|
|
14
|
+
if (typeof r.registryVersion !== "number")
|
|
15
|
+
throw new Error("missing registryVersion");
|
|
16
|
+
if (!r.release || !isString(r.release.id) || typeof r.release.version !== "number" || !isString(r.release.publishedAt)) {
|
|
17
|
+
throw new Error("missing release metadata");
|
|
18
|
+
}
|
|
19
|
+
if (!Array.isArray(r.instructionTemplates) || !Array.isArray(r.instructionTargets) || !Array.isArray(r.mcpClientProfiles)) {
|
|
20
|
+
throw new Error("missing registry collections");
|
|
21
|
+
}
|
|
22
|
+
for (const t of r.instructionTemplates) {
|
|
23
|
+
if (!isString(t.code) || !isString(t.body))
|
|
24
|
+
throw new Error("invalid instruction template");
|
|
25
|
+
}
|
|
26
|
+
for (const it of r.instructionTargets) {
|
|
27
|
+
if (!isString(it.filePathPattern) || !isString(it.runtimeCode) || !isString(it.templateCode)) {
|
|
28
|
+
throw new Error("invalid instruction target");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
for (const cp of r.mcpClientProfiles) {
|
|
32
|
+
if (!isString(cp.code) || !isString(cp.name) || !isString(cp.serverCommand) || !Array.isArray(cp.serverArgs)) {
|
|
33
|
+
throw new Error("invalid mcp client profile");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return r;
|
|
37
|
+
}
|
|
38
|
+
export async function fetchAgentRegistryOrThrow() {
|
|
39
|
+
const creds = requireCredentials();
|
|
40
|
+
try {
|
|
41
|
+
const raw = await callMcpTool("nexarch_get_agent_registry", {}, { companyId: creds.companyId });
|
|
42
|
+
const parsed = parseToolText(raw);
|
|
43
|
+
return ensureRegistryShape(parsed);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
47
|
+
throw new Error(`Nexarch integration registry unavailable. This command is blocked until service is restored. (${message})`);
|
|
48
|
+
}
|
|
49
|
+
}
|
package/dist/lib/clients.js
CHANGED
|
@@ -4,45 +4,15 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
|
4
4
|
function appData() {
|
|
5
5
|
return process.env.APPDATA ?? null;
|
|
6
6
|
}
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return [join(homedir(), ".config", "Claude", "claude_desktop_config.json")];
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
function cursorPaths() {
|
|
20
|
-
switch (process.platform) {
|
|
21
|
-
case "darwin":
|
|
22
|
-
return [join(homedir(), ".cursor", "mcp.json")];
|
|
23
|
-
case "win32": {
|
|
24
|
-
const ad = appData();
|
|
25
|
-
return ad ? [join(ad, "Cursor", "User", "mcp.json")] : [];
|
|
26
|
-
}
|
|
27
|
-
default:
|
|
28
|
-
return [join(homedir(), ".cursor", "mcp.json")];
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function windsurfPaths() {
|
|
32
|
-
switch (process.platform) {
|
|
33
|
-
case "darwin":
|
|
34
|
-
return [join(homedir(), ".windsurf", "mcp.json")];
|
|
35
|
-
case "win32": {
|
|
36
|
-
const ad = appData();
|
|
37
|
-
return ad ? [join(ad, "Windsurf", "User", "mcp.json")] : [];
|
|
38
|
-
}
|
|
39
|
-
default:
|
|
40
|
-
return [join(homedir(), ".windsurf", "mcp.json")];
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
function continueDevPaths() {
|
|
44
|
-
// Continue.dev stores config at ~/.continue/config.json on all platforms
|
|
45
|
-
return [join(homedir(), ".continue", "config.json")];
|
|
7
|
+
function expandPath(p) {
|
|
8
|
+
let out = p;
|
|
9
|
+
if (out.startsWith("~/"))
|
|
10
|
+
out = join(homedir(), out.slice(2));
|
|
11
|
+
const ad = appData();
|
|
12
|
+
if (ad)
|
|
13
|
+
out = out.replace(/%APPDATA%/g, ad);
|
|
14
|
+
out = out.replace(/%USERPROFILE%/g, homedir());
|
|
15
|
+
return out;
|
|
46
16
|
}
|
|
47
17
|
function mergeClaudeStyle(existing, serverBlock) {
|
|
48
18
|
let config = {};
|
|
@@ -57,8 +27,6 @@ function mergeClaudeStyle(existing, serverBlock) {
|
|
|
57
27
|
config.mcpServers = servers;
|
|
58
28
|
return JSON.stringify(config, null, 2);
|
|
59
29
|
}
|
|
60
|
-
// Continue.dev uses an array under mcpServers (each entry has a "name" field).
|
|
61
|
-
// We upsert by name so re-running setup is idempotent.
|
|
62
30
|
function mergeContinueStyle(existing, serverBlock) {
|
|
63
31
|
let config = {};
|
|
64
32
|
try {
|
|
@@ -92,14 +60,40 @@ function mergeFlatStyle(existing, serverBlock) {
|
|
|
92
60
|
config.mcpServers = servers;
|
|
93
61
|
return JSON.stringify(config, null, 2);
|
|
94
62
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
63
|
+
function mergeForStrategy(strategy) {
|
|
64
|
+
switch (strategy) {
|
|
65
|
+
case "claude_style":
|
|
66
|
+
return mergeClaudeStyle;
|
|
67
|
+
case "continue_style":
|
|
68
|
+
return mergeContinueStyle;
|
|
69
|
+
case "flat_style":
|
|
70
|
+
default:
|
|
71
|
+
return mergeFlatStyle;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function osKey() {
|
|
75
|
+
if (process.platform === "darwin")
|
|
76
|
+
return "darwin";
|
|
77
|
+
if (process.platform === "win32")
|
|
78
|
+
return "win32";
|
|
79
|
+
return "linux";
|
|
80
|
+
}
|
|
81
|
+
export function detectClientsFromRegistry(registry) {
|
|
82
|
+
const current = osKey();
|
|
83
|
+
const out = [];
|
|
84
|
+
for (const cp of [...registry.mcpClientProfiles].sort((a, b) => a.sortOrder - b.sortOrder || a.code.localeCompare(b.code))) {
|
|
85
|
+
const paths = cp.osPaths?.[current] ?? [];
|
|
86
|
+
for (const p of paths) {
|
|
87
|
+
const configPath = expandPath(p);
|
|
88
|
+
out.push({
|
|
89
|
+
code: cp.code,
|
|
90
|
+
name: cp.name,
|
|
91
|
+
configPath,
|
|
92
|
+
merge: mergeForStrategy(cp.mergeStrategy),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return out.filter((c) => existsSync(join(c.configPath, "..")));
|
|
103
97
|
}
|
|
104
98
|
export function writeClientConfig(client, serverBlock) {
|
|
105
99
|
const dir = join(client.configPath, "..");
|
|
@@ -107,9 +101,20 @@ export function writeClientConfig(client, serverBlock) {
|
|
|
107
101
|
const existing = existsSync(client.configPath) ? readFileSync(client.configPath, "utf-8") : "";
|
|
108
102
|
writeFileSync(client.configPath, client.merge(existing, serverBlock), "utf-8");
|
|
109
103
|
}
|
|
110
|
-
export function
|
|
104
|
+
export function nexarchServerBlockFromRegistry(registry) {
|
|
105
|
+
const first = [...registry.mcpClientProfiles].sort((a, b) => a.sortOrder - b.sortOrder || a.code.localeCompare(b.code))[0];
|
|
106
|
+
if (!first) {
|
|
107
|
+
throw new Error("Nexarch integration registry is missing MCP client profiles.");
|
|
108
|
+
}
|
|
111
109
|
return {
|
|
112
|
-
command:
|
|
113
|
-
args:
|
|
110
|
+
command: first.serverCommand,
|
|
111
|
+
args: first.serverArgs,
|
|
114
112
|
};
|
|
115
113
|
}
|
|
114
|
+
export function findClientProfile(registry, code) {
|
|
115
|
+
const normalized = code.trim().toLowerCase();
|
|
116
|
+
if (normalized === "continue") {
|
|
117
|
+
return registry.mcpClientProfiles.find((c) => c.code === "continue-dev") ?? null;
|
|
118
|
+
}
|
|
119
|
+
return registry.mcpClientProfiles.find((c) => c.code === normalized) ?? null;
|
|
120
|
+
}
|
package/dist/lib/mcp.js
CHANGED
|
@@ -33,7 +33,13 @@ async function callMcpRpc(method, params = {}, options = {}) {
|
|
|
33
33
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
34
34
|
const json = JSON.parse(raw);
|
|
35
35
|
if (json.error) {
|
|
36
|
-
|
|
36
|
+
const detail = json.error.data &&
|
|
37
|
+
typeof json.error.data === "object" &&
|
|
38
|
+
"detail" in json.error.data &&
|
|
39
|
+
typeof json.error.data.detail === "string"
|
|
40
|
+
? json.error.data.detail
|
|
41
|
+
: null;
|
|
42
|
+
reject(new Error(detail ? `${json.error.message} (${detail})` : json.error.message));
|
|
37
43
|
}
|
|
38
44
|
else if (json.result !== undefined) {
|
|
39
45
|
resolve(json.result);
|
|
@@ -62,7 +68,7 @@ export async function mcpInitialize(options = {}) {
|
|
|
62
68
|
return callMcpRpc("initialize", {
|
|
63
69
|
protocolVersion: "2024-11-05",
|
|
64
70
|
capabilities: {},
|
|
65
|
-
clientInfo: { name: "nexarch-cli", version: "0.
|
|
71
|
+
clientInfo: { name: "nexarch-cli", version: "0.4.2" },
|
|
66
72
|
}, options);
|
|
67
73
|
}
|
|
68
74
|
export async function mcpListTools(options = {}) {
|
package/package.json
CHANGED