nexarch 0.12.22 → 0.12.25
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/dist/commands/enroll.js +87 -0
- package/dist/commands/init-agent.js +93 -46
- package/dist/commands/init-project-data.js +181 -0
- package/dist/commands/init-project.js +133 -4
- package/dist/commands/mcp-proxy.js +81 -14
- package/dist/commands/verify-trust.js +32 -1
- package/dist/index.js +26 -0
- package/dist/lib/data-project-detect.js +204 -0
- package/dist/lib/mcp.js +17 -2
- package/dist/lib/trust.js +9 -2
- package/dist/lib/update-check.js +109 -0
- package/package.json +36 -35
package/dist/lib/mcp.js
CHANGED
|
@@ -33,7 +33,13 @@ async function requestOnce(method, params, options) {
|
|
|
33
33
|
headers: {
|
|
34
34
|
"Content-Type": "application/json",
|
|
35
35
|
Authorization: `Bearer ${creds.token}`,
|
|
36
|
-
|
|
36
|
+
// Scoped tokens carry their company_id server-side (see
|
|
37
|
+
// auth.ts's resolveCompanyId) and never populate creds.companyId
|
|
38
|
+
// locally, so an empty header here is the expected normal case —
|
|
39
|
+
// sending it as a literal empty string, rather than omitting it,
|
|
40
|
+
// used to matter for legacy static tokens where the header is the
|
|
41
|
+
// only source of truth. Omit rather than send "".
|
|
42
|
+
...(companyId ? { "x-company-id": companyId } : {}),
|
|
37
43
|
"Content-Length": Buffer.byteLength(body),
|
|
38
44
|
},
|
|
39
45
|
timeout: REQUEST_TIMEOUT_MS,
|
|
@@ -93,7 +99,16 @@ async function callMcpRpc(method, params = {}, options = {}) {
|
|
|
93
99
|
throw lastError instanceof Error ? lastError : new Error(String(lastError ?? "MCP call failed"));
|
|
94
100
|
}
|
|
95
101
|
export async function callMcpTool(toolName, toolArgs = {}, options = {}) {
|
|
96
|
-
|
|
102
|
+
// Every gateway tool schema declares companyId as z.string().uuid().optional() —
|
|
103
|
+
// "optional" means absent, not empty. Call sites pass creds.companyId
|
|
104
|
+
// unconditionally, which is "" for a scoped token (see credentials.ts);
|
|
105
|
+
// sent as a literal "", that fails .uuid() and the tool call is refused
|
|
106
|
+
// outright instead of falling back to the header/token-derived company.
|
|
107
|
+
// Single choke point: every call site funnels through here.
|
|
108
|
+
const toolArguments = { ...toolArgs };
|
|
109
|
+
if (toolArguments.companyId === "")
|
|
110
|
+
delete toolArguments.companyId;
|
|
111
|
+
return callMcpRpc("tools/call", { name: toolName, arguments: toolArguments }, options);
|
|
97
112
|
}
|
|
98
113
|
export async function mcpInitialize(options = {}) {
|
|
99
114
|
return callMcpRpc("initialize", {
|
package/dist/lib/trust.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import https from "https";
|
|
2
2
|
import { requireCredentials } from "./credentials.js";
|
|
3
3
|
const MCP_GATEWAY_URL = "https://mcp.nexarch.ai";
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* `contentHash` (sha256 hex of the exact managed-section text being written)
|
|
6
|
+
* binds the signature to the instructions themselves, not just to the claim
|
|
7
|
+
* that Nexarch minted *a* token for this agent — see ADR-0112. Omit it only
|
|
8
|
+
* when there is no section body yet to hash.
|
|
9
|
+
*/
|
|
10
|
+
export async function requestTrustAttestation(agentId, contentHash) {
|
|
5
11
|
const creds = requireCredentials();
|
|
6
12
|
const body = JSON.stringify({
|
|
7
13
|
agentId,
|
|
8
|
-
scope: "
|
|
14
|
+
scope: "agent_config_write",
|
|
15
|
+
...(contentHash ? { contentHash } : {}),
|
|
9
16
|
});
|
|
10
17
|
return new Promise((resolve) => {
|
|
11
18
|
const url = new URL("/trust/attest", MCP_GATEWAY_URL);
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
4
|
+
import { cliVersion } from "./version.js";
|
|
5
|
+
/**
|
|
6
|
+
* Best-effort "a newer version exists" advisory (feature request #1).
|
|
7
|
+
*
|
|
8
|
+
* Checked once per day per machine, not on every invocation — a network call
|
|
9
|
+
* on every single command would make the CLI feel like it always has a
|
|
10
|
+
* network dependency, when almost nothing else about a status check does.
|
|
11
|
+
* The result is cached in the same ~/.nexarch directory as credentials.json,
|
|
12
|
+
* so most invocations skip the network entirely and this costs nothing.
|
|
13
|
+
*
|
|
14
|
+
* Kicked off at the very start of the process and only awaited (with a short
|
|
15
|
+
* cap) right before the CLI would otherwise exit, so it rides alongside
|
|
16
|
+
* whatever network calls the command itself is already making rather than
|
|
17
|
+
* adding its own visible delay.
|
|
18
|
+
*/
|
|
19
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
20
|
+
const FETCH_TIMEOUT_MS = 1500;
|
|
21
|
+
/** How long we're willing to wait for an in-flight check before giving up on printing it this run. */
|
|
22
|
+
const PRINT_GRACE_MS = 200;
|
|
23
|
+
function cacheDir() {
|
|
24
|
+
return join(homedir(), ".nexarch");
|
|
25
|
+
}
|
|
26
|
+
function cachePath() {
|
|
27
|
+
return join(cacheDir(), "update-check.json");
|
|
28
|
+
}
|
|
29
|
+
function readCache() {
|
|
30
|
+
try {
|
|
31
|
+
if (!existsSync(cachePath()))
|
|
32
|
+
return null;
|
|
33
|
+
const parsed = JSON.parse(readFileSync(cachePath(), "utf8"));
|
|
34
|
+
if (typeof parsed.lastCheckedAt !== "string" || typeof parsed.latestVersion !== "string")
|
|
35
|
+
return null;
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function writeCache(cache) {
|
|
43
|
+
try {
|
|
44
|
+
mkdirSync(cacheDir(), { recursive: true });
|
|
45
|
+
writeFileSync(cachePath(), JSON.stringify(cache), "utf8");
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Best effort — a cache write failure should never surface to the user.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function fetchLatestVersion() {
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch("https://registry.npmjs.org/nexarch/latest", { signal: controller.signal });
|
|
56
|
+
if (!res.ok)
|
|
57
|
+
return null;
|
|
58
|
+
const body = (await res.json());
|
|
59
|
+
return typeof body.version === "string" ? body.version : null;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Offline, registry down, DNS failure, timeout — all the same outcome: say nothing.
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Plain numeric semver compare — good enough for release versions, no pre-release handling needed. */
|
|
70
|
+
function isNewer(latest, current) {
|
|
71
|
+
const a = latest.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
72
|
+
const b = current.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
73
|
+
for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
|
|
74
|
+
const x = a[i] ?? 0;
|
|
75
|
+
const y = b[i] ?? 0;
|
|
76
|
+
if (x !== y)
|
|
77
|
+
return x > y;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Starts the check immediately; resolves to update info only when the
|
|
83
|
+
* installed copy is actually behind. Never rejects.
|
|
84
|
+
*/
|
|
85
|
+
export async function checkForUpdate() {
|
|
86
|
+
const current = cliVersion();
|
|
87
|
+
const cache = readCache();
|
|
88
|
+
const cacheIsFresh = cache && Date.now() - new Date(cache.lastCheckedAt).getTime() < CHECK_INTERVAL_MS;
|
|
89
|
+
const latest = cacheIsFresh ? cache.latestVersion : await fetchLatestVersion();
|
|
90
|
+
if (!cacheIsFresh && latest) {
|
|
91
|
+
writeCache({ lastCheckedAt: new Date().toISOString(), latestVersion: latest });
|
|
92
|
+
}
|
|
93
|
+
if (!latest || !isNewer(latest, current))
|
|
94
|
+
return null;
|
|
95
|
+
return { current, latest };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Awaits an in-flight check with a short grace period and prints the
|
|
99
|
+
* advisory to stderr if it's ready and outdated — stderr so it never
|
|
100
|
+
* contaminates a command's --json stdout for an agent caller parsing it.
|
|
101
|
+
*/
|
|
102
|
+
export async function printUpdateNoticeIfReady(pending) {
|
|
103
|
+
const timeout = new Promise((resolve) => setTimeout(() => resolve(null), PRINT_GRACE_MS));
|
|
104
|
+
const info = await Promise.race([pending, timeout]);
|
|
105
|
+
if (!info)
|
|
106
|
+
return;
|
|
107
|
+
process.stderr.write(`\nA newer version of nexarch is available: ${info.current} -> ${info.latest}\n` +
|
|
108
|
+
`Run with npx nexarch@latest to use it — npx always fetches latest, nothing to install.\n`);
|
|
109
|
+
}
|
package/package.json
CHANGED
|
@@ -1,35 +1,36 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "nexarch",
|
|
3
|
-
"version": "0.12.
|
|
4
|
-
"description": "Your architecture workspace for AI delivery.",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"nexarch",
|
|
7
|
-
"mcp",
|
|
8
|
-
"architecture",
|
|
9
|
-
"ai"
|
|
10
|
-
],
|
|
11
|
-
"license": "MIT",
|
|
12
|
-
"author": "Nexarch <hello@nexarch.ai>",
|
|
13
|
-
"homepage": "https://nexarch.ai",
|
|
14
|
-
"engines": {
|
|
15
|
-
"node": ">=18"
|
|
16
|
-
},
|
|
17
|
-
"type": "module",
|
|
18
|
-
"bin": {
|
|
19
|
-
"nexarch": "dist/index.js"
|
|
20
|
-
},
|
|
21
|
-
"files": [
|
|
22
|
-
"dist"
|
|
23
|
-
],
|
|
24
|
-
"scripts": {
|
|
25
|
-
"build": "tsc",
|
|
26
|
-
"prepublishOnly": "tsc",
|
|
27
|
-
"dev": "tsx src/index.ts",
|
|
28
|
-
"typecheck": "tsc --noEmit"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "nexarch",
|
|
3
|
+
"version": "0.12.25",
|
|
4
|
+
"description": "Your architecture workspace for AI delivery.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"nexarch",
|
|
7
|
+
"mcp",
|
|
8
|
+
"architecture",
|
|
9
|
+
"ai"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": "Nexarch <hello@nexarch.ai>",
|
|
13
|
+
"homepage": "https://nexarch.ai",
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"type": "module",
|
|
18
|
+
"bin": {
|
|
19
|
+
"nexarch": "dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"prepublishOnly": "tsc",
|
|
27
|
+
"dev": "tsx src/index.ts",
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"test": "tsx scripts/test-mcp-proxy-response.ts"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^22",
|
|
33
|
+
"tsx": "^4",
|
|
34
|
+
"typescript": "^5"
|
|
35
|
+
}
|
|
36
|
+
}
|