nexarch 0.1.8 → 0.1.12

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
@@ -13,9 +13,9 @@ npx nexarch setup
13
13
 
14
14
  | Command | Description |
15
15
  |---|---|
16
- | `nexarch login` | Authenticate via browser and store credentials |
16
+ | `nexarch login [--company <id>]` | Authenticate via browser and store company-scoped credentials; prompts for company when multiple memberships exist |
17
17
  | `nexarch logout` | Remove local credentials |
18
- | `nexarch status` | Check connection and show architecture summary |
18
+ | `nexarch status` | Check connection and show architecture summary for the company selected at login |
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 |
@@ -23,6 +23,13 @@ npx nexarch setup
23
23
  | `nexarch init-agent --bind-to-external-key <key> [--bind-relationship-type <code>]` | Optionally bind the agent node to an existing graph external key |
24
24
  | `nexarch init-agent` (default behavior) | Also upserts technology component entities (host, OS, Node.js runtime) and links them to the agent |
25
25
 
26
+ ## Company context behavior
27
+
28
+ - Company is selected during `nexarch login`.
29
+ - If your user has one active company, login auto-selects it.
30
+ - If your user has multiple active companies, login requires explicit selection.
31
+ - To switch company later, run `nexarch login` again and pick another company.
32
+
26
33
  ## Requirements
27
34
 
28
35
  - Node.js 18 or later
@@ -0,0 +1,151 @@
1
+ import process from "process";
2
+ import { requireCredentials } from "../lib/credentials.js";
3
+ import { callMcpTool, mcpInitialize, mcpListTools } from "../lib/mcp.js";
4
+ function parseFlag(args, flag) {
5
+ return args.includes(flag);
6
+ }
7
+ function parseOptionValue(args, option) {
8
+ const idx = args.indexOf(option);
9
+ if (idx === -1)
10
+ return null;
11
+ const value = args[idx + 1];
12
+ if (!value || value.startsWith("--"))
13
+ return null;
14
+ return value;
15
+ }
16
+ function parseToolText(result) {
17
+ const text = result.content?.[0]?.text ?? "{}";
18
+ return JSON.parse(text);
19
+ }
20
+ function getDefaultAgentId() {
21
+ const osUser = process.env.USERNAME || process.env.USER || "unknown";
22
+ const host = process.env.HOSTNAME || "unknown-host";
23
+ return `nexarch-cli:${osUser}@${host}`;
24
+ }
25
+ function parseCsv(value) {
26
+ if (!value)
27
+ return [];
28
+ return value
29
+ .split(",")
30
+ .map((v) => v.trim())
31
+ .filter(Boolean);
32
+ }
33
+ export async function agentIdentify(args) {
34
+ const asJson = parseFlag(args, "--json");
35
+ const agentId = parseOptionValue(args, "--agent-id") ?? getDefaultAgentId();
36
+ const provider = parseOptionValue(args, "--provider");
37
+ const model = parseOptionValue(args, "--model");
38
+ const client = parseOptionValue(args, "--client");
39
+ const sessionId = parseOptionValue(args, "--session-id");
40
+ const framework = parseOptionValue(args, "--framework");
41
+ const toolVersion = parseOptionValue(args, "--tool-version");
42
+ const capabilities = parseCsv(parseOptionValue(args, "--capabilities"));
43
+ const notes = parseOptionValue(args, "--notes");
44
+ if (!provider || !model || !client) {
45
+ throw new Error("agent-identify requires --provider, --model, and --client");
46
+ }
47
+ const creds = requireCredentials();
48
+ await mcpInitialize({ companyId: creds.companyId });
49
+ const tools = await mcpListTools({ companyId: creds.companyId });
50
+ const toolNames = new Set(tools.map((t) => t.name));
51
+ const required = ["nexarch_upsert_entities"];
52
+ const missing = required.filter((name) => !toolNames.has(name));
53
+ if (missing.length) {
54
+ throw new Error(`missing required MCP tools: ${missing.join(", ")}`);
55
+ }
56
+ let policyBundleHash = null;
57
+ if (toolNames.has("nexarch_get_applied_policies")) {
58
+ try {
59
+ const policiesRaw = await callMcpTool("nexarch_get_applied_policies", {}, { companyId: creds.companyId });
60
+ const policies = parseToolText(policiesRaw);
61
+ policyBundleHash = policies.policyBundleHash ?? null;
62
+ }
63
+ catch {
64
+ policyBundleHash = null;
65
+ }
66
+ }
67
+ const nowIso = new Date().toISOString();
68
+ const upsertRaw = await callMcpTool("nexarch_upsert_entities", {
69
+ entities: [
70
+ {
71
+ externalKey: `agent:${agentId}`,
72
+ entityTypeCode: "agent",
73
+ name: `Agent ${agentId}`,
74
+ confidence: 1,
75
+ attributes: {
76
+ identity: {
77
+ source: "nexarch agent identify",
78
+ capturedAt: nowIso,
79
+ provider,
80
+ model,
81
+ client,
82
+ sessionId,
83
+ framework,
84
+ toolVersion,
85
+ capabilities,
86
+ notes,
87
+ },
88
+ },
89
+ },
90
+ ],
91
+ agentContext: {
92
+ agentId,
93
+ agentRunId: `agent-identify-${Date.now()}`,
94
+ repoRef: "nexarch-cli/agent-identify",
95
+ observedAt: nowIso,
96
+ source: "nexarch-cli",
97
+ model,
98
+ provider,
99
+ },
100
+ policyContext: policyBundleHash
101
+ ? {
102
+ policyBundleHash,
103
+ alignmentSummary: {
104
+ score: 1,
105
+ violations: [],
106
+ waivers: [],
107
+ },
108
+ }
109
+ : undefined,
110
+ dryRun: false,
111
+ }, { companyId: creds.companyId });
112
+ const upsert = parseToolText(upsertRaw);
113
+ const failed = Number(upsert.summary?.failed ?? 0) > 0;
114
+ const firstResult = upsert.results?.[0];
115
+ const output = {
116
+ ok: !failed,
117
+ detail: failed ? `failed (${upsert.errors?.[0]?.error ?? "unknown"})` : `agent ${firstResult?.action ?? "updated"}`,
118
+ agentId,
119
+ graphEntityId: firstResult?.graphEntityId ?? null,
120
+ identity: {
121
+ provider,
122
+ model,
123
+ client,
124
+ sessionId,
125
+ framework,
126
+ toolVersion,
127
+ capabilities,
128
+ notes,
129
+ capturedAt: nowIso,
130
+ },
131
+ };
132
+ if (asJson) {
133
+ process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
134
+ if (!output.ok)
135
+ process.exitCode = 1;
136
+ return;
137
+ }
138
+ if (output.ok) {
139
+ console.log("✅ Agent identity captured.");
140
+ console.log(`Agent: ${agentId}`);
141
+ console.log(`Provider/Model: ${provider} / ${model}`);
142
+ console.log(`Client: ${client}`);
143
+ if (framework)
144
+ console.log(`Framework: ${framework}`);
145
+ }
146
+ else {
147
+ console.log("⚠ Agent identity capture failed.");
148
+ console.log(output.detail);
149
+ process.exitCode = 1;
150
+ }
151
+ }
@@ -1,8 +1,10 @@
1
- import { arch, hostname, platform, release, type as osType, userInfo } from "os";
1
+ import { arch, homedir, hostname, platform, release, type as osType, userInfo } from "os";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
3
+ import { join } from "path";
2
4
  import process from "process";
3
5
  import { requireCredentials } from "../lib/credentials.js";
4
6
  import { callMcpTool, mcpInitialize, mcpListTools } from "../lib/mcp.js";
5
- const CLI_VERSION = "0.1.8";
7
+ const CLI_VERSION = "0.1.12";
6
8
  const AGENT_ENTITY_TYPE = "agent";
7
9
  const TECH_COMPONENT_ENTITY_TYPE = "technology_component";
8
10
  function parseFlag(args, flag) {
@@ -80,12 +82,89 @@ async function emitInitObservation(params) {
80
82
  waivers: [],
81
83
  },
82
84
  },
83
- });
85
+ }, { companyId: params.companyId });
84
86
  }
85
87
  catch {
86
88
  // non-fatal telemetry path
87
89
  }
88
90
  }
91
+ function writeAgentBootstrap(params) {
92
+ const dir = join(homedir(), ".nexarch");
93
+ mkdirSync(dir, { recursive: true });
94
+ const filePath = join(dir, "agent-bootstrap.md");
95
+ const identifyCmd = [
96
+ `nexarch agent identify`,
97
+ `--agent-id "${params.agentId}"`,
98
+ `--provider "<anthropic|openai|google|other>"`,
99
+ `--model "<model-id e.g. claude-sonnet-4-6>"`,
100
+ `--client "<claude-code|cursor|copilot|other>"`,
101
+ `--framework "<openclaw|n8n|m365-agent-framework|other>"`,
102
+ `--session-id "<your current session id if known, otherwise omit>"`,
103
+ `--tool-version "<your tool version if known, otherwise omit>"`,
104
+ `--capabilities "<comma,separated,capabilities>"`,
105
+ `--json`,
106
+ ].join(" \\\n ");
107
+ const content = `# Nexarch Agent Bootstrap
108
+
109
+ This file was written by \`nexarch init-agent\` on ${new Date().toISOString()}.
110
+ It contains instructions for a Claude Code agent to complete its Nexarch registration.
111
+
112
+ ## What has already been registered
113
+
114
+ The following entities are now in the architecture graph:
115
+
116
+ | Entity | Name | Type |
117
+ |--------|------|------|
118
+ | Agent | \`${params.agentId}\` | agent |
119
+ | Host | \`Host ${params.hostname}\` | tech_compute |
120
+ | OS | \`${params.osPlatform} ${params.osRelease}\` | tech_operating_system |
121
+ | Runtime | \`Node.js ${params.nodeVersion}\` | tech_runtime |
122
+
123
+ ## Your task
124
+
125
+ Run the command below, substituting your own values for the placeholders.
126
+ Return ONLY the completed command (no markdown, no explanation) — it will be executed directly.
127
+
128
+ \`\`\`
129
+ ${identifyCmd}
130
+ \`\`\`
131
+
132
+ Fill in:
133
+ - **provider** — your AI provider (e.g. \`anthropic\`)
134
+ - **model** — the exact model ID you are running as (e.g. \`claude-sonnet-4-6\`)
135
+ - **client** — the tool you are running inside (e.g. \`claude-code\`)
136
+ - **framework** — the agent framework in use (e.g. \`openclaw\`)
137
+ - **session-id** — your current session ID if available
138
+ - **tool-version** — your tool version if available
139
+ - **capabilities** — comma-separated list of your capabilities (e.g. \`code,files,bash,web\`)
140
+
141
+ Omit any flag whose value you genuinely do not know rather than guessing.
142
+
143
+ After running, confirm \`"ok": true\` in the JSON output. No further action is needed.
144
+ `;
145
+ writeFileSync(filePath, content, "utf8");
146
+ return filePath;
147
+ }
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 will tell you to run \`nexarch agent prompt\`. Do so, read the output, and follow the instructions — it contains a pre-filled \`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
+ function injectClaudeMd() {
155
+ const candidates = ["CLAUDE.md", "claude.md", "Claude.md"];
156
+ const found = candidates.find((name) => existsSync(join(process.cwd(), name)));
157
+ if (!found)
158
+ return null;
159
+ const filePath = join(process.cwd(), found);
160
+ const existing = readFileSync(filePath, "utf8");
161
+ if (existing.includes(CLAUDE_MD_SECTION_MARKER)) {
162
+ return { path: filePath, status: "already_present" };
163
+ }
164
+ const separator = existing.endsWith("\n") ? "" : "\n";
165
+ writeFileSync(filePath, existing + separator + CLAUDE_MD_SECTION, "utf8");
166
+ return { path: filePath, status: "injected" };
167
+ }
89
168
  export async function initAgent(args) {
90
169
  const asJson = parseFlag(args, "--json");
91
170
  const strict = parseFlag(args, "--strict");
@@ -105,13 +184,14 @@ export async function initAgent(args) {
105
184
  };
106
185
  const checks = [];
107
186
  const creds = requireCredentials();
108
- const init = await mcpInitialize();
187
+ const selectedCompanyId = creds.companyId;
188
+ const init = await mcpInitialize({ companyId: selectedCompanyId });
109
189
  checks.push({
110
190
  name: "mcp.initialize",
111
191
  ok: true,
112
192
  detail: `${init.serverInfo?.name ?? "unknown"}@${init.serverInfo?.version ?? "unknown"}`,
113
193
  });
114
- const tools = await mcpListTools();
194
+ const tools = await mcpListTools({ companyId: selectedCompanyId });
115
195
  const toolNames = new Set(tools.map((t) => t.name));
116
196
  const required = [
117
197
  "nexarch_get_applied_policies",
@@ -126,7 +206,7 @@ export async function initAgent(args) {
126
206
  ok: missing.length === 0,
127
207
  detail: missing.length ? `missing: ${missing.join(", ")}` : `required tools present (${required.length})`,
128
208
  });
129
- const policiesRaw = await callMcpTool("nexarch_get_applied_policies", {});
209
+ const policiesRaw = await callMcpTool("nexarch_get_applied_policies", {}, { companyId: selectedCompanyId });
130
210
  const policies = parseToolText(policiesRaw);
131
211
  checks.push({
132
212
  name: "governance.bootstrap",
@@ -135,7 +215,7 @@ export async function initAgent(args) {
135
215
  ? `policyBundleHash=${policies.policyBundleHash.slice(0, 12)}… policyCount=${policies.policyCount ?? 0}`
136
216
  : "missing policyBundleHash",
137
217
  });
138
- const contractRaw = await callMcpTool("nexarch_get_ingest_contract", {});
218
+ const contractRaw = await callMcpTool("nexarch_get_ingest_contract", {}, { companyId: selectedCompanyId });
139
219
  const contract = parseToolText(contractRaw);
140
220
  checks.push({
141
221
  name: "ingest.contract",
@@ -243,7 +323,7 @@ export async function initAgent(args) {
243
323
  },
244
324
  },
245
325
  dryRun: false,
246
- });
326
+ }, { companyId: selectedCompanyId });
247
327
  const upsert = parseToolText(upsertRaw);
248
328
  const firstResult = upsert.results?.[0];
249
329
  const failed = Number(upsert.summary?.failed ?? 0) > 0;
@@ -259,6 +339,7 @@ export async function initAgent(args) {
259
339
  if (!registration.ok) {
260
340
  await emitInitObservation({
261
341
  agentId,
342
+ companyId: selectedCompanyId,
262
343
  policyBundleHash: policies.policyBundleHash,
263
344
  message: `init-agent registration failed: ${registration.detail}`,
264
345
  confidence: 1,
@@ -274,6 +355,7 @@ export async function initAgent(args) {
274
355
  {
275
356
  externalKey: hostExternalKey,
276
357
  entityTypeCode: TECH_COMPONENT_ENTITY_TYPE,
358
+ entitySubtypeCode: "tech_compute",
277
359
  name: `Host ${runtime.hostname}`,
278
360
  description: "Execution host for registered agent runtimes",
279
361
  confidence: 0.95,
@@ -288,6 +370,7 @@ export async function initAgent(args) {
288
370
  {
289
371
  externalKey: osExternalKey,
290
372
  entityTypeCode: TECH_COMPONENT_ENTITY_TYPE,
373
+ entitySubtypeCode: "tech_operating_system",
291
374
  name: `${runtime.osPlatform} ${runtime.osRelease}`,
292
375
  description: "Operating system platform used by registered agents",
293
376
  confidence: 0.95,
@@ -302,6 +385,7 @@ export async function initAgent(args) {
302
385
  {
303
386
  externalKey: nodeExternalKey,
304
387
  entityTypeCode: TECH_COMPONENT_ENTITY_TYPE,
388
+ entitySubtypeCode: "tech_runtime",
305
389
  name: `Node.js ${runtime.nodeVersion}`,
306
390
  description: "Node.js runtime used by nexarch-cli agent",
307
391
  confidence: 0.95,
@@ -330,7 +414,7 @@ export async function initAgent(args) {
330
414
  },
331
415
  },
332
416
  dryRun: false,
333
- });
417
+ }, { companyId: selectedCompanyId });
334
418
  const techUpsert = parseToolText(techUpsertRaw);
335
419
  const techFailed = Number(techUpsert.summary?.failed ?? 0) > 0;
336
420
  techComponents = {
@@ -345,6 +429,7 @@ export async function initAgent(args) {
345
429
  if (!techComponents.ok) {
346
430
  await emitInitObservation({
347
431
  agentId,
432
+ companyId: selectedCompanyId,
348
433
  policyBundleHash: policies.policyBundleHash,
349
434
  message: `init-agent technology component upsert failed: ${techComponents.detail}`,
350
435
  confidence: 1,
@@ -394,7 +479,7 @@ export async function initAgent(args) {
394
479
  },
395
480
  },
396
481
  dryRun: false,
397
- });
482
+ }, { companyId: selectedCompanyId });
398
483
  const relUpsert = parseToolText(techRelRaw);
399
484
  const relFailed = Number(relUpsert.summary?.failed ?? 0) > 0;
400
485
  techRelationships = {
@@ -409,6 +494,7 @@ export async function initAgent(args) {
409
494
  if (!techRelationships.ok) {
410
495
  await emitInitObservation({
411
496
  agentId,
497
+ companyId: selectedCompanyId,
412
498
  policyBundleHash: policies.policyBundleHash,
413
499
  message: `init-agent technology relationship upsert failed: ${techRelationships.detail}`,
414
500
  confidence: 1,
@@ -449,7 +535,7 @@ export async function initAgent(args) {
449
535
  },
450
536
  },
451
537
  dryRun: false,
452
- });
538
+ }, { companyId: selectedCompanyId });
453
539
  const rel = parseToolText(relRaw);
454
540
  const relResult = rel.results?.[0];
455
541
  const relFailed = Number(rel.summary?.failed ?? 0) > 0;
@@ -466,6 +552,7 @@ export async function initAgent(args) {
466
552
  if (!binding.ok) {
467
553
  await emitInitObservation({
468
554
  agentId,
555
+ companyId: selectedCompanyId,
469
556
  policyBundleHash: policies.policyBundleHash,
470
557
  message: `init-agent binding failed: ${binding.detail}`,
471
558
  confidence: 1,
@@ -473,6 +560,29 @@ export async function initAgent(args) {
473
560
  }
474
561
  }
475
562
  }
563
+ let bootstrapFilePath = null;
564
+ let claudeMdResult = null;
565
+ if (registration.ok) {
566
+ try {
567
+ bootstrapFilePath = writeAgentBootstrap({
568
+ agentId,
569
+ hostname: runtime.hostname,
570
+ osPlatform: runtime.osPlatform,
571
+ osRelease: runtime.osRelease,
572
+ nodeVersion: runtime.nodeVersion,
573
+ arch: runtime.arch,
574
+ });
575
+ }
576
+ catch {
577
+ // non-fatal
578
+ }
579
+ try {
580
+ claudeMdResult = injectClaudeMd();
581
+ }
582
+ catch {
583
+ // non-fatal
584
+ }
585
+ }
476
586
  checks.push({
477
587
  name: "agent.registration",
478
588
  ok: registration.ok,
@@ -496,11 +606,17 @@ export async function initAgent(args) {
496
606
  });
497
607
  }
498
608
  const allPassed = checks.every((c) => c.ok);
609
+ const identifyPrompt = [
610
+ "Please identify your runtime so the workspace can register richer metadata.",
611
+ "Return ONLY the completed command below with real values (no markdown):",
612
+ `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`,
613
+ ].join("\n");
499
614
  if (asJson) {
500
615
  const output = {
501
616
  ok: strict ? allPassed : true,
502
617
  passed: allPassed,
503
618
  strict,
619
+ status: allPassed ? "needs_agent_identity_followup" : "handshake_completed_with_issues",
504
620
  checks,
505
621
  summary: {
506
622
  total: checks.length,
@@ -517,7 +633,16 @@ export async function initAgent(args) {
517
633
  relationships: techRelationships,
518
634
  },
519
635
  binding,
636
+ followUp: {
637
+ required: allPassed,
638
+ type: "agent_identity",
639
+ detail: "Prompt the coding agent to self-identify and submit via nexarch agent identify",
640
+ prompt: identifyPrompt,
641
+ 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`,
642
+ },
520
643
  companyId: creds.companyId,
644
+ bootstrapFile: bootstrapFilePath,
645
+ claudeMd: claudeMdResult ?? null,
521
646
  };
522
647
  process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
523
648
  if (strict && !allPassed)
@@ -539,6 +664,13 @@ export async function initAgent(args) {
539
664
  console.log("");
540
665
  if (allPassed) {
541
666
  console.log("✅ Agent handshake + registration completed.");
667
+ if (bootstrapFilePath) {
668
+ console.log(`\n📋 Bootstrap instructions written to: ${bootstrapFilePath}`);
669
+ }
670
+ if (claudeMdResult?.status === "injected") {
671
+ console.log(`📝 Nexarch registration instructions added to: ${claudeMdResult.path}`);
672
+ }
673
+ console.log("\n Tell Claude: \"run nexarch agent prompt and follow the instructions\"");
542
674
  }
543
675
  else {
544
676
  console.log("⚠ Handshake/registration completed with issues.");
@@ -60,6 +60,8 @@ function waitForCallback(port, expectedState) {
60
60
  const state = url.searchParams.get("state");
61
61
  const token = url.searchParams.get("token");
62
62
  const companyId = url.searchParams.get("companyId");
63
+ const companyName = url.searchParams.get("companyName") ?? undefined;
64
+ const companyCode = url.searchParams.get("companyCode") ?? undefined;
63
65
  const error = url.searchParams.get("error");
64
66
  const html = (message) => `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Nexarch CLI</title>
65
67
  <style>body{font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;background:#f8fafc}
@@ -90,7 +92,7 @@ function waitForCallback(port, expectedState) {
90
92
  res.writeHead(200, { "Content-Type": "text/html" });
91
93
  res.end(html("Authorization successful! You can close this tab and return to your terminal."));
92
94
  server.close();
93
- settle(() => resolve({ token, companyId }));
95
+ settle(() => resolve({ token, companyId, companyName, companyCode }));
94
96
  });
95
97
  const timeout = setTimeout(() => {
96
98
  server.close();
@@ -103,18 +105,38 @@ function waitForCallback(port, expectedState) {
103
105
  });
104
106
  });
105
107
  }
106
- export async function login(_args) {
108
+ function getArgValue(args, flag) {
109
+ const idx = args.indexOf(flag);
110
+ if (idx === -1)
111
+ return null;
112
+ const v = args[idx + 1];
113
+ if (!v || v.startsWith("--"))
114
+ return null;
115
+ return v;
116
+ }
117
+ export async function login(args) {
107
118
  const state = generateState();
108
119
  const port = await findFreePort();
109
- const authUrl = `${NEXARCH_URL}/auth/cli?port=${port}&state=${state}`;
120
+ const requestedCompany = getArgValue(args, "--company");
121
+ const qp = new URLSearchParams({ port: String(port), state });
122
+ if (requestedCompany)
123
+ qp.set("company", requestedCompany);
124
+ const authUrl = `${NEXARCH_URL}/auth/cli?${qp.toString()}`;
110
125
  console.log("Opening Nexarch in your browser…");
111
126
  console.log(`\n ${authUrl}\n`);
112
127
  console.log("If the browser did not open, copy the URL above and paste it in manually.\n");
113
128
  openBrowser(authUrl);
114
- const { token, companyId } = await waitForCallback(port, state);
129
+ const { token, companyId, companyName, companyCode } = await waitForCallback(port, state);
115
130
  const expiresAt = new Date();
116
131
  expiresAt.setDate(expiresAt.getDate() + 90);
117
- saveCredentials({ token, email: "", company: "", companyId, expiresAt: expiresAt.toISOString() });
132
+ saveCredentials({
133
+ token,
134
+ email: "",
135
+ company: companyName ?? companyCode ?? "",
136
+ companyCode: companyCode ?? undefined,
137
+ companyId,
138
+ expiresAt: expiresAt.toISOString(),
139
+ });
118
140
  console.log("✓ Logged in successfully");
119
141
  console.log("\nRun `nexarch status` to verify your connection.");
120
142
  }
@@ -2,14 +2,13 @@ import { requireCredentials } from "../lib/credentials.js";
2
2
  import { callMcpTool } from "../lib/mcp.js";
3
3
  export async function status(_args) {
4
4
  const creds = requireCredentials();
5
+ const selectedCompanyId = creds.companyId;
5
6
  const expiresAt = new Date(creds.expiresAt);
6
7
  const daysLeft = Math.ceil((expiresAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
7
8
  process.stdout.write("Connecting to mcp.nexarch.ai… ");
8
9
  let governance;
9
10
  try {
10
- const result = await callMcpTool("nexarch_get_governance_summary", {
11
- companyId: creds.companyId || undefined,
12
- });
11
+ const result = await callMcpTool("nexarch_get_governance_summary", { companyId: selectedCompanyId || undefined }, { companyId: selectedCompanyId || undefined });
13
12
  governance = JSON.parse(result.content[0]?.text ?? "{}");
14
13
  }
15
14
  catch (err) {
package/dist/index.js CHANGED
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import { homedir } from "os";
3
+ import { readFileSync } from "fs";
4
+ import { join } from "path";
2
5
  import { login } from "./commands/login.js";
3
6
  import { logout } from "./commands/logout.js";
4
7
  import { status } from "./commands/status.js";
@@ -6,6 +9,7 @@ import { setup } from "./commands/setup.js";
6
9
  import { mcpConfig } from "./commands/mcp-config.js";
7
10
  import { mcpProxy } from "./commands/mcp-proxy.js";
8
11
  import { initAgent } from "./commands/init-agent.js";
12
+ import { agentIdentify } from "./commands/agent-identify.js";
9
13
  const [, , command, ...args] = process.argv;
10
14
  const commands = {
11
15
  login,
@@ -15,15 +19,35 @@ const commands = {
15
19
  "mcp-config": mcpConfig,
16
20
  "mcp-proxy": mcpProxy,
17
21
  "init-agent": initAgent,
22
+ "agent-identify": agentIdentify,
18
23
  };
19
24
  async function main() {
25
+ if (command === "agent") {
26
+ const [subcommand, ...subArgs] = args;
27
+ if (subcommand === "identify") {
28
+ await agentIdentify(subArgs);
29
+ return;
30
+ }
31
+ if (subcommand === "prompt") {
32
+ const filePath = join(homedir(), ".nexarch", "agent-bootstrap.md");
33
+ try {
34
+ process.stdout.write(readFileSync(filePath, "utf8") + "\n");
35
+ }
36
+ catch {
37
+ console.error("No bootstrap file found. Run 'nexarch init-agent' first.");
38
+ process.exit(1);
39
+ }
40
+ return;
41
+ }
42
+ }
20
43
  const handler = commands[command ?? ""];
21
44
  if (!handler) {
22
45
  console.log(`
23
46
  nexarch — Connect AI coding tools to your architecture workspace
24
47
 
25
48
  Usage:
26
- nexarch login Authenticate in the browser and store credentials
49
+ nexarch login Authenticate in browser and store company-scoped credentials
50
+ Option: --company <id>
27
51
  nexarch logout Remove stored credentials
28
52
  nexarch status Check connection and show architecture summary
29
53
  nexarch setup Auto-configure your MCP client (Claude Desktop, Cursor, etc.)
@@ -33,6 +57,18 @@ Usage:
33
57
  Options: --agent-id <id> --bind-to-external-key <key>
34
58
  --bind-relationship-type <code> --redact-hostname
35
59
  --json --strict
60
+ nexarch agent prompt
61
+ Print bootstrap instructions for a Claude Code agent to complete
62
+ its registration (written by init-agent). Tell Claude:
63
+ "run nexarch agent prompt and follow the instructions"
64
+ nexarch agent identify
65
+ Capture richer coding-agent identity metadata
66
+ Options: --agent-id <id> --provider <provider> --model <model>
67
+ --client <name> [--framework <name>] [--session-id <id>]
68
+ [--tool-version <v>] [--capabilities <csv>]
69
+ [--notes <text>] [--json]
70
+ nexarch agent-identify
71
+ Alias of 'nexarch agent identify'
36
72
  `);
37
73
  process.exit(command ? 1 : 0);
38
74
  }
package/dist/lib/mcp.js CHANGED
@@ -2,8 +2,9 @@ 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
- async function callMcpRpc(method, params = {}) {
5
+ async function callMcpRpc(method, params = {}, options = {}) {
6
6
  const creds = requireCredentials();
7
+ const companyId = options.companyId ?? creds.companyId;
7
8
  const body = JSON.stringify({
8
9
  jsonrpc: "2.0",
9
10
  id: 1,
@@ -20,7 +21,7 @@ async function callMcpRpc(method, params = {}) {
20
21
  headers: {
21
22
  "Content-Type": "application/json",
22
23
  Authorization: `Bearer ${creds.token}`,
23
- "x-company-id": creds.companyId,
24
+ "x-company-id": companyId,
24
25
  "Content-Length": Buffer.byteLength(body),
25
26
  },
26
27
  timeout: REQUEST_TIMEOUT_MS,
@@ -54,18 +55,18 @@ async function callMcpRpc(method, params = {}) {
54
55
  req.end();
55
56
  });
56
57
  }
57
- export async function callMcpTool(toolName, toolArgs = {}) {
58
- return callMcpRpc("tools/call", { name: toolName, arguments: toolArgs });
58
+ export async function callMcpTool(toolName, toolArgs = {}, options = {}) {
59
+ return callMcpRpc("tools/call", { name: toolName, arguments: toolArgs }, options);
59
60
  }
60
- export async function mcpInitialize() {
61
+ export async function mcpInitialize(options = {}) {
61
62
  return callMcpRpc("initialize", {
62
63
  protocolVersion: "2024-11-05",
63
64
  capabilities: {},
64
- clientInfo: { name: "nexarch-cli", version: "0.1.8" },
65
- });
65
+ clientInfo: { name: "nexarch-cli", version: "0.1.11" },
66
+ }, options);
66
67
  }
67
- export async function mcpListTools() {
68
- const result = await callMcpRpc("tools/list", {});
68
+ export async function mcpListTools(options = {}) {
69
+ const result = await callMcpRpc("tools/list", {}, options);
69
70
  return result.tools ?? [];
70
71
  }
71
72
  export async function forwardToGateway(token, companyId, body) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.1.8",
3
+ "version": "0.1.12",
4
4
  "description": "Connect AI coding tools to your Nexarch architecture workspace",
5
5
  "keywords": [
6
6
  "nexarch",