nexarch 0.1.4 → 0.1.6

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
@@ -19,6 +19,8 @@ npx nexarch setup
19
19
  | `nexarch setup` | Auto-configure detected MCP clients |
20
20
  | `nexarch mcp-config [client]` | Print config block for manual setup (`claude-desktop`, `cursor`, `windsurf`) |
21
21
  | `nexarch mcp-proxy` | stdio→HTTP bridge used internally by MCP clients |
22
+ | `nexarch init-agent [--json] [--strict] [--agent-id <id>]` | Run onboarding handshake and mandatory `agent` entity registration in graph |
23
+ | `nexarch init-agent --bind-to-external-key <key> [--bind-relationship-type <code>]` | Optionally bind the agent node to an existing graph external key |
22
24
 
23
25
  ## Requirements
24
26
 
@@ -0,0 +1,305 @@
1
+ import { hostname, userInfo } from "os";
2
+ import { requireCredentials } from "../lib/credentials.js";
3
+ import { callMcpTool, mcpInitialize, mcpListTools } from "../lib/mcp.js";
4
+ const CLI_VERSION = "0.1.6";
5
+ function parseFlag(args, flag) {
6
+ return args.includes(flag);
7
+ }
8
+ function parseOptionValue(args, option) {
9
+ const idx = args.indexOf(option);
10
+ if (idx === -1)
11
+ return null;
12
+ const value = args[idx + 1];
13
+ if (!value || value.startsWith("--"))
14
+ return null;
15
+ return value;
16
+ }
17
+ function parseToolText(result) {
18
+ const text = result.content?.[0]?.text ?? "{}";
19
+ return JSON.parse(text);
20
+ }
21
+ function getDefaultAgentId() {
22
+ const osUser = process.env.USERNAME || process.env.USER || userInfo().username || "unknown";
23
+ const host = hostname() || "unknown-host";
24
+ return `nexarch-cli:${osUser}@${host}`;
25
+ }
26
+ async function emitInitObservation(params) {
27
+ if (!params.policyBundleHash)
28
+ return;
29
+ const nowIso = new Date().toISOString();
30
+ try {
31
+ await callMcpTool("nexarch_emit_observations", {
32
+ observations: [
33
+ {
34
+ kind: "entity",
35
+ externalKey: `agent:${params.agentId}`,
36
+ message: params.message,
37
+ confidence: params.confidence ?? 0.6,
38
+ evidence: { source: "nexarch init-agent", timestamp: nowIso },
39
+ },
40
+ ],
41
+ agentContext: {
42
+ agentId: params.agentId,
43
+ agentRunId: `init-agent-${Date.now()}`,
44
+ repoRef: "nexarch-cli/init-agent",
45
+ observedAt: nowIso,
46
+ source: "nexarch-cli",
47
+ model: "n/a",
48
+ provider: "n/a",
49
+ },
50
+ policyContext: {
51
+ policyBundleHash: params.policyBundleHash,
52
+ alignmentSummary: {
53
+ score: 1,
54
+ violations: [],
55
+ waivers: [],
56
+ },
57
+ },
58
+ });
59
+ }
60
+ catch {
61
+ // non-fatal telemetry path
62
+ }
63
+ }
64
+ export async function initAgent(args) {
65
+ const asJson = parseFlag(args, "--json");
66
+ const strict = parseFlag(args, "--strict");
67
+ const explicitAgentId = parseOptionValue(args, "--agent-id");
68
+ const bindToExternalKey = parseOptionValue(args, "--bind-to-external-key");
69
+ const bindRelationshipType = parseOptionValue(args, "--bind-relationship-type") ?? "depends_on";
70
+ const agentId = explicitAgentId ?? getDefaultAgentId();
71
+ const checks = [];
72
+ const creds = requireCredentials();
73
+ const init = await mcpInitialize();
74
+ checks.push({
75
+ name: "mcp.initialize",
76
+ ok: true,
77
+ detail: `${init.serverInfo?.name ?? "unknown"}@${init.serverInfo?.version ?? "unknown"}`,
78
+ });
79
+ const tools = await mcpListTools();
80
+ const toolNames = new Set(tools.map((t) => t.name));
81
+ const required = [
82
+ "nexarch_get_applied_policies",
83
+ "nexarch_get_ingest_contract",
84
+ "nexarch_upsert_entities",
85
+ "nexarch_upsert_relationships",
86
+ "nexarch_emit_observations",
87
+ ];
88
+ const missing = required.filter((name) => !toolNames.has(name));
89
+ checks.push({
90
+ name: "mcp.tools.list",
91
+ ok: missing.length === 0,
92
+ detail: missing.length ? `missing: ${missing.join(", ")}` : `required tools present (${required.length})`,
93
+ });
94
+ const policiesRaw = await callMcpTool("nexarch_get_applied_policies", {});
95
+ const policies = parseToolText(policiesRaw);
96
+ checks.push({
97
+ name: "governance.bootstrap",
98
+ ok: Boolean(policies.policyBundleHash),
99
+ detail: policies.policyBundleHash
100
+ ? `policyBundleHash=${policies.policyBundleHash.slice(0, 12)}… policyCount=${policies.policyCount ?? 0}`
101
+ : "missing policyBundleHash",
102
+ });
103
+ const contractRaw = await callMcpTool("nexarch_get_ingest_contract", {});
104
+ const contract = parseToolText(contractRaw);
105
+ checks.push({
106
+ name: "ingest.contract",
107
+ ok: Boolean(contract.contractVersion),
108
+ detail: `version=${contract.contractVersion ?? "unknown"}; entities=${contract.ontology?.entityCodes?.length ?? 0}; relationships=${contract.ontology?.relationshipCodes?.length ?? 0}`,
109
+ });
110
+ const hasAgentType = (contract.ontology?.entityCodes ?? []).some((code) => code.toLowerCase() === "agent");
111
+ checks.push({
112
+ name: "ontology.agent-type",
113
+ ok: hasAgentType,
114
+ detail: hasAgentType
115
+ ? "entity type 'agent' is enabled"
116
+ : "entity type 'agent' is not enabled for this workspace ontology",
117
+ });
118
+ const preflightPassed = checks.every((c) => c.ok);
119
+ let registration = {
120
+ ok: false,
121
+ detail: "registration not attempted",
122
+ };
123
+ let binding = {
124
+ attempted: false,
125
+ ok: true,
126
+ detail: "not requested",
127
+ };
128
+ if (preflightPassed && policies.policyBundleHash) {
129
+ const nowIso = new Date().toISOString();
130
+ const upsertRaw = await callMcpTool("nexarch_upsert_entities", {
131
+ entities: [
132
+ {
133
+ externalKey: `agent:${agentId}`,
134
+ entityTypeCode: "agent",
135
+ name: `Agent ${agentId}`,
136
+ description: "Agent identity registered from nexarch init-agent handshake",
137
+ confidence: 1,
138
+ attributes: {
139
+ kind: "ai_agent",
140
+ client: "nexarch-cli",
141
+ version: CLI_VERSION,
142
+ companyId: creds.companyId,
143
+ handshake: {
144
+ completedAt: nowIso,
145
+ source: "init-agent",
146
+ },
147
+ },
148
+ },
149
+ ],
150
+ agentContext: {
151
+ agentId,
152
+ agentRunId: `init-agent-${Date.now()}`,
153
+ repoRef: "nexarch-cli/init-agent",
154
+ observedAt: nowIso,
155
+ source: "nexarch-cli",
156
+ model: "n/a",
157
+ provider: "n/a",
158
+ },
159
+ policyContext: {
160
+ policyBundleHash: policies.policyBundleHash,
161
+ alignmentSummary: {
162
+ score: 1,
163
+ violations: [],
164
+ waivers: [],
165
+ },
166
+ },
167
+ dryRun: false,
168
+ });
169
+ const upsert = parseToolText(upsertRaw);
170
+ const firstResult = upsert.results?.[0];
171
+ const failed = Number(upsert.summary?.failed ?? 0) > 0;
172
+ registration = {
173
+ ok: !failed,
174
+ detail: failed
175
+ ? `failed to register agent entry (${upsert.errors?.[0]?.error ?? "unknown"})`
176
+ : `agent entry ${firstResult?.action ?? "upserted"}`,
177
+ graphEntityId: firstResult?.graphEntityId ?? null,
178
+ action: firstResult?.action ?? null,
179
+ errors: upsert.errors,
180
+ };
181
+ if (!registration.ok) {
182
+ await emitInitObservation({
183
+ agentId,
184
+ policyBundleHash: policies.policyBundleHash,
185
+ message: `init-agent registration failed: ${registration.detail}`,
186
+ confidence: 1,
187
+ });
188
+ }
189
+ if (registration.ok && bindToExternalKey) {
190
+ binding.attempted = true;
191
+ const relRaw = await callMcpTool("nexarch_upsert_relationships", {
192
+ relationships: [
193
+ {
194
+ relationshipTypeCode: bindRelationshipType,
195
+ fromEntityExternalKey: `agent:${agentId}`,
196
+ toEntityExternalKey: bindToExternalKey,
197
+ confidence: 1,
198
+ attributes: {
199
+ source: "nexarch-cli-init-agent",
200
+ createdAt: nowIso,
201
+ },
202
+ },
203
+ ],
204
+ agentContext: {
205
+ agentId,
206
+ agentRunId: `init-agent-bind-${Date.now()}`,
207
+ repoRef: "nexarch-cli/init-agent",
208
+ observedAt: nowIso,
209
+ source: "nexarch-cli",
210
+ model: "n/a",
211
+ provider: "n/a",
212
+ },
213
+ policyContext: {
214
+ policyBundleHash: policies.policyBundleHash,
215
+ alignmentSummary: {
216
+ score: 1,
217
+ violations: [],
218
+ waivers: [],
219
+ },
220
+ },
221
+ dryRun: false,
222
+ });
223
+ const rel = parseToolText(relRaw);
224
+ const relResult = rel.results?.[0];
225
+ const relFailed = Number(rel.summary?.failed ?? 0) > 0;
226
+ binding = {
227
+ attempted: true,
228
+ ok: !relFailed,
229
+ detail: relFailed
230
+ ? `failed to bind agent (${rel.errors?.[0]?.error ?? "unknown"})`
231
+ : `agent bound via ${bindRelationshipType}`,
232
+ graphRelationshipId: relResult?.graphRelationshipId ?? null,
233
+ action: relResult?.action ?? null,
234
+ errors: rel.errors,
235
+ };
236
+ if (!binding.ok) {
237
+ await emitInitObservation({
238
+ agentId,
239
+ policyBundleHash: policies.policyBundleHash,
240
+ message: `init-agent binding failed: ${binding.detail}`,
241
+ confidence: 1,
242
+ });
243
+ }
244
+ }
245
+ }
246
+ checks.push({
247
+ name: "agent.registration",
248
+ ok: registration.ok,
249
+ detail: registration.detail,
250
+ });
251
+ if (binding.attempted) {
252
+ checks.push({
253
+ name: "agent.binding",
254
+ ok: binding.ok,
255
+ detail: binding.detail,
256
+ });
257
+ }
258
+ const allPassed = checks.every((c) => c.ok);
259
+ if (asJson) {
260
+ const output = {
261
+ ok: strict ? allPassed : true,
262
+ passed: allPassed,
263
+ strict,
264
+ checks,
265
+ summary: {
266
+ total: checks.length,
267
+ ok: checks.filter((c) => c.ok).length,
268
+ failed: checks.filter((c) => !c.ok).length,
269
+ },
270
+ policyBundleHash: policies.policyBundleHash ?? null,
271
+ contractVersion: contract.contractVersion ?? null,
272
+ agentId,
273
+ registration,
274
+ binding,
275
+ companyId: creds.companyId,
276
+ };
277
+ process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
278
+ if (strict && !allPassed)
279
+ process.exitCode = 1;
280
+ return;
281
+ }
282
+ console.log("Running agent onboarding handshake…\n");
283
+ for (const check of checks) {
284
+ console.log(`${check.ok ? "✓" : "✗"} ${check.name}`);
285
+ if (check.detail)
286
+ console.log(` ${check.detail}`);
287
+ }
288
+ if (registration.graphEntityId) {
289
+ console.log(`\nRegistered graph entity: ${registration.graphEntityId}`);
290
+ }
291
+ if (binding.attempted && binding.graphRelationshipId) {
292
+ console.log(`Bound relationship id: ${binding.graphRelationshipId}`);
293
+ }
294
+ console.log("");
295
+ if (allPassed) {
296
+ console.log("✅ Agent handshake + registration completed.");
297
+ }
298
+ else {
299
+ console.log("⚠ Handshake/registration completed with issues.");
300
+ if (strict) {
301
+ console.log("Strict mode enabled, exiting non-zero.");
302
+ process.exitCode = 1;
303
+ }
304
+ }
305
+ }
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { status } from "./commands/status.js";
5
5
  import { setup } from "./commands/setup.js";
6
6
  import { mcpConfig } from "./commands/mcp-config.js";
7
7
  import { mcpProxy } from "./commands/mcp-proxy.js";
8
+ import { initAgent } from "./commands/init-agent.js";
8
9
  const [, , command, ...args] = process.argv;
9
10
  const commands = {
10
11
  login,
@@ -13,6 +14,7 @@ const commands = {
13
14
  setup,
14
15
  "mcp-config": mcpConfig,
15
16
  "mcp-proxy": mcpProxy,
17
+ "init-agent": initAgent,
16
18
  };
17
19
  async function main() {
18
20
  const handler = commands[command ?? ""];
@@ -27,6 +29,9 @@ Usage:
27
29
  nexarch setup Auto-configure your MCP client (Claude Desktop, Cursor, etc.)
28
30
  nexarch mcp-config Print MCP server config block for manual setup
29
31
  nexarch mcp-proxy Run as stdio MCP proxy (used by MCP clients)
32
+ nexarch init-agent Run handshake + mandatory agent registration in graph
33
+ Options: --agent-id <id> --bind-to-external-key <key>
34
+ --bind-relationship-type <code> --json --strict
30
35
  `);
31
36
  process.exit(command ? 1 : 0);
32
37
  }
package/dist/lib/mcp.js CHANGED
@@ -2,13 +2,13 @@ import https from "https";
2
2
  import { requireCredentials } from "./credentials.js";
3
3
  const MCP_GATEWAY_URL = "https://mcp.nexarch.ai";
4
4
  const REQUEST_TIMEOUT_MS = 30 * 1000;
5
- export async function callMcpTool(toolName, toolArgs = {}) {
5
+ async function callMcpRpc(method, params = {}) {
6
6
  const creds = requireCredentials();
7
7
  const body = JSON.stringify({
8
8
  jsonrpc: "2.0",
9
9
  id: 1,
10
- method: "tools/call",
11
- params: { name: toolName, arguments: toolArgs },
10
+ method,
11
+ params,
12
12
  });
13
13
  return new Promise((resolve, reject) => {
14
14
  const url = new URL("/mcp", MCP_GATEWAY_URL);
@@ -19,7 +19,7 @@ export async function callMcpTool(toolName, toolArgs = {}) {
19
19
  method: "POST",
20
20
  headers: {
21
21
  "Content-Type": "application/json",
22
- "Authorization": `Bearer ${creds.token}`,
22
+ Authorization: `Bearer ${creds.token}`,
23
23
  "x-company-id": creds.companyId,
24
24
  "Content-Length": Buffer.byteLength(body),
25
25
  },
@@ -34,7 +34,7 @@ export async function callMcpTool(toolName, toolArgs = {}) {
34
34
  if (json.error) {
35
35
  reject(new Error(json.error.message));
36
36
  }
37
- else if (json.result) {
37
+ else if (json.result !== undefined) {
38
38
  resolve(json.result);
39
39
  }
40
40
  else {
@@ -54,6 +54,20 @@ export async function callMcpTool(toolName, toolArgs = {}) {
54
54
  req.end();
55
55
  });
56
56
  }
57
+ export async function callMcpTool(toolName, toolArgs = {}) {
58
+ return callMcpRpc("tools/call", { name: toolName, arguments: toolArgs });
59
+ }
60
+ export async function mcpInitialize() {
61
+ return callMcpRpc("initialize", {
62
+ protocolVersion: "2024-11-05",
63
+ capabilities: {},
64
+ clientInfo: { name: "nexarch-cli", version: "0.1.6" },
65
+ });
66
+ }
67
+ export async function mcpListTools() {
68
+ const result = await callMcpRpc("tools/list", {});
69
+ return result.tools ?? [];
70
+ }
57
71
  export async function forwardToGateway(token, companyId, body) {
58
72
  return new Promise((resolve, reject) => {
59
73
  const url = new URL("/mcp", MCP_GATEWAY_URL);
@@ -64,7 +78,7 @@ export async function forwardToGateway(token, companyId, body) {
64
78
  method: "POST",
65
79
  headers: {
66
80
  "Content-Type": "application/json",
67
- "Authorization": `Bearer ${token}`,
81
+ Authorization: `Bearer ${token}`,
68
82
  "x-company-id": companyId,
69
83
  "Content-Length": Buffer.byteLength(body),
70
84
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Connect AI coding tools to your Nexarch architecture workspace",
5
5
  "keywords": [
6
6
  "nexarch",