nexarch 0.1.0 → 0.1.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.
@@ -0,0 +1,171 @@
1
+ import { createServer } from "http";
2
+ import { createServer as createNetServer } from "net";
3
+ import { randomBytes } from "crypto";
4
+ import { execFile } from "child_process";
5
+ import https from "https";
6
+ import { saveCredentials } from "../lib/credentials.js";
7
+ const NEXARCH_URL = "https://nexarch.ai";
8
+ const LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
9
+ const VERIFY_TIMEOUT_MS = 15 * 1000;
10
+ function generateState() {
11
+ return randomBytes(16).toString("hex");
12
+ }
13
+ function findFreePort() {
14
+ return new Promise((resolve, reject) => {
15
+ const server = createNetServer();
16
+ server.listen(0, "127.0.0.1", () => {
17
+ const addr = server.address();
18
+ const port = typeof addr === "object" && addr ? addr.port : null;
19
+ server.close(() => {
20
+ if (port)
21
+ resolve(port);
22
+ else
23
+ reject(new Error("Could not find a free port"));
24
+ });
25
+ });
26
+ server.on("error", reject);
27
+ });
28
+ }
29
+ function openBrowser(url) {
30
+ // Use execFile to avoid shell injection — URL is passed as a separate argument.
31
+ const [cmd, args] = process.platform === "darwin"
32
+ ? ["open", [url]]
33
+ : process.platform === "win32"
34
+ ? ["cmd", ["/c", "start", "", url]]
35
+ : ["xdg-open", [url]];
36
+ execFile(cmd, args, (err) => {
37
+ if (err) {
38
+ // Non-fatal — the URL is already printed so the user can open it manually.
39
+ }
40
+ });
41
+ }
42
+ function waitForCallback(port, expectedState) {
43
+ return new Promise((resolve, reject) => {
44
+ let settled = false;
45
+ const settle = (fn) => {
46
+ if (settled)
47
+ return;
48
+ settled = true;
49
+ fn();
50
+ };
51
+ // Declare server before timeout so the timeout handler can safely close it.
52
+ const server = createServer((req, res) => {
53
+ const url = new URL(req.url ?? "/", `http://localhost:${port}`);
54
+ if (url.pathname !== "/callback") {
55
+ res.writeHead(404);
56
+ res.end();
57
+ return;
58
+ }
59
+ clearTimeout(timeout);
60
+ const state = url.searchParams.get("state");
61
+ const token = url.searchParams.get("token");
62
+ const error = url.searchParams.get("error");
63
+ const html = (message) => `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Nexarch CLI</title>
64
+ <style>body{font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;background:#f8fafc}
65
+ .card{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:2rem;max-width:400px;text-align:center}
66
+ h1{font-size:1.25rem;margin:0 0 .5rem;color:#0f172a}p{color:#64748b;font-size:.9rem;margin:.5rem 0}</style></head>
67
+ <body><div class="card"><h1>Nexarch CLI</h1><p>${message}</p></div></body></html>`;
68
+ if (state !== expectedState) {
69
+ res.writeHead(400, { "Content-Type": "text/html" });
70
+ res.end(html("Invalid state parameter. Please run <code>nexarch login</code> again."));
71
+ server.close();
72
+ settle(() => reject(new Error("State mismatch — possible CSRF. Run `nexarch login` to try again.")));
73
+ return;
74
+ }
75
+ if (error) {
76
+ res.writeHead(200, { "Content-Type": "text/html" });
77
+ res.end(html("Authorization cancelled. You can close this tab."));
78
+ server.close();
79
+ settle(() => reject(new Error("Authorization was cancelled.")));
80
+ return;
81
+ }
82
+ if (!token) {
83
+ res.writeHead(400, { "Content-Type": "text/html" });
84
+ res.end(html("Missing token. Please run <code>nexarch login</code> again."));
85
+ server.close();
86
+ settle(() => reject(new Error("No token received from authorization server.")));
87
+ return;
88
+ }
89
+ res.writeHead(200, { "Content-Type": "text/html" });
90
+ res.end(html("Authorization successful! You can close this tab and return to your terminal."));
91
+ server.close();
92
+ settle(() => resolve({ token }));
93
+ });
94
+ const timeout = setTimeout(() => {
95
+ server.close();
96
+ settle(() => reject(new Error("Authorization timed out after 5 minutes. Run `nexarch login` to try again.")));
97
+ }, LOGIN_TIMEOUT_MS);
98
+ server.listen(port, "127.0.0.1");
99
+ server.on("error", (err) => {
100
+ clearTimeout(timeout);
101
+ settle(() => reject(err));
102
+ });
103
+ });
104
+ }
105
+ function fetchCompanyId(token) {
106
+ const body = JSON.stringify({
107
+ jsonrpc: "2.0",
108
+ id: 1,
109
+ method: "tools/call",
110
+ params: { name: "nexarch_get_governance_summary", arguments: {} },
111
+ });
112
+ return new Promise((resolve, reject) => {
113
+ const url = new URL("/mcp", "https://mcp.nexarch.ai");
114
+ const req = https.request({
115
+ hostname: url.hostname,
116
+ port: url.port || 443,
117
+ path: url.pathname,
118
+ method: "POST",
119
+ headers: {
120
+ "Content-Type": "application/json",
121
+ Authorization: `Bearer ${token}`,
122
+ "Content-Length": Buffer.byteLength(body),
123
+ },
124
+ timeout: VERIFY_TIMEOUT_MS,
125
+ }, (res) => {
126
+ const chunks = [];
127
+ res.on("data", (c) => chunks.push(c));
128
+ res.on("end", () => {
129
+ try {
130
+ const raw = Buffer.concat(chunks).toString("utf-8");
131
+ const json = JSON.parse(raw);
132
+ if (json.error)
133
+ return reject(new Error(json.error.message));
134
+ const text = json.result?.content?.[0]?.text ?? "{}";
135
+ const data = JSON.parse(text);
136
+ const companyId = data.companyId;
137
+ if (!companyId)
138
+ return reject(new Error("Could not retrieve workspace ID from Nexarch."));
139
+ resolve(companyId);
140
+ }
141
+ catch {
142
+ reject(new Error("Unexpected response while verifying credentials."));
143
+ }
144
+ });
145
+ });
146
+ req.on("timeout", () => {
147
+ req.destroy(new Error("Timed out verifying credentials. Check your connection and try again."));
148
+ });
149
+ req.on("error", reject);
150
+ req.write(body);
151
+ req.end();
152
+ });
153
+ }
154
+ export async function login(_args) {
155
+ const state = generateState();
156
+ const port = await findFreePort();
157
+ const authUrl = `${NEXARCH_URL}/auth/cli?port=${port}&state=${state}`;
158
+ console.log("Opening Nexarch in your browser…");
159
+ console.log(`\n ${authUrl}\n`);
160
+ console.log("If the browser did not open, copy the URL above and paste it in manually.\n");
161
+ openBrowser(authUrl);
162
+ const { token } = await waitForCallback(port, state);
163
+ process.stdout.write("Verifying credentials… ");
164
+ const companyId = await fetchCompanyId(token);
165
+ console.log("done\n");
166
+ const expiresAt = new Date();
167
+ expiresAt.setDate(expiresAt.getDate() + 90);
168
+ saveCredentials({ token, email: "", company: "", companyId, expiresAt: expiresAt.toISOString() });
169
+ console.log("✓ Logged in successfully");
170
+ console.log("\nRun `nexarch status` to verify your connection.");
171
+ }
@@ -0,0 +1,12 @@
1
+ import { loadCredentials, removeCredentials } from "../lib/credentials.js";
2
+ export async function logout(_args) {
3
+ const creds = loadCredentials();
4
+ if (!creds) {
5
+ console.log("Not logged in.");
6
+ return;
7
+ }
8
+ removeCredentials();
9
+ console.log("✓ Logged out. Credentials removed from ~/.nexarch/credentials.json");
10
+ console.log("\nNote: the token still exists in your Nexarch workspace until revoked.");
11
+ console.log("To revoke it, visit Workspace → Agents → Manage tokens.");
12
+ }
@@ -0,0 +1,32 @@
1
+ import { nexarchServerBlock } from "../lib/clients.js";
2
+ export async function mcpConfig(args) {
3
+ const clientFlag = args.find((a) => !a.startsWith("-")) ?? "claude-desktop";
4
+ const serverBlock = nexarchServerBlock();
5
+ console.log(`\nMCP server config for ${clientFlag}:\n`);
6
+ switch (clientFlag) {
7
+ case "claude-desktop": {
8
+ const config = { mcpServers: { nexarch: serverBlock } };
9
+ console.log("Add the following to your claude_desktop_config.json:\n");
10
+ console.log(JSON.stringify(config, null, 2));
11
+ console.log(`
12
+ Locations:
13
+ macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
14
+ Windows: %APPDATA%\\Claude\\claude_desktop_config.json
15
+ `);
16
+ break;
17
+ }
18
+ case "cursor":
19
+ case "windsurf": {
20
+ const config = { mcpServers: { nexarch: serverBlock } };
21
+ console.log(`Add the following to your ${clientFlag === "cursor" ? ".cursor/mcp.json" : ".windsurf/mcp.json"}:\n`);
22
+ console.log(JSON.stringify(config, null, 2));
23
+ break;
24
+ }
25
+ default: {
26
+ const config = { mcpServers: { nexarch: serverBlock } };
27
+ console.log(JSON.stringify(config, null, 2));
28
+ break;
29
+ }
30
+ }
31
+ console.log("\nOr run `nexarch setup` to have the CLI write the config automatically.");
32
+ }
@@ -0,0 +1,75 @@
1
+ import * as readline from "readline";
2
+ import { requireCredentials } from "../lib/credentials.js";
3
+ import { forwardToGateway } from "../lib/mcp.js";
4
+ /**
5
+ * stdio MCP proxy — bridges MCP clients (Claude Desktop, Cursor, etc.) that
6
+ * use stdio transport to the Nexarch HTTP MCP gateway.
7
+ *
8
+ * The MCP protocol over stdio is newline-delimited JSON-RPC 2.0.
9
+ * Each line from stdin is a complete JSON-RPC message.
10
+ * Responses are written to stdout as single-line JSON.
11
+ */
12
+ export async function mcpProxy(_args) {
13
+ const creds = requireCredentials();
14
+ const rl = readline.createInterface({
15
+ input: process.stdin,
16
+ terminal: false,
17
+ });
18
+ for await (const line of rl) {
19
+ const trimmed = line.trim();
20
+ if (!trimmed)
21
+ continue;
22
+ let message = {};
23
+ try {
24
+ message = JSON.parse(trimmed);
25
+ }
26
+ catch {
27
+ // Malformed input — write a parse error and continue
28
+ const errorResponse = JSON.stringify({
29
+ jsonrpc: "2.0",
30
+ id: null,
31
+ error: { code: -32700, message: "Parse error" },
32
+ });
33
+ process.stdout.write(errorResponse + "\n");
34
+ continue;
35
+ }
36
+ // Notifications (no id) — forward but don't write a response to stdout.
37
+ const isNotification = message.id === undefined || message.id === null;
38
+ try {
39
+ const { status, body } = await forwardToGateway(creds.token, creds.companyId, trimmed);
40
+ if (isNotification) {
41
+ // Notifications expect no response per JSON-RPC spec.
42
+ continue;
43
+ }
44
+ if (status === 200 && body) {
45
+ // Write response as a single line.
46
+ process.stdout.write(body.replace(/\n/g, " ") + "\n");
47
+ }
48
+ else if (status === 401) {
49
+ const errorResponse = JSON.stringify({
50
+ jsonrpc: "2.0",
51
+ id: message.id ?? null,
52
+ error: { code: -32001, message: "Unauthorized — run `nexarch login` to re-authenticate." },
53
+ });
54
+ process.stdout.write(errorResponse + "\n");
55
+ }
56
+ else {
57
+ const errorResponse = JSON.stringify({
58
+ jsonrpc: "2.0",
59
+ id: message.id ?? null,
60
+ error: { code: -32000, message: `Gateway error (HTTP ${status})` },
61
+ });
62
+ process.stdout.write(errorResponse + "\n");
63
+ }
64
+ }
65
+ catch (err) {
66
+ const message2 = err instanceof Error ? err.message : "Internal error";
67
+ const errorResponse = JSON.stringify({
68
+ jsonrpc: "2.0",
69
+ id: message.id ?? null,
70
+ error: { code: -32000, message: message2 },
71
+ });
72
+ process.stdout.write(errorResponse + "\n");
73
+ }
74
+ }
75
+ }
@@ -0,0 +1,32 @@
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
5
+ console.log("Detecting installed MCP clients…\n");
6
+ const clients = detectClients();
7
+ if (clients.length === 0) {
8
+ console.log("No supported MCP clients detected.");
9
+ console.log("\nSupported clients: Claude Desktop, Cursor, Windsurf");
10
+ console.log("\nTo configure manually, run:");
11
+ console.log(" nexarch mcp-config --client claude-desktop");
12
+ return;
13
+ }
14
+ const serverBlock = nexarchServerBlock();
15
+ for (const client of clients) {
16
+ process.stdout.write(` ${client.name.padEnd(20)} ${client.configPath}\n`);
17
+ process.stdout.write(` ${"".padEnd(20)} `);
18
+ try {
19
+ writeClientConfig(client, serverBlock);
20
+ console.log("✓ configured");
21
+ }
22
+ catch (err) {
23
+ const message = err instanceof Error ? err.message : String(err);
24
+ console.log(`✗ failed — ${message}`);
25
+ }
26
+ }
27
+ console.log("\nDone. Restart your MCP client for the changes to take effect.");
28
+ console.log("\nThe nexarch tools will be available to your AI coding assistant:");
29
+ console.log(" nexarch_list_architecture_snapshots");
30
+ console.log(" nexarch_get_snapshot_facts");
31
+ console.log(" nexarch_get_governance_summary");
32
+ }
@@ -0,0 +1,39 @@
1
+ import { requireCredentials } from "../lib/credentials.js";
2
+ import { callMcpTool } from "../lib/mcp.js";
3
+ export async function status(_args) {
4
+ const creds = requireCredentials();
5
+ const expiresAt = new Date(creds.expiresAt);
6
+ const daysLeft = Math.ceil((expiresAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
7
+ process.stdout.write("Connecting to mcp.nexarch.ai… ");
8
+ let governance;
9
+ try {
10
+ const result = await callMcpTool("nexarch_get_governance_summary", {
11
+ companyId: creds.companyId || undefined,
12
+ });
13
+ governance = JSON.parse(result.content[0]?.text ?? "{}");
14
+ }
15
+ catch (err) {
16
+ console.error("failed\n");
17
+ throw err;
18
+ }
19
+ console.log("connected\n");
20
+ if (creds.email) {
21
+ console.log(` Signed in as: ${creds.email}`);
22
+ }
23
+ if (creds.company) {
24
+ console.log(` Workspace: ${creds.company}`);
25
+ }
26
+ console.log(`\n Architecture facts: ${governance.canonicalFactCount}`);
27
+ console.log(` Pending review: ${governance.reviewQueue.pending_entities} entities, ${governance.reviewQueue.pending_relationships} relationships`);
28
+ if (governance.latestSnapshot) {
29
+ const snapshotDate = new Date(governance.latestSnapshot.created_at).toLocaleDateString();
30
+ console.log(` Latest snapshot: ${snapshotDate} (${governance.latestSnapshot.status})`);
31
+ }
32
+ else {
33
+ console.log(` Latest snapshot: none published yet`);
34
+ }
35
+ console.log(`\n Token expires: ${expiresAt.toLocaleDateString()} (${daysLeft} days)`);
36
+ if (daysLeft <= 14) {
37
+ console.log(`\n ⚠ Your token expires soon. Run \`nexarch login\` to renew.`);
38
+ }
39
+ }
package/dist/index.js ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ import { login } from "./commands/login.js";
3
+ import { logout } from "./commands/logout.js";
4
+ import { status } from "./commands/status.js";
5
+ import { setup } from "./commands/setup.js";
6
+ import { mcpConfig } from "./commands/mcp-config.js";
7
+ import { mcpProxy } from "./commands/mcp-proxy.js";
8
+ const [, , command, ...args] = process.argv;
9
+ const commands = {
10
+ login,
11
+ logout,
12
+ status,
13
+ setup,
14
+ "mcp-config": mcpConfig,
15
+ "mcp-proxy": mcpProxy,
16
+ };
17
+ async function main() {
18
+ const handler = commands[command ?? ""];
19
+ if (!handler) {
20
+ console.log(`
21
+ nexarch — Connect AI coding tools to your architecture workspace
22
+
23
+ Usage:
24
+ nexarch login Authenticate in the browser and store credentials
25
+ nexarch logout Remove stored credentials
26
+ nexarch status Check connection and show architecture summary
27
+ nexarch setup Auto-configure your MCP client (Claude Desktop, Cursor, etc.)
28
+ nexarch mcp-config Print MCP server config block for manual setup
29
+ nexarch mcp-proxy Run as stdio MCP proxy (used by MCP clients)
30
+ `);
31
+ process.exit(command ? 1 : 0);
32
+ }
33
+ try {
34
+ await handler(args);
35
+ }
36
+ catch (err) {
37
+ const message = err instanceof Error ? err.message : String(err);
38
+ console.error(`error: ${message}`);
39
+ process.exit(1);
40
+ }
41
+ }
42
+ main().catch((err) => {
43
+ console.error(`error: ${err instanceof Error ? err.message : String(err)}`);
44
+ process.exit(1);
45
+ });
@@ -0,0 +1,88 @@
1
+ import { homedir } from "os";
2
+ import { join } from "path";
3
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
4
+ function appData() {
5
+ return process.env.APPDATA ?? null;
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 mergeClaudeStyle(existing, serverBlock) {
44
+ let config = {};
45
+ try {
46
+ config = JSON.parse(existing);
47
+ }
48
+ catch {
49
+ // start fresh if file is empty or invalid
50
+ }
51
+ const servers = config.mcpServers ?? {};
52
+ servers["nexarch"] = serverBlock;
53
+ config.mcpServers = servers;
54
+ return JSON.stringify(config, null, 2);
55
+ }
56
+ function mergeFlatStyle(existing, serverBlock) {
57
+ let config = {};
58
+ try {
59
+ config = JSON.parse(existing);
60
+ }
61
+ catch {
62
+ config = { mcpServers: {} };
63
+ }
64
+ const servers = config.mcpServers ?? {};
65
+ servers["nexarch"] = serverBlock;
66
+ config.mcpServers = servers;
67
+ return JSON.stringify(config, null, 2);
68
+ }
69
+ export function detectClients() {
70
+ const candidates = [
71
+ ...claudeDesktopPaths().map((p) => ({ name: "Claude Desktop", configPath: p, merge: mergeClaudeStyle })),
72
+ ...cursorPaths().map((p) => ({ name: "Cursor", configPath: p, merge: mergeFlatStyle })),
73
+ ...windsurfPaths().map((p) => ({ name: "Windsurf", configPath: p, merge: mergeFlatStyle })),
74
+ ];
75
+ return candidates.filter((c) => existsSync(join(c.configPath, "..")));
76
+ }
77
+ export function writeClientConfig(client, serverBlock) {
78
+ const dir = join(client.configPath, "..");
79
+ mkdirSync(dir, { recursive: true });
80
+ const existing = existsSync(client.configPath) ? readFileSync(client.configPath, "utf-8") : "";
81
+ writeFileSync(client.configPath, client.merge(existing, serverBlock), "utf-8");
82
+ }
83
+ export function nexarchServerBlock() {
84
+ return {
85
+ command: "npx",
86
+ args: ["-y", "nexarch@latest", "mcp-proxy"],
87
+ };
88
+ }
@@ -0,0 +1,44 @@
1
+ import { homedir } from "os";
2
+ import { join } from "path";
3
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, rmSync } from "fs";
4
+ function credentialsPath() {
5
+ return join(homedir(), ".nexarch", "credentials.json");
6
+ }
7
+ export function loadCredentials() {
8
+ const path = credentialsPath();
9
+ if (!existsSync(path))
10
+ return null;
11
+ try {
12
+ const raw = readFileSync(path, "utf-8");
13
+ const creds = JSON.parse(raw);
14
+ if (!creds.token)
15
+ return null;
16
+ return creds;
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ }
22
+ export function saveCredentials(creds) {
23
+ const dir = join(homedir(), ".nexarch");
24
+ mkdirSync(dir, { recursive: true });
25
+ writeFileSync(credentialsPath(), JSON.stringify(creds, null, 2), {
26
+ encoding: "utf-8",
27
+ mode: 0o600,
28
+ });
29
+ }
30
+ export function removeCredentials() {
31
+ const path = credentialsPath();
32
+ if (existsSync(path))
33
+ rmSync(path);
34
+ }
35
+ export function requireCredentials() {
36
+ const creds = loadCredentials();
37
+ if (!creds) {
38
+ throw new Error("Not logged in. Run `nexarch login` to authenticate.");
39
+ }
40
+ if (new Date(creds.expiresAt) < new Date()) {
41
+ throw new Error("Your session has expired. Run `nexarch login` to re-authenticate.");
42
+ }
43
+ return creds;
44
+ }
@@ -0,0 +1,89 @@
1
+ import https from "https";
2
+ import { requireCredentials } from "./credentials.js";
3
+ const MCP_GATEWAY_URL = "https://mcp.nexarch.ai";
4
+ const REQUEST_TIMEOUT_MS = 30 * 1000;
5
+ export async function callMcpTool(toolName, toolArgs = {}) {
6
+ const creds = requireCredentials();
7
+ const body = JSON.stringify({
8
+ jsonrpc: "2.0",
9
+ id: 1,
10
+ method: "tools/call",
11
+ params: { name: toolName, arguments: toolArgs },
12
+ });
13
+ return new Promise((resolve, reject) => {
14
+ const url = new URL("/mcp", MCP_GATEWAY_URL);
15
+ const req = https.request({
16
+ hostname: url.hostname,
17
+ port: url.port || 443,
18
+ path: url.pathname,
19
+ method: "POST",
20
+ headers: {
21
+ "Content-Type": "application/json",
22
+ "Authorization": `Bearer ${creds.token}`,
23
+ "x-company-id": creds.companyId,
24
+ "Content-Length": Buffer.byteLength(body),
25
+ },
26
+ timeout: REQUEST_TIMEOUT_MS,
27
+ }, (res) => {
28
+ const chunks = [];
29
+ res.on("data", (chunk) => chunks.push(chunk));
30
+ res.on("end", () => {
31
+ try {
32
+ const raw = Buffer.concat(chunks).toString("utf-8");
33
+ const json = JSON.parse(raw);
34
+ if (json.error) {
35
+ reject(new Error(json.error.message));
36
+ }
37
+ else if (json.result) {
38
+ resolve(json.result);
39
+ }
40
+ else {
41
+ reject(new Error("Unexpected response from MCP gateway"));
42
+ }
43
+ }
44
+ catch {
45
+ reject(new Error("Failed to parse MCP gateway response"));
46
+ }
47
+ });
48
+ });
49
+ req.on("timeout", () => {
50
+ req.destroy(new Error("Request timed out. Check your connection and try again."));
51
+ });
52
+ req.on("error", reject);
53
+ req.write(body);
54
+ req.end();
55
+ });
56
+ }
57
+ export async function forwardToGateway(token, companyId, body) {
58
+ return new Promise((resolve, reject) => {
59
+ const url = new URL("/mcp", MCP_GATEWAY_URL);
60
+ const req = https.request({
61
+ hostname: url.hostname,
62
+ port: url.port || 443,
63
+ path: url.pathname,
64
+ method: "POST",
65
+ headers: {
66
+ "Content-Type": "application/json",
67
+ "Authorization": `Bearer ${token}`,
68
+ "x-company-id": companyId,
69
+ "Content-Length": Buffer.byteLength(body),
70
+ },
71
+ timeout: REQUEST_TIMEOUT_MS,
72
+ }, (res) => {
73
+ const chunks = [];
74
+ res.on("data", (chunk) => chunks.push(chunk));
75
+ res.on("end", () => {
76
+ resolve({
77
+ status: res.statusCode ?? 200,
78
+ body: Buffer.concat(chunks).toString("utf-8"),
79
+ });
80
+ });
81
+ });
82
+ req.on("timeout", () => {
83
+ req.destroy(new Error("Request timed out."));
84
+ });
85
+ req.on("error", reject);
86
+ req.write(body);
87
+ req.end();
88
+ });
89
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Connect AI coding tools to your Nexarch architecture workspace",
5
5
  "keywords": [
6
6
  "nexarch",
@@ -23,6 +23,7 @@
23
23
  ],
24
24
  "scripts": {
25
25
  "build": "tsc",
26
+ "prepublishOnly": "tsc",
26
27
  "dev": "tsx src/index.ts",
27
28
  "typecheck": "tsc --noEmit"
28
29
  },