nexarch 0.3.0 → 0.4.1

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 |
@@ -36,13 +36,8 @@ function loadIdentity() {
36
36
  }
37
37
  }
38
38
  const COMMAND_TYPE_HINTS = {
39
- update_project: "Re-scan the project with `npx nexarch init-project` to update the architecture graph.",
40
- drift_audit: "Compare the current codebase to the architecture graph and report any drift or missing entities.",
41
- access_audit: "Audit which agents and users have access to this application and report findings.",
42
- gap_check: "Run a gap analysis: identify architecturally significant platforms or infrastructure not yet in the graph.",
43
39
  policy_check: "Step 1: call nexarch_get_entity_policy_controls with entityExternalKey set to target_entity_key — this returns controls, each with a rules array (rule id, name, ruleText). Step 2: for each rule, examine the codebase and evaluate whether it passes, partially passes, or fails. Step 3: call nexarch_submit_policy_audit with applicationEntityKey, commandId, agentKey, and a findings array containing one entry per rule with policyControlId, policyRuleId, result (pass/partial/fail), rationale, and missingRequirements. Step 4: mark the command done with a brief summary. If no controls are returned, run command-fail with error 'No policy controls linked'.",
44
- enrich_entity: "Update the entity description, subtype, and metadata using information from the README or codebase.",
45
- register_aliases: "Find internal package names that are unresolved and register them as company aliases.",
40
+ data_model_review: "Goal: infer enterprise canonical logical data assets and map concrete physical implementations. 1) Identify logical business entities (canonical names, singular, enterprise meaning) and upsert as data_asset:data_logical. 2) Identify physical stores (tables/collections/files/indexes) and upsert as data_asset:data_physical with attributes (database, schema, table_name, store_type, source_system). 3) Link logical -> physical via implemented_by. 4) Link apps/services to physical via reads/writes. 5) Link logical assets to data domains via belongs_to_domain where evidence exists. 6) Avoid duplicate logical entities: reuse canonical logical entities if concept already exists. 7) Mark uncertain mappings with lower confidence in attributes.confidence.score and include rationale in command summary.",
46
41
  custom: "Execute the custom instructions provided.",
47
42
  };
48
43
  export async function checkIn(args) {
@@ -80,7 +75,7 @@ export async function checkIn(args) {
80
75
  return;
81
76
  }
82
77
  const cmd = result.command;
83
- const hint = COMMAND_TYPE_HINTS[cmd.command_type] ?? "";
78
+ const hint = COMMAND_TYPE_HINTS[cmd.command_type] ?? "No built-in playbook for this command type. Follow the command instructions and complete/fail it explicitly.";
84
79
  console.log(`\n╔══════════════════════════════════════════════════════════════════╗`);
85
80
  console.log(`║ COMMAND QUEUED — action required ║`);
86
81
  console.log(`╚══════════════════════════════════════════════════════════════════╝`);
@@ -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.1";
8
9
  const AGENT_ENTITY_TYPE = "agent";
9
10
  const TECH_COMPONENT_ENTITY_TYPE = "technology_component";
10
11
  function parseFlag(args, flag) {
@@ -145,36 +146,58 @@ After running, confirm \`"ok": true\` in the JSON output. No further action is n
145
146
  writeFileSync(filePath, content, "utf8");
146
147
  return filePath;
147
148
  }
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
- ];
149
+ function replaceInjectedSection(existing, sectionHeading, sectionBody) {
150
+ const headingRegex = new RegExp(`^${sectionHeading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?(?=^##\\s|\\Z)`, "m");
151
+ if (headingRegex.test(existing)) {
152
+ const replaced = existing.replace(headingRegex, sectionBody.trim());
153
+ return replaced.endsWith("\n") ? replaced : `${replaced}\n`;
154
+ }
155
+ return existing;
156
+ }
157
+ function injectAgentConfigs(registry) {
158
+ const templateByCode = new Map(registry.instructionTemplates.map((t) => [t.code, t]));
159
+ const targets = [...registry.instructionTargets].sort((a, b) => a.sortOrder - b.sortOrder || a.filePathPattern.localeCompare(b.filePathPattern));
165
160
  const results = [];
166
- for (const name of candidateNames) {
167
- const filePath = join(process.cwd(), name);
161
+ for (const target of targets) {
162
+ if (target.matchMode !== "exact")
163
+ continue;
164
+ const template = templateByCode.get(target.templateCode);
165
+ if (!template)
166
+ continue;
167
+ const filePath = join(process.cwd(), target.filePathPattern);
168
168
  if (!existsSync(filePath))
169
169
  continue;
170
170
  const existing = readFileSync(filePath, "utf8");
171
- if (existing.includes(CLAUDE_MD_SECTION_MARKER)) {
172
- results.push({ path: filePath, status: "already_present" });
171
+ const sectionBody = template.body.trim();
172
+ const sectionHeading = target.sectionHeading ?? "## Nexarch Agent Registration";
173
+ const sectionMarker = target.sectionMarker ?? sectionHeading;
174
+ if (target.insertionMode === "replace_section") {
175
+ let replaced = replaceInjectedSection(existing, sectionHeading, sectionBody);
176
+ if (replaced === existing && existing.includes(sectionMarker)) {
177
+ const markerIndex = existing.indexOf(sectionMarker);
178
+ const before = existing.slice(0, markerIndex).replace(/\s*$/, "\n\n");
179
+ replaced = `${before}${sectionBody}\n`;
180
+ }
181
+ if (replaced !== existing) {
182
+ writeFileSync(filePath, replaced, "utf8");
183
+ results.push({ path: filePath, status: "updated" });
184
+ }
185
+ else {
186
+ const separator = existing.endsWith("\n") ? "" : "\n";
187
+ writeFileSync(filePath, existing + separator + sectionBody + "\n", "utf8");
188
+ results.push({ path: filePath, status: "injected" });
189
+ }
173
190
  continue;
174
191
  }
175
192
  const separator = existing.endsWith("\n") ? "" : "\n";
176
- writeFileSync(filePath, existing + separator + CLAUDE_MD_SECTION, "utf8");
177
- results.push({ path: filePath, status: "injected" });
193
+ const next = existing + separator + sectionBody + "\n";
194
+ if (next === existing) {
195
+ results.push({ path: filePath, status: "already_present" });
196
+ }
197
+ else {
198
+ writeFileSync(filePath, next, "utf8");
199
+ results.push({ path: filePath, status: "injected" });
200
+ }
178
201
  }
179
202
  return results;
180
203
  }
@@ -198,6 +221,7 @@ export async function initAgent(args) {
198
221
  const checks = [];
199
222
  const creds = requireCredentials();
200
223
  const selectedCompanyId = creds.companyId;
224
+ const registry = await fetchAgentRegistryOrThrow();
201
225
  const init = await mcpInitialize({ companyId: selectedCompanyId });
202
226
  checks.push({
203
227
  name: "mcp.initialize",
@@ -207,6 +231,7 @@ export async function initAgent(args) {
207
231
  const tools = await mcpListTools({ companyId: selectedCompanyId });
208
232
  const toolNames = new Set(tools.map((t) => t.name));
209
233
  const required = [
234
+ "nexarch_get_agent_registry",
210
235
  "nexarch_get_applied_policies",
211
236
  "nexarch_get_ingest_contract",
212
237
  "nexarch_upsert_entities",
@@ -639,7 +664,7 @@ export async function initAgent(args) {
639
664
  // non-fatal
640
665
  }
641
666
  try {
642
- agentConfigResults = injectAgentConfigs();
667
+ agentConfigResults = injectAgentConfigs(registry);
643
668
  }
644
669
  catch {
645
670
  // non-fatal
@@ -703,6 +728,7 @@ export async function initAgent(args) {
703
728
  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
729
  },
705
730
  companyId: creds.companyId,
731
+ registry: { version: registry.release.version, registryVersion: registry.registryVersion, publishedAt: registry.release.publishedAt },
706
732
  bootstrapFile: bootstrapFilePath,
707
733
  agentConfigs: agentConfigResults,
708
734
  };
@@ -711,6 +737,7 @@ export async function initAgent(args) {
711
737
  process.exitCode = 1;
712
738
  return;
713
739
  }
740
+ console.log(`Using integration registry release v${registry.release.version}.`);
714
741
  console.log("Running agent onboarding handshake…\n");
715
742
  for (const check of checks) {
716
743
  console.log(`${check.ok ? "✓" : "✗"} ${check.name}`);
@@ -733,6 +760,9 @@ export async function initAgent(args) {
733
760
  if (r.status === "injected") {
734
761
  console.log(`📝 Nexarch registration instructions added to: ${r.path}`);
735
762
  }
763
+ if (r.status === "updated") {
764
+ console.log(`📝 Nexarch registration instructions updated in: ${r.path}`);
765
+ }
736
766
  }
737
767
  console.log("\n Tell your agent: \"run npx nexarch agent prompt and follow the instructions\"");
738
768
  }
@@ -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
@@ -50,7 +50,7 @@ async function main() {
50
50
  process.stdout.write(readFileSync(filePath, "utf8") + "\n");
51
51
  }
52
52
  catch {
53
- console.error("No bootstrap file found. Run 'nexarch init-agent' first.");
53
+ console.error("No bootstrap file found. Run 'nexarch setup' (or 'nexarch init-agent') first.");
54
54
  process.exit(1);
55
55
  }
56
56
  return;
@@ -59,17 +59,18 @@ async function main() {
59
59
  const handler = commands[command ?? ""];
60
60
  if (!handler) {
61
61
  console.log(`
62
- nexarch — Connect AI coding tools to your architecture workspace
62
+ nexarch — Your architecture workspace for AI delivery.
63
63
 
64
64
  Usage:
65
65
  nexarch login Authenticate in browser and store company-scoped credentials
66
66
  Option: --company <id>
67
67
  nexarch logout Remove stored credentials
68
68
  nexarch status Check connection and show architecture summary
69
- nexarch setup Auto-configure your MCP client (Claude Desktop, Cursor, etc.)
69
+ nexarch setup One-step onboarding: login (if needed) + MCP config + register agent
70
70
  nexarch mcp-config Print MCP server config block for manual setup
71
+ Client list is registry-managed (see 'nexarch mcp-config --client <code>')
71
72
  nexarch mcp-proxy Run as stdio MCP proxy (used by MCP clients)
72
- nexarch init-agent Run handshake + mandatory agent registration in graph
73
+ nexarch init-agent Run handshake + mandatory agent registration in graph (advanced/manual)
73
74
  Options: --agent-id <id> --bind-to-external-key <key>
74
75
  --bind-relationship-type <code> --redact-hostname
75
76
  --json --strict
@@ -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
@@ -62,7 +62,7 @@ export async function mcpInitialize(options = {}) {
62
62
  return callMcpRpc("initialize", {
63
63
  protocolVersion: "2024-11-05",
64
64
  capabilities: {},
65
- clientInfo: { name: "nexarch-cli", version: "0.3.0" },
65
+ clientInfo: { name: "nexarch-cli", version: "0.4.1" },
66
66
  }, options);
67
67
  }
68
68
  export async function mcpListTools(options = {}) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.3.0",
4
- "description": "Connect AI coding tools to your Nexarch architecture workspace",
3
+ "version": "0.4.1",
4
+ "description": "Your architecture workspace for AI delivery.",
5
5
  "keywords": [
6
6
  "nexarch",
7
7
  "mcp",