nexarch 0.1.11 → 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.
@@ -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.11";
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) {
@@ -86,6 +88,83 @@ async function emitInitObservation(params) {
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");
@@ -481,6 +560,29 @@ export async function initAgent(args) {
481
560
  }
482
561
  }
483
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
+ }
484
586
  checks.push({
485
587
  name: "agent.registration",
486
588
  ok: registration.ok,
@@ -504,11 +606,17 @@ export async function initAgent(args) {
504
606
  });
505
607
  }
506
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");
507
614
  if (asJson) {
508
615
  const output = {
509
616
  ok: strict ? allPassed : true,
510
617
  passed: allPassed,
511
618
  strict,
619
+ status: allPassed ? "needs_agent_identity_followup" : "handshake_completed_with_issues",
512
620
  checks,
513
621
  summary: {
514
622
  total: checks.length,
@@ -525,7 +633,16 @@ export async function initAgent(args) {
525
633
  relationships: techRelationships,
526
634
  },
527
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
+ },
528
643
  companyId: creds.companyId,
644
+ bootstrapFile: bootstrapFilePath,
645
+ claudeMd: claudeMdResult ?? null,
529
646
  };
530
647
  process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
531
648
  if (strict && !allPassed)
@@ -547,6 +664,13 @@ export async function initAgent(args) {
547
664
  console.log("");
548
665
  if (allPassed) {
549
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\"");
550
674
  }
551
675
  else {
552
676
  console.log("⚠ Handshake/registration completed with issues.");
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,8 +19,27 @@ 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(`
@@ -34,6 +57,18 @@ Usage:
34
57
  Options: --agent-id <id> --bind-to-external-key <key>
35
58
  --bind-relationship-type <code> --redact-hostname
36
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'
37
72
  `);
38
73
  process.exit(command ? 1 : 0);
39
74
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Connect AI coding tools to your Nexarch architecture workspace",
5
5
  "keywords": [
6
6
  "nexarch",