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 +9 -2
- package/dist/commands/agent-identify.js +151 -0
- package/dist/commands/init-agent.js +143 -11
- package/dist/commands/login.js +27 -5
- package/dist/commands/status.js +2 -3
- package/dist/index.js +37 -1
- package/dist/lib/mcp.js +10 -9
- package/package.json +1 -1
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.
|
|
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
|
|
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.");
|
package/dist/commands/login.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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({
|
|
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
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -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
|
|
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":
|
|
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.
|
|
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) {
|