nexarch 0.1.11 → 0.1.13

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.13";
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,92 @@ 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
+ `npx 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 "<run claude --version to get this, e.g. 1.2.3>"`,
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** — the version of the client tool you are running inside; run \`claude --version\` (or equivalent) to get it and use the version number only (e.g. \`1.2.3\`)
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 \`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
+ function injectAgentConfigs() {
155
+ const candidateNames = [
156
+ "CLAUDE.md", "claude.md", "Claude.md", // Claude Code
157
+ "AGENTS.md", "agents.md", // OpenAI Codex CLI
158
+ ".github/copilot-instructions.md", // GitHub Copilot
159
+ ".cursorrules", // Cursor (legacy)
160
+ ];
161
+ const results = [];
162
+ for (const name of candidateNames) {
163
+ const filePath = join(process.cwd(), name);
164
+ if (!existsSync(filePath))
165
+ continue;
166
+ const existing = readFileSync(filePath, "utf8");
167
+ if (existing.includes(CLAUDE_MD_SECTION_MARKER)) {
168
+ results.push({ path: filePath, status: "already_present" });
169
+ continue;
170
+ }
171
+ const separator = existing.endsWith("\n") ? "" : "\n";
172
+ writeFileSync(filePath, existing + separator + CLAUDE_MD_SECTION, "utf8");
173
+ results.push({ path: filePath, status: "injected" });
174
+ }
175
+ return results;
176
+ }
89
177
  export async function initAgent(args) {
90
178
  const asJson = parseFlag(args, "--json");
91
179
  const strict = parseFlag(args, "--strict");
@@ -481,6 +569,29 @@ export async function initAgent(args) {
481
569
  }
482
570
  }
483
571
  }
572
+ let bootstrapFilePath = null;
573
+ let agentConfigResults = [];
574
+ if (registration.ok) {
575
+ try {
576
+ bootstrapFilePath = writeAgentBootstrap({
577
+ agentId,
578
+ hostname: runtime.hostname,
579
+ osPlatform: runtime.osPlatform,
580
+ osRelease: runtime.osRelease,
581
+ nodeVersion: runtime.nodeVersion,
582
+ arch: runtime.arch,
583
+ });
584
+ }
585
+ catch {
586
+ // non-fatal
587
+ }
588
+ try {
589
+ agentConfigResults = injectAgentConfigs();
590
+ }
591
+ catch {
592
+ // non-fatal
593
+ }
594
+ }
484
595
  checks.push({
485
596
  name: "agent.registration",
486
597
  ok: registration.ok,
@@ -504,11 +615,17 @@ export async function initAgent(args) {
504
615
  });
505
616
  }
506
617
  const allPassed = checks.every((c) => c.ok);
618
+ const identifyPrompt = [
619
+ "Please identify your runtime so the workspace can register richer metadata.",
620
+ "Return ONLY the completed command below with real values (no markdown):",
621
+ `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`,
622
+ ].join("\n");
507
623
  if (asJson) {
508
624
  const output = {
509
625
  ok: strict ? allPassed : true,
510
626
  passed: allPassed,
511
627
  strict,
628
+ status: allPassed ? "needs_agent_identity_followup" : "handshake_completed_with_issues",
512
629
  checks,
513
630
  summary: {
514
631
  total: checks.length,
@@ -525,7 +642,16 @@ export async function initAgent(args) {
525
642
  relationships: techRelationships,
526
643
  },
527
644
  binding,
645
+ followUp: {
646
+ required: allPassed,
647
+ type: "agent_identity",
648
+ detail: "Prompt the coding agent to self-identify and submit via nexarch agent identify",
649
+ prompt: identifyPrompt,
650
+ 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`,
651
+ },
528
652
  companyId: creds.companyId,
653
+ bootstrapFile: bootstrapFilePath,
654
+ agentConfigs: agentConfigResults,
529
655
  };
530
656
  process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
531
657
  if (strict && !allPassed)
@@ -547,6 +673,15 @@ export async function initAgent(args) {
547
673
  console.log("");
548
674
  if (allPassed) {
549
675
  console.log("āœ… Agent handshake + registration completed.");
676
+ if (bootstrapFilePath) {
677
+ console.log(`\nšŸ“‹ Bootstrap instructions written to: ${bootstrapFilePath}`);
678
+ }
679
+ for (const r of agentConfigResults) {
680
+ if (r.status === "injected") {
681
+ console.log(`šŸ“ Nexarch registration instructions added to: ${r.path}`);
682
+ }
683
+ }
684
+ console.log("\n Tell Claude: \"run npx nexarch agent prompt and follow the instructions\"");
550
685
  }
551
686
  else {
552
687
  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 npx 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.13",
4
4
  "description": "Connect AI coding tools to your Nexarch architecture workspace",
5
5
  "keywords": [
6
6
  "nexarch",