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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # nexarch
2
2
 
3
- Connect AI coding tools to your Nexarch architecture workspace.
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 show architecture summary for the company selected at login |
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 (`claude-desktop`, `cursor`, `windsurf`) |
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.6";
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
- const CLAUDE_MD_SECTION_MARKER = "## Nexarch Agent Registration";
149
- const CLAUDE_MD_SECTION = `
150
- ## Nexarch Agent Registration
151
-
152
- When a human has run \`nexarch login\` and \`nexarch init-agent\`, they may ask you to run \`npx nexarch agent prompt\`. Do so, read the output, and follow the instructions — it contains a pre-filled \`npx nexarch agent identify\` command for you to complete with your own provider, model, client, and capability values, then execute. Confirm \`"ok": true\` in the JSON output.
153
-
154
- This applies to any AI coding assistant with MCP tool access (Claude Code, Cursor, Windsurf, Continue.dev, Codex CLI, etc.).
155
- `;
156
- function injectAgentConfigs() {
157
- const candidateNames = [
158
- "CLAUDE.md", "claude.md", "Claude.md", // Claude Code
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 name of candidateNames) {
167
- const filePath = join(process.cwd(), name);
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
- if (existing.includes(CLAUDE_MD_SECTION_MARKER)) {
172
- results.push({ path: filePath, status: "already_present" });
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
- writeFileSync(filePath, existing + separator + CLAUDE_MD_SECTION, "utf8");
177
- results.push({ path: filePath, status: "injected" });
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
- for (const check of checks) {
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 Tell your agent: \"run npx nexarch agent prompt and follow the instructions\"");
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.");
@@ -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 { nexarchServerBlock } from "../lib/clients.js";
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
- const serverBlock = nexarchServerBlock();
6
- console.log(`\nMCP server config for ${clientFlag}:\n`);
7
- switch (clientFlag) {
8
- case "claude-desktop": {
9
- const config = { mcpServers: { nexarch: serverBlock } };
10
- console.log("Add the following to your claude_desktop_config.json:\n");
11
- console.log(JSON.stringify(config, null, 2));
12
- console.log(`
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
- break;
66
- }
67
- default: {
68
- const config = { mcpServers: { nexarch: serverBlock } };
69
- console.log(JSON.stringify(config, null, 2));
70
- break;
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 (Claude Desktop, Cursor, etc.) that
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.
@@ -1,37 +1,49 @@
1
1
  import { requireCredentials } from "../lib/credentials.js";
2
- import { detectClients, writeClientConfig, nexarchServerBlock } from "../lib/clients.js";
3
- export async function setup(_args) {
4
- requireCredentials(); // ensure logged in before configuring anything
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 = detectClients();
18
+ const clients = detectClientsFromRegistry(registry);
7
19
  if (clients.length === 0) {
8
- console.log("No supported MCP clients detected.");
9
- console.log("\nSupported clients: Claude Desktop, Cursor, Windsurf, Continue.dev");
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
- console.log(" nexarch mcp-config --client claude-desktop");
12
- console.log(" nexarch mcp-config --client cursor");
13
- console.log(" nexarch mcp-config --client continue");
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
- const serverBlock = nexarchServerBlock();
19
- for (const client of clients) {
20
- process.stdout.write(` ${client.name.padEnd(20)} ${client.configPath}\n`);
21
- process.stdout.write(` ${"".padEnd(20)} `);
22
- try {
23
- writeClientConfig(client, serverBlock);
24
- console.log("✓ configured");
25
- }
26
- catch (err) {
27
- const message = err instanceof Error ? err.message : String(err);
28
- console.log(`✗ failed ${message}`);
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("\nDone. Restart your MCP client for the changes to take effect.");
32
- console.log("\nThe nexarch tools will be available to your AI coding assistant:");
33
- console.log(" nexarch_list_architecture_snapshots");
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
  }
@@ -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 result = await callMcpTool("nexarch_get_governance_summary", { companyId: selectedCompanyId || undefined }, { companyId: selectedCompanyId || undefined });
12
- governance = JSON.parse(result.content[0]?.text ?? "{}");
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 — Connect AI coding tools to your architecture workspace
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 Auto-configure your MCP client (Claude Desktop, Cursor, etc.)
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
+ }
@@ -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 claudeDesktopPaths() {
8
- switch (process.platform) {
9
- case "darwin":
10
- return [join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json")];
11
- case "win32": {
12
- const ad = appData();
13
- return ad ? [join(ad, "Claude", "claude_desktop_config.json")] : [];
14
- }
15
- default:
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
- export function detectClients() {
96
- const candidates = [
97
- ...claudeDesktopPaths().map((p) => ({ name: "Claude Desktop", configPath: p, merge: mergeClaudeStyle })),
98
- ...cursorPaths().map((p) => ({ name: "Cursor", configPath: p, merge: mergeFlatStyle })),
99
- ...windsurfPaths().map((p) => ({ name: "Windsurf", configPath: p, merge: mergeFlatStyle })),
100
- ...continueDevPaths().map((p) => ({ name: "Continue.dev", configPath: p, merge: mergeContinueStyle })),
101
- ];
102
- return candidates.filter((c) => existsSync(join(c.configPath, "..")));
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 nexarchServerBlock() {
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: "npx",
113
- args: ["-y", "nexarch@latest", "mcp-proxy"],
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
- reject(new Error(json.error.message));
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.3.1" },
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.3.1",
4
- "description": "Connect AI coding tools to your Nexarch architecture workspace",
3
+ "version": "0.4.2",
4
+ "description": "Your architecture workspace for AI delivery.",
5
5
  "keywords": [
6
6
  "nexarch",
7
7
  "mcp",